/*++

Copyright (c) 1998 Microsoft Corporation

Module Name:

    blobs.c

Abstract:

    Implements a set of APIs to manage BLOBS and arrays of BLOBS.

Author:

    Ovidiu Temereanca (ovidiut)   24-Nov-1999

Revision History:

    <alias> <date> <comments>

--*/

#include "pch.h"

//
// Includes
//

// None

#define DBG_BLOBS       "Blobs"

//
// Strings
//

// None

//
// Constants
//

#define BLOB_SIGNATURE              0x79563442
#define BLOB_GROWDATASIZE_DEFAULT   1024
#define BLOBS_GROWCOUNT_DEFAULT     64
#define BLOBS_SIGNATURE             0x12567841

//
// Macros
//

// None

//
// Types
//

typedef struct {
    DWORD       BlobSignature;
    DWORD       DataSize;
    DWORD       Flags;
} BLOBHDR, *PBLOBHDR;

typedef struct {
    DWORD       BlobsArraySignature;
    DWORD       BlobsCount;
} BLOBSARRAYHDR, *PBLOBSARRAYHDR;

//
// Globals
//

// None

//
// Macro expansion list
//

// None

//
// Private function prototypes
//

// None

//
// Macro expansion definition
//

// None

//
// Code
//


#ifdef DEBUG

#define ASSERT_VALID_BLOB(b)            MYASSERT (pIsValidBlob (b))
#define ASSERT_VALID_BLOBS_ARRAY(a)     MYASSERT (pIsValidBlobsArray (a))

BOOL
pIsValidBlob (
    IN      POURBLOB Blob
    )

/*++

Routine Description:

    pIsValidBlob checks if the passed-in blob points to a valid OURBLOB blob structure

Arguments:

    Blob - Specifies a pointer to the blob to be checked

Return Value:

    TRUE if the check was successful.
    FALSE if not.

--*/

{
    BOOL b = TRUE;

    if (!Blob) {
        return FALSE;
    }

    __try {
        b = !Blob->Data && !Blob->End && !Blob->Index && !Blob->AllocSize ||
            Blob->Data && Blob->AllocSize && Blob->End <= Blob->AllocSize && Blob->Index <= Blob->AllocSize;
    }
    __except (TRUE) {
        b = FALSE;
    }

    return b;
}

BOOL
pIsValidBlobsArray (
    IN      PBLOBS BlobsArray
    )

/*++

Routine Description:

    pIsValidBlobsArray checks if the passed-in bloba array points to a valid BLOBS array structure

Arguments:

    BlobsArray - Specifies a pointer to the blobs array to be checked

Return Value:

    TRUE if the check was successful.
    FALSE if not.

--*/

{
    BOOL b = TRUE;

    if (!BlobsArray) {
        return FALSE;
    }

    __try {
        b = !BlobsArray->Blobs && !BlobsArray->BlobsCount && !BlobsArray->BlobsAllocated ||
            BlobsArray->Signature == BLOBS_SIGNATURE &&
            BlobsArray->Blobs &&
            BlobsArray->BlobsAllocated &&
            BlobsArray->BlobsGrowCount &&
            BlobsArray->BlobsCount <= BlobsArray->BlobsAllocated;
    }
    __except (TRUE) {
        b = FALSE;
    }

    return b;
}

#else

#define ASSERT_VALID_BLOB(b)
#define ASSERT_VALID_BLOBS_ARRAY(a)

#endif


PVOID
pBlobAllocateMemory (
    IN      DWORD Size
    )

/*++

Routine Description:

    pBlobAllocateMemory is a private function that allocates space from the process heap

Arguments:

    Size - Specifies the size (in bytes) to allocate.

Return Value:

    A pointer to the successfully allocated memory or NULL if not enough memory

--*/

{
    MYASSERT (Size);
    return HeapAlloc (g_hHeap, 0, Size);
}


static
PVOID
pReAllocateMemory (
    IN      PVOID OldBuffer,
    IN      DWORD NewSize
    )

/*++

Routine Description:

    pReAllocateMemory is a private function that re-allocates space from the process heap

Arguments:

    OldBuffer - Specifies the buffer to be re-allocated
    Size - Specifies the size (in bytes) to allocate.

Return Value:

    A pointer to the successfully re-allocated memory or NULL if not enough memory

--*/

{
    MYASSERT (OldBuffer);
    MYASSERT (NewSize);
    return HeapReAlloc (g_hHeap, 0, OldBuffer, NewSize);
}


VOID
pBlobFreeMemory (
    IN      PVOID Buffer
    )

/*++

Routine Description:

    pBlobFreeMemory is a private function that frees space allocated from the process heap

Arguments:

    Buffer - Specifies a pointer to buffer to free.

Return Value:

    none

--*/

{
    MYASSERT (Buffer);
    HeapFree (g_hHeap, 0, Buffer);
}


POURBLOB
BlobCreate (
    VOID
    )
{
    POURBLOB newBlob;

    newBlob = pBlobAllocateMemory (DWSIZEOF (OURBLOB));
    if (newBlob) {
        ZeroMemory (newBlob, DWSIZEOF (OURBLOB));
    }
    return newBlob;
}


POURBLOB
BlobDuplicate (
    IN      POURBLOB SourceBlob
    )

