/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*-
 * ex: set softtabstop=4 tabstop=8 expandtab shiftwidth=4: *
 * Editor Settings: expandtabs and use 4 spaces for indentation */

/*
 * Copyright Likewise Software
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.  You should have received a copy of the GNU General
 * Public License along with this program.  If not, see
 * <http://www.gnu.org/licenses/>.
 *
 * LIKEWISE SOFTWARE MAKES THIS SOFTWARE AVAILABLE UNDER OTHER LICENSING
 * TERMS AS WELL.  IF YOU HAVE ENTERED INTO A SEPARATE LICENSE AGREEMENT
 * WITH LIKEWISE SOFTWARE, THEN YOU MAY ELECT TO USE THE SOFTWARE UNDER THE
 * TERMS OF THAT SOFTWARE LICENSE AGREEMENT INSTEAD OF THE TERMS OF THE GNU
 * GENERAL PUBLIC LICENSE, NOTWITHSTANDING THE ABOVE NOTICE.  IF YOU
 * HAVE QUESTIONS, OR WISH TO REQUEST A COPY OF THE ALTERNATE LICENSING
 * TERMS OFFERED BY LIKEWISE SOFTWARE, PLEASE CONTACT LIKEWISE SOFTWARE AT
 * license@likewisesoftware.com
 */

/*
 * Copyright (C) Likewise Software. All rights reserved.
 *
 * Module Name:
 *
 *        libmain.c
 *
 * Abstract:
 *
 *        Likewise IO (LWIO) - SRV
 *
 *        Protocols API - SMBV2
 *
 *        Library Main
 *
 * Authors: Sriram Nambakam (snambakam@likewise.com)
 *
 */

#include "includes.h"

static
NTSTATUS
SrvProcessRequestSpecific_SMB_V2(
    IN PSRV_EXEC_CONTEXT pExecContext,
    OUT PBOOLEAN pNeedPopStatistics,
    OUT PULONG pStatisticsCommand
    );

static
NTSTATUS
SrvSendInterimResponse_SMB_V2(
    PSRV_EXEC_CONTEXT pExecContext
    );

static
VOID
SrvCalculateResponseMessageSize_SMB_V2(
    IN OUT PSRV_MESSAGE_SMB_V2 Message,
    IN ULONG MaxLargeMtuBufferSize,
    OUT PULONG ResponseSizeHint
    );

static
NTSTATUS
SrvValidateCreditCharge(
    IN PLWIO_SRV_CONNECTION pConnection,
    IN PSRV_MESSAGE_SMB_V2 Message
    );

NTSTATUS
SrvProtocolInit_SMB_V2(
    VOID
    )
{
    NTSTATUS status = STATUS_SUCCESS;

    pthread_mutex_init(&gProtocolGlobals_SMB_V2.mutex, NULL);
    gProtocolGlobals_SMB_V2.pMutex = &gProtocolGlobals_SMB_V2.mutex;

    /* Configuration setup should always come first as other initalization
     * routines may rely on configuration parameters to be set */
    status = SrvConfigSetupInitial_SMB_V2();
    BAIL_ON_NT_STATUS(status);

error:

    return status;
}

static
VOID
SrvProtocolFreeCurCmdState_SMB_V2(
    PSRV_EXEC_CONTEXT pExecContext,
    PSRV_EXEC_CONTEXT_SMB_V2 pCtxSmb2
    )
{
    if (pExecContext)
    {
        SrvCancelTimedInterimResponse_SMB_V2(pExecContext);
        SrvReleaseExecContextAsync(pExecContext);
        if (pExecContext->ullAsyncId != 0)
        {
            SrvConnection2RemoveAsyncState(
                pExecContext->pConnection,
                pExecContext->ullAsyncId);
            pExecContext->ullAsyncId = 0;
        }
    }
    if (pCtxSmb2->hState)
    {
        pCtxSmb2->pfnStateRelease(pCtxSmb2->hState);
        pCtxSmb2->hState = NULL;
        pCtxSmb2->pfnStateRelease = NULL;
    }
}

