//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// This source code is licensed under Microsoft Shared Source License
// Version 1.0 for Windows CE.
// For a copy of the license visit http://go.microsoft.com/fwlink/?LinkId=3223.
//
//
// wsahwnd.c
//
// async winsock implementation of
//  WSAAsyncSelect
//  WSAAsyncGetHostByName
//  WSAAsyncGetAddrInfo
//  WSACancelAsyncRequest
//

#include <winsock2p.h>
#include <ws2tcpip.h>

int WSACancelAsyncRequest (HANDLE hAsyncTaskHandle);
HANDLE WSAAsyncGetHostByName (HWND hWnd, unsigned int wMsg, const char FAR * name, char FAR * buf, int buflen );
int WSAAsyncSelect (SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent );
HANDLE WSAAsyncGetAddrInfo(HWND hWnd, unsigned int uMsg, const char * name, unsigned short port, ADDRINFO **ppai);

	
int cpyhent(char * lpDstBuffer, const PHOSTENT lpSrcHostent, int * lpBufSize );

typedef struct _WSAASYNC_SOCKET {
    struct _WSAASYNC_SOCKET * ws_next;
    SOCKET      ws_socket;
    HWND        ws_hwnd;
    u_int       ws_wmsg;
    long        ws_levents;
    WSAEVENT    ws_hevent;
} WSAASYNC_SOCKET, * PWSAASYNC_SOCKET;

typedef struct WSAREQ {
    struct WSAREQ  *pNext;
    HWND            hWnd;
    uint            wMsg;
    char           *pBuf;
    DWORD           dwBufLen;
    char           *pName;
    DWORD           Status;
    unsigned short  port;
    ADDRINFO      **ppAI;
} WSAREQ;

BOOL g_bWSAAsyncCSInit;
CRITICAL_SECTION g_WSAAsyncCS;
PWSAASYNC_SOCKET g_WSAAsyncSelectList;
int  g_cWSAAsyncSelectSockets;
BOOL g_bWSAAsyncSelectThreadRunning;
WSAEVENT g_WSAAsyncSelectSignal;
WSAREQ * g_WSAReqList;

