/*++

   Copyright    (c)    1998    Microsoft Corporation

   Module Name :
     ulatq.cxx

   Abstract:
     Exported ULATQ.DLL routines
 
   Author:
     Bilal Alam (balam)             13-Dec-1999

   Environment:
     Win32 - User Mode

   Project:
     ULATQ.DLL
--*/

#include "precomp.hxx"

//
//  Configuration parameters registry key.
//

#define INET_INFO_KEY \
            "System\\CurrentControlSet\\Services\\w3svc"

#define INET_INFO_PARAMETERS_KEY \
            INET_INFO_KEY "\\Parameters"

const CHAR g_pszWpRegLocation[] =
    INET_INFO_PARAMETERS_KEY "\\w3dt";

DECLARE_DEBUG_PRINTS_OBJECT();
DECLARE_DEBUG_VARIABLE();
DECLARE_PLATFORM_TYPE();


WP_CONTEXT *            g_pwpContext = NULL;

//
// Completion routines for new requests, io completions, and disconnect
// notifications.  
//
// CODEWORK: Can we get away with these being global
//

PFN_ULATQ_NEW_REQUEST           g_pfnNewRequest = NULL;
PFN_ULATQ_IO_COMPLETION         g_pfnIoCompletion = NULL;
PFN_ULATQ_DISCONNECT            g_pfnDisconnect = NULL;
PFN_ULATQ_ON_SHUTDOWN           g_pfnOnShutdown = NULL;
PFN_ULATQ_COLLECT_PERF_COUNTERS g_pfnCollectCounters = NULL;


HRESULT
UlAtqInitialize(
    INT                 argc,
    LPWSTR              argv[],
    ULATQ_CONFIG *      pConfig
)
/*++

Routine Description:

    Initialize ULATQ

Arguments:

    argc - Number of command line parameters to worker process
    argv - Command line parameters
    pConfig - Configuration settings for ULATQ

Return Value:

    HRESULT

--*/
{
    HRESULT             rc = NO_ERROR;
    BOOL                fUlInit = FALSE;
    BOOL                fThreadPoolInit = FALSE;

    CREATE_DEBUG_PRINT_OBJECT("w3dt");
    if (!VALID_DEBUG_PRINT_OBJECT())
    {
        return E_FAIL;
    }

    LOAD_DEBUG_FLAGS_FROM_REG_STR( g_pszWpRegLocation, DEBUG_ERROR );

    INITIALIZE_PLATFORM_TYPE();

    //
    // Honour ULATQ_CONFIG settings.  Set completion routines
    // Need to set this up before we do any other initialization
    //
    
    g_pfnNewRequest = pConfig->pfnNewRequest;
    g_pfnIoCompletion = pConfig->pfnIoCompletion;
    g_pfnDisconnect = pConfig->pfnDisconnect;
    g_pfnOnShutdown = pConfig->pfnOnShutdown;
    g_pfnCollectCounters = pConfig->pfnCollectCounters;

    //
    // Initialize the thread pool
    //

    rc = ThreadPoolInitialize();
    if ( FAILED( rc ) )
    {
        goto Finished;
    }
    fThreadPoolInit = TRUE;

    //
    // Init UL
    //

    rc = HttpInitialize( 0 );
    if ( rc != NO_ERROR )
    {
        DBGPRINTF(( DBG_CONTEXT, "Error (rc=%08x) in UlInitialize. Exiting\n",
                    rc ));
        goto Finished;    
    }
    fUlInit = TRUE;

    //
    // Create global state object
    //

    g_pwpContext = new WP_CONTEXT;
    if ( g_pwpContext == NULL )
    {
        rc = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
        goto Finished;
    }

    //
    // Do global state initialization
    //

    rc = g_pwpContext->Initialize( argc, argv );
    if ( rc != NO_ERROR )
    {   
        //
        // WP_CONTEXT::Initialize returns a Win32 error code
        //
        
        rc = HRESULT_FROM_WIN32( rc );
        goto Finished;
    }

Finished:
    if ( rc != NO_ERROR )
    {
        if ( g_pwpContext != NULL )
        {
            delete g_pwpContext;
            g_pwpContext = NULL;
        }
        
        if ( fUlInit )
        {
            HttpTerminate();
        }
        
        if ( fThreadPoolInit )
        {
            ThreadPoolTerminate();
        }
    }

    return rc;
}