NTSTATUS
SrvProtocolExecute_SMB_V2(
    PSRV_EXEC_CONTEXT pExecContext
    )
{
    NTSTATUS                 ntStatus           = STATUS_SUCCESS;
    PLWIO_SRV_CONNECTION     pConnection        = pExecContext->pConnection;
    PSRV_EXEC_CONTEXT_SMB_V2 pCtxSmb2           = NULL;
    BOOLEAN                  needPopStatistics  = FALSE;
    ULONG                    statisticsCommand  = 0;

    if (!pExecContext->pProtocolContext)
    {
        SRV_LOG_CALL_DEBUG(
            pExecContext->pLogContext,
            SMB_PROTOCOL_VERSION_2,
            0xFFFF,
            &SrvLogRequest_SMB_V2,
            pExecContext);

        ntStatus = SrvBuildExecContext_SMB_V2(pExecContext);
        BAIL_ON_NT_STATUS(ntStatus);
    }
    pCtxSmb2 = pExecContext->pProtocolContext;

    if (!pExecContext->pSmbResponse)
    {
        ntStatus = SMBPacketAllocate(
                        pExecContext->pConnection->hPacketAllocator,
                        &pExecContext->pSmbResponse);
        BAIL_ON_NT_STATUS(ntStatus);

        ntStatus = SMBPacketBufferAllocate(
                        pConnection->hPacketAllocator,
                        pCtxSmb2->ResponsePacketSizeHint,
                        &pExecContext->pSmbResponse->pRawBuffer,
                        &pExecContext->pSmbResponse->bufferLen);
        BAIL_ON_NT_STATUS(ntStatus);

        ntStatus = SMB2InitPacket(pExecContext->pSmbResponse);

        BAIL_ON_NT_STATUS(ntStatus);
    }

    for (;
         pCtxSmb2->iMsg < pCtxSmb2->ulNumRequests;
         pCtxSmb2->iMsg++)
    {
        ULONG               iMsg        = pCtxSmb2->iMsg;
        PSRV_MESSAGE_SMB_V2 pResponse   = &pCtxSmb2->pResponses[iMsg];
        PSRV_MESSAGE_SMB_V2 pSmbRequest = &pCtxSmb2->pRequests[iMsg];

        pResponse->pBuffer = pExecContext->pSmbResponse->pRawBuffer +
                             pExecContext->pSmbResponse->bufferUsed;
        pResponse->ulBytesAvailable = pExecContext->pSmbResponse->bufferLen -
                                      pExecContext->pSmbResponse->bufferUsed;
        pResponse->ulMessageSize = 0;

        ntStatus = SrvProcessRequestSpecific_SMB_V2(
                        pExecContext,
                        &needPopStatistics,
                        &statisticsCommand);
        if (ntStatus != STATUS_PENDING)
        {
            if (ntStatus != STATUS_SUCCESS && !pExecContext->bInternal)
            {
                ntStatus = SrvBuildErrorResponse_SMB_V2(
                                pExecContext,
                                ntStatus);
            }
            SrvProtocolFreeCurCmdState_SMB_V2(pExecContext, pCtxSmb2);
        }
        else if (pExecContext->pInterimResponse)
        {
            SrvSendInterimResponse_SMB_V2(pExecContext);
        }
        BAIL_ON_NT_STATUS(ntStatus);

        if (pResponse->pHeader && iMsg < pCtxSmb2->ulNumRequests - 1)
        {
            PBYTE pCursor     = pResponse->pBuffer + pResponse->ulMessageSize;
            ULONG ulBytesUsed = 0;

            ntStatus = Align(
                            pResponse->pBuffer,
                            &pCursor,
                            &pResponse->ulBytesAvailable,
                            &ulBytesUsed,
                            8);
            if (ntStatus != STATUS_SUCCESS)
            {
                break;
            }

            pResponse->ulMessageSize += ulBytesUsed;
            pResponse->pHeader->ulChainOffset = pResponse->ulMessageSize;
        }

        pExecContext->pSmbResponse->bufferUsed += pResponse->ulMessageSize;

        if (pCtxSmb2->pSession &&
            (pCtxSmb2->pSession->bRequireSecuritySignatures ||
             (pSmbRequest->pHeader->ulFlags & SMB2_FLAGS_SIGNED) != 0) &&
            pCtxSmb2->pSession->pSessionKey &&
            pResponse->pHeader &&
            pResponse->pHeader->ullSessionId != 0)
        {
            SMB2PacketSign(
                pResponse->pHeader,
                pResponse->ulMessageSize,
                &pCtxSmb2->pSession->SigningKey);
        }

        if (needPopStatistics)
        {
            NTSTATUS ntStatus2 = STATUS_SUCCESS;

            ntStatus2 = SrvStatisticsPopMessage(
                            pExecContext->pStatInfo,
                            statisticsCommand,
                            (pResponse->ulMessageSize ?
                             pResponse->ulMessageSize :
                             pResponse->ulZctMessageSize),
                            ntStatus);
            if (ntStatus2)
            {
                LWIO_LOG_ERROR( "Error: Failed to notify statistics "
                                "module on end of message processing "
                                "[error: %u]", ntStatus2);
            }

            needPopStatistics = FALSE;
            statisticsCommand = 0;
        }
    }

    SMBPacketMarshallFooter(pExecContext->pSmbResponse);

cleanup:

    return ntStatus;

error:

    if (pCtxSmb2)
    {
        PSRV_MESSAGE_SMB_V2 pSmbRequest = &pCtxSmb2->pRequests[pCtxSmb2->iMsg];

        SRV_LOG_VERBOSE(
            pExecContext->pLogContext,
            SMB_PROTOCOL_VERSION_2,
            pSmbRequest->pHeader->command,
            "command:%u(%s),uid(%llu),cmd-seq(%llu),pid(%u),tid(%u),"
            "credits(%u),flags(0x%x),chain-offset(%u),"
            "internal(%s),status(0x%x:%s)",
            pSmbRequest->pHeader->command,
            LWIO_SAFE_LOG_STRING(SrvGetCommandDescription_SMB_V2(pSmbRequest->pHeader->command)),
            (long long)pSmbRequest->pHeader->ullSessionId,
            (long long)pSmbRequest->pHeader->ullCommandSequence,
            pSmbRequest->pHeader->ulPid,
            pSmbRequest->pHeader->ulTid,
            pSmbRequest->pHeader->Credits,
            pSmbRequest->pHeader->ulFlags,
            pSmbRequest->pHeader->ulChainOffset,
            pExecContext->bInternal? "TRUE" : "FALSE",
            ntStatus,
            LWIO_SAFE_LOG_STRING(LwNtStatusToName(ntStatus)));
    }

    if (ntStatus != STATUS_PENDING && needPopStatistics)
    {
        NTSTATUS ntStatus2 = STATUS_SUCCESS;

        ntStatus2 = SrvStatisticsPopMessage(
                        pExecContext->pStatInfo,
                        statisticsCommand,
                        pExecContext->pSmbResponse->bufferUsed,
                        ntStatus);
        if (ntStatus2)
        {
            LWIO_LOG_ERROR( "Error: Failed to notify statistics "
                            "module on end of message processing "
                            "[error: %u]", ntStatus2);
        }
    }

    goto cleanup;
}

NTSTATUS
SrvProtocolCloseFile_SMB_V2(
    PLWIO_SRV_TREE_2 pTree,
    PLWIO_SRV_FILE_2 pFile
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;

    if (!pTree || !pFile)
    {
        ntStatus = STATUS_INVALID_PARAMETER;
        BAIL_ON_NT_STATUS(ntStatus);
    }

    SrvFile2Rundown(pFile);

error:

    return ntStatus;
}

