/* -*- 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:
 *
 *        create.c
 *
 * Abstract:
 *
 *        Likewise IO (LWIO) - SRV
 *
 *        Protocols API - SMBV2
 *
 *        Create
 *
 * Authors: Krishna Ganugapati (kganugapati@likewise.com)
 *          Sriram Nambakam (snambakam@likewise.com)
 *
 *
 */

#include "includes.h"
#include <lw/swab.h>

static
VOID
SrvLogCreateParams_SMB_V2(
    PSRV_LOG_CONTEXT pLogContext,
    LWIO_LOG_LEVEL   logLevel,
    PCSTR            pszFunction,
    PCSTR            pszFile,
    ULONG            ulLine,
    ...
    );

static
NTSTATUS
SrvBuildCreateState_SMB_V2(
    PSRV_EXEC_CONTEXT           pExecContext,
    PSMB2_CREATE_REQUEST_HEADER pRequestHeader,
    PUNICODE_STRING             pwszFilename,
    PSRV_CREATE_CONTEXT*        ppCreateContexts,
    ULONG                       ulNumContexts,
    PSRV_CREATE_STATE_SMB_V2*   ppCreateState
    );

static inline
BOOLEAN
SrvIsDurableCreate(
    PSRV_CREATE_STATE_SMB_V2 pCreateState
    );

static inline
BOOLEAN
SrvIsDurableReconnect(
    PSRV_CREATE_STATE_SMB_V2 pCreateState
    );

static
NTSTATUS
SrvCreateDurableEntry(
    PSRV_CREATE_STATE_SMB_V2 pCreateState
    );

static
NTSTATUS
SrvQueryFileInformation_SMB_V2(
    PSRV_EXEC_CONTEXT pExecContext
    );

static
NTSTATUS
SrvProcessCreateContexts_SMB_V2(
    PSRV_EXEC_CONTEXT pExecContext
    );

static
NTSTATUS
SrvRequestCreateOplocks_SMB_V2(
    PSRV_EXEC_CONTEXT pExecContext
    );

static
VOID
SrvFreeCreateStateHandle_SMB_V2(
    HANDLE hState
    );

static
VOID
SrvFreeCreateState_SMB_V2(
    PSRV_CREATE_STATE_SMB_V2 pCreateState
    );

static
NTSTATUS
SrvBuildCreateResponse_SMB_V2(
    PSRV_EXEC_CONTEXT pExecContext
    );

static
NTSTATUS
SrvWriteCreateContext(
    PBYTE                 pOutBuffer,
    ULONG                 ulOffset,
    ULONG                 ulBytesAvailable,
    PBYTE                 pName,
    USHORT                usNameSize,
    PBYTE                 pData,
    ULONG                 ulDataSize,
    PULONG                pulAlignBytesUsed,
    PULONG                pulCCBytesUsed,
    PSMB2_CREATE_CONTEXT* ppCreateContext
    );