HRESULT
UlAtqStartListen(
    VOID
)
/*++

Routine Description:

    Begin listening for HTTP requests from UL.  This call must happen only
    after ULATQ has been initialized correctly (for example, completion
    routines should be set). 

Arguments:

    None

Return Value:

    HRESULT

--*/
{
    HRESULT         rc = NO_ERROR;

    DBG_ASSERT( g_pfnIoCompletion != NULL );
    DBG_ASSERT( g_pfnNewRequest != NULL );

    //
    // Make some UlReceiveHttpRequest calls
    //

    rc = g_pwpContext->Start();
    if ( rc != NO_ERROR )
    {
        return rc;
    }

    //
    // Send message to WAS that our initialization is complete
    //
    g_pwpContext->SendInitCompleteMessage( S_OK );

    //
    // Wait for shutdown because of WAS/request-count-max/etc.
    //

    g_pwpContext->RunMainThreadLoop();

    //
    // Before connection drain, allow the user to execute some code
    //

    if ( g_pfnOnShutdown != NULL )
    {
        g_pfnOnShutdown( g_pwpContext->QueryDoImmediateShutdown() );
    }

    //
    // Before we return, wait for outstanding requests to drain.  This 
    // prevents race before caller's shutdown and new requests coming in
    //
    
    g_pwpContext->CleanupOutstandingRequests();
    
    return rc;
}

VOID
UlAtqTerminate(
    HRESULT hrToSend
)
/*++

Routine Description:

    Terminate ULATQ

Arguments:

    None

Return Value:

    None

--*/
{
    if ( g_pwpContext != NULL )
    {
        if (FAILED(hrToSend))
        {
            g_pwpContext->SendInitCompleteMessage( hrToSend );
        }

        g_pwpContext->Terminate();
        delete g_pwpContext;
        g_pwpContext = NULL;
    } 
    
    HttpTerminate();

    ThreadPoolTerminate();

    DELETE_DEBUG_PRINT_OBJECT();
}

VOID *
UlAtqGetContextProperty(
    ULATQ_CONTEXT               pContext,
    ULATQ_CONTEXT_PROPERTY_ID   PropertyId
)
/*++

Routine Description:

    Get the UL_HTTP_REQUEST from the ULATQ_CONTEXT

Arguments:

    pContext - ULATQ_CONTEXT
    PropertyId - Property Id to set
    
Return Value:

    The actual property

--*/
{
    switch (PropertyId)
    {
    case ULATQ_PROPERTY_HTTP_REQUEST:
        UL_NATIVE_REQUEST *         pRequest;
        pRequest = (UL_NATIVE_REQUEST*) pContext;
        DBG_ASSERT( pRequest != NULL );
    
        return pRequest->QueryHttpRequest();

    case ULATQ_PROPERTY_APP_POOL_ID:
        return (VOID *)g_pwpContext->QueryConfig()->QueryAppPoolName();

    default:
        DBG_ASSERT(FALSE);
    }

    return NULL;
}

VOID
UlAtqSetContextProperty(
    ULATQ_CONTEXT               pContext,
    ULATQ_CONTEXT_PROPERTY_ID   PropertyId,
    PVOID                       pvData
)
/*++

Routine Description:

    Set a property of the ULATQ_CONTEXT

Arguments:

    pContext - ULATQ_CONTEXT
    PropertyId - Property Id to set
    pvData - Data specific to the property being set
    
Return Value:

    None

--*/
{
    UL_NATIVE_REQUEST *         pRequest;
    
    pRequest = (UL_NATIVE_REQUEST*) pContext;
    DBG_ASSERT( pRequest != NULL );

    switch ( PropertyId )
    {
    case ULATQ_PROPERTY_COMPLETION_CONTEXT:
        pRequest->SetContext( pvData );
        break;
    
    default:
        DBG_ASSERT( FALSE );
    }
}

