Milán Major

netscape-revival

1 branch
Code

lib/mac/macsock/src/sockets.cp

// Network stuff
#include "CNetwork.h"
#include "sockets.h"
// stdclib
#include <assert.h>
#include <stdlib.h>
// PowerPlant
#include <UException.h>
// NSPR threads
#include "prthread.h"
#include "swkern.h"
#include "prmon.h"

// For HoldMemoryGlue and UnholdMemoryGlue
#include "macutil.h"

#ifdef assert
#undef assert
#endif

//#define SOCKET_DEBUG

#if !defined(DEBUG) && defined(SOCKET_DEBUG)
#error Bruce has forgotten to turn off SOCKET_DEBUG in this file again
#endif

#ifdef DEBUG
#define assert(x) if (!(x)) abort()
#else
#define assert(x) ((void)0)
#endif

#define MAX_OUTSTANDING_WRITES 1

#ifdef PROFILE
#pragma profile on
#endif


void WaitForAsyncIO(void)
{
	if (PR_CurrentThread() != gPrimaryThread) {			// DonÕt block main thread
		PR_EnterMonitor(_pr_async_io_intr);
		PR_Wait(_pr_async_io_intr, LL_MAXINT);			// Block until interesting I/O happens
		PR_ExitMonitor(_pr_async_io_intr);
	}
}


// 
// 
// 
// 
struct hostent *macsock_gethostbyaddr(const void *addr, int addrlen, int type)
{
	assert(false);
	return NULL;
}


// Errors returned:
// ENETDOWN - no MacTCP driver
// EPROTONOSUPPORT - bad socket type/protocol
// ENOBUFS - not enough space for another socket, or failure in socket creation routine
int macsock_socket(int domain, int type, int protocol)
{
	int		result;
	
	result = MacSocket::CreateSocket(domain, type, protocol);
	
	if (result < 0)
		return MacSocket::SocketError(result);
	else 
		return MacToStdSocketID(result);
}

// This one is strange, I've guessed what it does
// It sets socket options, but I am not sure of the return values
// Errors:
// EBADF - bad socket id
// EOPNOTSUPP - this op not supported. Should probably implement it
int macsock_ioctl(int sID, unsigned int request, void *value)
{
	MacSocket * s;
	if (!(s = MacSocket::GetSocket(StdToMacSocketID(sID))))
		return MacSocket::SocketError(EBADF);

	switch(request) 
	{
		case FIONBIO:	/* Non-blocking I/O */
			return MacSocket::SocketError(s->SetBlocking(*((Boolean*)value)));
		default :
			return MacSocket::SocketError(EOPNOTSUPP);
	}
}

// Connect
// check the arguments validity
// issue the connect call to the stream
// Errors:
//	EBADF -- bad socket id, bad MacTCP stream
//	EAFNOSUPPORT -- bad address format
//	EADDRINUSE -- we are listening, or duplicate socket
//  EINPROGRESS -- we are connecting right now
//  EISCONN -- already connected
//  ECONNREFUSED -- other side has closed, or open has failed
//  EALREADY -- we are connected
//  EINTR -- user interrupted
int macsock_connect(int sID, struct sockaddr *name, int namelen)
{
	MacSocket * s;
	if (!(s = MacSocket::GetSocket(StdToMacSocketID((sID)))))
		return MacSocket::SocketError(EBADF);
	if (namelen != sizeof(struct sockaddr_in))
		return MacSocket::SocketError(EAFNOSUPPORT);
	// Tell our stream to connect
	struct sockaddr_in * sock_a = (struct sockaddr_in *)name;
	return s->Connect(sock_a->sin_addr.s_addr, sock_a->sin_port);
}

// Errors:
// EBADF -- bad socket id
// EFAULT -- bad buffer pointer
// EPIPE -- connection is closed, or we are not connected
// EINTR -- user interrupted, everything might have been sent
// EIO -- misc io error
// EAGAIN -- we are out of memory right now, try again, we are non-blocking, and write would block
int macsock_write(int sID, const void *buffer, unsigned buflen)
{
	MacSocket * s;
	if (!(s = MacSocket::GetSocket(StdToMacSocketID(sID))))
		return MacSocket::SocketError(EBADF);
	if (buffer == NULL)
		return MacSocket::SocketError(EFAULT);
	if (buflen == 0)
		return 0;
	return s->Write(buffer, buflen);
}