NTSTATUS
SrvProcessCreate_SMB_V2(
    PSRV_EXEC_CONTEXT pExecContext
    )
{
    NTSTATUS                   ntStatus = STATUS_SUCCESS;
    PLWIO_SRV_CONNECTION       pConnection = pExecContext->pConnection;
    PSRV_EXEC_CONTEXT_SMB_V2   pCtxSmb2 = pExecContext->pProtocolContext;
    PLWIO_SRV_SESSION_2        pSession = NULL;
    PLWIO_SRV_TREE_2           pTree = NULL;
    PSRV_CREATE_STATE_SMB_V2   pCreateState = NULL;
    PLWIO_ASYNC_STATE          pAsyncState  = NULL;
    PSRV_CREATE_CONTEXT        pCreateContexts = NULL;
    ULONG                      ulNumContexts = 0;

    pCreateState = pCtxSmb2->hState;
    if (pCreateState)
    {
        ntStatus = SRV_SCHEDULE_IF(
                        pExecContext,
                        pCreateState->hFile || pCreateState->pFile);
        BAIL_ON_NT_STATUS(ntStatus);
    }
    else
    {
        ULONG               iMsg        = pCtxSmb2->iMsg;
        PSRV_MESSAGE_SMB_V2 pSmbRequest = &pCtxSmb2->pRequests[iMsg];
        PSMB2_CREATE_REQUEST_HEADER pCreateRequestHeader = NULL;// Do not free
        UNICODE_STRING              wszFileName = {0};          // Do not free
        wchar16_t                   wszEmpty[] = {0};

        if (pCtxSmb2->pFile)
        {
            ntStatus = STATUS_INVALID_PARAMETER;
            BAIL_ON_NT_STATUS(ntStatus);
        }

        ntStatus = SrvConnection2FindSession_SMB_V2(
                        pCtxSmb2,
                        pConnection,
                        pSmbRequest->pHeader->ullSessionId,
                        &pSession);
        BAIL_ON_NT_STATUS(ntStatus);

        ntStatus = SrvSetStatSession2Info(pExecContext, pSession);
        BAIL_ON_NT_STATUS(ntStatus);

        ntStatus = SrvSession2FindTree_SMB_V2(
                        pCtxSmb2,
                        pSession,
                        pSmbRequest->pHeader->ulTid,
                        &pTree);
        BAIL_ON_NT_STATUS(ntStatus);

        ntStatus = SMB2UnmarshalCreateRequest(
                        pSmbRequest,
                        &pCreateRequestHeader,
                        &wszFileName,
                        &pCreateContexts,
                        &ulNumContexts);
        BAIL_ON_NT_STATUS(ntStatus);

        if (!wszFileName.Length)
        {
            wszFileName.Buffer = &wszEmpty[0];
            wszFileName.Length = 0;
            wszFileName.MaximumLength = sizeof(wszEmpty);
        }

        SRV_LOG_CALL_DEBUG(
                pExecContext->pLogContext,
                SMB_PROTOCOL_VERSION_2,
                pSmbRequest->pHeader->command,
                &SrvLogCreateParams_SMB_V2,
                pCreateRequestHeader,
                &wszFileName,
                &ulNumContexts);

        ntStatus = SrvConnection2CreateAsyncState(
                                pConnection,
                                COM2_CREATE,
                                pSmbRequest->pHeader->ullCommandSequence,
                                &SrvCancelExecContextHandle,
                                &SrvReleaseExecContextHandle,
                                &pAsyncState);
        BAIL_ON_NT_STATUS(ntStatus);

        pExecContext->ullAsyncId = pAsyncState->ullAsyncId;

        ntStatus = SrvBuildCreateState_SMB_V2(
                        pExecContext,
                        pCreateRequestHeader,
                        &wszFileName,
                        &pCreateContexts,
                        ulNumContexts,
                        &pCreateState);
        BAIL_ON_NT_STATUS(ntStatus);

        pCtxSmb2->hState = pCreateState;
        pCtxSmb2->pfnStateRelease = &SrvFreeCreateStateHandle_SMB_V2;

        pAsyncState->hAsyncState = SrvAcquireExecContext(pExecContext);
    }

    switch (pCreateState->stage)
    {
        case SRV_CREATE_STAGE_SMB_V2_INITIAL:

            pCreateState->stage = SRV_CREATE_STAGE_SMB_V2_CREATE_FILE_COMPLETED;

            SrvTimedInterimResponse_SMB_V2(
                pExecContext,
                SRV_SMB2_INTERIM_RESPONSE_TIMEOUT);
            // Any error already logged so ignore

            // Only perform create when not reconnecting durable handle
            if (!SrvIsDurableReconnect(pCreateState))
            {
                SrvPrepareExecContextAsync(pExecContext);

                ntStatus = SrvIoCreateFile(
                    pExecContext,
                    pCreateState->pTree->pShareInfo,
                    &pCreateState->hFile,
                    pExecContext->pAcb,
                    &pExecContext->ioStatusBlock,
                    pSession->pIoSecurityContext,
                    pCreateState->pFilename,
                    pCreateState->pSecurityDescriptor,
                    pCreateState->pSecurityQOS,
                    pCreateState->pRequestHeader->ulDesiredAccess,
                    0LL,
                    pCreateState->pRequestHeader->ulFileAttributes,
                    pCreateState->pRequestHeader->ulShareAccess,
                    pCreateState->pRequestHeader->ulCreateDisposition,
                    pCreateState->pRequestHeader->ulCreateOptions,
                    (pCreateState->pExtAContext ?
                        pCreateState->pExtAContext->pData : NULL),
                    (pCreateState->pExtAContext ?
                        pCreateState->pExtAContext->ulDataLength : 0),
                    &pCreateState->SnapshotTime,
                    &pCreateState->pEcpList);
                BAIL_ON_NT_STATUS(ntStatus);

                SrvReleaseExecContextAsync(pExecContext);
            }

            // intentional fall through

        case SRV_CREATE_STAGE_SMB_V2_CREATE_FILE_COMPLETED:

            ntStatus = pExecContext->ioStatusBlock.Status;
            BAIL_ON_NT_STATUS(ntStatus);

            pCreateState->ulCreateAction =
                pExecContext->ioStatusBlock.CreateResult;

            ntStatus = SrvTree2CreateFile(
                           pCreateState->pTree,
                           pCreateState->pwszFilename,
                           &pCreateState->hFile,
                           &pCreateState->pFilename,
                           pCreateState->pRequestHeader->ulDesiredAccess,
                           0,
                           pCreateState->pRequestHeader->ulFileAttributes,
                           pCreateState->pRequestHeader->ulShareAccess,
                           pCreateState->pRequestHeader->ulCreateDisposition,
                           pCreateState->pRequestHeader->ulCreateOptions,
                           pCreateState->SnapshotTime,
                           &pCreateState->pFile);
            BAIL_ON_NT_STATUS(ntStatus);

            if (SrvIsDurableReconnect(pCreateState))
            {
                // Reconnect file
                ntStatus = SrvSmb2DurableReconnect(
                    SrvGetDurableHandleTable(),
                    pCreateState->ReconnectId,
                    IoSecurityGetAccessToken(
                        pCreateState->pTree->pSession->pIoSecurityContext),
                    pCreateState->pFile,
                    pCreateState->pLeaseContext ?
                    &pCreateState->pLeaseContext->Key : NULL);
                BAIL_ON_NT_STATUS(ntStatus);

                // Fid was already generated, just override the persistent id
                pCreateState->pFile->fid.ullPersistentId =
                    pCreateState->ReconnectId;
                pCreateState->pFile->bIsDurable = TRUE;
                pCreateState->ulCreateAction = FILE_OPENED;
                SrvOplockGetLevel_SMB_V2(
                    pCreateState->pFile,
                    &pCreateState->ucOplockLevel,
                    NULL);

                if (pCreateState->ucOplockLevel == SMB2_OPLOCK_LEVEL_LEASE)
                {
                    BOOLEAN bFileLocked = FALSE;
                    PSRV_OPLOCK_STATE_SMB_V2 pOplockState = NULL;

                    LWIO_LOCK_RWMUTEX_SHARED(
                        bFileLocked,
                        &pCreateState->pFile->mutex);

                    pOplockState = pCreateState->pFile->hOplock;

                    LwRtlCopyMemory(
                        &pCreateState->LeaseContext,
                        &pOplockState->LeaseContext,
                        sizeof(pCreateState->LeaseContext));

                    LWIO_UNLOCK_RWMUTEX(
                        bFileLocked,
                        &pCreateState->pFile->mutex);

                    pCreateState->pLeaseContext = &pCreateState->LeaseContext;
                }
            }
            else if (SrvIsDurableCreate(pCreateState))
            {
                pCreateState->pFile->OpenState.DurableTimeout =
                    gProtocolGlobals_SMB_V2.config.DurableHandleTimeout;
            }

            pCreateState->stage = SRV_CREATE_STAGE_SMB_V2_ATTEMPT_QUERY_INFO;

            // intentional fall through

        case SRV_CREATE_STAGE_SMB_V2_ATTEMPT_QUERY_INFO:

            ntStatus = SrvQueryFileInformation_SMB_V2(pExecContext);
            BAIL_ON_NT_STATUS(ntStatus);

            pCreateState->stage = SRV_CREATE_STAGE_SMB_V2_QUERY_CREATE_CONTEXTS;

            // intentional fall through

        case SRV_CREATE_STAGE_SMB_V2_QUERY_CREATE_CONTEXTS:

            ntStatus = SrvProcessCreateContexts_SMB_V2(pExecContext);
            BAIL_ON_NT_STATUS(ntStatus);

            pCreateState->stage = SRV_CREATE_STAGE_SMB_V2_REQUEST_OPLOCK;

            // intentional fall through

        case SRV_CREATE_STAGE_SMB_V2_REQUEST_OPLOCK:

            if (!SrvIsDurableReconnect(pCreateState))
            {
                ntStatus = SrvRequestCreateOplocks_SMB_V2(pExecContext);
            }
            else
            {
                ntStatus = STATUS_SUCCESS;
            }
            // Don't fail if the op-lock cannot be granted

            pCreateState->stage = SRV_CREATE_STAGE_SMB_V2_QUERY_INFO_COMPLETED;

            // intentional fall through

        case SRV_CREATE_STAGE_SMB_V2_QUERY_INFO_COMPLETED:

            ntStatus = pExecContext->ioStatusBlock.Status;
            BAIL_ON_NT_STATUS(ntStatus);

            if (SrvIsDurableCreate(pCreateState) &&
                ((pCreateState->ucOplockLevel == SMB2_OPLOCK_LEVEL_BATCH) ||
                 ((pCreateState->ucOplockLevel == SMB2_OPLOCK_LEVEL_LEASE) &&
                  IsSetFlag(
                    pCreateState->pLeaseContext->State,
                    SMB2_LEASE_HANDLE_CACHING))))
            {
                // Can only grant a durable handle when granted a BATCH oplock
                // or  a HANDLE caching lease
                ntStatus = SrvCreateDurableEntry(pCreateState);
                BAIL_ON_NT_STATUS(ntStatus);
            }

            ntStatus = SrvBuildCreateResponse_SMB_V2(pExecContext);
            BAIL_ON_NT_STATUS(ntStatus);

            ntStatus = SrvElementsRegisterResource(
                            &pCreateState->pFile->resource,
                            NULL);
            BAIL_ON_NT_STATUS(ntStatus);

            pCreateState->stage = SRV_CREATE_STAGE_SMB_V2_DONE;

            // intentional fall through

        case SRV_CREATE_STAGE_SMB_V2_DONE:

            if (pCreateState->pRequestHeader->ulDesiredAccess & FILE_READ_DATA)
            {
                pCreateState->pFile->ulPermissions |= SRV_PERM_FILE_READ;
            }
            if (pCreateState->pRequestHeader->ulDesiredAccess & FILE_WRITE_DATA)
            {
                pCreateState->pFile->ulPermissions |= SRV_PERM_FILE_WRITE;
            }
            if (pCreateState->ulCreateAction == FILE_CREATED)
            {
                pCreateState->pFile->ulPermissions |= SRV_PERM_FILE_CREATE;
            }

            // transfer file so we do not run it down
            pCtxSmb2->pFile = pCreateState->pFile;
            pCreateState->pFile = NULL;

            break;
    }

cleanup:

    if (STATUS_PENDING != ntStatus)
    {
        pCtxSmb2->FileOpenStatus = ntStatus;
    }

    if (pTree)
    {
        SrvTree2Release(pTree);
    }

    if (pSession)
    {
        SrvSession2Release(pSession);
    }

    if (pAsyncState)
    {
        SrvAsyncStateRelease(pAsyncState);
    }

    SRV_SAFE_FREE_MEMORY(pCreateContexts);

    return ntStatus;

error:

    switch (ntStatus)
    {
        case STATUS_PENDING:

            // TODO: Add an indicator to the file object to trigger a
            //       cleanup if the connection gets closed and all the
            //       files involved have to be closed

            break;

        default:

            SrvReleaseExecContextAsync(pExecContext);

            break;
    }

    goto cleanup;
}