/*++

Routine Description:

    BlobDuplicate duplicates the data in the source blob, so the resulting blob will
    have an identical copy of data

Arguments:

    SourceBlob - Specifies the blob source of data

Return Value:

    Pointer to the new blob if duplicate was successful; NULL if not enough memory

--*/

{
    POURBLOB newBlob;
    DWORD dataSize;

    newBlob = BlobCreate ();
    if (newBlob && SourceBlob->Data) {
        dataSize = BlobGetDataSize (SourceBlob);
        newBlob->Data = pBlobAllocateMemory (dataSize);
        if (!newBlob->Data) {
            BlobDestroy (newBlob);
            return NULL;
        }
        newBlob->AllocSize = dataSize;
        newBlob->End = dataSize;
        CopyMemory (newBlob->Data, SourceBlob->Data, dataSize);
        newBlob->Flags = SourceBlob->Flags;
    }
    return newBlob;
}


VOID
BlobClear (
    IN OUT  POURBLOB Blob
    )

/*++

Routine Description:

    BlobClear clears the specified blob (frees its associated data)

Arguments:

    Blob - Specifies the blob to clear

Return Value:

    none

--*/

{
    if (Blob && Blob->Data) {
        pBlobFreeMemory (Blob->Data);
        ZeroMemory (Blob, DWSIZEOF (OURBLOB));
    }
}


VOID
BlobDestroy (
    IN OUT  POURBLOB Blob
    )

/*++

Routine Description:

    BlobDestroy destroys the specified blob (frees its associated data and the blob itself)

Arguments:

    Blob - Specifies the blob to destroy

Return Value:

    none

--*/

{
    if (Blob) {
        BlobClear (Blob);
        pBlobFreeMemory (Blob);
    }
}


BOOL
BlobSetIndex (
    IN OUT  POURBLOB Blob,
    IN      DWORD Index
    )

/*++

Routine Description:

    BlobSetIndex sets the current read/write pointer

Arguments:

    Blob - Specifies the blob
    Index - Specifies the new index value

Return Value:

    TRUE if the index move was successful

--*/

{
    ASSERT_VALID_BLOB (Blob);

    if (Index > Blob->End) {
        DEBUGMSG ((DBG_BLOBS, "BlobSetIndex: invalid Index specified (%lu)", Index));
        MYASSERT (FALSE);   //lint !e506
        return FALSE;
    }

    Blob->Index = Index;
    return TRUE;
}


DWORD
BlobGetRecordedDataType (
    IN      POURBLOB Blob
    )

/*++

Routine Description:

    BlobGetRecordedDataType returns the data type recorded at current read position

Arguments:

    Blob - Specifies the blob

Return Value:

    The current data type if the blob records data type and the read position is valid;
    BDT_NONE otherwise

--*/

{
    PBYTE p;

    if (BlobRecordsDataType (Blob)) {
        p = BlobGetPointer (Blob);
        if (p) {
            return *(DWORD*)p;
        }
    }
    return BDT_NONE;
}


BOOL
BlobWriteEx (
    IN OUT  POURBLOB Blob,
    IN      DWORD DataType,         OPTIONAL
    IN      BOOL RecordDataSize,
    IN      DWORD DataSize,
    IN      PCVOID Data
    )

/*++

Routine Description:

    BlobWriteEx writes data at the current index position, growing the blob if necessary
    and adjusting it's size.

Arguments:

    Blob - Specifies the blob
    DataType - Specifies the type of data to be stored; can be zero only if the blob
               doesn't record data types
    RecordDataSize - Specifies TRUE if this size has to be recorded in the blob
    DataSize - Specifies the size, in bytes, of the data to be stored
    Data - Specifies the data

Return Value:

    TRUE if write was successful; FALSE if not enough memory

--*/

{
    PBYTE p;
    DWORD totalDataSize;
    DWORD growTo;
    DWORD d;

    ASSERT_VALID_BLOB (Blob);
    MYASSERT (DataSize);

    MYASSERT (DataType || !BlobRecordsDataType (Blob));
    if (!DataType && BlobRecordsDataType (Blob)) {
        return FALSE;
    }

    if (!Blob->GrowSize) {
        Blob->GrowSize = BLOB_GROWDATASIZE_DEFAULT;
    }

    totalDataSize = Blob->Index + DataSize;
    if (BlobRecordsDataType (Blob)) {
        //
        // add the size of a DWORD
        //
        totalDataSize += DWSIZEOF (DWORD);
    }
    if (BlobRecordsDataSize (Blob) || RecordDataSize) {
        //
        // add the size of a DWORD
        //
        totalDataSize += DWSIZEOF (DWORD);
    }
    if (totalDataSize > Blob->AllocSize) {
        d = totalDataSize + Blob->GrowSize - 1;
        growTo = d - d % Blob->GrowSize;
    } else {
        growTo = 0;
    }

    if (!Blob->Data) {
        Blob->Data = (PBYTE) pBlobAllocateMemory (growTo);
        if (!Blob->Data) {
            DEBUGMSG ((DBG_ERROR, "BlobWriteEx: pBlobAllocateMemory (%lu) failed", growTo));
            return FALSE;
        }

        Blob->AllocSize = growTo;
    } else if (growTo) {
        p = pReAllocateMemory (Blob->Data, growTo);
        if (!p) {
            DEBUGMSG ((DBG_ERROR, "BlobWriteEx: pReAllocateMemory (%lu) failed", growTo));
            return FALSE;
        }

        Blob->AllocSize = growTo;
        Blob->Data = p;
    }

    p = BlobGetPointer (Blob);

    if (BlobRecordsDataType (Blob)) {
        *(PDWORD)p = DataType;
        p += DWSIZEOF (DWORD);
        Blob->Index += DWSIZEOF (DWORD);
    }
    if (BlobRecordsDataSize (Blob) || RecordDataSize) {
        *(PDWORD)p = DataSize;
        p += DWSIZEOF (DWORD);
        Blob->Index += DWSIZEOF (DWORD);
    }

    CopyMemory (p, Data, DataSize);
    Blob->Index += DataSize;

    //
    // adjust EOF
    //
    if (Blob->Index > Blob->End) {
        Blob->End = Blob->Index;
    }

    return TRUE;
}


