/* Network.c */
/*****************************************************************************/
/*                                                                           */
/*    System Dependency Library for Building Portable Software               */
/*    Macintosh Version                                                      */
/*    Written by Thomas R. Lawrence, 1993 - 1994.                            */
/*                                                                           */
/*    This file is Public Domain; it may be used for any purpose whatsoever  */
/*    without restriction.                                                   */
/*                                                                           */
/*    This package is distributed in the hope that it will be useful,        */
/*    but WITHOUT ANY WARRANTY; without even the implied warranty of         */
/*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.                   */
/*                                                                           */
/*    Thomas R. Lawrence can be reached at tomlaw@world.std.com.             */
/*                                                                           */
/*****************************************************************************/

#include "MiscInfo.h"
#include "Audit.h"
#include "Debug.h"
#include "Definitions.h"

#ifdef THINK_C
	#pragma options(pack_enums)
#endif
#include <memory.h>
#include <GestaltEqu.h>
#include <PPCToolbox.h>
#include <Script.h>
#include <Errors.h>
#ifdef THINK_C
	#pragma options(!pack_enums)
#endif

#include "Network.h"
#include "Memory.h"
#include "EventLoop.h"
#include "Array.h"


/* this is the AppleTalk PPCToolBox version of the Network module */


/* since this system can only handle one outstanding requested connection at */
/* a time, we might have to try several times to establish a connection.  these */
/* specify how many times to try and how long to wait between each one. */
#define MAXNUMTRIES (10)
#define TRYDELAY (1.0) /* in seconds */

#define DEFAULTTYPE "PPCToolBox"
#define DEFAULTTYPELENGTH (10)

/* size of a single buffer record */
#define BUFFERSIZE (256)

typedef enum
	{
		eAwaitingConnection EXECUTE(= -8982),
		eConnectionPending
	} PortStates;

struct PortIDType
	{
		PPCPortRefNum					ThePortRefnum;
		PortStates						PortState;
		PPCInformPBRec				ThePPCInformPBRec;
		PPCPortRec						ThePPCPortRec;
		PPCPortRec						RemotePortName; /* temporary buffer for it */
		LocationNameRec				RemoteLocationName; /* temporary buffer */
	};

typedef struct BufferRec
	{
		struct BufferRec*			Next;
		long									NumBytes;
		char									Buffer[BUFFERSIZE];
	} BufferRec;

typedef enum
	{
		eWriteIdle EXECUTE(= -4152),
		eWriteInProgress,
		eWriteFinished
	} SessionWriteStates;

typedef enum
	{
		eReadIdle EXECUTE(= -28874),
		eReadInProgress,
		eReadFinished
	} SessionReadStates;

typedef enum
	{
		eSessionNormal EXECUTE(= -9887),
		eSessionDataArrived,
		eSessionClosed
	} SessionStates;

struct SessionIDType
	{
		PPCSessRefNum					SessionRefnum;
		PortIDType*						Port;
		SessionWriteStates		WriteState;
		SessionReadStates			ReadState;
		SessionStates					OverallState;
		BufferRec*						SendHead;
		BufferRec*						SendTail;
		BufferRec*						SendInProgress; /* valid if WriteState != eWriteIdle */
		BufferRec*						ReceiveHead;
		BufferRec*						ReceiveTail;
		BufferRec*						ReceiveInProgress; /* valid if ReadState != eReadIdle */
		PPCReadPBRec					ThePPCReadPBRec;
		PPCWritePBRec					ThePPCWritePBRec;
		PPCPortRec						RemotePortName; /* probably not needed */
		LocationNameRec				RemoteLocationName; /* NBP name of remote machine */
	};


/* maintains the status of this module for debugging */
EXECUTE(static MyBoolean					Initialized = False;)

/* list of network ports.  NIL means the port slot is available.  the length */
/* is determined by the heap block size. */
static ArrayRec*									PortArray;

/* list of sessions.  NIL means session slot is available... */
static ArrayRec*									SessionArray;

/* This system allows you to make an outgoing connection without a port.  The */
/* Macintosh requires all connections to be made through ports, so we provide */
/* a "system port" through which all outgoing connections are made. */
static PortIDType*								AppleTalkSystemPort;

/* session scan counter.  this is used so that all of the sessions are checked */
/* fairly instead of favoring ones that are near the beginning of the list */
static long												SessionScan;


/* utility function prototypes */
static void					ReinstallPPCInform(PortIDType* Port);
static MyBoolean		DecodeMachineName(char* MachineStr, char ObjStr[32],
											char TypeStr[32], char ZoneStr[32]);
static void					UpdateRead(SessionIDType* Session);
static void					UpdateWrite(SessionIDType* Session);
static pascal void	MyPPCInformCompletionRoutine(PPCInformPBRec* PB);
static pascal void	MyPPCReadCompletionRoutine(PPCParamBlockRec* PB);
static pascal void	MyPPCWriteCompletionRoutine(PPCParamBlockRec* PB);


