/*++ Copyright (c) 1996 Microsoft Corporation Module Name: stubs.c Abstract: This module contains select routines for the Winsock 2 to Winsock 1.1 Mapper Service Provider. The following routines are exported by this module: WSPAsyncSelect() WSPEnumNetworkEvents() WSPEventSelect() WSPSelect() SockDestroyAsyncWindow() Author: Keith Moore (keithmo) 29-May-1996 Revision History: --*/ #include "precomp.h" #pragma hdrstop // // The message we'll actually pass down to the WS1 DLL. // #define WM_MY_ASYNC_SELECT_MESSAGE 0x1234 // // Window managment globals. // CHAR SockAsyncWindowClassName[] = "WinSock2MapperAsyncHelperWindow"; HWND SockAsyncWindowHandle; // // Forward procedure references. // LRESULT CALLBACK SockAsyncWindowProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ); VOID SockProcessAsyncSelectMessage( WPARAM wParam, LPARAM lParam ); BOOL SockCreateAsyncWindow( VOID ); INT SockMapWS2FdSetToWS1( PFD_SET WS2FdSet, PFD_SET * WS1FdSet, PULONG * TargetBuffer, PSOCKET_INFORMATION * SocketInfo ); INT SockMapWS1FdSetToWS2( PFD_SET WS1FdSet, PFD_SET WS2FdSet ); INT WSPAPI WSPAsyncSelect( IN SOCKET s, IN HWND hWnd, IN unsigned int wMsg, IN long lEvent, OUT LPINT lpErrno ) /*++ Routine Description: This routine is used to request that the service provider send a Window message to the client's window hWnd whenever it detects any of the network events specified by the lEvent parameter. The service provider should use the WPUPostMessage() function to post the message. The message to be sent is specified by the wMsg parameter. The socket for which notification is required is identified by s. This routine automatically sets socket s to non-blocking mode, regardless of the value of lEvent. See WSPIoctl() about how to set the socket back to blocking mode. The lEvent parameter is constructed by or'ing any of the values specified in the following list. Value Meaning ~~~~~ ~~~~~~~ FD_READ Issue notification of readiness for reading. FD_WRITE Issue notification of readiness for writing. FD_OOB Issue notification of the arrival of out-of-band data. FD_ACCEPT Issue notification of incoming connections. FD_CONNECT Issue notification of completed connection. FD_CLOSE Issue notification of socket closure. FD_QOS Issue notification of socket Quality of Service (QOS) changes. FD_GROUP_QOS Issue notification of socket group Quality of Service (QOS) changes. Invoking WSPAsyncSelect() for a socket cancels any previous WSPAsyncSelect() or WSPEventSelect() for the same socket. For example, to receive notification for both reading and writing, the WinSock SPI client must call WSPAsyncSelect() with both FD_READ and FD_WRITE, as follows: rc = WSPAsyncSelect(s, hWnd, wMsg, FD_READ | FD_WRITE, &error); It is not possible to specify different messages for different events. The following code will not work; the second call will cancel the effects of the first, and only FD_WRITE events will be reported with message wMsg2: rc = WSPAsyncSelect(s, hWnd, wMsg1, FD_READ, &error); rc = WSPAsyncSelect(s, hWnd, wMsg2, FD_WRITE, &error); // bad To cancel all notification - i.e., to indicate that the service provider should send no further messages related to network events on the socket - lEvent will be set to zero. rc = WSPAsyncSelect(s, hWnd, 0, 0, &error); Since a WSPAccept()'ed socket has the same properties as the listening socket used to accept it, any WSPAsyncSelect() events set for the listening socket apply to the accepted socket. For example, if a listening socket has WSPAsyncSelect() events FD_ACCEPT, FD_READ, and FD_WRITE, then any socket accepted on that listening socket will also have FD_ACCEPT, FD_READ, and FD_WRITE events with the same wMsg value used for messages. If a different wMsg or events are desired, the WinSock SPI client must call WSPAsyncSelect(), passing the accepted socket and the desired new information. When one of the nominated network events occurs on the specified socket s, the service provider uses WPUPostMessage() to send message wMsg to the WinSock SPI client's window hWnd. The wParam argument identifies the socket on which a network event has occurred. The low word of lParam specifies the network event that has occurred. The high word of lParam contains any error code. The error code be any error as defined in ws2spi.h. The possible network event codes which may be indicated are as follows: Value Meaning ~~~~~ ~~~~~~~ FD_READ Socket s ready for reading. FD_WRITE Socket s ready for writing. FD_OOB Out-of-band data ready for reading on socket s. FD_ACCEPT Socket s ready for accepting a new incoming connection. FD_CONNECT Connection initiated on socket s completed. FD_CLOSE Connection identified by socket s has been closed. FD_QOS Quality of Service associated with socket s has changed. FD_GROUP_QOS Quality of Service associated with the socket group to which s belongs has changed. Although WSPAsyncSelect() can be called with interest in multiple events, the service provider issues the same Windows message for each event. A WinSock 2 provider shall not continually flood a WinSock SPI client with messages for a particular network event. Having successfully posted notification of a particular event to a WinSock SPI client window, no further message(s) for that network event will be posted to the WinSock SPI client window until the WinSock SPI client makes the function call which implicitly reenables notification of that network event. Event Re-enabling functions ~~~~~ ~~~~~~~~~~~~~~~~~~~~~ FD_READ WSPRecv() or WSPRecvFrom(). FD_WRITE WSPSend() or WSPSendTo(). FD_OOB WSPRecv() or WSPRecvFrom(). FD_ACCEPT WSPAccept() unless the error code returned is WSATRY_AGAIN indicating that the condition function returned CF_DEFER. FD_CONNECT NONE FD_CLOSE NONE FD_QOS WSPIoctl() with SIO_GET_QOS FD_GROUP_QOS WSPIoctl() with SIO_GET_GROUP_QOS Any call to the reenabling routine, even one which fails, results in reenabling of message posting for the relevant event. For FD_READ, FD_OOB, and FD_ACCEPT events, message posting is "level- triggered." This means that if the reenabling routine is called and the relevant condition is still met after the call, a WSPAsyncSelect() message is posted to the WinSock SPI client. The FD_QOS and FD_GROUP_QOS events are considered edge triggered. A message will be posted exactly once when a QOS change occurs. Further messages will not be forthcoming until either the provider detects a further change in QOS or the WinSock SPI client renegotiates the QOS for the socket. If any event has already happened when the WinSock SPI client calls WSPAsyncSelect() or when the reenabling function is called, then a message is posted as appropriate. For example, consider the following sequence: 1. A WinSock SPI client calls WSPListen(). 2. A connect request is received but not yet accepted. 3. The WinSock SPI client calls WSPAsyncSelect() specifying that it wants to receive FD_ACCEPT messages for the socket. Due to the persistence of events, the WinSock service provider posts an FD_ACCEPT message immediately. The FD_WRITE event is handled slightly differently. An FD_WRITE message is posted when a socket is first connected with WSPConnect() (after FD_CONNECT, if also registered) or accepted with WSPAccept(), and then after a WSPSend() or WSPSendTo() fails with WSAEWOULDBLOCK and buffer space becomes available. Therefore, a WinSock SPI client can assume that sends are possible starting from the first FD_WRITE message and lasting until a send returns WSAEWOULDBLOCK. After such a failure the WinSock SPI client will be notified that sends are again possible with an FD_WRITE message. The FD_OOB event is used only when a socket is configured to receive out-of-band data separately. If the socket is configured to receive out-of-band data in-line, the out-of-band (expedited) data is treated as normal data and the WinSock SPI client must register an interest in FD_READ events, not FD_OOB events. The error code in an FD_CLOSE message indicates whether the socket close was graceful or abortive. If the error code is 0, then the close was graceful; if the error code is WSAECONNRESET, then the socket's virtual circuit was reset. This only applies to connection-oriented sockets such as SOCK_STREAM. The FD_CLOSE message is posted when a close indication is received for the virtual circuit corresponding to the socket. In TCP terms, this means that the FD_CLOSE is posted when the connection goes into the TIME WAIT or CLOSE WAIT states. This results from the remote end performing a WSPShutdown() on the send side or a WSPCloseSocket(). FD_CLOSE shall only be posted after all data is read from a socket. In the case of a graceful close, the service provider shall only send an FD_CLOSE message to indicate virtual circuit closure after all the received data has been read. It shall NOT send an FD_READ message to indicate this condition. The FD_QOS or FD_GROUP_QOS message is posted when any field in the flow spec associated with socket s or the socket group that s belongs to has changed, respectively. The service provider must update the QOS information available to the client via WSPIoctl() with SIO_GET_QOS and/or SIO_GET_GROUP_QOS. Here is a summary of events and conditions for each asynchronous notification message: FD_READ ~~~~~~~ 1. When WSPAsyncSelect() called, if there is data currently available to receive. 2. When data arrives, if FD_READ not already posted. 3. after WSPRecv() or WSPRecvfrom() called (with or without MSG_PEEK), if data is still available to receive. N.B. When WSPSetSockOpt() SO_OOBINLINE is enabled "data" includes both normal data and out-of-band (OOB) data in the instances noted above. FD_WRITE ~~~~~~~~ 1. When WSPAsyncSelect() called, if a WSPSend() or WSPSendTo() is possible. 2. After WSPConnect() or WSPAccept() called, when connection established. 3. After WSPSend() or WSPSendTo() fail with WSAEWOULDBLOCK, when WSPSend() or WSPSendTo() are likely to succeed. 4. After WSPBind() on a datagram socket. FD_OOB ~~~~~~ Only valid when WSPSetSockOpt() SO_OOBINLINE is disabled (default). 1. When WSPAsyncSelect() called, if there is OOB data currently available to receive with the MSG_OOB flag. 2. When OOB data arrives, if FD_OOB not already posted. 3. After WSPRecv() or WSPRecvfrom() called with or without MSG_OOB flag, if OOB data is still available to receive. FD_ACCEPT ~~~~~~~~~ 1. When WSPAsyncSelect() called, if there is currently a connection request available to accept. 2. When a connection request arrives, if FD_ACCEPT not already posted. 3. After WSPAccept() called, if there is another connection request available to accept. FD_CONNECT ~~~~~~~~~~ 1. When WSPAsyncSelect() called, if there is currently a connection established. 2. After WSPConnect() called, when connection is established (even when WSPConnect() succeeds immediately, as is typical with a datagram socket) FD_CLOSE ~~~~~~~~ Only valid on connection-oriented sockets (e.g. SOCK_STREAM) 1. When WSPAsyncSelect() called, if socket connection has been closed. 2. After remote system initiated graceful close, when no data currently available to receive (note: if data has been received and is waiting to be read when the remote system initiates a graceful close, the FD_CLOSE is not delivered until all pending data has been read). 3. After local system initiates graceful close with WSPShutdown() and remote system has responded with "End of Data" notification (e.g. TCP FIN), when no data currently available to receive. 4. When remote system aborts connection (e.g. sent TCP RST), and lParam will contain WSAECONNRESET error value. N.B. FD_CLOSE is not posted after WSPClosesocket() is called. FD_QOS ~~~~~~ 1. When WSPAsyncSelect() called, if the QOS associated with the socket has been changed. 2. After WSPIoctl() with SIO_GET_QOS called, when the QOS is changed. FD_GROUP_QOS ~~~~~~~~~~~~ 1. When WSPAsyncSelect() called, if the group QOS associated with the socket has been changed. 2. After WSPIoctl() with SIO_GET_GROUP_QOS called, when the group QOS is changed. Arguments: s - A descriptor identifying the socket for which event notification is required. hWnd - A handle identifying the window which should receive a message when a network event occurs. wMsg - The message to be sent when a network event occurs. lEvent - A bitmask which specifies a combination of network events in which the WinSock SPI client is interested. lpErrno - A pointer to the error code. Return Value: The return value is 0 if the WinSock SPI client's declaration of interest in the network event set was successful. Otherwise the value SOCKET_ERROR is returned, and a specific error code is available in lpErrno. --*/ { PSOCKET_INFORMATION socketInfo; INT err; INT result; HWND capturedWindowHandle; UINT capturedWindowMessage; SOCK_ENTER( "WSPAsyncSelect", (PVOID)s, (PVOID)hWnd, (PVOID)wMsg, (PVOID)lEvent ); SOCK_ASSERT( lpErrno != NULL ); err = SockEnterApi( TRUE, FALSE ); if( err != NO_ERROR ) { SOCK_EXIT( "WSPAsyncSelect", SOCKET_ERROR, TRUE ); *lpErrno = err; return SOCKET_ERROR; } // // Setup locals so we know how to cleanup on exit. // socketInfo = NULL; // // Attempt to find the socket in our lookup table. // socketInfo = SockFindAndReferenceWS2Socket( s ); if( socketInfo == NULL || socketInfo->State == SocketStateClosing ) { IF_DEBUG(SELECT) { SOCK_PRINT(( "WSPAsyncSelect failed on %s handle: %lx\n", socketInfo == NULL ? "unknown" : "closed", s )); } if( socketInfo != NULL ) { SockDereferenceSocket( socketInfo ); } SOCK_EXIT( "WSPAsyncSelect", SOCKET_ERROR, TRUE ); *lpErrno = WSAENOTSOCK; return SOCKET_ERROR; } // // BUGBUG: Check for outstanding WSPEventSelect! // // // Initialize the async window if necessary. // if( !SockCreateAsyncWindow() ) { err = WSAENOBUFS; // SWAG goto exit; } // // Before we call the hooker, save the async select state in the // socket. // capturedWindowHandle = socketInfo->WindowHandle; capturedWindowMessage = socketInfo->WindowMessage; socketInfo->WindowHandle = hWnd; socketInfo->WindowMessage = wMsg; // // Let the hooker do its thang. // SockPreApiCallout(); result = socketInfo->Hooker->WSAAsyncSelect( socketInfo->WS1Handle, SockAsyncWindowHandle, WM_MY_ASYNC_SELECT_MESSAGE, lEvent ); if( result == SOCKET_ERROR ) { err = socketInfo->Hooker->WSAGetLastError(); SOCK_ASSERT( err != NO_ERROR ); SockPostApiCallout(); goto exit; } SockPostApiCallout(); // // Success! // SOCK_ASSERT( err == NO_ERROR ); SOCK_ASSERT( result != SOCKET_ERROR ); exit: if( err != NO_ERROR ) { socketInfo->WindowHandle = capturedWindowHandle; socketInfo->WindowMessage = capturedWindowMessage; *lpErrno = err; } SockDereferenceSocket( socketInfo ); SOCK_EXIT( "WSPAsyncSelect", result, (BOOL)( result == SOCKET_ERROR ) ); return result; } // WSPAsyncSelect INT WSPAPI WSPEnumNetworkEvents( IN SOCKET s, IN WSAEVENT hEventObject, OUT LPWSANETWORKEVENTS lpNetworkEvents, OUT LPINT lpErrno ) /*++ Routine Description: This routine is used to report which network events have occurred for the indicated socket since the last invocation of this routine. It is intended for use in conjunction with WSPEventSelect(), which associates an event object with one or more network events. Recording of network events commences when WSPEventSelect() is called with a non-zero lNetworkEvents parameter and remains in effect until another call is made to WSPEventSelect() with the lNetworkEvents parameter set to zero, or until a call is made to WSPAsyncSelect(). The socket's internal record of network events is copied to the structure referenced by lpNetworkEvents, whereafter the internal network events record is cleared. If hEventObject is non-null, the indicated event object is also reset. The WinSock provider guarantees that the operations of copying the network event record, clearing it and resetting any associated event object are atomic, such that the next occurrence of a nominated network event will cause the event object to become set. In the case of this function returning SOCKET_ERROR, the associated event object is not reset and the record of network events is not cleared. The WSANETWORKEVENTS structure is defined as follows: typedef struct _WSANETWORKEVENTS { long lNetworkEvents; int iErrorCodes[FD_MAX_EVENTS]; } WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS; The lNetworkEvent field of the structure indicates which of the FD_XXX network events have occurred. The iErrorCodes array is used to contain any associated error codes, with array index corresponding to the position of event bits in lNetworkEvents. The identifiers FD_READ_BIT, FD_WRITE_BIT, etc. may be used to index the iErrorCodes array. Arguments: s - A descriptor identifying the socket. hEventObject - An optional handle identifying an associated event object to be reset. lpNetworkEvents - A pointer to a WSANETWORKEVENTS struct which is filled with a record of occurred network events and any associated error codes. lpErrno - A pointer to the error code. Return Value: The return value is 0 if the operation was successful. Otherwise the value SOCKET_ERROR is returned, and a specific error number is available in lpErrno. --*/ { return SOCKET_ERROR; } // WSPEnumNetworkEvents INT WSPAPI WSPEventSelect( IN SOCKET s, IN WSAEVENT hEventObject, IN long lNetworkEvents, OUT LPINT lpErrno ) /*++ Routine Description: This routine is used to specify an event object, hEventObject, to be associated with the selected network events, lNetworkEvents. The socket for which an event object is specified is identified by s. The event object is set when any of the nominated network events occur. WSPEventSelect() operates very similarly to WSPAsyncSelect(), the difference being in the actions taken when a nominated network event occurs. Whereas WSPAsyncSelect() causes a WinSock SPI client-specified Windows message to be posted, WSPEventSelect() sets the associated event object and records the occurrence of this event in an internal network event record. A WinSock SPI client can use WSPEnumNetworkEvents() to retrieve the contents of the internal network event record and thus determine which of the nominated network events have occurred. This routine automatically sets socket s to non-blocking mode, regardless of the value of lNetworkEvents. The lNetworkEvents parameter is constructed by OR'ing any of the values specified in the following list. Value Meaning ~~~~~ ~~~~~~~ FD_READ Issue notification of readiness for reading. FD_WRITE Issue notification of readiness for writing. FD_OOB Issue notification of the arrival of out-of-band data. FD_ACCEPT Issue notification of incoming connections. FD_CONNECT Issue notification of completed connection. FD_CLOSE Issue notification of socket closure. FD_QOS Issue notification of socket Quality of Service (QOS) changes. FD_GROUP_QOS Issue notification of socket group Quality of Service (QOS) changes. Issuing a WSPEventSelect() for a socket cancels any previous WSPAsyncSelect() or WSPEventSelect() for the same socket and clears the internal network event record. For example, to associate an event object with both reading and writing network events, the WinSock SPI client must call WSPEventSelect() with both FD_READ and FD_WRITE, as follows: rc = WSPEventSelect(s, hEventObject, FD_READ | FD_WRITE); It is not possible to specify different event objects for different network events. The following code will not work; the second call will cancel the effects of the first, and only FD_WRITE network event will be associated with hEventObject2: rc = WSPEventSelect(s, hEventObject1, FD_READ); rc = WSPEventSelect(s, hEventObject2, FD_WRITE); //bad To cancel the association and selection of network events on a socket, lNetworkEvents should be set to zero, in which case the hEventObject parameter will be ignored. rc = WSPEventSelect(s, hEventObject, 0); Closing a socket with WSPCloseSocket() also cancels the association and selection of network events specified in WSPEventSelect() for the socket. The WinSock SPI client, however, still must call WSACloseEvent() to explicitly close the event object and free any resources. Since a WSPAccept()'ed socket has the same properties as the listening socket used to accept it, any WSPEventSelect() association and network events selection set for the listening socket apply to the accepted socket. For example, if a listening socket has WSPEventSelect() association of hEventOject with FD_ACCEPT, FD_READ, and FD_WRITE, then any socket accepted on that listening socket will also have FD_ACCEPT, FD_READ, and FD_WRITE network events associated with the same hEventObject. If a different hEventObject or network events are desired, the WinSock SPI client should call WSPEventSelect(), passing the accepted socket and the desired new information. Having successfully recorded the occurrence of the network event and signaled the associated event object, no further actions are taken for that network event until the WinSock SPI client makes the function call which implicitly reenables the setting of that network event and signaling of the associated event object. Event Re-enabling functions ~~~~~ ~~~~~~~~~~~~~~~~~~~~~ FD_READ WSPRecv() or WSPRecvFrom(). FD_WRITE WSPSend() or WSPSendTo(). FD_OOB WSPRecv() or WSPRecvFrom(). FD_ACCEPT WSPAccept() unless the error code returned is WSATRY_AGAIN indicating that the condition function returned CF_DEFER. FD_CONNECT NONE FD_CLOSE NONE FD_QOS WSPIoctl() with SIO_GET_QOS FD_GROUP_QOS WSPIoctl() with SIO_GET_GROUP_QOS Any call to the reenabling routine, even one which fails, results in reenabling of recording and signaling for the relevant network event and event object, respectively. For FD_READ, FD_OOB, and FD_ACCEPT network events, network event recording and event object signaling are "level-triggered." This means that if the reenabling routine is called and the relevant network condition is still valid after the call, the network event is recorded and the associated event object is signaled . This allows a WinSock SPI client to be event- driven and not be concerned with the amount of data that arrives at any one time. Consider the following sequence: 1. The service provider receives 100 bytes of data on socket s, records the FD_READ network event and signals the associated event object. 2. The WinSock SPI client issues WSPRecv( s, buffptr, 50, 0) to read 50 bytes. 3. The service provider records the FD_READ network event and signals the associated event object again since there is still data to be read. With these semantics, a WinSock SPI client need not read all available data in response to an FD_READ network event --a single WSPRecv() in response to each FD_READ network event is appropriate. The FD_QOS and FD_GROUP_QOS events are considered edge triggered. A message will be posted exactly once when a QOS change occurs. Further indications will not be issued until either the service provider detects a further change in QOS or the WinSock SPI client renegotiates the QOS for the socket. If a network event has already happened when the WinSock SPI client calls WSPEventSelect() or when the reenabling function is called, then a network event is recorded and the associated event object is signaled as appropriate. For example, consider the following sequence: 1. A WinSock SPI client calls WSPListen(). 2. A connect request is received but not yet accepted. 3. The WinSock SPI client calls WSPEventSelect() specifying that it is interested in the FD_ACCEPT network event for the socket. The service provider records the FD_ACCEPT network event and signals the associated event object immediately. The FD_WRITE network event is handled slightly differently. An FD_WRITE network event is recorded when a socket is first connected with WSPConnect() or accepted with WSPAccept(), and then after a WSPSend() or WSPSendTo() fails with WSAEWOULDBLOCK and buffer space becomes available. Therefore, a WinSock SPI client can assume that sends are possible starting from the first FD_WRITE network event setting and lasting until a send returns WSAEWOULDBLOCK. After such a failure the WinSock SPI client will find out that sends are again possible when an FD_WRITE network event is recorded and the associated event object is signaled. The FD_OOB network event is used only when a socket is configured to receive out-of-band data separately. If the socket is configured to receive out-of-band data in-line, the out-of-band (expedited) data is treated as normal data and the WinSock SPI client should register an interest in, and will get, FD_READ network event, not FD_OOB network event. A WinSock SPI client may set or inspect the way in which out-of-band data is to be handled by using WSPSetSockOpt() or WSPGetSockOpt() for the SO_OOBINLINE option. The error code in an FD_CLOSE network event indicates whether the socket close was graceful or abortive. If the error code is 0, then the close was graceful; if the error code is WSAECONNRESET, then the socket's virtual circuit was reset. This only applies to connection-oriented sockets such as SOCK_STREAM. The FD_CLOSE network event is recorded when a close indication is received for the virtual circuit corresponding to the socket. In TCP terms, this means that the FD_CLOSE is recorded when the connection goes into the FIN WAIT or CLOSE WAIT states. This results from the remote end performing a WSPShutdown() on the send side or a WSPCloseSocket(). Service providers shall record ONLY an FD_CLOSE network event to indicate closure of a virtual circuit, they shall NOT record an FD_READ network event to indicate this condition. The FD_QOS or FD_GROUP_QOS network event is recorded when any field in the flow spec associated with socket s or the socket group that s belongs to has changed, respectively. This change must be made available to WinSock SPI clients via the WSPIoctl() function with SIO_GET_QOS and/or SIO_GET_GROUP_QOS to retrieve the current QOS for socket s or for the socket group s belongs to, respectively. Arguments: s - A descriptor identifying the socket. hEventObject - A handle identifying the event object to be associated with the supplied set of network events. lNetworkEvents - A bitmask which specifies the combination of network events in which the WinSock SPI client has interest. lpErrno - A pointer to the error code. Return Value: The return value is 0 if the WinSock SPI client's specification of the network events and the associated event object was successful. Otherwise the value SOCKET_ERROR is returned, and a specific error number is available in lpErrno. --*/ { return SOCKET_ERROR; } // WSPEventSelect INT WSPAPI WSPSelect( IN int nfds, IN OUT fd_set FAR * readfds, IN OUT fd_set FAR * writefds, IN OUT fd_set FAR * exceptfds, IN const struct timeval FAR * timeout, OUT LPINT lpErrno ) /*++ Routine Description: This routine is used to determine the status of one or more sockets. For each socket, the caller may request information on read, write or error status. The set of sockets for which a given status is requested is indicated by an fd_set structure. All entries in an fd_set correspond to sockets created by the service provider. Upon return, the structures are updated to reflect the subset of these sockets which meet the specified condition, and WSPSelect() returns the total number of sockets meeting the conditions. A set of macros is provided for manipulating an fd_set. These macros are compatible with those used in the Berkeley software, but the underlying representation is completely different. The parameter readfds identifies those sockets which are to be checked for readability. If the socket is currently WSPListen()ing, it will be marked as readable if an incoming connection request has been received, so that a WSPAccept() is guaranteed to complete without blocking. For other sockets, readability means that queued data is available for reading so that a WSPRecv() or WSPRecvfrom() is guaranteed not to block. For connection-oriented sockets, readability may also indicate that a close request has been received from the peer. If the virtual circuit was closed gracefully, then a WSPRecv() will return immediately with 0 bytes read. If the virtual circuit was reset, then a WSPRecv() will complete immediately with an error code, such as WSAECONNRESET. The presence of out-of-band data will be checked if the socket option SO_OOBINLINE has been enabled (see WSPSetSockOpt()). The parameter writefds identifies those sockets which are to be checked for writability. If a socket is WSPConnect()ing, writability means that the connection establishment successfully completed. If the socket is not in the process of WSPConnect()ing, writability means that a WSPSend() or WSPSendTo() are guaranteed to succeed. However, they may block on a blocking socket if the len exceeds the amount of outgoing system buffer space available.. (It is not specified how long these guarantees can be assumed to be valid, particularly in a multithreaded environment.) The parameter exceptfds identifies those sockets which are to be checked for the presence of out-of-band data or any exceptional error conditions. Note that out-of-band data will only be reported in this way if the option SO_OOBINLINE is FALSE. If a socket is WSPConnect()ing (non-blocking), failure of the connect attempt is indicated in exceptfds. Any two of readfds, writefds, or exceptfds may be given as NULL if no descriptors are to be checked for the condition of interest. At least one must be non-NULL, and any non- NULL descriptor set must contain at least one socket descriptor. A socket will be identified in a particular set when WSPSelect() returns if: readfds ~~~~~~~ If WSPListen()ing, a connection is pending, WSPAccept() will succeed. Data is available for reading (includes OOB data if SO_OOBINLINE is enabled). Connection has been closed/reset/aborted. writefds ~~~~~~~~ If WSPConnect()ing (non-blocking), connection has succeeded. Data may be sent. exceptfds ~~~~~~~~~ If WSPConnect()ing (non-blocking), connection attempt failed. OOB data is available for reading (only if SO_OOBINLINE is disabled). Three macros and one upcall function are defined in the header file ws2spi.h for manipulating and checking the descriptor sets. The variable FD_SETSIZE determines the maximum number of descriptors in a set. (The default value of FD_SETSIZE is 64, which may be modified by #defining FD_SETSIZE to another value before #including ws2spi.h.) Internally, socket handles in a fd_set are not represented as bit flags as in Berkeley Unix. Their data representation is opaque. Use of these macros will maintain software portability between different socket environments. The macros to manipulate and check fd_set contents are: FD_CLR(s, *set) Removes the descriptor s from set. FD_SET(s, *set) Adds descriptor s to set. FD_ZERO(*set) Initializes the set to the NULL set. The upcall function used to check the membership is: int WPUFDIsSet ( SOCKET s, FD_SET FAR * set ); which will return nonzero if s is a member of the set, or zero otherwise. The parameter timeout controls how long the WSPSelect() may take to complete. If timeout is a null pointer, WSPSelect() will block indefinitely until at least one descriptor meets the specified criteria. Otherwise, timeout points to a struct timeval which specifies the maximum time that WSPSelect() should wait before returning. When WSPSelect() returns, the contents of the struct timeval are not altered. If the timeval is initialized to {0, 0}, WSPSelect() will return immediately; this is used to "poll" the state of the selected sockets. If this is the case, then the WSPSelect() call is considered nonblocking and the standard assumptions for nonblocking calls apply. For example, the blocking hook will not be called, and the WinSock provider will not yield. WSPSelect() has no effect on the persistence of socket events registered with WSPAsyncSelect() or WSPEventSelect(). Arguments: nfds - This argument is ignored and included only for the sake of compatibility. readfds - An optional pointer to a set of sockets to be checked for readability. writefds - An optional pointer to a set of sockets to be checked for writability exceptfds - An optional pointer to a set of sockets to be checked for errors. timeout - The maximum time for WSPSelect() to wait, or NULL for a blocking operation. lpErrno - A pointer to the error code. Return Value: WSPSelect() returns the total number of descriptors which are ready and contained in the fd_set structures, or SOCKET_ERROR if an error occurred. If the return value is SOCKET_ERROR, a specific error code is available in lpErrno. --*/ { PSOCKET_INFORMATION socketInfo; INT err; INT result; UINT fdsetCount; UINT socketCount; UINT i; PFD_SET ws1ReadFds; PFD_SET ws1WriteFds; PFD_SET ws1ExceptFds; PVOID fdsetBuffer; PULONG fdsetBufferScan; ULONG fdsetBufferLength; SOCK_ENTER( "WSPSelect", (PVOID)nfds, (PVOID)readfds, (PVOID)writefds, (PVOID)exceptfds ); SOCK_ASSERT( lpErrno != NULL ); err = SockEnterApi( TRUE, FALSE ); if( err != NO_ERROR ) { SOCK_EXIT( "WSPSelect", SOCKET_ERROR, TRUE ); *lpErrno = err; return SOCKET_ERROR; } // // Setup locals so we know how to cleanup on exit. // fdsetCount = 0; socketCount = 0; ws1ReadFds = NULL; ws1WriteFds = NULL; ws1ExceptFds = NULL; fdsetBuffer = NULL; socketInfo = NULL; // // Count the number of FD_SETs and sockets in those sets. // if( readfds != NULL ) { fdsetCount++; socketCount += readfds->fd_count; } if( writefds != NULL ) { fdsetCount++; socketCount += writefds->fd_count; } if( exceptfds != NULL ) { fdsetCount++; socketCount += exceptfds->fd_count; } // // Bail if there's no sets or no sockets. // if( fdsetCount == 0 || socketCount == 0 ) { SOCK_EXIT( "WSPSelect", 0, FALSE ); return 0; } // // Allocate a buffer to hold *all* of the mapped FD_SETs. // fdsetBufferLength = ( fdsetCount * sizeof(UINT) ) + ( socketCount * sizeof(SOCKET) ); fdsetBuffer = SOCK_ALLOCATE_HEAP( fdsetBufferLength ); if( fdsetBuffer == NULL ) { SOCK_EXIT( "WSPSelect", SOCKET_ERROR, TRUE ); *lpErrno = WSAENOBUFS; return SOCKET_ERROR; } RtlZeroMemory( fdsetBuffer, fdsetBufferLength ); // // Acquire the global lock. Any failure from this point must release // the lock before returning. // // // Build the mapped FD_SETs. // fdsetBufferScan = fdsetBuffer; if( readfds != NULL ) { err = SockMapWS2FdSetToWS1( readfds, &ws1ReadFds, &fdsetBufferScan, &socketInfo ); if( err != NO_ERROR ) { goto exit; } } if( writefds != NULL ) { err = SockMapWS2FdSetToWS1( writefds, &ws1WriteFds, &fdsetBufferScan, &socketInfo ); if( err != NO_ERROR ) { goto exit; } } if( exceptfds != NULL ) { err = SockMapWS2FdSetToWS1( exceptfds, &ws1ExceptFds, &fdsetBufferScan, &socketInfo ); if( err != NO_ERROR ) { goto exit; } } SOCK_ASSERT( socketInfo != NULL ); // // Now that we've got the sets mapped, we can let the hooker do // its thang. // SockPrepareForBlockingHook( socketInfo ); SockPreApiCallout(); result = socketInfo->Hooker->select( fdsetCount, ws1ReadFds, ws1WriteFds, ws1ExceptFds, timeout ); if( result == SOCKET_ERROR ) { err = socketInfo->Hooker->WSAGetLastError(); SOCK_ASSERT( err != NO_ERROR ); SockPostApiCallout(); goto exit; } SockPostApiCallout(); // // Now, map the WS1 FD_SETs back to WS2 FD_Sets. // result = 0; if( ws1ReadFds != NULL ) { SOCK_ASSERT( readfds != NULL ); result += SockMapWS1FdSetToWS2( ws1ReadFds, readfds ); } if( ws1WriteFds != NULL ) { SOCK_ASSERT( writefds != NULL ); result += SockMapWS1FdSetToWS2( ws1WriteFds, writefds ); } if( ws1ExceptFds != NULL ) { SOCK_ASSERT( exceptfds != NULL ); result += SockMapWS1FdSetToWS2( ws1ExceptFds, exceptfds ); } // // Success! // exit: if( err != NO_ERROR ) { *lpErrno = err; result = SOCKET_ERROR; } if( fdsetBuffer != NULL ) { SOCK_FREE_HEAP( fdsetBuffer ); } if( socketInfo != NULL ) { SockDereferenceSocket( socketInfo ); } SOCK_EXIT( "WSPSelect", result, (BOOL)( result == SOCKET_ERROR ) ); return result; } // WSPSelect BOOL SockCreateAsyncWindow( VOID ) /*++ Routine Description: This routine creates the async window. Arguments: None. Return Value: BOOL - TRUE if the window was created successfully, FALSE otherwise. --*/ { WNDCLASS WndClass; // // First ensure that the window hasn't already been created. // if( SockAsyncWindowHandle != NULL ) { return TRUE; } SockAcquireGlobalLock(); if( SockAsyncWindowHandle != NULL ) { SockReleaseGlobalLock(); return TRUE; } // // Register the window class. // WndClass.style = 0; WndClass.lpfnWndProc = SockAsyncWindowProc; WndClass.cbClsExtra = 0; WndClass.cbWndExtra = 0; WndClass.hInstance = NULL; WndClass.hIcon = NULL; WndClass.hCursor = NULL; WndClass.hbrBackground = NULL; WndClass.lpszMenuName = NULL; WndClass.lpszClassName = SockAsyncWindowClassName; if( !RegisterClass( &WndClass ) ) { SOCK_PRINT(( "SockCreateAsyncWindow: cannot register class\n" )); SockReleaseGlobalLock(); return FALSE; } // // Create the window. // SockAsyncWindowHandle = CreateWindow( SockAsyncWindowClassName, "", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, NULL, NULL ); if( SockAsyncWindowHandle == NULL ) { SOCK_PRINT(( "SockCreateAsyncWindow: cannot create window\n" )); SockReleaseGlobalLock(); return FALSE; } IF_DEBUG(SELECT) { SOCK_PRINT(( "SockCreateAsyncWindow: created %08lX\n", SockAsyncWindowHandle )); } ShowWindow( SockAsyncWindowHandle, SW_HIDE ); UpdateWindow( SockAsyncWindowHandle ); // // Success! // SockReleaseGlobalLock(); return TRUE; } // SockCreateAsyncWindow VOID SockDestroyAsyncWindow( VOID ) /*++ Routine Description: This routine destroys the async window. Arguments: None. Return Value: None. --*/ { SockAcquireGlobalLock(); if( SockAsyncWindowHandle != NULL ) { IF_DEBUG(SELECT) { SOCK_PRINT(( "SockDestroyAsyncWindow: destroying %08lX\n", SockAsyncWindowHandle )); } SOCK_REQUIRE( DestroyWindow( SockAsyncWindowHandle ) ); SockAsyncWindowHandle = NULL; } SockReleaseGlobalLock(); } // SockDestroyAsyncWindow LRESULT CALLBACK SockAsyncWindowProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ) { LRESULT Result; Result = 0; switch( msg ) { case WM_DESTROY : PostQuitMessage( 0 ); break; case WM_MY_ASYNC_SELECT_MESSAGE : SockProcessAsyncSelectMessage( wParam, lParam ); default : Result = DefWindowProc( hwnd, msg, wParam, lParam ); break; } return Result; } // SockAsyncWindowProc VOID SockProcessAsyncSelectMessage( WPARAM wParam, LPARAM lParam ) { PSOCKET_INFORMATION socketInfo; // // See if we can map the WS1 handle to our socket info structure. // If we can't, it's probably due to a race condition between the // application and the WS1 DLL, so we'll just eat the message. // socketInfo = SockFindAndReferenceWS1Socket( (SOCKET)wParam ); if( socketInfo != NULL ) { // // Post the message to the application. // PostMessage( socketInfo->WindowHandle, socketInfo->WindowMessage, (WPARAM)socketInfo->WS2Handle, lParam ); SockDereferenceSocket( socketInfo ); } } // SockProcessAsyncSelectMessage INT SockMapWS2FdSetToWS1( PFD_SET WS2FdSet, PFD_SET * WS1FdSet, PULONG * TargetBuffer, PSOCKET_INFORMATION * SocketInfo ) { PSOCKET_INFORMATION socketInfo; PSOCKET_INFORMATION returnedSocketInfo; PFD_SET fdSet; PULONG bufferScan; UINT i; INT err; // // Sanity check. // SOCK_ASSERT( WS2FdSet != NULL ); SOCK_ASSERT( WS1FdSet != NULL ); SOCK_ASSERT( TargetBuffer != NULL ); SOCK_ASSERT( SocketInfo != NULL ); // // Grab the starting location for the mapped FD_SET. // bufferScan = *TargetBuffer; fdSet = (PFD_SET)bufferScan; returnedSocketInfo = *SocketInfo; // // Set the mapped count. // *bufferScan++ = WS2FdSet->fd_count; // // Scan the incoming array and map the WS2 handles to WS1 handles. // for( i = 0 ; i < WS2FdSet->fd_count ; i++ ) { socketInfo = SockFindAndReferenceWS2Socket( WS2FdSet->fd_array[i] ); if( socketInfo == NULL || socketInfo->State == SocketStateClosing ) { IF_DEBUG(SELECT) { SOCK_PRINT(( "SockMapWS2FdSetToWS1: failed on %s handle: %lx\n", socketInfo == NULL ? "unknown" : "closed", fdSet->fd_array[i] )); } if( socketInfo != NULL ) { SockDereferenceSocket( socketInfo ); } return WSAENOTSOCK; } // // Save the WS1 handle in the mapped FD_SET. // *bufferScan++ = socketInfo->WS1Handle; // // This is kind of a hack. The caller (WSPSelect) needs the // PSOCKET_INFORMATION structure for any one socket in any of // the FD_SETs. If we have not yet returned a PSOCKET_INFORMATION // structure to the caller, we'll pass it the current one and // *not* dereference it. Otherwise (we've already passed one back) // we'll dereference it ourselves. // if( returnedSocketInfo == NULL ) { returnedSocketInfo = socketInfo; } else { SockDereferenceSocket( socketInfo ); } } // // Return the pointers back to the caller. // *WS1FdSet = fdSet; *TargetBuffer = bufferScan; *SocketInfo = returnedSocketInfo; return NO_ERROR; } // SockMapWS2FdSetToWS1 INT SockMapWS1FdSetToWS2( PFD_SET WS1FdSet, PFD_SET WS2FdSet ) { PSOCKET_INFORMATION socketInfo; UINT i, j; // // Sanity check. // SOCK_ASSERT( WS2FdSet != NULL ); SOCK_ASSERT( WS1FdSet != NULL ); // // Move the count over. // WS2FdSet->fd_count = WS1FdSet->fd_count; // // Map the sockets over. // for( i = 0, j = 0 ; i < WS1FdSet->fd_count ; i++ ) { // // Try to find the info for this WS1 socket. If we can't, // just remove it from the list. // socketInfo = SockFindAndReferenceWS1Socket( WS1FdSet->fd_array[i] ); if( socketInfo == NULL || socketInfo->State == SocketStateClosing ) { WS2FdSet->fd_count--; if( socketInfo != NULL ) { SockDereferenceSocket( socketInfo ); } } else { WS2FdSet->fd_array[j++] = socketInfo->WS2Handle; SockDereferenceSocket( socketInfo ); } } return j; } // SockMapWS1FdSetToWS2