PBYTE
BlobReadEx (
    IN OUT  POURBLOB Blob,
    IN      DWORD ExpectedDataType,     OPTIONAL
    IN      DWORD ExpectedDataSize,     OPTIONAL
    IN      BOOL RecordedDataSize,
    OUT     PDWORD ActualDataSize,      OPTIONAL
    OUT     PVOID Data,                 OPTIONAL
    IN      PMHANDLE Pool               OPTIONAL
    )

/*++

Routine Description:

    BlobReadEx reads data from the specified blob, at the current index position

Arguments:

    Blob - Specifies the blob to read from
    ExpectedDataType - Specifies the expected data type; optional
    ExpectedDataSize - Specifies the expected data size; optional
    RecordedDataSize - Specifies TRUE if the data size was recorded in the blob
    ActualDataSize - Receives the actual data size; optional
    Data - Receives the actual data; optional; if NULL, a buffer will be allocated
    Pool - Specifies the pool to use for memory allocations; optional;
           if NULL, the process heap will be used

Return Value:

    A pointer to the buffer containing the data; NULL if an error occured
    or some data conditions don't match

--*/

{
    DWORD initialIndex;
    PBYTE readPtr;
    DWORD actualDataType;
    DWORD actualDataSize = 0;

    ASSERT_VALID_BLOB (Blob);

    readPtr = BlobGetPointer (Blob);
    if (!readPtr) {
        return NULL;
    }

    //
    // data size must be available some way
    //
    MYASSERT (BlobRecordsDataSize (Blob) || RecordedDataSize || ExpectedDataSize);

    initialIndex = BlobGetIndex (Blob);

    if (BlobRecordsDataType (Blob)) {

        if (readPtr + DWSIZEOF (DWORD) > BlobGetEOF (Blob)) {
            return NULL;
        }
        //
        // check actual data type
        //
        actualDataType = *(DWORD*)readPtr;

        if (ExpectedDataType && ExpectedDataType != actualDataType) {

            DEBUGMSG ((
                DBG_ERROR,
                "BlobReadEx: Actual data type (%lu) different than expected data type (%lu)",
                actualDataType,
                ExpectedDataType
                ));

            return NULL;
        }

        Blob->Index += DWSIZEOF (DWORD);
        readPtr += DWSIZEOF (DWORD);
    }

    if (BlobRecordsDataSize (Blob) || RecordedDataSize) {

        if (readPtr + DWSIZEOF (DWORD) > BlobGetEOF (Blob)) {
            BlobSetIndex (Blob, initialIndex);
            return NULL;
        }
        //
        // read actual data size
        //
        actualDataSize = *(DWORD*)readPtr;

        if (ExpectedDataSize && ExpectedDataSize != actualDataSize) {

            DEBUGMSG ((
                DBG_ERROR,
                "BlobReadEx: Actual data size (%lu) different than expected data size (%lu)",
                actualDataSize,
                ExpectedDataSize
                ));

            BlobSetIndex (Blob, initialIndex);
            return NULL;
        }

        Blob->Index += DWSIZEOF (DWORD);
        readPtr += DWSIZEOF (DWORD);

    } else {
        actualDataSize = ExpectedDataSize;
    }

    if (!actualDataSize) {
        BlobSetIndex (Blob, initialIndex);
        return NULL;
    }

    if (ActualDataSize) {
        *ActualDataSize = actualDataSize;
    }

    //
    // don't read over end of file
    //
    if (readPtr + actualDataSize > BlobGetEOF (Blob)) {
        //
        // corrupt blob; undo anyway
        //
        MYASSERT (FALSE);   //lint !e506
        BlobSetIndex (Blob, initialIndex);
        return NULL;
    }

    if (!Data) {

        if (Pool) {
            Data = PmGetMemory (Pool, actualDataSize);
        } else {
            Data = pBlobAllocateMemory (actualDataSize);
        }

        if (!Data) {
            BlobSetIndex (Blob, initialIndex);
            return NULL;
        }
    }

    CopyMemory (Data, readPtr, actualDataSize);

    Blob->Index += actualDataSize;

    return Data;
}


BOOL
BlobWriteDword (
    IN OUT  POURBLOB Blob,
    IN      DWORD Data
    )

/*++

Routine Description:

    BlobWriteDword writes a DWORD at the current writing position in the specified blob

Arguments:

    Blob - Specifies the blob to write to
    Data - Specifies the DWORD

Return Value:

    TRUE if data was successfully stored in the blob

--*/

{
    return BlobWriteEx (Blob, BDT_DWORD, FALSE, DWSIZEOF (DWORD), &Data);
}


