/*
§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§

    (C) Copyright 1998
        All rights reserved.

§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§

  Portions of this software are:

    (C) Copyright 1994 TriplePoint, Inc. -- http://www.TriplePoint.com
        License to use this software is granted under the same terms
        outlined in the Microsoft Windows Device Driver Development Kit.

    (C) Copyright 1992 Microsoft Corp. -- http://www.Microsoft.com
        License to use this software is granted under the terms outlined in
        the Microsoft Windows Device Driver Development Kit.

§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§

@doc INTERNAL TpiParam TpiParam_c

@module TpiParam.c |

    This module, along with <f TpiParam\.h>, implements a table driven parser
    for the NDIS registry parameters.

@comm

    See <f Keywords\.h> for details of how to add new parameters.<nl>

    This is a driver independent module which can be re-used, without
    change, by any NDIS3 driver.

@head3 Contents |
@index class,mfunc,func,msg,mdata,struct,enum | TpiParam_c

@end
§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§
*/

#define  __FILEID__     TPI_MODULE_PARAMS   // Unique file ID for error logging

#include <ndis.h>
#include "TpiDebug.h"
#include "TpiParam.h"

#if defined(_VXD_) && !defined(NDIS_LCODE)
#  define NDIS_LCODE code_seg("_LTEXT", "LCODE")
#  define NDIS_LDATA data_seg("_LDATA", "LCODE")
#endif

#if defined(NDIS_LCODE)
#   pragma NDIS_LCODE   // Windows 95 wants this code locked down!
#   pragma NDIS_LDATA
#endif

static NDIS_PHYSICAL_ADDRESS    g_HighestAcceptableAddress =
                                    NDIS_PHYSICAL_ADDRESS_CONST(-1,-1);

static NDIS_STRING              g_NullString =
                                    NDIS_STRING_CONST("\0");


/* @doc INTERNAL TpiParam TpiParam_c ustrlen
§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§

@func

    <f ustrlen> counts the number of characters in
    a UNICODE (wide) string.

@comm

@rdesc

    <f ustrlen> returns the length of the UNICODE string
    pointed to by <p string>.  The terminating NULL character is not
    counted.

*/
USHORT ustrlen(
    IN PUSHORT                  string                      // @parm
    // Pointer to the beginning of a UNICODE string ending
    // with a 0x0000 value.
    )
{
    USHORT                      ct;

    for (ct = 0; *string != 0x0000; string++, ct++)
        ;

    return(ct);
}


/* @doc INTERNAL TpiParam TpiParam_c ParamUnicodeStringToAnsiString
§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§

@func

    <f ParamUnicodeStringToAnsiString> converts a double byte string to a
    single byte string.

@comm

    The original release of the NDIS Wrapper for Windows 95 and 3.1 does not
    return UNICODE strings from the NdisReadConfiguration routine.  So this
    routine attempts to auto detect this situation by examining the first
    character of the string.  If the second byte of the first character is
    a zero, the string is assumed to be UNICODE, and it is converted to an
    ANSI string; otherwise the ANSI string is just copied.
    <nl>
    <f Note>: This also assumes that the first character of any UNICODE
    string will not use the second byte (i.e. not an extended character).
    This routine will only successfully convert non-extended character
    strings anyway.

@xref
    <f ParamParseRegistry>
*/

