/*++

Copyright (c) 2000 Microsoft Corporation

Module Name:

    compression.h

Abstract:

    Do Http compression

Author:

    Anil Ruia (AnilR)           10-Apr-2000

--*/

#ifndef _COMPRESSION_H_
#define _COMPRESSION_H_

#define COMPRESSION_MIN_IO_BUFFER_SIZE                  256
#define COMPRESSION_MAX_IO_BUFFER_SIZE               100000
#define COMPRESSION_MIN_COMP_BUFFER_SIZE               1024
#define COMPRESSION_MAX_COMP_BUFFER_SIZE             100000
#define COMPRESSION_MAX_QUEUE_LENGTH                  10000
#define COMPRESSION_MIN_FILES_DELETED_PER_DISK_FREE       1
#define COMPRESSION_MAX_FILES_DELETED_PER_DISK_FREE    1024
#define COMPRESSION_MAX_COMPRESSION_LEVEL                10

#define COMPRESSION_DEFAULT_DISK_SPACE_USAGE      100000000
#define COMPRESSION_DEFAULT_BUFFER_SIZE                8192
#define COMPRESSION_DEFAULT_QUEUE_LENGTH               1000
#define COMPRESSION_DEFAULT_FILES_DELETED_PER_DISK_FREE 256
#define COMPRESSION_DEFAULT_FILE_SIZE_FOR_COMPRESSION     1


class COMPRESSION_SCHEME
{
 public:
    COMPRESSION_SCHEME()
        : m_hCompressionDll            (NULL),
          m_pCompressionContext        (NULL),
          m_dwPriority                 (1),
          m_dwDynamicCompressionLevel  (0),
          m_dwOnDemandCompressionLevel (COMPRESSION_MAX_COMPRESSION_LEVEL),
          m_dwCreateFlags              (0),
          m_fDoStaticCompression       (TRUE),
          m_fDoOnDemandCompression     (TRUE),
          m_fDoDynamicCompression      (TRUE),
          m_pfnInitCompression         (NULL),
          m_pfnDeInitCompression       (NULL),
          m_pfnCreateCompression       (NULL),
          m_pfnCompress                (NULL),
          m_pfnDestroyCompression      (NULL),
          m_pfnResetCompression        (NULL)
    {}

    HRESULT Initialize(MB *pmb, LPWSTR schemeName);

    ~COMPRESSION_SCHEME()
    {
        if (m_pfnDestroyCompression && m_pCompressionContext)
        {
            m_pfnDestroyCompression(m_pCompressionContext);
            m_pCompressionContext = NULL;
        }

        if (m_pfnDeInitCompression)
        {
            m_pfnDeInitCompression();
        }

        if (m_hCompressionDll)
        {
            FreeLibrary(m_hCompressionDll);
            m_hCompressionDll = NULL;
        }
    }

    STRU                           m_strCompressionSchemeName;
    STRA                           m_straCompressionSchemeName;
    STRU                           m_strFilePrefix;
    MULTISZ                        m_mszFileExtensions;
    MULTISZ                        m_mszScriptFileExtensions;

    DWORD                          m_dwPriority;

    HMODULE                        m_hCompressionDll;
    PFNCODEC_INIT_COMPRESSION      m_pfnInitCompression;
    PFNCODEC_DEINIT_COMPRESSION    m_pfnDeInitCompression;
    PFNCODEC_CREATE_COMPRESSION    m_pfnCreateCompression;
    PFNCODEC_COMPRESS              m_pfnCompress;
    PFNCODEC_DESTROY_COMPRESSION   m_pfnDestroyCompression;
    PFNCODEC_RESET_COMPRESSION     m_pfnResetCompression;

    // The compression context used for static compression
    PVOID                          m_pCompressionContext;

    DWORD                          m_dwDynamicCompressionLevel;
    DWORD                          m_dwOnDemandCompressionLevel;
    DWORD                          m_dwCreateFlags;
    BOOL                           m_fDoDynamicCompression;
    BOOL                           m_fDoStaticCompression;
    BOOL                           m_fDoOnDemandCompression;
};

