/*
    File    netnum.cpp

    Private helper functions for setting the internal network number.
    We talk to ndis directly to set this number.

    Paul Mayfield, 1/5/98
*/

#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <winsvc.h>
#include <ndispnp.h>

extern "C" {
    DWORD OutputDebugger (LPSTR pszError, ...);
};

//$ REVIEW - Start - This is moving to private\inc\ipxpnp.h

#define IPX_RECONFIG_VERSION        0x1

#define RECONFIG_AUTO_DETECT        1
#define RECONFIG_MANUAL             2
#define RECONFIG_PREFERENCE_1       3
#define RECONFIG_NETWORK_NUMBER_1   4
#define RECONFIG_PREFERENCE_2       5
#define RECONFIG_NETWORK_NUMBER_2   6
#define RECONFIG_PREFERENCE_3       7
#define RECONFIG_NETWORK_NUMBER_3   8
#define RECONFIG_PREFERENCE_4       9
#define RECONFIG_NETWORK_NUMBER_4   10

#define RECONFIG_PARAMETERS         10

//
// Main configuration structure.
//

typedef struct _RECONFIG {
   unsigned long  ulVersion;
   BOOLEAN        InternalNetworkNumber;
   BOOLEAN        AdapterParameters[RECONFIG_PARAMETERS];
} RECONFIG, *PRECONFIG;

//$ REVIEW - End - This is moving to private\inc\ipxpnp.h

static const TCHAR c_szIpx[]                    = TEXT("nwlnkipx");
static const TCHAR c_szEmpty[]                  = TEXT("");
static const TCHAR c_szVirtualNetworkNumber[]   = TEXT("VirtualNetworkNumber");
static const TCHAR c_szIpxParameters[]          = TEXT("System\\CurrentControlSet\\Services\\NwlnkIpx\\Parameters");
static const TCHAR c_szDevice[]                 = TEXT("\\Device\\");

ULONG
CchMsz (
        LPCTSTR pmsz)
{
    ULONG cchTotal = 0;
    ULONG cch;

    // NULL strings have zero length by definition.
    if (!pmsz)
        return 0;

    while (*pmsz)
    {
        cch = lstrlen (pmsz) + 1;
        cchTotal += cch;
        pmsz += cch;
    }

    // Return the count of characters so far plus room for the
    // extra null terminator.
    return cchTotal + 1;
}

void
SetUnicodeMultiString (
        IN OUT UNICODE_STRING*  pustr,
        IN     LPCWSTR          pmsz )
{
    //AssertSz( pustr != NULL, "Invalid Argument" );
    //AssertSz( pmsz != NULL, "Invalid Argument" );

    pustr->Buffer = const_cast<PWSTR>(pmsz);
    pustr->Length = (USHORT) (CchMsz(pustr->Buffer) * sizeof(WCHAR));
    pustr->MaximumLength = pustr->Length;
}

void
SetUnicodeString (
        IN OUT UNICODE_STRING*  pustr,
        IN     LPCWSTR          psz )
{
    //AssertSz( pustr != NULL, "Invalid Argument" );
    //AssertSz( psz != NULL, "Invalid Argument" );

    pustr->Buffer = const_cast<PWSTR>(psz);
    pustr->Length = (USHORT)(lstrlenW(pustr->Buffer) * sizeof(WCHAR));
    pustr->MaximumLength = pustr->Length + sizeof(WCHAR);
}