BOOL
BlobReadDword (
    IN OUT  POURBLOB Blob,
    OUT     PDWORD Data
    )

/*++

Routine Description:

    BlobReadDword reads a DWORD from the current reading position in the specified blob

Arguments:

    Blob - Specifies the blob to read from
    Data - Receives the DWORD

Return Value:

    TRUE if data was successfully read from the blob

--*/

{
    return BlobReadEx (Blob, BDT_DWORD, DWSIZEOF (DWORD), FALSE, NULL, Data, NULL) != NULL;
}


BOOL
BlobWriteQword (
    IN OUT  POURBLOB Blob,
    IN      DWORDLONG Data
    )

/*++

Routine Description:

    BlobWriteQword writes a DWORDLONG at the current writing position in the specified blob

Arguments:

    Blob - Specifies the blob to write to
    Data - Specifies the DWORDLONG

Return Value:

    TRUE if data was successfully stored in the blob

--*/

{
    return BlobWriteEx (Blob, BDT_QWORD, FALSE, DWSIZEOF (DWORDLONG), &Data);
}


BOOL
BlobReadQword (
    IN OUT  POURBLOB Blob,
    OUT     PDWORDLONG Data
    )

/*++

Routine Description:

    BlobReadQword reads a DWORDLONG from the current reading position in the specified blob

Arguments:

    Blob - Specifies the blob to read from
    Data - Receives the DWORDLONG

Return Value:

    TRUE if data was successfully read from the blob

--*/

{
    return BlobReadEx (Blob, BDT_QWORD, DWSIZEOF (DWORDLONG), FALSE, NULL, Data, NULL) != NULL;
}


/*++

Routine Description:

    BlobWriteString writes a string at the current writing position in the specified blob;
    the string is stored in UNICODE inside the blob if BF_UNICODESTRINGS is set

Arguments:

    Blob - Specifies the blob to write to
    Data - Specifies the string

Return Value:

    TRUE if data was successfully stored in the blob

--*/

BOOL
BlobWriteStringA (
    IN OUT  POURBLOB Blob,
    IN      PCSTR Data
    )
{
    PCWSTR unicodeString;
    BOOL b;

    if (BlobRecordsUnicodeStrings (Blob)) {
        unicodeString = ConvertAtoW (Data);
        b = BlobWriteStringW (Blob, unicodeString);
        FreeConvertedStr (unicodeString);
        return b;
    }
    return BlobWriteEx (Blob, BDT_SZA, TRUE, SizeOfStringA (Data), Data);
}


BOOL
BlobWriteStringW (
    IN OUT  POURBLOB Blob,
    IN      PCWSTR Data
    )
{
    return BlobWriteEx (Blob, BDT_SZW, TRUE, SizeOfStringW (Data), Data);
}


/*++

Routine Description:

    BlobReadString reads a string from the current reading position in the specified blob;
    the string may be converted to the ANSI/UNICODE format.
    If the blob doesn't store data types, this is assumed to be BDT_SZA for the ANSI version
    and BDT_SZW for the UNICODE version of this function

Arguments:

    Blob - Specifies the blob to read from
    Data - Receives a pointer to the new allocated string
    Pool - Specifies the pool to use for allocating memory;
           if NULL, the process heap will be used

Return Value:

    TRUE if data was successfully read from the blob

--*/

BOOL
BlobReadStringA (
    IN OUT  POURBLOB Blob,
    OUT     PCSTR* Data,
    IN      PMHANDLE Pool       OPTIONAL
    )
{
    PSTR ansiString;
    PCWSTR unicodeString;
    DWORD dataType;
    DWORD index;
    DWORD length = 0;

    //
    // save initial index; in case of failure it will be restored
    //
    index = BlobGetIndex (Blob);
    if (!index) {
        return FALSE;
    }

    ansiString = NULL;
    unicodeString = NULL;

    if (BlobRecordsDataType (Blob)) {

        dataType = BlobGetRecordedDataType (Blob);

        if (dataType == BDT_SZA) {

            ansiString = BlobReadEx (Blob, BDT_SZA, 0, TRUE, NULL, NULL, Pool);

        } else if (dataType == BDT_SZW) {

            unicodeString = (PCWSTR)BlobReadEx (Blob, BDT_SZW, 0, TRUE, &length, NULL, Pool);

        } else {

            DEBUGMSG ((DBG_ERROR, "BlobReadStringA: unexpected data type (%lu)", dataType));
            return FALSE;

        }
    } else {
        if (BlobRecordsUnicodeStrings (Blob)) {

            unicodeString = (PCWSTR)BlobReadEx (Blob, BDT_SZW, 0, TRUE, &length, NULL, Pool);

        } else {
            //
            // assume an ANSI string is stored there
            //
            ansiString = BlobReadEx (Blob, BDT_SZA, 0, TRUE, NULL, NULL, Pool);
        }
    }

    if (!ansiString) {

        if (!unicodeString) {
            return FALSE;
        }

        if (Pool) {
            ansiString = PmGetMemory (Pool, length);
        } else {
            ansiString = pBlobAllocateMemory (length);
        }

        if (ansiString) {
            DirectUnicodeToDbcsN (ansiString, unicodeString, length);
        }

        if (Pool) {
            PmReleaseMemory (Pool, (PVOID)unicodeString);
        } else {
            pBlobFreeMemory ((PVOID)unicodeString);
        }

        if (!ansiString) {
            //
            // recover prev state
            //
            BlobSetIndex (Blob, index);

            return FALSE;
        }
    }

    *Data = ansiString;
    return TRUE;
}

