/*++

Copyright (c) 1991  Microsoft Corporation

Module Name:

    zipstub.c

Abstract:

    ZIP handling for non-routing nodes.

Author:

	Garth Conboy		initial coding
    Nikhil Kamkolkar    recoding/mpsafe

Revision History:

	GC - (06/29/92):  Added capability to find the default zone for a cable;
                      needed because we now have "defaultOrDesiredZone" as two
                      separate concepts.
    GC - (11/16/92):  Corrected above feature!

--*/
	
#define	ZIPGLBL
#include "atalk.h"

#if (Iam an AppleTalkRouter) and (IamNot an AppleTalkStack)
	#include "atpconst.h"
#endif

LOCAL
INCOMING_DDPHANDLER GetMyZoneReply, GetZoneListReply;

LOCAL
TimerHandler GetZoneInfoTimerExpired;

LOCAL
VOID SendZipPacketTo(ZipCompletionInfo completionInfo);




LONG
ZipPacketIn(
	APPLETALK_ERROR ErrorCode,
	ULONG UserData,
	int Port,
	APPLETALK_ADDRESS Source,
	long DestinationSocket,
	int ProtocolType,
	PUCHAR Datagram,
	int DatagramLength,
	APPLETALK_ADDRESS ActualDestination)
{
	int commandType;
	int index;
	int flags;
	USHORT netRangeStart, netRangeEnd;
	BOOLEAN	foundZone;
	int zoneLength, defaultZoneLength, multicastAddressLength;
	CHAR zone[MAXIMUM_ZONELENGTH + 1], defaultZone[MAXIMUM_ZONELENGTH + 1];
	CHAR multicastAddress[MAXIMUM_HARDWAREADDRESSLENGTH];
	PPORT_HANDLERS	portHandlers;

	PUCHAR	startOfPacket = Datagram;
	int	bytesAccepted = DatagramLength;
	int	logEventPlace = 0;
	
	// "Use" unneeded actual parameters.
	UNREFERENCED_PARAMETER(DestinationSocket);
	UNREFERENCED_PARAMETER(UserData);
	UNREFERENCED_PARAMETER(ActualDestination);
	
	// Only play if we've been asked nicely!
	if (ErrorCode == ATsocketClosed)
		return(0);
	else if (ErrorCode != ATnoError) {
		INTERNAL_ERROR(__LINE__ | __ZIPSTUB__, ErrorCode, NULL, 0);
		return(0);
	}

	//
	//	We are the handler for the non-router ZIP socket. We only have
	//	to care about ZIP Notifies (when a router changes our zone on us)
	//	and NetInfoReplies (when we are trying to find out info about the
	//	net)
	//
	//	Our sole purpose of existence is so we can find out if the zone we
	//	have as our defaultZone is valid for this network, and if not, to
	//	get the default zone from the router.
	//
	//	On a non-extended network ofcourse, there is only one zone on the net
	//	and that is '*'. So for a non-extended port, this routine has no
	//	reason to exist. It is in AarpForNodeOnPort (hence only extended networks)
	//	that this routine is set as a handler for the ZIP socket.
	//

	if (!AtalkVerifyPortDescriptorInterlocked(
			GET_PORTDESCRIPTOR(Port),
			__LINE__ | __ZIPSTUB__)) {

		return(0);
	}


	do {

		if (!GET_PORTDESCRIPTOR(Port)->ExtendedNetwork) {
			INTERNAL_ERROR(
				__LINE__ | __ZIPSTUB__,
				GET_PORTDESCRIPTOR(Port)->ExtendedNetwork,
				NULL,
				0);
	
			break;
		}

		//	Verify the datagram satisfies the length constraints
		//	Use the fact that both packets have the same format

		index = ZIP_ZONELENGTHOFFSET;
		if (DatagramLength < index + 1) {
			logEventPlace = __ZIPSTUB__ | __LINE__;
			break;
		}

		GETUCHAR2INT(Datagram + index, &zoneLength);
		index += (1 + zoneLength);

		//	Check if length will allow multicast address length to fit
		if (DatagramLength < index + 1) {
			logEventPlace = __ZIPSTUB__ | __LINE__;
			break;
		}

		GETUCHAR2INT(Datagram + index, &multicastAddressLength);
		index += (1 + multicastAddressLength);

		//	Check if length will allow final zone name length to fit
		if (DatagramLength < index + 1) {
			logEventPlace = __ZIPSTUB__ | __LINE__;
			break;
		}

		GETUCHAR2INT(Datagram + index, &defaultZoneLength);
		index += (1 + defaultZoneLength);

		//	Check if the datagram can fit in the expected zone name
		if (DatagramLength < index) {
			logEventPlace = __ZIPSTUB__ | __LINE__;
			break;
		}

		Datagram += ZIP_COMMANDOFFSET;		// Start from command offset

		GETUCHAR2INT(Datagram, &commandType);
		Datagram += 1;

		if (((ProtocolType != DDPPROTOCOL_ZIP) ||
			 ((commandType != ZIP_NOTIFYCOMMAND) &&
			  (commandType != ZIP_NETINFOREPLYCOMMAND)))
	
			||
	
			//	Dont care if this is not a reply packet, and we are
			//	in the process of trying to find out our default zone
			((GET_PORTDESCRIPTOR(Port)->LookingForDefaultZone) &&
			 (commandType != ZIP_NETINFOREPLYCOMMAND))
	
			||
	
			//	If we already know our default zone, or if the zone we
			//	have set for this node is valid, then we do not care
			//	about reply packets
			((!GET_PORTDESCRIPTOR(Port)->LookingForDefaultZone) &&
			 (commandType == ZIP_NETINFOREPLYCOMMAND) &&
			 (GET_PORTDESCRIPTOR(Port)->ThisZoneValid))) {

			bytesAccepted = 0;
			break;
		}
		
		portHandlers = &portSpecificInfo[GET_PORTDESCRIPTOR(Port)->PortType];
		
		//
		// 	Handle our two cases.  Decode both of them as if we have a net info
		//  reply. 'if' above will prevent anything other than ZIP_NOTIFYCOMMAND
		//	and ZIP_NETINFOREPLYCOMMAND from getting here. Both these packets have
		//	the same format.
		//
	
		GETUCHAR2INT(Datagram, &flags);
		Datagram += 1;

		//
		//	Get the network number range start/end values, only meaningful for
		//	GetNetInfo replies. Since Notifies will be really rare, we will do this
		//	useless piece of work for those also.
		//

		GETUSHORT2USHORT(Datagram, &netRangeStart);
		Datagram += 2;

		GETUSHORT2USHORT(Datagram, &netRangeEnd);
		Datagram += 2;

		//
		//	We already have the zone/multicast address lengths, and the packet is
		//	guaranteed to be holding all of them.
		//

		//	Skip the zone length, verify it is an ok value
		Datagram += 1;
		if (zoneLength > MAXIMUM_ZONELENGTH) {
			logEventPlace = __ZIPSTUB__ | __LINE__;
			break;
		}

		MoveMem(
			zone,
			Datagram,
			zoneLength);

		zone[zoneLength] = 0;

		//	Done with zone, skip it to get to the multicast address length
		Datagram += zoneLength;
		
		//
		// 	If we're requesting the zone name, make sure the response matches our
		//  request.  ZoneLength will be zero when we're looking for the default
		//  zone, so we won't do this test.
		//
		
		EnterCriticalSection(GLOBAL_ROUTING);
		if ((commandType == ZIP_NETINFOREPLYCOMMAND) && (zoneLength != 0)) {
			if (!CompareCaseInsensitive(
					zone,
					GET_PORTDESCRIPTOR(Port)->InitialDesiredZone)) {

				//	BUGBUG: Do we care to log this?
				LeaveCriticalSection(GLOBAL_ROUTING);
				break;
			}
		}
		
		// If we're a notify make sure we're in the zone that is being changed.
		if (commandType == ZIP_NOTIFYCOMMAND) {
			if ((GET_PORTDESCRIPTOR(Port)->ThisZoneValid) &&
				(!CompareCaseInsensitive(
					zone,
					GET_PORTDESCRIPTOR(Port)->ThisZone)) {

				LeaveCriticalSection(GLOBAL_ROUTING);
				break;
			}
		}
		
		// Skip multicast length
		Datagram += 1;

		if (multicastAddressLength != portHandlers->HardwareAddressLength) {
			logEventPlace = __ZIPSTUB__ | __LINE__;
			LeaveCriticalSection(GLOBAL_ROUTING);
			break;
		}

		MoveMem(
			multicastAddress,
			Datagram,
			multicastAddressLength);

		Datagram += multicastAddressLength;
		if (flags & ZIP_USEBROADCASTFLAG)
			MoveMem(
				multicastAddress,
				portHandlers->BroadcastAddress,
				multicastAddressLength);

		
		// Grab second zone name, if needed or present.
		if (commandType == ZIP_NOTIFYCOMMAND || defaultZoneLength > 0) {
			if ((defaultZoneLength == 0) ||
				(defaultZoneLength > MAXIMUM_ZONELENGTH)) {

				logEventPlace = __ZIPSTUB__ | __LINE__;
				LeaveCriticalSection(GLOBAL_ROUTING);
				break;
			}

			MoveMem(
				defaultZone,
				Datagram,
				defaultZoneLength);

			defaultZone[defaultZoneLength] = 0;
			Datagram += defaultZoneLength;
		}
		
		//
		// 	Make "defaultZone" be the "new" one.  We may not have a default/new
		//  zone in NetInfoReplyCase and we requested with a correct zone.
		//
		
		if (defaultZoneLength == 0) {
			strcpy(defaultZone, zone);
			defaultZoneLength = zoneLength;
		}
		
		//
		// 	If we're just looking for the default zone, set here and note our
		//  mission completed.
		//
		
		if (GET_PORTDESCRIPTOR(Port)->LookingForDefaultZone) {
			strcpy(GET_PORTDESCRIPTOR(Port)->ThisDefaultZone, defaultZone);
			GET_PORTDESCRIPTOR(Port)->ThisDefaultZoneValid = TRUE;
		}
		
		//
		// 	Okay, now we want to "accept" all of the information about "this zone"
		//  for the nodes on the current port.
		//
		//  If the new multicast address is different, remove the old and set
		//  the new.  Don't allow mucking with the "broadcast" multicast address.
		//
		
		if (!FixedCompareCaseSensitive(
				multicastAddress,
				multicastAddressLength,
				GET_PORTDESCRIPTOR(Port)->ZoneMulticastAddress,
				multicastAddressLength)) {

			if ((GET_PORTDESCRIPTOR(Port)->ThisZoneValid &&
			    (!FixedCompareCaseSensitive(
					GET_PORTDESCRIPTOR(Port)->ZoneMulticastAddress,
					multicastAddressLength,
					portHandlers->BroadcastAddress,
					multicastAddressLength)))) {

				(*portHandlers->RemoveMulticastAddress)(
					Port,
					1,
					GET_PORTDESCRIPTOR(Port)->ZoneMulticastAddress);
			}

			if (!FixedCompareCaseSensitive(
					multicastAddress,
					multicastAddressLength,
					portHandlers->BroadcastAddress,
					multicastAddressLength)) {

				(*portHandlers->AddMulticastAddress)(
					Port,
					1,
					multicastAddress);
			}

			MoveMem(
				GET_PORTDESCRIPTOR(Port)->ZoneMulticastAddress,
				multicastAddress,
				multicastAddressLength);
		}
		
		// Set this cable range if we're a net info reply.
		if (commandType == ZIP_NETINFOREPLYCOMMAND) {
			GET_PORTDESCRIPTOR(Port)->ThisCableRange.FirstNetworkNumber =
																	netRangeStart;
			GET_PORTDESCRIPTOR(Port)->ThisCableRange.LastNetworkNumber =
																	netRangeEnd;

			GET_PORTDESCRIPTOR(Port)->ARouter.NetworkNumber = Source.NetworkNumber;
			GET_PORTDESCRIPTOR(Port)->ARouter.NodeNumber = Source.NodeNumber;
			GET_PORTDESCRIPTOR(Port)->SeenRouterRecently = TRUE;
		}
		
		// Okay, now we know the zone!
		strcpy(GET_PORTDESCRIPTOR(Port)->ThisZone, defaultZone);
		GET_PORTDESCRIPTOR(Port)->ThisZoneValid = TRUE;
		LeaveCriticalSection(GLOBAL_ROUTING);

	} while (FALSE);


	if (logEventPlace) {
		LOG_ERRORONPORT(
			Port,
			EVENT_ATALK_INVALIDAARPPACKET,
			logEventPlace,
			STATUS_INSUFFICIENT_RESOURCES,
			startOfPacket,
			Length,
			0,
			NULL);
	}

	AtalkDereferencePortDescriptor(GET_PORTDESCRIPTOR(Port),__LINE__ | __ZIPSTUB__);
	return(DatagramLength);
	
} // ZipPacketIn




BOOLEAN
GetNetworkInfoForNode(
	int Port,
	EXTENDED_NODENUMBER ExtendedNode,
	BOOLEAN FindDefaultZone,
	BOOLEAN	WaitForAllowed
	)
{
	int numberOfRequests;
	int datagramLength;
	BOOLEAN	done;
	PBUFFER_DESC datagram, copy;
	APPLETALK_ADDRESS source, destination;
	
	//
	// 	Our node is just coming up; send out some ZipGetNetInfo packets,
	//  and see what we can find out!
	//
	
	if (!AtalkVerifyPortDescriptorInterlocked(
			GET_PORTDESCRIPTOR(Port),
			__LINE__ | __ZIPSTUB__)) {

		return(FALSE);
	}

	// Setup.
	if (!GET_PORTDESCRIPTOR(Port)->ExtendedNetwork) {
		INTERNAL_ERROR(__LINE__ | __ZIPSTUB__, ErrorCode, NULL, 0);
		return(FALSE);
	}

	if (FindDefaultZone) {
		GET_PORTDESCRIPTOR(Port)->ThisDefaultZoneValid = FALSE;
		GET_PORTDESCRIPTOR(Port)->LookingForDefaultZone = TRUE;
	} else {
		GET_PORTDESCRIPTOR(Port)->ThisZoneValid = FALSE;
	}
	
	source.NetworkNumber = ExtendedNode.NetworkNumber;
	source.NodeNumber = ExtendedNode.NodeNumber;
	source.SocketNumber = ZONESINFORMATION_SOCKET;
	destination.NetworkNumber = CABLEWIDE_BROADCASTNETWORKNUMBER;
	destination.NodeNumber = APPLETALK_BROADCASTNODENUMBER;
	destination.SocketNumber = ZONESINFORMATION_SOCKET;

	done = FALSE;
	do {

		// Allocate a buffer descriptor.
		if ((datagram = NewBufferDescriptor(
							ZIP_ZONENAMEOFFSET + MAXIMUM_ZONELENGTH)) == NULL) {
	
			LOG_ERROR(
				EVENT_ATALK_MEMORY_RESOURCES,
				(__ZIPSTUB__ | __LINE__),
				0,
				NULL,
				0,
				0,
				NULL);
	
			break;
		}
		
		// Build a ZipGetNetInfo datagram...
		datagram->data[ZIP_COMMANDOFFSET] = ZIP_GETNETINFOCOMMAND;
		datagram->data[ZIP_FLAGSOFFSET] = 0;
		datagram->data[ZIP_CABLERANGESTARTOFFSET] = 0;
		datagram->data[ZIP_CABLERANGESTARTOFFSET + 1] = 0;
		datagram->data[ZIP_CABLERANGEENDOFFSET] = 0;
		datagram->data[ZIP_CABLERANGEENDOFFSET + 1] = 0;
		if (FindDefaultZone) {
			datagram->data[ZIP_ZONELENGTHOFFSET] = 0;
			datagramLength = ZIP_ZONENAMEOFFSET;
	
		} else {
	
			datagram->data[ZIP_ZONELENGTHOFFSET] =
						(UCHAR)strlen(GET_PORTDESCRIPTOR(Port)->InitialDesiredZone);
			if (datagram->data[ZIP_ZONELENGTHOFFSET] != 0) {
				strcpy(
					datagram->data + ZIP_ZONENAMEOFFSET,
					GET_PORTDESCRIPTOR(Port)->InitialDesiredZone);
	
				datagramLength =
					ZIP_ZONENAMEOFFSET + datagram->data[ZIP_ZONELENGTHOFFSET];
			} else {
				datagramLength = ZIP_ZONENAMEOFFSET;
			}
		}
		
		//
		// 	Okay, blast a few of these guys out to see if any router on the
		//  network knows what's up.
		//
	
		for (numberOfRequests = 0;
			 numberOfRequests < NUMBEROF_GETNETINFOS;
			 numberOfRequest++) {
	
			// Are we done?
			EnterCriticalSection(GLOBAL_ROUTING);
			done =
			  ((FindDefaultZone && GET_PORTDESCRIPTOR(Port)->ThisDefaultZoneValid) ||
			  (!FindDefaultZone && GET_PORTDESCRIPTOR(Port)->ThisZoneValid));
			LeaveCriticalSection(GLOBAL_ROUTING);

			if (done) {
				break;
			}
		
			//
			// 	Copy our datagram due to multiple sends and possible asynchronous
			//  transmit completion.
			//
		
			if ((copy = CopyBufferChain(datagram)) == NULL) {
				break;
			}
	
			if (!TransmitDdp(
					Port,
					source,
					destination,
					DDPPROTOCOL_ZIP,
					copy,
					datagramLength,
					0,
					NULL,
					NULL,
					NULL,
					0)) {
	
				LOG_ERRORONPORT(
					Port,
					EVENT_ATALK_ZIPSENDPACKET,
					(__ZIPSTUB__ | __LINE__),
					0,
					NULL,
					0,
					0,
					NULL);
		
				break;
			}

			if (!WaitForAllowed) {
				break;
			}

			if (FindDefaultZone) {
				WaitFor(
					ZIP_GETNETINFOHUNDRETHS,
					&GET_PORTDESCRIPTOR(Port)->ThisDefaultZoneValid);

				continue;
			}

			WaitFor(
				ZIP_GETNETINFOHUNDRETHS,
				&GET_PORTDESCRIPTOR(Port)->ThisZoneValid);
		}
		
		//	Well... did we get anything?
		FreeBufferChain(datagram);
	
		EnterCriticalSection(GLOBAL_ROUTING);
		if (FindDefaultZone) {
			GET_PORTDESCRIPTOR(Port)->LookingForDefaultZone = FALSE;
			done = GET_PORTDESCRIPTOR(Port)->ThisDefaultZoneValid;
		} else {
			done = GET_PORTDESCRIPTOR(Port)->ThisZoneValid;
		}
		LeaveCriticalSection(GLOBAL_ROUTING);
		break;

	} while (FALSE);

	AtalkDereferencePortDescriptor(GET_PORTDESCRIPTOR(Port),__LINE__ | __ZIPSTUB__);
	return(done);
	
}  // GetNetworkInfoOnPort




APPLETALK_ERROR
GetMyZone(
	int Port,                       		 // On what port?
	PVOID OpaqueBuffer,         			 // "Buffer" for the zone name
	GetMyZoneComplete *CompletionRoutine,	 // Routine to call when we're finished.
	ULONG UserData					         // Passed along to above.
	)
{
	long socket;
	PZIP_COMPLETIONINFO completionInfo;
	APPLETALK_ERROR errorCode = ATnoError;
	
	//
	// 	Call the supplied completion routine with the following arguments when
	//  we've come up with the answer:
	//
	//  	errorCode - APPLETALK_ERROR; completion status.
	//      UserData  - ULONG; as passed to us.
	//      OpaqueBuffer - PVOID ; ad passed to us (filled in now).
	//
	//
	
	// If the "default port" is requested, demystify it.
	if (Port == DEFAULT_PORT)
	   if ((Port = FindDefaultPort()) < 0)
		  return(ATappleTalkShutDown);
	
	if (!AtalkVerifyStackInterlocked(__LINE__ | __ZIPSTUB__)) {
		return(ATappleTalkShutDown);
	}

	if (!AtalkVerifyPortDescriptorInterlocked(
			GET_PORTDESCRIPTOR(Port),
				__LINE__ | __ZIPSTUB__)) {

		return(ATappleTalkShutDown);
	}

	do {

		EnterCriticalSection(GLOBAL_ROUTING);

		// For extended networks, we either know or can't find out!
		if (GET_PORTDESCRIPTOR(Port)->ExtendedNetwork) {
			if (GET_PORTDESCRIPTOR(Port)->ThisZoneValid) {
				MoveToOpaque(
					OpaqueBuffer,
					0,
					GET_PORTDESCRIPTOR(Port)->ThisZone,
					strlen(GET_PORTDESCRIPTOR(Port)->ThisZone) + 1);
	
			} else {
	
				MoveToOpaque(
					OpaqueBuffer,
					0,
					WILDCARD_ZONE,
					WILDCARD_ZONESIZE);
			}
			LeaveCriticalSection(GLOBAL_ROUTING);
	
			(*CompletionRoutine)(ATnoError, UserData, OpaqueBuffer);
			break;
		}
		
		//
		// 	For non-extended networks we need to ask a router.  If we don't know
		//  about a router, don't play.
		//
		
		if (!GET_PORTDESCRIPTOR(Port)->SeenRouterRecently) {
			MoveToOpaque(
				OpaqueBuffer,
				0,
				WILDCARD_ZONE,
				WILDCARD_ZONESIZE);
	
			LeaveCriticalSection(GLOBAL_ROUTING);
	
			(*CompletionRoutine)(ATnoError, UserData, OpaqueBuffer);
			break;
		}

		LeaveCriticalSection(GLOBAL_ROUTING);
		
		// Build a completion node.
		if ((completionInfo = AllocateZipCompletionInfo()) == NULL) {
			errorCode = ATnoMemory;
			break;
		}

		// Open a socket for handling the replies.
		if ((errorCode = OpenSocketOnNode(
							&socket,
							Port,
							NULL,
							UNKNOWN_SOCKET,
							GetMyZoneReply,
							(ULONG)completionInfo,
							FALSE,
							NULL,
							0,
							NULL)) != ATnoError) {

			Free(completionInfo);
			break;
		}
		
		completionInfo->AtpRequestType = ZIP_GETMYZONECOMMAND;
		completionInfo->Socket = socket;
		completionInfo->Router.NetworkNumber =
					GET_PORTDESCRIPTOR(Port)->ARouter.NetworkNumber;
		completionInfo->Router.NodeNumber =
					GET_PORTDESCRIPTOR(Port)->ARouter.NodeNumber;
		completionInfo->Router.SocketNumber = ZONESINFORMATION_SOCKET;
		completionInfo->OpaqueBuffer = OpaqueBuffer;
		completionInfo->MyZoneCompletionRoutine = CompletionRoutine;
		completionInfo->UserData = UserData;

		EnterCriticalSection(GLOBAL_ROUTING);

		//	Timer (removed in timer expiration, or if timer successfully cancelled
		AtalkReferenceZipCompletion(
			completionInfo,
			__ZIPSTUB__ | __LINE__);

		//	Request (removed upon request completion)
		AtalkReferenceZipCompletion(
			completionInfo,
			__ZIPSTUB__ | __LINE__);

		//	Insert into the list, before starting the timer etc
        InsertTailList (
            &ZipCompletionInfoList,
            &completionInfo->Linkage);

		LeaveCriticalSection(GLOBAL_ROUTING);


		// Start the retry timer, then send the first query.
		completionInfo->timerId = StartTimer(
										GetZoneInfoTimerExpired,
										GETZONEINFO_TIMERSECONDS,
										sizeof(PZIP_COMPLETIONINFO),
										(PCHAR)completionInfo);
	
		SendZipPacketTo(completionInfo);

	} while (FALSE);

	AtalkDereferencePortDescriptor(GET_PORTDESCRIPTOR(Port),__LINE__ | __ZIPSTUB__);
	AtalkDereferenceStack(__LINE__ | __ZIPSTUB__);

	return(errorCode);
	
}  // GetMyZone




APPLETALK_ERROR
GetZoneList(
	int Port,                  	// Port to get zone list from.
	BOOLEAN GetLocalZones,     	// Does the caller want only the local zones?
	PVOID OpaqueBuffer,    		// "Buffer" to fill with zone list.
	int BufferSize,            	// Buffer size (bytes).
	GetZoneListComplete *CompletionRoutine,
								// Routine to call when we're finished.
	ULONG UserData    			// Passed along to above.
	)
{
	long socket;
	ZipCompletionInfo completionInfo;
	APPLETALK_ERROR errorCode;
	
	//
	// Call the supplied completion routine with the following arguments when
	//   we've come up with the answer:
	//
	//       errorCode - APPLETALK_ERROR; completion status.
	//       UserData  - ULONG; as passed to us.
	//       OpaqueBuffer
	//                 - PVOID ; addr passed to us (filled in now).
	//       zoneCount - int; how many zones did we pack in buffer.
	//
	//

	// If the "default port" is requested, demystify it.
	if (Port == DEFAULT_PORT)
	   if ((Port = FindDefaultPort()) < 0)
		  return(ATappleTalkShutDown);
	
	if (!AtalkVerifyStackInterlocked(__LINE__ | __ZIPSTUB__)) {
		return(ATappleTalkShutDown);
	}

	if (!AtalkVerifyPortDescriptorInterlocked(
			GET_PORTDESCRIPTOR(Port),
				__LINE__ | __ZIPSTUB__)) {

		return(ATappleTalkShutDown);
	}

	do {

		EnterCriticalSection(GLOBAL_ROUTING);

		// If we don't know about a router, don't play.
		if (!GET_PORTDESCRIPTOR(Port)->SeenRouterRecently) {
			MoveToOpaque(
				OpaqueBuffer,
				0,
				WILDCARD_ZONE,
				WILDCARD_ZONESIZE);
		
			LeaveCriticalSection(GLOBAL_ROUTING);

			(*CompletionRoutine)(ATnoError, UserData, OpaqueBuffer, 1);
			break;
		}

		LeaveCriticalSection(GLOBAL_ROUTING);

		// Build a completion node.
		if ((completionInfo = AllocateZipCompletionInfo()) == NULL) {
			errorCode = ATnoMemory;
			break;
		}

		// Open a socket for handling the replies.
		if ((errorCode = OpenSocketOnNode(
							&socket,
							Port,
							NULL,
							UNKNOWN_SOCKET,
							GetZoneListReply,
							(ULONG)completionInfo,
							FALSE,
							NULL,
							0,
							NULL)) != ATnoError) {

			Free(completionInfo);
			break;
		}
		
		if (GetLocalZones)
			completionInfo->AtpRequestType = ZIP_GETLOCALZONESCOMMAND;
		else
			completionInfo->AtpRequestType = ZIP_GETZONELISTCOMMAND;

		completionInfo->Router.NetworkNumber =
					GET_PORTDESCRIPTOR(Port)->ARouter.NetworkNumber;
		completionInfo->Router.NodeNumber =
					GET_PORTDESCRIPTOR(Port)->ARouter.NodeNumber;
		completionInfo->Router.SocketNumber = ZONESINFORMATION_SOCKET;

		completionInfo->Socket = socket;
		completionInfo->OpaqueBuffer = OpaqueBuffer;
		completionInfo->BufferSize = BufferSize;
		completionInfo->ZoneListCompletionRoutine = CompletionRoutine;
		completionInfo->UserData = UserData;
		completionInfo->ZoneListIndex = 1;
		
		EnterCriticalSection(GLOBAL_ROUTING);

		//	Timer (removed in timer expiration, or if timer successfully cancelled
		AtalkReferenceZipCompletion(
			completionInfo,
			__ZIPSTUB__ | __LINE__);

		//	Request (removed upon request completion)
		AtalkReferenceZipCompletion(
			completionInfo,
			__ZIPSTUB__ | __LINE__);

		//	Insert into the list, before starting the timer etc
        InsertTailList (
            &ZipCompletionInfoList,
            &completionInfo->Linkage);

		LeaveCriticalSection(GLOBAL_ROUTING);


		// Start the retry timer, then send the first query.
		completionInfo->TimerId = StartTimer(
									GetZoneInfoTimerExpired,
									GETZONEINFO_TIMERSECONDS,
									sizeof(PZIP_COMPLETIONINFO),
									(PCHAR)completionInfo);

		SendZipPacketTo(completionInfo);

	} while (FALSE);

	AtalkDereferencePortDescriptor(GET_PORTDESCRIPTOR(Port),__LINE__ | __ZIPSTUB__);
	AtalkDereferenceStack(__LINE__ | __ZIPSTUB__);

	return(errorCode);

}  // GetZoneList




VOID
SendZipPacketTo(
	PZIP_COMPLETIONINFO completionInfo
	)
{
	PBUFFER_DESC datagram;

	// Allocate a buffer descriptor.
	if ((datagram = NewBufferDescriptor(ZIP_GETZONELISTDDPSIZE)) == NULL) {

		LOG_ERROR(
			EVENT_ATALK_MEMORY_RESOURCES,
			(__ZIPSTUB__ | __LINE__),
			0,
			NULL,
			0,
			0,
			NULL);

		return;
	}
	
	// Build our toy ATP packet.
	PUTUCHAR2UCHAR(
		ATP_REQUESTFUNCTIONCODE,
		datagram->data + ATP_COMMANDCONTROLOFFSET);

	PUTUCHAR2UCHAR(
		1,
		datagram->data + ATP_BITMAPOFFSET);

	PUTUSHORT2USHORT(
		0,
		datagram->data + ATP_TRANSACTIONIDOFFSET);

	PUTUCHAR2UCHAR(
	    completionInfo->AtpRequestType,
		datagram->data + ATPZIP_COMMANDOFFSET);

	//	0 byte padding
	PUTUCHAR2UCHAR(
	    0,
		datagram->data + ATPZIP_COMMANDOFFSET + 1);

	PUTUSHORT2USHORT(
        completionInfo->ZoneListIndex,
        datagram->data + ATPZIP_STARTINDEXOFFSET);
	
	// Send the datagram to the specified destination.
	if (DeliverDdp(
			completionInfo->Socket,
			completionInfo->Router,
			DDPPROTOCOL_ATP,
			datagram,
			ZIP_GETZONELISTDDPSIZE,
			NULL,
			NULL,
			0) != ATnoError) {

		LOG_ERRORONPORT(
			Port,
			EVENT_ATALK_ZIPSENDPACKET,
			(__ZIPSTUB__ | __LINE__),
			0,
			NULL,
			0,
			0,
			NULL);
	}
	
	return;
	
}  // SendZipPacketTo




LONG
GetMyZoneReply(
	APPLETALK_ERROR errorCode,
	ULONG UserData,
	int Port,
	APPLETALK_ADDRESS source,
	long DestinationSocket,
	int ProtocolType,
	PCHAR Datagram,
	int DatagramLength,
	APPLETALK_ADDRESS ActualDestination
	)
{
	PZIP_COMPLETIONINFO completionInfo;
	USHORT zoneCount;
	USHORT zoneLength;
	PVOID opaqueBuffer;
	BOOLEAN	success;
	GetMyZoneComplete *completionRoutine;
	
	// "Use" unused formals.
	UNREFERENCED_PARAMETERS(Port);
	UNREFERENCED_PARAMETERS(source);
	UNREFERENCED_PARAMETERS(ActualDestination);
	
	completionInfo = (PZIP_COMPLETIONINFO)UserData;

	//
	//	On most error cases flee -- however, remove our completion info on
	//  socket closed.
	//
	
	if (errorCode == ATsocketClosed) {
		AtalkDereferenceZipCompletion(completionInfo);
	}

	if ((errorCode != ATnoError) ||
		(ProtocolType != DDPPROTOCOL_ATP) ||
		(DatagramLength <= ATPZIP_FIRSTZONEOFFSET)) {

		return(0);
	}
	
	// We should have one quality zone!
	GETUSHORT2USHORT(
		Datagram + ATPZIP_ZONECOUNTOFFSET,
		&zoneCount);

	GETUCHAR2USHORT(
		Datagram + ATPZIP_FIRSTZONEOFFSET,
		&zoneLength);

	if ((zoneCount != 1) ||
		(zoneLength == 0) ||
		(zoneLength > MAXIMUM_ZONELENGTH)) {

		return(0);
	}
	
	// Okay, move the zone name into the user's buffer.
	MoveToOpaque(
		completionInfo->OpaqueBuffer,
		0,
		Datagram + ATPZIP_FIRSTZONEOFFSET + 1,
		zoneLength);

	// Move a null after the zone name
	MoveToOpaque(
		completionInfo->OpaqueBuffer,
		zoneLength,
		"",
		1);
	
	//
	// 	We're all set: cancel the retry timer, close our socket [this will cause
	//  a recursive call which will remove the completion info], call the
	//  completion routine, flee.
	//

	success = TRUE;
	EnterCriticalSection(GLOBAL_ROUTING);
	if (completionInfo->Flags & ZIPCOMPLETION_DONE) {

		//	Hmm, looks like the timer timed out, and has already
		//	called the completion routine. It must be trying to
		//	close the socket right now. We just get out, without
		//	doing anything, the Timer routines, socketClose should
		//	lead to the dereference for the request.

		success = FALSE;

	} else {
		completionInfo->Flags |= ZIPCOMPLETION_DONE;
	}
	LeaveCriticalSection(GLOBAL_ROUTING);

	if (success) {
		success = CancelTimer(completionInfo->TimerId);
		if (success) {
	
			//	Dereference for the timer reference
			AtalkDereferenceZipCompletion(completionInfo);
		}
	
		opaqueBuffer = completionInfo->OpaqueBuffer;
		completionRoutine = completionInfo->MyZoneCompletionRoutine;
		UserData = completionInfo->UserData;
		if (CloseSocketOnNode(
				completionInfo->Socket,
				NULL,
				(ULONG)0) != ATnoError) {
	
			LOG_ERRORONPORT(
				Port,
				EVENT_ATALK_ZIPSOCKETCLOSE,
				(__ZIPSTUB__ | __LINE__),
				0,
				NULL,
				0,
				0,
				NULL);
		}
	
		(*completionRoutine)(ATnoError, UserData, opaqueBuffer);
	}

	return(DatagramLength);
	
}  // GetMyZoneReply




VOID
GetZoneInfoTimerExpired(
	ULONG TimerId,
	int AdditionalDataSize,
	PCHAR AdditionalData
	)
{
	PZIP_COMPLETIONINFO completionInfo;
	BOOLEAN getMyZone;
	
	// "Use" unused formal.
	UNREFERENCED_PARAMETERS(TimerId);
	
	// Verify args.
	if (AdditionalDataSize != sizeof(PZIP_COMPLETIONINFO )) {
		INTERNAL_ERROR(__LINE__ | __ZIPSTUB__, additionalDataSize, NULL, 0);
		return;
	}

	completionInfo = *(PZIP_COMPLETIONINFO *)AdditionalData;
	
	// Bump our expiration count, if there's still time try it again.
	completionInfo->ExpirationCount += 1;
	if (completionInfo->ExpirationCount < GETZONEINFO_RETRIES) {

		//	BUGBUG: TimerId assignment in a critical section...?
		completionInfo->TimerId = StartTimer(
									GetZoneInfoTimerExpired,
									GETZONEINFO_TIMERSECONDS,
									sizeof(PZIP_COMPLETIONINFO),
									(PCHAR)completionInfo);

		SendZipPacketTo(completionInfo);
		return;
	}
	
	//
	// 	Else, we're out of time, just plead ignorance, close the temporary
	//  socket (in turn freeing the completion info), and call the completion
	//  routine.
	//
	
	getMyZone = (completionInfo->AtpRequestType == ZIP_GETMYZONECOMMAND);
	MoveToOpaque(
		completionInfo->OpaqueBuffer,
		0,
		WILDCARD_ZONE,
		WILDCARD_ZONESIZE);

	if (!getMyZone) {
		completionInfo->ZoneCount = 1;
	}

	if (CloseSocketOnNode(completionInfo->Socket) != ATnoError)
		LOG_ERRORONPORT(
			Port,
			EVENT_ATALK_ZIPSOCKETCLOSE,
			(__ZIPSTUB__ | __LINE__),
			completionInfo->Socket,
			NULL,
			0,
			0,
			NULL);

	if (getMyZone) {
	   (*completionInfo->MyZoneCompletionRoutine)(
			ATnoError,
			completionInfo->UserData,
			completionInfo->OpaqueBuffer);

	} else {
		(*completionInfo->ZoneListCompletionRoutine)(
			ATnoError,
			completionInfo->UserData,
			completionInfo->OpaqueBuffer,
			completionInfo->ZoneCount);
	}

	AtalkDereferenceZipCompletion(completionInfo, __ZIPSTUB__ | __LINE__);
	return;
	
}  // GetZoneInfoTimerExpired




LONG
GetZoneListReply(
	APPLETALK_ERROR ErrorCode,
	ULONG UserData,
	int Port,
	APPLETALK_ADDRESS Source,
	long DestinationSocket,
	int ProtocolType,
	PCHAR Datagram,
	int DatagramLength,
	APPLETALK_ADDRESS ActualDestination
	)
{
	PZIP_COMPLETIONINFO completionInfo;
	int zoneCount;
	int zoneLength;
	PVOID opaqueBuffer;
	GetZoneListComplete *completionRoutine;
	BOOLEAN lastFlag, formatError = FALSE, overflow = FALSE;
	int datagramIndex;
	PCHAR currentZone;
	int validZones = 0;
	
	// "Use" unused formals.
	UNREFERENCED_PARAMETERS(Source);
	UNREFERENCED_PARAMETERS(Port);
	UNREFERENCED_PARAMETERS(ActualDestination);
	
	// Get our completionInfo
	completionInfo = (PZIP_COMPLETIONINFO)UserData;
	
	
	//
	// 	On most error cases flee -- however, remove our completion info on
	//  socket closed.
	//
	
	if (ErrorCode == ATsocketClosed) {
		if (previousCompletionInfo == NULL)
		  zipCompletionInfoList = completionInfo->Next;
		else
		  previousCompletionInfo->Next = completionInfo->Next;
		Free(completionInfo);
	}
	if (ErrorCode != ATnoError or
		ProtocolType != DDPPROTOCOL_ATP or
		DatagramLength < ATPZIP_FIRSTZONEOFFSET) {
		HandleIncomingPackets();
		HandleDeferredTimerChecks();
		return((long)TRUE);
	}
	
	// We should have a zone list now.
	
	lastFlag = (Datagram[ATPZIP_LASTFLAGOFFSET] != 0);
	zoneCount = (Datagram[ATPZIP_ZONECOUNTOFFSET] << 8);
	zoneCount += (unsigned char)(Datagram[ATPZIP_ZONECOUNTOFFSET + 1]);
	if (zoneCount == 0)
	   lastFlag = TRUE;
	datagramIndex = ATPZIP_FIRSTZONEOFFSET;
	while (zoneCount > 0) {
		// Pull out the next zoneLength/zone pair.
	
		if (DatagramIndex + 1 > DatagramLength) {
		  formatError = TRUE;
		  break;
		}
		zoneLength = (unsigned char)Datagram[datagramIndex];
		if (zoneLength == 0 or
		   zoneLength > MAXIMUM_ZONELENGTH) {
		  formatError = TRUE;
		  break;
		}
		datagramIndex += 1;
		if (datagramIndex + zoneLength > DatagramLength) {
		  formatError = TRUE;
		  break;
		}
		currentZone = Datagram + datagramIndex;
		datagramIndex += zoneLength;
	
		// Place the new zone into the user's buffer.
	
		if (completionInfo->NextZoneOffset + zoneLength + 1 >
		   completionInfo->BufferSize) {
		  overflow = TRUE;
		  break;
		}
		MoveToOpaque(completionInfo->OpaqueBuffer, completionInfo->NextZoneOffset,
					currentZone, zoneLength);
		MoveToOpaque(completionInfo->OpaqueBuffer,
					completionInfo->NextZoneOffset + zoneLength, "", 1);
		completionInfo->NextZoneOffset += (zoneLength + 1);
		completionInfo->ZoneCount += 1;
		validZones += 1;
		zoneCount -= 1;
	}
	
	//
	// The next one we'll want will be past the ones we've just read.  If
	//   we've had a format error or we don't have them all yet, retry the
	//   request.
	//
	
	completionInfo->ZoneListIndex += validZones;
	if (formatError or (not overflow and not lastFlag)) {
		SendZipPacketTo(completionInfo);
		HandleIncomingPackets();
		HandleDeferredTimerChecks();
		return((long)TRUE);
	}
	
	//
	// Otherwise, we're set, either due to overflow or we have them all:
	//   cancel the retry timer, close our socket [this will cause
	//   a recursive call which will remove the completion info], call the
	//   completion routine, flee.
	//
	
	CancelTimer(completionInfo->TimerId);
	opaqueBuffer = completionInfo->OpaqueBuffer;
	completionRoutine = completionInfo->ZoneListCompletionRoutine;
	UserData = completionInfo->UserData;
	zoneCount = completionInfo->ZoneCount;
	if (CloseSocketOnNode(completionInfo->Socket) != ATnoError)
		LOG_ERRORONPORT(
			Port,
			EVENT_ATALK_ZIPSOCKETCLOSE,
			(__ZIPSTUB__ | __LINE__),
			0,
			NULL,
			0,
			0,
			NULL);

	   ErrorLog("GetZoneListReply", ISevError, __LINE__, Port,
				IErrZipStubBadSocketClose, IMsgZipStubBadSocketClose,
				Insert0());
	
	HandleIncomingPackets();
	HandleDeferredTimerChecks();
	if (overflow)
	   ErrorCode = ATzipBufferTooSmall;
	else
	   ErrorCode = ATnoError;
	(*completionRoutine)(ErrorCode, UserData, opaqueBuffer, zoneCount);
	
	return((long)TRUE);
	
}  // GetZoneListReply



PZIP_COMPLETIONINFO
AllocateZipCompletionInfo(
	VOID
	)
{
    PZIP_COMPLETIONINFO	info;

	if ((info = (PZIP_COMPLETIONINFO)Calloc(ZIP_COMPLETIONSIZE, 1)) != NULL) {
		
		info->Type = ZIP_COMPLETIONTYPE;
		info->Size = ZIP_COMPLETIONSIZE;
		
		InitializeListHead(&info->Linkage);
	}

	return(info);
}



VOID
AtalkRefZipCompletion(
	PZIP_COMPLETIONINFO	ZipCompletion
	)
{
	ASSERT(ZipCompletion->ReferenceCount >= 0);
	ZipCompletion->ReferenceCount++;
	return;
}




VOID
AtalkDerefZipCompletion(
	PZIP_COMPLETIONINFO	ZipCompletion
	)
{
	ASSERT(ZipCompletion->ReferenceCount > 0);
	if (--ZipCompletion->ReferenceCount > 0) {
		return;
	}

	//	Reference count is now zero, remove from list and free it up
	RemoveEntryList (&ZipCompletion->Linkage);
	InitializeListHead (&ZipCompletion->Linkage);

	Free(ZipCompletion);
	return;
}