/*
 * COPYRIGHT:   See COPYING in the top level directory
 * PROJECT:     ReactOS WinSock 2 API
 * FILE:        async.c
 * PURPOSE:     Async Block Object and Async Thread Management
 * PROGRAMMER:  Alex Ionescu (alex@relsoft.net)
 */

/* INCLUDES ******************************************************************/
#include <ws2_32.h>

/* DATA **********************************************************************/
BOOLEAN WsAsyncThreadInitialized;
LONG WsAsyncTaskHandle;
PLIST_ENTRY WsAsyncQueue;
CRITICAL_SECTION WsAsyncCritSect;
HANDLE WsAsyncEvent;
HANDLE WsAsyncCurrentTaskHandle;
HANDLE WsAsyncCancelledTaskHandle;
HINSTANCE WsAsyncDllHandle;

#define WsAsyncLock()   EnterCriticalSection(&WsAsyncCritSect);
#define WsAsyncUnlock() LeaveCriticalSection(&WsAsyncCritSect);

/* FUNCTIONS *****************************************************************/

VOID
WSAAPI
WsAsyncGlobalInitialize(VOID)
{
    /* Initialize the async lock */
    InitializeCriticalSection(&WsAsyncCritSect);
}

VOID
WSAAPI
WsAsyncGlobalTerminate(VOID)
{
    /* Destroy the async lock */
    DeleteCriticalSection(&WsAsyncCritSect);
}


SIZE_T
WSAAPI
BytesInHostent(PHOSTENT Hostent)
{
    SIZE_T Bytes;
    INT i;

    /* Start with the static stuff */
    Bytes = sizeof(HOSTENT) + strlen(Hostent->h_name) + sizeof(CHAR);

    /* Add 2 pointers for the list-terminators */
    Bytes += 2 * sizeof(ULONG_PTR);

    /* Now loop for the alises */
    for (i = 0; Hostent->h_aliases[i]; i++)
    {
        /* Add the alias size, plus the space its pointer takes */
        Bytes += strlen(Hostent->h_aliases[i]) + sizeof(CHAR) + sizeof(ULONG_PTR);
    }

    /* Now loop for the hostnames  */
    for (i = 0; Hostent->h_addr_list[i]; i++)
    {
        /* Add the alias size, plus the space its pointer takes */
        Bytes += Hostent->h_length + sizeof(ULONG_PTR);
    }

    /* Align to 8 bytes */
    return (Bytes + 7) & ~7;
}

SIZE_T
WSAAPI
BytesInServent(PSERVENT Servent)
{
    SIZE_T Bytes;
    INT i;

    /* Start with the static stuff */
    Bytes = sizeof(SERVENT) +
            strlen(Servent->s_name) + sizeof(CHAR) +
            strlen(Servent->s_proto) + sizeof(CHAR);

    /* Add 1 pointers for the list terminator */
    Bytes += sizeof(ULONG_PTR);

    /* Now loop for the alises */
    for (i = 0; Servent->s_aliases[i]; i++)
    {
        /* Add the alias size, plus the space its pointer takes */
        Bytes += strlen(Servent->s_aliases[i]) + sizeof(CHAR) + sizeof(ULONG_PTR);
    }

    /* return */
    return Bytes;
}

SIZE_T
WSAAPI
BytesInProtoent(PPROTOENT Protoent)
{
    SIZE_T Bytes;
    INT i;

    /* Start with the static stuff */
    Bytes = sizeof(SERVENT) + strlen(Protoent->p_name) + sizeof(CHAR);

    /* Add 1 pointers for the list terminator */
    Bytes += sizeof(ULONG_PTR);

    /* Now loop for the alises */
    for (i = 0; Protoent->p_aliases[i]; i++)
    {
        /* Add the alias size, plus the space its pointer takes */
        Bytes += strlen(Protoent->p_aliases[i]) + sizeof(CHAR) + sizeof(ULONG_PTR);
    }

    /* return */
    return Bytes;
}