/* initialize the network interface.  the user calls this.  it is not called from */
/* the standard Level 0 initialization routine because not all programs need */
/* networking.  Could return eNetNoError, eNetNoMemory, eNetNetworkNotAvailable, */
/* eNetCouldntInitNet, or eNetUnknownError */
NetErrors						InitializeNetwork(void)
	{
		OSErr							Error;
		long							Result;
		NetErrors					ReturnValue;
		unsigned long			PortIDScan;

		ERROR(Initialized,PRERR(ForceAbort,"InitializeNetwork:  already initialized"));
		EXECUTE(Initialized = True;)
		Error = Gestalt(gestaltPPCToolboxAttr,&Result);
		if (Error != noErr)
			{
				ReturnValue = eNetNetworkNotAvailable;
			 FailurePoint1:
				EXECUTE(Initialized = False;)
				return ReturnValue;
			}
		if ((Result & gestaltPPCSupportsRealTime) == 0)
			{
				/* not initialized.  try initializing */
				Error = PPCInit();
				if (Error != noErr)
					{
						ReturnValue = eNetCouldntInitNet;
						goto FailurePoint1;
					}
				Error = Gestalt(gestaltPPCToolboxAttr,&Result);
			}
		/* don't know what to do in these cases */
		if ((Result & gestaltPPCSupportsOutGoing) == 0)
			{
			}
		if ((Result & gestaltPPCSupportsIncoming) == 0)
			{
			}
		/* allocate arrays for holding session and port records */
		PortArray = NewArray();
		if (PortArray == NIL)
			{
				ReturnValue = eNetNoMemory;
			 FailurePoint2:
				goto FailurePoint1;
			}
		SessionArray = NewArray();
		if (SessionArray == NIL)
			{
				ReturnValue = eNetNoMemory;
			 FailurePoint3:
				DisposeArray(PortArray);
				goto FailurePoint2;
			}
		/* initialize the session counter for NetUpdate */
		SessionScan = 0;
		/* trying to obtain a port for outgoing connections.  port names must be unique */
		/* and since there may be several programs using this package running on the */
		/* same computer, we need to search for an available port to work out of. */
		for (PortIDScan = 0xffffffff; PortIDScan > 0; PortIDScan -= 1)
			{
				char*							PortNameTemp;
				int								Scan;

				PortNameTemp = AllocPtrCanFail(8,"SysPortName");
				if (PortNameTemp == NIL)
					{
						ReturnValue = eNetNoMemory;
						goto FailurePoint3;
					}
				for (Scan = 0; Scan < 8; Scan += 1)
					{
						PortNameTemp[Scan] = 'a' + ((PortIDScan >> (Scan * 4)) & 15);
					}
				/* try to open the port */
				ReturnValue = NetListenAtPort(PortNameTemp,&AppleTalkSystemPort,eNetAppleTalk);
				ReleasePtr(PortNameTemp);
				/* see if the port worked */
				switch (ReturnValue)
					{
						default:
							EXECUTE(PRERR(ForceAbort,
								"InitializeNetwork:  bad value from NetListenAtPort"));
							break;
						case eNetNoError:
							goto SystemPortAllocatedPoint;
						case eNetNoMemory:
							/* value of ReturnValue stays the same */
							/* ReturnValue = eNetNoMemory; */
						 FailurePoint4:
							DisposeArray(SessionArray);
							goto FailurePoint3;
						case eNetPortInUse:
							break; /* loop again */
						case eNetBadPortString:
							EXECUTE(PRERR(ForceAbort,"InitializeNetwork:  bad port string"));
							break;
						case eNetUnknownError:
							/* value of ReturnValue stays the same */
							/* ReturnValue = eNetUnknownError; */
							goto FailurePoint4;
							break;
					}
			}
		/* fall through:  couldn't allocate any system ports */
		ReturnValue = eNetUnknownError;
		goto FailurePoint4;
		/* jump here if all is good */
	 SystemPortAllocatedPoint:
		return eNetNoError;
	}


/* discard any pending data, close any open connections, and clean internal data */
void								ShutdownNetwork(void)
	{
		long							Scan;
		long							Limit;

		ERROR(!Initialized,PRERR(ForceAbort,"ShutdownNetwork:  not initialized"));
		/* first, close all sessions */
		Limit = ArrayGetLength(SessionArray);
		for (Scan = 0; Scan < Limit; Scan += 1)
			{
				SessionIDType*		Session;

				Session = (SessionIDType*)ArrayGetElement(SessionArray,Scan);
				EXECUTE(PRERR(AllowResume,"ShutdownNetwork:  session still open"));
				NetCloseSession(Session);
			}
		/* now close all open ports, including the system port */
		NetTerminatePortAndSessions(AppleTalkSystemPort); /* technically not needed */
		Limit = ArrayGetLength(PortArray);
		for (Scan = 0; Scan < Limit; Scan += 1)
			{
				PortIDType*				Port;

				Port = (PortIDType*)ArrayGetElement(PortArray,Scan);
				EXECUTE(PRERR(AllowResume,"ShutdownNetwork:  found a port that's still open"));
				NetTerminatePortAndSessions(Port);
			}
		/* now release memory occupied by arrays */
		DisposeArray(PortArray);
		DisposeArray(SessionArray);
		EXECUTE(Initialized = False;)
	}