// Errors:
// EBADF -- bad socket id
// EFAULT -- bad buffer
// EPIPE -- bad stream
int macsock_read(int sID, void *buf, unsigned nbyte)
{
	MacSocket * s;
	
	if (!(s = MacSocket::GetSocket(StdToMacSocketID(sID))))
		return MacSocket::SocketError(EBADF);
	if (buf == NULL)
		return MacSocket::SocketError(EFAULT);
	if (nbyte == 0)
		return 0;
	return s->Read(buf, nbyte);
}

// Errors:
// EBADF -- bad socket id
int macsock_close(int sID)
{
	MacSocket * s;
	if (!(s = MacSocket::GetSocket(StdToMacSocketID(sID))))
		return MacSocket::SocketError(EBADF);
	return MacSocket::SocketError(s->Destroy(false));
}

// Errors:
// EBADF -- bad socket id
// EFAULT -- bad address format
int macsock_bind(int sID, const struct sockaddr *name, int namelen)
{
	MacSocket * s;
	if (!(s = MacSocket::GetSocket(StdToMacSocketID(sID))))
		return MacSocket::SocketError(EBADF);
	if ((name == NULL) || (namelen != sizeof(sockaddr_in)))
		return MacSocket::SocketError(EFAULT);
	sockaddr_in * sadress = (sockaddr_in *)name;
	return MacSocket::SocketError(s->Bind(sadress->sin_addr.s_addr, sadress->sin_port));
}

// Errors:
// EBADF -- bad socket id
// EOPNOTSUPP -- socket is already connected, and closing
// EISCONN -- already connected
// EINPROGRESS -- connecting right now
int macsock_listen(int sID, int backlog)
{
	MacSocket * s;
	if (!(s = MacSocket::GetSocket(StdToMacSocketID(sID))))
		return MacSocket::SocketError(EBADF);
	return MacSocket::SocketError(s->Listen(backlog));
}

int macsock_accept(int sID, struct sockaddr *addr, int *addrlen)
{
	MacSocket * s;
	if (!(s = MacSocket::GetSocket(StdToMacSocketID(sID))))
		return MacSocket::SocketError(EBADF);
	*addrlen = sizeof(sockaddr_in);
	struct sockaddr_in * sock_a = (struct sockaddr_in *)addr;
	return s->Connect(sock_a->sin_addr.s_addr, sock_a->sin_port);

}

int macsock_dup(int sID)
{
	assert(FALSE);
	return EBADF;
}

int macsock_getpeername(int sID, struct sockaddr *name, int *namelen)
{
	MacSocket * s;
	if (!(s = MacSocket::GetSocket(StdToMacSocketID(sID))))
		return MacSocket::SocketError(EBADF);
	if ((name == NULL) || (namelen == NULL) || *namelen != sizeof(sockaddr_in))
		return MacSocket::SocketError(EFAULT);
	struct sockaddr_in * sock_a = (struct sockaddr_in *)name;
	sock_a->sin_family = AF_INET;
	sock_a->sin_len = 4;
	return MacSocket::SocketError(s->GetPeerName(sock_a->sin_addr.s_addr, sock_a->sin_port));
}

int macsock_getsockname(int sID, struct sockaddr *name, int *namelen)
{
	MacSocket * s;
	if (!(s = MacSocket::GetSocket(StdToMacSocketID(sID))))
		return MacSocket::SocketError(EBADF);
	if ((name == NULL) || (*namelen <=0))
		return MacSocket::SocketError( EFAULT );
	struct sockaddr_in * sock_a = (struct sockaddr_in *)name;
	sock_a->sin_family = AF_INET;
	sock_a->sin_len = 4;
	return MacSocket::SocketError(s->GetSockName(sock_a->sin_addr.s_addr, sock_a->sin_port));
}