SIZE_T
WSAAPI
CopyHostentToBuffer(IN PCHAR Buffer,
                    IN INT BufferLength,
                    IN PHOSTENT Hostent)
{
    SIZE_T BufferSize, CurrentSize, NameSize;
    PCHAR p = Buffer;
    DWORD Aliases = 0, Names = 0;
    DWORD i;
    PHOSTENT ReturnedHostent = (PHOSTENT)Buffer;

    /* Determine the buffer size required */
    BufferSize = BytesInHostent(Hostent);

    /* Check which size to use */
    if ((DWORD)BufferLength > BufferSize)
    {
        /* Zero the buffer */
        RtlZeroMemory(Buffer, BufferSize);
    }
    else
    {
        /* Zero the buffer */
        RtlZeroMemory(Buffer, BufferLength);
    }

    /* Start with the raw Hostent */
    CurrentSize = sizeof(HOSTENT);

    /* Return the size needed now */
    if (CurrentSize > (DWORD)BufferLength) return BufferSize;

    /* Copy the Hostent and initialize it */
    CopyMemory(p, Hostent, sizeof(HOSTENT));
    p = Buffer + CurrentSize;
    ReturnedHostent->h_name = NULL;
    ReturnedHostent->h_aliases = NULL;
    ReturnedHostent->h_addr_list = NULL;

    /* Find out how many aliases there are */
    while (Hostent->h_aliases[Aliases])
    {
        /* Increase the alias count */
        Aliases++;
    }

    /* Add the aliases to the size, and validate it */
    CurrentSize += (Aliases + 1) * sizeof(ULONG_PTR);
    if (CurrentSize > (DWORD)BufferLength)
    {
        /* Clear the aliases and return */
        Hostent->h_aliases = NULL;
        return BufferSize;
    }

    /* Write the aliases, update the pointer */
    ReturnedHostent->h_aliases = (PCHAR*)p;
    p = Buffer + CurrentSize;

    /* Find out how many names there are */
    while (Hostent->h_addr_list[Names])
    {
        /* Increase the alias count */
        Names++;
    }

    /* Add the names to the size, and validate it */
    CurrentSize += (Names + 1) * sizeof(ULONG_PTR);
    if (CurrentSize > (DWORD)BufferLength)
    {
        /* Clear the aliases and return */
        Hostent->h_addr_list = NULL;
        return BufferSize;
    }

    /* Write the names, update the pointer */
    ReturnedHostent->h_addr_list = (PCHAR*)p;
    p = Buffer + CurrentSize;

    /* Now add the names */
    for (i = 0; i < Names; i++)
    {
        /* Update size and validate */
        CurrentSize += Hostent->h_length;
        if (CurrentSize > (DWORD)BufferLength) return BufferSize;

        /* Write pointer and copy */
        ReturnedHostent->h_addr_list[i] = p;
        CopyMemory(p, Hostent->h_addr_list[i], Hostent->h_length);

        /* Update pointer */
        p = Buffer + CurrentSize;
    }

    /* Finalize the list */
    ReturnedHostent->h_addr_list[i] = NULL;

    /* Add the service name to the size, and validate it */
    NameSize = strlen(Hostent->h_name) + sizeof(CHAR);
    CurrentSize += NameSize;
    if (CurrentSize > (DWORD)BufferLength) return BufferSize;

    /* Write the service name and update the pointer */
    ReturnedHostent->h_name = p;
    CopyMemory(p, Hostent->h_name, NameSize);
    p = Buffer + CurrentSize;

    /* Now add the aliases */
    for (i = 0; i < Aliases; i++)
    {
        /* Update size and validate */
        NameSize = strlen(Hostent->h_aliases[i]) + sizeof(CHAR);
        CurrentSize += NameSize;
        if (CurrentSize > (DWORD)BufferLength) return BufferSize;

        /* Write pointer and copy */
        ReturnedHostent->h_aliases[i] = p;
        CopyMemory(p, Hostent->h_aliases[i], NameSize);

        /* Update pointer */
        p = Buffer + CurrentSize;
    }

    /* Finalize the list and return */
    ReturnedHostent->h_aliases[i] = NULL;
    return BufferSize;
}