/* Network update routine.  It handles periodic update tasks and should be called */
/* frequently (preferably in the main event loop next to GetAnEvent). */
NetEvents						NetUpdate(SessionIDType** SessionNumber)
	{
		OSErr							Error;
		long							Scan;
		long							Limit;
		long							OldSessionScan;
		NetEvents					ReturnValue;

		ERROR(SessionNumber == NIL,PRERR(ForceAbort,"NetUpdate:  SessionNumber == NIL"));
		ERROR(!Initialized,PRERR(ForceAbort,"NetUpdate:  not initialized"));
		/* look for PPCInforms that completed */
		Limit = ArrayGetLength(PortArray);
		for (Scan = 0; Scan < Limit; Scan += 1)
			{
				PortIDType*				Port;

				Port = (PortIDType*)ArrayGetElement(PortArray,Scan);
				if (Port->PortState == eConnectionPending)
					{
						SessionIDType*		Session;

						/* return an event indicating the new session and reset the PPCInform */
						Session = (SessionIDType*)AllocPtrCanFail(sizeof(SessionIDType),"Session");
						if ((Session == NIL) || (Port == AppleTalkSystemPort))
							{
								PPCEndPBRec				ThePPCEndPBRec;

								/* if we are out of memory, then kill the session */
								/* we also do this if the connection was established on the */
								/* system port */
							 NewSessionFailurePoint1:
								ThePPCEndPBRec.sessRefNum = Port->ThePPCInformPBRec.sessRefNum;
								Error = PPCEnd(&ThePPCEndPBRec,False/*sync*/);
								ERROR(Error != noErr,PRERR(AllowResume,"NetUpdate:  PPCEnd != noErr"));
								ReinstallPPCInform(Port); /* this resets PortState */
								goto NextPortPoint;
							}
						if (!ArrayAppendElement(SessionArray,Session))
							{
								goto NewSessionFailurePoint1;
							}
						/* initialize all of the fields of the session record */
						Session->SessionRefnum = Port->ThePPCInformPBRec.sessRefNum;
						Session->Port = Port;
						Session->WriteState = eWriteIdle;
						Session->ReadState = eReadIdle;
						Session->OverallState = eSessionNormal;
						Session->SendHead = NIL;
						Session->SendTail = NIL;
						Session->SendInProgress = NIL;
						Session->ReceiveHead = NIL;
						Session->ReceiveTail = NIL;
						Session->ReceiveInProgress = NIL;
						/* initiate the first PPC read operation */
						UpdateRead(Session);
						/* copy over the port and location names of the remote session */
						Session->RemotePortName = Port->RemotePortName;
						Session->RemoteLocationName = Port->RemoteLocationName;
						ReinstallPPCInform(Port); /* this resets PortState */
						/* return session number for caller */
						*SessionNumber = Session;
						return eNetEventNewSession;
					}
				/* jump here after killing a session that we couldn't handle so that */
				/* we can check the next port. */
			 NextPortPoint:
				;
			}
		/* now do session scans */
		ReturnValue = eNetEventNone;
		Limit = ArrayGetLength(SessionArray);
		if (Limit > 0)
			{
				OldSessionScan = SessionScan;
				if (OldSessionScan > Limit - 1)
					{
						/* this prevents us from getting stuck in an infinite loop if someone */
						/* removed an array element and left SessionScan beyond the end of the array */
						OldSessionScan = 0;
					}
				do
					{
						SessionIDType*			Session;

						/* we increment first so that we don't dwell on hot sessions */
						SessionScan += 1;
						if (SessionScan >= Limit)
							{
								SessionScan = 0;
							}
						Session = (SessionIDType*)ArrayGetElement(SessionArray,SessionScan);
						/* check to see if port is closed */
						if (Session->OverallState == eSessionClosed)
							{
								*SessionNumber = Session;
								return eNetEventSessionClosed;
							}
						/* check to see if write needs to be retriggered */
						if (Session->WriteState != eWriteInProgress)
							{
								if (Session->WriteState == eWriteFinished)
									{
										ReturnValue = eNetEventInternal;
									}
								UpdateWrite(Session);
							}
						/* check to see if read needs to be retriggered */
						if (Session->ReadState != eReadInProgress)
							{
								if (Session->ReadState == eReadFinished)
									{
										ReturnValue = eNetEventInternal;
									}
								UpdateRead(Session);
							}
						/* check to see if there is new data */
						if (Session->OverallState == eSessionDataArrived)
							{
								Session->OverallState = eSessionNormal;
								*SessionNumber = Session;
								return eNetEventDataIncoming;
							}
					} while (SessionScan != OldSessionScan);
			}
		return ReturnValue;
	}


/* local utility routine for installing an asynchronous PPC callback */
static void					ReinstallPPCInform(PortIDType* Port)
	{
		OSErr							Error;

		Port->ThePPCInformPBRec.ioCompletion = NewPPCCompProc(&MyPPCInformCompletionRoutine);
		Port->ThePPCInformPBRec.portRefNum = Port->ThePortRefnum;
		Port->ThePPCInformPBRec.autoAccept = True;
		/* provide a place for identifying the remote port */
		Port->ThePPCInformPBRec.portName = &(Port->RemotePortName);
		/* provide a place for identifying the remote machine */
		Port->ThePPCInformPBRec.locationName = &(Port->RemoteLocationName);
		/* we don't support user names */
		Port->ThePPCInformPBRec.userName = NIL;
		Port->PortState = eAwaitingConnection; /* BEFORE the call */
		Error = PPCInform(&(Port->ThePPCInformPBRec),True/*async*/);
		ERROR(Error != noErr,PRERR(AllowResume,"ReinstallPPCInform:  PPCInform != noErr"));
	}


/* Listen at the specified port.  Returns the reference number of the port being */
/* listened at.  PortString is a non-null-terminated heap block containing the string */
/* identifying the port.  The port string format depends on the underlying network */
/* scheme.  For instance, TCP/IP would be an integer between 128 and 9999. */
/* for AppleTalk (NBP), it is a valid NBP name.  Could return eNetNoError, */
/* eNetNoMemory, eNetPortInUse, eNetBadPortString, eNetUnknownError, or */
/* eNetProtocolNotSupported. */
NetErrors						NetListenAtPort(char* PortString, PortIDType** PortOut,
											NetworkTypes WhichNetwork)
	{
		PPCOpenPBRec			ThePPCOpenPBRec;
		long							PortStringLength;
		PortIDType*				Port;
		OSErr							Error;
		NetErrors					ReturnValue;

		ERROR(!Initialized,PRERR(ForceAbort,"NetListenAtPort:  not initialized"));
		CheckPtrExistence(PortString);
		ERROR(PortOut == NIL,PRERR(ForceAbort,"NetListenAtPort:  PortOut is NIL"));
		/* verify network connection requested */
		switch (WhichNetwork)
			{
				case eNetDefault:
				case eNetAppleTalk:
					break;
				case eNetTCP:
					return eNetProtocolNotSupported;
				default:
					EXECUTE(PRERR(ForceAbort,"NetListenAtPort:  bad protocol specified"));
					break;
			}
		/* make a place for it */
		Port = (PortIDType*)AllocPtrCanFail(sizeof(PortIDType),"PortIDType");
		if (Port == NIL)
			{
				ReturnValue = eNetNoMemory;
			 FailurePoint1:
				return ReturnValue;
			}
		if (!ArrayAppendElement(PortArray,Port))
			{
				ReturnValue = eNetNoMemory;
			 FailurePoint2:
				ReleasePtr((char*)Port);
				goto FailurePoint1;
			}
		/* initialize the port record */
		Port->ThePPCPortRec.nameScript = smRoman;
		PortStringLength = PtrSize(PortString);
		if (PortStringLength > 31)
			{
				ReturnValue = eNetBadPortString;
			 FailurePoint3:
				ArrayDeleteElement(PortArray,ArrayFindElement(PortArray,Port));
				goto FailurePoint2;
			}
		CopyData(PortString,(char*)&(Port->ThePPCPortRec.name[1]),PortStringLength);
		Port->ThePPCPortRec.name[0] = PortStringLength; /* stupid pascal strings */
		Port->ThePPCPortRec.portKindSelector = ppcByString;
		CopyData(DEFAULTTYPE,(char*)&(Port->ThePPCPortRec.u.portTypeStr[1]),
			DEFAULTTYPELENGTH);
		Port->ThePPCPortRec.u.portTypeStr[0] = DEFAULTTYPELENGTH;
		/* initialize the Openrec */
		ThePPCOpenPBRec.serviceType = ppcServiceRealTime;
		ThePPCOpenPBRec.resFlag = 0;
		ThePPCOpenPBRec.portName = &(Port->ThePPCPortRec);
		ThePPCOpenPBRec.locationName = NIL;
		ThePPCOpenPBRec.networkVisible = True;
		/* try to open it */
		Error = PPCOpen(&ThePPCOpenPBRec,False/*synchronous*/);
		switch (Error)
			{
				case noErr:
					Port->ThePortRefnum = ThePPCOpenPBRec.portRefNum;
					break; /* continue on to the next phase */
				case badLocNameErr:
					ReturnValue = eNetBadPortString;
					goto FailurePoint3;
				case portNameExistsErr:
				case nbpDuplicate:
					ReturnValue = eNetPortInUse;
					goto FailurePoint3;
				default:
					ReturnValue = eNetUnknownError;
					goto FailurePoint3;
			}
		/* indicate that the callback is out there */
		Port->PortState = eAwaitingConnection;
		/* enable listen callback */
		ReinstallPPCInform(Port);
		/* put reference number out for caller */
		*PortOut = Port;
		return eNetNoError;
	}