static
VOID
SrvLogCreateParams_SMB_V2(
    PSRV_LOG_CONTEXT pLogContext,
    LWIO_LOG_LEVEL   logLevel,
    PCSTR            pszFunction,
    PCSTR            pszFile,
    ULONG            ulLine,
    ...
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PSMB2_CREATE_REQUEST_HEADER pCreateRequestHeader = NULL;
    PUNICODE_STRING             pwszFileName = NULL;
    PULONG                      pulNumContexts = NULL;
    PWSTR pwszFilename1 = NULL;
    PSTR  pszFilename   = NULL;
    va_list msgList;

    va_start(msgList, ulLine);

    pCreateRequestHeader = va_arg(msgList, PSMB2_CREATE_REQUEST_HEADER);
    pwszFileName = va_arg(msgList, PUNICODE_STRING);
    pulNumContexts = va_arg(msgList, PULONG);

    if (pwszFileName->Length)
    {
        ntStatus = SrvAllocateMemory(
                        pwszFileName->Length + sizeof(wchar16_t),
                        (PVOID*)&pwszFilename1);
        BAIL_ON_NT_STATUS(ntStatus);

        memcpy( (PBYTE)pwszFilename1,
                (PBYTE)pwszFileName->Buffer,
                pwszFileName->Length);

        ntStatus = SrvWc16sToMbs(pwszFilename1, &pszFilename);
        BAIL_ON_NT_STATUS(ntStatus);
    }

    LW_RTL_LOG_RAW(
        logLevel,
        "srv",
        pszFunction,
        pszFile,
        ulLine,
        "Create params: "
        "create-disp(0x%x),desired-access(0x%x),num-contexts(%u),"
        "create-options(0x%x),share-access(0x%x),flags(0x%" PRIx64 "),"
        "security-flags(0x%x),oplock-level(%u),file-name(%s)",
        pCreateRequestHeader->ulCreateDisposition,
        pCreateRequestHeader->ulDesiredAccess,
        *pulNumContexts,
        pCreateRequestHeader->ulCreateOptions,
        pCreateRequestHeader->ulShareAccess,
        pCreateRequestHeader->ullCreateFlags,
        pCreateRequestHeader->ucSecurityFlags,
        pCreateRequestHeader->ucOplockLevel,
        LWIO_SAFE_LOG_STRING(pszFilename));

error:

    va_end(msgList);

    SRV_SAFE_FREE_MEMORY(pwszFilename1);
    SRV_SAFE_FREE_MEMORY(pszFilename);

    return;
}