SIZE_T
WSAAPI
CopyServentToBuffer(IN PCHAR Buffer,
                    IN INT BufferLength,
                    IN PSERVENT Servent)
{
    SIZE_T BufferSize, CurrentSize, NameSize;
    PCHAR p = Buffer;
    DWORD Aliases = 0;
    DWORD i;
    PSERVENT ReturnedServent = (PSERVENT)Buffer;

    /* Determine the buffer size required */
    BufferSize = BytesInServent(Servent);

    /* Check which size to use */
    if ((DWORD)BufferLength > BufferSize)
    {
        /* Zero the buffer */
        ZeroMemory(Buffer, BufferSize);
    }
    else
    {
        /* Zero the buffer */
        ZeroMemory(Buffer, BufferLength);
    }

    /* Start with the raw servent */
    CurrentSize = sizeof(SERVENT);

    /* Return the size needed now */
    if (CurrentSize > (DWORD)BufferLength) return BufferSize;

    /* Copy the servent and initialize it */
    CopyMemory(p, Servent, sizeof(SERVENT));
    p = Buffer + CurrentSize;
    ReturnedServent->s_name = NULL;
    ReturnedServent->s_aliases = NULL;
    ReturnedServent->s_proto = NULL;

    /* Find out how many aliases there are */
    while (Servent->s_aliases[Aliases])
    {
        /* Increase the alias count */
        Aliases++;
    }

    /* Add the aliases to the size, and validate it */
    CurrentSize += (Aliases + 1) * sizeof(ULONG_PTR);
    if (CurrentSize > (DWORD)BufferLength)
    {
        /* Clear the aliases and return */
        Servent->s_aliases = NULL;
        return BufferSize;
    }

    /* Write the aliases, update the pointer */
    ReturnedServent->s_aliases = (PCHAR*)p;
    p = Buffer + CurrentSize;

    /* Add the service name to the size, and validate it */
    NameSize = strlen(Servent->s_name) + sizeof(CHAR);
    CurrentSize += NameSize;
    if (CurrentSize > (DWORD)BufferLength) return BufferSize;

    /* Write the service name and update the pointer */
    ReturnedServent->s_name = p;
    CopyMemory(p, Servent->s_name, NameSize);
    p = Buffer + CurrentSize;

    /* Now add the aliases */
    for (i = 0; i < Aliases; i++)
    {
        /* Update size and validate */
        NameSize = strlen(Servent->s_aliases[i]) + sizeof(CHAR);
        CurrentSize += NameSize;
        if (CurrentSize > (DWORD)BufferLength) return BufferSize;

        /* Write pointer and copy */
        ReturnedServent->s_aliases[i] = p;
        CopyMemory(p, Servent->s_aliases[i], NameSize);

        /* Update pointer */
        p = Buffer + CurrentSize;
    }

    /* Finalize the list and return */
    ReturnedServent->s_aliases[i] = NULL;
    return BufferSize;
}

SIZE_T
WSAAPI
CopyProtoentToBuffer(IN PCHAR Buffer,
                     IN INT BufferLength,
                     IN PPROTOENT Protoent)
{
    SIZE_T BufferSize, CurrentSize, NameSize;
    PCHAR p = Buffer;
    DWORD Aliases = 0;
    DWORD i;
    PPROTOENT ReturnedProtoent = (PPROTOENT)Buffer;

    /* Determine the buffer size required */
    BufferSize = BytesInProtoent(Protoent);

    /* Check which size to use */
    if ((DWORD)BufferLength > BufferSize)
    {
        /* Zero the buffer */
        ZeroMemory(Buffer, BufferSize);
    }
    else
    {
        /* Zero the buffer */
        ZeroMemory(Buffer, BufferLength);
    }

    /* Start with the raw servent */
    CurrentSize = sizeof(PROTOENT);

    /* Return the size needed now */
    if (CurrentSize > (DWORD)BufferLength) return BufferSize;

    /* Copy the servent and initialize it */
    CopyMemory(p, Protoent, sizeof(PROTOENT));
    p = Buffer + CurrentSize;
    ReturnedProtoent->p_name = NULL;
    ReturnedProtoent->p_aliases = NULL;

    /* Find out how many aliases there are */
    while (Protoent->p_aliases[Aliases])
    {
        /* Increase the alias count */
        Aliases++;
    }

    /* Add the aliases to the size, and validate it */
    CurrentSize += (Aliases + 1) * sizeof(ULONG_PTR);
    if (CurrentSize > (DWORD)BufferLength)
    {
        /* Clear the aliases and return */
        Protoent->p_aliases = NULL;
        return BufferSize;
    }

    /* Write the aliases, update the pointer */
    ReturnedProtoent->p_aliases = (PCHAR*)p;
    p = Buffer + CurrentSize;

    /* Add the service name to the size, and validate it */
    NameSize = strlen(Protoent->p_name) + sizeof(CHAR);
    CurrentSize += NameSize;
    if (CurrentSize > (DWORD)BufferLength) return BufferSize;

    /* Write the service name and update the pointer */
    ReturnedProtoent->p_name = p;
    CopyMemory(p, Protoent->p_name, NameSize);
    p = Buffer + CurrentSize;

    /* Now add the aliases */
    for (i = 0; i < Aliases; i++)
    {
        /* Update size and validate */
        NameSize = strlen(Protoent->p_aliases[i]) + sizeof(CHAR);
        CurrentSize += NameSize;
        if (CurrentSize > (DWORD)BufferLength) return BufferSize;

        /* Write pointer and copy */
        ReturnedProtoent->p_aliases[i] = p;
        CopyMemory(p, Protoent->p_aliases[i], NameSize);

        /* Update pointer */
        p = Buffer + CurrentSize;
    }

    /* Finalize the list and return */
    ReturnedProtoent->p_aliases[i] = NULL;
    return BufferSize;
}