/* Stop listening at a port and let the OS use it for someone else.  Sessions */
/* established through this port are terminated. */
void								NetTerminatePortAndSessions(PortIDType* Port)
	{
		OSErr							Error;
		PPCClosePBRec			TheCloseRec;
		long							Scan;
		long							Limit;

		ERROR(!Initialized,PRERR(ForceAbort,
			"NetTerminatePortAndSessions:  not initialized"));
		CheckPtrExistence(Port);
		/* close any sessions registered through this port */
		Limit = ArrayGetLength(SessionArray);
		Scan = 0;
		while (Scan < Limit)
			{
				SessionIDType*			Session;

				Session = (SessionIDType*)ArrayGetElement(SessionArray,Scan);
				if (Session->Port == Port)
					{
						/* clean up our local data structures */
						NetCloseSession(Session);
						/* decrement limit, since we just dropped current element from array */
						Limit -= 1;
					}
				 else
					{
						/* only increment this if we didn't delete the element */
						Scan += 1;
					}
			}
		/* close the port itself */
		TheCloseRec.portRefNum = Port->ThePortRefnum;
		Error = PPCClose(&TheCloseRec,False/*synchronous*/);
		ERROR(Error != noErr,PRERR(AllowResume,
			"NetTerminatePortAndSessions:  return from PPCClose != noErr"));
		ArrayDeleteElement(PortArray,ArrayFindElement(PortArray,Port));
		ReleasePtr((char*)Port);
	}