VOID
UlAtqFreeContext(
    ULATQ_CONTEXT               pContext
)
/*++

Routine Description:

    Frees the ULATQ_CONTEXT so that it can be used to retrieve next request

Arguments:

    pContext - ULATQ_CONTEXT
    
Return Value:

    None

--*/
{
    UL_NATIVE_REQUEST *         pRequest;
    
    pRequest = (UL_NATIVE_REQUEST*) pContext;
    DBG_ASSERT( pRequest != NULL );
    
    pRequest->ResetContext();
}

HRESULT
UlAtqSendHttpResponse(
    ULATQ_CONTEXT               pContext,
    BOOL                        fAsync,
    DWORD                       dwFlags,
    HTTP_RESPONSE *             pResponse,
    HTTP_CACHE_POLICY *         pCachePolicy,
    DWORD                      *pcbSent,
    HTTP_LOG_FIELDS_DATA       *pUlLogData
)
/*++

Routine Description:

    Send a response to the client

Arguments:

    pContext - ULATQ_CONTEXT
    fAsync - Asynchronous or not?
    dwFlags - Response flags (like killing the connection)
    pResponse - UL_HTTP_RESPONSE to send
    pCachePolicy - Cache policy
    
Return Value:

    Win32 Error

--*/
{
    UL_NATIVE_REQUEST *         pRequest;
    
    pRequest = (UL_NATIVE_REQUEST*) pContext;
    DBG_ASSERT( pRequest != NULL );
   
    return pRequest->SendResponse( fAsync,
                                   dwFlags,
                                   pResponse,
                                   pCachePolicy,
                                   pcbSent,
                                   pUlLogData ); 
}

HRESULT
UlAtqSendEntityBody(
    ULATQ_CONTEXT               pContext,
    BOOL                        fAsync,
    DWORD                       dwFlags,
    DWORD                       cChunks,
    HTTP_DATA_CHUNK *           pChunks,
    DWORD                      *pcbSent,
    HTTP_LOG_FIELDS_DATA       *pUlLogData
)
/*++

Routine Description:

    Send entity to the client

Arguments:

    pContext - ULATQ_CONTEXT
    fAsync - Asynchronous or not?
    dwFlags - Response flags (like killing the connection)
    cChunks - Number of chunks in the response
    pChunks - Points to array of chunks
    
Return Value:

    HRESULT

--*/
{
    UL_NATIVE_REQUEST *         pRequest;
    
    pRequest = (UL_NATIVE_REQUEST*) pContext;
    DBG_ASSERT( pRequest != NULL );
    
    return pRequest->SendEntity( fAsync,
                                 dwFlags,
                                 cChunks,
                                 pChunks,
                                 pcbSent,
                                 pUlLogData );
}

HRESULT
UlAtqReceiveEntityBody(
    ULATQ_CONTEXT               pContext,
    BOOL                        fAsync,
    DWORD                       dwFlags,
    VOID *                      pBuffer,
    DWORD                       cbBuffer,
    DWORD *                     pBytesReceived
)
/*++

Routine Description:

    Receive entity from the client

Arguments:

    pContext - ULATQ_CONTEXT
    fAsync - Asynchronous or not?
    dwFlags - Response flags (like killing the connection)
    pBuffer - Buffer to store the data
    cbBuffer - The size of the receive buffer
    pBytesReceived - The number of bytes copied to the buffer upon return
    
Return Value:

    HRESULT

--*/
{
    UL_NATIVE_REQUEST *         pRequest;
    
    pRequest = (UL_NATIVE_REQUEST*) pContext;
    DBG_ASSERT( pRequest != NULL );
    
    return pRequest->ReceiveEntity( fAsync,
                                    dwFlags,
                                    pBuffer,
                                    cbBuffer,
                                    pBytesReceived);
}