//
//
int macsock_send(int sID, const void *msg, int len, int flags)
{
	return macsock_sendto(sID, msg, len, flags, NULL, 0);
}


//
//
int macsock_sendto(int sID, const void *msg, int len, int flags, struct sockaddr *toAddr, int toLen)
{
	MacSocket *s;
	InetHost	host;
	InetPort	port;
	
	s = MacSocket::GetSocket(StdToMacSocketID(sID));
	
	if (NULL == s)
		return MacSocket::SocketError(EBADF);
	
	if (NULL == toAddr) {
		host = NULL;
		port = 0;
	}
	else {
		sockaddr_in * sadress = (sockaddr_in *)toAddr;
		host = sadress->sin_addr.s_addr;
		port = sadress->sin_port;
	}
	return s->SendTo(msg, len, flags, host, port);
}


// Errors:
// EBADF - bad socket ID
// ENOTCONN - no such connection
int macsock_recv(int sID, void *buf, int len, int flags)
{
	return macsock_recvfrom(sID, buf, len, flags, NULL, 0);
}


// Errors:
// EBADF - bad socket id
int macsock_recvfrom(int sID, void *buf, int len, int flags, struct sockaddr *from, int *fromLen)
{
	MacSocket * s;
	
	s = MacSocket::GetSocket(StdToMacSocketID(sID));
	
	if (s == NULL)
		return MacSocket::SocketError(EBADF);
	
	return s->ReceiveFrom(buf, len, flags, from, *fromLen);	
}


// Unimplemented socket options
int macsock_shutdown(int sID, int how) {assert(FALSE);return EBADF;}
int macsock_getsockopt(int sID, int level, int optname, void *optval,int *optlen) {assert(FALSE);return EBADF;}


// Errors:
// EBADF - bad socket id
// ENOTCONN - socket hasnÕt been properly created 
int macsock_setsockopt(int sID, int level, int optname, const void *optval, int optlen) 
{
	MacSocket*	s;
	
	s = MacSocket::GetSocket(StdToMacSocketID(sID));
	if (s == NULL)
		return MacSocket::SocketError(EBADF);
	
	return s->SetSocketOption(level, optname, optval);	
}


//
// Provide the IOCTL(FIORNREAD) functionality.
// Returns 0 for failure, 1 for success.
//
// Should we be doing a macsock_ioctl instead?  If we have more of these
// we probably should.
//
int macsock_socketavailable(int sID, size_t *bytesAvailable)
{
	MacSocket * s;
	size_t		available;
	OSStatus	result;
	
	s = MacSocket::GetSocket(StdToMacSocketID(sID));
	if (s == NULL)
		return 0;
	if (NULL == bytesAvailable)
		return 0;	

	result = s->SocketAvailable(available);
	
	if (result)
		*bytesAvailable = available;
	else
		*bytesAvailable = 0;
		
	return result;
}


// Currently ignores the timeout
int select (int nfds, fd_set *readfds, fd_set *writefds,
               fd_set *exceptfds, struct timeval *timeout)
{
	#pragma ignore (timeout)
	
	int howMany = nfds > MAX_SOCKETS ? MAX_SOCKETS : nfds;
	int ready = 0;
	for (int i=0; i<MAX_SOCKETS; i++)
		if ((readfds && FD_ISSET(i, readfds))
		 	|| (writefds && FD_ISSET(i, writefds))
		 	|| (exceptfds && FD_ISSET(i, exceptfds)))
		{
			Boolean readReady, writeReady, exceptReady;
			readReady = writeReady = exceptReady = FALSE;
			MacSocket * s = MacSocket::GetSocket(StdToMacSocketID(i));
			
			s && s->Select(readReady, writeReady, exceptReady);
			
			if (readfds) {
				if (readReady)
					FD_SET(i, readfds);
				else
					FD_CLR(i, readfds);
			}
			
			if (writefds) {
				if (writeReady)
					FD_SET(i, writefds);
				else
					FD_CLR(i, writefds);
			}
			
			if (exceptfds) {
				if (exceptReady)
					FD_SET(i, exceptfds);
				else
					FD_CLR(i, exceptfds);
			}
			
			if (readReady || writeReady || exceptReady)
				ready++;
		}
	return ready;
}


