/* -*- 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:
 *
 *        samdbcounter.c
 *
 * Abstract:
 *
 *
 *      Likewise SAM Database Provider
 *
 *      Database Counters
 *
 * Authors: Krishna Ganugapati (krishnag@likewisesoftware.com)
 *          Sriram Nambakam (snambakam@likewise.com)
 *          Rafal Szczesniak (rafal@likewise.com)
 *
 */

#include "includes.h"


static
DWORD
SamDbGetNextId(
    PSAMDB_CONTEXT DbContext,
    PCSTR Query,
    PDWORD pId
    );

static
DWORD
SamDbIsAvailableId(
    PSAMDB_CONTEXT DbContext,
    PCSTR QueryTemplate,
    DWORD Id
    );


DWORD
SamDbGetNextAvailableUid(
    PSAMDB_CONTEXT DbContext,
    PDWORD pUid
    )
{
    DWORD error = ERROR_SUCCESS;
    PCSTR queryTemplate =
        "SELECT UIDCounter FROM " SAM_DB_CONFIG_TABLE \
        "; UPDATE " SAM_DB_CONFIG_TABLE               \
        "   SET UIDCounter = UIDCounter + 1";
    PCSTR uidQueryTemplate =
        "SELECT " SAM_DB_COL_UID " FROM " SAM_DB_OBJECTS_TABLE \
        " WHERE " SAM_DB_COL_UID " = %u";
    DWORD uid = 0;

    /*
     * Get the next UID and make sure it's not used yet
     */
    do
    {
        error = SamDbGetNextId(
                        DbContext,
                        queryTemplate,
                        &uid);
        BAIL_ON_SAMDB_ERROR(error);

        error = SamDbIncrementSequenceNumber(
                        DbContext,
                        NULL);
        BAIL_ON_SAMDB_ERROR(error);

        error = SamDbIsAvailableId(
                        DbContext,
                        uidQueryTemplate,
                        uid);
        if (error != ERROR_SUCCESS &&
            error != ERROR_USER_EXISTS)
        {
            BAIL_ON_SAMDB_ERROR(error);
        }

    } while (error == ERROR_USER_EXISTS &&
             uid <= SAM_DB_MAX_UID);

    if (uid > SAM_DB_MAX_UID)
    {
        /* Not likely but it's happened - we've run out of UID space */
        error = ERROR_INTERNAL_ERROR;

        SAMDB_LOG_ERROR("Sam database has run out of UID space "
                        "(uid = %u, max uid = %u)",
                        uid, SAM_DB_MAX_UID);
        goto cleanup;
    }
    BAIL_ON_SAMDB_ERROR(error);

    *pUid = uid;

cleanup:
    if (error)
    {
        *pUid = 0;
    }

    return error;
}


DWORD
SamDbGetNextAvailableGid(
    PSAMDB_CONTEXT DbContext,
    PDWORD pGid
    )
{
    DWORD error = ERROR_SUCCESS;
    PCSTR queryTemplate =
        "SELECT GIDCounter FROM " SAM_DB_CONFIG_TABLE \
        "; UPDATE " SAM_DB_CONFIG_TABLE               \
        "   SET GIDCounter = GIDCounter + 1";
    PCSTR gidQueryTemplate =
        "SELECT " SAM_DB_COL_GID " FROM " SAM_DB_OBJECTS_TABLE \
        " WHERE " SAM_DB_COL_GID " = %u";
    DWORD gid = 0;

    /*
     * Get the next GID and make sure it's not used yet
     */
    do
    {
        error = SamDbGetNextId(
                        DbContext,
                        queryTemplate,
                        &gid);
        BAIL_ON_SAMDB_ERROR(error);

        error = SamDbIncrementSequenceNumber(
                        DbContext,
                        NULL);
        BAIL_ON_SAMDB_ERROR(error);

        error = SamDbIsAvailableId(
                        DbContext,
                        gidQueryTemplate,
                        gid);
        if (error != ERROR_SUCCESS &&
            error != ERROR_USER_EXISTS)
        {
            BAIL_ON_SAMDB_ERROR(error);
        }

    } while (error == ERROR_USER_EXISTS &&
             gid <= SAM_DB_MAX_GID);

    if (gid > SAM_DB_MAX_GID)
    {
        /* Not likely but it's happened - we've run out of GID space */
        error = ERROR_INTERNAL_ERROR;

        SAMDB_LOG_ERROR("Sam database has run out of GID space "
                        "(gid = %u, max gid = %u)",
                        gid, SAM_DB_MAX_GID);
        goto cleanup;
    }
    BAIL_ON_SAMDB_ERROR(error);

    *pGid = gid;

cleanup:
    if (error)
    {
        *pGid = 0;
    }

    return error;
}