typedef enum
{
    COMPRESSION_WORK_ITEM_COMPRESS,
    COMPRESSION_WORK_ITEM_DELETE,
    COMPRESSION_WORK_ITEM_TERMINATE
} COMPRESSION_WORK_ITEM_TYPE;

typedef struct
{
    LIST_ENTRY                  ListEntry;
    COMPRESSION_WORK_ITEM_TYPE  WorkItemType;
    COMPRESSION_SCHEME         *scheme;
    STRU                        strPhysicalPath;
} COMPRESSION_WORK_ITEM;

#define MAX_SERVER_SCHEMES 100

typedef enum
{
    DO_STATIC_COMPRESSION,
    DO_DYNAMIC_COMPRESSION
} COMPRESSION_TO_PERFORM;

#define DYNAMIC_COMPRESSION_BUFFER_SIZE 4096

typedef enum
{
    IN_CHUNK_LENGTH,
    IN_CHUNK_EXTENSION,
    IN_CHUNK_HEADER_NEW_LINE,
    AT_CHUNK_DATA_NEW_LINE,
    IN_CHUNK_DATA_NEW_LINE,
    IN_CHUNK_DATA
} COMPRESS_CHUNK_STATE;

class COMPRESSION_BUFFER
{
 public:
    static HRESULT Initialize()
    {
        ALLOC_CACHE_CONFIGURATION acConfig;
        
        acConfig.nConcurrency = 1;
        acConfig.nThreshold = 100;
        acConfig.cbSize = sizeof COMPRESSION_BUFFER;

        allocHandler = new ALLOC_CACHE_HANDLER("COMPRESSION_BUFFER",
                                               &acConfig);
        if (allocHandler == NULL)
        {
            return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
        }

        return S_OK;
    }

    static void Terminate()
    {
        if (allocHandler != NULL)
        {
            delete allocHandler;
        }
    }

    void *operator new(size_t size)
    {
        DBG_ASSERT(size == sizeof COMPRESSION_BUFFER);
        DBG_ASSERT(allocHandler != NULL);
        return allocHandler->Alloc();
    }

    void operator delete(void *pCompressionBuffer)
    {
        DBG_ASSERT(pCompressionBuffer != NULL);
        DBG_ASSERT(allocHandler != NULL);
        DBG_REQUIRE(allocHandler->Free(pCompressionBuffer));
    }

    BYTE       buffer[6 + DYNAMIC_COMPRESSION_BUFFER_SIZE + 7];
    LIST_ENTRY listEntry;

    static ALLOC_CACHE_HANDLER *allocHandler;
};

class COMPRESSION_CONTEXT
{
 public:
    static HRESULT Initialize()
    {
        ALLOC_CACHE_CONFIGURATION acConfig;
        
        acConfig.nConcurrency = 1;
        acConfig.nThreshold = 100;
        acConfig.cbSize = sizeof COMPRESSION_CONTEXT;

        allocHandler = new ALLOC_CACHE_HANDLER("COMPRESSION_CONTEXT",
                                               &acConfig);
        if (allocHandler == NULL)
        {
            return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
        }

        return S_OK;
    }

    static void Terminate()
    {
        if (allocHandler != NULL)
        {
            delete allocHandler;
        }
    }

    void *operator new(size_t size)
    {
        DBG_ASSERT(size == sizeof COMPRESSION_CONTEXT);
        DBG_ASSERT(allocHandler != NULL);
        return allocHandler->Alloc();
    }

    void operator delete(void *pCompressionContext)
    {
        DBG_ASSERT(pCompressionContext != NULL);
        DBG_ASSERT(allocHandler != NULL);
        DBG_REQUIRE(allocHandler->Free(pCompressionContext));
    }

    COMPRESSION_CONTEXT()
        : m_pScheme                      (NULL),
          m_fTransferChunkEncoded        (FALSE),
          m_pCompressionContext          (NULL),
          m_dwBytesInCurrentEncodedChunk (0),
          m_encodedChunkState            (IN_CHUNK_LENGTH),
          m_pIoBuffer                    (NULL),
          m_fRequestIsHead               (FALSE),
          m_fOriginalBodyEmpty           (TRUE)
    {
        InitializeListHead(&m_BufferListHead);
    }