HANDLE g_hCoreDll;
typedef BOOL (*pfnPostMessageW)(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
pfnPostMessageW g_pfnPostMessageW;
BOOL IsAPIReady(DWORD PSL);

//
// Initialize async winsocket globals
//
BOOL WS2AsyncInit(void)
{
    BOOL bRet;

    if (!IsAPIReady(SH_WMGR)) {
        // Window manager not available
        SetLastError(WSASYSNOTREADY);
        return FALSE;
    }

    // Init async select globals
    if (FALSE == g_bWSAAsyncCSInit) {
        g_bWSAAsyncCSInit = TRUE;
        InitializeCriticalSection(&g_WSAAsyncCS);
        EnterCriticalSection(&g_WSAAsyncCS);
        g_WSAAsyncSelectSignal = CreateEvent(NULL, FALSE, FALSE, NULL);
        g_bWSAAsyncSelectThreadRunning = FALSE;
        g_cWSAAsyncSelectSockets = 0;
    } else {
        EnterCriticalSection(&g_WSAAsyncCS);
    }

    bRet = TRUE;
    // Init PostMessage function pointer
    if (NULL == g_hCoreDll) {
        g_hCoreDll = (HINSTANCE )GetModuleHandle(L"coredll");
        if (g_hCoreDll) {
            g_pfnPostMessageW = (pfnPostMessageW)GetProcAddress(g_hCoreDll, L"PostMessageW");
            if (NULL == g_pfnPostMessageW) {
                // Window manager APIs not in this config
                FreeLibrary(g_hCoreDll);
                g_hCoreDll = NULL;
                SetLastError(WSASYSNOTREADY);
                bRet = FALSE;
            }
        } else {
            // If we can't load coredll, it is likely an out of memory condition
            SetLastError(WSAENOBUFS);
            bRet = FALSE;
        }
    }
    LeaveCriticalSection(&g_WSAAsyncCS);

    return bRet;
}   // WS2AsyncInit

DWORD
WS2AsyncResolverThread(
    LPVOID lpContext
    )
{
    WSAREQ  *pReq = (WSAREQ *)lpContext;
    WSAREQ  *pPrev;
    WSAREQ  *pCurr;
    HOSTENT *pHostent;
    DWORD   lParm = 0;
    DWORD   dwLen;
    DWORD   dwStatus;

    if (pReq->pName) {
        if (pReq->pBuf) {
            // gethostbyname request
            DEBUGMSG(ZONE_MISC, (L"WS2:WS2AsyncResolverThread: calling gethostbyname(%a)\n", pReq->pName));
            if (!(pHostent = gethostbyname(pReq->pName))) {
                lParm = GetLastError();
                DEBUGMSG(ZONE_MISC, (L"WS2:WS2AsyncResolverThread: gethostbyname(%a) failed %d\n", pReq->pName, lParm));
            }
        } else {
            // getaddrinfo request
            char szPort[16];
            sprintf(szPort, "%hu", pReq->port);
            DEBUGMSG(ZONE_MISC, (L"WS2:WS2AsyncResolverThread: calling getaddrinfo(%a)\n", pReq->pName));
            if (getaddrinfo(pReq->pName, szPort, NULL, pReq->ppAI)) {
                lParm = GetLastError();
                DEBUGMSG(ZONE_MISC, (L"WS2:WS2AsyncResolverThread: getaddrinfo(%a) failed %d\n", pReq->pName, lParm));
            }
        }
    } else {
        lParm = WSAEINVAL; // No name specified!
    }

    EnterCriticalSection(&g_WSAAsyncCS);
    // Remove request from list
    if (pReq == g_WSAReqList) {
        g_WSAReqList = pReq->pNext;
    } else {
        pPrev = pCurr = g_WSAReqList;
        while (pCurr) {
            if (pCurr == pReq) {
                break;
            }
            pPrev = pCurr;
            pCurr = pCurr->pNext;
        }
        ASSERT(pCurr == pReq);
        pPrev->pNext = pReq->pNext;
    }
    LeaveCriticalSection(&g_WSAAsyncCS);

    if (pReq->Status != SOCKET_ERROR) {
        if (lParm) {
            lParm <<= 16;
        } else {
            if (pReq->pBuf) {
                // gethostbyname request
                dwLen = pReq->dwBufLen;
                dwStatus = cpyhent(pReq->pBuf, pHostent, &dwLen);
                // cpyhent might fail for lack of buffer space
                // but that's not our problem - just forward the condition to 
                // the async caller
                lParm = (dwStatus << 16)|dwLen;
            }
        }
        DEBUGMSG(ZONE_MISC, (L"WS2:WS2AsyncResolverThread: Posting results for %a (%d)\n", pReq->pName, lParm));
        g_pfnPostMessageW(pReq->hWnd, pReq->wMsg, (WPARAM)pReq, lParm);
    }

    if (pReq->pName) {
        LocalFree(pReq->pName);
    }
    LocalFree(pReq);
    return 0;
}   // WS2AsyncResolverThread

//
// Try to match the specified thread's priority to the current thread's priority
//
void
MatchCurrentThreadPriority(
    HANDLE hThd
    )
{
    switch (CeGetCallerTrust()) {
    case OEM_CERTIFY_TRUST:
        CeSetThreadPriority(hThd, CeGetThreadPriority(GetCurrentThread()));
        break;
    case OEM_CERTIFY_RUN:
        SetThreadPriority(hThd, GetThreadPriority(GetCurrentThread()));
        break;
    default:
        ASSERT(0);
    }
}


//
// Common worker for WSAAsyncGetHostByName and WSAGetAddrInfo
//
HANDLE
WS2StartAsyncRequest(
    HWND            hWnd,
    unsigned int    wMsg,
    const char *    name,
    char *          buf,
    int             buflen,
    unsigned short  port,
    ADDRINFO **     ppAI
    )
{
    HANDLE  hThread;
    DWORD   ThreadId;
    WSAREQ  *pReq;
    DWORD   dwLen;
    DWORD   Status;

    if (!WS2AsyncInit()) {
        return NULL;
    }

    Status = WSAENOBUFS;

    if (pReq = (WSAREQ *)LocalAlloc(LPTR, sizeof(*pReq))) {
        pReq->hWnd = hWnd;
        pReq->wMsg = wMsg;
        pReq->pBuf = buf;
        pReq->dwBufLen = (DWORD)buflen;
        pReq->port = port;
        pReq->ppAI = ppAI;
        dwLen = strlen(name) + 1;
        if (pReq->pName = (char *)LocalAlloc(LPTR, dwLen)) {
            memcpy(pReq->pName, name, dwLen);
            pReq->Status = 0;
            EnterCriticalSection(&g_WSAAsyncCS);
            hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)WS2AsyncResolverThread,
                                    pReq, CREATE_SUSPENDED, &ThreadId);

            if (hThread) {
                // put it in list of pending items
                pReq->pNext = g_WSAReqList;
                g_WSAReqList = pReq;
                LeaveCriticalSection(&g_WSAAsyncCS);
                DEBUGMSG(ZONE_MISC, (L"WS2:WS2StartAsyncRequest: Started search for %a\n", pReq->pName));
                MatchCurrentThreadPriority(hThread);
                ResumeThread(hThread);
                CloseHandle(hThread);
                return (HANDLE)pReq;
            } else {
                LeaveCriticalSection(&g_WSAAsyncCS);
                LocalFree(pReq->pName);
                LocalFree(pReq);
            }
        } else {
            LocalFree(pReq);
        }
    }

    DEBUGMSG(ZONE_MISC, (L"WS2:WS2StartAsyncRequest: Starting search for %a failed %d\n", name, Status));
    SetLastError(Status);
    return NULL;
}   // WS2StartAsyncRequest