HRESULT
HrSendNdisHandlePnpEvent (
        UINT        uiLayer,
        UINT        uiOperation,
        LPCWSTR     pszUpper,
        LPCWSTR     pszLower,
        LPCWSTR     pmszBindList,
        PVOID       pvData,
        DWORD       dwSizeData)
{
    UNICODE_STRING    umstrBindList;
    UNICODE_STRING    ustrLower;
    UNICODE_STRING    ustrUpper;
    UINT nRet;
    HRESULT hr = S_OK;

    //Assert(NULL != pszUpper);
    //Assert((NDIS == uiLayer)||(TDI == uiLayer));
    //Assert( (BIND == uiOperation) || (RECONFIGURE == uiOperation) || (UNBIND == uiOperation) );
    //AssertSz( FImplies( ((NULL != pmszBindList) && (0 != lstrlenW( pmszBindList ))),
    //        (RECONFIGURE == uiOperation) &&
    //        (TDI == uiLayer) &&
    //        (0 == lstrlenW( pszLower ))),
    //        "bind order change requires a bind list, no lower, only for TDI, and with Reconfig for the operation" );

    // optional strings must be sent as empty strings
    //
    if (NULL == pszLower)
    {
        pszLower = c_szEmpty;
    }
    if (NULL == pmszBindList)
    {
        pmszBindList = c_szEmpty;
    }

    // build UNICDOE_STRINGs
    SetUnicodeMultiString( &umstrBindList, pmszBindList );
    SetUnicodeString( &ustrUpper, pszUpper );
    SetUnicodeString( &ustrLower, pszLower );

    // Now submit the notification
    nRet = NdisHandlePnPEvent( uiLayer,
            uiOperation,
            &ustrLower,
            &ustrUpper,
            &umstrBindList,
            (PVOID)pvData,
            dwSizeData );
    if (!nRet)
    {
        //hr = HrFromLastWin32Error();
        hr = GetLastError();
    }

    return( hr );
}

HRESULT
HrSendNdisPnpReconfig (
        UINT        uiLayer,
        LPCWSTR     wszUpper,
        LPCWSTR     wszLower,
        PVOID       pvData,
        DWORD       dwSizeData)
{
    //Assert(NULL != wszUpper);
    //Assert((NDIS == uiLayer)||(TDI == uiLayer));
    //tstring strLower;
    WCHAR strLower[512];
    BOOL bSendNull = FALSE;

    if (NULL == wszLower)
    {
        wszLower = c_szEmpty;
    }

    // If a lower component is specified, prefix with "\Device\" else
    // strLower's default of an empty string will be used.
    if ( wszLower && lstrlenW(wszLower))
    {
        //strLower = c_szDevice;
        //strLower += wszLower;
        wcscpy(strLower, c_szDevice);
        wcscat(strLower, wszLower);
    }
    else
        bSendNull = TRUE;

    HRESULT hr = HrSendNdisHandlePnpEvent(uiLayer,
                RECONFIGURE,
                wszUpper,
                //strLower.c_str(),
                (bSendNull) ? NULL : strLower,
                c_szEmpty,
                pvData,
                dwSizeData);

    OutputDebugger( "HrSendNdisHandlePnpEvent: %x\n", hr);

    return hr;
}

HRESULT HrSetIpxVirtualNetNum(DWORD dwValue)
{
    RECONFIG  Config;
    HKEY      hkey;
    HRESULT   hr;

    // Open the registry key
    LONG lr = RegOpenKeyEx(HKEY_LOCAL_MACHINE, c_szIpxParameters, 0,
                           KEY_ALL_ACCESS, &hkey);
    hr = HRESULT_FROM_WIN32(lr);
    if (SUCCEEDED(hr))
    {
        // Splat the data
        lr = RegSetValueEx(hkey, c_szVirtualNetworkNumber, 0,
                           REG_DWORD, (LPBYTE)&dwValue, sizeof(DWORD));
        hr = HRESULT_FROM_WIN32(lr);
        if (SUCCEEDED(hr))
        {
            memset(&Config, 0, sizeof(RECONFIG));
            Config.ulVersion             = IPX_RECONFIG_VERSION;
            Config.InternalNetworkNumber = TRUE;

            // Workstation or server?

            // Paul, Normally I only send this notification for servers. I
            // Assume you'll be able to distinguish

            // Now submit the global reconfig notification
            hr = HrSendNdisPnpReconfig(NDIS, c_szIpx, c_szEmpty, &Config, sizeof(RECONFIG));
        }

        RegCloseKey(hkey);
    }

    return hr;
}


// Here's the function we want -- it sets the ipx internal network number
// programatically.
EXTERN_C
DWORD SetIpxInternalNetNumber(DWORD dwNetNum) {
    return HrSetIpxVirtualNetNum(dwNetNum);
}