static
NTSTATUS
SrvBuildCreateState_SMB_V2(
    PSRV_EXEC_CONTEXT           pExecContext,
    PSMB2_CREATE_REQUEST_HEADER pRequestHeader,
    PUNICODE_STRING             pwszFilename,
    PSRV_CREATE_CONTEXT*        ppCreateContexts,
    ULONG                       ulNumContexts,
    PSRV_CREATE_STATE_SMB_V2*   ppCreateState
    )
{
    NTSTATUS                   ntStatus       = STATUS_SUCCESS;
    PLWIO_SRV_CONNECTION       pConnection    = pExecContext->pConnection;
    PSRV_EXEC_CONTEXT_SMB_V2   pCtxSmb2       = pExecContext->pProtocolContext;
    PSRV_CREATE_STATE_SMB_V2   pCreateState   = NULL;
    ULONG                      iCtx       = 0;
    wchar16_t wszBackSlash[]              = { '\\', 0};
    wchar16_t wszForwardSlash[]           = { '/' , 0};

    if (*pwszFilename->Buffer == wszBackSlash[0])
    {
        ntStatus = STATUS_INVALID_PARAMETER;
        BAIL_ON_NT_STATUS(ntStatus);
    }

    if (*pwszFilename->Buffer == wszForwardSlash[0])
    {
        ntStatus = STATUS_OBJECT_NAME_INVALID;
        BAIL_ON_NT_STATUS(ntStatus);
    }

    ntStatus = SrvAllocateMemory(
                    sizeof(SRV_CREATE_STATE_SMB_V2),
                    (PVOID*)&pCreateState);
    BAIL_ON_NT_STATUS(ntStatus);

    pCreateState->stage = SRV_CREATE_STAGE_SMB_V2_INITIAL;

    pCreateState->ReconnectId = (ULONG64) -1;

    ntStatus = SrvAllocateMemory(
                    pwszFilename->Length + sizeof(wchar16_t),
                    (PVOID*)&pCreateState->pwszFilename);
    BAIL_ON_NT_STATUS(ntStatus);

    if (pwszFilename->Length)
    {
        memcpy((PBYTE)pCreateState->pwszFilename,
               (PBYTE)pwszFilename->Buffer,
               pwszFilename->Length);
    }

    // TODO: Handle root fids
    ntStatus = SrvAllocateMemory(
                    sizeof(IO_FILE_NAME),
                    (PVOID*)&pCreateState->pFilename);
    BAIL_ON_NT_STATUS(ntStatus);

    pCreateState->pTree = SrvTree2Acquire(pCtxSmb2->pTree);

    ntStatus = SrvBuildTreeRelativePath_SMB_V2(
                    pCtxSmb2->pTree,
                    pCreateState->pwszFilename,
                    pCreateState->pFilename);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = IoRtlEcpListAllocate(&pCreateState->pEcpList);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = IoRtlEcpListInsert(pCreateState->pEcpList,
                                  IO_ECP_TYPE_NET_OPEN_INFO,
                                  &pCreateState->networkOpenInfo,
                                  sizeof(pCreateState->networkOpenInfo),
                                  NULL);
    BAIL_ON_NT_STATUS(ntStatus);

    /* For named pipes, we need to pipe some extra data into the npfs driver:
     *  - Session key
     *  - Client principal name
     *  - Client address
     */
    if (SrvTree2IsNamedPipe(pCtxSmb2->pTree))
    {
        ntStatus = SrvSession2GetNamedPipeSessionKey(
                       pCtxSmb2->pSession,
                       pCreateState->pEcpList);
        BAIL_ON_NT_STATUS(ntStatus);

        ntStatus = SrvConnectionGetNamedPipeClientAddress(
                       pConnection,
                       pCreateState->pEcpList);
        BAIL_ON_NT_STATUS(ntStatus);

        ntStatus = IoRtlEcpListInsert(pCreateState->pEcpList,
                                      IO_ECP_TYPE_PIPE_INFO,
                                      &pCreateState->filePipeInfo,
                                      sizeof(pCreateState->filePipeInfo),
                                      NULL);
        BAIL_ON_NT_STATUS(ntStatus);

        ntStatus = IoRtlEcpListInsert(pCreateState->pEcpList,
                                      IO_ECP_TYPE_PIPE_LOCAL_INFO,
                                      &pCreateState->filePipeLocalInfo,
                                      sizeof(pCreateState->filePipeLocalInfo),
                                      NULL);
        BAIL_ON_NT_STATUS(ntStatus);
    }

    /* Enable ABE (but only we if know this is not a file access) */

    if ((pCreateState->pTree->pShareInfo->Flags & SHARE_INFO_FLAG_ABE_ENABLED) &&
        !IsSetFlag(pRequestHeader->ulCreateOptions, FILE_NON_DIRECTORY_FILE))
    {
        if (!pCreateState->pEcpList)
        {
            ntStatus = IoRtlEcpListAllocate(&pCreateState->pEcpList);
            BAIL_ON_NT_STATUS(ntStatus);
        }

        ntStatus = SrvIoPrepareAbeEcpList(pCreateState->pEcpList);
        BAIL_ON_NT_STATUS(ntStatus);
    }

    pCreateState->pCreateContexts = *ppCreateContexts;
    *ppCreateContexts = NULL;

    pCreateState->ulNumContexts   = ulNumContexts;

    for (iCtx = 0; iCtx < pCreateState->ulNumContexts; iCtx++)
    {
        PSRV_CREATE_CONTEXT pContext = &pCreateState->pCreateContexts[iCtx];

        switch (pContext->contextItemType)
        {
            case SMB2_CONTEXT_ITEM_TYPE_EXT_ATTRS:
                pCreateState->pExtAContext = pContext;
                break;

            case SMB2_CONTEXT_ITEM_TYPE_MAX_ACCESS:

                if (!pCreateState->pEcpList)
                {
                    ntStatus = IoRtlEcpListAllocate(&pCreateState->pEcpList);
                    BAIL_ON_NT_STATUS(ntStatus);
                }

                ntStatus = IoRtlEcpListInsert(
                                pCreateState->pEcpList,
                                IO_ECP_TYPE_MAX_ACCESS,
                                &pCreateState->ulMaximalAccessMask,
                                sizeof(pCreateState->ulMaximalAccessMask),
                                NULL);
                BAIL_ON_NT_STATUS(ntStatus);

                break;

            case SMB2_CONTEXT_ITEM_TYPE_SEC_DESC:
                {
                    SECURITY_INFORMATION secInfoAll = 0;

                    if (!pContext->ulDataLength ||
                        !RtlValidRelativeSecurityDescriptor(
                                (PSECURITY_DESCRIPTOR_RELATIVE)pContext->pData,
                                pContext->ulDataLength,
                                secInfoAll))
                    {
                        ntStatus = STATUS_INVALID_PARAMETER;
                        BAIL_ON_NT_STATUS(ntStatus);
                    }

                    pCreateState->pSecurityDescriptor =
                            (PSECURITY_DESCRIPTOR_RELATIVE)pContext->pData;
                }

                break;

            case SMB2_CONTEXT_ITEM_TYPE_DURABLE_HANDLE:

                // We should ignore this if the client did not request a BATCH
                // oplock or a HANDLE caching lease.  This will be enforced
                // when we attempt to create the durable handle entry (after
                // the oplock request has been granted).  Otherwise we have an
                // ordering problem if the DURABLE context is processed here
                // before the LEASE context.

                pCreateState->bDurable = TRUE;
                pCreateState->ReconnectId = (ULONG64) -1;

                break;

            case SMB2_CONTEXT_ITEM_TYPE_DURABLE_HANDLE_RECONNECT:

                if (pContext->ulDataLength < sizeof(ULONG64))
                {
                    ntStatus = STATUS_INVALID_PARAMETER;
                    BAIL_ON_NT_STATUS(ntStatus);
                }

                // Verification of matching LeaseKey is done as part of the
                // reconnection in SrvProcessCreate_SMB_V2

                pCreateState->ReconnectId =
                    SMB_LTOH64(*(PULONG64) pContext->pData);

                if (pCreateState->ReconnectId == (ULONG64) -1)
                {
                    ntStatus = STATUS_INVALID_PARAMETER;
                    BAIL_ON_NT_STATUS(ntStatus);
                }

                pCreateState->bDurable = TRUE;

                break;

            case SMB2_CONTEXT_ITEM_TYPE_LEASE:
                // Only parse lease request if
                // (a) we negotiated SMB2.1,
                // (b) ONE of the following
                //     i. the requested oplock level is for a lease, OR
                //     ii.  this is a durable handle reconnect request and
                //          the oplock level is NONE
                // (c) the create context size is correct

                if ((pConnection->Dialect == SMB_PROTOCOL_DIALECT_SMB_2_1) &&
                    ((pRequestHeader->ucOplockLevel == SMB2_OPLOCK_LEVEL_LEASE) ||
                     ((pRequestHeader->ucOplockLevel == SMB2_OPLOCK_LEVEL_NONE) &&
                      (pCreateState->ReconnectId != (ULONG64) -1))) &&
                    (pContext->ulDataLength == sizeof(pCreateState->LeaseContext)))
                {
                    PGUID pFsKey = NULL;

                    pRequestHeader->ucOplockLevel = SMB2_OPLOCK_LEVEL_LEASE;

                    LwRtlCopyMemory(
                        &pCreateState->LeaseContext,
                        (PSMB2_CREATE_LEASE)pContext->pData,
                        sizeof(pCreateState->LeaseContext));
                    pCreateState->pLeaseContext = &pCreateState->LeaseContext;

                    // Add new or existing FS lease key to the ECP list.
                    // This key is used during the open phase to prevent
                    // lease breaks should this client already have a lease
                    // on this file.  The key will also be remembered by
                    // the file system and used during the oplock phase.
                    ntStatus = SrvLeaseRecordFindOrAllocate(
                                    pExecContext->pConnection,
                                    &pCreateState->LeaseContext.Key,
                                    &pCreateState->pLeaseRecord);
                    BAIL_ON_NT_STATUS(ntStatus);

                    pFsKey = &pCreateState->pLeaseRecord->FileSystemLeaseKey;
                    ntStatus = IoRtlEcpListInsert(
                                    pCreateState->pEcpList,
                                    IO_ECP_TYPE_LEASE_KEY,
                                    pFsKey,
                                    sizeof(*pFsKey),
                                    NULL);
                    BAIL_ON_NT_STATUS(ntStatus);
                }
                break;

            case SMB2_CONTEXT_ITEM_TYPE_SHADOW_COPY:
                if (!pCtxSmb2->pTree->UnderstandSnapshots)
                {
                    // If a snapshot was requested, but the filesystem does not
                    // understand snapshots, we must fail the operation
                    ntStatus = STATUS_INVALID_PARAMETER;
                    BAIL_ON_NT_STATUS(ntStatus);
                }

                if (pContext->ulDataLength !=
                    sizeof(pCreateState->SnapshotTime))
                {
                    ntStatus = STATUS_INVALID_PARAMETER;
                    LWIO_LOG_ERROR(
                        "Invalid length timewarp token on create context "
                        "(expected = %u, got = %u",
                        (unsigned int) sizeof(pCreateState->SnapshotTime),
                        (unsigned int) sizeof(pContext->ulDataLength));
                    BAIL_ON_NT_STATUS(ntStatus);
                }

                // Handle alignment and little-endianness.
                // NOTE: Alignment would not need to be handled here
                // if the ECP unmarshal code verified alignment (since
                // our buffers are worst-case alignment).
                memcpy(&pCreateState->SnapshotTime,
                       pContext->pData,
                       sizeof(pCreateState->SnapshotTime));
                pCreateState->SnapshotTime =
                        LW_LTOH64(pCreateState->SnapshotTime);

                break;

            default:

                break;
        }
    }

    pCreateState->pRequestHeader = pRequestHeader;

    *ppCreateState = pCreateState;

cleanup:

    return ntStatus;

error:

    *ppCreateState = NULL;

    if (pCreateState)
    {
        SrvFreeCreateState_SMB_V2(pCreateState);
    }

    goto cleanup;
}