static
NTSTATUS
SrvProcessRequestSpecific_SMB_V2(
    IN PSRV_EXEC_CONTEXT pExecContext,
    OUT PBOOLEAN pNeedPopStatistics,
    OUT PULONG pStatisticsCommand
    )
{
    NTSTATUS                   ntStatus    = STATUS_SUCCESS;
    PSRV_EXEC_CONTEXT_SMB_V2   pCtxSmb2    = pExecContext->pProtocolContext;
    ULONG                      iMsg        = pCtxSmb2->iMsg;
    PSRV_MESSAGE_SMB_V2        pSmbRequest = &pCtxSmb2->pRequests[iMsg];
    BOOLEAN needPopStatistics = FALSE;
    ULONG statisticsCommand = 0;

    if (pExecContext->pStatInfo)
    {
        statisticsCommand = pSmbRequest->pHeader->command;

        ntStatus = SrvStatisticsPushMessage(
                        pExecContext->pStatInfo,
                        statisticsCommand,
                        pSmbRequest->ulMessageSize);
        BAIL_ON_NT_STATUS(ntStatus);

        needPopStatistics = TRUE;
    }

    SRV_LOG_VERBOSE(pExecContext->pLogContext,
                    SMB_PROTOCOL_VERSION_2,
                    pSmbRequest->pHeader->command,
                    "command:%u(%s),uid(%llu),cmd-seq(%llu),pid(%u),tid(%u),credits(%u),flags(0x%x),chain-offset(%u)",
                    pSmbRequest->pHeader->command,
                    SrvGetCommandDescription_SMB_V2(pSmbRequest->pHeader->command),
                    (long long)pSmbRequest->pHeader->ullSessionId,
                    (long long)pSmbRequest->pHeader->ullCommandSequence,
                    pSmbRequest->pHeader->ulPid,
                    pSmbRequest->pHeader->ulTid,
                    pSmbRequest->pHeader->Credits,
                    pSmbRequest->pHeader->ulFlags,
                    pSmbRequest->pHeader->ulChainOffset);

    if (!pSmbRequest->ulVisit)
    {
        pExecContext->bInterimSent = FALSE;
        pExecContext->ullAsyncId = 0;
        pExecContext->usCreditsGranted = 0;
    }

    if (!pSmbRequest->ulVisit++ &&
        !pExecContext->bInternal &&
        (pSmbRequest->pHeader->command != COM2_CANCEL))
    {
        NTSTATUS ntCreditChargeStatus = STATUS_SUCCESS;

        ntCreditChargeStatus = SrvValidateCreditCharge(
                                    pExecContext->pConnection,
                                    pSmbRequest);

        // Consume requested credit even if the credit charge
        // was miscalculated by the client.
        ntStatus = SrvCreditorUpdateCreditWindow(
                        pExecContext->pConnection->pCreditor,
                        pSmbRequest->pHeader->ullCommandSequence,
                        pSmbRequest->CreditCharge,
                        pSmbRequest->pHeader->Credits,
                        &pExecContext->usCreditsGranted);
        if (ntStatus != STATUS_SUCCESS)
        {
            LWIO_LOG_ERROR(
                "Client request exceeded available credit",
                ntStatus);

            SrvConnectionSetInvalid(pExecContext->pConnection);
            ntStatus = STATUS_CONNECTION_RESET;
            BAIL_ON_NT_STATUS(ntStatus);
        }

        ntStatus = ntCreditChargeStatus;
        BAIL_ON_NT_STATUS(ntStatus);

        if ((pSmbRequest->pHeader->ulFlags & SMB2_FLAGS_RELATED_OPERATION) != 0)
        {
            // The first message in a chain cannot be related to anything.
            if (!iMsg)
            {
                ntStatus = STATUS_INVALID_PARAMETER;
                BAIL_ON_NT_STATUS(ntStatus);
            }
        }
        else
        {
            if (iMsg)
            {
                // Clear context items before processing an
                // un-related message
                if (pCtxSmb2->pFile)
                {
                    SrvFile2Release(pCtxSmb2->pFile);
                    pCtxSmb2->pFile = NULL;
                }
                if (pCtxSmb2->pTree)
                {
                    SrvTree2Release(pCtxSmb2->pTree);
                    pCtxSmb2->pTree = NULL;
                }
                if (pCtxSmb2->pSession)
                {
                    SrvSession2Release(pCtxSmb2->pSession);
                    pCtxSmb2->pSession = NULL;
                }

                pCtxSmb2->FileOpenStatus = STATUS_INVALID_PARAMETER;
            }

            /* Find the session so we can verify the signature. */
            /* XXX Could do Tree and File here too? */
            if (pSmbRequest->pHeader->ullSessionId != 0)
            {
                ntStatus = SrvConnection2FindSession(
                                pExecContext->pConnection,
                                pSmbRequest->pHeader->ullSessionId,
                                &pCtxSmb2->pSession);
                BAIL_ON_NT_STATUS(ntStatus);
            }
        }

        if (pCtxSmb2->pSession &&
            (pCtxSmb2->pSession->bRequireSecuritySignatures ||
             (pSmbRequest->pHeader->ulFlags & SMB2_FLAGS_SIGNED) != 0))
        {
            if ((pSmbRequest->pHeader->ulFlags & SMB2_FLAGS_SIGNED) == 0)
            {
                ntStatus = STATUS_ACCESS_DENIED;
                BAIL_ON_NT_STATUS(ntStatus);
            }
            else
            {
                ntStatus = SMB2PacketVerifySignature(
                                pSmbRequest->pHeader,
                                pSmbRequest->ulMessageSize,
                                &pCtxSmb2->pSession->SigningKey);
                BAIL_ON_NT_STATUS(ntStatus);
            }
        }
    }

    switch (pSmbRequest->pHeader->command)
    {
        case COM2_NEGOTIATE:
            ntStatus = SrvProcessNegotiate_SMB_V2(pExecContext);
            BAIL_ON_NT_STATUS(ntStatus);

            SrvConnectionSetState(
                    pExecContext->pConnection,
                    LWIO_SRV_CONN_STATE_NEGOTIATE);
            break;

        case COM2_ECHO:
        case COM2_SESSION_SETUP:
            {
                switch (SrvConnectionGetState(pExecContext->pConnection))
                {
                    case LWIO_SRV_CONN_STATE_NEGOTIATE:
                    case LWIO_SRV_CONN_STATE_READY:
                        break;

                    default:
                        ntStatus = STATUS_INVALID_SERVER_STATE;
                        break;
                }
            }
            break;

        default:
            switch (SrvConnectionGetState(pExecContext->pConnection))
            {
                case LWIO_SRV_CONN_STATE_READY:
                    break;

                default:
                    ntStatus = STATUS_INVALID_SERVER_STATE;
                    break;
            }
            break;
    }
    BAIL_ON_NT_STATUS(ntStatus);

    switch (pSmbRequest->pHeader->command)
    {
        case COM2_NEGOTIATE:
            break;

        case COM2_SESSION_SETUP:
            ntStatus = SrvProcessSessionSetup_SMB_V2(pExecContext);
            break;

        case COM2_LOGOFF:
            ntStatus = SrvProcessLogoff_SMB_V2(pExecContext);
            break;

        case COM2_TREE_CONNECT:
            ntStatus = SrvProcessTreeConnect_SMB_V2(pExecContext);
            break;

        case COM2_TREE_DISCONNECT:
            ntStatus = SrvProcessTreeDisconnect_SMB_V2(pExecContext);
            break;

        case COM2_CREATE:
            ntStatus = SrvProcessCreate_SMB_V2(pExecContext);
            break;

        case COM2_CLOSE:
            ntStatus = SrvProcessClose_SMB_V2(pExecContext);
            break;

        case COM2_FLUSH:
            ntStatus = SrvProcessFlush_SMB_V2(pExecContext);
            break;

        case COM2_READ:
            ntStatus = SrvProcessRead_SMB_V2(pExecContext);
            break;

        case COM2_WRITE:
            ntStatus = SrvProcessWrite_SMB_V2(pExecContext);
            break;

        case COM2_LOCK:
            ntStatus = SrvProcessLock_SMB_V2(pExecContext);
            break;

        case COM2_IOCTL:
            ntStatus = SrvProcessIOCTL_SMB_V2(pExecContext);
            break;

        case COM2_CANCEL:
            ntStatus = SrvProcessCancel_SMB_V2(pExecContext);
            break;

        case COM2_ECHO:
            ntStatus = SrvProcessEcho_SMB_V2(pExecContext);
            break;

        case COM2_FIND:
            ntStatus = SrvProcessFind_SMB_V2(pExecContext);
            break;

        case COM2_NOTIFY:
            ntStatus = SrvProcessNotify_SMB_V2(pExecContext);
            break;

        case COM2_GETINFO:
            ntStatus = SrvProcessGetInfo_SMB_V2(pExecContext);
            break;

        case COM2_SETINFO:
            ntStatus = SrvProcessSetInfo_SMB_V2(pExecContext);
            break;

        case COM2_BREAK:
            if (pExecContext->bInternal)
            {
                ntStatus = SrvProcessOplock_SMB_V2(pExecContext);
            }
            else
            {
                ntStatus = SrvProcessOplockBreak_SMB_V2(pExecContext);
            }
            break;

        default:
            ntStatus = STATUS_NOT_SUPPORTED;
            break;
    }

cleanup:

    *pNeedPopStatistics = needPopStatistics;
    *pStatisticsCommand = statisticsCommand;

    return ntStatus;

error:

    SRV_LOG_VERBOSE(
            pExecContext->pLogContext,
            SMB_PROTOCOL_VERSION_2,
            pSmbRequest->pHeader->command,
            "command:%u(%s),uid(%llu),cmd-seq(%llu),pid(%u),tid(%u),"
            "credits(%u),flags(0x%x),chain-offset(%u),"
            "internal(%s),status(0x%x:%s)",
            pSmbRequest->pHeader->command,
            LWIO_SAFE_LOG_STRING(SrvGetCommandDescription_SMB_V2(pSmbRequest->pHeader->command)),
            (long long)pSmbRequest->pHeader->ullSessionId,
            (long long)pSmbRequest->pHeader->ullCommandSequence,
            pSmbRequest->pHeader->ulPid,
            pSmbRequest->pHeader->ulTid,
            pSmbRequest->pHeader->Credits,
            pSmbRequest->pHeader->ulFlags,
            pSmbRequest->pHeader->ulChainOffset,
            pExecContext->bInternal? "TRUE" : "FALSE",
            ntStatus,
            LWIO_SAFE_LOG_STRING(LwNtStatusToDescription(ntStatus)));

    goto cleanup;
}