/* Open a session to another (or the same) machine.  Returns a session number in */
/* *SessionOut.  PortString is the remote port to connect to, as described above, */
/* and MachineString is a machine string determined by the network stack.  For */
/* instance, TCP/IP would support standard a.b.c.d or machine.zone.domain format. */
/* The Macintosh uses machine:type@zone.  Could return eNetNoError, eNetNoMemory, */
/* eNetBadPortString, eNetBadMachineString, eNetMachineUnknown, eNetUnknownError, */
/* eNetConnectRefused, or eNetProtocolNotSupported. */
NetErrors						NetOpenSession(char* PortString, char* MachineString,
											SessionIDType** SessionOut, NetworkTypes WhichNetwork)
	{
		PPCStartPBRec			ThePPCStartPBRec;
		OSErr							Error;
		long							PortNameLength;
		SessionIDType*		Session;
		NetErrors					ReturnValue;
		long							TryCounter;
		unsigned char			UserNamePlace[32];

		ERROR(!Initialized,PRERR(ForceAbort,"NetOpenSession:  not initialized"));
		CheckPtrExistence(PortString);
		CheckPtrExistence(MachineString);
		ERROR(SessionOut == NIL,PRERR(ForceAbort,"NetOpenSession:  SessionOut == NIL"));
		/* verify network connection requested */
		switch (WhichNetwork)
			{
				case eNetDefault:
				case eNetAppleTalk:
					break;
				case eNetTCP:
					return eNetProtocolNotSupported;
				default:
					EXECUTE(PRERR(ForceAbort,"NetListenAtPort:  bad protocol specified"));
					break;
			}
		/* allocate a place for it */
		Session = (SessionIDType*)AllocPtrCanFail(sizeof(SessionIDType),"SessionIDType");
		if (Session == NIL)
			{
				ReturnValue = eNetNoMemory;
			 FailurePoint1:
				return ReturnValue;
			}
		if (!ArrayAppendElement(SessionArray,Session))
			{
				ReturnValue = eNetNoMemory;
			 FailurePoint2:
				ReleasePtr((char*)Session);
				goto FailurePoint1;
			}
		/* initialize the ppc record */
		EXECUTE(ThePPCStartPBRec.ioCompletion = NewPPCCompProc((void*)0x81818181);)
		ThePPCStartPBRec.portRefNum = AppleTalkSystemPort->ThePortRefnum;
		ThePPCStartPBRec.serviceType = ppcServiceRealTime;
		ThePPCStartPBRec.resFlag = 0;
		/* see if this connection is for this machine or another */
		PortNameLength = PtrSize(PortString);
		if (PortNameLength > 31)
			{
				ReturnValue = eNetBadPortString;
			 FailurePoint3:
				ArrayDeleteElement(SessionArray,ArrayFindElement(SessionArray,Session));
				goto FailurePoint2;
			}
		CopyData(PortString,(char*)&(Session->RemotePortName.name[1]),PortNameLength);
		Session->RemotePortName.name[0] = PortNameLength;
		Session->RemotePortName.nameScript = smRoman;
		Session->RemotePortName.portKindSelector = ppcByString;
		CopyData(DEFAULTTYPE,(char*)&(Session->RemotePortName.u.portTypeStr[1]),
			DEFAULTTYPELENGTH);
		Session->RemotePortName.u.portTypeStr[0] = DEFAULTTYPELENGTH;
		ThePPCStartPBRec.portName = &(Session->RemotePortName);
		if ((PtrSize(MachineString) != 9) && (PtrSize(MachineString) != 0))
			{
				char							MyNBPObjectStr[32];
				char							MyNBPTypeStr[32];
				char							MyNBPZoneStr[32];

				/* remote machine specified */
			 RemoteName:
				if (!DecodeMachineName(MachineString,MyNBPObjectStr,MyNBPTypeStr,MyNBPZoneStr))
					{
						ReturnValue = eNetBadMachineString;
						goto FailurePoint2;
					}
				CopyData(&(MyNBPObjectStr[0]),(char*)&(Session->RemoteLocationName
					.u.nbpEntity.objStr),MyNBPObjectStr[0] + 1);
				CopyData(&(MyNBPTypeStr[0]),(char*)&(Session->RemoteLocationName
					.u.nbpEntity.typeStr),MyNBPTypeStr[0] + 1);
				CopyData(&(MyNBPZoneStr[0]),(char*)&(Session->RemoteLocationName
					.u.nbpEntity.zoneStr),MyNBPZoneStr[0] + 1);
				Session->RemoteLocationName.locationKindSelector = ppcNBPLocation;
				ThePPCStartPBRec.locationName = &(Session->RemoteLocationName);
			}
		 else
			{
				if (PtrSize(MachineString) == 9)
					{
						long							Scan;

						for (Scan = 0; Scan < 9; Scan += 1)
							{
								if ("localhost"[Scan] != MachineString[Scan])
									{
										goto RemoteName;
									}
							}
					}
				/* it's the local machine */
				Session->RemoteLocationName.locationKindSelector = ppcNoLocation;
				ThePPCStartPBRec.locationName = &(Session->RemoteLocationName);
			}
		ThePPCStartPBRec.userData = 0; /* users are not used */
		if (noErr != GetDefaultUser(&(ThePPCStartPBRec.userRefNum),UserNamePlace))
			{
				ThePPCStartPBRec.userRefNum = 0;
			}
		TryCounter = MAXNUMTRIES;
	 TryAgainPoint:
		Error = PPCStart(&ThePPCStartPBRec,False/*sync*/);
		switch (Error)
			{
				default:
					ReturnValue = eNetUnknownError;
					goto FailurePoint3;
				case destPortErr:
				case noResponseErr:
				case userRejectErr:
				case localOnlyErr:
				case guestNotAllowedErr:
					ReturnValue = eNetConnectRefused;
					goto FailurePoint3;
				case noErr:
					break; /* continue */
				case noInformErr:
					TryCounter -= 1;
					/* sleep a little bit */
					{
						double			Now;

						Now = ReadTimer();
						while (TimerDifference(ReadTimer(),Now) < TRYDELAY)
							{
								RelinquishCPUCheckCancel();
							}
					}
					/* see if we should try again */
					if (TryCounter > 0)
						{
							goto TryAgainPoint;
						}
					ReturnValue = eNetConnectRefused;
					goto FailurePoint3;
				case portClosedErr:
				case badPortNameErr:
				case noUserRecErr:
				case noPortErr:
				case badServiceMethodErr:
				case nameTypeErr:
					EXECUTE(PRERR(ForceAbort,"NetOpenSession:  bad error code"));
					break;
			}
		/* initialize all of the structure's parameters */
		Session->SessionRefnum = ThePPCStartPBRec.sessRefNum;
		Session->Port = AppleTalkSystemPort;
		Session->WriteState = eWriteIdle;
		Session->ReadState = eReadIdle;
		Session->OverallState = eSessionNormal;
		Session->SendHead = NIL;
		Session->SendTail = NIL;
		Session->SendInProgress = NIL;
		Session->ReceiveHead = NIL;
		Session->ReceiveTail = NIL;
		Session->ReceiveInProgress = NIL;
		*SessionOut = Session;
		return eNetNoError;
	}


/* utility routine to decode string of format <name>:<type>@<zone> */
/* we allow the omission of <type> and automatically substitute DEFAULTTYPE for it */
/* you can omit the zone name as well. */
static MyBoolean		DecodeMachineName(char* MachineStr, char ObjStr[32],
											char TypeStr[32], char ZoneStr[32])
	{
		long							Limit;
		long							Scan;

		Limit = PtrSize(MachineStr);
		Scan = 0;
		while ((Scan < Limit) && (MachineStr[Scan] != ':') && (MachineStr[Scan] != '@'))
			{
				if (Scan >= 31)
					{
						return False;
					}
				ObjStr[Scan + 1] = MachineStr[Scan];
				Scan += 1;
			}
		ObjStr[0] = Scan;
		if ((Scan == Limit) || (MachineStr[Scan] == '@'))
			{
				/* type name omitted */
				CopyData(DEFAULTTYPE,&(TypeStr[1]),DEFAULTTYPELENGTH);
				TypeStr[0] = DEFAULTTYPELENGTH;
				goto ElidedTypePoint;
			}
		MachineStr += Scan + 1;
		Limit -= Scan + 1;
		Scan = 0;
		while ((Scan < Limit) && (MachineStr[Scan] != '@'))
			{
				if (Scan >= 31)
					{
						return False;
					}
				TypeStr[Scan + 1] = MachineStr[Scan];
				Scan += 1;
			}
		TypeStr[0] = Scan;
	 ElidedTypePoint:
		if (Scan == Limit)
			{
				/* no zone name */
				ZoneStr[0] = 0;
				return True;
			}
		MachineStr += Scan + 1;
		Limit -= Scan + 1;
		Scan = 0;
		while (Scan < Limit)
			{
				if (Scan >= 31)
					{
						return False;
					}
				ZoneStr[Scan + 1] = MachineStr[Scan];
				Scan += 1;
			}
		ZoneStr[0] = Scan;
		return True;
	}


