/*++

Copyright (c) 1991-92  Microsoft Corporation

Module Name:

    packstr.c

Abstract:

    Contains utilities for allocating and/or packing buffers that contain
    a fixed section and a variable (string) section.  The following
    functions are available:

        NetpPackString
        NetpCopyStringToBuffer
        NetpCopyDataToBuffer
        NetpAllocateEnumBuffer

Author:

    various

Environment:

    User Mode -Win32

Revision History:

    30-Apr-1991     danl
        NetpAllocateEnumBuffer:  Removed use of NetApiBufferFree.  It has
        been changed to use MIDL_user_allocate and MIDL_user_free.
        Also added NT-style headers where needed.
    16-Apr-1991 JohnRo
        Clarify UNICODE handling of pack and copy routines.  Got rid of tabs
        in source file.
    21-Nov-1991 JohnRo
        Removed NT dependencies to reduce recompiles.
    15-Apr-1992 JohnRo
        FORMAT_POINTER is obsolete.

--*/

// These must be included first:

#include <windef.h>     // IN, OUT, etc.
#include <lmcons.h>     // Needed by <netlib.h>.
#include <lmerr.h>      // NERR_*
#include <rpcutil.h>    // MIDL_user_allocate & MIDL_user_free

// These may be included in any order:

#include <align.h>      // ROUND_UP_COUNT().
#include <debuglib.h>   // IF_DEBUG().
#include <netdebug.h>   // NetpKdPrint(), FORMAT_ equates.
#include <netlib.h>     // My prototype.
#include <tstring.h>    // STRCPY(), STRLEN(), STRNCPY().


DWORD
NetpPackString(
    IN OUT LPTSTR * string,     // pointer by reference: string to be copied.
    IN LPBYTE dataend,          // pointer to end of fixed size data.
    IN OUT LPTSTR * laststring  // pointer by reference: top of string data.
    )

/*++

Routine Description:

    NetPackString is used to stuff variable-length data, which
    is pointed to by (surpise!) a pointer.  The data is assumed
    to be a nul-terminated string (ASCIIZ).  Repeated calls to
    this function are used to pack data from an entire structure.

    Upon first call, the laststring pointer should point to just
    past the end of the buffer.  Data will be copied into the buffer from
    the end, working towards the beginning.  If a data item cannot
    fit, the pointer will be set to NULL, else the pointer will be
    set to the new data location.

    Pointers which are passed in as NULL will be set to be pointer
    to and empty string, as the NULL-pointer is reserved for
    data which could not fit as opposed to data not available.

    See the test case for sample usage.  (tst/packtest.c)


Arguments:

    string - pointer by reference:  string to be copied.

    dataend - pointer to end of fixed size data.

    laststring - pointer by reference:  top of string data.

Return Value:

    0  - if it could not fit data into the buffer.  Or...

    sizeOfData - the size of data stuffed (guaranteed non-zero)

--*/

{
    DWORD size;

    IF_DEBUG(PACKSTR) {
        NetpKdPrint(("NetpPackString:\n"));
        NetpKdPrint(("  string=" FORMAT_LPVOID
                ", *string=" FORMAT_LPVOID
                ", **string='" FORMAT_LPSTR "'\n",
                (LPVOID) string, (LPVOID) *string, *string));
        NetpKdPrint(("  end=" FORMAT_LPVOID "\n", (LPVOID) dataend));
        NetpKdPrint(("  last=" FORMAT_LPVOID
                ", *last=" FORMAT_LPVOID
                ", **last='" FORMAT_LPSTR "'\n",
                (LPVOID) laststring, (LPVOID) *laststring, *laststring));
    }

    //
    //  convert NULL ptr to pointer to NULL string
    //

    if (*string == NULL) {
        // BUG 20.1160 - replaced (dataend +1) with dataend
        // to allow for a NULL ptr to be packed
        // (as a NULL string) with one byte left in the
        // buffer. - ERICPE
        //

        if ( *laststring > (LPTSTR)dataend ) {
            *(--(*laststring)) = 0;
            *string = *laststring;
            return 1;
        } else {
            return 0;
        }
    }

    //
    //  is there room for the string?
    //

    size = STRLEN(*string) + 1;
    if ( ((DWORD)(*laststring - (LPTSTR)dataend)) < size) {
        *string = NULL;
        return(0);
    } else {
        *laststring -= size;
        STRCPY(*laststring, *string);
        *string = *laststring;
        return(size);
    }
} // NetpPackString