//=========================================================================================
// MacSocket
//=========================================================================================

Boolean	MacSocket::sNotified = FALSE;
UInt32 MacSocket::sCurrentSerialNumber = 1;
MacSocket* MacSocket::sSockets[MAX_SOCKETS];

// Mimics UNIX socket(int domain, int type, int protocol) call
Int16 MacSocket::CreateSocket(int domain, int type, int protocol)
{
	if (!gNetDriver->AssertOpen())
		return SocketError(ENETDOWN);	// Any better errors when there is no TCP?

	// We only deal with internet domain
	if (domain != AF_INET)
		return SocketError(EPROTONOSUPPORT);
	
	// We only know about tcp & udp
	if (!((type == SOCK_STREAM) || (type == SOCK_DGRAM)))
		return SocketError(EPROTONOSUPPORT);
	
	// Convert default types to specific types.
	if (protocol == 0)  {
		if (type == SOCK_DGRAM)
			protocol = IPPROTO_UDP;
		else if (type == SOCK_STREAM)
			protocol = IPPROTO_TCP;
	}
	
	// Only support default protocol for tcp
	if ((type == SOCK_STREAM)  && (protocol != IPPROTO_TCP))
		return SocketError(EPROTONOSUPPORT);
				
	// Only support default protocol for udp
	if ((type == SOCK_DGRAM)  && (protocol != IPPROTO_UDP))
		return SocketError(EPROTONOSUPPORT);
		
	// Find a place in the socket table
	Int16 sIndex = -1;
	for (int i=3; (i<MAX_SOCKETS) && (sIndex == -1); i++)
		if (sSockets[i] == NULL)
			sIndex = i;

	if (sIndex == -1)
		return SocketError(ENOBUFS);
	
	// Create a socket, we might run out of memory
	MacSocket * volatile s = NULL;
	Try_
	{
		s = new MacSocket(sIndex, type);
		int err = s->Create();
		ThrowIfOSErr_(err);
	}
	Catch_(inErr)
	{
		if (s)
			s->Destroy(TRUE);
		return SocketError(ENOBUFS);
	}
	EndCatch_
	return sIndex;
}

MacSocket::MacSocket(Int16 index, int type)
{
	::HoldMemoryGlue( this, sizeof (*this) );	// Because async completion routines access us
	fIndex = index;
	fBlocking = TRUE;
	fStream = gNetDriver->CreateStream(this);
	assert (sSockets[fIndex] == NULL);
	sSockets[index] = this;
	fReadReady = 
	fConnectException  = fCloseException = fDNSException = fListenException =
	fReadSelect = fConnectSelect = FALSE;
	fDNSLookup = NULL;
	fConnectStatus = eIdle;
	fOutstandingWrites = 0;
	fWriteStatus = 0;
	fType = type;
	fSerialNumber = sCurrentSerialNumber++;	// Needed to uniquely ID a socket.
}

MacSocket::~MacSocket()
{
	sSockets[fIndex] = NULL;
	if (fDNSLookup)
		fDNSLookup->SelfDestruct();
	if (fStream)
		fStream->SocketDeleted();
	// Compliments HoldMemory
	::UnholdMemoryGlue( this, sizeof (*this));
}

 
int MacSocket::StartAsyncDNSLookup(char * hostName, struct hostent ** hoststruct_ptr_ptr)
{
	gNetDriver->SpendTime();	// So that our DNS lookups get processed
	if (fDNSLookup == NULL)
	{
		Try_
		{
			fDNSLookup = gNetDriver->StrToAddress(hostName, this);
			ThrowIfNil_(fDNSLookup);
		}
		Catch_(inErr)
		{
			fDNSLookup = NULL;
			return -1;
		}
		EndCatch_
	}
					
	// DNS lookups might get back right away, give driver time to process
	gNetDriver->SpendTime();
	
	if (fDNSLookup->fStatus == kDNSInProgress)	// In progress
		return EAGAIN;
	else						// Lookup comlete
	{
		if (fDNSLookup->fStatus == kDNSDoneNoErr)
		// success
		{
			fDNSLookup->StoreResults();
			*hoststruct_ptr_ptr = &gNetDriver->fHostEnt;
			fDNSException = FALSE;	// DNS Exception has been processed
			delete fDNSLookup;
			return 0;
		}
		else
		// failure
		{
			delete fDNSLookup;
			fDNSException = FALSE;	// DNS Exception has been processed
			return -1;
		}
	}
}