static
NTSTATUS
SrvQueryFileInformation_SMB_V2(
    PSRV_EXEC_CONTEXT pExecContext
    )
{
    NTSTATUS                 ntStatus     = 0;
    PSRV_EXEC_CONTEXT_SMB_V2 pCtxSmb2     = pExecContext->pProtocolContext;
    PSRV_CREATE_STATE_SMB_V2 pCreateState = NULL;

    pCreateState = pCtxSmb2->hState;

    if (!IoRtlEcpListIsAcknowledged(
            pCreateState->pEcpList,
            IO_ECP_TYPE_NET_OPEN_INFO))
    {
        if (!pCreateState->pNetworkOpenInfo)
        {
            pCreateState->pNetworkOpenInfo = &pCreateState->networkOpenInfo;

            SrvPrepareExecContextAsync(pExecContext);

            ntStatus = IoQueryInformationFile(
                            pCreateState->pFile->hFile,
                            pExecContext->pAcb,
                            &pExecContext->ioStatusBlock,
                            pCreateState->pNetworkOpenInfo,
                            sizeof(pCreateState->networkOpenInfo),
                            FileNetworkOpenInformation);
            BAIL_ON_NT_STATUS(ntStatus);

            SrvReleaseExecContextAsync(pExecContext); // completed sync
        }
    }
    else
    {
        pCreateState->pNetworkOpenInfo = &pCreateState->networkOpenInfo;
    }

    if (!(pCreateState->pNetworkOpenInfo->FileAttributes & FILE_ATTRIBUTE_DIRECTORY))
    {
        SrvFile2BlockIdleTimeout(pCreateState->pFile);
    }
    else
    {
        SrvFile2UnblockIdleTimeout(pCreateState->pFile);
    }

    if (SrvTree2IsNamedPipe(pCreateState->pTree))
    {
        if (!IoRtlEcpListIsAcknowledged(
                pCreateState->pEcpList,
                IO_ECP_TYPE_PIPE_INFO))
        {
            if (!pCreateState->pFilePipeInfo)
            {
                pCreateState->pFilePipeInfo = &pCreateState->filePipeInfo;

                SrvPrepareExecContextAsync(pExecContext);

                ntStatus = IoQueryInformationFile(
                                pCreateState->pFile->hFile,
                                pExecContext->pAcb,
                                &pExecContext->ioStatusBlock,
                                pCreateState->pFilePipeInfo,
                                sizeof(pCreateState->filePipeInfo),
                                FilePipeInformation);
                BAIL_ON_NT_STATUS(ntStatus);

                SrvReleaseExecContextAsync(pExecContext); // completed sync
            }
        }
        else
        {
            pCreateState->pFilePipeInfo = &pCreateState->filePipeInfo;
        }

        if (!IoRtlEcpListIsAcknowledged(
                pCreateState->pEcpList,
                IO_ECP_TYPE_PIPE_LOCAL_INFO))
        {
            if (!pCreateState->pFilePipeLocalInfo)
            {
                pCreateState->pFilePipeLocalInfo =
                    &pCreateState->filePipeLocalInfo;

                SrvPrepareExecContextAsync(pExecContext);

                ntStatus = IoQueryInformationFile(
                                pCreateState->pFile->hFile,
                                pExecContext->pAcb,
                                &pExecContext->ioStatusBlock,
                                pCreateState->pFilePipeLocalInfo,
                                sizeof(pCreateState->filePipeLocalInfo),
                                FilePipeLocalInformation);
                BAIL_ON_NT_STATUS(ntStatus);

                SrvReleaseExecContextAsync(pExecContext); // completed sync
            }
        }
        else
        {
            pCreateState->pFilePipeLocalInfo = &pCreateState->filePipeLocalInfo;
        }
    }

cleanup:

    return ntStatus;

error:

    goto cleanup;
}

static
NTSTATUS
SrvProcessCreateContexts_SMB_V2(
    PSRV_EXEC_CONTEXT pExecContext
    )
{
    NTSTATUS                 ntStatus     = STATUS_SUCCESS;
    PSRV_EXEC_CONTEXT_SMB_V2 pCtxSmb2     = pExecContext->pProtocolContext;
    PSRV_CREATE_STATE_SMB_V2 pCreateState = NULL;

    pCreateState = pCtxSmb2->hState;

    for (;
         pCreateState->iContext < pCreateState->ulNumContexts;
         pCreateState->iContext++)
    {
        PSRV_CREATE_CONTEXT pCreateContext =
                        &pCreateState->pCreateContexts[pCreateState->iContext];

        switch (pCreateContext->contextItemType)
        {
            case SMB2_CONTEXT_ITEM_TYPE_MAX_ACCESS:

                // The driver should have filled this in.

                break;

            case SMB2_CONTEXT_ITEM_TYPE_DURABLE_HANDLE:
            case SMB2_CONTEXT_ITEM_TYPE_QUERY_DISK_ID:
            case SMB2_CONTEXT_ITEM_TYPE_EXT_ATTRS:
            case SMB2_CONTEXT_ITEM_TYPE_SHADOW_COPY:
            default:

                // TODO:

                break;
        }
    }

    return ntStatus;
}

static SRV_OPLOCK_INFO leaseChain[] =
{
    {
        IO_OPLOCK_REQUEST_LEASE,
        SMB2_OPLOCK_LEVEL_LEASE,
        SMB2_LEASE_READ_CACHING | SMB2_LEASE_WRITE_CACHING |
        SMB2_LEASE_HANDLE_CACHING
    },
    {
        IO_OPLOCK_REQUEST_LEASE,
        SMB2_OPLOCK_LEVEL_LEASE,
        SMB2_LEASE_READ_CACHING | SMB2_LEASE_HANDLE_CACHING
    },
    {
        IO_OPLOCK_REQUEST_LEASE,
        SMB2_OPLOCK_LEVEL_LEASE,
        SMB2_LEASE_READ_CACHING | SMB2_LEASE_WRITE_CACHING
    },
    {
        IO_OPLOCK_REQUEST_LEASE,
        SMB2_OPLOCK_LEVEL_LEASE,
        SMB2_LEASE_READ_CACHING
    },
    {
        IO_OPLOCK_REQUEST_LEASE,
        SMB2_OPLOCK_LEVEL_LEASE,
        SMB2_LEASE_NONE
     }
};

static SRV_OPLOCK_INFO batchOplockChain[] =
{
    {
        IO_OPLOCK_REQUEST_OPLOCK_BATCH,
        SMB2_OPLOCK_LEVEL_BATCH,
        SMB2_LEASE_NONE
    },
    {
        IO_OPLOCK_REQUEST_OPLOCK_LEVEL_2,
        SMB2_OPLOCK_LEVEL_II,
        SMB2_LEASE_NONE
    },
    {
        IO_OPLOCK_REQUEST_NONE,
        SMB2_OPLOCK_LEVEL_NONE,
        SMB2_LEASE_NONE
    }
};

static SRV_OPLOCK_INFO exclOplockChain[] =
{
    {
        IO_OPLOCK_REQUEST_OPLOCK_LEVEL_1,
        SMB2_OPLOCK_LEVEL_I,
        SMB2_LEASE_NONE
    },
    {
        IO_OPLOCK_REQUEST_OPLOCK_LEVEL_2,
        SMB2_OPLOCK_LEVEL_II,
        SMB2_LEASE_NONE
    },
    {
        IO_OPLOCK_REQUEST_NONE,
        SMB2_OPLOCK_LEVEL_NONE,
        SMB2_LEASE_NONE
    }
};