BOOL
NetpCopyStringToBuffer (
    IN LPTSTR String OPTIONAL,
    IN DWORD CharacterCount,
    IN LPBYTE FixedDataEnd,
    IN OUT LPTSTR *EndOfVariableData,
    OUT LPTSTR *VariableDataPointer
    )

/*++

Routine Description:

    This routine puts a single variable-length string into an output buffer.
    The string is not written if it would overwrite the last fixed structure
    in the buffer.

    The code is swiped from svcsupp.c written by DavidTr.

    Sample usage:

            LPBYTE FixedDataEnd = OutputBuffer + sizeof(WKSTA_INFO_202);
            LPTSTR EndOfVariableData = OutputBuffer + OutputBufferSize;

            //
            // Copy user name
            //

            NetpCopyStringToBuffer(
                UserInfo->UserName.Buffer;
                UserInfo->UserName.Length;
                FixedDataEnd,
                &EndOfVariableData,
                &WkstaInfo->wki202_username
                );

Arguments:

    String - Supplies a pointer to the source string to copy into the
        output buffer.  If String is null then a pointer to a zero terminator
        is inserted into output buffer.

    CharacterCount - Supplies the length of String, not including zero
        terminator.

    FixedDataEnd - Supplies a pointer to just after the end of the last
        fixed structure in the buffer.

    EndOfVariableData - Supplies an address to a pointer to just after the
        last position in the output buffer that variable data can occupy.
        Returns a pointer to the string written in the output buffer.

    VariableDataPointer - Supplies a pointer to the place in the fixed
        portion of the output buffer where a pointer to the variable data
        should be written.

Return Value:

    Returns TRUE if string fits into output buffer, FALSE otherwise.

--*/
{
    DWORD BytesNeeded = (CharacterCount + 1) * sizeof(TCHAR);

    IF_DEBUG(PACKSTR) {
        NetpKdPrint(("NetpStringToBuffer: String at " FORMAT_LPVOID
                ", CharacterCount=" FORMAT_DWORD
                ",\n  FixedDataEnd at " FORMAT_LPVOID
                ", *EndOfVariableData at " FORMAT_LPVOID
                ",\n  VariableDataPointer at " FORMAT_LPVOID
                ", BytesNeeded=" FORMAT_DWORD ".\n",
                (LPVOID) String, CharacterCount, FixedDataEnd,
                (LPVOID) *EndOfVariableData,
                (LPVOID) VariableDataPointer, BytesNeeded));
    }

    //
    // Determine if string will fit, allowing for a zero terminator.  If no,
    // just set the pointer to NULL.
    //

    if ((*EndOfVariableData - (CharacterCount+1)) >= (LPTSTR)FixedDataEnd) {

        //
        // It fits.  Move EndOfVariableData pointer up to the location where
        // we will write the string.
        //

        *EndOfVariableData -= (CharacterCount+1);

        //
        // Copy the string to the buffer if it is not null.
        //

        if (CharacterCount > 0 && String != NULL) {

            STRNCPY(*EndOfVariableData, String, CharacterCount);
        }

        //
        // Set the zero terminator.
        //

        *(*EndOfVariableData + CharacterCount) = TCHAR_EOS;

        //
        // Set up the pointer in the fixed data portion to point to where the
        // string is written.
        //

        *VariableDataPointer = *EndOfVariableData;

        return TRUE;

    }
    else {

        //
        // It doesn't fit.  Set the offset to NULL.
        //

        *VariableDataPointer = NULL;

        return FALSE;
    }
} // NetpCopyStringToBuffer