static
NTSTATUS
SrvSendInterimResponse_SMB_V2(
    PSRV_EXEC_CONTEXT pExecContext
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;

    ntStatus = SrvProtocolTransportSendResponse(
                    pExecContext->pConnection,
                    pExecContext->pInterimResponse,
                    NULL /* Statistics info */);
    BAIL_ON_NT_STATUS(ntStatus);

    pExecContext->usCreditsGranted = 0;
    pExecContext->bInterimSent = TRUE;

cleanup:

    SMBPacketRelease(
                pExecContext->pConnection->hPacketAllocator,
                pExecContext->pInterimResponse);

    pExecContext->pInterimResponse = NULL;

    return ntStatus;

error:

    LWIO_LOG_ERROR("Failed to send auxiliary response "
                   "[code:0x%08x",
                   ntStatus);

    goto cleanup;
}

static
VOID
SrvProtocolFreeContext_SMB_V2(
    PSRV_EXEC_CONTEXT pExecContext,
    PVOID pProtocolContext
    )
{
    PSRV_EXEC_CONTEXT_SMB_V2 pCtxSmb2 = pProtocolContext;

    SrvProtocolFreeCurCmdState_SMB_V2(pExecContext, pCtxSmb2);

    if (pCtxSmb2->pFile)
    {
        SrvFile2Release(pCtxSmb2->pFile);
    }

    if (pCtxSmb2->pTree)
    {
        SrvTree2Release(pCtxSmb2->pTree);
    }

    if (pCtxSmb2->pSession)
    {
        SrvSession2Release(pCtxSmb2->pSession);
    }

    if (pCtxSmb2->pResponses)
    {
        SrvFreeMemory(pCtxSmb2->pResponses);
    }

    if (pCtxSmb2->pRequests)
    {
        SrvFreeMemory(pCtxSmb2->pRequests);
    }

    if (pCtxSmb2->pErrorMessage)
    {
        SrvFreeMemory(pCtxSmb2->pErrorMessage);
    }

    SrvFreeMemory(pCtxSmb2);
}

////////////////////////////////////////////////////////////////////////

static
NTSTATUS
SrvMarshalInterimResponse_SMB_V2(
    PSRV_MESSAGE_SMB_V2 pSmbRequest,
    PBYTE               pBuffer,
    ULONG               ulOffset,
    ULONG               ulBytesAvailable,
    ULONG64             ullAsyncId,
    USHORT              usCreditsGranted,
    PULONG              pulBytesUsed
    );

NTSTATUS
SrvInterimResponse_SMB_V2(
    PSRV_EXEC_CONTEXT pExecContext
    )
{
    NTSTATUS                   ntStatus      = STATUS_SUCCESS;
    PSRV_EXEC_CONTEXT_SMB_V2   pCtxSmb2      = pExecContext->pProtocolContext;
    ULONG                      iMsg          = pCtxSmb2->iMsg;
    PSRV_MESSAGE_SMB_V2        pSmbRequest   = &pCtxSmb2->pRequests[iMsg];
    PSRV_MESSAGE_SMB_V2        pSmbResponse  = &pCtxSmb2->pResponses[iMsg];
    PBYTE    pBuffer          = pSmbResponse->pBuffer;
    ULONG    ulBytesAvailable = pSmbResponse->ulBytesAvailable;
    ULONG    ulOffset         = 0;
    ULONG    ulBytesUsed      = 0;
    
    if (pCtxSmb2->ulNumRequests > 1 && iMsg != pCtxSmb2->ulNumRequests - 1)
    {
        // Caller should have broken the chain by returning
        // STATUS_INSUFFICIENT_RESOURCES, or issued an independent
        // interim response via SrvTimedInterimResponse_SMB_V2().
        ntStatus = STATUS_INTERNAL_ERROR;
        BAIL_ON_NT_STATUS(ntStatus);
    }

    ntStatus = SrvMarshalInterimResponse_SMB_V2(
                    pSmbRequest,
                    pBuffer,
                    ulOffset,
                    ulBytesAvailable,
                    pExecContext->ullAsyncId,
                    pExecContext->usCreditsGranted,
                    &ulBytesUsed);
    BAIL_ON_NT_STATUS(ntStatus);

    pSmbResponse->ulMessageSize = ulBytesUsed;
    pExecContext->pSmbResponse->bufferUsed += pSmbResponse->ulMessageSize;
    SMBPacketMarshallFooter(pExecContext->pSmbResponse);

    pExecContext->pInterimResponse = pExecContext->pSmbResponse;
    pExecContext->pSmbResponse = NULL;

cleanup:

    return ntStatus;

error:

    goto cleanup;
}

////////////////////////////////////////////////////////////////////////

