/**********************************************************************/
/**                       Microsoft Windows/NT                       **/
/**                Copyright(c) Microsoft Corp., 1995                **/
/**********************************************************************/

/*
    dhcpopt.cpp
        Defines the class behaviors for the application.

    FILE HISTORY:
        
       This program reads a CSV-formatted Excel spreadsheet containing the standard
       (globally defined) DHCP option information.   This data is parsed into
       a CObListParamTypes, each element of which contains a default value.

       Here are a couple of example lines:


        255,End,Generated,-,y,y,1,Indicates end of options in DHCP packet,
        21,Max DG reassembly size,Short,-,n,n,2,Maximum size datagram for reass. by client; max 576,
        22,Default time-to-live,Octet,-,?,y,1,default TTL for client's use on outgoing DGs,

       The fields, in order, are:

        Option number, numeric.   if this is empty or non-numeric, the line is skipped.
        Option name, string.
        Option type, string.      one of "generated", "IP addr", "string", etc.
        Array flag, character:    'Y' if it's an array.
        Length, numeric:          length of each element.
        Description, string:      comment info
        Remark, string:           ignored

*/

#include "stdafx.h"
#include "options.h"
#include "dhcpopt.h"        // From the MC File.

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

const char * pszResourceName = "DHCPOPT" ;
const char * pszResourceType = "TEXT" ;
const int cchFieldMax = 500 ;

enum OPT_FIELD
{
    OPTF_OPTION,
    OPTF_NAME,
    OPTF_TYPE,
    OPTF_ARRAY_FLAG,
    OPTF_LENGTH,
    OPTF_DESCRIPTION,
    OPTF_REMARK,
    OPTF_MAX
};

typedef struct
{
    int eOptType ;
    const char * pszOptTypeName ;
} OPT_TOKEN ;

OPT_TOKEN aOptToken [] =
{
    { DhcpIpAddressOption,  "IP Addr"       },
    { DhcpIpAddressOption,  "IPAddr"        },
    { DhcpIpAddressOption,  "IP Address"    },
    { DhcpIpAddressOption,  "IP Pairs"      },
    { DhcpByteOption,       "byte"          },
    { DhcpByteOption,       "boolean"       },
    { DhcpByteOption,       "octet"         },
    { DhcpWordOption,       "short"         },
    { DhcpDWordOption,      "long"          },
    { DhcpDWordDWordOption, "double"        },
    { DhcpStringDataOption, "string"        },
    { DhcpBinaryDataOption, "binary"        },
    { -1,                   "generated"     },
    { -1,                   NULL            }
};


int 
recognizeToken ( 
    OPT_TOKEN * apToken, 
    const char * pszToken 
    )
{
    int i ;
    for ( i = 0 ;
          apToken[i].pszOptTypeName && ::lstrcmpi( apToken[i].pszOptTypeName, pszToken ) != 0 ;
           i++ ) ;

    return apToken[i].eOptType ;
}

const char * 
skipToNextLine ( 
    const char * pszLine 
    )
{
    for ( ; *pszLine && *pszLine != '\n' ; pszLine++ ) ;
    if ( *pszLine )
    {
        pszLine++ ;   // Don't overscan buffer delimiter.
    }
    return pszLine ;
}

BOOL 
skipWs ( 
    const char * * ppszLine 
    )
{
     const char * pszLine ;
     BOOL bResult = FALSE ;

     for ( pszLine = *ppszLine ; *pszLine ; pszLine++ )
     {
        switch ( *pszLine )
        {
            case ' ':
            case '\r':
            case '\t':
                break ;
            default:
                bResult = TRUE ;
                break ;
        }
        if ( bResult )
        {
            break ;
        }
     }

     *ppszLine = pszLine ;
     return *pszLine != 0 ;
}