PWSASYNCBLOCK
WSAAPI
WsAsyncAllocateBlock(IN SIZE_T ExtraLength)
{
    PWSASYNCBLOCK AsyncBlock;

    /* Add the size of the block */
    ExtraLength += sizeof(WSASYNCBLOCK);

    /* Allocate it */
    AsyncBlock = HeapAlloc(WsSockHeap, 0, ExtraLength);

    /* Get a handle to it */
    AsyncBlock->TaskHandle = UlongToPtr(InterlockedIncrement(&WsAsyncTaskHandle));

    /* Return it */
    return AsyncBlock;
}

BOOL
WINAPI
WsAsyncThreadBlockingHook(VOID)
{
    /* Check if this task is being cancelled */
    if (WsAsyncCurrentTaskHandle == WsAsyncCancelledTaskHandle)
    {
        /* Cancel the blocking call so we can get back */
        WSACancelBlockingCall();
    }

    /* Return to system */
    return FALSE;
}

VOID
WSAAPI
WsAsyncFreeBlock(IN PWSASYNCBLOCK AsyncBlock)
{
    /* Free it */
    HeapFree(WsSockHeap, 0, AsyncBlock);
}

VOID
WSAAPI
WsAsyncGetServ(IN HANDLE TaskHandle,
               IN DWORD Operation,
               IN HWND hWnd,
               IN UINT wMsg,
               IN CHAR FAR *ByWhat,
               IN CHAR FAR *Protocol,
               IN CHAR FAR *Buffer,
               IN INT BufferLength)
{
    PSERVENT Servent;
    SIZE_T BufferSize = 0;
    LPARAM lParam;
    INT ErrorCode = 0;

    /* Check the operaiton */
    if (Operation == WsAsyncGetServByName)
    {
        /* Call the API */
        Servent = getservbyname(ByWhat, Protocol);
    }
    else
    {
        /* Call the API */
        Servent = getservbyport(PtrToUlong(ByWhat), Protocol);
    }

    /* Make sure we got one */
    if (!Servent) ErrorCode = GetLastError();

    /* Acquire the lock */
    WsAsyncLock();

    /* Check if this task got cancelled */
    if (TaskHandle == WsAsyncCancelledTaskHandle)
    {
        /* Return */
        WsAsyncUnlock();
        return;
    }

    /* If we got a Servent back, copy it */
    if (Servent)
    {
        /* Copy it into the buffer */
        BufferSize = CopyServentToBuffer(Buffer, BufferLength, Servent);

        /* Check if we had enough space */
        if (BufferSize > (DWORD)BufferLength)
        {
            /* Not enough */
            ErrorCode = WSAENOBUFS;
        }
        else
        {
            /* Perfect */
            ErrorCode = NO_ERROR;
        }
    }

    /* Not processing anyomre */
    WsAsyncCurrentTaskHandle = NULL;

    /* Release the lock */
    WsAsyncUnlock();

    /* Make the messed-up lParam reply */
    lParam = WSAMAKEASYNCREPLY(BufferSize, ErrorCode);

    /* Sent it through the Upcall API */
    WPUPostMessage(hWnd, wMsg, (WPARAM)TaskHandle, lParam);
}