static
NTSTATUS
SrvMarshalInterimResponse_SMB_V2(
    PSRV_MESSAGE_SMB_V2 pSmbRequest,
    PBYTE               pBuffer,
    ULONG               ulOffset,
    ULONG               ulBytesAvailable,
    ULONG64             ullAsyncId,
    USHORT              usCreditsGranted,
    PULONG              pulBytesUsed
    )
{
    NTSTATUS ntStatus         = STATUS_SUCCESS;
    ULONG    ulBytesUsed      = 0;
    ULONG    ulTotalBytesUsed = 0;

    ntStatus = SMB2MarshalHeader(
                    pBuffer,
                    ulOffset,
                    ulBytesAvailable,
                    pSmbRequest->pHeader->command,
                    pSmbRequest->CreditCharge,
                    usCreditsGranted,
                    pSmbRequest->pHeader->ulPid,
                    pSmbRequest->pHeader->ullCommandSequence,
                    pSmbRequest->pHeader->ulTid,
                    pSmbRequest->pHeader->ullSessionId,
                    ullAsyncId,
                    STATUS_PENDING,
                    TRUE,
                    LwIsSetFlag(
                        pSmbRequest->pHeader->ulFlags,
                        SMB2_FLAGS_RELATED_OPERATION),
                    NULL,
                    &ulBytesUsed);
    BAIL_ON_NT_STATUS(ntStatus);

    pBuffer          += ulBytesUsed;
    ulOffset         += ulBytesUsed;
    ulBytesAvailable -= ulBytesUsed;
    ulTotalBytesUsed += ulBytesUsed;

    ntStatus = SMB2MarshalError(
                    pBuffer,
                    ulOffset,
                    ulBytesAvailable,
                    NULL,
                    0,
                    &ulBytesUsed);
    BAIL_ON_NT_STATUS(ntStatus);

    ulTotalBytesUsed += ulBytesUsed;
    *pulBytesUsed = ulTotalBytesUsed;

cleanup:

    return ntStatus;

error:

    goto cleanup;
}

////////////////////////////////////////////////////////////////////////

static
NTSTATUS
SrvBuildInterimResponse_SMB_V2(
    PSRV_EXEC_CONTEXT pExecContext
    );

static
VOID
SrvTimedInterimResponseCB_SMB_V2(
    IN PSRV_TIMER_REQUEST pTimerRequest,
    IN PVOID pUserData
    );

NTSTATUS
SrvTimedInterimResponse_SMB_V2(
    IN PSRV_EXEC_CONTEXT pExecContext,
    IN LONG MillisecondTimeout
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    LONG64 expirationTime = 0;
    PSRV_EXEC_CONTEXT pTimerExecContext = NULL;
    PSRV_TIMER_REQUEST pTimerRequest = NULL;
    PSRV_EXEC_CONTEXT_SMB_V2 pSmb2Context = pExecContext->pProtocolContext;

    if (MillisecondTimeout == 0)
    {
        // Send immediately
        ntStatus = SrvBuildInterimResponse_SMB_V2(pExecContext);
        BAIL_ON_NT_STATUS(ntStatus);

        ntStatus = SrvSendInterimResponse_SMB_V2(pExecContext);
        BAIL_ON_NT_STATUS(ntStatus);
    }
    else
    {
        // Timed event
        pTimerExecContext = SrvAcquireExecContext(pExecContext);

        ntStatus = WireGetCurrentNTTime(&expirationTime);
        BAIL_ON_NT_STATUS(ntStatus);

        expirationTime += MillisecondTimeout *
                          WIRE_FACTOR_MILLISECS_TO_HUNDREDS_OF_NANOSECS;

        ntStatus = SrvTimerPostRequest(
                       expirationTime,
                       pTimerExecContext,
                       SrvTimedInterimResponseCB_SMB_V2,
                       &pTimerRequest);
        BAIL_ON_NT_STATUS(ntStatus);

        pSmb2Context->InterimResponseTimer = pTimerRequest;
    }

error:

    if (!NT_SUCCESS(ntStatus))
    {
        LWIO_LOG_ERROR(
            "Failed to queue interim response for async id %u (timeout == %d ms).  "
            "Error was %s\n",
            pExecContext->ullAsyncId,
            MillisecondTimeout,
            LwNtStatusToName(ntStatus));

        if (pTimerExecContext)
        {
            SrvReleaseExecContext(pTimerExecContext);
        }
        if (pTimerRequest)
        {
            SrvTimerRelease(&pTimerRequest);
        }
    }

    return ntStatus;
}

////////////////////////////////////////////////////////////////////////

static
NTSTATUS
SrvBuildInterimResponse_SMB_V2(
    PSRV_EXEC_CONTEXT pExecContext
    )
{
    NTSTATUS                   ntStatus      = STATUS_SUCCESS;
    PSRV_EXEC_CONTEXT_SMB_V2   pCtxSmb2      = pExecContext->pProtocolContext;
    ULONG                      iMsg          = pCtxSmb2->iMsg;
    PSRV_MESSAGE_SMB_V2        pSmbRequest   = &pCtxSmb2->pRequests[iMsg];
    PSMB_PACKET pInterimResponse = NULL;
    PBYTE pOutBuffer             = NULL;
    ULONG ulOffset               = 0;
    ULONG ulBytesAvailable       = 0;
    ULONG ulBytesUsed            = 0;

    ntStatus = SMBPacketAllocate(
                    pExecContext->pConnection->hPacketAllocator,
                    &pInterimResponse);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = SMBPacketBufferAllocate(
                    pExecContext->pConnection->hPacketAllocator,
                    SMB_PACKET_DEFAULT_SIZE_SMB_V2,
                    &pInterimResponse->pRawBuffer,
                    &pInterimResponse->bufferLen);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = SMB2InitPacket(pInterimResponse);
 
    BAIL_ON_NT_STATUS(ntStatus);

    pOutBuffer = pInterimResponse->pRawBuffer + sizeof(NETBIOS_HEADER);
    ulBytesAvailable = pInterimResponse->bufferLen - sizeof(NETBIOS_HEADER);

    ntStatus = SrvMarshalInterimResponse_SMB_V2(
                    pSmbRequest,
                    pOutBuffer,
                    ulOffset,
                    ulBytesAvailable,
                    pExecContext->ullAsyncId,
                    pExecContext->usCreditsGranted,
                    &ulBytesUsed);

    pInterimResponse->bufferUsed += ulBytesUsed;

    SMBPacketMarshallFooter(pInterimResponse);

    if (pExecContext->pInterimResponse)
    {
        SMBPacketRelease(
                pExecContext->pConnection->hPacketAllocator,
                pExecContext->pInterimResponse);

        pExecContext->pInterimResponse = NULL;
    }

    pExecContext->pInterimResponse = pInterimResponse;

cleanup:

    return ntStatus;

error:

    if (pInterimResponse)
    {
        SMBPacketRelease(
            pExecContext->pConnection->hPacketAllocator,
            pInterimResponse);
    }

    goto cleanup;
}

////////////////////////////////////////////////////////////////////////

