/*++

Copyright (c) 1995 Intel Corp

Module Name:

    chatsock.c

Abstract:

    Socket-related functions for the WinSock2 Chat sample application.

--*/

#include "nowarn.h"  /* turn off benign warnings */
#ifndef _WINSOCKAPI_
#define _WINSOCKAPI_   /* Prevent inclusion of winsock.h in windows.h */
#endif
#include <windows.h>
#include "nowarn.h"  /* some warnings may have been turned back on */
#include <winsock2.h>
#include <stdlib.h>
#include <assert.h>
#include "ws2chat.h"
#include "chatsock.h"
#include "chatdlg.h"



//
// Static Globals
//

// points to an array of WSAPROTOCOL_INFO structs
static LPWSAPROTOCOL_INFO InstalledProtocols = NULL; 

// number of WSAPROTOCOL_INFO structs in the InstalledProtocols buffer 
static int NumProtocols = 0;

// static array of sockets, one for each listening socket  
static LISTENDATA ListeningSockets[MAX_LISTENING_SOCKETS];

// number of meaningful entries in ListeningSockets
static int NumFound = 0;




// 
// Function Prototypes -- Internal Functions
//

DWORD
IOThreadFunc(
    IN LPVOID ParamPtr);

BOOL
HandleSocketEvent(
    IN OUT PCONNDATA ConnData);

BOOL
HandleOutputEvent(
    IN OUT PCONNDATA);

BOOL
HandleOtherEvent(
    IN     DWORD     WaitStatus,
    IN OUT PCONNDATA ConnData);

int
HandleEvents(
    IN PCONNDATA          ConnData,
    IN LPWSANETWORKEVENTS NetworkEvents);

BOOL
FillLocalAddress(
    IN LPVOID          SockAddr);

int
DoRecv(
    IN PCONNDATA ConnData);

int
DoOverlappedCallbackSend(
    IN POUTPUT_REQUEST OutReq,
    IN PCONNDATA       ConnData);

int
DoOverlappedEventSend(
    IN POUTPUT_REQUEST OutReq,
    IN PCONNDATA       ConnData);

int
DoSend(
    IN POUTPUT_REQUEST OutReq,
    IN PCONNDATA       ConnData);

void CALLBACK
SendCompFunc(
    IN DWORD           Error,
    IN DWORD           BytesTransferred,
    IN LPWSAOVERLAPPED OverlappedPtr,
    IN DWORD           Flags);

int CALLBACK
AcceptCondFunc(
    IN LPWSABUF    CallerId,
    IN LPWSABUF    CallerData,
    IN LPQOS       CallerSQos,
    IN LPQOS       CallerGQos,
    IN LPWSABUF    CalleeId,
    OUT LPWSABUF   CalleeData,
    OUT GROUP FAR  *Group,
    IN DWORD   CallbackData);

LPWSAPROTOCOL_INFO
GetProtoFromSocket(
    IN SOCKET Socket);

BOOL
GetMaxMsgSize(
    IN OUT PCONNDATA ConnData);




//
// Function Definitions
//


BOOL
InitWS2(void)
/*++

Routine Description:

    Calls WSAStartup, makes sure we have a good version of WinSock2

Arguments:

    None.

Return Value:

    TRUE - WinSock 2 DLL successfully started up

    FALSE - Error starting up WinSock 2 DLL.

--*/