HANDLE
WSAAsyncGetHostByName(
    HWND            hWnd,
    unsigned int    wMsg,
    const char *    name,
    char *          buf,
    int             buflen
    )
{
    return (HANDLE)WS2StartAsyncRequest(hWnd, wMsg, name, buf, buflen, 0, NULL);
}   // WSAAsyncGetHostByName


HANDLE
WSAAsyncGetAddrInfo(
    HWND            hWnd,
    unsigned int    uMsg,
    const char *    name,
    unsigned short  port,
    ADDRINFO **     ppAI
    )
{
    return (HANDLE)WS2StartAsyncRequest(hWnd, uMsg, name, NULL, 0, port, ppAI);
}   // WSAAsyncGetAddrInfo


int
WSACancelAsyncRequest(
    HANDLE hAsyncTaskHandle
    )
{
    WSAREQ  *pReq;
    int     Status;

    if (!WS2AsyncInit()) {
        return SOCKET_ERROR;
    }

    EnterCriticalSection(&g_WSAAsyncCS);
    pReq = g_WSAReqList;
    while (pReq) {
        if (pReq == (WSAREQ *)hAsyncTaskHandle) {
            break;
        }
        pReq = pReq->pNext;
    }

    if (pReq) {
        pReq->Status = SOCKET_ERROR;
        LeaveCriticalSection(&g_WSAAsyncCS);
        Status = 0;
    } else {
        LeaveCriticalSection(&g_WSAAsyncCS);
        Status = SOCKET_ERROR;
        SetLastError(WSAEINVAL);
    }

    return Status;
}   // WSACancelAsyncRequest


 
// cpyhent - copy a hostent structure to a new buffer.
// lpBuf - [out] destination buffer
// lpSrc - [in] source hostent buffer
// lpBufSize - [in/out] comes in with size of lpBuf, goes out with resulting hostent size
//
// here's what we have to copy...
//struct  hostent 
//{
//        char    * h_name;           /* official name of host */
//        char    * * h_aliases;  /* alias list */
//        short   h_addrtype;             /* host address type */
//        short   h_length;               /* length of address */
//        char    * * h_addr_list; /* list of addresses */
//};


// Need to dword-align the sub-structures inside the HOSTENT buffer,
// because some processors (actually, most other than x86) don't like
// unaligned pointers.
// I'm not too happy about this - seems we should be able to devise
// syntax that would tell the compiler what we want, without making
// this explicit noise about DWORD alignment.
// (e.g. this code will break on a device that requires QWORD alignment)
// the difficulty is that we're implementing a miniature memory allocator
// inside the HOSTENT buffer, where we carve out chunks of memory and
// advance a byte pointer.

#define DWORDALIGN(i) (((i)+3)&~3)
#define ALNPTR(ptr) ptr=(char*)DWORDALIGN((int)(ptr))