static SRV_OPLOCK_INFO level2OplockChain[] =
{
    {
        IO_OPLOCK_REQUEST_OPLOCK_LEVEL_2,
        SMB2_OPLOCK_LEVEL_II,
        SMB2_LEASE_NONE
    },
    {
        IO_OPLOCK_REQUEST_NONE,
        SMB2_OPLOCK_LEVEL_NONE,
        SMB2_LEASE_NONE
    }
};

static SRV_OPLOCK_INFO noOplockChain[] = {
    {
        IO_OPLOCK_REQUEST_NONE,
        SMB2_OPLOCK_LEVEL_NONE,
        SMB2_LEASE_NONE
    }
};

static
NTSTATUS
SrvRequestCreateOplocks_SMB_V2(
    PSRV_EXEC_CONTEXT pExecContext
    )
{
    NTSTATUS                   ntStatus      = STATUS_SUCCESS;
    PSRV_OPLOCK_INFO           pOplockCursor = NULL;
    PSRV_EXEC_CONTEXT_SMB_V2   pCtxSmb2      = pExecContext->pProtocolContext;
    PSRV_CREATE_STATE_SMB_V2   pCreateState  = pCtxSmb2->hState;
    PSRV_OPLOCK_STATE_SMB_V2   pOplockState  = NULL;
    BOOLEAN                    bContinue     = TRUE;
    IO_LEASE_STATE currentLeaseState = IO_LEASE_STATE_NONE;

    // Optimization
    if (IsSetFlag(
            pCreateState->pNetworkOpenInfo->FileAttributes,
            FILE_ATTRIBUTE_DIRECTORY))
    {
        if (pCreateState->pRequestHeader->ucOplockLevel ==
            SMB2_OPLOCK_LEVEL_LEASE)
        {
            // Filter lease requests on directories as a basic optimization
            pCreateState->pRequestHeader->ucOplockLevel =
                SMB2_OPLOCK_LEVEL_NONE;
        }

        pOplockCursor = &noOplockChain[0];
        ntStatus = STATUS_SUCCESS;

        goto done;
    }

    // Optimization
    if (SrvTree2IsNamedPipe(pCreateState->pTree) ||
        (pCreateState->pRequestHeader->ucOplockLevel == SMB2_OPLOCK_LEVEL_NONE))
    {
        pOplockCursor = &noOplockChain[0];
        ntStatus = STATUS_SUCCESS;

        goto done;
    }

    ntStatus = SrvBuildOplockState_SMB_V2(
                    pCreateState->pFile,
                    &pOplockState);
    BAIL_ON_NT_STATUS(ntStatus);

    switch (pCreateState->pRequestHeader->ucOplockLevel)
    {
        case SMB2_OPLOCK_LEVEL_LEASE:
            if (pCreateState->pLeaseContext != NULL)
            {
                LwRtlCopyMemory(
                    &pOplockState->LeaseContext,
                    &pCreateState->LeaseContext,
                    sizeof(pOplockState->LeaseContext));
                pOplockState->pLeaseContext = &pOplockState->LeaseContext;

                ntStatus = STATUS_OPLOCK_NOT_GRANTED;

                currentLeaseState = pCreateState->pLeaseRecord->LeaseState;

                for (pOplockCursor = &leaseChain[0];
                     pOplockCursor->leaseState != SMB2_LEASE_NONE;
                     pOplockCursor++)
                {
                    if (pOplockCursor->leaseState ==
                        pOplockState->LeaseContext.State)
                    {
                        break;
                    }
                }
            }
            else
            {
                // Without a Lease Create Context we should reset the
                // request to NONE
                pOplockCursor = &noOplockChain[0];
            }

            break;

        case SMB2_OPLOCK_LEVEL_BATCH:
            pOplockCursor = &batchOplockChain[0];
            break;

        case SMB2_OPLOCK_LEVEL_I:
            pOplockCursor = &exclOplockChain[0];
            break;

        case SMB2_OPLOCK_LEVEL_II:
            pOplockCursor = &level2OplockChain[0];
            break;

        default:

            // We should never reach the default case here, since we
            // don't want to uselessly allocate an OplockState;
            // this is handled at the top of the function.

            LWIO_ASSERT(0);
    }

    while (bContinue && (pOplockCursor->oplockLevel != SMB2_OPLOCK_LEVEL_NONE))
    {
        pOplockState->oplockBuffer_in.OplockRequestType =
            pOplockCursor->oplockRequest;

        if (IO_OPLOCK_REQUEST_LEASE == pOplockCursor->oplockRequest)
        {
            pOplockState->LeaseContext.State = pOplockCursor->leaseState;

            pOplockState->oplockBuffer_in.RequestLeaseState =
                SrvLeaseMapSmb2ToIoLeaseState(pOplockState->LeaseContext.State);

            pOplockState->oplockBuffer_in.LeaseFlags = 0;

            if ((pOplockCursor->leaseState & ~currentLeaseState) == 0)
            {
                // If the current lease state is a superset of the requested
                // lease, bypass the file system request and add the open
                // directly to the existing lease.
                ntStatus = STATUS_SUCCESS;
                break;
            }
        }

        SrvPrepareOplockStateAsync_SMB_V2(pOplockState);

        ntStatus = IoFsControlFile(
                        pCreateState->pFile->hFile,
                        pOplockState->pAcb,
                        &pOplockState->ioStatusBlock,
                        IO_FSCTL_OPLOCK_REQUEST,
                        &pOplockState->oplockBuffer_in,
                        sizeof(pOplockState->oplockBuffer_in),
                        &pOplockState->oplockBuffer_out,
                        sizeof(pOplockState->oplockBuffer_out));
        switch (ntStatus)
        {
            case STATUS_OPLOCK_NOT_GRANTED:

                // completed sync
                SrvReleaseOplockStateAsync_SMB_V2(pOplockState);
                pOplockCursor++;
                break;

            case STATUS_OPLOCK_BREAK_IN_PROGRESS:

                // More work needed here to decide what to do
                SrvReleaseOplockStateAsync_SMB_V2(pOplockState);
                break;

            case STATUS_PENDING:

                if (SMB2_OPLOCK_LEVEL_LEASE == pOplockCursor->oplockLevel)
                {
                    pCreateState->pLeaseRecord->LeaseState =
                        pOplockCursor->leaseState;
                }

                ntStatus = STATUS_SUCCESS;
                bContinue = FALSE;

                break;

            default:

                // completed sync
                SrvReleaseOplockStateAsync_SMB_V2(pOplockState);

                BAIL_ON_NT_STATUS(ntStatus);

                break;
        }
    }

done:
    if (ntStatus == STATUS_SUCCESS &&
        pOplockCursor->oplockLevel != SMB2_OPLOCK_LEVEL_NONE)
    {
        SrvAcquireOplock(pOplockState);
        ntStatus = SrvFile2SetOplock(
                       pCreateState->pFile,
                       &pOplockState->Oplock);
        if (ntStatus != STATUS_SUCCESS)
        {
            SrvReleaseOplock(pOplockState);
        }
        BAIL_ON_NT_STATUS(ntStatus);

        if (SMB2_OPLOCK_LEVEL_LEASE == pOplockCursor->oplockLevel)
        {
            pCreateState->LeaseContext.State =
                pCreateState->pLeaseRecord->LeaseState;
            pCreateState->LeaseContext.Flags = 0;
            pCreateState->LeaseContext.Duration = 0;

            ntStatus = SrvLeaseRecordAddOplock(
                            pCreateState->pLeaseRecord,
                            &pOplockState->Oplock);
            BAIL_ON_NT_STATUS(ntStatus);
        }

        SrvOplockSetLevel_SMB_V2(
            pCreateState->pFile,
            pOplockCursor->oplockLevel,
            pOplockState->pLeaseContext ?
            pOplockState->pLeaseContext->State : SMB2_LEASE_NONE);
    }

    pCreateState->ucOplockLevel = pOplockCursor->oplockLevel;

error:

    if (pOplockState)
    {
        SrvReleaseOplock(pOplockState);
    }

    if (!NT_SUCCESS(ntStatus))
    {
        if (pCreateState->pRequestHeader->ucOplockLevel ==
            SMB2_OPLOCK_LEVEL_LEASE)
        {
            // No downgrade on lease state.  But you do have to set the
            // oplock level and return the create lease context in the response
            pCreateState->ucOplockLevel = SMB2_OPLOCK_LEVEL_LEASE;
            pCreateState->LeaseContext.Flags = 0;
            pCreateState->LeaseContext.Duration = 0;

            pCreateState->LeaseContext.State =
                SrvLeaseMapIoToSmb2LeaseState(currentLeaseState);
        }
    }

    return ntStatus;
}