BOOL
BlobReadStringW (
    IN OUT  POURBLOB Blob,
    OUT     PCWSTR* Data,
    IN      PMHANDLE Pool       OPTIONAL
    )
{
    PWSTR unicodeString;
    PCSTR ansiString;
    DWORD dataType;
    DWORD index;
    DWORD length;

    //
    // save initial index; in case of failure it will be restored
    //
    index = BlobGetIndex (Blob);
    if (!index) {
        return FALSE;
    }

    if (BlobRecordsDataType (Blob)) {

        dataType = BlobGetRecordedDataType (Blob);

        if (dataType == BDT_SZW) {

            unicodeString = (PWSTR)BlobReadEx (Blob, BDT_SZW, 0, TRUE, NULL, NULL, Pool);

        } else if (dataType == BDT_SZA) {

            ansiString = BlobReadEx (Blob, BDT_SZA, 0, TRUE, &length, NULL, Pool);

            if (!ansiString) {
                return FALSE;
            }

            if (Pool) {
                unicodeString = PmGetMemory (Pool, length * DWSIZEOF (WCHAR));
            } else {
                unicodeString = pBlobAllocateMemory (length * DWSIZEOF (WCHAR));
            }
            if (unicodeString) {
                DirectDbcsToUnicodeN (unicodeString, ansiString, length);
            }

            if (Pool) {
                PmReleaseMemory (Pool, (PVOID)ansiString);
            } else {
                pBlobFreeMemory ((PVOID)ansiString);
            }

            if (!unicodeString) {
                //
                // recover prev state
                //
                BlobSetIndex (Blob, index);
                return FALSE;
            }

        } else {

            DEBUGMSG ((DBG_ERROR, "BlobReadStringW: unexpected data type (%lu)", dataType));
            return FALSE;

        }
    } else {
        //
        // assume an UNICODE string is stored there
        //
        unicodeString = (PWSTR)BlobReadEx (Blob, BDT_SZW, 0, TRUE, NULL, NULL, Pool);
    }

    if (!unicodeString) {
        return FALSE;
    }

    *Data = unicodeString;
    return TRUE;
}


/*++

Routine Description:

    BlobWriteMultiSz writes a multisz at the current writing position in the specified blob;
    the multisz is stored in UNICODE inside the blob if BF_UNICODESTRINGS is set

Arguments:

    Blob - Specifies the blob to write to
    Data - Specifies the multisz

Return Value:

    TRUE if data was successfully stored in the blob

--*/

BOOL
BlobWriteMultiSzA (
    IN OUT  POURBLOB Blob,
    IN      PCSTR Data
    )
{
    PWSTR unicodeString;
    BOOL b;
    DWORD stringSize = SizeOfMultiSzA (Data);

    if (BlobRecordsUnicodeStrings (Blob)) {
        unicodeString = AllocTextW (stringSize);
        DirectDbcsToUnicodeN (unicodeString, Data, stringSize);
        b = BlobWriteMultiSzW (Blob, unicodeString);
        FreeTextW (unicodeString);
        return b;
    }

    return BlobWriteEx (Blob, BDT_MULTISZA, TRUE, stringSize, Data);
}

BOOL
BlobWriteMultiSzW (
    IN OUT  POURBLOB Blob,
    IN      PCWSTR Data
    )
{
    return BlobWriteEx (Blob, BDT_MULTISZW, TRUE, SizeOfMultiSzW (Data), Data);
}



/*++

Routine Description:

    BlobReadMultiSz reads a multisz from the current reading position in the specified blob;
    the string may be converted to the ANSI/UNICODE format.
    If the blob doesn't store data types, this is assumed to be BDT_MULTISZA for the ANSI version
    and BDT_MULTISZW for the UNICODE version of this function

Arguments:

    Blob - Specifies the blob to read from
    Data - Receives a pointer to the new allocated multisz
    Pool - Specifies the pool to use for allocating memory;
           if NULL, the process heap will be used

Return Value:

    TRUE if data was successfully read from the blob

--*/