VOID
WSAAPI
WsAsyncGetProto(IN HANDLE TaskHandle,
                IN DWORD Operation,
                IN HWND hWnd,
                IN UINT wMsg,
                IN CHAR FAR *ByWhat,
                IN CHAR FAR *Buffer,
                IN INT BufferLength)
{
    PPROTOENT Protoent;
    SIZE_T BufferSize = 0;
    LPARAM lParam;
    INT ErrorCode = 0;

    /* Check the operaiton */
    if (Operation == WsAsyncGetProtoByName)
    {
        /* Call the API */
        Protoent = getprotobyname(ByWhat);
    }
    else
    {
        /* Call the API */
        Protoent = getprotobynumber(PtrToUlong(ByWhat));
    }

    /* Make sure we got one */
    if (!Protoent) ErrorCode = GetLastError();

    /* Acquire the lock */
    WsAsyncLock();

    /* Check if this task got cancelled */
    if (TaskHandle == WsAsyncCancelledTaskHandle)
    {
        /* Return */
        WsAsyncUnlock();
        return;
    }

    /* If we got a Servent back, copy it */
    if (Protoent)
    {
        /* Copy it into the buffer */
        BufferSize = CopyProtoentToBuffer(Buffer, BufferLength, Protoent);

        /* Check if we had enough space */
        if (BufferSize > (DWORD)BufferLength)
        {
            /* Not enough */
            ErrorCode = WSAENOBUFS;
        }
        else
        {
            /* Perfect */
            ErrorCode = NO_ERROR;
        }
    }

    /* Not processing anyomre */
    WsAsyncCurrentTaskHandle = NULL;

    /* Release the lock */
    WsAsyncUnlock();

    /* Make the messed-up lParam reply */
    lParam = WSAMAKEASYNCREPLY(BufferSize, ErrorCode);

    /* Sent it through the Upcall API */
    WPUPostMessage(hWnd, wMsg, (WPARAM)TaskHandle, lParam);
}

VOID
WSAAPI
WsAsyncGetHost(IN HANDLE TaskHandle,
               IN DWORD Operation,
               IN HWND hWnd,
               IN UINT wMsg,
               IN CHAR FAR *ByWhat,
               IN INT Length,
               IN INT Type,
               IN CHAR FAR *Buffer,
               IN INT BufferLength)
{
    PHOSTENT Hostent;
    SIZE_T BufferSize = 0;
    LPARAM lParam;
    INT ErrorCode = 0;

    /* Check the operaiton */
    if (Operation == WsAsyncGetHostByAddr)
    {
        /* Call the API */
        Hostent = gethostbyaddr(ByWhat, Length, Type);
    }
    else
    {
        /* Call the API */
        Hostent = gethostbyname(ByWhat);
    }

    /* Make sure we got one */
    if (!Hostent) ErrorCode = GetLastError();

    /* Acquire the lock */
    WsAsyncLock();

    /* Check if this task got cancelled */
    if (TaskHandle == WsAsyncCancelledTaskHandle)
    {
        /* Return */
        WsAsyncUnlock();
        return;
    }

    /* If we got a Servent back, copy it */
    if (Hostent)
    {
        /* Copy it into the buffer */
        BufferSize = CopyHostentToBuffer(Buffer, BufferLength, Hostent);

        /* Check if we had enough space */
        if (BufferSize > (DWORD)BufferLength)
        {
            /* Not enough */
            ErrorCode = WSAENOBUFS;
        }
        else
        {
            /* Perfect */
            ErrorCode = NO_ERROR;
        }
    }

    /* Not processing anyomre */
    WsAsyncCurrentTaskHandle = NULL;

    /* Release the lock */
    WsAsyncUnlock();

    /* Make the messed-up lParam reply */
    lParam = WSAMAKEASYNCREPLY(BufferSize, ErrorCode);

    /* Sent it through the Upcall API */
    WPUPostMessage(hWnd, wMsg, (WPARAM)TaskHandle, lParam);
}