BOOL
NetpCopyDataToBuffer (
    IN LPBYTE Data,
    IN DWORD ByteCount,
    IN LPBYTE FixedDataEnd,
    IN OUT LPBYTE *EndOfVariableData,
    OUT LPBYTE *VariableDataPointer,
    IN DWORD Alignment
    )

/*++

Routine Description:

    This routine puts the specified data into an output buffer.
    The data is not written if it would overwrite the last fixed structure
    in the buffer.

    The output buffer is aligned as requested.

    Sample usage:

            LPBYTE FixedDataEnd = OutputBuffer + sizeof(WKSTA_INFO_202);
            LPBYTE EndOfVariableData = OutputBuffer + OutputBufferSize;

            //
            // Copy Logon hours
            //

            NetpCopyDataToBuffer(
                StructurePointer,
                sizeof( STRUCTURE_TYPE),
                FixedDataEnd,
                &EndOfVariableData,
                &UserInfo->usri2->LogonHours,
                sizeof(ULONG)
                );

Arguments:

    Data - Supplies a pointer to the source data to copy into the
        output buffer.  If Data is null then a null pointer is returned in
        VariableDataPointer.

    ByteCount - Supplies the length of Data.

    FixedDataEnd - Supplies a pointer to just after the end of the last
        fixed structure in the buffer.

    EndOfVariableData - Supplies an address to a pointer to just after the
        last position in the output buffer that variable data can occupy.
        Returns a pointer to the data written in the output buffer.

    VariableDataPointer - Supplies a pointer to the place in the fixed
        portion of the output buffer where a pointer to the variable data
        should be written.

    Alignment - Supplies the required alignment of the data expressed
        as the number of bytes in the primitive datatype (e.g., 1 for byte,
        2 for short, 4 for long, and 8 for quad).

Return Value:

    Returns TRUE if data fits into output buffer, FALSE otherwise.

--*/
{

    LPBYTE NewEndOfVariableData;

    //
    // If there is no data to copy, just return success.
    //

    if ( Data == NULL ) {
        *VariableDataPointer = NULL;
        return TRUE;
    }

    //
    // Compute where the data will be copied to (taking alignment into
    //  consideration).
    //
    // We may end up with a few unused byte after the copied data.
    //

    NetpAssert((Alignment == 1) || (Alignment == 2) ||
               (Alignment == 4) || (Alignment == 8));

    NewEndOfVariableData = (LPBYTE)
        ( ((DWORD)(*EndOfVariableData - ByteCount)) &
        (0xFFFFFFFF - (Alignment - 1)));

    //
    // If the data doesn't fit into the buffer, error out
    //

    if ( NewEndOfVariableData < FixedDataEnd) {
        *VariableDataPointer = NULL;
        return FALSE;
    }

    //
    // Copy the data to the buffer
    //

    if (ByteCount > 0) {
        NetpMoveMemory(NewEndOfVariableData, Data, ByteCount);
    }

    //
    // Return the pointer to the new data and update the pointer to
    // how much of the buffer we've used so far.
    //

    *VariableDataPointer = NewEndOfVariableData;
    *EndOfVariableData = NewEndOfVariableData;

    return TRUE;

} // NetpCopyDataToBuffer


NET_API_STATUS
NetpAllocateEnumBuffer(
    IN OUT PBUFFER_DESCRIPTOR BufferDescriptor,
    IN BOOL IsGet,
    IN DWORD PrefMaxSize,
    IN DWORD NeededSize,
    IN VOID (*RelocationRoutine)( IN DWORD RelocationParameter,
                                  IN OUT PBUFFER_DESCRIPTOR BufferDescriptor,
                                  IN PTRDIFF_T Offset ),
    IN DWORD RelocationParameter
    )