const char * 
scanNextField ( 
    const char * pszLine, 
    char * pszOut, 
    int cFieldSize 
    )
{
    //
    // Skip junk; return NULL if end-of-buffer.
    //
    if ( ! skipWs( & pszLine ) )
    {
        return NULL ;
    }

    int cch = 0 ;
    BOOL bDone = FALSE ;
    char * pszField = pszOut ;
    char ch ;

    if ( *pszLine == '\"' )
    {
        //
        //  Quoted string.
        //
        while ( ch = *++pszLine )
        {
            if ( ch == '\r' )
            {
                continue ;
            }
            if ( ch == '\n' || ch == '\"' || cch == cFieldSize )
            {
                break ;
            }
            *pszField++ = ch ;
            cch++ ;
        }
        if ( ch == '\"' )
        {
            pszLine++ ;
        }
    }
    else
    while ( ! bDone )
    {
        ch = *pszLine++ ;

        ASSERT( ch != 0 ) ;

        switch ( ch )
        {
            case '\n':
                pszLine-- ;  // Don't scan past the NL
            case ',':
            case '\r':
                bDone = TRUE ;
                break ;
            default:
                if ( cch < cFieldSize )
                {
                    *pszField++ = ch ;
                    cch++ ;
                }
                break ;
        }
    }

    //
    //  Trim spaces off the end of the field.
    //
    while ( pszField > pszOut && *(pszField-1) == ' ' )
    {
        pszField-- ;
    }
    *pszField = 0 ;
    return pszLine ;
}

BOOL 
allDigits ( 
    const char * psz 
    )
{
    for ( ; *psz ; psz++ )
    {
        if ( ! isdigit( *psz ) )
        {
            return FALSE ;
        }
    }

    return TRUE ;
}

BOOL 
scanNextParamType (
    const char * * ppszText,
    CDhcpParamType * * ppParamType 
    )
{
    char szField [ cchFieldMax ] ;
    char szName  [ cchFieldMax ] ;
    char szComment [ cchFieldMax ] ;
    BOOL bResult = TRUE ;
    BOOL bArray = FALSE ;
    int eofld, cch, itype, cbLgt ;
    const char * pszText = *ppszText ;
    CDhcpParamType * pParamType = NULL ;
    DHCP_OPTION_ID did ;
    DHCP_OPTION_DATA_TYPE dtype ;

    for ( eofld = OPTF_OPTION ;
          pszText = scanNextField( pszText, szField, sizeof szField ) ;
          eofld++ )
    {
        cch = ::strlen( szField ) ;

        switch ( eofld )
        {
            case OPTF_OPTION:
                if ( cch > 0 && allDigits( szField ) )
                {
                    did = (DHCP_OPTION_ID) ::atoi( szField ) ;
                }
                else
                {
                    bResult = FALSE ;
                }
                break ;

            case OPTF_NAME:
                if ( ::strlen( szField ) == 0 )
                {
                    bResult = FALSE ;
                    break ;
                }
                ::strcpy( szName, szField ) ;
                break ;

            case OPTF_TYPE:
                if ( (itype = recognizeToken( aOptToken, szField )) < 0 )
                {
                    TRACEEOLID( "options CSV ID " << did
                        << ", cannot recognize type " << szField ) ;
                    bResult = FALSE ;
                    break ;
                }
                dtype = (DHCP_OPTION_DATA_TYPE) itype ;
                break ;

            case OPTF_ARRAY_FLAG:   
                bArray = szField[0] == 'y' || szField[0] == 'Y' ;
                break ;

            case OPTF_LENGTH:
                cbLgt = ::atoi( szField ) ;
                break ;
            case OPTF_DESCRIPTION:
                ::strcpy( szComment, szField ) ;
                break ;

            case OPTF_REMARK:
            case OPTF_MAX:
                break ;
        }

        if ( eofld == OPTF_REMARK || ! bResult )
        {
            pszText = skipToNextLine( pszText ) ;
            if ( *pszText == 0 )
            {
                pszText = NULL ;
            }
            break;
        }
    }

    if ( bResult )
    {
        pParamType = new CDhcpParamType( did, dtype, szName, szComment,
                         bArray ? DhcpArrayTypeOption
                            : DhcpUnaryElementTypeOption ) ;
        bResult = pParamType->QueryError() == 0 ;
    }

    if ( bResult )
    {
        *ppParamType = pParamType ;
    }
    else
    {
        delete pParamType ;
        *ppParamType = NULL ;
    }

    *ppszText = pszText ;
    return pszText != NULL ;
}