DWORD
SamDbGetNextAvailableRid(
    PSAMDB_CONTEXT DbContext,
    PDWORD pRid
    )
{
    DWORD error = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PCSTR queryTemplate =
        "SELECT RIDCounter FROM " SAM_DB_CONFIG_TABLE \
        "; UPDATE " SAM_DB_CONFIG_TABLE               \
        "   SET RIDCounter = RIDCounter + 1";
    PCSTR objectSidQueryTemplate =
        "SELECT " SAM_DB_COL_OBJECT_SID " FROM " SAM_DB_OBJECTS_TABLE \
        " WHERE " SAM_DB_COL_OBJECT_SID " = '%s-%s'";
    PSTR domainQuery = NULL;
    PSID domainSid = NULL;
    PSTR pszDomainSid = NULL;
    PSTR objectRidQueryTemplate = NULL;
    DWORD rid = 0;

    error = SamDbGetLocalDomainSid(&domainSid);
    BAIL_ON_SAMDB_ERROR(error);

    ntStatus = RtlAllocateCStringFromSid(
                       &pszDomainSid,
                       domainSid);
    BAIL_ON_NT_STATUS(ntStatus);

    //
    // Create the template for querying account SID (DOMAIN_SID-ACCOUNT_RID)
    //
    error = LwAllocateStringPrintf(
                    &objectRidQueryTemplate,
                    objectSidQueryTemplate,
                    pszDomainSid,
                    "%u");
    BAIL_ON_SAMDB_ERROR(error);

    /*
     * Get the next RID and make sure it's not used yet
     */
    do
    {
        error = SamDbGetNextId(
                        DbContext,
                        queryTemplate,
                        &rid);
        BAIL_ON_SAMDB_ERROR(error);

        error = SamDbIncrementSequenceNumber(
                        DbContext,
                        domainSid);
        BAIL_ON_SAMDB_ERROR(error);

        error = SamDbIsAvailableId(
                        DbContext,
                        objectRidQueryTemplate,
                        rid);
        if (error != ERROR_SUCCESS &&
            error != ERROR_USER_EXISTS)
        {
            BAIL_ON_SAMDB_ERROR(error);
        }

    } while (error == ERROR_USER_EXISTS &&
             rid <= SAM_DB_MAX_RID);

    if (rid > SAM_DB_MAX_RID)
    {
        /* Not likely but it's happened - we've run out of RID space */
        error = ERROR_INTERNAL_ERROR;

        SAMDB_LOG_ERROR("Sam database has run out of RID space "
                        "(rid = %u, max rid = %u)",
                        rid, SAM_DB_MAX_RID);
        goto cleanup;
    }
    BAIL_ON_SAMDB_ERROR(error);

cleanup:
error:
    if (error)
    {
        *pRid = 0;
    }

    LW_SAFE_FREE_MEMORY(domainSid);
    RTL_FREE(&pszDomainSid);
    LW_SAFE_FREE_MEMORY(domainQuery);
    LW_SAFE_FREE_MEMORY(objectRidQueryTemplate);

    if (error == ERROR_SUCCESS &&
        ntStatus != STATUS_SUCCESS)
    {
        error = LwNtStatusToWin32Error(ntStatus);
    }

    *pRid = rid;

    return error;
}