void MacSocket::SetDNSLookupObject(CDNSObject* dns)
{
	fDNSLookup = dns;
}

void MacSocket::ClearDNSSelect()
{
	if (fDNSLookup)
	{
		fDNSLookup->SelfDestruct();
		fDNSLookup = NULL;
	}
}


// ¥¥ sock routines interface
int MacSocket::Create()
{
	return SocketError(fStream->CreateStream());
}

int MacSocket::Destroy(Boolean doAbort)
{
	if (fStream)
	{
		fCloseException = FALSE;
		fSerialNumber = 0;				// Make sure we clear these, 
		fWriteStatus = 0;				// other threads might be watching them
		fStream->DestroyStream(doAbort);
	}
	#ifdef SOCKET_DEBUG
	XP_Trace("Sockets: Destroyed socket %X with stream %X.\n", this, fStream);
	#endif
	
	delete this;
	return 0;	// Always successful
}

int MacSocket::SetBlocking(Boolean blocking)
{
	fBlocking = blocking;
	return 0;
}


Boolean MacSocket::Select(Boolean& readReady,Boolean& writeReady,Boolean& exceptReady)
{
	if (fStream == NULL)
	{
		exceptReady = true;
		return true;
	}

	fStream->Select(readReady,writeReady,exceptReady);
	exceptReady = exceptReady ||
				fConnectException || 
				fCloseException || 
				fDNSException;
	writeReady = writeReady
				&& ( fConnectStatus == eConnected )
				&& ( fOutstandingWrites < MAX_OUTSTANDING_WRITES );
	writeReady = writeReady || fListenException;	// Because you select on read for accept
	if (readReady || writeReady || exceptReady)
		return TRUE;
	return FALSE;
}

// INTERRUPT routine, no callbacks
void MacSocket::ConnectComplete(Boolean isListen, OSStatus err)
{
	fConnectException = TRUE;
	// Listening sockets
	if (isListen)
	{
		if (err == noErr)
			fConnectStatus = eListenSuccess;
		else
			fConnectStatus = eListenFailed;
	}
	else
	{
		if (err == noErr)
			fConnectStatus = eConnectSuccess;
		else
			fConnectStatus = eConnectFailed;
	}
}

/*
	Bridge between two interfaces
	Streams get called with connect only once
	Sockets get called repeatedly until success
	We keep a state machine in fConnectStatus
*/
int MacSocket::Connect(InetHost host, InetPort port)
{
	int err;
	if ( fStream == NULL )
		return SocketError(ECONNREFUSED);

	switch (fConnectStatus)	{
		case eIdle:
			fConnectStatus = eConnectInProgress;
			err = fStream->ActiveConnect(host, port);
			if ( (err == noErr) || (err != EINPROGRESS) )
			{
				fConnectException = FALSE;
				fConnectStatus = eIdle;
				return SocketError(0);
			}
			// Drop to the usual EINPROGRESS state
		case eConnectInProgress:
			// Here, err is EINPROGRESS
			gNetDriver->SpendTime();	// Process the pending completed requests

			if (fBlocking)				// We'll get notified at interrupt time
				while (fConnectStatus == eConnectInProgress)	
					gNetDriver->SpendTime();
			
			
			if (fConnectStatus == eConnectSuccess)
			{
				fConnectStatus = eConnected;
				fConnectException = FALSE;
				return 0;
			}
			else if (fConnectStatus == eConnectInProgress)
			{
				return SocketError(EINPROGRESS);
			}
			else	// CatchAll, status should be eConnectFailed here
			{
				fConnectStatus = eIdle;
				fConnectException = FALSE;
				return SocketError(ECONNREFUSED);
			}

			break;
		case eConnectSuccess:
			fConnectStatus = eConnected;
			fConnectException = FALSE;
			return 0;
		case eConnectFailed:
			fConnectStatus = eIdle;
			fConnectException = FALSE;
			return SocketError(ECONNREFUSED);
		case eListenInProgress:
		case eListenFailed:
		case eListenSuccess:
		case eAcceptInProgress:
		case eAcceptSuccess:
		case eAcceptFailed:
			fConnectStatus = eIdle;
			fConnectException = FALSE;
			return SocketError(EIO);	// Not really sure what to return here
		case eConnected:
			return 0;
	}
	return -1;	// Never reached
}