static
VOID
SrvTimedInterimResponseCB_SMB_V2(
    IN PSRV_TIMER_REQUEST pTimerRequest,
    IN PVOID pUserData
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PSRV_EXEC_CONTEXT pExecContext = pUserData;
    PSRV_EXEC_CONTEXT_SMB_V2 pSmb2Ctx = NULL;
    BOOLEAN execContextLocked = FALSE;

    LWIO_LOCK_MUTEX(execContextLocked, &pExecContext->execMutex);

    pSmb2Ctx = pExecContext->pProtocolContext;
    if (pSmb2Ctx && pSmb2Ctx->InterimResponseTimer == pTimerRequest)
    {
        SrvTimerRelease(&pSmb2Ctx->InterimResponseTimer);

        if (pExecContext->ullAsyncId)
        {
            ntStatus = SrvBuildInterimResponse_SMB_V2(pExecContext);
            BAIL_ON_NT_STATUS(ntStatus);

            ntStatus = SrvSendInterimResponse_SMB_V2(pExecContext);
            BAIL_ON_NT_STATUS(ntStatus);
        }
    }

error:
    if (!NT_SUCCESS(ntStatus))
    {
        LWIO_LOG_ERROR(
            "Failed to send interim response for Async ID %u.  Error was %s\n",
            pExecContext->ullAsyncId,
            LwNtStatusToName(ntStatus));
    }

    LWIO_UNLOCK_MUTEX(execContextLocked, &pExecContext->execMutex);

    SrvReleaseExecContext(pExecContext);
}

////////////////////////////////////////////////////////////////////////

VOID
SrvCancelTimedInterimResponse_SMB_V2(
    IN PSRV_EXEC_CONTEXT pExecContext
    )
{
    PSRV_EXEC_CONTEXT_SMB_V2 pCtxSmb2 = pExecContext->pProtocolContext;

    if (pCtxSmb2->InterimResponseTimer)
    {
        NTSTATUS ntStatus = STATUS_SUCCESS;

        ntStatus = SrvTimerCancelRequest(pCtxSmb2->InterimResponseTimer, NULL);
        if (ntStatus == STATUS_SUCCESS)
        {
            SrvReleaseExecContext(pExecContext);
        }
        SrvTimerRelease(&pCtxSmb2->InterimResponseTimer);
    }
}

////////////////////////////////////////////////////////////////////////

NTSTATUS
SrvBuildExecContext_SMB_V2(
    PSRV_EXEC_CONTEXT pExecContext
    )
{
    PLWIO_SRV_CONNECTION     pConnection  = pExecContext->pConnection;
    PSMB_PACKET              pSmbRequest  = pExecContext->pSmbRequest;
    PSRV_EXEC_CONTEXT_SMB_V2 pSmb2Context = NULL;
    NTSTATUS ntStatus      = STATUS_SUCCESS;
    ULONG ulNumRequests    = 0;
    ULONG iRequest         = 0;
    ULONG ulBytesAvailable = pSmbRequest->bufferUsed;
    PBYTE pBuffer          = pSmbRequest->pRawBuffer;
    ULONG totalSizeHint    = 0;
    ULONG maxMessageBufferSize =
        pConnection->serverProperties.ulMaxBufferSize_SMB_V2;

    if (ulBytesAvailable < sizeof(NETBIOS_HEADER))
    {
        ntStatus = STATUS_INVALID_NETWORK_RESPONSE;
        BAIL_ON_NT_STATUS(ntStatus);
    }

    pBuffer += sizeof(NETBIOS_HEADER);
    ulBytesAvailable -= sizeof(NETBIOS_HEADER);

    ntStatus = SrvAllocateMemory(
                    sizeof(SRV_EXEC_CONTEXT_SMB_V2),
                    (PVOID*)&pSmb2Context);
    BAIL_ON_NT_STATUS(ntStatus);

    pSmb2Context->FileOpenStatus = STATUS_INVALID_PARAMETER;

    while (pBuffer)
    {
        PSMB2_HEADER pHeader     = NULL; // Do not free
        ULONG        ulOffset    = 0;
        ULONG        ulBytesUsed = 0;

        ulNumRequests++;

        ntStatus = SrvUnmarshalHeader_SMB_V2(
                        pBuffer,
                        ulOffset,
                        ulBytesAvailable,
                        &pHeader,
                        &ulBytesUsed);
        BAIL_ON_NT_STATUS(ntStatus);

        if (pHeader->ulChainOffset)
        {
            if (ulBytesAvailable < pHeader->ulChainOffset)
            {
                ntStatus = STATUS_INVALID_NETWORK_RESPONSE;
                BAIL_ON_NT_STATUS(ntStatus);
            }

            pBuffer += pHeader->ulChainOffset;
            ulBytesAvailable -= pHeader->ulChainOffset;
        }
        else
        {
            pBuffer = NULL;
        }
    }

    ntStatus = SrvAllocateMemory(
                    sizeof(SRV_MESSAGE_SMB_V2) * ulNumRequests,
                    (PVOID*)&pSmb2Context->pRequests);
    BAIL_ON_NT_STATUS(ntStatus);

    pSmb2Context->ulNumRequests = ulNumRequests;

    ntStatus = SrvAllocateMemory(
                    sizeof(SRV_MESSAGE_SMB_V2) * ulNumRequests,
                    (PVOID*)&pSmb2Context->pResponses);
    BAIL_ON_NT_STATUS(ntStatus);

    pSmb2Context->ulNumResponses = ulNumRequests;

    pBuffer = pSmbRequest->pRawBuffer + sizeof(NETBIOS_HEADER);
    ulBytesAvailable = pSmbRequest->bufferUsed - sizeof(NETBIOS_HEADER);

    if (pConnection->Dialect >= SMB_PROTOCOL_DIALECT_SMB_2_1)
    {
        maxMessageBufferSize = pConnection->serverProperties.ulMaxBufferSizeLargeMtu_SMB_V2;
    }

    for (; iRequest < ulNumRequests; iRequest++)
    {
        PSRV_MESSAGE_SMB_V2 pMessage = &pSmb2Context->pRequests[iRequest];
        ULONG ulOffset = 0;
        ULONG sizeHint = 0;

        pMessage->pBuffer = pBuffer;

        ntStatus = SrvUnmarshalHeader_SMB_V2(
                        pMessage->pBuffer,
                        ulOffset,
                        ulBytesAvailable,
                        &pMessage->pHeader,
                        &pMessage->ulHeaderSize);
        BAIL_ON_NT_STATUS(ntStatus);

        if (pMessage->pHeader && pMessage->pHeader->ulChainOffset)
        {
            pMessage->ulMessageSize = pMessage->pHeader->ulChainOffset;
            pMessage->ulBytesAvailable = pMessage->pHeader->ulChainOffset;
            pBuffer += pMessage->pHeader->ulChainOffset;
            ulBytesAvailable -= pMessage->pHeader->ulChainOffset;
        }
        else
        {
            pMessage->ulMessageSize = ulBytesAvailable;
            pMessage->ulBytesAvailable = ulBytesAvailable;
        }

        SrvCalculateResponseMessageSize_SMB_V2(
            pMessage,
            maxMessageBufferSize,
            &sizeHint);

        ntStatus = LwRtlSafeAddULONG(&totalSizeHint, totalSizeHint, sizeHint);
        BAIL_ON_NT_STATUS(ntStatus);
    }

    ntStatus = LwRtlSafeAddULONG(
                    &totalSizeHint,
                    totalSizeHint,
                    sizeof(NETBIOS_HEADER));
    BAIL_ON_NT_STATUS(ntStatus);

    pSmb2Context->ResponsePacketSizeHint = totalSizeHint;

    pExecContext->pProtocolContext = pSmb2Context;
    pExecContext->pfnFreeContext   = SrvProtocolFreeContext_SMB_V2;

cleanup:

    return ntStatus;

error:

    if (pSmb2Context)
    {
        SrvProtocolFreeContext_SMB_V2(NULL, pSmb2Context);
    }

    goto cleanup;
}