DWORD
SamDbCheckAvailableUid(
    PSAMDB_CONTEXT DbContext,
    DWORD Uid
    )
{
    DWORD error = ERROR_SUCCESS;
    PCSTR uidQueryTemplate =
        "SELECT " SAM_DB_COL_UID " FROM " SAM_DB_OBJECTS_TABLE \
        " WHERE " SAM_DB_COL_UID " = %u";

    error = SamDbIsAvailableId(
                    DbContext,
                    uidQueryTemplate,
                    Uid);
    BAIL_ON_SAMDB_ERROR(error);

cleanup:
    return error;
}


DWORD
SamDbCheckAvailableGid(
    PSAMDB_CONTEXT DbContext,
    DWORD Gid
    )
{
    DWORD error = ERROR_SUCCESS;
    PCSTR gidQueryTemplate =
        "SELECT " SAM_DB_COL_GID " FROM " SAM_DB_OBJECTS_TABLE \
        " WHERE " SAM_DB_COL_GID " = %u";

    error = SamDbIsAvailableId(
                    DbContext,
                    gidQueryTemplate,
                    Gid);
    if (error == ERROR_USER_EXISTS)
    {
        // We've been looking for local group's id in fact.
        error = ERROR_ALIAS_EXISTS;
    }
    else if (error != ERROR_SUCCESS)
    {
        BAIL_ON_SAMDB_ERROR(error);
    }

cleanup:
    return error;
}


DWORD
SamDbCheckAvailableSid(
    PSAMDB_CONTEXT DbContext,
    PCSTR Sid
    )
{
    DWORD error = ERROR_SUCCESS;
    PCSTR pszQueryTemplate =
        "SELECT " SAM_DB_COL_OBJECT_SID " FROM " SAM_DB_OBJECTS_TABLE \
        " WHERE " SAM_DB_COL_OBJECT_SID " = '%s'";
    PSTR pszQuery = NULL;
    PSTR *ppszResult = NULL;
    int nRows = 0;
    int nCols = 0;
    PSTR pszError = NULL;

    error = LwAllocateStringPrintf(
                    &pszQuery,
                    pszQueryTemplate,
                    Sid);
    BAIL_ON_SAMDB_ERROR(error);

    error = sqlite3_get_table(
                    DbContext->DbHandle,
                    pszQuery,
                    &ppszResult,
                    &nRows,
                    &nCols,
                    &pszError);
    BAIL_ON_SAMDB_ERROR(error);

    if (nRows == 0)
    {
        error = ERROR_SUCCESS;
    }
    else if (nRows == 1)
    {
        error = ERROR_USER_EXISTS;
    }
    else
    {
        error = ERROR_INTERNAL_ERROR;

        SAMDB_LOG_ERROR("Two identical SIDs found in the database");
        goto cleanup;
    }
    BAIL_ON_SAMDB_ERROR(error);

cleanup:
    if (ppszResult)
    {
        sqlite3_free_table(ppszResult);
    }

    if (pszError)
    {
        sqlite3_free(pszError);
    }

    LW_SAFE_FREE_MEMORY(pszQuery);

    return error;
}


static
DWORD
SamDbGetNextId(
    PSAMDB_CONTEXT DbContext,
    PCSTR Query,
    PDWORD pId
    )
{
    DWORD error = ERROR_SUCCESS;
    PSTR errorMessage = NULL;
    int rows = 0;
    int cols = 0;
    PSTR* pResult = NULL;

    error = sqlite3_get_table(
                    DbContext->DbHandle,
                    Query,
                    &pResult,
                    &rows,
                    &cols,
                    &errorMessage);
    BAIL_ON_SAMDB_SQLITE_ERROR_DB(error, DbContext->DbHandle);

    if (!rows || (cols != 1))
    {
        error = ERROR_INTERNAL_ERROR;

        SAMDB_LOG_ERROR("Couldn't query samdbconfig table"
                        "(rows = %u, cols = %u): %s",
                        rows, cols, errorMessage);
        goto cleanup;
    }

    *pId = strtoul(pResult[1], NULL, 10);

cleanup:
    if (error)
    {
        *pId = 0;
    }

    if (pResult)
    {
        sqlite3_free_table(pResult);
    }

    if (errorMessage)
    {
        sqlite3_free(errorMessage);
    }

    return error;
}