/* Close a session.  Any waiting data in either direction is discarded. */
void								NetCloseSession(SessionIDType* Session)
	{
		OSErr							Error;
		PPCEndPBRec				EndRec;
		BufferRec*				BuffScan;

		ERROR(!Initialized,PRERR(ForceAbort,"NetCloseSession:  not initialized"));
		CheckPtrExistence(Session);
		/* first, kill the session to complete any reads/writes in progress. */
		/* this might fail if the session is already dead. */
		EndRec.sessRefNum = Session->SessionRefnum;
		Error = PPCEnd(&EndRec,False/*sync*/);
		/* now that that's finished, we can dump the buffers */
		if (Session->ReceiveInProgress != NIL)
			{
				ReleasePtr((char*)(Session->ReceiveInProgress));
			}
		if (Session->SendInProgress != NIL)
			{
				ReleasePtr((char*)(Session->SendInProgress));
			}
		BuffScan = Session->SendHead;
		while (BuffScan != NIL)
			{
				BufferRec*				BuffTemp;

				BuffTemp = BuffScan;
				BuffScan = BuffScan->Next;
				ReleasePtr((char*)BuffTemp);
			}
		BuffScan = Session->ReceiveHead;
		while (BuffScan != NIL)
			{
				BufferRec*				BuffTemp;

				BuffTemp = BuffScan;
				BuffScan = BuffScan->Next;
				ReleasePtr((char*)BuffTemp);
			}
		/* now dump the session record */
		ArrayDeleteElement(SessionArray,ArrayFindElement(SessionArray,Session));
		ReleasePtr((char*)Session);
	}


/* utility routine to submit a new read operation if one has completed */
static void					UpdateRead(SessionIDType* Session)
	{
		OSErr							Error;

		CheckPtrExistence(Session);
		ERROR(Session->ReadState == eReadInProgress,PRERR(ForceAbort,
			"UpdateRead:  read is already in progress"));
		/* handle the data that comes back from a read operation. */
		if (Session->ReadState == eReadFinished)
			{
				CheckPtrExistence(Session->ReceiveInProgress);
				/* check for session closed notification */
				if (Session->ThePPCReadPBRec.ioResult == sessClosedErr)
					{
						Session->OverallState = eSessionClosed;
					}
				ERROR((Session->ThePPCReadPBRec.ioResult != sessClosedErr)
					&& (Session->ThePPCReadPBRec.ioResult != noErr),PRERR(AllowResume,
					"UpdateRead:  error returned from PPCRead"));
				/* get read length */
				Session->ReceiveInProgress->NumBytes = Session->ThePPCReadPBRec.actualLength;
				ERROR((Session->ReceiveInProgress->NumBytes < 0)
					|| (Session->ReceiveInProgress->NumBytes > BUFFERSIZE),PRERR(ForceAbort,
					"UpdateRead:  too many bytes returned from PPCRead"));
				if (Session->ReceiveInProgress->NumBytes != 0)
					{
						/* move this buffer onto the list */
						Session->ReceiveInProgress->Next = NIL;
						if (Session->ReceiveTail != NIL)
							{
								CheckPtrExistence(Session->ReceiveTail);
								Session->ReceiveTail->Next = Session->ReceiveInProgress;
							}
						 else
							{
								Session->ReceiveHead = Session->ReceiveInProgress;
							}
						Session->ReceiveTail = Session->ReceiveInProgress;
					}
				 else
					{
						ReleasePtr((char*)(Session->ReceiveInProgress));
					}
				/* clear buffer register */
				Session->ReceiveInProgress = NIL;
				/* indicate that data has arrived */
				if (Session->OverallState == eSessionNormal)
					{
						/* don't change this if it contains eSessionClosed! */
						Session->OverallState = eSessionDataArrived;
					}
				/* reset read state */
				Session->ReadState = eReadIdle;
			}
		/* if the session has closed with the previous read, don't retrigger the read */
		/* but instead just exit */
		if (Session->OverallState == eSessionClosed)
			{
				return;
			}
		/* retrigger the read operation on a new block */
		Session->ReceiveInProgress = (BufferRec*)AllocPtrCanFail(
			sizeof(BufferRec),"ReadBufferRec");
		if (Session->ReceiveInProgress == NIL)
			{
				return;
			}
		Session->ThePPCReadPBRec.ioCompletion = NewPPCCompProc(&MyPPCReadCompletionRoutine);
		Session->ThePPCReadPBRec.sessRefNum = Session->SessionRefnum;
		Session->ThePPCReadPBRec.bufferLength = BUFFERSIZE;
		Session->ThePPCReadPBRec.bufferPtr = &(Session->ReceiveInProgress->Buffer[0]);
		Session->ReadState = eReadInProgress; /* BEFORE the call! */
		Error = PPCRead(&(Session->ThePPCReadPBRec),True/*async*/);
	}