    ~COMPRESSION_CONTEXT()
    {
        FreeBuffers();

        if (m_pIoBuffer != NULL)
        {
            delete m_pIoBuffer;
        }

        if (m_pCompressionContext)
        {
            m_pScheme->m_pfnDestroyCompression(m_pCompressionContext);
            m_pCompressionContext = NULL;
        }
    }

    void FreeBuffers()
    {
        while (!IsListEmpty(&m_BufferListHead))
        {
            LIST_ENTRY *pEntry = RemoveHeadList(&m_BufferListHead);
            COMPRESSION_BUFFER *pBuffer = CONTAINING_RECORD(pEntry,
                                                            COMPRESSION_BUFFER,
                                                            listEntry);
            delete pBuffer;
        }
    }

    BYTE *GetNewBuffer()
    {
        COMPRESSION_BUFFER *pBuffer = new COMPRESSION_BUFFER;
        if (pBuffer == NULL)
        {
            return NULL;
        }

        InitializeListHead(&pBuffer->listEntry);
        InsertHeadList(&m_BufferListHead, &pBuffer->listEntry);

        return pBuffer->buffer;
    }

    HRESULT SetupCurrentULChunk();

    DWORD QueryBytesAvailable()
    {
        if (m_fCurrentULChunkFromMemory)
        {
            return m_pCurrentULChunk->FromMemory.BufferLength;
        }

        // We don't handle (nor do we plan to) FileName chunks
        DBG_ASSERT(m_pCurrentULChunk->DataChunkType == HttpDataChunkFromFileHandle);
        return m_bytesInIoBuffer - m_currentLocationInIoBuffer;
    }

    BYTE *QueryBytePtr()
    {
        if (m_fCurrentULChunkFromMemory)
        {
            return (PBYTE)m_pCurrentULChunk->FromMemory.pBuffer;
        }

        // We don't handle (nor do we plan to) FileName chunks
        DBG_ASSERT(m_pCurrentULChunk->DataChunkType == HttpDataChunkFromFileHandle);
        return m_pIoBuffer + m_currentLocationInIoBuffer;
    }

    HRESULT ProcessEncodedChunkHeader();

    HRESULT CalculateEncodedChunkByteCount();

    HRESULT DeleteEncodedChunkExtension();

    HRESULT IncrementPointerInULChunk(IN DWORD dwIncr = 1);

    COMPRESSION_SCHEME          *m_pScheme;

    //
    // Is the original response chunk encoded?
    //
    BOOL                        m_fTransferChunkEncoded;

    //
    // If the original response is Chunk encoded, information about the
    // current chunk in the response
    //
    DWORD                       m_dwBytesInCurrentEncodedChunk;
    COMPRESS_CHUNK_STATE        m_encodedChunkState;

    //
    // The context used by the compression routines
    //
    PVOID                       m_pCompressionContext;

    //
    // Storage for the original response
    //
    BUFFER                      m_ULChunkBuffer;
    DWORD                       m_cULChunks;

    //
    // position in the original response
    //
    DWORD                       m_cCurrentULChunk;
    HTTP_DATA_CHUNK            *m_pCurrentULChunk;
    BOOL                        m_fCurrentULChunkFromMemory;

    //
    // buffer for reading data for FileHandle chunks
    //
    BYTE                       *m_pIoBuffer;
    DWORD                       m_currentLocationInIoBuffer;
    DWORD                       m_bytesInIoBuffer;

    static ALLOC_CACHE_HANDLER *allocHandler;

    LIST_ENTRY                  m_BufferListHead;

    //
    // Some members to keep track of HEAD request body suppression
    //
    BOOL                        m_fRequestIsHead;
    BOOL                        m_fOriginalBodyEmpty;
};

class HTTP_COMPRESSION
{
 public:

    static HRESULT Initialize();

    static VOID Terminate();

    static HRESULT DoStaticFileCompression(IN     W3_CONTEXT    *pW3Context,
                                           IN OUT W3_FILE_INFO **ppFileInfo);

    static HRESULT OnSendResponse(
                       IN  W3_CONTEXT *pW3Context,
                       IN  BOOL        fMoreData);