static
DWORD
SamDbIsAvailableId(
    PSAMDB_CONTEXT DbContext,
    PCSTR QueryTemplate,
    DWORD Id
    )
{
    DWORD error = ERROR_SUCCESS;
    PSTR query = NULL;
    PSTR *pResult = NULL;
    int rows = 0;
    int cols = 0;
    PSTR errorMessage = NULL;

    error = LwAllocateStringPrintf(
                    &query,
                    QueryTemplate,
                    Id);
    BAIL_ON_SAMDB_ERROR(error);

    error = sqlite3_get_table(
                    DbContext->DbHandle,
                    query,
                    &pResult,
                    &rows,
                    &cols,
                    &errorMessage);
    BAIL_ON_SAMDB_SQLITE_ERROR_DB(error, DbContext->DbHandle);

    if (rows == 0)
    {
        error = ERROR_SUCCESS;
    }
    else if (rows == 1)
    {
        error = ERROR_USER_EXISTS;
    }
    else
    {
        error = ERROR_INTERNAL_ERROR;

        SAMDB_LOG_ERROR("More than one record with the same id found in "
                        "samdbconfig table (rows = %u, cols = %u): %s",
                        rows, cols, errorMessage);
        goto cleanup;
    }
    BAIL_ON_SAMDB_ERROR(error);

cleanup:
    if (pResult)
    {
        sqlite3_free_table(pResult);
    }

    if (errorMessage)
    {
        sqlite3_free(errorMessage);
    }

    LW_SAFE_FREE_MEMORY(query);

    return error;
}


#define SAMDB_SQL_INCREASE_SEQUENCE_NUMBER \
    "UPDATE " SAM_DB_OBJECTS_TABLE " SET " SAM_DB_COL_SEQUENCE_NUMBER \
    " = " SAM_DB_COL_SEQUENCE_NUMBER " + 1 WHERE " \
    SAM_DB_COL_OBJECT_SID " = ?%u;"