BOOL
BlobReadMultiSzA (
    IN OUT  POURBLOB Blob,
    OUT     PCSTR* Data,
    IN      PMHANDLE Pool       OPTIONAL
    )
{
    PSTR ansiString;
    PCWSTR unicodeString;
    DWORD dataType;
    DWORD index;
    DWORD length = 0;

    //
    // save initial index; in case of failure it will be restored
    //
    index = BlobGetIndex (Blob);
    if (!index) {
        return FALSE;
    }

    ansiString = NULL;
    unicodeString = NULL;

    if (BlobRecordsDataType (Blob)) {

        dataType = BlobGetRecordedDataType (Blob);

        if (dataType == BDT_MULTISZA) {

            ansiString = BlobReadEx (Blob, BDT_MULTISZA, 0, TRUE, NULL, NULL, Pool);

        } else if (dataType == BDT_MULTISZW) {

            unicodeString = (PCWSTR)BlobReadEx (Blob, BDT_MULTISZW, 0, TRUE, &length, NULL, Pool);

        } else {

            DEBUGMSG ((DBG_ERROR, "BlobReadMultiSzA: unexpected data type (%lu)", dataType));
            return FALSE;

        }
    } else {
        if (BlobRecordsUnicodeStrings (Blob)) {

            unicodeString = (PCWSTR)BlobReadEx (Blob, BDT_MULTISZW, 0, TRUE, &length, NULL, Pool);

        } else {
            //
            // assume an ANSI string is stored there
            //
            ansiString = BlobReadEx (Blob, BDT_MULTISZA, 0, TRUE, NULL, NULL, Pool);
        }
    }

    if (!ansiString) {
        if (!unicodeString) {
            return FALSE;
        }

        if (Pool) {
            ansiString = PmGetMemory (Pool, length);
        } else {
            ansiString = pBlobAllocateMemory (length);
        }

        if (ansiString) {
            DirectUnicodeToDbcsN (ansiString, unicodeString, length);
        }

        if (Pool) {
            PmReleaseMemory (Pool, (PVOID)unicodeString);
        } else {
            pBlobFreeMemory ((PVOID)unicodeString);
        }

        if (!ansiString) {
            //
            // recover prev state
            //
            BlobSetIndex (Blob, index);
            return FALSE;
        }
    }

    *Data = ansiString;
    return TRUE;
}

BOOL
BlobReadMultiSzW (
    IN OUT  POURBLOB Blob,
    OUT     PCWSTR* Data,
    IN      PMHANDLE Pool       OPTIONAL
    )
{
    PWSTR unicodeString;
    PCSTR ansiString;
    DWORD dataType;
    DWORD index;
    DWORD length;

    //
    // save initial index; in case of failure it will be restored
    //
    index = BlobGetIndex (Blob);
    if (!index) {
        return FALSE;
    }

    if (BlobRecordsDataType (Blob)) {

        dataType = BlobGetRecordedDataType (Blob);

        if (dataType == BDT_MULTISZW) {

            unicodeString = (PWSTR)BlobReadEx (Blob, BDT_MULTISZW, 0, TRUE, NULL, NULL, Pool);

        } else if (dataType == BDT_MULTISZA) {

            ansiString = BlobReadEx (Blob, BDT_MULTISZA, 0, TRUE, &length, NULL, Pool);

            if (!ansiString) {
                return FALSE;
            }

            if (Pool) {
                unicodeString = PmGetMemory (Pool, length * DWSIZEOF (WCHAR));
            } else {
                unicodeString = pBlobAllocateMemory (length * DWSIZEOF (WCHAR));
            }

            if (unicodeString) {
                DirectDbcsToUnicodeN (unicodeString, ansiString, length);
            }

            if (Pool) {
                PmReleaseMemory (Pool, (PVOID)ansiString);
            } else {
                pBlobFreeMemory ((PVOID)ansiString);
            }

            if (!unicodeString) {
                //
                // recover prev state
                //
                BlobSetIndex (Blob, index);

                return FALSE;
            }

        } else {

            DEBUGMSG ((DBG_ERROR, "BlobReadMultiSzW: unexpected data type (%lu)", dataType));
            return FALSE;

        }
    } else {
        //
        // assume an UNICODE string is stored there
        //
        unicodeString = (PWSTR)BlobReadEx (Blob, BDT_MULTISZW, 0, TRUE, NULL, NULL, Pool);
    }

    if (!unicodeString) {
        return FALSE;
    }

    *Data = unicodeString;
    return TRUE;
}


BOOL
BlobWriteBinaryEx (
    IN OUT  POURBLOB Blob,
    IN      PBYTE Data,
    IN      DWORD Size,
    IN      BOOL RecordDataSize
    )

/*++

Routine Description:

    BlobWriteBinary writes a buffer at the current writing position in the specified blob

Arguments:

    Blob - Specifies the blob to write to
    Data - Specifies the source buffer
    Size - Specifies the size of the buffer
    RecordDataSize - Specifies TRUE if data size should be recorded, too

Return Value:

    TRUE if data was successfully stored in the blob

--*/

{
    return BlobWriteEx (Blob, BDT_BINARY, RecordDataSize, Size, Data);
}


BOOL
BlobReadBinary (
    IN OUT  POURBLOB Blob,
    OUT     PBYTE* Data,
    OUT     PDWORD Size,
    IN      PMHANDLE Pool       OPTIONAL
    )

/*++

Routine Description:

    BlobReadBinary reads a buffer from the current reading position in the specified blob

Arguments:

    Blob - Specifies the blob to read from
    Data - Receives a pointer to the new allocated buffer
    Size - Receives the size of the buffer
    Pool - Specifies the pool to use for allocating memory;
           if NULL, the process heap will be used

Return Value:

    TRUE if data was successfully read from the blob

--*/

{
    *Data = BlobReadEx (Blob, BDT_BINARY, 0, TRUE, Size, NULL, Pool);
    return *Data != NULL;
}


BOOL
BlobWriteToFile (
    IN      POURBLOB Blob,
    IN      HANDLE File
    )

/*++

Routine Description:

    BlobWriteToFile writes the specified blob to the given file

Arguments:

    Blob - Specifies the blob to save
    File - Specifies the handle of the file to write the blob to

Return Value:

    TRUE if blob was successfully written to the file

--*/