DWORD
WINAPI
WsAsyncThread(IN PWSASYNCCONTEXT Context)
{
    PWSASYNCBLOCK AsyncBlock;
    PLIST_ENTRY Entry;
    HANDLE AsyncEvent = Context->AsyncEvent;
    PLIST_ENTRY ListHead = &Context->AsyncQueue;

    /* Set the blocking hook */
    WSASetBlockingHook((FARPROC)WsAsyncThreadBlockingHook);

    /* Loop */
    while (TRUE)
    {
        /* Wait for the event */
        WaitForSingleObject(AsyncEvent, INFINITE);

        /* Get the lock */
        WsAsyncLock();

        /* Process the queue */
        while (ListHead->Flink != ListHead)
        {
            /* Remove this entry and get the async block */
            Entry = RemoveHeadList(ListHead);
            AsyncBlock = CONTAINING_RECORD(Entry, WSASYNCBLOCK, AsyncQueue);

            /* Save the current task handle */
            WsAsyncCurrentTaskHandle = AsyncBlock->TaskHandle;

            /* Release the lock */
            WsAsyncUnlock();

            /* Check which operation to do */
            switch (AsyncBlock->Operation)
            {
                /* Get Host by Y */
                case WsAsyncGetHostByAddr: case WsAsyncGetHostByName:

                    /* Call the handler */
                    WsAsyncGetHost(AsyncBlock->TaskHandle,
                                   AsyncBlock->Operation,
                                   AsyncBlock->GetHost.hWnd,
                                   AsyncBlock->GetHost.wMsg,
                                   AsyncBlock->GetHost.ByWhat,
                                   AsyncBlock->GetHost.Length,
                                   AsyncBlock->GetHost.Type,
                                   AsyncBlock->GetHost.Buffer,
                                   AsyncBlock->GetHost.BufferLength);
                    break;

                /* Get Proto by Y */
                case WsAsyncGetProtoByNumber: case WsAsyncGetProtoByName:

                    /* Call the handler */
                    WsAsyncGetProto(AsyncBlock->TaskHandle,
                                    AsyncBlock->Operation,
                                    AsyncBlock->GetProto.hWnd,
                                    AsyncBlock->GetProto.wMsg,
                                    AsyncBlock->GetHost.ByWhat,
                                    AsyncBlock->GetProto.Buffer,
                                    AsyncBlock->GetProto.BufferLength);
                    break;

                /* Get Serv by Y */
                case WsAsyncGetServByPort: case WsAsyncGetServByName:

                    /* Call the handler */
                    WsAsyncGetServ(AsyncBlock->TaskHandle,
                                   AsyncBlock->Operation,
                                   AsyncBlock->GetServ.hWnd,
                                   AsyncBlock->GetServ.wMsg,
                                   AsyncBlock->GetServ.ByWhat,
                                   AsyncBlock->GetServ.Protocol,
                                   AsyncBlock->GetServ.Buffer,
                                   AsyncBlock->GetServ.BufferLength);
                    break;

                /* Termination */
                case WsAsyncTerminate:

                    /* Clean up the extra reference */
                    WSACleanup();

                    /* Free the context block */
                    WsAsyncFreeBlock(AsyncBlock);

                    /* Acquire the lock */
                    WsAsyncLock();

                    /* Loop the queue and flush it */
                    while (!IsListEmpty(ListHead))
                    {
                        Entry = RemoveHeadList(ListHead);
                        AsyncBlock = CONTAINING_RECORD(Entry,
                                                       WSASYNCBLOCK,
                                                       AsyncQueue);
                        WsAsyncFreeBlock(AsyncBlock);
                    }

                    /* Release lock */
                    WsAsyncUnlock();

                    /* Close the event, free the Context */
                    CloseHandle(AsyncEvent);
                    HeapFree(WsSockHeap, 0, Context);

                    /* Remove the extra DLL reference and kill us */
                    FreeLibraryAndExitThread(WsAsyncDllHandle, 0);

                default:
                    break;
            }

            /* Done processing */
            WsAsyncCurrentTaskHandle = NULL;

            /* Free this block, get lock and reloop */
            WsAsyncFreeBlock(AsyncBlock);
            WsAsyncLock();
        }

        /* Release the lock */
        WsAsyncUnlock();
    }
}