int MacSocket::Bind(InetHost host, InetPort port)
{
	if ( fStream == NULL )
		return ECONNREFUSED;
	int err;
loop:
	err = fStream->Bind(host, port);
	if (fBlocking && (err == EINPROGRESS))
	{	
		gNetDriver->SpendTime();	// Allow for processing of async notifications
		goto loop;
	}
	return SocketError(err);
}

// Similar to connect
// Can be called only once.
int MacSocket::Listen(int backlog)
{
	int err;
	switch (fConnectStatus)	{
		case eIdle:
			fConnectStatus = eListenInProgress;
			err = fStream->Listen(backlog);
			if ( err != noErr )
				return SocketError(err);
			else
				return SocketError(0);
		case eListenInProgress:
		case eListenSuccess:
		case eListenFailed:
		case eConnectFailed:
		case eConnectSuccess:
		case eConnected:
		case eAcceptInProgress:
		case eAcceptSuccess:
		case eAcceptFailed:
			return SocketError( EIO );
	}
	return SocketError(-1);	// Never reached
}

void MacSocket::AcceptComplete(OSStatus err)
{
	if (err != noErr)
		fConnectStatus = eAcceptFailed;
	else
		fConnectStatus = eAcceptSuccess;
}

int MacSocket::Accept(InetHost &host, InetPort &port)
{
	if ( fStream == NULL )
		return MacSocket::SocketError(ECONNREFUSED);

	gNetDriver->SpendTime();	// Process the pending completed requests
	switch (fConnectStatus)	{
		case eIdle:
			return SocketError( EPROTO );
		case eListenInProgress:
			return SocketError( EWOULDBLOCK );
		case eListenSuccess:
			fConnectStatus = eAcceptInProgress;
			int err =  fStream->Accept();
			if ( (err == EWOULDBLOCK) && fBlocking )
				while (fConnectStatus == eAcceptInProgress)
					gNetDriver->SpendTime();
			if (fConnectStatus == eAcceptSuccess)
			{
				fStream->GetPeerName(host, port);
				fConnectStatus = eConnected;
				return 0;
			}
			else
				return SocketError(err);
			break;
		case eAcceptSuccess:
			fConnectStatus = eConnected;
			fStream->GetPeerName(host, port);
			return SocketError( 0 );
		case eAcceptFailed:
		case eListenFailed:
			fConnectStatus  = eIdle;
			return SocketError( ECONNREFUSED );
		case eConnectFailed:
		case eConnectSuccess:
			return SocketError ( EIO );
		case eConnected:
			return SocketError ( EALREADY );
	} 
	return SocketError(EIO);
}



void MacSocket::WriteComplete(int err)
{
	fWriteStatus = err;
	fOutstandingWrites--;
//	assert( fOutstandingWrites >= 0 );
	if (fOutstandingWrites < 0)
	{
//		SysBeep(1);
// 		km - I commented out this beep to fix bug #31473.
//			 Mailing attachments over slow connections will
//			 run through this code thousands of times.
//			 For Dogbert we should review all SysBeep calls
//			 and justify their existence.
		fOutstandingWrites = 0;
	}
}