{
    BLOBHDR header;
    DWORD d;

    if (!Blob->End) {
        DEBUGMSG ((DBG_BLOBS, "BlobWriteToFile: Did not write empty blob to file"));
        return FALSE;
    }

    //
    // save blob's Flags and End position
    //
    header.BlobSignature = BLOB_SIGNATURE;
    header.DataSize = Blob->End;
    header.Flags = Blob->Flags;

    if (!WriteFile (File, &header, DWSIZEOF (BLOBHDR), &d, NULL) || d != DWSIZEOF (BLOBHDR)) {
        DEBUGMSG ((DBG_ERROR, "BlobWriteToFile: Error writing blob header!"));
        return FALSE;
    }
    if (!WriteFile (File, Blob->Data, Blob->End, &d, NULL) || d != Blob->End) {
        DEBUGMSG ((DBG_ERROR, "BlobWriteToFile: Error writing blob data!"));
        return FALSE;
    }
    return TRUE;
}


BOOL
BlobReadFromFile (
    OUT     POURBLOB Blob,
    IN      HANDLE File
    )

/*++

Routine Description:

    BlobReadFromFile reads data from the given file in the specified blob

Arguments:

    Blob - Receives the data
    File - Specifies the handle of the file to read from

Return Value:

    TRUE if blob was successfully read from the file

--*/

{
    BLOBHDR header;
    DWORD d;

    //
    // read blob's Flags and End position
    //
    if (!ReadFile (File, &header, DWSIZEOF (BLOBHDR), &d, NULL) || d != DWSIZEOF (BLOBHDR)) {
        DEBUGMSG ((DBG_ERROR, "BlobReadFromFile: Error reading blob header!"));
        return FALSE;
    }

    if (header.BlobSignature != BLOB_SIGNATURE) {
        DEBUGMSG ((DBG_ERROR, "BlobReadFromFile: Not a valid blob signature!"));
        return FALSE;
    }

    Blob->Data = pBlobAllocateMemory (header.DataSize);
    if (!Blob->Data) {
        return FALSE;
    }

    if (!ReadFile (File, Blob->Data, header.DataSize, &d, NULL) || d != header.DataSize) {
        DEBUGMSG ((DBG_ERROR, "BlobReadFromFile: Error reading blob data!"));
        pBlobFreeMemory (Blob->Data);
        Blob->Data = NULL;
        return FALSE;
    }

    Blob->AllocSize = header.DataSize;
    Blob->End = header.DataSize;
    Blob->Flags = header.Flags;
    Blob->Index = 0;
    return TRUE;
}


BOOL
BlobsAdd (
    IN OUT  PBLOBS BlobsArray,
    IN      POURBLOB Blob
    )

/*++

Routine Description:

    BlobsAdd adds the specified Blob to a blobs array

Arguments:

    BlobsArray - Specifies the array to add to
    Blob - Specifies the blob to add

Return Value:

    TRUE if the new blob pointer was added successfully

--*/

{
    ASSERT_VALID_BLOBS_ARRAY (BlobsArray);

    if (!BlobsArray->BlobsGrowCount) {
        BlobsArray->BlobsGrowCount = BLOBS_GROWCOUNT_DEFAULT;
    }

    if (!BlobsArray->Blobs) {

        BlobsArray->Blobs = (POURBLOB*)pBlobAllocateMemory (
                                        BlobsArray->BlobsGrowCount * DWSIZEOF (POURBLOB)
                                        );
        if (!BlobsArray->Blobs) {
            DEBUGMSG ((DBG_ERROR, "BlobsAddE: Initial alloc failed"));
            return FALSE;
        }
        BlobsArray->Signature = BLOBS_SIGNATURE;
        BlobsArray->BlobsAllocated = BlobsArray->BlobsGrowCount;
        BlobsArray->BlobsCount = 0;

    } else if (BlobsArray->BlobsCount == BlobsArray->BlobsAllocated) {

        BlobsArray->BlobsAllocated += BlobsArray->BlobsGrowCount;
        BlobsArray->Blobs = (POURBLOB*)pReAllocateMemory (
                                        BlobsArray->Blobs,
                                        BlobsArray->BlobsAllocated * DWSIZEOF (POURBLOB)
                                        );
        if (!BlobsArray->Blobs) {
            BlobsArray->BlobsAllocated -= BlobsArray->BlobsGrowCount;
            DEBUGMSG ((DBG_ERROR, "BlobsAdd: Realloc failed"));
            return FALSE;
        }
    }

    *(BlobsArray->Blobs + BlobsArray->BlobsCount) = Blob;
    BlobsArray->BlobsCount++;

    ASSERT_VALID_BLOBS_ARRAY (BlobsArray);
    return TRUE;
}


VOID
BlobsFree (
    IN OUT  PBLOBS BlobsArray,
    IN      BOOL DestroyBlobs
    )

/*++

Routine Description:

    BlobsFree destroys the array and optionally destroys all blobs in it

Arguments:

    BlobsArray - Specifies the array to delete
    DestroyBlobs - Specifies TRUE if the component blobs are to be deleted, too

Return Value:

    none

--*/

{
    BLOB_ENUM e;

    ASSERT_VALID_BLOBS_ARRAY (BlobsArray);

    if (DestroyBlobs) {
        if (EnumFirstBlob (&e, BlobsArray)) {
            do {
                BlobDestroy (e.CurrentBlob);
            } while (EnumNextBlob (&e));
        }
    }

    pBlobFreeMemory (BlobsArray->Blobs);
    ZeroMemory (BlobsArray, DWSIZEOF (BLOBS));
}