DWORD
SamDbIncrementSequenceNumber(
    PSAMDB_CONTEXT DbContext,
    PSID ObjectSid
    )
{
    DWORD error = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    union {
        SID Sid;
        BYTE Buffer[SID_MAX_SIZE];
    } builtinDomainSid;
    DWORD builtinDomainSidLength = sizeof(builtinDomainSid);
    PSID localDomainSid = NULL;
    PSTR pszDomainSid = NULL;
    size_t sqlStatementSize = 256;
    size_t sqlStatementLength = 0;
    size_t maxSqlStatementSize = 1024;
    PSTR sqlStatementString = NULL;
    sqlite3_stmt* sqlStatement = NULL;
    DWORD domainSidIndex = 1;

    ntStatus = RtlCreateWellKnownSid(
                    WinBuiltinDomainSid,
                    NULL,
                    &builtinDomainSid.Sid,
                    &builtinDomainSidLength);
    BAIL_ON_NT_STATUS(ntStatus);

    //
    // If BUILTIN domain SID is a prefix of ObjectSid then increment
    // sequence number in BUILTIN domain. Otherwise, use local
    // domain's sequence number.
    //
    if (ObjectSid &&
        RtlIsPrefixSid(
                    &builtinDomainSid.Sid,
                    ObjectSid))
    {
        ntStatus = RtlAllocateCStringFromSid(
                        &pszDomainSid,
                        &builtinDomainSid.Sid);
        BAIL_ON_NT_STATUS(ntStatus);
    }
    else
    {
        error = SamDbGetLocalDomainSid(&localDomainSid);
        BAIL_ON_SAMDB_ERROR(error);

        error = RtlAllocateCStringFromSid(
                        &pszDomainSid,
                        localDomainSid);
        BAIL_ON_NT_STATUS(ntStatus);
    }

    //
    // Allocate a SID of domain the change applies to (determined
    // (by ObjectSid passed)
    //
    //
    // Create SQL UPDATE statement to increase the sequence number
    //
    error = LwAllocateMemory(
                    sizeof(sqlStatementString[0]) * sqlStatementSize,
                    OUT_PPVOID(&sqlStatementString));
    BAIL_ON_SAMDB_ERROR(error);

    error = SamDbPrintfString(
                    &sqlStatementString,
                    &sqlStatementSize,
                    &sqlStatementLength,
                    maxSqlStatementSize,
                    SAMDB_SQL_INCREASE_SEQUENCE_NUMBER,
                    domainSidIndex);
    BAIL_ON_SAMDB_ERROR(error);

    error = sqlite3_prepare_v2(
                      DbContext->DbHandle,
                      sqlStatementString,
                      -1,
                      &sqlStatement,
                      NULL);
    if (error)
    {
        SAMDB_LOG_DEBUG("Error when preparing SQL statement:\n%s",
                        sqlStatementString);
    }
    BAIL_ON_SAMDB_SQLITE_ERROR_DB(error, DbContext->DbHandle);

    error = sqlite3_bind_text(
                      sqlStatement,
                      domainSidIndex,
                      pszDomainSid,
                      -1,
                      SQLITE_TRANSIENT);
    BAIL_ON_SAMDB_SQLITE_ERROR_STMT(error, sqlStatement);

    // SQLITE_TRANSIENT causes sqlite make a local copy
    // of bound value, so pszDomainSid can be freed.
    RTL_FREE(&pszDomainSid);

    //
    // Execute the statement
    //
    error = sqlite3_step(sqlStatement);
    if (error == SQLITE_DONE)
    {
        error = ERROR_SUCCESS;
    }
    else
    {
        SAMDB_LOG_ERROR("Error code %u returned from sqlite3_step() "
                        "while %u was expected",
                        error,
                        SQLITE_DONE);
        BAIL_ON_SAMDB_SQLITE_ERROR_STMT(error, sqlStatement);
    }

cleanup:
error:
    if (sqlStatement)
    {
        sqlite3_finalize(sqlStatement);
    }

    LW_SAFE_FREE_MEMORY(localDomainSid);
    RTL_FREE(&pszDomainSid);
    LW_SAFE_FREE_MEMORY(sqlStatementString);

    if (error == ERROR_SUCCESS &&
        ntStatus != STATUS_SUCCESS)
    {
        error = LwNtStatusToWin32Error(ntStatus);
    }

    return error;
}


#define SAMDB_SQL_SELECT_SEQUENCE_NUMBER \
    "SELECT " SAM_DB_COL_SEQUENCE_NUMBER "," SAM_DB_COL_OBJECT_SID \
    " FROM " SAM_DB_OBJECTS_TABLE \
    " WHERE " SAM_DB_COL_OBJECT_CLASS " = ?%u;"