/* utility routine to submit another write if one has completed */
static void					UpdateWrite(SessionIDType* Session)
	{
		OSErr							Error;

		CheckPtrExistence(Session);
		ERROR((Session->WriteState == eWriteInProgress),PRERR(ForceAbort,
			"UpdateWrite:  write is already in progress"));
		/* if a write has completed, then handle it */
		if (Session->WriteState == eWriteFinished)
			{
				/* check for session closed notification */
				if (Session->ThePPCWritePBRec.ioResult == sessClosedErr)
					{
						Session->OverallState = eSessionClosed;
					}
				ERROR((Session->ThePPCWritePBRec.ioResult != sessClosedErr)
					&& (Session->ThePPCWritePBRec.ioResult != noErr),PRERR(AllowResume,
					"UpdateWrite:  error returned from PPCWrite"));
				/* reset write state stuff */
				Session->WriteState = eWriteIdle;
				/* dispose of the write buffer block */
				ReleasePtr((char*)(Session->SendInProgress));
				Session->SendInProgress = NIL;
			}
		/* if there is more data queued to be sent, then send it */
		if (Session->SendHead != NIL)
			{
				CheckPtrExistence(Session->SendHead);
				/* move the first block to the in progress register */
				Session->SendInProgress = Session->SendHead;
				/* pop the block off the queue */
				Session->SendHead = Session->SendHead->Next;
				if (Session->SendHead == NIL)
					{
						Session->SendTail = NIL;
					}
				/* construct the parameter record */
				Session->ThePPCWritePBRec.ioCompletion = NewPPCCompProc(&MyPPCWriteCompletionRoutine);
				Session->ThePPCWritePBRec.sessRefNum = Session->SessionRefnum;
				Session->ThePPCWritePBRec.bufferLength = Session->SendInProgress->NumBytes;
				Session->ThePPCWritePBRec.bufferPtr = &(Session->SendInProgress->Buffer[0]);
				Session->ThePPCWritePBRec.more = False; /* always false */
				Session->ThePPCWritePBRec.userData = 0;
				Session->ThePPCWritePBRec.blockCreator = 0;
				Session->ThePPCWritePBRec.blockType = 0;
				Session->WriteState = eWriteInProgress; /* BEFORE the call! */
				Error = PPCWrite(&(Session->ThePPCWritePBRec),True/*async*/);
			}
	}


/* Verify that a session is still usable.  Returns True if the session is still */
/* available, or False if the remote system disconnected it.  If it returns False, */
/* then you should call NetCloseSession to dispose of the session record. */
MyBoolean						NetIsSessionStillAlive(SessionIDType* Session)
	{
		ERROR(!Initialized,PRERR(ForceAbort,"NetIsSessionStillAlive:  not initialized"));
		CheckPtrExistence(Session);
		return (Session->OverallState != eSessionClosed);
	}


/* Obtain a string identifying the machine from which a session has been */
/* established.  The string is a non-null-terminated heap block. */
char*								NetSessionGetRemoteMachineName(SessionIDType* Session)
	{
		char							LocalBuffer[32 + 32 + 32 + 1 + 1];
		long							Index;
		long							Scan;
		char*							Pointer;

		ERROR(!Initialized,PRERR(ForceAbort,
			"NetSessionGetRemoteMachineName:  not initialized"));
		CheckPtrExistence(Session);
		switch (Session->RemoteLocationName.locationKindSelector)
			{
				default:
					EXECUTE(PRERR(ForceAbort,
						"NetSessionGetRemoteMachineName:  unknown locationKindSelector"));
					break;
				case ppcNoLocation:
					Index = 9;
					CopyData("localhost",LocalBuffer,9);
					break;
				case ppcNBPLocation:
					Index = 0;
					for (Scan = 0; Scan < Session->RemoteLocationName
						.u.nbpEntity.objStr[0]; Scan += 1)
						{
							LocalBuffer[Index++] = Session->RemoteLocationName
								.u.nbpEntity.objStr[Scan + 1];
						}
					if (Session->RemoteLocationName.u.nbpEntity.typeStr[0] != 0)
						{
							LocalBuffer[Index++] = ':';
						}
					for (Scan = 0; Scan < Session->RemoteLocationName
						.u.nbpEntity.typeStr[0]; Scan += 1)
						{
							LocalBuffer[Index++] = Session->RemoteLocationName
								.u.nbpEntity.typeStr[Scan + 1];
						}
					if (Session->RemoteLocationName.u.nbpEntity.zoneStr[0] != 0)
						{
							LocalBuffer[Index++] = '@';
						}
					for (Scan = 0; Scan < Session->RemoteLocationName
						.u.nbpEntity.zoneStr[0]; Scan += 1)
						{
							LocalBuffer[Index++] = Session->RemoteLocationName
								.u.nbpEntity.zoneStr[Scan + 1];
						}
					break;
			}
		Pointer = AllocPtrCanFail(Index,"NBPNameString");
		if (Pointer != NIL)
			{
				CopyData(&(LocalBuffer[0]),&(Pointer[0]),Index);
			}
		return Pointer;
	}


/* Find out how much data is waiting to be read from the port. */
long								NetHowMuchDataToRead(SessionIDType* Session)
	{
		long							DataCount;
		BufferRec*				BufferScan;

		ERROR(!Initialized,PRERR(ForceAbort,"NetHowMuchDataToRead:  not initialized"));
		CheckPtrExistence(Session);
		BufferScan = Session->ReceiveHead;
		DataCount = 0;
		while (BufferScan != NIL)
			{
				DataCount += BufferScan->NumBytes;
				BufferScan = BufferScan->Next;
			}
		return DataCount;
	}


/* Find out how much data is waiting in local buffers to be written to a port. */
long								NetHowMuchDataToWrite(SessionIDType* Session)
	{
		long							DataCount;
		BufferRec*				BufferScan;

		ERROR(!Initialized,PRERR(ForceAbort,"NetHowMuchDataToWrite:  not initialized"));
		CheckPtrExistence(Session);
		/* initiate another read if there isn't one pending */
		if (Session->ReadState == eReadIdle)
			{
				UpdateRead(Session);
			}
		/* count data in buffers waiting to be eaten */
		BufferScan = Session->SendHead;
		DataCount = 0;
		while (BufferScan != NIL)
			{
				DataCount += BufferScan->NumBytes;
				BufferScan = BufferScan->Next;
			}
		return DataCount;
	}