//
//  Build the master list of default (global) parameter types and default values.
//
CObListParamTypes * 
CDhcpApp :: QueryMasterOptionList ()
{
    APIERR err = 0 ;
    CObListParamTypes * poblTypes = NULL ;
    CDhcpParamType * pParamType ;
    HRSRC hRes = NULL ;
    HGLOBAL hText = NULL ;
    char * pszText = NULL ;
    const char * pcszText ;
    size_t cchText;
    LPTSTR * szParms;
	char szUnknown[] = "(Unknown)";		// This is just to prevent a GP fault (should not be in resource)

    CATCH_MEM_EXCEPTION
    {
        do
        {
            /*
            hRes = ::FindResource( m_hInstance,
                          pszResourceName,
                          pszResourceType ) ;
            if ( hRes == NULL )
            {
                TRACEEOLID( "Unable to find options CSV file resource " << pszResourceName ) ;
                break ;
            }
    
            cchText = ::SizeofResource( m_hInstance, hRes ) ;

            if ( (hText = ::LoadResource( m_hInstance, hRes )) == NULL )
            {
                TRACEEOLID( "Unable to load options CSV file resource " << pszResourceName ) ;
                break ;
            }
            pszText = new char[ cchText + 2 ] ;
            ::memcpy( (void *) pszText, ::LockResource( hText ), cchText ) ;

            for ( cch = 0 ; cch < cchText ; cch++ )
            {
                if ( pszText[cch] == 0 || pszText[cch] == 0x1a )
                {
                    break ;
                }
            }
            pszText[cch] = 0 ;
            */

            //
            // IMPORTANT!!! There is no way to determine from the .mc file how many
            //              options are defined.  This number is therefore hard-coded
            //              here, and should reflect the highest parameter number in
            //              the .mc file.
            //

			// BUGBUG (t-danmo): The extra 16 entries are for safety
			// when calling FormatMessage().
            szParms = new LPTSTR[IDS_OPTION_MAX+16];

            TRACEEOLID("Now building list of option parameters");

            CString strOptionText;

			// Initialize the extra entries to something that will not GP fault.
			for (int i = 0; i < 16; i++)
				{
				szParms[IDS_OPTION_MAX+i] = szUnknown;
				}
            //
            // Don't mess with the order of the ID definitions!!!
            //
            for (i = 0; i < IDS_OPTION_MAX; ++i)
            {
                if (strOptionText.LoadString(IDS_OPTION1 + i))
                {
                    ASSERT(strOptionText.GetLength() > 0);
                    szParms[i] = new TCHAR[strOptionText.GetLength()+1];
                    ::strcpy(szParms[i], (LPCSTR)strOptionText);
                }
                else
                {
                    //
                    // Failed to load the string from the resource
                    // for some reason.
                    //
                    TRACEEOLID("WARNING: Failed to load option text " << IDS_OPTION1 + i);
                    err = ::GetLastError();
					szParms[i] = szUnknown; // Prevent from GP faulting
                    break;
                }
            }

            if (err != ERROR_SUCCESS)
            {
                break;
            }

            cchText = ::FormatMessageA(
                FORMAT_MESSAGE_ALLOCATE_BUFFER
                | FORMAT_MESSAGE_FROM_HMODULE
                | FORMAT_MESSAGE_ARGUMENT_ARRAY,
                NULL,			// hModule
                DHCP_OPTIONS,	// dwMessageId loaded from a system dll
                0L,				// dwLanguageId
                OUT (LPTSTR)&pszText,
                0,
                (va_list *)szParms
                );

            if (cchText == 0)
            {
                err = ::GetLastError();
                break;
            }

            //
            //  Walk the resource, parsing each line.  If the line converts
            //  to a tangible type, add it to the list.
            //
            poblTypes = new CObListParamTypes ;

            for ( pcszText = pszText ; scanNextParamType( & pcszText, & pParamType ) ; )
            {
                if ( pParamType )
                {
                    poblTypes->AddTail( pParamType ) ;
                }
            }

        } while ( FALSE ) ;
    }
    END_MEM_EXCEPTION( err )

    //delete pszText ;

    //if ( hText )
    //{
        //UnlockResource( hText ) ;
        //::FreeResource( hText ) ;
    //}

    for (int i = 0; i < IDS_OPTION_MAX; ++i)
    {
		if (szParms[i] != szUnknown)
			delete[] szParms[i];
    }

    delete[] szParms;
    ::LocalFree(pszText);

    return poblTypes ;
}

//  End of DHCPMOPT.CPP