/*++

Routine Description:

    Ensures a buffer is allocated which contains the needed size.

Arguments:

    BufferDescriptor - Points to a structure which describes the allocated
        buffer.  On the first call, pass in BufferDescriptor->Buffer set
        to NULL.  On subsequent calls (in the 'enum' case), pass in the
        structure just as it was passed back on a previous call.

        The caller must deallocate the BufferDescriptor->Buffer using
        MIDL_user_free if it is non-null.

    IsGet - TRUE iff this is a 'get' rather than an 'enum' operation.

    PrefMaxSize - Callers prefered maximum size

    NeededSize - the number of bytes which must be available in the allocated
        buffer.

    RelocationRoutine - Supplies a pointer to a routine that will be called
        when the buffer needs to be relocated.  The routine is called with
        both the fixed portion and the strings already copied.  Merely,
        the pointers to the strings need to be adjusted.

        The 'Offset' parameter to the relocation routine merely needs to
        be added to the each pointer within the allocated buffer which points
        to a place within the allocated buffer.  It is a byte-offset.  This
        design depends on a 'flat' address space where the addresses of two
        unrelated pointers can simply be subtracted.

    RelocationParameter - Supplies a parameter which will (in turn) be passed
        to the relocation routine.

Return Value:

    Error code for the operation.

    If this is an Enum call, the status can be ERROR_MORE_DATA implying that
    the Buffer has grown to PrefMaxSize and that this much data should
    be returned to the caller.

--*/