BOOL
WSAAPI
WsAsyncCheckAndInitThread(VOID)
{
    HANDLE ThreadHandle;
    DWORD Tid;
    PWSASYNCCONTEXT Context = NULL;
    WSADATA WsaData;

    /* Make sure we're not initialized */
    if (WsAsyncThreadInitialized) return TRUE;

    /* Acquire the lock */
    WsAsyncLock();

    /* Make sure we're not initialized */
    if (!WsAsyncThreadInitialized)
    {
        /* Initialize Thread Context */
        Context = HeapAlloc(WsSockHeap, 0, sizeof(*Context));

        /* Initialize the Queue and event */
        WsAsyncQueue = &Context->AsyncQueue;
        InitializeListHead(WsAsyncQueue);
        Context->AsyncEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
        WsAsyncEvent = Context->AsyncEvent;

        /* Prevent us from ever being killed while running */
        WSAStartup(MAKEWORD(2,2), &WsaData);

        /* Create the thread */
        ThreadHandle = CreateThread(NULL,
                                    0,
                                    (LPTHREAD_START_ROUTINE)WsAsyncThread,
                                    Context,
                                    0,
                                    &Tid);

        /* Close the handle and set init */
        CloseHandle(ThreadHandle);
        WsAsyncThreadInitialized = TRUE;
    }

    /* Release the lock */
    WsAsyncUnlock();
    return WsAsyncThreadInitialized;
}

VOID
WSAAPI
WsAsyncTerminateThread(VOID)
{
    PWSASYNCBLOCK AsyncBlock;

    /* Make sure we're initialized */
    if (!WsAsyncThreadInitialized) return;

    /* Allocate a block */
    AsyncBlock = WsAsyncAllocateBlock(0);

    /* Initialize it for termination */
    AsyncBlock->Operation = WsAsyncTerminate;

    /* Queue the request and return */
    WsAsyncQueueRequest(AsyncBlock);
    WsAsyncThreadInitialized = FALSE;
}

VOID
WSAAPI
WsAsyncQueueRequest(IN PWSASYNCBLOCK AsyncBlock)
{
    /* Get the lock */
    WsAsyncLock();

    /* Insert it into the queue */
    InsertTailList(WsAsyncQueue, &AsyncBlock->AsyncQueue);

    /* Wake up the thread */
    SetEvent(WsAsyncEvent);

    /* Release lock and return */
    WsAsyncUnlock();
}

INT
WSAAPI
WsAsyncCancelRequest(IN HANDLE TaskHandle)
{
    PLIST_ENTRY Entry;
    PWSASYNCBLOCK AsyncBlock;

    /* Make sure we're initialized */
    if (!WsAsyncThreadInitialized) return WSAEINVAL;

    /* Acquire the lock */
    WsAsyncLock();

    /* Check if we're cancelling the current task */
    if (TaskHandle == WsAsyncCurrentTaskHandle)
    {
        /* Mark us as cancelled, the async thread will see this later */
        WsAsyncCancelledTaskHandle = TaskHandle;

        /* Release lock and return */
        WsAsyncUnlock();
        return NO_ERROR;
    }

    /* Loop the queue */
    Entry = WsAsyncQueue->Flink;
    while (Entry != WsAsyncQueue)
    {
        /* Get the Async Block */
        AsyncBlock = CONTAINING_RECORD(Entry, WSASYNCBLOCK, AsyncQueue);

        /* Check if this is the one */
        if (TaskHandle == AsyncBlock->TaskHandle)
        {
            /* It is, remove it */
            RemoveEntryList(Entry);

            /* Release the lock, free the block, and return */
            WsAsyncUnlock();
            WsAsyncFreeBlock(AsyncBlock);
            return NO_ERROR;
        }
        
        /* Move to the next entry */
        Entry = Entry->Flink;
    }

    /* Nothing found, fail */
    WsAsyncUnlock();
    return WSAEINVAL;
}