/* Read data from a session.  It is an error to read more data than there is waiting. */
void								NetReadData(SessionIDType* Session, char* Buffer, long NumBytes)
	{
		ERROR(!Initialized,PRERR(ForceAbort,"NetReadData:  not initialized"));
		CheckPtrExistence(Session);
		ERROR((NumBytes < 0) || (NumBytes > NetHowMuchDataToRead(Session)),
			PRERR(ForceAbort,"NetReadData:  number of bytes is out of range"));
		/* extract data from blocks */
		while (NumBytes > 0)
			{
				long							TempNumBytes;
				BufferRec*				TempBuffer;

				TempBuffer = Session->ReceiveHead;
				CheckPtrExistence(TempBuffer);
				TempNumBytes = TempBuffer->NumBytes;
				CopyData(&(TempBuffer->Buffer[0]),&(Buffer[0]),TempNumBytes);
				Session->ReceiveHead = TempBuffer->Next;
				NumBytes -= TempNumBytes;
				Buffer += TempNumBytes;
				ReleasePtr((char*)TempBuffer);
			}
		if (Session->ReceiveHead == NIL)
			{
				Session->ReceiveTail = NIL;
			}
		/* initiate another read if there isn't one pending */
		if (Session->ReadState == eReadIdle)
			{
				UpdateRead(Session);
			}
	}


/* Write data to a session.  If data could not be sent without blocking, then */
/* it is locally buffered until it can be sent (that's what NetUpdate is for) */
/* returns True if successful, or False if there isn't enough memory.  if it fails, */
/* then NO data is written (i.e. data is never partially written) */
MyBoolean						NetWriteData(SessionIDType* Session, char* Buffer, long NumBytes)
	{
		BufferRec*				LocalHead;
		BufferRec*				LocalTail;
		BufferRec*				BuffScan;
		long							TempByteCount;

		ERROR(!Initialized,PRERR(ForceAbort,"NetWriteData:  not initialized"));
		CheckPtrExistence(Session);
		/* we create the buffers totally first, and only submit them if we could */
		/* allocate ALL of them */
		LocalHead = NIL;
		LocalTail = NIL;
		TempByteCount = NumBytes;
		while (TempByteCount > 0)
			{
				BufferRec*				TempBuff;

				if (TempByteCount > BUFFERSIZE)
					{
						TempByteCount -= BUFFERSIZE;
					}
				 else
					{
						TempByteCount -= TempByteCount;
					}
				TempBuff = (BufferRec*)AllocPtrCanFail(sizeof(BufferRec),"BufferRec");
				if (TempBuff == NIL)
					{
						while (LocalHead != NIL)
							{
								/* dump ones that we already allocated */
								CheckPtrExistence(LocalHead);
								TempBuff = LocalHead;
								LocalHead = LocalHead->Next;
								ReleasePtr((char*)TempBuff);
							}
						return False; /* oops */
					}
				TempBuff->Next = NIL;
				if (LocalTail != NIL)
					{
						LocalTail->Next = TempBuff;
					}
				 else
					{
						LocalHead = TempBuff;
					}
				LocalTail = TempBuff;
			}
		/* buffers allocated, now fill them */
		TempByteCount = NumBytes;
		BuffScan = LocalHead;
		while (TempByteCount > 0)
			{
				long							NumBytesThisTime;

				if (TempByteCount > BUFFERSIZE)
					{
						NumBytesThisTime = BUFFERSIZE;
					}
				 else
					{
						NumBytesThisTime = TempByteCount;
					}
				CheckPtrExistence(BuffScan);
				BuffScan->NumBytes = NumBytesThisTime;
				CopyData(&(Buffer[0]),&(BuffScan->Buffer[0]),NumBytesThisTime);
				Buffer += NumBytesThisTime;
				TempByteCount -= NumBytesThisTime;
				BuffScan = BuffScan->Next;
			}
		ERROR(BuffScan != NIL,PRERR(ForceAbort,
			"NetWriteData:  BuffScan not NIL after buffer fill loop"));
		/* now, tack it on the end of the write buffer list */
		if (Session->SendTail != NIL)
			{
				Session->SendTail->Next = LocalHead;
			}
		 else
			{
				Session->SendHead = LocalHead;
			}
		Session->SendTail = LocalTail;
		/* if there is no outstanding write, then make a write */
		if (Session->WriteState == eWriteIdle)
			{
				UpdateWrite(Session);
			}
		/* successful */
		return True;
	}


#ifdef THINK_C
	#if __option(profile)
		#define Profiling (True)
	#else
		#define Profiling (False)
	#endif

	#pragma options(!profile)
#endif

static pascal void	MyPPCInformCompletionRoutine(PPCInformPBRec* PB)
	{
		PortIDType*				Port;

		/* some goofy pointer arithmetic to get from a member of the struct */
		/* back to the beginning of the struct. */
		Port = (PortIDType*)((char*)PB - (long)&(((PortIDType*)NIL)->ThePPCInformPBRec));
		/* now we can access the other members */
		Port->PortState = eConnectionPending; /* indicate that PPCInform is done. */
	}

static pascal void	MyPPCReadCompletionRoutine(PPCParamBlockRec* PB)
	{
		SessionIDType*		Session;

		/* some goofy pointer arithmetic to get from a member of the struct */
		/* back to the beginning of the struct. */
		Session = (SessionIDType*)((char*)PB
			- (long)&(((SessionIDType*)NIL)->ThePPCReadPBRec));
		/* now we can access the other members */
		Session->ReadState = eReadFinished;
	}

static pascal void	MyPPCWriteCompletionRoutine(PPCParamBlockRec* PB)
	{
		SessionIDType*		Session;

		/* some goofy pointer arithmetic to get from a member of the struct */
		/* back to the beginning of the struct. */
		Session = (SessionIDType*)((char*)PB
			- (long)&(((SessionIDType*)NIL)->ThePPCWritePBRec));
		/* now we can access the other members */
		Session->WriteState = eWriteFinished;
	}

#ifdef THINK_C
	#if Profiling
		#pragma options(profile)
	#endif
#endif