static
VOID
SrvFreeCreateStateHandle_SMB_V2(
    HANDLE hState
    )
{
    SrvFreeCreateState_SMB_V2(hState);
}

static
VOID
SrvFreeCreateState_SMB_V2(
    PSRV_CREATE_STATE_SMB_V2 pCreateState
    )
{
    if (pCreateState->pEcpList)
    {
        IoRtlEcpListFree(&pCreateState->pEcpList);
    }

    SrvLeaseRecordRelease(&pCreateState->pLeaseRecord);

    // TODO: Free the following if set
    // pSecurityDescriptor;
    // pSecurityQOS;

    if (pCreateState->pFilename)
    {
        SRV_FREE_UNICODE_STRING(&pCreateState->pFilename->Name);
        SrvFreeMemory(pCreateState->pFilename);
    }

    if (pCreateState->hFile)
    {
        IoCloseFile(pCreateState->hFile);
    }

    SRV_SAFE_FREE_MEMORY(pCreateState->pwszFilename);
    SRV_SAFE_FREE_MEMORY(pCreateState->pCreateContexts);

    if (pCreateState->pFile)
    {
        SrvFile2ResetOplock(pCreateState->pFile);
        SrvFile2Rundown(pCreateState->pFile);
        SrvFile2Release(pCreateState->pFile);
    }

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

    SrvFreeMemory(pCreateState);
}

// Returns true if create is requesting a brand new durable handle
static inline
BOOLEAN
SrvIsDurableCreate(
    PSRV_CREATE_STATE_SMB_V2 pCreateState
    )
{
    return pCreateState->bDurable && pCreateState->ReconnectId == (ULONG64) -1;
}

// Returns true if create is requesting to reconnect a durable handle
static inline
BOOLEAN
SrvIsDurableReconnect(
    PSRV_CREATE_STATE_SMB_V2 pCreateState
    )
{
    return pCreateState->bDurable && pCreateState->ReconnectId != (ULONG64) -1;
}

static
NTSTATUS
SrvCreateDurableEntry(
    PSRV_CREATE_STATE_SMB_V2 pCreateState
    )
{
    NTSTATUS status = STATUS_SUCCESS;

    status = SrvSmb2DurableCreate(
                    SrvGetDurableHandleTable(),
                    &pCreateState->pFile->pDurable,
                    pCreateState->pFile->fid.ullPersistentId,
                    IoSecurityGetAccessToken(
                        pCreateState->pTree->pSession->pIoSecurityContext),
                    &pCreateState->pFile->OpenState,
                    pCreateState->pFile,
                    pCreateState->pLeaseContext ?
                    &pCreateState->pLeaseContext->Key : NULL);
    BAIL_ON_NT_STATUS(status);

    pCreateState->pFile->bIsDurable = TRUE;

error:

    return status;
}