{
    int           Error;              // catches return value of WSAStartup
    WORD          VersionRequested;   // passed to WSAStartup
    WSADATA       WsaData;            // receives data from WSAStartup
    BOOL          ReturnValue = TRUE; // return value
    
    // Start WinSock 2.  If it fails, we don't need to call
    // WSACleanup().
    VersionRequested = MAKEWORD(VERSION_MAJOR, VERSION_MINOR); 
    Error = WSAStartup(VersionRequested, &WsaData);
    if (Error) {
        MessageBox(GlobalFrameWindow,
                   "Could not find high enough version of WinSock",
                   "Error", MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
        ReturnValue = FALSE;
    } else {

        // Now confirm that the WinSock 2 DLL supports the exact version
        // we want. If not, make sure to call WSACleanup().
        if (LOBYTE(WsaData.wVersion) != VERSION_MAJOR ||
            HIBYTE(WsaData.wVersion) != VERSION_MINOR) {
            MessageBox(GlobalFrameWindow,
                       "Could not find the correct version of WinSock",
                       "Error",  MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
            WSACleanup();
            ReturnValue = FALSE;
        }
    }
    return(ReturnValue);
    
} // InitWS2()





BOOL
FindProtocols(void)
/*++

Routine Description:

    Finds out about all transport protocols installed on the local
    machine and saves the information into global variables.

Implementation:

    This function uses WSAEnumProtocols to find out about all
    installed protocols on the local machine.  It stores this
    information in two variables which are global to this file;
    InstalledProtocols is a pointer to a buffer of WSAPROTOCOL_INFO
    structs, while NumProtocols is the number of protocols in that
    buffer.  This function is the only function in the file allowed to
    touch these variables. 

Arguments:

    None.

Return Value:

    TRUE - Successfully initialized the protocol buffer.

    FALSE - Some kind of problem arose.  The user is informed of the
    error.

--*/
{
    
    DWORD BufferSize = 0;       // size of InstalledProtocols buffer
    char  MsgText[MSG_LEN];     // holds message strings

    // Call WSAEnumProtocols to figure out how big of a buffer we need.
    NumProtocols = WSAEnumProtocols(NULL,  
                                    NULL,  
                                    &BufferSize); 

    if ((NumProtocols != SOCKET_ERROR) && (WSAGetLastError() != WSAENOBUFS)) {
        // We're in trouble!!
        MessageBox(GlobalFrameWindow, "WSAEnumProtocols is broken.", "Error", 
                   MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
        goto Fail;
    }
    
    // Allocate a buffer, call WSAEnumProtocols to get an array of
    // WSAPROTOCOL_INFO structs.
    InstalledProtocols = (LPWSAPROTOCOL_INFO)malloc(BufferSize);
    if (InstalledProtocols == NULL) {
        MessageBox(GlobalFrameWindow, "malloc failed.", "Error", 
                   MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
        goto Fail;
    }
    NumProtocols = WSAEnumProtocols(NULL, 
                                    (LPVOID)InstalledProtocols,
                                    &BufferSize);
    if (NumProtocols == SOCKET_ERROR) {
        // uh-oh
        wsprintf(MsgText, "WSAEnumProtocols failed.  Error Code: %d",
                 WSAGetLastError());
        MessageBox(GlobalFrameWindow, MsgText, "Error", 
                   MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
        goto Fail;
    }
    return(TRUE);

 Fail:

    WSACleanup();
    return(FALSE);

} // FindProtocols()





BOOL
FillLocalAddress(
    IN LPWSABUF SockAddrBuf)
/*++

Routine Description:

    Takes the buffer passed in, and fills it in with a local address,
    as given by the user, on which a socket may listen.  The buffer
    can be of any length, but the first two bytes must be the address
    family of the protocol, and this value is used to pop up an
    appropriate dialog box.  For address families chat doesn't
    recognize, a default dialog box is shown and the user may fill in
    the address by hand.

Arguments:

    SockAddrBuf -- A buffer that will receive the local address for
    listening as chosen by the user.  The first two bytes should
    indicate the address family.

Return Value:

    TRUE -- FillLocalAddress recognized the address family and
    successfully filled in the struct sockaddr.  This structure is
    ready to be passed to a bind() call.

    FALSE -- FillLocalAddress didn't touch the struct sockaddr,
    probably because the user cancelled the dialog box.

--*/
{

    struct sockaddr_in *SockAddrInet; // used to cast SockAddr to an
                                      // Internet-style address
    BOOL ReturnValue = TRUE;          // holds the return value
    
    switch (((struct sockaddr *)SockAddrBuf->buf)->sa_family) {
        
    case AF_INET:
        
        // Bring up the dialog box that lets the user pick an internet
        // port to listen on.  Cast the pointer so we can now access
        // it's fields as a struct sockaddr_in, the address structure
        // for internet addresses. 
        SockAddrInet = (struct sockaddr_in *)SockAddrBuf->buf;
        if (!DialogBoxParam(GlobalInstance, 
                            "InetListenPortDlg", 
                            GlobalFrameWindow, 
                            InetListenPortDlgProc,
                            (LPARAM)SockAddrInet)) {
            ReturnValue = FALSE;
        }
        break;

    default:
        
        // Bring up the default dialog box to let the user pick an
        // address to listen on, regardless of the address family.
        if (!DialogBoxParam(GlobalInstance,
                            "DefaultListenAddrDlg",
                            GlobalFrameWindow,
                            DefaultListenAddrDlgProc,
                            (LPARAM)SockAddrBuf)) {
            ReturnValue = FALSE;
        }
        break;

    }
    return(ReturnValue);
    
} // FillLocalAddress()





BOOL
ListenAll(void)

/*++

Routine Description:

    For each installed, connection-oriented protocol, this function
    creates a socket, binds to a local address, and listens on the
    created socket.  The socket is set up for Windows message
    notification for any connection attempts.

Arguments:

    None.

Return Value:

    TRUE - Successfully listened on all installed protocols.

    FALSE - Error listening on all installed protocols.  It is not an
    error if there are more connection-oriented protocols than
    MAX_LISTENING_SOCKETS installed; in this case we just ignore any
    extra protocols.

--*/

{
    LPWSAPROTOCOL_INFO COProtocolInfo; // current protocol info to examine
    int             i;                // counting variable
    char            MsgText[MSG_LEN]; // holds message strings
    int             Error;            // holds return values
    int             Index;            // indexes into text messages
    WSABUF          SockAddrBuf;      // to pass to FillLocalAddress
    
    // Find all protocols that support connection-oriented data
    // transfer; create a socket, bind to a local address and listen
    // for connections on that socket for each such protocol found.
    // Also fill in an entry in the ListeningSockets array.
    for (i = 0; i < NumProtocols; i++) {

        COProtocolInfo = &InstalledProtocols[i];
        assert(COProtocolInfo != NULL);
        if (UseProtocol(COProtocolInfo)) {

            // We've found a suitable protocol.  Create a socket, fill
            // in the next entry in ListeningSockets
            ListeningSockets[NumFound].Socket = WSASocket(0,              
                                                          0,              
                                                          0,              
                                                          COProtocolInfo, 
                                                          0,              
                                                          WSA_FLAG_OVERLAPPED);
            if (ListeningSockets[NumFound].Socket == INVALID_SOCKET) {
                MessageBox(GlobalFrameWindow, "Could not open a socket.", 
                           "Non-fatal error.", 
                           MB_OK | MB_SETFOREGROUND);
                continue;
            }
            ListeningSockets[NumFound].ProtocolInfo = COProtocolInfo;
            
            // Allocate a block of memory for the socket address and
            // zero it out.
            SockAddrBuf.len = COProtocolInfo->iMaxSockAddr;
            SockAddrBuf.buf = (char *)malloc(SockAddrBuf.len);
            if (!SockAddrBuf.buf) {
                ChatSysError("malloc()", 
                             "ListenAll()", 
                             TRUE);
            }
            memset(SockAddrBuf.buf, 0, SockAddrBuf.len);
            
            // Fill in the address family of the socket address. The
            // garbage on the left-hand side of this assignment is to
            // make the generic SockAddrBuf.buf look like a struct
            // sockaddr pointer for the purposes of assigning the
            // first two bytes to be the appropriate address family.
            ((struct sockaddr *)SockAddrBuf.buf)->sa_family = 
              (u_short)COProtocolInfo->iAddressFamily;

            // Get a local address to bind the socket to
            FillLocalAddress(&SockAddrBuf);       

            // Bind the socket to SockAddr.
            Error = bind(ListeningSockets[NumFound].Socket, 
                         (struct sockaddr *)SockAddrBuf.buf, 
                         SockAddrBuf.len);
            if (Error == SOCKET_ERROR) {

                // bind() failed
                MessageBox(GlobalFrameWindow, "Could not bind the socket.",
                           "Non-fatal error", MB_OK | MB_SETFOREGROUND);
                free(SockAddrBuf.buf);
                continue;
            }
            free(SockAddrBuf.buf);

            // Set up the socket for windows message event notification.
            // Note that this call automatically puts the socket into
            // non-blocking mode, as if we had called WSAIoctl with
            // the FIONBIO flag.
            Error = WSAAsyncSelect(ListeningSockets[NumFound].Socket,
                                   GlobalFrameWindow, 
                                   USMSG_ACCEPT, 
                                   FD_ACCEPT);
            if (Error == SOCKET_ERROR) {
                MessageBox(GlobalFrameWindow, "Error: WSAAsyncSelect()", 
                           "Non-fatal error", MB_OK | MB_SETFOREGROUND);
                continue;
            }
            
            // Listen for incoming connection requests on the socket.
            Error = listen(ListeningSockets[NumFound].Socket,
                           SOMAXCONN);
            if (Error == SOCKET_ERROR) {
                MessageBox(GlobalFrameWindow, "Error: listen()", 
                           "Non-fatal error",
                           MB_OK | MB_SETFOREGROUND);
                continue;
            }
            
            // Looks good -- increase the count, check for overflow of
            // the max amount, re-iterate if we're ok.
            if (++NumFound == MAX_LISTENING_SOCKETS) {
                wsprintf(MsgText, 
                         "More than %d useable protocols. Ignoring extras.",
                         MAX_LISTENING_SOCKETS);
                MessageBox(GlobalFrameWindow, MsgText, "Alert", 
                           MB_OK | MB_SETFOREGROUND);
                break;
            } 
                    
        } // if (UseProtocol(COProtocolInfo)) 
    } // for(; ; ;)

    if (NumFound == 0) {

        Index = wsprintf(MsgText, 
                         "Couldn't find a suitable protocol ");
        Index += wsprintf(MsgText + Index, 
                          "and/or no listening sockets could be opened.\r\n");
        wsprintf(MsgText + Index, "Shall we continue anyway?");

        if (MessageBox(GlobalFrameWindow, MsgText, "No protocols.",
                       MB_ICONQUESTION | MB_YESNO | MB_SETFOREGROUND) 
            == IDNO) {

            WSACleanup();
            return(FALSE);
        }
    }

    return(TRUE);

} // ListenAll()





BOOL
UseProtocol(
    IN LPWSAPROTOCOL_INFO Proto)
/*++

Routine Description:

    Returns true if Proto is suitable for use by Chat.

Arguments:

    Proto -- Points to a protocol information struct.

Return Value:

    TRUE -- Chat likes it.

    FALSE -- Get this chintzy protocol out of here!

--*/
{

    if (!(Proto->dwServiceFlags1 & XP1_CONNECTIONLESS) &&
        (Proto->dwServiceFlags1 & XP1_GUARANTEED_DELIVERY) &&
        (Proto->dwServiceFlags1 & XP1_GUARANTEED_ORDER)) {
        
        return(TRUE);
    
    } else {
        
        return(FALSE);
    }
} // UseProtocol





void 
CleanUpSockets(void)
/*++

Routine Description:

    This function closes all listening sockets.

Arguments:

    None.

Return Value:

    None.

--*/
{

    int i; // counting variable
    
    for (i = 0; i < NumFound; i++) {
        closesocket(ListeningSockets[i].Socket);
    }
} // CleanUpSockets()





int CALLBACK
AcceptCondFunc(
    IN LPWSABUF   CallerId,
    IN LPWSABUF   CallerData,
    IN LPQOS      CallerSQos,
    IN LPQOS      CallerGQos,
    IN LPWSABUF   CalleeId,
    OUT LPWSABUF  CalleeData,
    OUT GROUP FAR *Group,
    IN DWORD  CallbackData)
/*++

Routine Description:

    Condition function called when an incoming connection request is
    handled. 

Implementation:

    This function allows the user to accept or reject an incoming
    connection request after examining the caller's name and the
    subject of the call. If the call is accepted, and
    connection-time data transfer is supported by the particular
    protocol on which the connection is made, a dialog box comes up
    which prompts the user for his/her name.

Arguments:

    CallerId -- Supplies the address of the caller.

    CallerData -- Supplies the caller's user data.  Chat uses this
    parameter to send the caller's name and the subject of the call.

    CallerSQos -- Supplies the forward and backward QOS.

    CallerGQos -- Supplies the forward and backward flow specs for the
    socket group the caller is to create.  Not used by chat.

    CalleeId -- Supplies the local address.

    CalleeData -- Returns user data back to the caller (Chat uses this
    parameter to return the callee's name) 

    Group -- Returns the appropriate group action to take on the
    connecting socket.  Not used by chat.  Always returns NULL.

    CallbackData -- Supplies a pointer the CONNDATA structure
    associated with this connection.

Return Value:

    CF_ACCEPT - Accept the connection request from the caller.

    CF_REJECT - Reject the connection request from the caller.

--*/

{
    char          MsgText[MSG_LEN];         // build message string here   
    char          TitleText[TITLE_LEN + 1]; // build title string here
    PCONNDATA     ConnData;                 // connection-specific data
    int           Index;                    // index into MsgText
    int           ReturnValue = CF_ACCEPT;  // return value
    
    ConnData = (PCONNDATA)CallbackData;

    // CallerId contains the socket address of the connecting entity.
    // Copy this into the connection-specific data.
    ConnData->RemoteSockAddr.len = CallerId->len;
    ConnData->RemoteSockAddr.buf = malloc(ConnData->RemoteSockAddr.len);
    if (ConnData->RemoteSockAddr.buf == NULL) {
        ChatSysError("malloc()",
                     "AcceptCondFunc()",
                     TRUE);
    }
    memcpy((char *)ConnData->RemoteSockAddr.buf, (char *)CallerId->buf, 
           CallerId->len);
    
    // Translate the RemoteSockAddr into a human readable form, and
    // store it in ConnData->PeerAddress
    GetAddressString(ConnData->PeerAddress, 
                     ConnData->RemoteSockAddr.buf,
                     ConnData->RemoteSockAddr.len,
                     ConnData->ProtocolInfo);

    Index = wsprintf(MsgText, "Someone is attempting a chat connection.\r\n");

    if (CallerData != NULL) {
        
        // The connection request has come with some caller data.  Use
        // it to inform the user of who is trying to connect
        ExtractTwoStrings(CallerData->buf, 
                          ConnData->PeerName, 
                          NAME_LEN + 1, 
                          ConnData->Subject, 
                          SUB_LEN + 1);
        
        // Build the strings for the message box and the title of the
        // connection window
        Index += wsprintf(MsgText + Index,
                 "From: %s\r\nSubject: %s\r\n", ConnData->PeerName, 
                          ConnData->Subject);
        wsprintf(TitleText, "Connected to: %s @ %s", ConnData->PeerName, 
                 ConnData->PeerAddress);
        
    } else {
        
        // There is no caller data...build a string for the title.
        wsprintf(TitleText, "Connected to: %s", ConnData->PeerAddress);
    }

    // Continue building MsgText.
    Index += wsprintf(MsgText + Index, "Address: %s\r\n", 
                      ConnData->PeerAddress);          
    Index += wsprintf(MsgText + Index, "Would you like to accept it?");
    
    // Prompt the user to accept or reject the connection.
    //
    // ****NOTE****
    // This is NOT the right way to do this.  The application should
    // not hold up this thread by putting up this message box, or the
    // dialog box below.  As mentioned in the API spec, this function
    // should  return "as soon as possible", and clearly this is not
    // what's being done here.  Please look to future versions of chat
    // for a fix.  Thanks.
    if (MessageBox(ConnData->ConnectionWindow, MsgText,
                   "Connection Request", 
                   MB_ICONQUESTION | MB_YESNO | MB_SETFOREGROUND) == IDYES) {
        
        // The user has accepted the connection request.
        SetWindowText(ConnData->ConnectionWindow, TitleText);
        if (CalleeData != NULL) {

            // We can try to pass user data back. Call up a dialog box
            // to get a name string and put it into CalleeData.
            if (!DialogBoxParam(GlobalInstance, 
                                "AcceptConnectionDlg",
                                ConnData->ConnectionWindow,
                                AcceptConnectionDlgProc, 
                                (LPARAM)CalleeData)) {
                
                CalleeData->len = 0;
            }
        }
        ReturnValue = CF_ACCEPT;

    } // if (MessageBox(...))
    else {

        // The user has rejected the connection request.
        ReturnValue = CF_REJECT;
    }
    return(ReturnValue);

} // AcceptCondFunc()





BOOL
GetAddressString(
    OUT char            *String,
    IN  LPVOID          SockAddr,
    IN  int             SockAddrLen,
    IN  LPWSAPROTOCOL_INFO ProtocolInfo)
/*++

Routine Description:

    This function translates the SockAddr into a human-readable
    string, if possible.  If chat doesn't recognize the protocol, then
    the string "(unknown)" is returned in String.

Arguments:

    String -- Returns the string representing the SockAddr in a
    human-readable form.

    SockAddr -- An address.

    SockAddrLen -- The length, in bytes, of SockAddr.

    ProtocolInfo -- Pointer to the protocol information structure.

Return Value:

    TRUE -- Chat recognized the protocol family and successfully
    translated the address.

    FALSE -- Chat did not recognize the protocol family and returned
    the string "(unknown)".

--*/
{

    char               *TempString;   // string returned by inet_ntoa
    struct sockaddr_in *SockAddrInet; // casts the address to a sockaddr_in
    BOOL               ReturnValue;   // holds the return value

    ReturnValue = TRUE;

    switch (ProtocolInfo->iAddressFamily) {

    case AF_INET:
        
        // It's an Internet-style address.
        SockAddrInet = (struct sockaddr_in *)SockAddr;
        TempString = inet_ntoa(SockAddrInet->sin_addr);
        if (TempString == NULL) {
            MessageBox(NULL, "inet_ntoa() failed.", "Error.", 
                       MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
        } else {
            strcpy(String, TempString);
        }
        ReturnValue = TRUE;
        break;

    default:

        strcpy(String, "(unknown)");
        ReturnValue = FALSE;
        break;
    }
    return(ReturnValue);

} // GetAddressString()





int
HandleEvents(
    IN PCONNDATA          ConnData,
    IN LPWSANETWORKEVENTS NetworkEvents)

/*++

Routine Description:

    Handles network events that may occur on a connected socket.
    The events handled by this function are FD_CLOSE, FD_READ, and
    FD_WRITE.  

Arguments:

    ConnData - Supplies a pointer to data for the connection on which
    the event happened.

    NetworkEvents - Supplies a WSANETWORKEVENT structure, which is a
    record of the network events that have occurred as well as any
    accompanying error-codes.

Return Value:

    CHAT_OK -- The network event was successfully handled.

    CHAT_ERROR -- Some kind of error occurred while handling the
    event, and the connection should be closed.

    CHAT_CLOSED -- The connection has been gracefully closed.

--*/

{
    int Result;                // holds the result of DoRecv
    int ReturnValue = CHAT_OK; // return value
    
    // The following three if statements all execute unless one gets
    // an error or closed socket, in which case we return immediately.
    if (NetworkEvents->lNetworkEvents & FD_READ) {
        
        // An FD_READ event has occurred on the connected socket.
        if (NetworkEvents->iErrorCode[FD_READ_BIT] == WSAENETDOWN) {

            // There is an error.
            ReturnValue = CHAT_ERROR;
            goto Done;

        } else {

            // Read data off the socket...
            Result = DoRecv(ConnData);
            if ((Result == CHAT_ERROR) || (Result == CHAT_CLOSED)) {
                ReturnValue = Result;
                goto Done;
            }
        }        
    } 

    if (NetworkEvents->lNetworkEvents & FD_WRITE) {
        
        // An FD_WRITE event has occurred on the connected socket.
        if (NetworkEvents->iErrorCode[FD_WRITE_BIT] == WSAENETDOWN) {

            // There is an error.
            ReturnValue = CHAT_ERROR;
            goto Done;

        } else {
            
            // Allow chat to send on this socket, and signal the
            // OuputEventObject in case there is pending output that is
            // not completed due to WSAEWOULDBLOCK.
            ConnData->WriteOk = TRUE;
            SetEvent(ConnData->OutputEventObject);
        }
    }

    if (NetworkEvents->lNetworkEvents & FD_CLOSE) {

        if (NetworkEvents->iErrorCode[FD_CLOSE_BIT] == 0) {

            // A graceful shutdown has occurred...
            ReturnValue = CHAT_CLOSED;
            goto Done;

        } else {
            
            // This is some other type of abortive close or failure...
            ReturnValue = CHAT_ABORTED;
            goto Done;
        }
        
    } 

 Done:
    return(ReturnValue);

} // HandleEvents()





DWORD
IOThreadFunc(
    IN LPVOID ParamPtr)

/*++

Routine Description:

    This routine is invoked as a separate thread to handle all input
    and output for a connection.  

Implementation:
    
    This thread sits in a loop, waiting for one of several things to
    occur.  These can be:

        1. The user interface thread has some input ready to be sent,
        and has signaled the ouput event object.

        2. WinSock 2 has indicated there is a network event associated
        with the socket.  

        3. Callback notification.  This can be via an event or via a
        queued callback function, depending on whether or not we've
        compiled with the CALLBACK_NOTIFICATION flag.
        
    The return value from the wait indicates what has
    happened, and a switch statement handles all the possible cases.
    When there is an error or the connection is being closed, the loop
    is broken and the thread will exit and the connection window will
    be closed (and sent a WM_DESTROY message).

Arguments:

    ParamPtr - Supplies a pointer to a ConnData structure that holds
    data specific to this connection.

Return Value:

    0 - Always returns 0.  The return value is not needed.

--*/
{
    char             MsgText[MSG_LEN]; // holds message strings
    DWORD            WaitStatus;       // holds return value of the wait
    PCONNDATA        ConnData;         // connection-specific data
    BOOL             KeepGoing = TRUE; // keep processing output requests?
    BOOL             Forever = TRUE;   // constant to avoid warning
    
    ConnData =  (PCONNDATA)ParamPtr;

    // Initialize the EventArray.  The only two events in it, for now,
    // are the Socket and Ouput events; if this is the event
    // notification version of chat, then there will be an additional
    // event for each overlapped send waiting for a completion
    // notification. 
    ConnData->NumEvents = 2;
    ConnData->EventArray[0] = ConnData->SocketEventObject;
    ConnData->EventArray[1] = ConnData->OutputEventObject;

    // Initialize the array of output requests, which is indexed in
    // parallel to the above event array.  When an event is signaled,
    // we can figure out which output request and overlapped
    // structures to free by indexing into this array.
    // These first two entries should never be referenced, because
    // their parallel entries in EventArray (see above) are for the
    // permanent Socket and Output event objects.
    ConnData->OutReqArray[0] = ConnData->OutReqArray[1] = NULL;

    while (Forever) {

        // Wait for an event (or a queued callback function) to wake
        // us up.  This is an alertable wait state (fAlertable == TRUE).
        WaitStatus = WSAWaitForMultipleEvents(ConnData->NumEvents,
                                              ConnData->EventArray,
                                              FALSE,        // fWaitAll
                                              WSA_INFINITE, // dwTimeout
                                              TRUE);        // fAlertable
        
        // Determine why we woke up and act accordingly.  Note that
        // breaking out of the switch causes us to break out of the
        // while loop as well.  When we don't want to break out of the
        // loop, case statements end with the continue statement.
        switch (WaitStatus) {
            
        case WSA_WAIT_FAILED:

            // A fatal error.  Pop up a message box and break out of
            // the while loop to end the thread.
            
            wsprintf(MsgText, 
                     "WSAWaitForMultipleEvents() failed.  Error code: %d", 
                     WSAGetLastError());
            MessageBox(ConnData->ConnectionWindow, MsgText, "Fatal Error",
                       MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
            break;

        case WAIT_IO_COMPLETION:
            
            // An I/O completion routine has been executed.  Cleanup
            // has already occurred in SendCompFunc, so there is
            // nothing left to do.  Just reiterate through the loop.
            
            continue;
            
        case WSA_WAIT_EVENT_0:
            
            // The SocketEventObject has been signaled.  Handle it in
            // a separate function.  Break out of the thread if
            // HandleSocketEvent returns FALSE, indicating error.
            if (HandleSocketEvent(ConnData)) {
                continue;
            } else {
                break;
            }
            
        // Please note: WSA_WAIT_EVENT_[1,2,3] are defined in
        // chatsock.h, not winsock2.h like WSA_WAIT_EVENT_0

        case WSA_WAIT_EVENT_1:

            // The OuputEventObject has been signaled.  Handle it in a
            // separate function, and break out of the thread if it
            // returns FALSE
            if (HandleOutputEvent(ConnData)) {
                continue;
            } else {
                break;
            }                

        default:

            // Some other event has been signaled.  Handle it in a
            // separate function, and break out of the thread if it
            // returns FALSE.
            if (HandleOtherEvent(WaitStatus, ConnData)) {
                continue;
            } else {
                break;
            }

        } // switch (WaitStatus)

        // Break out of the while loop.
        break;
    
    } // while (1)
    
    // Thread is ending because the connection was closed or an error
    // occurred 
    PostMessage(ConnData->ConnectionWindow, 
                WM_CLOSE, 
                0, 
                0);
    return(0);

} // IOThreadFunc()





BOOL
HandleSocketEvent(
    IN OUT PCONNDATA ConnData)
/*++

Routine Description:

    Handles the case in IOThreadFunc where the thread is woken up by a
    signal to the socket event object.

Arguments:

    ConnData - Supplies a pointer to data for the connection on which
    the event happened.
    
Return Value:

    TRUE -- Chat successfully handled the event and the thread should
    continue on.

    FALSE -- An error occurred and Chat should kill the thread and the
    connection. 

--*/
{
    int              Result;           // holds return values
    WSANETWORKEVENTS NetworkEvents;    // tells us what events happened
    char             MsgText[MSG_LEN]; // holds text strings
    BOOL             ReturnValue;      // holds the return value

    // Find out what happened and act accordingly.
    Result = WSAEnumNetworkEvents(ConnData->Socket, 
                                  ConnData->SocketEventObject,
                                  &NetworkEvents);
    if (Result == SOCKET_ERROR) {
        
        // Handle the fatal error.
        wsprintf(MsgText,
                 "WSAEnumNetworkEvents failed.  Error code: %d", 
                 Result);
        MessageBox(GlobalFrameWindow, MsgText, "Fatal Error",
                   MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
        ReturnValue = FALSE;
        
    } else {
        
        // Handle all of the network events on the given socket
        Result = HandleEvents(ConnData, &NetworkEvents);
        
        if (Result == CHAT_CLOSED) {
            
            // HandleEvents() has determined that the remote party
            // has terminated the connection.  Inform the user, and
            // return. 
            if (ConnData->PeerName[0] != 0) {
                wsprintf(MsgText, 
                         "%s @ %s has terminated the connection.", 
                         ConnData->PeerName, ConnData->PeerAddress);
            } else {
                wsprintf(MsgText, 
                         "The party at %s has terminated the connection.",
                         ConnData->PeerAddress);
            }
            MessageBox(ConnData->ConnectionWindow, MsgText, "Sorry!", 
                       MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
            ReturnValue = FALSE;
            
        } else if (Result == CHAT_ABORTED) {

            // HandleEvents has determined that the connection has
            // been aborted due to an undetermined error.
            MessageBox(ConnData->ConnectionWindow, 
                       "The connection has been broken.",
                       "Connection aborted.", 
                       MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
            ReturnValue = FALSE;
            
        } else if (Result == CHAT_ERROR) {
            
            // HandleEvents() has returned an error.  Inform the
            // user and break to exit the thread and kill the window.
            MessageBox(ConnData->ConnectionWindow,
                       "An unidentified network or system error occurred.",
                       "Error.", MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
            ReturnValue = FALSE;
            
        } else if (Result == CHAT_OK) {
            
            // Inform the caller that everything went fine.
            ReturnValue = TRUE;
            
        
        } else {
            
            // This case should only occur if there is a
            // programming error.  Break out of the while loop to
            // kill the thread, the window, and therefore the
            // connection. 
            MessageBox(ConnData->ConnectionWindow,
                       "HandleEvents() returned an unexpected value.",
                       "Error.", MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
            ReturnValue = FALSE;
        }
    } 
    
    return(ReturnValue);

} // HandleSocketEvent()





BOOL
HandleOutputEvent(
    IN OUT PCONNDATA ConnData)
/*++

Routine Description:

    Handles the case in IOThreadFunc where the thread is woken up by a
    signal to the output event object.

Arguments:

    ConnData - Supplies a pointer to data for the connection on which
    the event happened.
    
Return Value:

    TRUE -- Chat successfully handled the event and the thread should
    continue on.

    FALSE -- An error occurred and Chat should kill the thread and the
    connection. 

--*/
{
    BOOL            ReturnValue; // holds the return value
    BOOL            KeepGoing;   // keep pulling requests off the queue?
    POUTPUT_REQUEST OutReq;      // pointer to the output request
    int             Result;      // holds results of functions

    ReturnValue = TRUE;

    // First we check to see if a previous send attempt failed with
    // WSAEWOULDBLOCK; if so, we don't have to bother trying to send
    // data until we get an FD_WRITE network event, so just return
    // with TRUE to indicate that the thread should wait again.
    if (ConnData->WriteOk) {
        
        // This loop pulls output requests off the queue and hands
        // them to DoSend.
        KeepGoing = TRUE;
        while (KeepGoing) {
            
            OutReq = (POUTPUT_REQUEST)QRemove(ConnData->OutputQueue);
            if (OutReq == NULL) {
                
                // Nothing is left on the queue.
                KeepGoing = FALSE;
                ReturnValue = TRUE;

            } else {

                // Do the output
                Result = DoSend(OutReq, ConnData);
                if (Result == CHAT_WOULD_BLOCK) {
                        
                    // The send would have blocked; we need to
                    // requeue the output request and wait for an
                    // FD_WRITE network event.
                    KeepGoing = FALSE;
                    QInsertAtHead(ConnData->OutputQueue, (LPVOID)OutReq);
                    ReturnValue = TRUE;
                    
                } else if (Result == CHAT_ERROR) {
                        
                    // An error occurred in DoSend.  Set KeepGoing to
                    // FALSE in order to get us out of the loop.
                    KeepGoing = FALSE;
                    ReturnValue = FALSE;
                    MessageBox(ConnData->ConnectionWindow,
                               "Error sending data.",
                               "Error.", 
                               MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
                    
                } else {
                    
                    // DoSend returned CHAT_OK.  Keep looping.
                    continue;
                }                        
            }
        } // while (KeepGoing)
    } // if (ConnData->WriteOk)

    return(ReturnValue);

} // HandleOutputEvent()





BOOL
HandleOtherEvent(
    IN     DWORD     WaitStatus,
    IN OUT PCONNDATA ConnData)
/*++

Routine Description:

    Handles the case in IOThreadFunc where the thread is woken up by
    an event that is either an overlapped I/O event or was not
    specified in the event array (which is an error!).

Arguments:

    WaitStatus -- Supplies the value returned by
    WSAWaitForMultipleEvents. 

    ConnData - Supplies a pointer to data for the connection on which
    the event happened.
    
Return Value:

    TRUE -- Chat successfully handled the event and the thread should
    continue on.

    FALSE -- An error occurred and Chat should kill the thread and the
    connection. 

--*/
{
    POUTPUT_REQUEST OutReq;           // points to an output request
    BOOL            ReturnValue;      // holds the return value
    DWORD           Count;            // counting variable
    char            MsgText[MSG_LEN]; // holds message strings

    // First do a sanity check to make sure the index returned is
    // within the bounds of what WE think is the event array. 
    if ((WaitStatus >= WSA_WAIT_EVENT_0) &&
        (WaitStatus <= (WSA_WAIT_EVENT_0  + ConnData->NumEvents - 1))) {

        // Free the data buffer, the overlapped structure, the output
        // request itself, and the event 
        OutReq = ConnData->OutReqArray[WaitStatus - WSA_WAIT_EVENT_0];
        free(OutReq->Buffer.buf);
        free(OutReq->Overlapped);
        free(OutReq);
        CloseHandle(ConnData->EventArray[WaitStatus - WSA_WAIT_EVENT_0]);
        
        // Update all our event and output request arrays to
        // reflect that the overlapped send has completed.
        ConnData->NumEvents--;
        for (Count = (WaitStatus - WSA_WAIT_EVENT_0); 
             Count < ConnData->NumEvents;
             Count++) {
            ConnData->EventArray[Count] = ConnData->EventArray[Count + 1];
            ConnData->OutReqArray[Count] = ConnData->OutReqArray[Count + 1];
        }
        ReturnValue = TRUE;

    } else {
        
        // WSAWaitForMultipleEvents returned an unexpected
        // value... 
        wsprintf(MsgText, "WSAWaitForMultipleEvents() returned %d.",
                 WaitStatus);
        MessageBox(GlobalFrameWindow, MsgText, "Error.", 
                   MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
        ReturnValue = FALSE;
    }

    return(ReturnValue);

} // HandleOtherEvent()





void
HandleAcceptMessage(
    IN HWND   ConnectionWindow,
    IN SOCKET Socket,
    IN LPARAM LParam)
/*++

Routine Description:

    Handles the reception of a USMSG_ACCEPT message, which indicates a
    connection attempt is incoming.

Implementation:

    This function lets the user decide whether to accept the
    connection; if he or she does, the function  initializes
    connection-specific data, calls WSAEventSelect to register
    interest in certain network events, and starts a network event
    handling thread.

Arguments:

    ConnectionWindow -- Handle to the connection window associated
    with this connection request.

    Socket -- Contains the handle to the listening socket to which the
    connection request has been made. 

    LParam -- The LParam that was delivered with the USMSG_ACCEPT
    message; contains the error code.

Return Value:

    None.

--*/
{

    int       Error;            // gets error code if necessary
    char      MsgText[MSG_LEN]; // holds message strings
    DWORD     ThreadId;         // needed for CreateThread
    PCONNDATA ConnData;         // connection-specific data
        
    ConnData = GetConnData(ConnectionWindow);

    Error = WSAGETSELECTERROR(LParam);

    // Check to see if there was an error on the connection attempt.
    if (Error) {
        
        // Some kind of error occurred.
        if (Error == WSAENETDOWN) {
            MessageBox(ConnectionWindow,
                       "The network is down!", "Uh-oh", 
                       MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
        } else {
            MessageBox(ConnectionWindow,
                       "Unknown error on FD_ACCEPT", "Error",
                       MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
        }
        goto Fail;
    }
    
    // Get a pointer to the associated protocol information structure
    // for the socket we are listening on (Socket). This will be the
    // same as the protocol info for the new socket.  Note that we
    // could have allocated a new buffer and called getsockopt with
    // the SO_PROTOCOL_INFO option.  But since we've already allocated
    // the memory, this way is more efficient.
    ConnData->ProtocolInfo = GetProtoFromSocket(Socket);
    assert(ConnData->ProtocolInfo != NULL);
        
    // Accept the connection (this calls AcceptCondFunc, of course,
    // before actually accepting the connection).
    {
        WSABUF SockAddrBuf;
        
        SockAddrBuf.len = ConnData->ProtocolInfo->iMaxSockAddr;
        SockAddrBuf.buf = (char *)malloc(SockAddrBuf.len);
        ConnData->Socket = WSAAccept(Socket, 
                                     (struct sockaddr *)SockAddrBuf.buf, 
                                     &SockAddrBuf.len, 
                                     NULL,
                                     (DWORD)NULL);
//         ConnData->Socket = WSAAccept(Socket, 
//                                      NULL, 
//                                      NULL, 
//                                      AcceptCondFunc,
//                                      (DWORD)ConnData);
    }
    
    if (ConnData->Socket == INVALID_SOCKET) {
        
        // WSAAccept failed -- inform the user and that's all.
        Error = WSAGetLastError();
        if (Error != WSAECONNREFUSED) {

            // An unexpected error code.
            wsprintf(MsgText, "WSAAccept failed.  Error code: %d",
                     Error);
            MessageBox(ConnectionWindow, MsgText, "Error",
                       MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);  
            goto Fail;
        
        } else {
            
            // AcceptCondFunc returned CF_REJECT...
            MessageBox(GlobalFrameWindow, 
                       "The connection attempt has been refused.",
                       "Connection refused.", MB_OK | MB_SETFOREGROUND);
            goto Fail;
        }
    } 
    
    // Put Connection in Event Object Notification Mode.
    WSAEventSelect(ConnData->Socket,
                   ConnData->SocketEventObject,
                   FD_READ | FD_WRITE | FD_CLOSE);

    // Determine the maximum message size, if any.
    if (!GetMaxMsgSize(ConnData)) {
        goto Fail;
    }
    
    // Start the I/O thread, and save the thread handle.
    ConnData->IOThreadHandle = 
      CreateThread(NULL, 
                   0, 
                   (LPTHREAD_START_ROUTINE)IOThreadFunc, 
                   ConnData, 
                   0,
                   &ThreadId);
    if (ConnData->IOThreadHandle == NULL) {
        ChatSysError("CreateThread()",
                     "HandleAcceptMessage()",
                     TRUE);
    }

    return;

 Fail:

    DestroyWindow(ConnectionWindow);
    return;

} // HandleAcceptMessage()





void
HandleConnectMessage(
    IN HWND   ConnectionWindow,
    IN LPARAM LParam)
/*++

Routine Description:

    Handles the reception of a USMSG_CONNECT message, which indicates
    a connection attempt is complete (though not necessarily
    successful). 

Implementation:

    As with HandleAcceptMessages, this function initializes
    connection-specific data, calls WSAEventSelect to register
    interest in certain network events, and starts a network event
    handling thread. 

Arguments:

    ConnectionWindow -- Handle to the connection window associated
    with this connection request.

    LParam -- Contains the LParam that was a parameter of the
    USMSG_CONNECT message.

Return Value:

    None.

--*/
{

    PCONNDATA ConnData;               // connection-specific data
    char      TitleText[TITLE_LEN];   // text buffer for window title
    DWORD     ThreadId;               // needed for CreateThread()
    int       Error;                  // holds error codes
    
    Error = WSAGETSELECTERROR(LParam);

    // Check to see if there was an error on the connection attempt.
    if (Error) {

        // Some kind of error occurred.
        if (Error == WSAECONNREFUSED) {

            MessageBox(ConnectionWindow,
                       "Your connection attempt has been refused",
                       "Connection Refused",
                       MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
        } else {

            MessageBox(ConnectionWindow,
                       "Couldn't connect",
                       "Error",
                       MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
        }

        goto Fail;

    } 
    
    // Connection has been accepted.  Change the title of the
    // connection window to reflect who the user has connected to
    ConnData = GetConnData(ConnectionWindow);
    
    if (ConnData->CalleeBuffer.len != 0) {
        
        // The callee buffer contains the connection-time data
        // sent by the callee. -- a string containing the name of
        // the callee. 
        wsprintf(TitleText, "Connected To: %s @ %s",
                 ConnData->CalleeBuffer.buf, ConnData->PeerAddress);
    } else {
        
        // ConnData->PeerAddress just contains the address that
        // the user typed in before the connection attempt.
        wsprintf(TitleText, "Connected To: %s", ConnData->PeerAddress);
    }
    SetWindowText(ConnectionWindow, TitleText);
    
    // Put Connection in Event Object Notification Mode.
    WSAEventSelect(ConnData->Socket,
                   ConnData->SocketEventObject,
                   FD_READ | FD_WRITE | FD_CLOSE);

    // Determine the maximum message size, if any.
    if (!GetMaxMsgSize(ConnData)) {
        goto Fail;
    }
        
    // Start the I/O thread, and save the thread handle.
    ConnData->IOThreadHandle = 
      CreateThread(NULL, 
                   0, 
                   (LPTHREAD_START_ROUTINE)IOThreadFunc, 
                   ConnData,
                   0, 
                   &ThreadId);
    if (ConnData->IOThreadHandle == NULL) {
        ChatSysError("CreateThread()",
                     "HandleConnectMessage()",
                     TRUE);
    }

    return;
    
 Fail:
    
    DestroyWindow(ConnectionWindow);
    return;
    
} // HandleConnectMessage()





BOOL
GetMaxMsgSize(
    IN OUT PCONNDATA ConnData)
/*++

Routine Description:

    Determines the maximum message size (if any) of a connected
    socket.  The connection must already be established, i.e. the
    socket must be bound to a local address, for this function to
    work.  Fills the correct value into a field of the
    connection-specific data.

Arguments:

    ConnData -- Connection data for a connected socket. 

Return Value:

    TRUE -- The maximum message size was succesfully determined and
    stored in ConnData->MaxMsgSize.

    FALSE -- There was an error calling getsockopt to get the maximum
    message size.

--*/
{
    BOOL ReturnValue = TRUE;       // return value
    int  DwordLen = sizeof(DWORD); // sizeof a DWORD!
    int  Error;                    // return value of getsockopt

    if ((ConnData->ProtocolInfo->dwMessageSize == 0) ||
        (ConnData->ProtocolInfo->dwMessageSize == 0xffffffff)) {
        
        // Either the protocol isn't message-oriented, or there is no
        // maximum message size.
        ConnData->MaxMsgSize = NO_MAX_MSG_SIZE;
        
    } else {
        
        // There is a maximum message size.  Note it.
        if (ConnData->MaxMsgSize == 0x1) {

            // The actual maximum message size was not stored in the
            // protocol information structure -- rather, we need to
            // get it using getsockopt().
            Error = getsockopt(ConnData->Socket,
                               SOL_SOCKET,
                               SO_MAX_MSG_SIZE,
                               (char *)&ConnData->MaxMsgSize,
                               &DwordLen);
            if (Error) {
                ReturnValue = FALSE;
            }

        } // if (ConnData->MaxMsgSize == 0x1)
        else {
            
            // The message size is stored in the protocol information
            // structure.  Use it.
            ConnData->MaxMsgSize = ConnData->ProtocolInfo->dwMessageSize;
        }
            

    } // else
    
    return(ReturnValue);
    
} // GetMaxMsgSize()





BOOL
IsSendable(
    char Char)
/*++

Routine Description:

    Determines whether a certain character value is ok to send over
    the socket to the far end.  Eliminates most non-printable
    characters except for newline and backspace.

Arguments:

    Char -- Supplies the character to be tested.

Return Value:

    TRUE -- The character should be sent as is.

    FALSE -- The character should not be sent.

--*/
{

    if (isprint(Char) || (Char == '\b') || (Char == '\r')) {
        return(TRUE);
    } else {
        return(FALSE);
    }

} // IsSendable()





int
DoRecv(
    IN PCONNDATA ConnData)

/*++

Routine Description:

    Receives as much available data as possible up to the size of the
    receive buffer-1 (BUFFER_LENGTH-1).  A single byte is reserved at
    the end of the receive buffer to append a terminating NULL.

Arguments:

    ConnData -- Points to the data for the connection that is ready to
    receive data.

Return Value:

    CHAT_OK -- Received data was successfully sent to the receive edit
           control.

    CHAT_ERROR -- Error receiving data.

    CHAT_CLOSED -- The socket was gracefully closed.

--*/

{
    DWORD  NumBytes;               // stores how many bytes we received
    int    Error;                  // gets error values
    int    Result;                 // gets return value from WSARecv
    char   Buf[BUFFER_LENGTH];     // buffer to receive data
    int    ReturnValue = CHAT_OK;  // returnValue
    WSABUF RecvBuffer;             // WSABuf to pass to WSARecv
    DWORD  Flags = 0;              // flags for WSARecv
    
    RecvBuffer.buf = Buf;
    RecvBuffer.len = BUFFER_LENGTH - 1;

    // Do the receive
    Result = WSARecv(ConnData->Socket,
                     &RecvBuffer,
                     1,
                     &NumBytes,
                     &Flags,
                     NULL,
                     NULL);

    // Check for errors.
    if (Result == SOCKET_ERROR) {

        Error = WSAGetLastError();
        
        switch (Error) {

        case WSAENETRESET:  // flow through
        case WSAECONNRESET:

            // The remote party has reset the connection.
            ReturnValue = CHAT_CLOSED;
            goto Done;

        case WSAEWOULDBLOCK:
            
            // No data received; return to wait for another read event.
            ReturnValue = CHAT_OK;

        default:

            // Some other error...hit the panic button.
            ReturnValue = CHAT_ERROR;
            goto Done;
        }

    }

    // Append a NULL to the text buffer.
    RecvBuffer.buf[NumBytes] = '\0';

    // Output the received text into the receive edit control.
    OutputString(ConnData->RecvWindow, RecvBuffer.buf);
 
 Done:
    return(ReturnValue);

} // DoRecv()





int
DoOverlappedCallbackSend(
    IN POUTPUT_REQUEST OutReq,
    IN PCONNDATA       ConnData)

/*++

Routine Description:

    Sends a buffer of data over a connected socket using overlapped
    I/O with callback function completion notification.

Implementation:

    This function just does an overlapped send, giving WinSock 2 a
    completion function which will be called when the operation has
    finished, and within which cleanup will occur.

Arguments:

    OutReq -- Points to a OUTPUT_REQUEST structure, which specifies an
    output request.

    ConnData -- Supplies a pointer to a CONNDATA structure, which
    identifies a Chat connection. 

Return Value:

    CHAT_WOULD_BLOCK -- The send operation failed with WSAEWOULDBLOCK.

    CHAT_ERROR -- A unexpected error occurred.
    
    CHAT_OK -- The send was successfully initiated. WinSock 2 will
    execute the callback function when the send has completed.

--*/

{
    int             Size = 0;      // how many bytes we send
    int             Error;         // return value of WSASend
    int             Errno;         // result of WSAGetLastError
    LPWSABUF        Buffers;       // points to an array of WSABUFs
    DWORD           BytesSent;     // needed in WSASend
    int             ReturnValue;   // the return value

    ReturnValue = CHAT_OK;
        
    // Allocate an OVERLAPPED structure.
    OutReq->Overlapped = (LPWSAOVERLAPPED)malloc(sizeof(WSAOVERLAPPED));
    if (OutReq->Overlapped == NULL) {
        ChatSysError("malloc()",
                     "DoOverlappedCallbackSend()",
                     TRUE);
    }
    Buffers = &OutReq->Buffer;
    
    // The hEvent field of the Overlapped structure is not used
    // for callback notification...thus we can use it any way we
    // want to pass information to the callback routine.  We use
    // it to store a pointer to the OutReq associated with this
    // overlapped send operation, so the callback function can
    // free the memory.
    OutReq->Overlapped->hEvent = (WSAEVENT)OutReq;
    
    Error = WSASend(ConnData->Socket, 
                    Buffers, 
                    1, 
                    &BytesSent, 
                    0, 
                    OutReq->Overlapped,
                    SendCompFunc);
    
    if (Error == SOCKET_ERROR) {
        
        // There is an error...
        Errno = WSAGetLastError();
        if (Errno == WSAEWOULDBLOCK) {
            
            // WSAEWOULDBLOCK means we have to wait for an FD_WRITE
            // before we can send. 
            ConnData->WriteOk = FALSE;
            ReturnValue = CHAT_WOULD_BLOCK;
            
        } else if (Errno == WSA_IO_PENDING) {
            
            // Overlapped send successfully initiated.
            ReturnValue = CHAT_OK;
        } 
        else {
            
            // An unexpected error occurred. 
            ReturnValue = CHAT_ERROR;
        }
        
    }

    // No error -- the I/O request was completed immediately...
    return(ReturnValue);

} // DoOverlappedCallbackSend()





int
DoOverlappedEventSend(
    IN POUTPUT_REQUEST OutReq,
    IN PCONNDATA       ConnData)

/*++

Routine Description:

    Sends a buffer of data over a connected socket using overlapped
    I/O with event notification.

Implementation:

    If the overlapped send is succesfully initiated, this function
    increases the event count and adds an entry to the event array of
    the associated connection.  During IOThreadFunc, the thread will
    thus wait on the new event as well, and will perform the
    appropriate cleanup action when awoken.

Arguments:

    OutReq -- Points to a OUTPUT_REQUEST structure, which specifies an
    output request.

    ConnData -- Supplies a pointer to a CONNDATA structure, which
    identifies a Chat connection. 

Return Value:

    CHAT_WOULD_BLOCK -- The send operation failed with WSAEWOULDBLOCK.

    CHAT_ERROR -- A unexpected error occurred.
    
    CHAT_OK -- The send was successfully initiated and the new event
    placed in the event array; it will be signaled by WinSock 2 when
    the send operation has completed.

--*/

{
    int             Size = 0;      // how many bytes we send
    int             Error;         // return value of WSASend
    int             Errno;         // result of WSAGetLastError
    LPWSABUF        Buffers;       // points to an array of WSABUFs
    DWORD           BytesSent;     // needed in WSASend
    int             ReturnValue;   // the return value

    ReturnValue = CHAT_OK;
        
    // Allocate an OVERLAPPED structure.
    
    // Note that the pointer to an overlapped structure is kept in
    // OutReq.  We use an array of OutReq pointers that is parallel to
    // the event array.  With that one pointer, then, we can find the
    // associated Overlapped structure and free it, and then free the
    // OutReq structure itself. 
    OutReq->Overlapped = (LPWSAOVERLAPPED)malloc(sizeof(WSAOVERLAPPED));
    if (OutReq->Overlapped == NULL) {
        ChatSysError("malloc()",
                     "DoOverlappedEventSend()",
                     TRUE);
    }
    Buffers = &OutReq->Buffer;
    
    OutReq->Overlapped->hEvent = 
      (WSAEVENT)CreateEvent(NULL,  
                            FALSE, 
                            FALSE, 
                            NULL); 
    if (OutReq->Overlapped->hEvent == NULL) {
        ChatSysError("CreateEvent()",
                     "DoOverlappedEventSend()",
                     TRUE);
    }

    // Do the send.
    Error = WSASend(ConnData->Socket, 
                    Buffers, 
                    1, 
                    &BytesSent, 
                    0, 
                    OutReq->Overlapped,
                    NULL);
        
    if (Error == SOCKET_ERROR) {

        // There is an error...
        Errno = WSAGetLastError();
        if (Errno == WSAEWOULDBLOCK) {
            
            // WSAEWOULDBLOCK means we have to wait for an FD_WRITE
            // before we can send. 
            ConnData->WriteOk = FALSE;
            ReturnValue = CHAT_WOULD_BLOCK;
            
        } else if (Errno == WSA_IO_PENDING) {
            
            // Overlapped send successfully initiated.
            // Increase the event count, and update the event and
            // output request arrays to hold the entries for this
            // overlapped send.
            ConnData->NumEvents++;
            if (ConnData->NumEvents > WSA_MAXIMUM_WAIT_EVENTS) {
                
                // We're trying to wait on too many events at
                // once.  It's very unlikely this could happen --
                // you'd have to get about 62 overlapped sends, in
                // row, before one of them is completed and
                // signaled.  So just return CHAT_ERROR which will
                // break the connection.
                ReturnValue = CHAT_ERROR;
                
            } else {
                    
                ConnData->EventArray[ConnData->NumEvents - 1] = 
                  OutReq->Overlapped->hEvent;
                
                ConnData->OutReqArray[ConnData->NumEvents - 1] = OutReq;
                ReturnValue = CHAT_OK;
            }
        } // else if (...)
        else {
            
            // An unexpected error occurred. 
            ReturnValue = CHAT_ERROR;
        }
        
    } // if (Error == SOCKET_ERROR)

    // No error -- the I/O request was completed immediately...
    ReturnValue = CHAT_OK;
    
    return(ReturnValue);
    
} // DoOverlappedEventSend()





int
DoSend(
    IN POUTPUT_REQUEST OutReq,
    IN PCONNDATA       ConnData)

/*++

Routine Description:

    Sends a buffer of data over a connected socket.

Implementation:

    This function uses the information contained in the output request
    structure to determine whether to use overlapped or non-overlapped
    I/O.  If using overlapped I/O, it calls an appropriate function
    depending on whether the symbol CALLBACK_NOTIFICATION has been
    defined. Otherwise, the non-overlapped send in performed
    immediately.  

Arguments:

    OutReq -- Points to a OUTPUT_REQUEST structure, which specifies an
    output request.

    ConnData -- Supplies a pointer to a CONNDATA structure, which
    identifies a Chat connection. 

Return Value:

    CHAT_WOULD_BLOCK -- The send operation failed with WSAEWOULDBLOCK.

    CHAT_ERROR -- A unexpected error occurred.
    
    CHAT_OK -- The send was successfully initiated.  If the send was
    overlapped, either a callback function is forthcoming or another
    event was placed in the ConnData->EventArray for IOThreadFunc to
    wait on until it's signaled by WinSock 2.

--*/

{
    int             Size = 0;      // how many bytes we send
    int             Error;         // return value of WSASend
    int             Errno;         // result of WSAGetLastError
    WSABUF          Buffers[1];    // points to the data to be sent
    DWORD           BytesSent;     // needed in WSASend
    int             ReturnValue;   // the return value
        
    if (OutReq->Type == NON_OVERLAPPED_IO) {

        // Do a non-overlapped send by setting lpOverlapped and
        // lpCompletionRoutine to NULL.
        Buffers[0].len = OutReq->Buffer.len;
        Buffers[0].buf = OutReq->Buffer.buf;
        Error = WSASend(ConnData->Socket, 
                        Buffers, 
                        1, 
                        &BytesSent, 
                        0, 
                        NULL,
                        NULL);
                
        if (Error == SOCKET_ERROR) {

            // There's an error...
            Errno = WSAGetLastError();
            if (Errno == WSAEWOULDBLOCK) {

                //  WSAEWOULDBLOCK means we have to wait for an FD_WRITE
                //  before we can send.
                ConnData->WriteOk = FALSE;
                ReturnValue = CHAT_WOULD_BLOCK;

            } else {
                
                // It's an unexpected error.
                ReturnValue = CHAT_ERROR;
            }
        } else {

            // There's no error.
            ReturnValue = CHAT_OK;
            free(OutReq);
        }
        
    } else if (OutReq->Type == OVERLAPPED_IO) {
        
#ifdef CALLBACK_NOTIFICATION
        
        ReturnValue = DoOverlappedCallbackSend(OutReq, ConnData);
        
#else
        
        ReturnValue = DoOverlappedEventSend(OutReq, ConnData);

#endif
        
    } else {

        // Unknown output type...
        MessageBox(ConnData->ConnectionWindow,
                   "Unknown output type given to IOThread. Aborting.",
                   "Error.", MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
        ReturnValue = CHAT_ERROR;
    } 

    return(ReturnValue);

} // DoSend()





void CALLBACK
SendCompFunc(
    IN DWORD           Error,
    IN DWORD           BytesTransferred,
    IN LPWSAOVERLAPPED Overlapped,
    IN DWORD           Flags)
/*++

Routine Description:

    Completion routine called after a successfully initiated overlapped
    send operation completes; cleans up data structures associated
    with the send.

Arguments:

    Error -- Supplies the completion status for the overlapped
    operation. 

    BytesTransferred -- Supplies the actual number of bytes sent.

    Overlapped -- Supplies a pointer to a WSAOVERLAPPED structure.
    The hEvent field in the structure contains a pointer to the
    OUTPUT_REQUEST structure associated with this send operation.

    Flags -- Not yet used.
    
Return Value:

    None

--*/

{
    POUTPUT_REQUEST OutReq; // The output request
    
    OutReq = (POUTPUT_REQUEST)(Overlapped->hEvent);
        
    if (Error) {
        MessageBox(NULL,
                   "Error during overlapped send.",
                   "Error",
                   MB_OK | MB_SETFOREGROUND);
    }

    // Free the data buffer, the output request structure,  and the
    // overlapped I/O structure. 
    free(OutReq->Buffer.buf);
    free(OutReq);
    free(Overlapped);
    
} // SendCompFunc()





PCONNDATA
GetConnData(
    IN HWND ConnectionWindow
    )

/*++

Routine Description:

    Retrieves a pointer to the PCONNDATA data structure associated
    with a connection window

Arguments:

    ConnectionWindow - Supplies the handle to a connection window.

Return Value:

    Returns the PCONNDATA associated with the given connection window 
    handle.

--*/

{
    return((PCONNDATA)GetWindowLong(ConnectionWindow, GWL_CONNINFO));
    
} // GetConnData()





BOOL
MakeConnection(
    IN HWND ConnectionWindow)

/*++

Routine Description:

    Initiates a call to another instance of chat.
    1) Creates a socket
    2) Set the socket up for windows message notification of FD_CONNECT
       events.
    3) Allocate a buffer for caller name & subject, and fill it in.
    4) Allocate a buffer to receive callee name.
    5) Set up quality of service for the connection.
    6) Attempt a connection

Arguments:

    ConnectionWindow - Handle to the window that receives
    notification when an FD_CONNECT event occurs. 

Return Value:

    TRUE - A connection attempt was successfully initiated.

    FALSE - Error occurred while attempting to initiate a connection.

--*/

{
    int             ConnectStatus;    // the return value of WSAConnect
    int             Error;            // gets error values
    BOOL            ReturnValue;      // holds the return value
    PCONNDATA       ConnData;         // connection-specific data
    LPWSABUF        CallerBuffer;     // user data we will send
    LPWSABUF        CalleeBuffer;     // user data we will receive
    QOS             QualityOfService; // QOS structure, used by WSAConnect
    LPFLOWSPEC      FlowSpec;         // dummy pointer for code readability
    char            MsgText[MSG_LEN]; // holds message strings
    BOOL            QOSSupported;     // does the protocol support QOS?
    BOOL            CTDTSupported;    // support for conn-time data xfer?
    struct sockaddr *SockAddr;        // socket address for WSAConnect
    int             SockAddrLen;      // the length of the above
    
    ReturnValue = TRUE;
    ConnData = GetConnData(ConnectionWindow);
    QOSSupported = (ConnData->ProtocolInfo->dwServiceFlags1 & 
                    XP1_QOS_SUPPORTED);
    CTDTSupported = (ConnData->ProtocolInfo->dwServiceFlags1 & 
                      XP1_CONNECT_DATA);
    if (CTDTSupported) {
        
        // CallerBuffer and CallerBuffer->buf (allocated within the
        // NameAndSubject dialog box procedure) both get freed by the
        // end of this function.  CalleeBuffer.buf gets freed upon
        // connection shutdown -- it needs to be valid after this
        // function has exited.
        CallerBuffer = (LPWSABUF)malloc(sizeof(WSABUF));
        if (CallerBuffer == NULL) {
            ChatSysError("malloc()",
                         "MakeConnection()",
                         TRUE);
        }
        CalleeBuffer = &ConnData->CalleeBuffer;

        // The connection-time data sent back when/if this connection
        // is accepted will just contain the callee's name.
        CalleeBuffer->len = NAME_LEN + 1;
        CalleeBuffer->buf = (char *)malloc(CalleeBuffer->len);
        if (CallerBuffer->buf == NULL) {
            ChatSysError("malloc()",
                         "MakeConnection()",
                         TRUE);
        }
        
        // Prompt the user for his name and the subject of the call.
        // a pointer to the CallerBuffer is passed to the dialog box
        // procedure and the data is packed into this memory.
        if (!DialogBoxParam(GlobalInstance, 
                            "NameAndSubjectDlg", 
                            ConnectionWindow, 
                            NameAndSubjectDlgProc,
                            (LPARAM)CallerBuffer)) {
            ReturnValue = FALSE;
            goto Done;
        }
    } // if (CTDTSupported)

    // Create a socket for this connection.
    ConnData->Socket = WSASocket(0, 
                                 0, 
                                 0, 
                                 ConnData->ProtocolInfo, 
                                 0,
                                 WSA_FLAG_OVERLAPPED);
    
    if (ConnData->Socket == INVALID_SOCKET) {

        MessageBox(ConnectionWindow, "Could not open a socket.", "Error", 
                   MB_OK | MB_SETFOREGROUND);
        ReturnValue = FALSE;
        goto Done;

    } else {

        // Set up socket for windows message event notification.
        WSAAsyncSelect(ConnData->Socket, 
                       ConnectionWindow, 
                       USMSG_CONNECT, 
                       FD_CONNECT);
    }
    
    if (QOSSupported) {

        // Set  up  quality  of  service  info  we  want.   Do  not include any
        // provider-specific information.
        QualityOfService.ProviderSpecific.len = 0;
        QualityOfService.ProviderSpecific.buf = NULL;
        
        // FlowSpec is used only to make this code more readable.
        FlowSpec = & QualityOfService.SendingFlowspec;
        FlowSpec->TokenRate = TOKENRATE;
        FlowSpec->TokenBucketSize = QOS_UNSPECIFIED;
        FlowSpec->PeakBandwidth = QOS_UNSPECIFIED;
        FlowSpec->Latency = QOS_UNSPECIFIED;
        FlowSpec->DelayVariation = QOS_UNSPECIFIED;
        FlowSpec->LevelOfGuarantee = BestEffortService;
        FlowSpec->CostOfCall = 0;
        FlowSpec->NetworkAvailability = 1;
            
        // again, the extra FlowSpec variable is for readability
        FlowSpec = & QualityOfService.ReceivingFlowspec;
        FlowSpec->TokenRate = TOKENRATE;
        FlowSpec->TokenBucketSize = QOS_UNSPECIFIED;
        FlowSpec->PeakBandwidth = QOS_UNSPECIFIED;
        FlowSpec->Latency = QOS_UNSPECIFIED;
        FlowSpec->DelayVariation = QOS_UNSPECIFIED;
        FlowSpec->LevelOfGuarantee = BestEffortService;
        FlowSpec->CostOfCall = 0;
        FlowSpec->NetworkAvailability = 1;
    }

    // Reminder: WinSock 2 requires that we pass a struct sockaddr *
    // to WSAConnect; however, the service provider is free to
    // interpret the pointer as an arbitrary chunk of data of size
    // SockAddrLen. 
    SockAddr = (struct sockaddr *)ConnData->RemoteSockAddr.buf;
    SockAddrLen = ConnData->RemoteSockAddr.len;

    // Try to connect; depending on whether QOS or Connection Time
    // Data Transfer are supported, pass the appropriate data.
    if (QOSSupported && CTDTSupported) {
        
        // Both are supported...
        ConnectStatus = WSAConnect(ConnData->Socket,
                                   SockAddr,
                                   SockAddrLen,
                                   CallerBuffer,
                                   CalleeBuffer,
                                   &QualityOfService,
                                   NULL);
    } else if (!QOSSupported && CTDTSupported) {
        
        // Only CTDT is supported...
        ConnectStatus = WSAConnect(ConnData->Socket,
                                   SockAddr,
                                   SockAddrLen,
                                   CallerBuffer,
                                   CalleeBuffer,
                                   NULL,
                                   NULL);
        
    } else if (QOSSupported && !CTDTSupported) {
        
        // Only QOS is supported...
        ConnectStatus = WSAConnect(ConnData->Socket,
                                   SockAddr,
                                   SockAddrLen,
                                   NULL,
                                   NULL,
                                   &QualityOfService,
                                   NULL);
        
    } else {
        
        // Neither is supported...
        ConnectStatus = WSAConnect(ConnData->Socket,
                                   SockAddr,
                                   SockAddrLen,
                                   NULL,
                                   NULL,
                                   NULL,
                                   NULL);
    }

    // Check for errors.
    if (ConnectStatus == SOCKET_ERROR) {

        Error = WSAGetLastError();
        if (Error != WSAEWOULDBLOCK) {
            wsprintf(MsgText, "WSAConnect failed.  Error code: %d",
                     Error);
            MessageBox(ConnectionWindow, MsgText, "Error", 
                       MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
            ReturnValue = FALSE;
        }      
      
    } else {

        MessageBox(ConnectionWindow, 
                   "WSAConnect should have returned SOCKET_ERROR.",
                   "Error", MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
        ReturnValue = FALSE;
    }

 Done:
    
    // Free allocated memory, except CalleeBuffer which will be
    // freed when connection window is destroyed.
    if (CTDTSupported) {
        free(CallerBuffer->buf);
        free(CallerBuffer);
    }
    return(ReturnValue);

} // MakeConnection()





BOOL
FillInFamilies(
    IN HWND  DialogWindow,
    IN DWORD FamilyLB)
/*++

Routine Description:

    Fills in the listbox with a string for each protocol on which Chat
    has a listening socket.  

Implementation:

    For each socket in the ListeningSockets array, this function:

        1. Gets the associated protocol information structure. 
        
        2. If Chat recognizes the address family, it prints it in a
        string; if not, it prints "unknown".

        3. In the second half of the string, prints out the
        human-readable string contained in the protocol information.

        4. Sends the string to be an entry in the listbox identified
        by FamilyLB.

Arguments:

    DialogWindow -- Window handle for the dialog box.

    FamilyLB -- Integer identifier for a listbox in the dialog box. 

Return Value:

    TRUE - All messages were successfully sent to the listbox.

    FALSE - An LB_ADDSTRING message failed.

--*/

{

    int     i;                 // counting variable
    LRESULT Result;            // result of SendMessage calls
    char    LBString[MSG_LEN]; // string to send to the listbox
    int     Offset;             // index into the string

    // Iterate through all members of the ListeningSockets array.
    for (i = 0; i < NumFound; i++){

		// We are prepending a number to the list box string to be displayed
		// since the list box does us the favor of alphabetizing the 
		// the sting and we are depending on the list entries appearing in the
        // list box in the order that we added them to the list.

		Offset = wsprintf(LBString, "%03i",i);
         
        switch (ListeningSockets[i].ProtocolInfo->iAddressFamily) {
           
        case AF_INET:
            
            Offset += wsprintf(LBString+ Offset, "AF_INET/");
            wsprintf(LBString + Offset, "%s", 
                     ListeningSockets[i].ProtocolInfo->szProtocol);
            Result = SendMessage(GetDlgItem(DialogWindow, FamilyLB),
                                 LB_ADDSTRING, 0, (LPARAM)LBString);
            break;

        default:
            
            Offset += wsprintf(LBString+ Offset, "(unknown)/");
            wsprintf(LBString + Offset, "%s",
                     ListeningSockets[i].ProtocolInfo->szProtocol);
            Result = SendMessage(GetDlgItem(DialogWindow, FamilyLB),
                                 LB_ADDSTRING, 0, (LPARAM)LBString);
            break;

        }
        if ((Result == LB_ERR) || (Result == LB_ERRSPACE)) {
            return(FALSE);
        }
    }
    return(TRUE);

} // FillInFamilies()





LPWSAPROTOCOL_INFO
GetProtoFromIndex(
    IN int LBIndex)
/*++

Routine Description:

    Takes an index into the ChooseFamily dialog box and returns the
    protocol associated with that index. This works because when the
    dialog box is set up, the strings representing the protocols are
    added to the listbox in the order they fall in ListeningSockets. 

Arguments:

    LBIndex -- The index of the user's selection.

Return Value:

    NULL -- The index doesn't correspond to an element in the
    ListeningSockets array.

    LPWSAPROTOCOL_INFO -- A pointer to the protocol information struct
    corresponding to LBIndex.

--*/
{
    if (LBIndex > (NumFound - 1)) {
        return(NULL);
    }    
    return(ListeningSockets[LBIndex].ProtocolInfo);

} // GetProtoFromIndex()





LPWSAPROTOCOL_INFO
GetProtoFromSocket(
    IN SOCKET Socket)
/*++

Routine Description:

    Searches the ListeningSockets array for the first entry with a
    Socket field equal to the Socket parameter.  When found, it
    returns a pointer to the associated protocol information
    structure.  If not found, return NULL.

Arguments:

    Socket -- The socket who's associated WSAPROTOCOL_INFO struct we are
    looking for. 

Return Value:

    NULL -- The socket was not found in the ListeningSockets array.

    LPWSAPROTOCOL_INFO -- A pointer to the protocol information struct
    for the socket.

--*/
{

    int i; // counting variable

    for (i = 0; i < NumFound; i++) {
        if (ListeningSockets[i].Socket == Socket) {
            return(ListeningSockets[i].ProtocolInfo);
        }
    }
    return(NULL);

} // GetProtoFromSocket()





void
CleanupConnection(
    IN PCONNDATA ConnData)
/*++

Routine Description:

    Frees all memory and objects allocated for this connection.

Arguments:

    ConnData -- Pointer to the connection-specific data structure.

Return Value:

    None.

--*/
{

    // Clean up connection-specific data.  To keep this code
    // readable, we ignore any errors.
    if (ConnData->SocketEventObject != NULL) {
        CloseHandle(ConnData->SocketEventObject);
        ConnData->SocketEventObject = NULL;
    }
    if (ConnData->OutputEventObject != NULL) {
        CloseHandle(ConnData->SocketEventObject);
        ConnData->SocketEventObject = NULL;
    }
    if (ConnData->OutputQueue != NULL) {
        QFree(ConnData->OutputQueue);
        ConnData->OutputQueue = NULL;
    }
    if (ConnData->IOThreadHandle != NULL) {
        CloseHandle(ConnData->IOThreadHandle);
        ConnData->IOThreadHandle = NULL;
    }
    if (ConnData->Socket != INVALID_SOCKET) {
        closesocket(ConnData->Socket);
    }
    if (ConnData->RemoteSockAddr.buf != NULL) {
        free(ConnData->RemoteSockAddr.buf);
        ConnData->RemoteSockAddr.buf = NULL;
    }
    if (ConnData->CalleeBuffer.buf != NULL) {
        free(ConnData->CalleeBuffer.buf);
        ConnData->CalleeBuffer.buf = NULL;
    }
    free(ConnData);

} // CleanupConnection()