VOID
SrvProtocolShutdown_SMB_V2(
    VOID
    )
{
    if (gProtocolGlobals_SMB_V2.pMutex)
    {
        pthread_mutex_destroy(&gProtocolGlobals_SMB_V2.mutex);
        gProtocolGlobals_SMB_V2.pMutex = NULL;
    }

    /* Configuration shutdown should always come last as other shutdown
     * routines may rely on configuration parameters to be set */
    SrvConfigShutdown_SMB_V2();
}

PCSTR
SrvGetCommandDescription_SMB_V2(
    ULONG ulCommand
    )
{
    static struct
    {
        ULONG ulCommand;
        PCSTR pszDescription;
    } commandLookup[] =
    {
        {
            COM2_NEGOTIATE,
            COM2_NEGOTIATE_DESC
        },
        {
            COM2_SESSION_SETUP,
            COM2_SESSION_SETUP_DESC
        },
        {
            COM2_LOGOFF,
            COM2_LOGOFF_DESC
        },
        {
            COM2_TREE_CONNECT,
            COM2_TREE_CONNECT_DESC
        },
        {
            COM2_TREE_DISCONNECT,
            COM2_TREE_DISCONNECT_DESC
        },
        {
            COM2_CREATE,
            COM2_CREATE_DESC
        },
        {
            COM2_CLOSE,
            COM2_CLOSE_DESC
        },
        {
            COM2_FLUSH,
            COM2_FLUSH_DESC
        },
        {
            COM2_READ,
            COM2_READ_DESC
        },
        {
            COM2_WRITE,
            COM2_WRITE_DESC
        },
        {
            COM2_LOCK,
            COM2_LOCK_DESC
        },
        {
            COM2_IOCTL,
            COM2_IOCTL_DESC
        },
        {
            COM2_CANCEL,
            COM2_CANCEL_DESC
        },
        {
            COM2_ECHO,
            COM2_ECHO_DESC
        },
        {
            COM2_FIND,
            COM2_FIND_DESC
        },
        {
            COM2_NOTIFY,
            COM2_NOTIFY_DESC
        },
        {
            COM2_GETINFO,
            COM2_GETINFO_DESC
        },
        {
            COM2_SETINFO,
            COM2_SETINFO_DESC
        },
        {
            COM2_BREAK,
            COM2_BREAK_DESC
        }
    };
    PCSTR pszDescription = NULL;
    ULONG iDesc = 0;

    for (; iDesc < sizeof(commandLookup)/sizeof(commandLookup[0]); iDesc++)
    {
        if (commandLookup[iDesc].ulCommand == ulCommand)
        {
            pszDescription = commandLookup[iDesc].pszDescription;
            break;
        }
    }

    return (pszDescription ? pszDescription : "SMB2_UNKNOWN_COMMAND");
}

////////////////////////////////////////////////////////////////////////