int cpyhent(char * lpDstBuffer, const PHOSTENT lpSrcHostent, int * lpBufSize )
{
    int total_size;
    PHOSTENT lpDstHostent;
    char * lpWritePointer;
    int num_aliases;
    int num_addrs;
    int addr_size;
    char ** ptr;
    char ** src, **dst;
    int i;
    
    total_size = DWORDALIGN(sizeof(HOSTENT));
    // first, step through the sources HOSTENT and figure out how big it is
    total_size += DWORDALIGN(strlen(lpSrcHostent->h_name) + 1);
    // count the number entries in h_aliases
    num_aliases = 0;
    for (ptr = lpSrcHostent->h_aliases; ptr && *ptr != NULL; ptr++) {
        total_size += DWORDALIGN(strlen(*ptr) + 1);
        num_aliases ++;
    }
    total_size += DWORDALIGN((num_aliases + 1) * sizeof(char *));
    // count the number of entries in h_addr_list
    num_addrs = 0;
    for (ptr = lpSrcHostent->h_addr_list; *ptr != NULL; ptr++) {
        num_addrs ++;
    }
    // each addr occupies (sizeof(char*) + h_length) bytes
    total_size += DWORDALIGN(num_addrs * (sizeof(char*) + lpSrcHostent->h_length));
    // now, check to see that there's enough room in the destination buffer
    if (total_size > *lpBufSize) {
        *lpBufSize = total_size; // inform async caller how big the buffer should be.
        return WSAENOBUFS;
    }
    // now we can actually start copying stuff, safe in the knowledge it will all fit
    // the lpWritePointer pointer tracks the current write position in the destination buffer
    
    lpWritePointer = lpDstBuffer;
    lpDstHostent = (PHOSTENT) lpDstBuffer;
    // copy the absolute data first, then the strings
    lpDstHostent->h_length = lpSrcHostent->h_length;
    lpDstHostent->h_addrtype = lpSrcHostent->h_addrtype;
    
    // account for the HOSTENT structure itself
    lpWritePointer += sizeof(HOSTENT);
    ALNPTR(lpWritePointer);
    lpDstHostent->h_name = lpWritePointer;
    lpWritePointer +=strlen(lpSrcHostent->h_name) + 1;
    ALNPTR(lpWritePointer);
    ASSERT(lpWritePointer - lpDstBuffer <= *lpBufSize);
    // copy the host name
    strcpy(lpDstHostent->h_name, lpSrcHostent->h_name);
    // copy the aliases table
    if (lpSrcHostent->h_aliases)
    {
        lpDstHostent->h_aliases = (char **) lpWritePointer;
        lpWritePointer += (num_aliases+1) * sizeof(char*);
        ALNPTR(lpWritePointer);
        src = lpSrcHostent->h_aliases;
        dst = lpDstHostent->h_aliases;
        for (i = 0; i < num_aliases; i++) {
            *dst = lpWritePointer;
            lpWritePointer += strlen(*src) + 1;
            ALNPTR(lpWritePointer);
            ASSERT(lpWritePointer - lpDstBuffer <= *lpBufSize);
            strcpy(*dst, *src);
            dst++;
            src++;
        }
        // null-terminate the aliases table
        *dst = NULL;
        ASSERT(*src == NULL);
        // copy the address table
    }
    else
    {
        lpDstHostent->h_aliases = NULL;
    }
    
    // now copy the address table
    lpDstHostent->h_addr_list = (char **) lpWritePointer;
    lpWritePointer += (num_addrs+1) * sizeof(char*);
    ALNPTR(lpWritePointer);
    ASSERT(lpWritePointer - lpDstBuffer <= *lpBufSize);
    src = lpSrcHostent->h_addr_list;
    dst = lpDstHostent->h_addr_list;
    addr_size = lpSrcHostent->h_length;
    for (i = 0; i < num_addrs; i++) {
        *dst = lpWritePointer;
        lpWritePointer += addr_size;
        ALNPTR(lpWritePointer);
        ASSERT(lpWritePointer - lpDstBuffer <= *lpBufSize);
        memcpy(*dst, *src, addr_size);
        dst++;
        src++;
    }
    *dst = NULL;
    ASSERT(*src == NULL);
    return ERROR_SUCCESS; 
}   // cpyhent





//
// WSAAsyncSelect based on WSAEventSelect and WSAEnumNetworkEvents
//