    static HRESULT DoDynamicCompression(
                       IN  W3_CONTEXT *pW3Context,
                       IN  BOOL        fMoreData);

    static BOOL QueryDoStaticCompression()
    {
        return sm_fDoStaticCompression;
    }

    static BOOL QueryDoDynamicCompression()
    {
        return sm_fDoDynamicCompression;
    }

 private:

    static COMPRESSION_SCHEME *sm_pCompressionSchemes[MAX_SERVER_SCHEMES];
    static DWORD sm_dwNumberOfSchemes;
    static STRU *sm_pstrCompressionDirectory;
    static STRA *sm_pstrCacheControlHeader;
    static STRA *sm_pstrExpiresHeader;
    static BOOL  sm_fDoStaticCompression;
    static BOOL  sm_fDoDynamicCompression;
    static BOOL  sm_fDoOnDemandCompression;
    static BOOL  sm_fDoDiskSpaceLimiting;
    static BOOL  sm_fNoCompressionForHttp10;
    static BOOL  sm_fNoCompressionForProxies;
    static BOOL  sm_fNoCompressionForRange;
    static BOOL  sm_fSendCacheHeaders;
    static DWORD sm_dwMaxDiskSpaceUsage;
    static DWORD sm_dwIoBufferSize;
    static DWORD sm_dwCompressionBufferSize;
    static DWORD sm_dwMaxQueueLength;
    static DWORD sm_dwFilesDeletedPerDiskFree;
    static DWORD sm_dwMinFileSizeForCompression;
    static PBYTE sm_pIoBuffer;
    static PBYTE sm_pCompressionBuffer;
    static CRITICAL_SECTION sm_CompressionDirectoryLock;
    static DWORD sm_dwCurrentDiskSpaceUsage;
    static BOOL  sm_fCompressionVolumeIsFat;
    static LIST_ENTRY sm_CompressionThreadWorkQueue;
    static CRITICAL_SECTION sm_CompressionThreadLock;
    static HANDLE sm_hThreadEvent;
    static HANDLE sm_hCompressionThreadHandle;
    static DWORD sm_dwCurrentQueueLength;
    static BOOL  sm_fHttpCompressionInitialized;
    static BOOL  sm_fIsTerminating;

    static HRESULT ReadMetadata(MB *pmb);

    static HRESULT InitializeCompressionSchemes(MB *pmb);

    static HRESULT InitializeCompressionDirectory();

    static HRESULT InitializeCompressionThread();

    static DWORD WINAPI CompressionThread(LPVOID);

    static BOOL QueueWorkItem(
        IN COMPRESSION_WORK_ITEM   *WorkItem,
        IN BOOL                     fOverrideMaxQueueLength,
        IN BOOL                     fQueueAtHead);

    static VOID FindMatchingSchemes(
                    IN  CHAR * pszAcceptEncoding,
                    IN  LPWSTR pszExtension,
                    IN  COMPRESSION_TO_PERFORM performCompr,
                    OUT DWORD  matchingSchemes[],
                    OUT DWORD *pdwClientCompressionCount);

    static HRESULT ConvertPhysicalPathToCompressedPath(
        IN COMPRESSION_SCHEME *scheme,
        IN STRU  *pstrPhysicalPath,
        OUT STRU *pstrCompressedFileName);

    static BOOL CheckForExistenceOfCompressedFile(
        IN  W3_FILE_INFO  *pOrigFile,
        IN  STRU          *pstrCompressedFileName,
        OUT W3_FILE_INFO **ppCompFile,
        IN  BOOL           fDeleteAllowed = TRUE);

    static BOOL QueueCompressFile(
        IN COMPRESSION_SCHEME *scheme,
        IN STRU               &strPhysicalPath);

    static VOID CompressFile(IN COMPRESSION_SCHEME *scheme,
                             IN STRU               &strPhysicalPath);

    static VOID FreeDiskSpace();

    static BOOL CompressAndWriteData(
                    IN  COMPRESSION_SCHEME *scheme,
                    IN  PBYTE               InputBuffer,
                    IN  DWORD               BytesToCompress,
                    OUT PDWORD              BytesWritten,
                    IN  HANDLE              hCompressedFile);
};

#endif _COMPRESSION_H_