static
VOID
SrvCalculateResponseMessageSize_SMB_V2(
    IN OUT PSRV_MESSAGE_SMB_V2 Message,
    IN ULONG MaxLargeMtuBufferSize,
    OUT PULONG ResponseSizeHint
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    ULONG responseSize = 0;

    switch (Message->pHeader->command)
    {
        case COM2_NEGOTIATE:
            // Security buffer variable length set to 2 bytes
            responseSize = sizeof(SMB2_HEADER) +
                SMB2_MAX_NORMAL_BUFFER_SIZE +
                COM2_NEGOTIATE_RESP_STRUCT_SIZE;
            break;

        case COM2_SESSION_SETUP:
            // Security buffer variable length set to 2 bytes
            responseSize = sizeof(SMB2_HEADER) +
                SMB2_MAX_NORMAL_BUFFER_SIZE +
                COM2_SESSION_SETUP_RESP_STRUCT_SIZE;
            break;

        case COM2_LOGOFF:
            responseSize = sizeof(SMB2_HEADER) +
                COM2_LOGOFF_RESP_STRUCT_SIZE;
            break;

        case COM2_TREE_CONNECT:
            responseSize = sizeof(SMB2_HEADER) +
                COM2_TREE_CONNECT_RESP_STRUCT_SIZE;
            break;

        case COM2_TREE_DISCONNECT:
            responseSize = sizeof(SMB2_HEADER) +
                COM2_TREE_DISCONNECT_RESP_STRUCT_SIZE;
            break;

        case COM2_CREATE:
            // Create context buffer length is 4 bytes but we will limit to 64k
            responseSize = sizeof(SMB2_HEADER) +
                SMB2_MAX_NORMAL_BUFFER_SIZE +
                COM2_CREATE_RESP_STRUCT_SIZE;
            break;

        case COM2_CLOSE:
            responseSize = sizeof(SMB2_HEADER) + COM2_CLOSE_RESP_STRUCT_SIZE;
            break;

        case COM2_FLUSH:
            responseSize = sizeof(SMB2_HEADER) + COM2_FLUSH_RESP_STRUCT_SIZE;
            break;

        case COM2_READ:
            {
                PSMB2_READ_REQUEST_HEADER readHeader = NULL;

                status = SMB2UnmarshalReadRequest(Message, &readHeader);
                BAIL_ON_NT_STATUS(status);

                if (readHeader->ulDataLength > MaxLargeMtuBufferSize)
                {
                    status = STATUS_INVALID_PARAMETER;
                    BAIL_ON_NT_STATUS(status);
                }

                Message->MultiCreditBufferLength = readHeader->ulDataLength;

                responseSize = sizeof(SMB2_HEADER) +
                    readHeader->ulDataLength +
                    COM2_READ_RESP_STRUCT_SIZE;
            }
            break;

        case COM2_WRITE:
            {
                PSMB2_WRITE_REQUEST_HEADER writeHeader = NULL;
                PBYTE writeBuffer = NULL;

                status = SMB2UnmarshalWriteRequest(Message, &writeHeader, &writeBuffer);
                BAIL_ON_NT_STATUS(status);

                if (writeHeader->ulDataLength > MaxLargeMtuBufferSize)
                {
                    status = STATUS_INVALID_PARAMETER;
                    BAIL_ON_NT_STATUS(status);
                }

                Message->MultiCreditBufferLength = writeHeader->ulDataLength;

                responseSize = sizeof(SMB2_HEADER) + COM2_WRITE_RESP_STRUCT_SIZE;
            }
            break;

        case COM2_LOCK:
            responseSize = sizeof(SMB2_HEADER) + COM2_LOCK_RESP_STRUCT_SIZE;
            break;

        case COM2_IOCTL:
            {
                PSMB2_IOCTL_REQUEST_HEADER ioctlHeader = NULL;
                PBYTE dataBuffer = NULL;

                status = SMB2UnmarshalIOCTLRequest(
                             Message,
                             &ioctlHeader,
                             &dataBuffer);
                BAIL_ON_NT_STATUS(status);

                Message->MultiCreditBufferLength = LW_MAX(
                    (ioctlHeader->ulInLength + ioctlHeader->ulOutLength),
                    (ioctlHeader->ulMaxOutLength + ioctlHeader->ulOutLength));

                if (Message->MultiCreditBufferLength > MaxLargeMtuBufferSize)
                {
                    status = STATUS_INVALID_PARAMETER;
                    BAIL_ON_NT_STATUS(status);
                }

                // Limit maxOutputBuffer size to 64k
                responseSize = sizeof(SMB2_HEADER) +
                    ioctlHeader->ulMaxOutLength +
                    COM2_IOCTL_RESP_STRUCT_SIZE;
            }
            break;

        case COM2_CANCEL:
            // No response sent in response to a cancel request
            responseSize = COM2_CANCEL_RESP_STRUCT_SIZE;
            break;

        case COM2_ECHO:
            responseSize = sizeof(SMB2_HEADER) + COM2_ECHO_RESP_STRUCT_SIZE;
            break;

        case COM2_FIND:
            {
                PSMB2_FIND_REQUEST_HEADER findHeader = NULL;
                UNICODE_STRING fileName = { 0 };

                status = SMB2UnmarshalFindRequest(Message, &findHeader, &fileName);
                BAIL_ON_NT_STATUS(status);

                if (findHeader->ulOutBufferLength > MaxLargeMtuBufferSize)
                {
                    status = STATUS_INVALID_PARAMETER;
                    BAIL_ON_NT_STATUS(status);
                }

                Message->MultiCreditBufferLength = findHeader->ulOutBufferLength;

                responseSize = sizeof(SMB2_HEADER) +
                    findHeader->ulOutBufferLength +
                    COM2_FIND_RESP_STRUCT_SIZE;
            }
            break;

        case COM2_NOTIFY:
            {
                PSMB2_NOTIFY_CHANGE_HEADER notifyHeader = NULL;

                status = SMB2UnmarshalNotifyRequest(Message, &notifyHeader);
                BAIL_ON_NT_STATUS(status);

                if (notifyHeader->ulOutputBufferLength > MaxLargeMtuBufferSize)
                {
                    status = STATUS_INVALID_PARAMETER;
                    BAIL_ON_NT_STATUS(status);
                }

                Message->MultiCreditBufferLength = notifyHeader->ulOutputBufferLength;

                responseSize = sizeof(SMB2_HEADER) +
                    notifyHeader->ulOutputBufferLength +
                    COM2_NOTIFY_RESP_STRUCT_SIZE;
            }
            break;

        case COM2_GETINFO:
            {
                PSMB2_GET_INFO_REQUEST_HEADER getInfoHeader = NULL;
                PBYTE inputBuffer = NULL;
                ULONG inputBufferLength = 0;

                status = SMB2UnmarshalGetInfoRequest(
                             Message,
                             &getInfoHeader,
                             &inputBuffer,
                             &inputBufferLength);
                BAIL_ON_NT_STATUS(status);

                if (getInfoHeader->ulOutputBufferLen > MaxLargeMtuBufferSize)
                {
                    status = STATUS_INVALID_PARAMETER;
                    BAIL_ON_NT_STATUS(status);
                }

                Message->MultiCreditBufferLength = getInfoHeader->ulOutputBufferLen;

                responseSize = sizeof(SMB2_HEADER) +
                    getInfoHeader->ulOutputBufferLen +
                    COM2_GETINFO_RESP_STRUCT_SIZE;
            }
            break;

        case COM2_SETINFO:
            {
                PSMB2_SET_INFO_REQUEST_HEADER setInfoHeader = NULL;
                PBYTE inputBuffer = NULL;

                status = SMB2UnmarshalSetInfoRequest(
                             Message,
                             &setInfoHeader,
                             &inputBuffer);
                BAIL_ON_NT_STATUS(status);

                if (setInfoHeader->ulInputBufferLen > MaxLargeMtuBufferSize)
                {
                    status = STATUS_INVALID_PARAMETER;
                    BAIL_ON_NT_STATUS(status);
                }

                Message->MultiCreditBufferLength = setInfoHeader->ulInputBufferLen;

                responseSize = sizeof(SMB2_HEADER) + COM2_SETINFO_RESP_STRUCT_SIZE;
            }
            break;

        case COM2_BREAK:
            // Use the lease break NOTIFY response since it is larger
            responseSize = sizeof(SMB2_HEADER) + COM2_BREAK_LEASE_NOTIFY_STRUCT_SIZE;
            break;

        default:
            status = STATUS_INVALID_PARAMETER;
            BAIL_ON_NT_STATUS(status);
            break;
    }

    // Make sure there is enough space for an error response.
    responseSize = LW_MAX(responseSize, SMB_PACKET_ERROR_RESPONSE_SIZE_SMB_V2);

error:

    if (!NT_SUCCESS(status))
    {
        // Don't bail but allocate enough space for an error response.
        // Let the requests specific handler deal with failing.
        responseSize = SMB_PACKET_ERROR_RESPONSE_SIZE_SMB_V2;
    }

    responseSize = LW_ALIGN_UP(responseSize, 8);

    *ResponseSizeHint = responseSize;

    return;
}

////////////////////////////////////////////////////////////////////////

static
NTSTATUS
SrvValidateCreditCharge(
    IN PLWIO_SRV_CONNECTION pConnection,
    IN PSRV_MESSAGE_SMB_V2 pMessage
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    USHORT calculatedCredits = 1;
    ULONG bufferLength = 0;
    ULONG creditChunkSize = 64 * 1024;

    // Dialects prior to SMB 2.1 do not transmit a credit charge.  The
    // largest command is 64K and thus all commands consume exactly 1 credit.
    pMessage->CreditCharge = 1;

    if (pConnection->Dialect >= SMB_PROTOCOL_DIALECT_SMB_2_1)
    {
        // Tollerate clients that transmit a credit charge of
        // 0 when they should be sending 1.
        pMessage->CreditCharge = LW_MAX(pMessage->pHeader->CreditCharge, 1);

        // Only do the check if we have previously recorded a buffer length
        // to calculate the charge against.  This would have been done in
        // SrvCalculateResponseMessageSize_SMB_V2()
        if (pMessage->MultiCreditBufferLength != 0)
        {
            // Round the buffer size up to the next 64k boundary
            bufferLength = LW_ALIGN_UP(
                                pMessage->MultiCreditBufferLength,
                                creditChunkSize);

            calculatedCredits = bufferLength / creditChunkSize;

            if (calculatedCredits > pMessage->CreditCharge)
            {
                status = STATUS_INVALID_PARAMETER;
                BAIL_ON_NT_STATUS(status);
            }
        }
    }

error:

    return status;
}