VOID ParamUnicodeStringToAnsiString(
    OUT PANSI_STRING            out,                        // @parm
    // A pointer to where the converted ANSI string is to be stored.

    IN PUNICODE_STRING          in                          // @parm
    // A pointer to the UNICODE string to be converted.
    )
{
    DBG_FUNC("ParamUnicodeStringToAnsiString")

    UINT Index;

    /* CAVEAT - NDIS_BUG
    // NDIS driver for Windows 95 does not return UNICODE from
    // registry parser, so we need to kludge it up here.
    */
    if (in->Length > 1)
    {
        if (((PUCHAR)(in->Buffer))[1] == 0)
        {
            /*
            // Probably a UNICODE string since all our parameters are ASCII
            // strings.
            */
            DBG_FILTER(DbgInfo, DBG_TRACE_ON,
                       ("UNICODE STRING IN @%x#%d='%ls'\n",
                       in->Buffer, in->Length, in->Buffer));
            for (Index = 0; Index < (in->Length / sizeof(WCHAR)) &&
                 Index < out->MaximumLength; Index++)
            {
                out->Buffer[Index] = (UCHAR) in->Buffer[Index];
            }
        }
        else
        {
            /*
            // Probably an ANSI string since all our parameters are more
            // than 1 byte long and should not be zero in the second byte.
            */
            PANSI_STRING in2 = (PANSI_STRING) in;

            DBG_FILTER(DbgInfo, DBG_TRACE_ON,
                       ("ANSI STRING IN @%x#%d='%s'\n",
                       in2->Buffer, in2->Length, in2->Buffer));

            for (Index = 0; Index < in2->Length &&
                 Index < out->MaximumLength; Index++)
            {
                out->Buffer[Index] = in2->Buffer[Index];
            }
        }
    }
    else
    {
        DBG_WARNING(DbgInfo,("1 BYTE STRING IN @%x=%04x\n",
                    in->Buffer, in->Buffer[0]));
        out->Buffer[0] = (UCHAR) in->Buffer[0];
        Index = 1;
    }
    out->Length = (USHORT) Index; // * sizeof(UCHAR);

    // NULL terminate the string if there's room.
    if (out->Length <= (out->MaximumLength - sizeof(UCHAR)))
    {
        out->Buffer[Index] = 0;
    }
    ASSERT(out->Length <= out->MaximumLength);
}


/* @doc INTERNAL TpiParam TpiParam_c ParamUnicodeCopyString
§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§

@func

    <f ParamUnicodeCopyString> copies a double byte string to a double byte
    string.

@comm

    The original release of the NDIS Wrapper for Windows 95 and 3.1 does not
    return UNICODE strings from the NdisReadConfiguration routine.  So this
    routine attempts to auto detect this situation by examining the first
    character of the string.  If the second byte of the first character is
    a zero, the string is assumed to be UNICODE, and it just copied;
    otherwise the ANSI string is converted to UNICODE.
    <nl>
    <f Note>: This also assumes that the first character of any UNICODE
    string will not use the second byte (i.e. not an extended character).
    This routine will only successfully convert non-extended character
    strings anyway.

@xref
    <f ParamParseRegistry>

*/

VOID ParamUnicodeCopyString(
    OUT PUNICODE_STRING         out,                        // @parm
    // A pointer to where the new UNICODE string is to be stored.

    IN PUNICODE_STRING          in                          // @parm
    // A pointer to the UNICODE string to be copied.
    )
{
    DBG_FUNC("ParamUnicodeCopyString")

    UINT Index;

    /* CAVEAT - NDIS_BUG
    // NDIS driver for Windows 95 does not return UNICODE from
    // registry parser, so we need to kludge it up here.
    */
    if (in->Length > 1)
    {
        if (((PUCHAR)(in->Buffer))[1] == 0)
        {
            /*
            // Probably a UNICODE string since all our parameters are ASCII
            // strings.
            */
            DBG_FILTER(DbgInfo, DBG_TRACE_ON,
                       ("UNICODE STRING IN @%x#%d='%ls'\n",
                       in->Buffer, in->Length, in->Buffer));
            for (Index = 0; Index < (in->Length / sizeof(WCHAR)) &&
                 Index < (out->MaximumLength / sizeof(WCHAR)); Index++)
            {
                out->Buffer[Index] = in->Buffer[Index];
            }
        }
        else
        {
            /*
            // Probably an ANSI string since all our parameters are more
            // than 1 byte long and should not be zero in the second byte.
            */
            PANSI_STRING in2 = (PANSI_STRING) in;

            DBG_FILTER(DbgInfo, DBG_TRACE_ON,
                       ("ANSI STRING IN @%x#%d='%s'\n",
                       in2->Buffer, in2->Length, in2->Buffer));
            for (Index = 0; Index < in2->Length &&
                 Index < (out->MaximumLength / sizeof(WCHAR)); Index++)
            {
                out->Buffer[Index] = (WCHAR) in2->Buffer[Index];
            }
        }
    }
    else
    {
        DBG_WARNING(DbgInfo,("1 BYTE STRING IN @%x=%04x\n",
                    in->Buffer, in->Buffer[0]));
        out->Buffer[0] = (WCHAR) in->Buffer[0];
        Index = 1;
    }
    out->Length = Index * sizeof(WCHAR);

    // NULL terminate the string if there's room.
    if (out->Length <= (out->MaximumLength - sizeof(WCHAR)))
    {
        out->Buffer[Index] = 0;
    }
    ASSERT(out->Length <= out->MaximumLength);
}