DWORD
SamDbGetSequenceNumber(
    PSAMDB_CONTEXT DbContext,
    PSID DomainSid,
    PLONG64 pSequenceNumber
    )
{
    DWORD error = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PSID builtinDomainSid = NULL;
    SAMDB_OBJECT_CLASS objectClass = SAMDB_OBJECT_CLASS_DOMAIN;
    DWORD objectClassIndex = 1;
    DWORD sequenceNumberIndex = 0;
    DWORD objectSidIndex = 1;
    DWORD sequenceNumber = 0;
    size_t sqlStatementSize = 256;
    size_t sqlStatementLength = 0;
    size_t maxSqlStatementSize = 1024;
    PSTR sqlStatementString = NULL;
    sqlite3_stmt* sqlStatement = NULL;
    const unsigned char* stringValue = NULL;
    PSID domainSid = NULL;

    //
    // If DomainSid is NULL then by default we're looking for
    // local domain's sequence number (meaning we're actually
    // looking for anything but the builtin domain).
    //
    if (!DomainSid)
    {
        error = LwAllocateWellKnownSid(
                       WinBuiltinDomainSid,
                       NULL,
                       &builtinDomainSid,
                       NULL);
        BAIL_ON_SAMDB_ERROR(error);
    }

    error = LwAllocateMemory(
                    sizeof(sqlStatementString[0]) * sqlStatementSize,
                    OUT_PPVOID(&sqlStatementString));
    BAIL_ON_SAMDB_ERROR(error);

    //
    // Create SQL statement string (SELECT ... )
    //
    error = SamDbPrintfString(
                    &sqlStatementString,
                    &sqlStatementSize,
                    &sqlStatementLength,
                    maxSqlStatementSize,
                    SAMDB_SQL_SELECT_SEQUENCE_NUMBER,
                    objectClassIndex);
    BAIL_ON_SAMDB_ERROR(error);

    error = sqlite3_prepare_v2(
                      DbContext->DbHandle,
                      sqlStatementString,
                      -1,
                      &sqlStatement,
                      NULL);
    if (error)
    {
        SAMDB_LOG_DEBUG("Error when preparing SQL statement:\n%s",
                        sqlStatementString);
    }
    BAIL_ON_SAMDB_SQLITE_ERROR_DB(error, DbContext->DbHandle);

    error = sqlite3_bind_int(
                      sqlStatement,
                      objectClassIndex,
                      (int)objectClass);
    BAIL_ON_SAMDB_SQLITE_ERROR_STMT(error, sqlStatement);

    do
    {
        //
        // Execute the statement
        //
        error = sqlite3_step(sqlStatement);
        if (error == SQLITE_DONE)
        {
            error = ERROR_SUCCESS;
        }
        else if (error == SQLITE_ROW)
        {
            error = ERROR_MORE_DATA;
        }
        else
        {
            SAMDB_LOG_ERROR("Error code %u returned from sqlite3_step() "
                            "while %u was expected",
                            error,
                            SQLITE_DONE);
            BAIL_ON_SAMDB_SQLITE_ERROR_STMT(error, sqlStatement);
        }

        if (error == ERROR_MORE_DATA)
        {
            stringValue = sqlite3_column_text(
                                 sqlStatement,
                                 objectSidIndex);

            ntStatus = RtlAllocateSidFromCString(
                                 &domainSid,
                                 (PCSTR)stringValue);
            BAIL_ON_NT_STATUS(ntStatus);

            if ((DomainSid == NULL &&
                 !RtlEqualSid(builtinDomainSid,
                              domainSid)) ||
                (DomainSid != NULL &&
                 RtlEqualSid(DomainSid,
                             domainSid)))
            {
                //
                // The SID we got is either a requested SID (DomainSid)
                // or not a builtin domain SID. If it's the latter then
                // it can only be a local domain SID - get the sequence
                // number and leave the loop.
                //
                sequenceNumber = (LONG64)sqlite3_column_int64(
                                             sqlStatement,
                                             sequenceNumberIndex);
                error = ERROR_SUCCESS;
                break;
            }
        }

        RTL_FREE(&domainSid);

    } while (error == ERROR_MORE_DATA);

cleanup:
error:
    if (sqlStatement)
    {
        sqlite3_finalize(sqlStatement);
    }

    RTL_FREE(&domainSid);
    LW_SAFE_FREE_MEMORY(builtinDomainSid);
    LW_SAFE_FREE_MEMORY(sqlStatementString);

    if (error == ERROR_SUCCESS &&
        ntStatus != STATUS_SUCCESS)
    {
        error = LwNtStatusToWin32Error(ntStatus);
    }

    *pSequenceNumber = sequenceNumber;

    return error;
}