//
// WS2FindWsaAsyncSocket: Find async socket structure based on either the associated
// socket handle or event handle
//
// Note: call with g_WSAAsyncCS taken
//
PWSAASYNC_SOCKET
WS2FindWsaAsyncSocket(
    SOCKET s,
    WSAEVENT e
    )
{
    PWSAASYNC_SOCKET pwsa;

    for (pwsa = g_WSAAsyncSelectList; pwsa; pwsa = pwsa->ws_next) {
        if (s) {
            if (pwsa->ws_socket == s) {
                return pwsa;
            }
        }
        if (e) {
            if (pwsa->ws_hevent == e) {
                return pwsa;
            }
        }
    }
    return NULL;
}   // WS2FindWsaAsyncSocket


// Note: call with g_WSAAsyncCS taken
void
WS2DeleteWsaAsyncSocket(
    PWSAASYNC_SOCKET pwsa
    )
{
    PWSAASYNC_SOCKET pcur, pprev;

    for (pcur = pprev = g_WSAAsyncSelectList; pcur;) {
        if (pcur == pwsa) {
            if (pcur == g_WSAAsyncSelectList) {
                g_WSAAsyncSelectList = pcur->ws_next;
            } else {
                pprev->ws_next = pcur->ws_next;
            }
            break;
        }
        pprev = pcur;
        pcur = pcur->ws_next;
    }
    if (pcur == pwsa) {
        g_cWSAAsyncSelectSockets--;
        WSAEventSelect(pwsa->ws_socket, pwsa->ws_hevent, 0);    // Cancel notifications
        CloseHandle(pwsa->ws_hevent);
        LocalFree(pwsa);
    }
}   // WS2DeleteWsaAsyncSocket


DWORD WS2AsyncSelectThread(LPVOID Nothing);

// Note: call with g_WSAAsyncCS taken
PWSAASYNC_SOCKET
WS2CreateWsaAsyncSocket(
    SOCKET  s,
    long    lEvents
    )
{
    PWSAASYNC_SOCKET pwsa;
    HANDLE hThd;

    // Start thread if needed
    if (FALSE == g_bWSAAsyncSelectThreadRunning) {
        g_bWSAAsyncSelectThreadRunning = TRUE;
        hThd = CreateThread(NULL, 0, WS2AsyncSelectThread, NULL, 0, NULL);
        if (hThd) {
            MatchCurrentThreadPriority(hThd);
            CloseHandle(hThd);
        } else {
            g_bWSAAsyncSelectThreadRunning = FALSE;
            return NULL;
        }
    }

    if ((MAXIMUM_WAIT_OBJECTS-2) < g_cWSAAsyncSelectSockets) {
        return NULL;
    }

    if (pwsa = LocalAlloc(0, sizeof(WSAASYNC_SOCKET))) {
        if (WSA_INVALID_EVENT != (pwsa->ws_hevent = WSACreateEvent())) {
            if (0 == WSAEventSelect(s, pwsa->ws_hevent, lEvents)) {
                g_cWSAAsyncSelectSockets++;
                return pwsa;
            } else {
                CloseHandle(pwsa->ws_hevent);
            }
        }
        LocalFree(pwsa);
    }
    return NULL;
}   // WS2CreateWsaAsyncSocket


//
// Process signalled network events by posting windows messages as appropriate
//
void
WS2ProcessWsaAsyncSocket(
    WSAEVENT hEvent
    )
{
    PWSAASYNC_SOCKET pwsa;
	WSANETWORKEVENTS NetworkEvents;
    long EventMask;
    DWORD lParam;
    int i;

    if (NULL == (pwsa = WS2FindWsaAsyncSocket(0, hEvent))) {
        //
        // The socket must have been closed and had WSAAsyncSelect(s,0,0,0) called
        // on it before this thread could process any of its events.
        //
        return;
    }

    if (SOCKET_ERROR == WSAEnumNetworkEvents(pwsa->ws_socket, pwsa->ws_hevent, &NetworkEvents)) {
        WS2DeleteWsaAsyncSocket(pwsa);
        return;
    }

    for (i = 0, EventMask = FD_READ; i < FD_MAX_EVENTS; i++, EventMask <<= 1) {
        if (EventMask & NetworkEvents.lNetworkEvents & pwsa->ws_levents) {
            lParam = WSAMAKESELECTREPLY(EventMask, NetworkEvents.iErrorCode[i]);
            g_pfnPostMessageW(pwsa->ws_hwnd, pwsa->ws_wmsg, pwsa->ws_socket, lParam);
        }
    }

    if (FD_CLOSE & NetworkEvents.lNetworkEvents) {
        WS2DeleteWsaAsyncSocket(pwsa);
    }

}   // WS2ProcessWsaAsyncSocket