BOOL
EnumFirstBlob (
    OUT     PBLOB_ENUM BlobEnum,
    IN      PBLOBS BlobsArray
    )

/*++

Routine Description:

    EnumFirstBlob enumerates the first blob in the given array

Arguments:

    BlobEnum - Receives enum info
    BlobsArray - Specifies the array to enum from

Return Value:

    TRUE if a first blob was found; FALSE if array is empty

--*/

{
    ASSERT_VALID_BLOBS_ARRAY (BlobsArray);

    BlobEnum->Index = 0;
    BlobEnum->Array = BlobsArray;
    return EnumNextBlob (BlobEnum);
}


BOOL
EnumNextBlob (
    IN OUT  PBLOB_ENUM BlobEnum
    )

/*++

Routine Description:

    EnumNextBlob enumerates the next blob in the given array

Arguments:

    BlobEnum - Specifies/receives enum info

Return Value:

    TRUE if a next blob was found; FALSE if no more blobs

--*/

{
    if (BlobEnum->Index >= BlobEnum->Array->BlobsCount) {
        return FALSE;
    }

    BlobEnum->CurrentBlob = *(BlobEnum->Array->Blobs + BlobEnum->Index);
    BlobEnum->Index++;
    return TRUE;
}


BOOL
BlobsWriteToFile (
    IN      PBLOBS BlobsArray,
    IN      HANDLE File
    )

/*++

Routine Description:

    BlobsWriteToFile writes the specified blobs array to the given file

Arguments:

    BlobsArray - Specifies the blobs array to save
    File - Specifies the handle of the file to write the array to

Return Value:

    TRUE if array was successfully written to the file

--*/

{
    BLOBSARRAYHDR header;
    DWORD d;
    POURBLOB* blob;

    if (!BlobsArray->BlobsCount) {
        DEBUGMSG ((DBG_BLOBS, "BlobsWriteToFile: Did not write empty blobs array to file"));
        return FALSE;
    }

    //
    // save blobs count
    //
    header.BlobsArraySignature = BLOBS_SIGNATURE;
    header.BlobsCount = BlobsArray->BlobsCount;

    if (!WriteFile (File, &header, DWSIZEOF (BLOBSARRAYHDR), &d, NULL) ||
        d != DWSIZEOF (BLOBSARRAYHDR)
        ) {
        DEBUGMSG ((DBG_ERROR, "BlobsWriteToFile: Error writing blobs array header!"));
        return FALSE;
    }
    for (blob = BlobsArray->Blobs; blob < BlobsArray->Blobs + BlobsArray->BlobsCount; blob++) {
        if (!BlobWriteToFile (*blob, File)) {
            DEBUGMSG ((
                DBG_BLOBS,
                "BlobsWriteToFile: Error writing blob # %lu to file",
                blob - BlobsArray->Blobs
                ));
            return FALSE;
        }
    }

    return TRUE;
}


BOOL
BlobsReadFromFile (
    OUT     PBLOBS BlobsArray,
    IN      HANDLE File
    )

/*++

Routine Description:

    BlobsReadFromFile reads data from the given file in the specified blobs array

Arguments:

    BlobsArray - Receives the data
    File - Specifies the handle of the file to read from

Return Value:

    TRUE if array was successfully read from the file

--*/

{
    BLOBSARRAYHDR header;
    DWORD d;
    UINT u;
    POURBLOB blob;

    //
    // read blobs count
    //
    if (!ReadFile (File, &header, DWSIZEOF (BLOBSARRAYHDR), &d, NULL) ||
        d != DWSIZEOF (BLOBSARRAYHDR)
        ) {
        DEBUGMSG ((DBG_ERROR, "BlobsReadFromFile: Error reading blobs array header!"));
        return FALSE;
    }

    if (header.BlobsArraySignature != BLOBS_SIGNATURE) {
        DEBUGMSG ((DBG_ERROR, "BlobsReadFromFile: Not a valid blobs array signature!"));
        return FALSE;
    }

    BlobsArray->Blobs = (POURBLOB*)pBlobAllocateMemory (header.BlobsCount * DWSIZEOF (POURBLOB*));
    if (!BlobsArray->Blobs) {
        return FALSE;
    }

    ZeroMemory (BlobsArray->Blobs, header.BlobsCount * DWSIZEOF (POURBLOB));

    BlobsArray->Signature = BLOBS_SIGNATURE;
    BlobsArray->BlobsAllocated = header.BlobsCount;
    BlobsArray->BlobsCount = 0;
    BlobsArray->BlobsGrowCount = BLOBS_GROWCOUNT_DEFAULT;

    for (u = 0; u < header.BlobsCount; u++) {

        blob = BlobCreate ();
        if (!blob) {
            return FALSE;
        }

        if (!BlobReadFromFile (blob, File)) {

            DEBUGMSG ((
                DBG_BLOBS,
                "BlobsReadFromFile: Error reading blob # %lu from file",
                u
                ));

            BlobsFree (BlobsArray, TRUE);
            return FALSE;
        }

        if (!BlobsAdd (BlobsArray, blob)) {
            DEBUGMSG ((
                DBG_BLOBS,
                "BlobsReadFromFile: Error adding blob # %lu to array",
                u
                ));

            BlobsFree (BlobsArray, TRUE);
            return FALSE;
        }
    }

    return TRUE;
}