/* @doc INTERNAL TpiParam TpiParam_c ParamGetNumEntries
§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§

@func

    <f ParamGetNumEntries> counts the number of records in the registry
    parameter table.

@rdesc

    <f ParamGetNumEntries> returns the number of entries in the parameter
    table.

@xref
    <f ParamParseRegistry>

*/

DBG_STATIC UINT ParamGetNumEntries(
    IN PPARAM_TABLE             Parameters                  // @parm
    // A pointer to an array of registry parameter records.
    )
{
    UINT NumRecs = 0;

    /*
    // Scan the parameter array until we find an entry with zero length name.
    */
    if (Parameters)
    {
        while (Parameters->RegVarName.Length)
        {
            NumRecs++;
            Parameters++;
        }
    }
    return(NumRecs);
}


/* @doc INTERNAL TpiParam TpiParam_c ParamParseRegistry
§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§

@func

    <f ParamParseRegistry> parses the registry parameter table and attempts
    to read a value from the registry for each parameter record.

@rdesc

    <f ParamParseRegistry> returns one of the following values:
    @flag NDIS_STATUS_SUCCESS |
        If this function is successful.

    <f Note>: A non-zero return value indicates one of the following error codes:

@iex
    NDIS_STATUS_FAILURE

@xref
    <f MiniportInitialize>
    <f ParamGetNumEntries>
    <f NdisOpenConfiguration>
    <f NdisWriteErrorLogEntry>
    <f NdisReadConfiguration>
    <f NdisCloseConfiguration>
    <f NdisAllocateMemory>
    <f NdisZeroMemory>
    <f ParamUnicodeStringToAnsiString>
    <f ParamUnicodeCopyString>
*/