DWORD
WS2AsyncSelectThread(
    LPVOID Nothing
    )
{
    int cAlloc, cCurr;
    DWORD ret;
    HANDLE * pList, * pHnd;
    PWSAASYNC_SOCKET pwsa;

    pList = NULL;
    cAlloc = 0;

    EnterCriticalSection(&g_WSAAsyncCS);
    while (1) {
        //
        // Count async sockets and allocate handle array if needed (include 1 extra for signal)
        //
        for (cCurr = 1, pwsa = g_WSAAsyncSelectList; pwsa; pwsa = pwsa->ws_next) {
            cCurr++;
        }

        if (1 == cCurr) {
            // No sockets to process
            goto wast_exit;
        }

        if (cCurr > cAlloc) {
            cAlloc = cCurr;
            if (pList) {
                LocalFree(pList);
            }
            pList = LocalAlloc(0, sizeof(pList) * cCurr);
            ASSERT(pList);
            if (NULL == pList) {
                goto wast_exit;
            }
        }

        pHnd = pList;
        *pHnd = g_WSAAsyncSelectSignal; // Signal event is first in the list
        pHnd++;

        //
        // Build array of event handles
        //
        for (pwsa = g_WSAAsyncSelectList; pwsa; pHnd++, pwsa = pwsa->ws_next) {
            *pHnd = pwsa->ws_hevent;
        }
        LeaveCriticalSection(&g_WSAAsyncCS);

        ret = WaitForMultipleObjects(cCurr, pList, FALSE, INFINITE);

        EnterCriticalSection(&g_WSAAsyncCS);
        if (WAIT_OBJECT_0 == ret) {
            // We got signalled by WSAAsyncSelect. Continuing will cause rebuild of event array
            continue;
        } else {
            if (ret < (DWORD)cCurr) {
                WS2ProcessWsaAsyncSocket(pList[ret]);
            } else {
                DEBUGMSG(1, (L"WS2AsyncSelectThread - WaitForMultipleObjects returned %d, error %d\n",
                             ret, GetLastError()));
                goto wast_exit;
            }
        }
    }

wast_exit:
    if (pList) {
        LocalFree(pList);
    }
    g_bWSAAsyncSelectThreadRunning = FALSE;
    LeaveCriticalSection(&g_WSAAsyncCS);
    return 0;
}   // WS2AsyncSelectThread


//
// Enable/disable notification of networks events via windows messages
//
int
WSAAPI
WSAAsyncSelect(
    IN SOCKET s,
    IN HWND hWnd,
    IN u_int wMsg,
    IN long lEvent
    )
{
    PWSAASYNC_SOCKET pwsa;
    int ret;
    DWORD dwLastError = 0;

    if (!WS2AsyncInit()) {
        return SOCKET_ERROR;
    }

    EnterCriticalSection(&g_WSAAsyncCS);

    pwsa = WS2FindWsaAsyncSocket(s, NULL);

    if (0 == lEvent) {
        // Remove socket from async system
        if (pwsa) {
            WS2DeleteWsaAsyncSocket(pwsa);
        } else {
            dwLastError = WSAEINVAL;
        }
        goto was_exit;
    }

    if (NULL == pwsa) {
        if (pwsa = WS2CreateWsaAsyncSocket(s, lEvent)) {
            //
    		// set socket to non-blocking
            //
            ret = 1;
    		if (SOCKET_ERROR == ioctlsocket(s, FIONBIO, &ret)) {
                // failed to set non-blocking
                WS2DeleteWsaAsyncSocket(pwsa);
                dwLastError = WSAENOBUFS;
                goto was_exit;
    		}

            pwsa->ws_socket = s;
            pwsa->ws_next   = g_WSAAsyncSelectList;
            g_WSAAsyncSelectList = pwsa;
        } else {
            dwLastError = WSAENOBUFS;
            goto was_exit;
        }
    } 

    pwsa->ws_hwnd   = hWnd;
    pwsa->ws_wmsg   = wMsg;
    pwsa->ws_levents = lEvent;

was_exit:
    if (dwLastError) {
        SetLastError(dwLastError);
        ret = SOCKET_ERROR;
    } else {
        SetEvent(g_WSAAsyncSelectSignal);
        ret = 0;
    }
    LeaveCriticalSection(&g_WSAAsyncCS);
    return ret;
}   // WSAAsyncSelect