{
    NET_API_STATUS NetStatus;
    PBUFFER_DESCRIPTOR Desc = BufferDescriptor;

    IF_DEBUG(PACKSTR) {
        NetpKdPrint((
            "NetpAllocateEnumBuffer: Isget: " FORMAT_DWORD " PrefMaxSize: "
            FORMAT_HEX_DWORD " NeededSize: " FORMAT_HEX_DWORD "\n",
            IsGet, PrefMaxSize, NeededSize ));

        NetpKdPrint((
            "+BufferDescriptor: Buffer: " FORMAT_HEX_DWORD " AllocSize: "
            FORMAT_HEX_DWORD " AllocIncr: " FORMAT_HEX_DWORD "\n",
            Desc->Buffer, Desc->AllocSize, Desc->AllocIncrement ));
        NetpKdPrint(( "                  variable: " FORMAT_HEX_DWORD " Fixed:"
            FORMAT_HEX_DWORD "\n",
            Desc->EndOfVariableData, Desc->FixedDataEnd ));
    }

    //
    // If this is not a resume, initialize a buffer descriptor.
    //

    if ( Desc->Buffer == NULL ) {

        //
        // Allocate the return buffer.
        //
        // If this is a Getinfo call, allocate the buffer the correct size.
        //
        // If the is an Enum call, this is an initial allocation which
        // might be reallocated later if this size isn't big enough.
        // The initial allocation is the user's prefered maximum
        // length unless that length is deemed to be very large.
        // In that case we allocate a good sized buffer and will
        // reallocate later as needed.
        //

        if ( IsGet ) {

            Desc->AllocSize = NeededSize;

        } else {

            if ( PrefMaxSize < NeededSize ) {
                NetStatus = NERR_BufTooSmall;
                goto Cleanup;
            }

            Desc->AllocSize = min(PrefMaxSize, NETP_ENUM_GUESS);
            Desc->AllocSize = max(NeededSize, Desc->AllocSize );

        }

        // Some callers pack data on that top end of this buffer so
        // ensure the buffer size allows for proper alignment.
        Desc->AllocSize = ROUND_UP_COUNT( Desc->AllocSize, ALIGN_WORST );

        Desc->AllocIncrement = Desc->AllocSize;
        IF_DEBUG(PACKSTR) {
            NetpKdPrint((" Allocated size : " FORMAT_HEX_DWORD "\n",
                Desc->AllocSize ));
        }

        Desc->Buffer = MIDL_user_allocate(Desc->AllocSize);

        if (Desc->Buffer == NULL) {
            goto Cleanup;
        }

        IF_DEBUG(PACKSTR) {
            NetpKdPrint((" Allocated: " FORMAT_HEX_DWORD "\n", Desc->Buffer ));
        }

        Desc->FixedDataEnd = Desc->Buffer;
        Desc->EndOfVariableData = Desc->Buffer + Desc->AllocSize;

    //
    // If this is a resume, get the buffer descriptor that the caller passed in.
    //

    } else {

        //
        // If the entry doesn't fit, reallocate a larger return buffer
        //

        if ((DWORD)(Desc->EndOfVariableData - Desc->FixedDataEnd) < NeededSize){

            BUFFER_DESCRIPTOR OldDesc;
            DWORD FixedSize;        // Total size of the fixed data
            DWORD StringSize;       // Total size of the string data

            //
            // If the buffer is as big as is allowed,
            //      we're all done for now.
            //

            if ( Desc->AllocSize >= PrefMaxSize ) {
                NetStatus = ERROR_MORE_DATA;
                goto Cleanup;
            }

            //
            // Allocate a larger return buffer.
            //

            OldDesc = *Desc;

            Desc->AllocSize += Desc->AllocIncrement;
            Desc->AllocSize = min( Desc->AllocSize, PrefMaxSize );
            IF_DEBUG(PACKSTR) {
                NetpKdPrint(("Re-Allocated size : " FORMAT_HEX_DWORD "\n",
                    Desc->AllocSize ));
            }
            Desc->Buffer = MIDL_user_allocate( Desc->AllocSize );

            if ( Desc->Buffer == NULL ) {
                MIDL_user_free( OldDesc.Buffer );
                goto Cleanup;
            }
            IF_DEBUG(PACKSTR) {
                NetpKdPrint(("ReAllocated: " FORMAT_HEX_DWORD "\n",
                             Desc->Buffer ));
            }

            //
            // Copy the fixed length portion of the data to the new buffer
            //

            FixedSize = OldDesc.FixedDataEnd - OldDesc.Buffer;

            RtlCopyMemory( Desc->Buffer,
                            OldDesc.Buffer,
                            FixedSize );

            Desc->FixedDataEnd = Desc->Buffer + FixedSize ;

            //
            // Copy the string portion of the data to the new buffer
            //

            StringSize = OldDesc.AllocSize -
                                (OldDesc.EndOfVariableData - OldDesc.Buffer);

            Desc->EndOfVariableData = Desc->Buffer + Desc->AllocSize - StringSize;

            RtlCopyMemory( Desc->EndOfVariableData, OldDesc.EndOfVariableData, StringSize );

            //
            // Callback to allow the pointers into the string data to be
            // relocated.
            //
            // The callback routine merely needs to add the value I pass it
            // to all of the pointers from the fixed area to the string area.
            //

            (*RelocationRoutine)(
                    RelocationParameter,
                    Desc,
                    Desc->EndOfVariableData - OldDesc.EndOfVariableData );

            //
            // Free the old buffer.
            //

            MIDL_user_free( OldDesc.Buffer );
        }
    }

    NetStatus = NERR_Success;

    //
    // Clean up
    //

Cleanup:
    //
    //
    //

    if ( NetStatus != NERR_Success && NetStatus != ERROR_MORE_DATA &&
        Desc->Buffer != NULL ) {

        MIDL_user_free (Desc->Buffer );
        Desc->Buffer = NULL;
    }

    IF_DEBUG(PACKSTR) {
        NetpKdPrint((
            "BufferDescriptor: Buffer: " FORMAT_HEX_DWORD " AllocSize: "
            FORMAT_HEX_DWORD " AllocIncr: " FORMAT_HEX_DWORD "\n",
            Desc->Buffer, Desc->AllocSize, Desc->AllocIncrement ));
        NetpKdPrint(( "                  variable: " FORMAT_HEX_DWORD " Fixed:"
            FORMAT_HEX_DWORD "\n",
            Desc->EndOfVariableData, Desc->FixedDataEnd ));
    }

    return NetStatus;

} // NetpAllocateEnumBuffer