HRESULT
UlAtqWaitForDisconnect( 
    HTTP_CONNECTION_ID              connectionId,
    BOOL                            fAsync,
    PVOID                           pvContext
)
/*++

Routine Description:

    Used to wait for a connection to close.  

Arguments:

    connectionId - connection in question
    fAsync - should we wait asynchronously?
    pvContext - context to pass back on async disconnect wait
    
Return Value:

    HRESULT

--*/
{
    UL_DISCONNECT_CONTEXT *         pContext;
    ULONG                           Status;
    HRESULT                         hr = NO_ERROR;

    //
    // Allocate an async context which will be freed once the connection
    // has been closed
    //
    
    pContext = new UL_DISCONNECT_CONTEXT( pvContext );
    if ( pContext == NULL )
    {
        return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
    }
    
    //
    // Do the wait
    //
    
    Status = HttpWaitForDisconnect( g_pwpContext->GetAsyncHandle(),
                                    connectionId,
                                    fAsync ? &(pContext->_Overlapped) : NULL );

    if ( Status != ERROR_IO_PENDING && Status != NO_ERROR )
    {
        hr = HRESULT_FROM_WIN32( Status );
        delete pContext;
    }

    return hr;
}

HRESULT
UlAtqInduceShutdown(
    BOOL fImmediate
)
/*++

Routine Description:

    Induce shutdown (used when IIS+ hosted in inetinfo.exe).  Simulates
    WAS telling us to shutdown

Arguments:

    None
    
Return Value:

    HRESULT

--*/
{
    DBG_ASSERT( g_pwpContext != NULL );

    if ( !g_pwpContext->IndicateShutdown( fImmediate ) )
    {
        return HRESULT_FROM_WIN32( GetLastError() );
    }

    return NO_ERROR;
}

HRESULT
UlAtqReceiveClientCertificate(
    ULATQ_CONTEXT               pContext,
    BOOL                        fAsync,
    BOOL                        fDoCertMap,
    HTTP_SSL_CLIENT_CERT_INFO **ppClientCertInfo
)
/*++

Routine Description:

    Receive client certificate

Arguments:

    pContext - ULATQ context
    fAsync - TRUE if we should do it asynchronously
    fDoCertMap - Map client certificate to token
    ppClientCertInfo - Set to point to client cert on success
    
Return Value:

    HRESULT

--*/
{
    UL_NATIVE_REQUEST *         pRequest;
    
    pRequest = (UL_NATIVE_REQUEST*) pContext;
    DBG_ASSERT( pRequest != NULL );
    DBG_ASSERT( pRequest->CheckSignature() );
    
    return pRequest->ReceiveClientCertificate( fAsync,
                                               fDoCertMap,
                                               ppClientCertInfo );
}

HRESULT
UlAtqFlushUlCache(
    WCHAR *                     pszUrlPrefix
)
/*++

Routine Description:

    Flush the UL cache at the given URL prefix 

Arguments:

    pszUrlPrefix - UL prefix to flush
    
Return Value:

    HRESULT

--*/
{
    if ( pszUrlPrefix == NULL )
    {
        DBG_ASSERT( FALSE );
        return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
    }
    
    if ( g_pwpContext == NULL )
    {
        //
        // Before removing this assert, please think hard (and then
        // think again)
        //
        
        DBG_ASSERT( FALSE );
        return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
    }
    
    HttpFlushResponseCache( g_pwpContext->GetAsyncHandle(),
                            pszUrlPrefix,
                            HTTP_FLUSH_RESPONSE_FLAG_RECURSIVE,
                            NULL );

    //
    // Mask the error since we may be flushing URLs which aren't 
    // in the cache (that's OK)
    //

    return NO_ERROR;
}