NDIS_STATUS ParamParseRegistry(
    IN NDIS_HANDLE              AdapterHandle,              // @parm
    // Handle to pass to NdisWriteErrorLogEntry if any errors are encountered.

    IN NDIS_HANDLE              WrapperConfigurationContext,// @parm
    // Handle to pass to NdisOpenConfiguration.

    IN PUCHAR                   BaseContext,                // @parm
    // References the base of the structure where the values read from the
    // registry are written.  Typically, this will be a pointer to the first
    // byte of the adapter information structure.

    IN PPARAM_TABLE             Parameters                  // @parm
    // A pointer to an array of registry parameter records <t PARAM_TABLE>.
    )
{
    DBG_FUNC("ParamParseRegistry")

    PNDIS_CONFIGURATION_PARAMETER   pReturnedValue;
    NDIS_CONFIGURATION_PARAMETER    ReturnedValue;
    NDIS_PARAMETER_TYPE             ParamType;

    /*
    // The handle for reading from the registry.
    */
    NDIS_HANDLE     ConfigHandle;

    UINT            NumRecs = ParamGetNumEntries(Parameters);
    UINT            i;
    PPARAM_TABLE    pParameter;
    NDIS_STATUS     Status;
    UINT            Value;
    PANSI_STRING    pAnsi;
    UINT            Length;

    /*
    // Open the configuration registry so we can get our config values.
    */
    NdisOpenConfiguration(
            &Status,
            &ConfigHandle,
            WrapperConfigurationContext
            );

    if (Status != NDIS_STATUS_SUCCESS)
    {
        /*
        // Log error message and exit.
        */
        DBG_ERROR(DbgInfo,("NdisOpenConfiguration failed (Status=%X)\n",Status));

        NdisWriteErrorLogEntry(
                AdapterHandle,
                NDIS_ERROR_CODE_UNSUPPORTED_CONFIGURATION,
                3,
                Status,
                __FILEID__,
                __LINE__
                );
        return NDIS_STATUS_FAILURE;
    }

    /*
    // Walk through all the parameters in the table.
    */
    for (i = 0, pParameter = Parameters; i < NumRecs; i++, pParameter++)
    {
#if DBG
        ANSI_STRING ansiRegString;
        char        ansiRegName[64];

        /*
        // Get a printable parameter name.
        */
        ansiRegString.Length = 0;
        ansiRegString.MaximumLength = sizeof(ansiRegName);
        ansiRegString.Buffer = (PCHAR)ansiRegName;
        NdisZeroMemory(ansiRegName, sizeof(ansiRegName));
        ParamUnicodeStringToAnsiString(
                &ansiRegString,
                (PUNICODE_STRING)&pParameter->RegVarName
                );
#endif // DBG

        ASSERT(pParameter->Type <= (UINT) NdisParameterMultiString);

        /*
        // Attempt to read the parameter value from the registry.
        */
        ParamType = (NDIS_PARAMETER_TYPE) pParameter->Type;
        NdisReadConfiguration(&Status,
                              &pReturnedValue,
                              ConfigHandle,
                              &pParameter->RegVarName,
                              ParamType
                             );
        /*
        // If value is not present, and it is mandatory, return failure code.
        */
        if (Status != NDIS_STATUS_SUCCESS && pParameter->Mandantory)
        {
            /*
            // Log error message and exit.
            */
            DBG_ERROR(DbgInfo,("%s: NOT IN REGISTRY!\n",
                      ansiRegName));

            NdisWriteErrorLogEntry(
                    AdapterHandle,
                    NDIS_ERROR_CODE_MISSING_CONFIGURATION_PARAMETER,
                    4,
                    i,
                    Status,
                    __FILEID__,
                    __LINE__
                    );

            NdisCloseConfiguration(ConfigHandle);
            return NDIS_STATUS_FAILURE;
        }

        /*
        // Determine how the caller wants to interpret this parameter.
        */
        if (ParamType == NdisParameterInteger ||
            ParamType == NdisParameterHexInteger)
        {
            ASSERT(pParameter->Size <= sizeof(ULONG));

            /*
            // If value read, use it, otherwise use default.
            */
            if (Status == NDIS_STATUS_SUCCESS)
            {
                Value = pReturnedValue->ParameterData.IntegerData;
            }
            else
            {
                Value = (UINT) (LONG_PTR)(pParameter->Default);
            }

            /*
            // If there are min/max boundaries, verify that value is in range.
            */
            if (pParameter->Min || pParameter->Max)
            {
                if (Value < pParameter->Min)
                {
                    DBG_ERROR(DbgInfo,("%s: Value=%X < Min=%X\n",
                              ansiRegName, Value, pParameter->Min));
                    Value = pParameter->Min;
                }
                else if (Value > pParameter->Max)
                {
                    DBG_ERROR(DbgInfo,("%s: Value=%X > Max=%X\n",
                              ansiRegName, Value, pParameter->Max));
                    Value = pParameter->Max;
                }
            }

            /*
            // Size of destination in bytes 1, 2, or 4 (default==INT).
            */
            switch (pParameter->Size)
            {
            case 0:
                *(PUINT)(BaseContext+pParameter->Offset)   = (UINT) Value;
                break;

            case 1:
                if (Value & 0xFFFFFF00)
                {
                    DBG_WARNING(DbgInfo,("%s: OVERFLOWS UCHAR\n",
                                ansiRegName));
                }
                *(PUCHAR)(BaseContext+pParameter->Offset)  = (UCHAR) Value;
                break;

            case 2:
                if (Value & 0xFFFF0000)
                {
                    DBG_WARNING(DbgInfo,("%s: OVERFLOWS USHORT\n",
                                ansiRegName));
                }
                *(PUSHORT)(BaseContext+pParameter->Offset) = (USHORT) Value;
                break;

            case 4:
                *(PULONG)(BaseContext+pParameter->Offset)  = (ULONG) Value;
                break;

            default:
                DBG_ERROR(DbgInfo,("%s: Invalid ParamSize=%d\n",
                          ansiRegName, pParameter->Size));
                NdisCloseConfiguration(ConfigHandle);
                return NDIS_STATUS_FAILURE;
                break;
            }

            if (ParamType == NdisParameterInteger)
            {
                DBG_PARAMS(DbgInfo,("%s: Value=%d Size=%d (%s)\n",
                           ansiRegName, Value, pParameter->Size,
                           (Status == NDIS_STATUS_SUCCESS) ?
                           "Registry" : "Default"));
            }
            else
            {
                DBG_PARAMS(DbgInfo,("%s: Value=0x%X Size=%d (%s)\n",
                           ansiRegName, Value, pParameter->Size,
                           (Status == NDIS_STATUS_SUCCESS) ?
                           "Registry" : "Default"));
            }
        }
        else if (ParamType == NdisParameterString ||
                 ParamType == NdisParameterMultiString)
        {
            ASSERT(pParameter->Size == sizeof(ANSI_STRING));

            /*
            // If value not read from registry.
            */
            if (Status != NDIS_STATUS_SUCCESS)
            {
                /*
                // Use our own temporary ReturnedValue.
                */
                pReturnedValue = &ReturnedValue;
                pReturnedValue->ParameterType = ParamType;

                /*
                // If default non-zero, use default value.
                */
                if (pParameter->Default != 0)
                {
                    NdisMoveMemory(&pReturnedValue->ParameterData.StringData,
                                   (PANSI_STRING) pParameter->Default,
                                   sizeof(ANSI_STRING));
                }
                else
                {
                    /*
                    // Otherwise, use null string value.
                    */
                    NdisMoveMemory(&pReturnedValue->ParameterData.StringData,
                                   &g_NullString,
                                   sizeof(g_NullString));
                }
            }

            /*
            // Assume the string is ANSI and points to the string data
            // structure.  We can get away with this because ANSI and
            // UNICODE strings have a common structure header.  An extra
            // character is allocated to make room for a null terminator.
            */
            pAnsi = (PANSI_STRING) (BaseContext+pParameter->Offset);
            Length = pReturnedValue->ParameterData.StringData.Length+1;

            /*
            // The caller wants a UNICODE string returned, we have to
            // allocated twice as many bytes to hold the result.
            // NOTE:
            // This wouldn't be necessary if NDIS would always return
            // a UNICODE string, but some Win95 versions of NDIS return
            // an ANSI string, so Length will be too small for UNICODE.
            // The down-side is that we may allocate twice as much as
            // we need to hold the string.  (oh well)
            */
            if (pParameter->Flags == PARAM_FLAGS_UNICODESTRING)
            {
                Length *= sizeof(WCHAR);
            }

            /*
            // Allocate memory for the string.
            */
#if !defined(NDIS50_MINIPORT)
            Status = NdisAllocateMemory(
                            (PVOID *) &(pAnsi->Buffer),
                            Length,
                            0,
                            g_HighestAcceptableAddress
                            );
#else  // NDIS50_MINIPORT
            Status = NdisAllocateMemoryWithTag(
                            (PVOID *) &(pAnsi->Buffer),
                            Length,
                            __FILEID__
                            );
#endif // NDIS50_MINIPORT

            if (Status != NDIS_STATUS_SUCCESS)
            {
                /*
                // Log error message and exit.
                */
                DBG_ERROR(DbgInfo,("NdisAllocateMemory(Size=%d, File=%s, Line=%d) failed (Status=%X)\n",
                          Length, __FILE__, __LINE__, Status));

                NdisWriteErrorLogEntry(
                        AdapterHandle,
                        NDIS_ERROR_CODE_OUT_OF_RESOURCES,
                        4,
                        Status,
                        Length,
                        __FILEID__,
                        __LINE__
                        );
                NdisCloseConfiguration(ConfigHandle);
                return NDIS_STATUS_FAILURE;
            }
            else
            {
                DBG_FILTER(DbgInfo, DBG_MEMORY_ON,
                           ("NdisAllocateMemory(Size=%d, Ptr=0x%x)\n",
                            Length, pAnsi->Buffer));
            }
            /*
            // Zero the string buffer to start with.
            */
            ASSERT(pAnsi->Buffer);
            NdisZeroMemory(pAnsi->Buffer, Length);
            pAnsi->MaximumLength = (USHORT) Length;

            if (pParameter->Flags == PARAM_FLAGS_ANSISTRING)
            {
                /*
                // The caller wants an ANSI string returned, so we convert
                // it from UNICODE to ANSI.
                */
                ParamUnicodeStringToAnsiString(
                        pAnsi,
                        (PUNICODE_STRING) &(pReturnedValue->ParameterData.StringData)
                        );
#if DBG
                if (ParamType == NdisParameterMultiString)
                    {
                    USHORT        ct = 0;

                    while (ct < pAnsi->Length)
                        {
                        DBG_PARAMS(DbgInfo,("%s: ANSI='%s' Len=%d of %d\n",
                            ansiRegName,
                            &(pAnsi->Buffer[ct]),
                            (strlen(&(pAnsi->Buffer[ct]))),
                            pAnsi->Length));

                        ct = ct + (strlen(&(pAnsi->Buffer[ct])) + 1);
                        }
                    }
                else
                    {
                    DBG_PARAMS(DbgInfo,("%s: ANSI='%s' Len=%d\n",
                           ansiRegName, pAnsi->Buffer, pAnsi->Length));
                    }
#endif
            }
            else // PARAM_FLAGS_UNICODESTRING
            {
                /*
                // The caller wants a UNICODE string returned, so we can
                // just copy it.  The pAnsi buffer was allocated large
                // enough to hold the UNICODE string.
                */
                ParamUnicodeCopyString(
                        (PUNICODE_STRING) pAnsi,
                        (PUNICODE_STRING) &(pReturnedValue->ParameterData.StringData)
                        );
#if DBG
                if (ParamType == NdisParameterMultiString)
                    {
                    USHORT        ct = 0;

                    BREAKPOINT;

                    while (ct < (pAnsi->Length / 2))
                        {
                        DBG_PARAMS(DbgInfo,("%s: UNICODE='%ls' Len=%d of %d\n",
                           ansiRegName,
                           &((PUSHORT)pAnsi->Buffer)[ct],
                           (ustrlen(&((PUSHORT)pAnsi->Buffer)[ct]) * 2),
                           pAnsi->Length));

                        ct = ct + (ustrlen(&((PUSHORT)pAnsi->Buffer)[ct]) + 1);
                        }
                    }
                else
                    {
                    DBG_PARAMS(DbgInfo,("%s: UNICODE='%ls' Len=%d\n",
                               ansiRegName, pAnsi->Buffer, pAnsi->Length));
                    }
#endif

            }
        }
        else
        {
            /*
            // Report a bogus parameter type in the caller's table.
            */
            DBG_ERROR(DbgInfo,("Invalid ParamType=%d '%s'\n",
                      ParamType, ansiRegName));

            NdisCloseConfiguration(ConfigHandle);
            return NDIS_STATUS_FAILURE;
        }
    }
    NdisCloseConfiguration(ConfigHandle);
    return(NDIS_STATUS_SUCCESS);
}