diff options
author | Benjamin Barenblat <bbarenblat@gmail.com> | 2021-11-23 23:13:26 -0500 |
---|---|---|
committer | Benjamin Barenblat <bbarenblat@gmail.com> | 2021-11-23 23:13:26 -0500 |
commit | e5df1aafb6d1346207343ccb858fa373e6b86301 (patch) | |
tree | fb26f0091dda7dd69d48d6b06169ea618332b99e /SrcShared/SocketMessaging.cpp |
Check in the Palm OS Emulator, version 3.5 (2001). These files come from
the tarball present in the Debian archives [1]. The SHA-256 digest of
the tarball, c5e0d23424e88525bfba0ecdf0a432a8d93c885d04740df06a9eeee44e5f25e4,
matches the digest preserved in the FreeBSD ports tree [2], giving
further confidence that these files are as distributed by upstream.
[1] http://archive.debian.org/debian/pool/contrib/p/pose/
[2] https://svnweb.freebsd.org/ports/head/palm/pose/distinfo?revision=271305&view=markup&pathrev=282162
Diffstat (limited to 'SrcShared/SocketMessaging.cpp')
-rw-r--r-- | SrcShared/SocketMessaging.cpp | 1170 |
1 files changed, 1170 insertions, 0 deletions
diff --git a/SrcShared/SocketMessaging.cpp b/SrcShared/SocketMessaging.cpp new file mode 100644 index 0000000..4a1b9fc --- /dev/null +++ b/SrcShared/SocketMessaging.cpp @@ -0,0 +1,1170 @@ +/* -*- mode: C++; tab-width: 4 -*- */ +/* ===================================================================== *\ + Copyright (c) 1998-2001 Palm, Inc. or its subsidiaries. + All rights reserved. + + This file is part of the Palm OS Emulator. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. +\* ===================================================================== */ + +#include "EmCommon.h" +#include "SocketMessaging.h" + +#include "EmException.h" // EmExceptionReset +#include "EmSession.h" // EmSessionStopper +#include "Logging.h" // LogAppendMsg + +#include <algorithm> // find() + +#if PLATFORM_MAC +#include <errno.h> // ENOENT, errno +#include <sys/types.h> // u_short, ssize_t, etc. +#include <sys/socket.h> // sockaddr +#include <sys/errno.h> // Needed for error translation. +#include <sys/time.h> // fd_set +#include <netdb.h> // hostent +#include <unistd.h> // close +#include <sys/filio.h> // FIONBIO +#include <sys/ioctl.h> // ioctl +#include <netinet/in.h> // sockaddr_in +#endif + +#if PLATFORM_UNIX +#include <errno.h> // ENOENT, errno +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/time.h> // timeval +#include <sys/ioctl.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <netdb.h> +#include <unistd.h> // close +#endif + +// --------------------------------------------------------------------------- +// ¥ Stuff +// --------------------------------------------------------------------------- + +#define PRINTF if (!LogLLDebugger ()) ; else LogAppendMsg + + +// --------------------------------------------------------------------------- +// ¥ CSocket +// --------------------------------------------------------------------------- + +typedef vector<CSocket*> SocketList; +static SocketList gSockets; +static SocketList gSocketsToBeAdded; + + +/*********************************************************************** + * + * FUNCTION: CSocket::Startup + * + * DESCRIPTION: Create all the sockets we'll need for the application + * and start them listening for clients. Call this once + * at application startup. + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void CSocket::Startup (void) +{ +} + + +/*********************************************************************** + * + * FUNCTION: CSocket::Shutdown + * + * DESCRIPTION: Close all the sockets we've created. Call this once at + * application shutdown. + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +void CSocket::Shutdown (void) +{ + // Add any sockets waiting to be added to gSockets. + + CSocket::AddPending (); + + SocketList::iterator iter = gSockets.begin (); + while (iter != gSockets.end ()) + { + CSocket* s = *iter; + + if (!s->Deleted()) + { + s->Close (); + s->Delete(); + } + + ++iter; + } + + CSocket::DeletePending (); + + EmAssert (gSockets.size() == 0); + EmAssert (gSocketsToBeAdded.size() == 0); +} + + +/*********************************************************************** + * + * FUNCTION: CSocket::IdleAll + * + * DESCRIPTION: Idle all of the created sockets. + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +ErrCode CSocket::IdleAll (void) +{ + // Prevent recursion + static Boolean inIdleAll; + if (inIdleAll) + return errNone; + + inIdleAll = true; + +// PRINTF ("CSocket::IdleAll -- calling AddPending (1)..."); + CSocket::AddPending (); + +// PRINTF ("CSocket::IdleAll -- calling DeletePending (1)..."); + CSocket::DeletePending (); + + // Iterate over all the (non-deleted) sockets and Idle them. + + ErrCode err = errNone; + SocketList::iterator iter = gSockets.begin (); + + while (iter != gSockets.end ()) + { + if (!(*iter)->Deleted ()) + { +// PRINTF ("CSocket::IdleAll -- Idling (0x%08X)...", (*iter)); + err = (*iter)->Idle (); + if (err != errNone) + break; + } + + ++iter; + } + +// PRINTF ("CSocket::IdleAll -- calling AddPending (2)..."); + CSocket::AddPending (); + +// PRINTF ("CSocket::IdleAll -- calling DeletePending (2)..."); + CSocket::DeletePending (); + + inIdleAll = false; + + return err; +} + + +/*********************************************************************** + * + * FUNCTION: CSocket::AddPending + * + * DESCRIPTION: DESCRIPTION + * + * PARAMETERS: None + * + * RETURNED: Nothing + * + ***********************************************************************/ + +void CSocket::AddPending (void) +{ + SocketList::iterator iter = gSocketsToBeAdded.begin (); + while (iter != gSocketsToBeAdded.end ()) + { + gSockets.push_back (*iter); + ++iter; + } + + gSocketsToBeAdded.clear(); +} + + +/*********************************************************************** + * + * FUNCTION: CSocket::DeletePending + * + * DESCRIPTION: DESCRIPTION + * + * PARAMETERS: None + * + * RETURNED: Nothing + * + ***********************************************************************/ + + +void CSocket::DeletePending (void) +{ + // Delete any sockets pending to be deleted. + + SocketList::iterator iter = gSockets.begin (); + + while (iter != gSockets.end ()) + { + if ((*iter)->Deleted ()) + { + delete *iter; + iter = gSockets.erase(iter); + continue; + } + + ++iter; + } +} + + +/*********************************************************************** + * + * FUNCTION: CSocket::CSocket + * + * DESCRIPTION: . + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +CSocket::CSocket (void) : + fDeleted (false) +{ + gSocketsToBeAdded.push_back (this); +} + + +/*********************************************************************** + * + * FUNCTION: CSocket::~CSocket + * + * DESCRIPTION: . + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +CSocket::~CSocket (void) +{ + EmAssert (fDeleted); +} + + +/*********************************************************************** + * + * FUNCTION: CSocket::Delete + * + * DESCRIPTION: DESCRIPTION + * + * PARAMETERS: None + * + * RETURNED: Nothing + * + ***********************************************************************/ + +void CSocket::Delete (void) +{ + PRINTF ("CSocket(0x%08X)::Delete...", this); +// EmAssert (!fDeleted); + fDeleted = true; +} + + +/*********************************************************************** + * + * FUNCTION: CSocket::Deleted + * + * DESCRIPTION: DESCRIPTION + * + * PARAMETERS: None + * + * RETURNED: Nothing + * + ***********************************************************************/ + +Bool CSocket::Deleted (void) const +{ + return fDeleted; +} + + +/*********************************************************************** + * + * FUNCTION: CSocket::ShortPacketHack + * + * DESCRIPTION: In general, we should be using the full Serial Link + * Protocol format, which includes a header, body, and + * footer. However, for stupid reasons, I was too lazy + * to implement the protocol for all transports, and + * omitted the footer and didn't fill in the header + * checksum field. The SLP sub-system needs to know if + * the guy at the other end of this socket is expecting + * the full protocol or just the abbreviated one. + * + * PARAMETERS: None. + * + * RETURNED: True for full packets (checksums and footers). + * + ***********************************************************************/ + +Bool CSocket::ShortPacketHack (void) +{ + return false; +} + + +/*********************************************************************** + * + * FUNCTION: CSocket::ByteswapHack + * + * DESCRIPTION: In general, all data is sent in Big-Endian format. + * However, due to a bug, some fields in some packets + * didn't get byteswapped. This bug was fixed when using + * TCP sockets as the transport medium, but people + * connecting via other methods already expect the broken + * format, so we have to still support that for them. + * + * PARAMETERS: None. + * + * RETURNED: True for mis-byteswapped packets. + * + ***********************************************************************/ + +Bool CSocket::ByteswapHack (void) +{ + return false; +} + + +// --------------------------------------------------------------------------- +// ¥ CTCPSocket +// --------------------------------------------------------------------------- + +enum { kSocketState_Unconnected, kSocketState_Listening, kSocketState_Connected }; + +/*********************************************************************** + * + * FUNCTION: CTCPSocket::CTCPSocket + * + * DESCRIPTION: Creates and initializes the socket object. Nothing else + * is attempted at this time (that is, no underlying + * SOCKET objects are created, no listening is performed, + * no connections are attempted). + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +CTCPSocket::CTCPSocket (EventCallback fn, int port) : + fEventCallback (fn), + fPort (port), + fSocketState (kSocketState_Unconnected), + fListeningSocket (INVALID_SOCKET), + fConnectedSocket (INVALID_SOCKET) +{ +} + + +/*********************************************************************** + * + * FUNCTION: CTCPSocket::~CTCPSocket + * + * DESCRIPTION: . + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +CTCPSocket::~CTCPSocket (void) +{ + EmAssert (fSocketState == kSocketState_Unconnected); +} + + +/*********************************************************************** + * + * FUNCTION: CTCPSocket::Open + * + * DESCRIPTION: Starts up the sockets mechanism for communicating with + * the debugger. It first attempts to connect to the + * debugger, assuming that it's been started up and issued + * a listen(). If that fails, it creates a socket to + * listen on itself. + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + * On any error, the sub-system will be closed down. All + * sockets will be closed and the state will be set to + * unconnected. + * + ***********************************************************************/ + +ErrCode CTCPSocket::Open (void) +{ + PRINTF ("CTCPSocket(0x%08X)::Open...", this); + + EmAssert (fSocketState == kSocketState_Unconnected); + EmAssert (fListeningSocket == INVALID_SOCKET); + EmAssert (fConnectedSocket == INVALID_SOCKET); + + int result; + sockaddr addr; + +#if TRY_TO_CONNECT_BEFORE_LISTENING + fConnectedSocket = this->NewSocket (); + if (fConnectedSocket == INVALID_SOCKET) + { + PRINTF ("...NewSocket failed for connecting socket; result = %08X", this->GetError ()); + return this->ErrorOccurred (); + } + + + // Attempt to connect to that address (in case anyone is listening). + + result = connect (fConnectedSocket, this->FillAddress (&addr, true), sizeof(addr)); + if (result == 0) + { + PRINTF ("...connected!"); + + fSocketState = kSocketState_Connected; + return errNone; + } + + // Didn't connect; attempt to bind to that address so that we + // can listen for connections. + + closesocket (fConnectedSocket); + fConnectedSocket = INVALID_SOCKET; +#endif + + fListeningSocket = this->NewSocket (); + if (fListeningSocket == INVALID_SOCKET) + { + PRINTF ("...NewSocket failed for listening socket; result = %08X", this->GetError ()); + return this->ErrorOccurred (); + } + + result = bind (fListeningSocket, this->FillAddress (&addr, false), sizeof(addr)); + if (result != 0) + { + PRINTF ("...bind failed; result = %08X", this->GetError ()); + return this->ErrorOccurred (); + } + + // Start listening for a connection. + + result = listen (fListeningSocket, 1); + if (result != 0) + { + PRINTF ("...listen failed; result = %08X", this->GetError ()); + return this->ErrorOccurred (); + } + + PRINTF ("...listening for connection"); + + fSocketState = kSocketState_Listening; + + return errNone; +} + + +/*********************************************************************** + * + * FUNCTION: CTCPSocket::Close + * + * DESCRIPTION: Closes the sockets session. Closes all sockets, sets + * our state to unconnected, and closes down the sockets + * library (on Windows). + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +ErrCode CTCPSocket::Close (void) +{ + PRINTF ("CTCPSocket(0x%08X)::Close...", this); + + Bool needDisconnectNotification = false; + + if (fConnectedSocket != INVALID_SOCKET) + { + closesocket (fConnectedSocket); + fConnectedSocket = INVALID_SOCKET; + PRINTF ("...closed connection port..."); + needDisconnectNotification = fSocketState == kSocketState_Connected; + } + + if (fListeningSocket != INVALID_SOCKET) + { + closesocket (fListeningSocket); + fListeningSocket = INVALID_SOCKET; + PRINTF ("...closed listening port..."); + } + + fSocketState = kSocketState_Unconnected; + + // Tell the callback function that the socket is now disconnected + // (and not even listening). Send out this notification at this + // point so that the callback function can put the socket back into + // the listening state if it wants. + + if (needDisconnectNotification && fEventCallback) + { + fEventCallback (this, kDisconnected); + } + + return errNone; +} + + +/*********************************************************************** + * + * FUNCTION: CTCPSocket::Idle + * + * DESCRIPTION: Perform idle-time maintenance on this socket. Mostly + * this means looking for any newly-arrived data. + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +ErrCode CTCPSocket::Idle (void) +{ + if (this->HasUnreadData (0)) + { + if (fEventCallback) + { + try + { + fEventCallback (this, kDataReceived); + } + catch (const EmExceptionReset& e) + { + EmSessionStopper stopper (gSession, kStopNow); + e.Display (); + } + } + } + + return errNone; +} + + +/*********************************************************************** + * + * FUNCTION: CTCPSocket::Write + * + * DESCRIPTION: Writes data out to the buffer. + * + * PARAMETERS: buffer - pointer to the data to be written. + * + * amountToWrite - number of bytes in the buffer. + * + * RETURNED: Nothing. + * + * On any error, the sub-system will be closed down. All + * sockets will be closed and the state will be set to + * unconnected. + * + ***********************************************************************/ + +ErrCode CTCPSocket::Write (const void* buffer, long amountToWrite, long* amtWritten) +{ + PRINTF ("CTCPSocket(0x%08X)::Write...", this); + + long dummy; + if (!amtWritten) + amtWritten = &dummy; + + *amtWritten = 0; + + if (this->CheckConnection ()) + { + if (LogLLDebuggerData ()) + LogAppendData (buffer, amountToWrite, "...writing %ld bytes.", amountToWrite); + else + PRINTF ("...writing %ld bytes.", amountToWrite); + + while (amountToWrite) + { + int sentThisTime = send (fConnectedSocket, + &((char*) buffer)[*amtWritten], + amountToWrite, 0); + + if (sentThisTime == SOCKET_ERROR ) + { + PRINTF ("...error calling send: %08X", this->GetError ()); + + return this->ErrorOccurred (); + } + + *amtWritten += sentThisTime; + amountToWrite -= sentThisTime; + } + } + else + { + PRINTF ("...no connection."); + } + + return errNone; +} + + +/*********************************************************************** + * + * FUNCTION: CTCPSocket::Read + * + * DESCRIPTION: Read bytes from the socket. At most sizeOfBuffer bytes + * are read. It is assumed that HasUnreadData has already + * been called and returned true. + * + * PARAMETERS: buffer - pointer to the buffer to put the data. + * + * sizeOfBuffer - size of the buffer. + * + * RETURNED: The actual number of bytes read.. + * + * On any error, the sub-system will be closed down. All + * sockets will be closed and the state will be set to + * unconnected. + * + ***********************************************************************/ + +ErrCode CTCPSocket::Read (void* buffer, long sizeOfBuffer, long* amtRead) +{ + PRINTF ("CTCPSocket(0x%08X)::Read...", this); + + long dummy; + if (!amtRead) + amtRead = &dummy; + + *amtRead = 0; + + if (this->CheckConnection ()) + { + while (*amtRead < sizeOfBuffer) + { + long r = recv (fConnectedSocket, ((char*) buffer) + *amtRead, sizeOfBuffer - *amtRead, 0); + + // More from the sockets manual for the select() function: + // + // For connection-oriented sockets, readability can also indicate that + // a request to close the socket has been received from the peer. If the + // virtual circuit was closed gracefully, and all data was received, then + // a recv will return immediately with zero bytes read. If the virtual + // circuit was reset, then a recv will complete immediately with an error + // code such as WSAECONNRESET. The presence of OOB data will be checked + // if the socket option SO_OOBINLINE has been enabled (see setsockopt). + + if (r == 0 && *amtRead == 0) + { + // OK, the connection was closed on the other side. Close it on + // our side, too. + + PRINTF ("...found a closed connection."); + this->Close (); + return errNone; + } + + if (r == SOCKET_ERROR) + { + PRINTF ("...error calling recv: %08X", this->GetError ()); + + return this->ErrorOccurred (); + } + + if (r > 0) + { + *amtRead += r; + + if (LogLLDebuggerData ()) + LogAppendData (((char*) buffer) + *amtRead - r, r, + "...got %ld of %ld bytes of data.", *amtRead, sizeOfBuffer); + else + PRINTF ("...got %ld of %ld bytes of data.", *amtRead, sizeOfBuffer); + } + } + } + else + { + PRINTF ("...no connection."); + } + + return errNone; +} + + +/*********************************************************************** + * + * FUNCTION: CTCPSocket::ConnectPending + * + * DESCRIPTION: Returns whether or not there is a pending connect. This + * function can be called at any time. It will only return + * true if there is a non-blocking listen issued with a + * pending connect that we haven't yet accepted. + * + * PARAMETERS: None. + * + * RETURNED: True if there is a pending connect. If there is, the + * caller should next call CTCPSocket::AcceptConnection. + * + ***********************************************************************/ + +Bool CTCPSocket::ConnectPending (void) +{ + // From the sockets manual for the select() function: + // + // The parameter readfds identifies the sockets that are to be + // checked for readability. If the socket is currently in the + // listen state, it will be marked as readable if an incoming + // connection request has been received such that an accept is + // guaranteed to complete without blocking. For other sockets, + // readability means that queued data is available for reading + // such that a call to recv, WSARecv, WSARecvFrom, or recvfrom + // is guaranteed not to block. + + Bool havePendingConnect = false; + +#if 0 + if (fSocketState == kSocketState_Unconnected) + { + PRINTF ("CTCPSocket(0x%08X)::ConnectPending...", this); + PRINTF ("...starting to listen for connection."); + + this->Open (); + } +#endif + + if (fSocketState == kSocketState_Listening) + { + EmAssert (fListeningSocket != INVALID_SOCKET); + EmAssert (fConnectedSocket == INVALID_SOCKET); + + fd_set readfds; + + FD_ZERO (&readfds); + FD_SET (fListeningSocket, &readfds); + + timeval timeout = {0, 0}; // Don't wait at all + + int result = select (fListeningSocket + 1, &readfds, NULL, NULL, &timeout); + + if (result > 0 && FD_ISSET (fListeningSocket, &readfds)) + { + PRINTF ("CTCPSocket(0x%08X)::ConnectPending...", this); + PRINTF ("...have pending connection."); + + havePendingConnect = true; + } + else if (result == SOCKET_ERROR) + { + PRINTF ("CTCPSocket(0x%08X)::ConnectPending...", this); + PRINTF ("...error calling select: %08X", this->GetError ()); + + result = this->GetError (); + } + else if (result > 0) + { + PRINTF ("CTCPSocket(0x%08X)::ConnectPending...", this); + PRINTF ("...fell through cases in kSocketState_Listening"); + } + } + + return havePendingConnect; +} + + +/*********************************************************************** + * + * FUNCTION: CTCPSocket::AcceptConnection + * + * DESCRIPTION: Accepts a pending connection. Assumes that + * ConnectPending has already been called and returned + * true. After this, the socket connection should be open + * for business. + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + * On any error, the sub-system will be closed down. All + * sockets will be closed and the state will be set to + * unconnected. + * + ***********************************************************************/ + +ErrCode CTCPSocket::AcceptConnection (void) +{ + PRINTF ("CTCPSocket(0x%08X)::AcceptConnection...", this); + + EmAssert (fSocketState == kSocketState_Listening); + EmAssert (fListeningSocket != INVALID_SOCKET); + EmAssert (fConnectedSocket == INVALID_SOCKET); + + sockaddr addr; + socklen_t addr_len; + + memset (&addr, 0, sizeof (addr)); + addr_len = sizeof(addr); + + fConnectedSocket = accept (fListeningSocket, &addr, &addr_len); + + if (fConnectedSocket == INVALID_SOCKET) + { + PRINTF ("...error calling accept: %08X", this->GetError ()); + return this->ErrorOccurred (); + } + + closesocket (fListeningSocket); + fListeningSocket = INVALID_SOCKET; + + // Set the socket to not use the "Nagle delay algorithm" that clumps + // batches of small writes together into one big send. + + protoent* protocolEntity = getprotobyname ("tcp"); + if (!protocolEntity) + { + PRINTF ("...error calling getprotobyname: %08X", this->GetError ()); + return this->ErrorOccurred (); + } + +#ifdef TCP_NODELAY + int tmp = 1; + if (setsockopt (fConnectedSocket, protocolEntity->p_proto, TCP_NODELAY, (char*) &tmp, sizeof (tmp))) + { + PRINTF ("...error calling setsockopt: %08X", this->GetError ()); + return this->ErrorOccurred (); + } +#endif + + PRINTF ("...accepted the connection."); + + fSocketState = kSocketState_Connected; + + if (fEventCallback) + { + fEventCallback (this, kConnected); + } + + return errNone; +} + + +/*********************************************************************** + * + * FUNCTION: CTCPSocket::IsConnected + * + * DESCRIPTION: Returns whether or not we have a live connection to an + * external debugger via a socket. This function can be + * called at any time. + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + * On any error, the sub-system will be closed down. All + * sockets will be closed and the state will be set to + * unconnected. + * + ***********************************************************************/ + +Bool CTCPSocket::IsConnected (void) +{ + // If we thought we were connected, see if we still are. + + if (fSocketState == kSocketState_Connected) + { + // From the sockets manual for the select() function: + // + // If a socket is processing a connect call (nonblocking), a socket is + // writable if the connection establishment successfully completes. If + // the socket is not processing a connect call, writability means a send, + // sendto, or WSASendto are guaranteed to succeed. + + fd_set writefds; + + FD_ZERO (&writefds); + FD_SET (fConnectedSocket, &writefds); + + timeval timeout = {0, 0}; // Don't wait at all + + int result = select (fConnectedSocket+1, NULL, &writefds, NULL, &timeout); + + Bool isConnected = result > 0 && FD_ISSET (fConnectedSocket, &writefds); + + // If we're no longer connected, update our internal state. + + if (!isConnected) + { + PRINTF ("CTCPSocket(0x%08X)::IsConnected...", this); + PRINTF ("...found a broken connection, or error calling select: %08X", this->GetError ()); + + this->ErrorOccurred (); + } + } + + return fSocketState == kSocketState_Connected; +} + + +/*********************************************************************** + * + * FUNCTION: CTCPSocket::CheckConnection + * + * DESCRIPTION: Attempt to ensure that a connection is in place. + * + * PARAMETERS: None. + * + * RETURNED: True if a connection is in place. False otherwise. + * + ***********************************************************************/ + +Bool CTCPSocket::CheckConnection (void) +{ + if (!this->IsConnected() && this->ConnectPending ()) + { + this->AcceptConnection (); + } + + return this->IsConnected (); +} + + +/*********************************************************************** + * + * FUNCTION: CTCPSocket::HasUnreadData + * + * DESCRIPTION: Returns whether or not there is data to be read from + * the socket. + * + * PARAMETERS: msecs - time to wait in milliseconds for any data. + * + * RETURNED: True if there is data to be read. There is no way to + * get how many bytes are currently in the buffer. + * + * On any error, the sub-system will be closed down. All + * sockets will be closed and the state will be set to + * unconnected. + * + ***********************************************************************/ + +Bool CTCPSocket::HasUnreadData (long msecs) +{ + Bool hasData = false; + + // From the sockets manual for the select() function: + // + // The parameter readfds identifies the sockets that are to be + // checked for readability. If the socket is currently in the + // listen state, it will be marked as readable if an incoming + // connection request has been received such that an accept is + // guaranteed to complete without blocking. For other sockets, + // readability means that queued data is available for reading + // such that a call to recv, WSARecv, WSARecvFrom, or recvfrom + // is guaranteed not to block. + + if (this->CheckConnection ()) + { + EmAssert (fSocketState == kSocketState_Connected); + + fd_set readfds; + + FD_ZERO (&readfds); + FD_SET (fConnectedSocket, &readfds); + + timeval timeout = {msecs / 1000, msecs * 1000}; + + int result = select (fConnectedSocket+1, &readfds, NULL, NULL, &timeout); + + if (result > 0 && FD_ISSET (fConnectedSocket, &readfds)) + { + PRINTF ("CTCPSocket(0x%08X)::HasUnreadData...", this); + PRINTF ("...has pending data."); + + hasData = true; + } + else if (result == SOCKET_ERROR) + { + PRINTF ("CTCPSocket(0x%08X)::HasUnreadData...", this); + PRINTF ("...error calling select: %08X", this->GetError ()); + + this->ErrorOccurred (); + return false; + } + } + + return hasData; +} + + +/*********************************************************************** + * + * FUNCTION: CTCPSocket::NewSocket + * + * DESCRIPTION: Creates and returns a new socket configured the way we + * like it. The way we like it is: reusable after it's + * closed, uses "keep alive" packets, and non-blocking. + * + * PARAMETERS: None. + * + * RETURNED: The created socket, or INVALID_SOCKET on error. + * + * On any error, the sub-system will be closed down. All + * sockets will be closed and the state will be set to + * unconnected. + * + ***********************************************************************/ + +SOCKET CTCPSocket::NewSocket (void) +{ + PRINTF ("CTCPSocket(0x%08X)::NewSocket...", this); + + int tmp; + int result; + + SOCKET newSocket = socket (AF_INET, SOCK_STREAM, 0); + if (newSocket == INVALID_SOCKET) + { + PRINTF ("...error calling socket: %08X", this->GetError ()); + goto Error; + } + + // Allow rapid reuse of this port. + + tmp = 1; + result = setsockopt (newSocket, SOL_SOCKET, SO_REUSEADDR, (char*) &tmp, sizeof(tmp)); + if (result != 0) + { + PRINTF ("...error calling setsockopt (SO_REUSEADDR): %08X", this->GetError ()); + goto Error; + } + + // Enable TCP keep alive process. + + tmp = 1; + result = setsockopt (newSocket, SOL_SOCKET, SO_KEEPALIVE, (char*) &tmp, sizeof(tmp)); + if (result != 0) + { + PRINTF ("...error calling setsockopt (SO_KEEPALIVE): %08X", this->GetError ()); + goto Error; + } + + return newSocket; + +Error: + if (newSocket != INVALID_SOCKET) + { + closesocket (newSocket); + } + + return INVALID_SOCKET; +} + + +/*********************************************************************** + * + * FUNCTION: CTCPSocket::FillAddress + * + * DESCRIPTION: Fill in a sockaddr data structure with values + * appropriate for connecting. For now, this is an + * Internet address on the local machine with port 2000. + * + * PARAMETERS: addr - the sockaddr to fill in. + * + * RETURNED: The same sockaddr passed in. + * + ***********************************************************************/ + +sockaddr* CTCPSocket::FillAddress (sockaddr* addr, bool forConnect) +{ + PRINTF ("CTCPSocket(0x%08X)::FillAddress...", this); + + sockaddr_in* addr_in = (sockaddr_in*) addr; + +#ifdef HAVE_SIN_LEN + addr_in->sin_len = sizeof (*addr_in); +#endif + addr_in->sin_family = AF_INET; + addr_in->sin_port = htons (fPort); + addr_in->sin_addr.s_addr = htonl (forConnect ? INADDR_LOOPBACK : INADDR_ANY); + + return addr; +} + + +/*********************************************************************** + * + * FUNCTION: CTCPSocket::ErrorOccurred + * + * DESCRIPTION: Handles any internal errors. + * + * PARAMETERS: None. + * + * RETURNED: Nothing. + * + ***********************************************************************/ + +ErrCode CTCPSocket::ErrorOccurred (void) +{ + ErrCode result = this->GetError (); + + this->Close (); + + return result; // Return the original error code. +} + + +/*********************************************************************** + * + * FUNCTION: CTCPSocket::GetError + * + * DESCRIPTION: Returns the error code for the last operation. + * + * PARAMETERS: None. + * + * RETURNED: The error code as returned by WSAGetLastError (on + * Windows) or errno (on the Mac or UNIX). + * + ***********************************************************************/ + +ErrCode CTCPSocket::GetError (void) +{ +#if defined (_WIN32) + return (ErrCode) ::WSAGetLastError (); +#else + return (ErrCode) errno; +#endif +} |