static
NTSTATUS
SrvBuildCreateResponse_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];
    PSRV_CREATE_STATE_SMB_V2   pCreateState  = NULL;
    ULONG                      iContext      = 0;
    ULONG                      ulCreateContextOffset = 0;
    ULONG                      ulCreateContextLength = 0;
    PSMB2_CREATE_CONTEXT       pPrevCreateContext    = NULL;
    PSMB2_CREATE_RESPONSE_HEADER pResponseHeader = NULL; // Do not free
    PBYTE pOutBuffer       = pSmbResponse->pBuffer;
    ULONG ulBytesAvailable = pSmbResponse->ulBytesAvailable;
    ULONG ulOffset         = 0;
    ULONG ulBytesUsed      = 0;
    ULONG ulTotalBytesUsed = 0;

    pCreateState = pCtxSmb2->hState;

    ntStatus = SMB2MarshalHeader(
                    pOutBuffer,
                    ulOffset,
                    ulBytesAvailable,
                    COM2_CREATE,
                    pSmbRequest->CreditCharge,
                    pExecContext->usCreditsGranted,
                    pSmbRequest->pHeader->ulPid,
                    pSmbRequest->pHeader->ullCommandSequence,
                    pCtxSmb2->pTree->ulTid,
                    pCtxSmb2->pSession->ullUid,
                    pExecContext->bInterimSent ? pExecContext->ullAsyncId : 0,
                    STATUS_SUCCESS,
                    TRUE,
                    LwIsSetFlag(
                            pSmbRequest->pHeader->ulFlags,
                            SMB2_FLAGS_RELATED_OPERATION),
                    &pSmbResponse->pHeader,
                    &pSmbResponse->ulHeaderSize);
    BAIL_ON_NT_STATUS(ntStatus);

    pOutBuffer       += pSmbResponse->ulHeaderSize;
    ulOffset         += pSmbResponse->ulHeaderSize;
    ulBytesAvailable -= pSmbResponse->ulHeaderSize;
    ulTotalBytesUsed += pSmbResponse->ulHeaderSize;

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

    pResponseHeader = (PSMB2_CREATE_RESPONSE_HEADER)pOutBuffer;

    pOutBuffer       += sizeof(SMB2_CREATE_RESPONSE_HEADER);
    ulBytesUsed       = sizeof(SMB2_CREATE_RESPONSE_HEADER);
    ulOffset         += sizeof(SMB2_CREATE_RESPONSE_HEADER);
    ulBytesAvailable -= sizeof(SMB2_CREATE_RESPONSE_HEADER);
    ulTotalBytesUsed += sizeof(SMB2_CREATE_RESPONSE_HEADER);

    pResponseHeader->ucOplockLevel = pCreateState->ucOplockLevel;

    pResponseHeader->ucReserved        = 0;
    pResponseHeader->ulCreateAction    = pCreateState->ulCreateAction;
    pResponseHeader->ullCreationTime   =
        pCreateState->pNetworkOpenInfo->CreationTime;
    pResponseHeader->ullLastAccessTime =
        pCreateState->pNetworkOpenInfo->LastAccessTime;
    pResponseHeader->ullLastWriteTime  =
        pCreateState->pNetworkOpenInfo->LastWriteTime;
    pResponseHeader->ullLastChangeTime =
        pCreateState->pNetworkOpenInfo->ChangeTime;
    pResponseHeader->ullAllocationSize =
        pCreateState->pNetworkOpenInfo->AllocationSize;
    pResponseHeader->ullEndOfFile      =
        pCreateState->pNetworkOpenInfo->EndOfFile;
    pResponseHeader->ulFileAttributes  =
        pCreateState->pNetworkOpenInfo->FileAttributes;
    pResponseHeader->ulReserved2       = 0;
    pResponseHeader->fid               = pCreateState->pFile->fid;
    pResponseHeader->usLength          = ulBytesUsed + 1;

    for (; iContext < pCreateState->ulNumContexts; iContext++)
    {
        PSMB2_CREATE_CONTEXT pCurCreateContext     = NULL;
        ULONG                ulCCBytesUsed         = 0;
        ULONG                ulAlignBytesUsed      = 0;
        PSRV_CREATE_CONTEXT  pCreateContextRequest =
                        &pCreateState->pCreateContexts[iContext];


        // Check for a DURABLE handle reconnect
        // Ignore all create contexts except for LEASE if so

        switch (pCreateContextRequest->contextItemType)
        {
            case SMB2_CONTEXT_ITEM_TYPE_MAX_ACCESS:

                if (!SrvIsDurableReconnect(pCreateState))
                {
                    CHAR szName[] = SMB2_CONTEXT_NAME_MAX_ACCESS;
                    SMB2_MAXIMAL_ACCESS_MASK_CREATE_CONTEXT maxAcCC = {0};

                    maxAcCC.accessMask = pCreateState->ulMaximalAccessMask;

                    ntStatus = SrvWriteCreateContext(
                                    pOutBuffer,
                                    ulOffset,
                                    ulBytesAvailable,
                                    (PBYTE)&szName[0],
                                    strlen(szName),
                                    (PBYTE)&maxAcCC,
                                    sizeof(maxAcCC),
                                    &ulAlignBytesUsed,
                                    &ulCCBytesUsed,
                                    &pCurCreateContext);
                    BAIL_ON_NT_STATUS(ntStatus);
                }

                break;

            case SMB2_CONTEXT_ITEM_TYPE_LEASE:
                if (pCreateState->pRequestHeader->ucOplockLevel ==
                    SMB2_OPLOCK_LEVEL_LEASE)
                {
                    CHAR szName[] = SMB2_CONTEXT_NAME_LEASE;

                    ntStatus = SrvWriteCreateContext(
                                    pOutBuffer,
                                    ulOffset,
                                    ulBytesAvailable,
                                    (PBYTE)&szName[0],
                                    strlen(szName),
                                    (PBYTE)&pCreateState->LeaseContext,
                                    sizeof(pCreateState->LeaseContext),
                                    &ulAlignBytesUsed,
                                    &ulCCBytesUsed,
                                    &pCurCreateContext);
                    BAIL_ON_NT_STATUS(ntStatus);
                }
                break;

            case SMB2_CONTEXT_ITEM_TYPE_DURABLE_HANDLE:
                if (pCreateState->pFile->bIsDurable)
                {
                    CHAR szName[] = SMB2_CONTEXT_NAME_DURABLE_HANDLE;
                    SMB2_DURABLE_RESPONSE_CREATE_CONTEXT durableResponse = {0};

                    ntStatus = SrvWriteCreateContext(
                        pOutBuffer,
                        ulOffset,
                        ulBytesAvailable,
                        (PBYTE)&szName[0],
                        strlen(szName),
                        (PBYTE)&durableResponse,
                        sizeof(durableResponse),
                        &ulAlignBytesUsed,
                        &ulCCBytesUsed,
                        &pCurCreateContext);
                    BAIL_ON_NT_STATUS(ntStatus);
                }
                break;

            case SMB2_CONTEXT_ITEM_TYPE_QUERY_DISK_ID:
            case SMB2_CONTEXT_ITEM_TYPE_EXT_ATTRS:
            case SMB2_CONTEXT_ITEM_TYPE_SHADOW_COPY:
            case SMB2_CONTEXT_ITEM_TYPE_SEC_DESC:
            default:

                // TODO:

                break;
        }

        if (pCurCreateContext)
        {
            if (!ulCreateContextOffset)
            {
                // First context
                ulCreateContextOffset = ulOffset + ulAlignBytesUsed;
            }

            if (ulAlignBytesUsed)
            {
                pOutBuffer       += ulAlignBytesUsed;
                ulBytesUsed       = ulAlignBytesUsed;
                ulOffset         += ulAlignBytesUsed;
                ulBytesAvailable -= ulAlignBytesUsed;
                ulTotalBytesUsed += ulAlignBytesUsed;

                if (pPrevCreateContext)
                {
                    pPrevCreateContext->ulNextContextOffset += ulAlignBytesUsed;
                    ulCreateContextLength += ulAlignBytesUsed;
                }
            }

            pOutBuffer       += ulCCBytesUsed;
            ulBytesUsed       = ulCCBytesUsed;
            ulOffset         += ulCCBytesUsed;
            ulBytesAvailable -= ulCCBytesUsed;
            ulTotalBytesUsed += ulCCBytesUsed;

            ulCreateContextLength += ulCCBytesUsed;

            pPrevCreateContext = pCurCreateContext;
        }
    }

    pResponseHeader->ulCreateContextOffset = ulCreateContextOffset;
    pResponseHeader->ulCreateContextLength = ulCreateContextLength;

    if (pPrevCreateContext)
    {
        pPrevCreateContext->ulNextContextOffset = 0;
    }

    pSmbResponse->ulMessageSize = ulTotalBytesUsed;

cleanup:

    return ntStatus;

error:

    if (ulTotalBytesUsed)
    {
        pSmbResponse->pHeader      = NULL;
        pSmbResponse->ulHeaderSize = 0;
        memset(pSmbResponse->pBuffer, 0, ulTotalBytesUsed);
    }

    pSmbResponse->ulMessageSize = 0;

    goto cleanup;
}

static
NTSTATUS
SrvWriteCreateContext(
    PBYTE                 pOutBuffer,
    ULONG                 ulOffset,
    ULONG                 ulBytesAvailable,
    PBYTE                 pName,
    USHORT                usNameSize,
    PBYTE                 pData,
    ULONG                 ulDataSize,
    PULONG                pulAlignBytesUsed,
    PULONG                pulCCBytesUsed,
    PSMB2_CREATE_CONTEXT* ppCreateContext
    )
{
    NTSTATUS             ntStatus          = STATUS_SUCCESS;
    PBYTE                pDataCursor       = pOutBuffer;
    ULONG                ulBytesAvailable1 = ulBytesAvailable;
    ULONG                ulCCBytesUsed     = 0;
    ULONG                ulAlignBytesUsed  = 0;
    PSMB2_CREATE_CONTEXT pCreateContext    = NULL;

    if (ulOffset % 4)
    {
        USHORT usAlign = 4 - (ulOffset % 4);

        if (ulBytesAvailable1 < usAlign)
        {
            ntStatus = STATUS_INVALID_BUFFER_SIZE;
            BAIL_ON_NT_STATUS(ntStatus);
        }

        memset(pDataCursor, 0, usAlign);
        ulOffset          += usAlign;
        ulAlignBytesUsed  += usAlign;
        ulBytesAvailable1 -= usAlign;
        pDataCursor       += usAlign;
    }

    ntStatus = SMB2MarshalCreateContext(
                    pDataCursor,
                    ulOffset,
                    pName,
                    usNameSize,
                    pData,
                    ulDataSize,
                    ulBytesAvailable1,
                    &ulCCBytesUsed,
                    &pCreateContext);
    BAIL_ON_NT_STATUS(ntStatus);

    pCreateContext->ulNextContextOffset = ulCCBytesUsed;

    *pulAlignBytesUsed = ulAlignBytesUsed;
    *pulCCBytesUsed    = ulCCBytesUsed;
    *ppCreateContext   = pCreateContext;

cleanup:

    return ntStatus;

error:

    *pulAlignBytesUsed = 0;
    *pulCCBytesUsed    = 0;
    *ppCreateContext   = NULL;

    goto cleanup;
}