int MacSocket::Write (const void * buffer, unsigned int buflen)
{
	assert(fStream);
	
	if (fStream == NULL )
		return MacSocket::SocketError(EPIPE);

	if ( (buffer == NULL) || ( buflen == 0 ) )
		return SocketError(EFAULT);

	UInt16 bytesWritten;
	int err;

	gNetDriver->SpendTime();
	if (fOutstandingWrites >= MAX_OUTSTANDING_WRITES) 
		if (!fBlocking )
			return SocketError(EWOULDBLOCK);
		else
			while ( fOutstandingWrites >= MAX_OUTSTANDING_WRITES )
				gNetDriver->SpendTime();
				
	if ( (fWriteStatus != 0) && (fWriteStatus != EWOULDBLOCK))
		return SocketError(EPIPE);		// Our last write has failed

loop:	// Loop until we are able to write

	fWriteStatus = 1;
	if ( fOutstandingWrites >= MAX_OUTSTANDING_WRITES )	// Wait
		err = EWOULDBLOCK;
	else
	{
		fOutstandingWrites++;
		err = fStream->Write(buffer, buflen, bytesWritten);
		if (err != 0)
			fOutstandingWrites--;
	}

	if ( fBlocking &&  (err == EWOULDBLOCK) )
	{	
		gNetDriver->SpendTime();	// Allow for processing of async notifications
		goto loop;
	}
	// If we are broken while attempting to write, return an error
	if (err != noErr)
		return SocketError(err);
	// If we are blocking, loop till we are done
	if (fBlocking)
	{
		while (fWriteStatus == 1)
			gNetDriver->SpendTime();
		if (fWriteStatus < 0)
			return SocketError(fWriteStatus);
		else
			return buflen;
	}
	else
		return bytesWritten;
}


int MacSocket::Read(void * buffer, unsigned int buflen)
{
	if ( fStream == NULL )
		return SocketError(ENOTCONN);

	UInt16 bytesRead = 0;
	int err = fStream->Read(buffer, buflen, bytesRead);
			
	if (fBlocking && (err == EWOULDBLOCK))	{	
		// Tricky logic ahead
		// We need to spin.  If this is a read, we spin until the buffer is full.
		// If this is a recv, we read until the read is complete.
		buffer = (void*)((unsigned long)buffer + (unsigned long)bytesRead);
		buflen -= bytesRead;
	loop:	// Exit if: read enough to fill up the buffer || encounter an error
		assert(buflen >= 0);
		if (buflen <= 0)
			return bytesRead;
		err = fStream->Read(buffer, buflen, bytesRead);
		buffer = (void*)((unsigned long)buffer + (unsigned long)bytesRead);
		buflen -= bytesRead;
		
		if (buflen <= 0)				// Even if we got an error, we got the bytes we need
			return bytesRead;
			
		if ((err == noErr) || (err == EWOULDBLOCK))
		{	UInt32		currentSocketID = fSerialNumber;
		
			gNetDriver->SpendTime();			// Allow for processing of async notifications
			
			WaitForAsyncIO();

			// While we were gone, did another thread delete us?  
			// If we were deleted, did another thread reuse the memory?
			if ((fStream == NULL) || (currentSocketID != fSerialNumber))
				return SocketError(ENOTCONN);
			
			goto loop;
		}
		
		// err is something other than noErr & EWOULDBLOCK, bail.
		return SocketError(err);
	}
	
	else	// Nonblocking
	{
		if (err == noErr)
			return bytesRead;
		else
			return SocketError(err);
	}
}


	
int MacSocket::GetPeerName(InetHost& host, InetPort& port)
{
	if (fStream == NULL)
		return SocketError(ENOTCONN);
	else 
		return fStream->GetPeerName(host, port);
}


int MacSocket::GetSockName(InetHost& host, InetPort& port)
{
	if (fStream == NULL)
		return SocketError(ENOTCONN);
	else 
		return fStream->GetSockName(host, port);
}


int MacSocket::SendTo(const void *msg, int len, int flags, InetHost host, InetPort port)
{
	int		result;
		
	if (fStream == NULL)
		return SocketError(ENOTCONN);
		
	else if ((NULL == msg) || (0 == len))		// Do we have anything to send?
		return SocketError(EFAULT);
		
	else switch (fType) {
		case SOCK_STREAM:
				result = Write(msg, len);
				break;
		case SOCK_DGRAM:
				result = UDPSendTo(msg, len, flags, host, port);
				break;
		default:
				assert(false);
	}
	if (result < noErr)	
		return SocketError(result);
	else {
		SocketError(noErr);
		return result;
	}
}


int MacSocket::ReceiveFrom(void *buffer, int len, int flags, struct sockaddr *from, int& fromLen)
{
	int		result;
	
	if (NULL == fStream)
		return SocketError(ENOTCONN);
		
	if (NULL == buffer)
		return SocketError(EFAULT);
	
	// rcv() & rcvfrom() can be called on any type of socket connection.  We need to determine
	// which type of protocol is being used & do the proper type of read.
	switch (fType) {
		case SOCK_STREAM:
				result =  Read(buffer, len);
				break;
		case SOCK_DGRAM:
				result =  UDPReceiveFrom(buffer, len, flags, from, fromLen);
				break;
		default:
				assert(false);
	}
	if (result < noErr)							// Did an error occur?
		return SocketError(result);
	else {
		SocketError(noErr);
		return result;
	}
}


int MacSocket::UDPSendTo(const void *msg, int len, int flags, InetHost host, InetPort port)
{
	OSStatus		status;
	int				bytesWritten = 0;
	UInt32			currentSocketID = fSerialNumber;

	if (fStream == NULL)
		return SocketError(ENOTCONN);
		
	else if ((NULL == msg) || (0 == len))		// Do we have anything to send?
		return SocketError(EFAULT);

loop:		// Spin until we are able to write
	fWriteStatus = 1;
	
	if ( fOutstandingWrites >= MAX_OUTSTANDING_WRITES )	// Wait
		status = EWOULDBLOCK;
	else {
		fOutstandingWrites++;
		status = fStream->UDPSendTo(msg, len, flags, host, port, bytesWritten);
		if (status != 0)
			fOutstandingWrites--;
	}
	if ( fBlocking &&  (status == EWOULDBLOCK) ) {
		
		gNetDriver->SpendTime();	// Allow for processing of async notifications
		
		WaitForAsyncIO();
		
		// While we were gone, was this socket shut down & we were deleted?
		if ((fStream == NULL) || (currentSocketID != fSerialNumber))
			return SocketError(ENOTCONN);
		goto loop;
	}
	
	// We wrote.  Now spin & wait for the completion if we need to.
	if (fBlocking) {
		while (fWriteStatus == 1) {
			gNetDriver->SpendTime();
			WaitForAsyncIO();
		}
		if (fWriteStatus < noErr)
			return SocketError(fWriteStatus);
		else if (currentSocketID != fSerialNumber)			// Socket was closed underneath us.
			return SocketError(ENOTCONN);
		else
			return len;
	}
	else
		return bytesWritten;
	
}


int MacSocket::UDPReceiveFrom(void *buffer, int len, int flags, struct sockaddr *from, int& fromLen)
{
	OSStatus	status;
	UInt32		currentSocketID = fSerialNumber;
	
	while (true) {
		status = fStream->UDPReceiveFrom(buffer, len, flags, from, fromLen);
		
		if (fBlocking && (status == EWOULDBLOCK)) {			// ÒBlockÓ if we need to
			gNetDriver->SpendTime();						// Allow for processing of async notifications
			
			WaitForAsyncIO();
			
			// While we were gone, was this socket shut down & were we deleted?
			if ((fStream == NULL) || (currentSocketID != fSerialNumber))
				return SocketError(ENOTCONN);
				
			continue;
		}
		else				// If weÕre done, get out of Dodge
			break;
	}
	return status;
}


int MacSocket::SetSocketOption(int level, int optname, const void *optval)
{	
	if (fStream == NULL)
		return ENOTCONN;
	else
		return fStream->SetSocketOption(level, optname, optval);
}


int MacSocket::SocketAvailable(size_t& bytesAvailable)
{
	OSStatus	result;
	
	// Careful of the return value here.  0 => failure, 1 => success
	if (fStream == NULL)
		return 0;
	else
		result = fStream->BytesAvailable(bytesAvailable);
	
	return (result == noErr);
}



#ifdef PROFILE
#pragma profile off
#endif