//============================================================================
// Copyright (c) 1995, Microsoft Corporation
//
// File:    api.c
//
// History:
//      Abolade Gbadegesin  August 31, 1995     Created
//
// BOOTP Relay Agent's interface to Router Manager
//============================================================================

#include "pchbootp.h"


IPBOOTP_GLOBALS ig;


DWORD
MibGetInternal(
    PIPBOOTP_MIB_GET_INPUT_DATA pimgid,
    PIPBOOTP_MIB_GET_OUTPUT_DATA pimgod,
    PDWORD pdwOutputSize,
    DWORD dwGetMode
    );

BOOL
DllStartup(
    );

BOOL
DllCleanup(
    );

DWORD
ProtocolStartup(
    HANDLE hEventEvent,
    PSUPPORT_FUNCTIONS pFunctionTable,
    PVOID pConfig
    );

DWORD
ProtocolCleanup(
    BOOL bCleanupWinsock
    );

DWORD
BindInterface(
    IN DWORD dwIndex,
    IN PVOID pBinding
    );

DWORD
UnBindInterface(
    IN DWORD dwIndex
    );

DWORD
EnableInterface(
    IN DWORD dwIndex
    );

DWORD
DisableInterface(
    IN DWORD dwIndex
    );


//----------------------------------------------------------------------------
// Function:    DLLMAIN
//
// This is the entry-point for IPBOOTP.DLL.
//----------------------------------------------------------------------------

BOOL
WINAPI
DLLMAIN(
    HINSTANCE hInstance,
    DWORD dwReason,
    PVOID pUnused
    ) {

    BOOL bErr;


    bErr = FALSE;

    switch(dwReason) {
        case DLL_PROCESS_ATTACH:

            DisableThreadLibraryCalls(hInstance);

            bErr = DllStartup();
            break;

        case DLL_PROCESS_DETACH:

            bErr = DllCleanup();
            break;

        default:
            bErr = TRUE;
            break;
    }

    return bErr;
}



//----------------------------------------------------------------------------
// Function:    DllStartup
//
// This function initializes IPBOOTP's global structure
// in preparation for calls to the API functions exported.
// It creates the global critical section, heap, and router-manager
// event message queue.
//----------------------------------------------------------------------------

BOOL
DllStartup(
    ) {

    BOOL bErr;
    DWORD dwErr;


    bErr = FALSE;


    do {

        ZeroMemory(&ig, sizeof(IPBOOTP_GLOBALS));


        try {
            InitializeCriticalSection(&ig.IG_CS);
        }
        except (EXCEPTION_EXECUTE_HANDLER) {

            dwErr = GetExceptionCode();
            break;
        }


        ig.IG_Status = IPBOOTP_STATUS_STOPPED;


        //
        // create the global heap for BOOTP
        //

        ig.IG_GlobalHeap = HeapCreate(0, 0, 0);
        if (ig.IG_GlobalHeap == NULL) {

            dwErr = GetLastError();
            break;
        }


        //
        // allocate space for the Router manager event queue
        //

        ig.IG_EventQueue = BOOTP_ALLOC(sizeof(LOCKED_LIST));

        if (ig.IG_EventQueue == NULL) {

            dwErr = GetLastError();
            break;
        }


        //
        // now initialize the locked-list allocated
        //

        try {
            CREATE_LOCKED_LIST(ig.IG_EventQueue);
        }
        except(EXCEPTION_EXECUTE_HANDLER) {

            dwErr = GetExceptionCode();
            break;
        }


        bErr = TRUE;

    } while(FALSE);

    if (!bErr) {
        DllCleanup();
    }

    return bErr;
}



//----------------------------------------------------------------------------
// Function:    DllCleanup
//
// This function is called when the IPBOOTP DLL is being unloaded.
// It releases the resources allocated in DllStartup.
//----------------------------------------------------------------------------

BOOL
DllCleanup(
    ) {

    BOOL bErr;


    bErr = TRUE;

    do {


        //
        // delete and deallocate the event message queue
        //

        if (ig.IG_EventQueue != NULL) {

            if (LOCKED_LIST_CREATED(ig.IG_EventQueue)) {
                DELETE_LOCKED_LIST(ig.IG_EventQueue);
            }

            BOOTP_FREE(ig.IG_EventQueue);
        }



        //
        // destroy the global heap
        //

        if (ig.IG_GlobalHeap != NULL) {

            HeapDestroy(ig.IG_GlobalHeap);
        }


        //
        // delete the global critical section
        //

        DeleteCriticalSection(&ig.IG_CS);


        if (ig.IG_LoggingHandle != NULL)
            RouterLogDeregister(ig.IG_LoggingHandle);
        if (ig.IG_TraceID != INVALID_TRACEID) {
            TraceDeregister(ig.IG_TraceID);
        }

    } while(FALSE);


    return bErr;
}



//----------------------------------------------------------------------------
// Function:    ProtocolStartup
//
// This function is called by the router manager to start IPBOOTP.
// It sets up the data structures needed and starts the input thread.
//----------------------------------------------------------------------------

DWORD
ProtocolStartup(
    HANDLE hEventEvent,
    PSUPPORT_FUNCTIONS pFunctionTable,
    PVOID pConfig
    ) {

    WSADATA wd;
    HANDLE hThread;
    BOOL bCleanupWinsock;
    DWORD dwErr, dwSize, dwThread;
    PIPBOOTP_GLOBAL_CONFIG pgcsrc, pgcdst;


    ig.IG_TraceID = TraceRegister("IPBOOTP");
    ig.IG_LoggingHandle = RouterLogRegister("IPBOOTP");


    //
    // acquire the global critical section
    // while we look at the status code
    //

    EnterCriticalSection(&ig.IG_CS);


    //
    // make sure that BOOTP has not already started up
    //

    if (ig.IG_Status != IPBOOTP_STATUS_STOPPED) {

        TRACE0(START, "StartProtocol() has already been called");
        LOGWARN0(ALREADY_STARTED, 0);

        LeaveCriticalSection(&ig.IG_CS);

        return ERROR_CAN_NOT_COMPLETE;
    }


    //
    // initialize the global structures:
    //


    bCleanupWinsock = FALSE;


    do { // error break-out loop


        TRACE0(START, "IPBOOTP is starting up...");



        //
        // copy the global configuration passed in:
        // find its size, and the allocate space for the copy
        //

        pgcsrc = (PIPBOOTP_GLOBAL_CONFIG)pConfig;
        dwSize = GC_SIZEOF(pgcsrc);

        pgcdst = BOOTP_ALLOC(dwSize);

        if (pgcdst == NULL) {

            dwErr = GetLastError();
            TRACE2(
                START, "error %d allocating %d bytes for global config",
                dwErr, dwSize
                );
            LOGERR0(HEAP_ALLOC_FAILED, dwErr);

            break;
        }


        RtlCopyMemory(pgcdst, pgcsrc, dwSize);

        ig.IG_Config = pgcdst;
        ig.IG_LoggingLevel = pgcdst->GC_LoggingLevel;



        //
        // initialize Windows Sockets
        //

        dwErr = (DWORD)WSAStartup(MAKEWORD(1,1), &wd);

        if (dwErr != NO_ERROR) {

            TRACE1(START, "error %d initializing Windows Sockets", dwErr);
            LOGERR0(INIT_WINSOCK_FAILED, dwErr);

            break;
        }


        bCleanupWinsock = TRUE;


        //
        // create the global structure lock
        //

        try {
            CREATE_READ_WRITE_LOCK(&ig.IG_RWL);
        }
        except (EXCEPTION_EXECUTE_HANDLER) {

            dwErr = GetExceptionCode();
            TRACE1(START, "error %d creating synchronization object", dwErr);
            LOGERR0(CREATE_RWL_FAILED, dwErr);

            break;
        }


        //
        // initialize the interface table
        //

        ig.IG_IfTable = BOOTP_ALLOC(sizeof(IF_TABLE));

        if (ig.IG_IfTable == NULL) {

            dwErr = GetLastError();
            TRACE2(
                START, "error %d allocating %d bytes for interface table",
                dwErr, sizeof(IF_TABLE)
                );
            LOGERR0(HEAP_ALLOC_FAILED, dwErr);

            break;
        }



        //
        // initialize the interface table
        //

        dwErr = CreateIfTable(ig.IG_IfTable);

        if (dwErr != NO_ERROR) {

            TRACE1(START, "error %d initializing interface table", dwErr);
            LOGERR0(CREATE_IF_TABLE_FAILED, dwErr);

            break;
        }


        //
        // allocate the receive queue
        //

        ig.IG_RecvQueue = BOOTP_ALLOC(sizeof(LOCKED_LIST));

        if (ig.IG_RecvQueue == NULL) {

            dwErr = GetLastError();
            TRACE2(
                START, "error %d allocating %d bytes for receive queue",
                dwErr, sizeof(LOCKED_LIST)
                );
            LOGERR0(HEAP_ALLOC_FAILED, dwErr);

            break;
        }


        //
        // initialize the receive queue
        //

        try {
            CREATE_LOCKED_LIST(ig.IG_RecvQueue);
        }
        except (EXCEPTION_EXECUTE_HANDLER) {

            dwErr = GetExceptionCode();
            TRACE1(START, "exception %d initializing locked list", dwErr);
            LOGERR0(INIT_CRITSEC_FAILED, dwErr);

            break;
        }


        //
        // copy the support-function table and Router Manager event
        //

        ig.IG_FunctionTable = pFunctionTable;

        ig.IG_EventEvent = hEventEvent;



        //
        // initialize count of active threads, and create the semaphore
        // signalled by threads exiting API functions and work functions
        //

        ig.IG_ActivityCount = 0;

        ig.IG_ActivitySemaphore = CreateSemaphore(NULL, 0, 0xfffffff, NULL);

        if (ig.IG_ActivitySemaphore == NULL) {

            dwErr = GetLastError();
            TRACE1(START, "error %d creating semaphore", dwErr);
            LOGERR0(CREATE_SEMAPHORE_FAILED, dwErr);

            break;
        }



        //
        // create the event used to signal on incoming packets
        //

        ig.IG_InputEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

        if (ig.IG_InputEvent == NULL) {

            dwErr = GetLastError();
            TRACE1(START, "error %d creating input-event", dwErr);
            LOGERR0(CREATE_EVENT_FAILED, dwErr);

            break;
        }


        //
        // register the InputEvent with the NtdllWait thread
        //

         
        if (! RegisterWaitForSingleObject(
                  &ig.IG_InputEventHandle,
                  ig.IG_InputEvent,
                  CallbackFunctionNetworkEvents,
                  NULL,      //null context
                  INFINITE,  //no timeout
                  (WT_EXECUTEINWAITTHREAD|WT_EXECUTEONLYONCE)
                  )) {

            dwErr = GetLastError();
            TRACE1(
                START, "error %d returned by RegisterWaitForSingleObjectEx",
                dwErr
                );
            LOGERR0(REGISTER_WAIT_FAILED, dwErr);
            break;

        }



        //
        // now set the status to running
        //

        ig.IG_Status = IPBOOTP_STATUS_RUNNING;


#if DBG

        //
        // register a timer queue with the NtdllTimer thread
        //
        ig.IG_TimerQueueHandle = CreateTimerQueue();

        if (!ig.IG_TimerQueueHandle) {

            dwErr = GetLastError();
            TRACE1(START, "error %d returned by CreateTimerQueue()", dwErr);
            LOGERR0(CREATE_TIMER_QUEUE_FAILED, dwErr);

            break;
        }



        //
        // set timer with NtdllTimer thread to display IPBOOTP MIB periodically
        //

        ig.IG_MibTraceID = TraceRegisterEx("IPBOOTPMIB", TRACE_USE_CONSOLE);

        if (ig.IG_MibTraceID != INVALID_TRACEID) {

             
            if (! CreateTimerQueueTimer(
                      &ig.IG_MibTimerHandle,
                      ig.IG_TimerQueueHandle,
                      CallbackFunctionMibDisplay,
                      NULL,           // null context
                      10000,          // display after 10 seconds
                      10000,          // display every 10 seconds
                      0               // execute in timer thread
                      )) {

                dwErr = GetLastError();
                TRACE1(
                    START, "error %d returned by CreateTimerQueueTimer()",
                    dwErr
                    );
                break;
            }
        }

#endif


        TRACE0(START, "IP BOOTP started successfully");

        LOGINFO0(STARTED, 0);


        LeaveCriticalSection(&ig.IG_CS);

        return NO_ERROR;

    } while(FALSE);


    //
    // an error occurred if control-flow brings us here
    //

    TRACE0(START, "IPRIP failed to start");

    ProtocolCleanup(bCleanupWinsock);

    LeaveCriticalSection(&ig.IG_CS);

    return (dwErr == NO_ERROR ? ERROR_CAN_NOT_COMPLETE : dwErr);
}




//----------------------------------------------------------------------------
// Function:    ProtocolCleanup
//
// This function cleans up resources used by IPBOOTP while it is
// in operation. Essentially, everything created in ProtocolStartup
// is cleaned up by the function.
//----------------------------------------------------------------------------

DWORD
ProtocolCleanup(
    BOOL bCleanupWinsock
    ) {

    DWORD dwErr;


    //
    // lock things down while we clean up
    //

    EnterCriticalSection(&ig.IG_CS);

#if DBG

    TraceDeregister(ig.IG_MibTraceID);

#endif


    if (ig.IG_InputEvent != NULL) {

        CloseHandle(ig.IG_InputEvent);
        ig.IG_InputEvent = NULL;
    }


    if (ig.IG_ActivitySemaphore != NULL) {

        CloseHandle(ig.IG_ActivitySemaphore);
        ig.IG_ActivitySemaphore = NULL;
    }


    if (ig.IG_RecvQueue != NULL) {

        if (LOCKED_LIST_CREATED(ig.IG_RecvQueue)) {
            DELETE_LOCKED_LIST(ig.IG_RecvQueue);
        }

        BOOTP_FREE(ig.IG_RecvQueue);
        ig.IG_RecvQueue = NULL;
    }


    if (ig.IG_IfTable != NULL) {

        if (IF_TABLE_CREATED(ig.IG_IfTable)) {
            DeleteIfTable(ig.IG_IfTable);
        }

        BOOTP_FREE(ig.IG_IfTable);
        ig.IG_IfTable = NULL;
    }


    if (READ_WRITE_LOCK_CREATED(&ig.IG_RWL)) {

        try {
            DELETE_READ_WRITE_LOCK(&ig.IG_RWL);
        }
        except (EXCEPTION_EXECUTE_HANDLER) {
            dwErr = GetExceptionCode();
        }
    }


    if (bCleanupWinsock) {
        WSACleanup();
    }


    if (ig.IG_Config != NULL) {

        BOOTP_FREE(ig.IG_Config);
        ig.IG_Config = NULL;
    }


    ig.IG_Status = IPBOOTP_STATUS_STOPPED;

    LeaveCriticalSection(&ig.IG_CS);

    return NO_ERROR;
}



//----------------------------------------------------------------------------
// Function:    RegisterProtocol
//
// This function is called by the router manager
// to retrieve information about IPBOOTP's capabilities
//----------------------------------------------------------------------------

DWORD
APIENTRY
RegisterProtocol(
    IN OUT PMPR_ROUTING_CHARACTERISTICS pRoutingChar,
    IN OUT PMPR_SERVICE_CHARACTERISTICS pServiceChar
    )
{
    if(pRoutingChar->dwProtocolId != MS_IP_BOOTP)
    {
        return ERROR_NOT_SUPPORTED;
    }

    pServiceChar->fSupportedFunctionality = 0;

    if(!(pRoutingChar->fSupportedFunctionality & RF_ROUTING))
    {
        return ERROR_NOT_SUPPORTED;
    }

    pRoutingChar->fSupportedFunctionality = RF_ROUTING;

    pRoutingChar->pfnStartProtocol      = StartProtocol;
    pRoutingChar->pfnStartComplete      = StartComplete;
    pRoutingChar->pfnStopProtocol       = StopProtocol;
    pRoutingChar->pfnGetGlobalInfo      = GetGlobalInfo;
    pRoutingChar->pfnSetGlobalInfo      = SetGlobalInfo;
    pRoutingChar->pfnQueryPower         = NULL;
    pRoutingChar->pfnSetPower           = NULL;

    pRoutingChar->pfnAddInterface       = AddInterface;
    pRoutingChar->pfnDeleteInterface    = DeleteInterface;
    pRoutingChar->pfnInterfaceStatus    = InterfaceStatus;
    pRoutingChar->pfnGetInterfaceInfo   = GetInterfaceConfigInfo;
    pRoutingChar->pfnSetInterfaceInfo   = SetInterfaceConfigInfo;

    pRoutingChar->pfnGetEventMessage    = GetEventMessage;

    pRoutingChar->pfnUpdateRoutes       = NULL;

    pRoutingChar->pfnConnectClient      = NULL;
    pRoutingChar->pfnDisconnectClient   = NULL;

    pRoutingChar->pfnGetNeighbors       = NULL;
    pRoutingChar->pfnGetMfeStatus       = NULL;

    pRoutingChar->pfnMibCreateEntry     = MibCreate;
    pRoutingChar->pfnMibDeleteEntry     = MibDelete;
    pRoutingChar->pfnMibGetEntry        = MibGet;
    pRoutingChar->pfnMibSetEntry        = MibSet;
    pRoutingChar->pfnMibGetFirstEntry   = MibGetFirst;
    pRoutingChar->pfnMibGetNextEntry    = MibGetNext;

    return NO_ERROR;
}



//----------------------------------------------------------------------------
// Function:    StartProtocol
//
// This function is called by the router manager
// to start IPBOOTP.
//----------------------------------------------------------------------------

DWORD
WINAPI
StartProtocol (
    HANDLE              NotificationEvent,
    SUPPORT_FUNCTIONS   *SupportFunctions,
    LPVOID              GlobalInfo,
    ULONG               StructureVersion,
    ULONG               StructureSize,
    ULONG               StructureCount
    )
{
    return ProtocolStartup(NotificationEvent, SupportFunctions, GlobalInfo);
}



//----------------------------------------------------------------------------
// Function:    StartComplete
//
// This function is called by the router manager
// to start IPBOOTP.
//----------------------------------------------------------------------------

DWORD
WINAPI
StartComplete (
    VOID
    )
{
    return NO_ERROR;
}




//----------------------------------------------------------------------------
// Function:    StopProtocol
//
// This function notifies all active threads to stop, and frees resources
// used by IP BOOTP
//----------------------------------------------------------------------------

DWORD
APIENTRY
StopProtocol(
    VOID
    ) {


    DWORD dwErr;
    LONG lThreadCount;
    HANDLE WaitHandle;
    

    //
    // make sure IPBOOTP has not already stopped
    //

    EnterCriticalSection(&ig.IG_CS);

    if (ig.IG_Status != IPBOOTP_STATUS_RUNNING) {

        LeaveCriticalSection(&ig.IG_CS);
        return ERROR_CAN_NOT_COMPLETE;
    }


    TRACE0(ENTER, "entering StopProtocol");



    //
    // update the status to prevent any APIs or worker-functions from running
    //

    ig.IG_Status = IPBOOTP_STATUS_STOPPING;



    //
    // see how many threads are already in API calls
    // or in worker-function code
    //

    lThreadCount = ig.IG_ActivityCount;

    TRACE1(STOP, "%d threads are active in IPBOOTP", lThreadCount);



    LeaveCriticalSection(&ig.IG_CS);



    //
    // wait for active threads to stop
    //

    while (lThreadCount-- > 0) {
        WaitForSingleObject(ig.IG_ActivitySemaphore, INFINITE);
    }


    //
    // deregister the mib timer from the Ntdll threads
    // This has to be done outside IG_CS lock.
    //

#if DBG
    DeleteTimerQueueEx(ig.IG_TimerQueueHandle, INVALID_HANDLE_VALUE);
#endif

    //
    // set the handle to NULL, so that Unregister wont be called
    //

    WaitHandle = InterlockedExchangePointer(&ig.IG_InputEventHandle, NULL);
        
    if (WaitHandle) {
        UnregisterWaitEx( WaitHandle, INVALID_HANDLE_VALUE ) ;
    }



    //
    // enter the critical section and leave,
    // to make certain all the threads have returned from LeaveBootpWorker
    //

    EnterCriticalSection(&ig.IG_CS);
    LeaveCriticalSection(&ig.IG_CS);


    //
    // now all threads have stopped
    //

    TRACE0(STOP, "all threads stopped, BOOTP is cleaning up resources");

    LOGINFO0(STOPPED, 0);


    ProtocolCleanup(TRUE);

    return NO_ERROR;
}





//----------------------------------------------------------------------------
// Function:    GetGlobalInfo
//
// Copies BOOTP's global config into the buffer provided.
//----------------------------------------------------------------------------

DWORD WINAPI
GetGlobalInfo (
    PVOID   OutGlobalInfo,
    PULONG  GlobalInfoSize,
    PULONG  StructureVersion,
    PULONG  StructureSize,
    PULONG  StructureCount
    )
{
    DWORD dwErr = NO_ERROR, dwSize;

    if (!ENTER_BOOTP_API()) { return ERROR_CAN_NOT_COMPLETE; }

    TRACE2(ENTER, "entering GetGlobalInfo: 0x%08x 0x%08x", OutGlobalInfo, GlobalInfoSize);


    //
    // in order to do anything, we need a valid size pointer
    //

    if (GlobalInfoSize == NULL) {
        dwErr = ERROR_INVALID_PARAMETER;
    }
    else {

        //
        // check the size of the config block passed in
        // and copy the config if the buffer is large enough
        //

        ACQUIRE_READ_LOCK(&ig.IG_RWL);

        dwSize = GC_SIZEOF(ig.IG_Config);
        if (*GlobalInfoSize < dwSize) {
            dwErr = ERROR_INSUFFICIENT_BUFFER;
        }
        else
        if (OutGlobalInfo != NULL) {

            RtlCopyMemory(
                OutGlobalInfo,
                ig.IG_Config,
                dwSize
                );
        }

        *GlobalInfoSize = dwSize;

        if (StructureSize) *StructureSize = *GlobalInfoSize;
        if (StructureCount) *StructureCount = 1;
        if (StructureVersion) *StructureVersion = BOOTP_CONFIG_VERSION_500;
        
        RELEASE_READ_LOCK(&ig.IG_RWL);
    }

    TRACE1(LEAVE, "leaving GetGlobalInfo: %d", dwErr);

    LEAVE_BOOTP_API();

    return dwErr;
}



//----------------------------------------------------------------------------
// Function:    SetGlobalInfo
//
// Copies over the specified configuration .
//----------------------------------------------------------------------------

DWORD WINAPI
SetGlobalInfo (
    PVOID   GlobalInfo,
    ULONG   StructureVersion,
    ULONG   StructureSize,
    ULONG   StructureCount
    )
{
    DWORD dwErr, dwSize;
    PIPBOOTP_GLOBAL_CONFIG pgcsrc, pgcdst;

    if (!GlobalInfo || !ENTER_BOOTP_API()) { return ERROR_CAN_NOT_COMPLETE; }

    TRACE1(ENTER, "entering SetGlobalInfo: %p", GlobalInfo);


    ACQUIRE_WRITE_LOCK(&ig.IG_RWL);

    pgcsrc = (PIPBOOTP_GLOBAL_CONFIG)GlobalInfo;
    dwSize = GC_SIZEOF(pgcsrc);


    //
    // allocate memory for the new config block, and copy it over
    //

    pgcdst = BOOTP_ALLOC(dwSize);

    if (pgcdst == NULL) {

        dwErr = GetLastError();
        TRACE2(
            CONFIG, "error %d allocating %d bytes for global config",
            dwErr, dwSize
            );
        LOGERR0(HEAP_ALLOC_FAILED, dwErr);
    }
    else {

        RtlCopyMemory(
            pgcdst,
            pgcsrc,
            dwSize
            );

        BOOTP_FREE(ig.IG_Config);
        ig.IG_Config = pgcdst;

        dwErr = NO_ERROR;
    }

    RELEASE_WRITE_LOCK(&ig.IG_RWL);


    TRACE1(LEAVE, "leaving SetGlobalInfo: %d", dwErr);

    LEAVE_BOOTP_API();

    return dwErr;
}



//----------------------------------------------------------------------------
// Function:    AddInterface
//
// Adds an interface with the specified index and configuration.
//----------------------------------------------------------------------------

DWORD WINAPI
AddInterface (
    PWCHAR              pwszInterfaceName,
    ULONG               InterfaceIndex,
    NET_INTERFACE_TYPE  InterfaceType,
    DWORD               MediaType,
    WORD                AccessType,
    WORD                ConnectionType,
    PVOID               InterfaceInfo,
    ULONG               StructureVersion,
    ULONG               StructureSize,
    ULONG               StructureCount
    )
{
    DWORD dwErr;
    PIF_TABLE pTable;

    if (!ENTER_BOOTP_API()) { return ERROR_CAN_NOT_COMPLETE; }

    TRACE3(
        ENTER, "entering AddInterface: %d %d %p",
        InterfaceIndex, InterfaceType, InterfaceInfo
        );


    pTable = ig.IG_IfTable;

    ACQUIRE_WRITE_LOCK(&pTable->IT_RWL);


    dwErr = CreateIfEntry(pTable, InterfaceIndex, InterfaceInfo);


    RELEASE_WRITE_LOCK(&pTable->IT_RWL);


    TRACE1(LEAVE, "leaving AddInterface: %d", dwErr);

    LEAVE_BOOTP_API();

    return dwErr;
}




//----------------------------------------------------------------------------
// Function:    DeleteInterface
//
// Removes the interface with the specified index.
//----------------------------------------------------------------------------
DWORD
APIENTRY
DeleteInterface(
    IN DWORD dwIndex
    ) {

    DWORD dwErr;
    PIF_TABLE pTable;

    if (!ENTER_BOOTP_API()) { return ERROR_CAN_NOT_COMPLETE; }

    TRACE1(ENTER, "entering DeleteInterface: %d", dwIndex);


    pTable = ig.IG_IfTable;

    ACQUIRE_WRITE_LOCK(&pTable->IT_RWL);


    dwErr = DeleteIfEntry(pTable, dwIndex);


    RELEASE_WRITE_LOCK(&pTable->IT_RWL);


    TRACE1(LEAVE, "leaving DeleteInterface: %d", dwErr);

    LEAVE_BOOTP_API();

    return dwErr;
}



//----------------------------------------------------------------------------
// Function:    GetEventMessage
//
// Returns the first event in the ROuter Manager event queue, if any.
//----------------------------------------------------------------------------
DWORD
APIENTRY
GetEventMessage(
    OUT ROUTING_PROTOCOL_EVENTS *pEvent,
    OUT MESSAGE *pResult
    ) {

    DWORD dwErr;
    PLOCKED_LIST pll;

    if (!ENTER_BOOTP_API()) { return ERROR_CAN_NOT_COMPLETE; }

    TRACE2(ENTER, "entering GetEventMessage: 0x%08x 0x%08x", pEvent, pResult);


    pll = ig.IG_EventQueue;

    ACQUIRE_LIST_LOCK(pll);

    dwErr = DequeueEvent(pll, pEvent, pResult);

    RELEASE_LIST_LOCK(pll);


    TRACE1(LEAVE, "leaving GetEventMessage: %d", dwErr);

    LEAVE_BOOTP_API();

    return dwErr;
}



//----------------------------------------------------------------------------
// Function:    GetInterfaceConfigInfo
//
// Returns the configuration for the specified interface.
//----------------------------------------------------------------------------

DWORD WINAPI
GetInterfaceConfigInfo (
    ULONG   InterfaceIndex,
    PVOID   OutInterfaceInfo,
    PULONG  InterfaceInfoSize,
    PULONG  StructureVersion,
    PULONG  StructureSize,
    PULONG  StructureCount
    )
{
    PIF_TABLE pTable;
    DWORD dwErr, dwSize;
    PIF_TABLE_ENTRY pite;

    if (!ENTER_BOOTP_API()) { return ERROR_CAN_NOT_COMPLETE; }

    TRACE3(
        ENTER, "entering GetInterfaceConfigInfo: %d %p %p",
        InterfaceIndex, InterfaceInfoSize, OutInterfaceInfo
        );


    //
    // in order to do anything, we need a valid size pointer
    //

    if (InterfaceInfoSize == NULL) {
        dwErr = ERROR_INVALID_PARAMETER;
    }
    else {

        pTable = ig.IG_IfTable;

        ACQUIRE_READ_LOCK(&pTable->IT_RWL);


        //
        // retrieve the interface to be re-configured
        //

        pite = GetIfByIndex(pTable, InterfaceIndex);

        if (pite == NULL) {
            dwErr = ERROR_INVALID_PARAMETER;
        }
        else {


            //
            // compute the interface configuration's size,
            // and copy the config to the caller's buffer
            // if the caller's buffer is large enough
            //

            dwSize = IC_SIZEOF(pite->ITE_Config);

            if (*InterfaceInfoSize < dwSize || OutInterfaceInfo == NULL) {
                dwErr = ERROR_INSUFFICIENT_BUFFER;
            }
            else {

                PIPBOOTP_IF_CONFIG picdst = OutInterfaceInfo;

                CopyMemory(picdst, pite->ITE_Config, dwSize);

                picdst->IC_State = 0;

                if (IF_IS_ENABLED(pite)) {
                    picdst->IC_State |= IPBOOTP_STATE_ENABLED;
                }

                if (IF_IS_BOUND(pite)) {
                    picdst->IC_State |= IPBOOTP_STATE_BOUND;
                }

                dwErr = NO_ERROR;
            }


            *InterfaceInfoSize = dwSize;
            
            if (StructureSize) *StructureSize = *InterfaceInfoSize;
            if (StructureCount) *StructureCount = 1;
            if (StructureVersion) *StructureVersion = BOOTP_CONFIG_VERSION_500;
        
        }

        RELEASE_READ_LOCK(&pTable->IT_RWL);
    }



    TRACE1(LEAVE, "leaving GetInterfaceConfigInfo: %d", dwErr);

    LEAVE_BOOTP_API();

    return dwErr;
}



//----------------------------------------------------------------------------
// Function:    SetInterfaceConfigInfo
//
// Copies over the specified interface configuration.
//----------------------------------------------------------------------------

DWORD WINAPI
SetInterfaceConfigInfo (
    ULONG   InterfaceIndex,
    PVOID   InterfaceInfo,
    ULONG   StructureVersion,
    ULONG   StructureSize,
    ULONG   StructureCount
    )
{
    PIF_TABLE pTable;
    DWORD dwErr, dwSize;
    PIF_TABLE_ENTRY pite;
    PIPBOOTP_IF_CONFIG picsrc, picdst;

    if (!ENTER_BOOTP_API()) { return ERROR_CAN_NOT_COMPLETE; }

    TRACE2(
        ENTER, "entering SetInterfaceConfigInfo: %d %p", InterfaceIndex, InterfaceInfo
        );


    pTable = ig.IG_IfTable;

    ACQUIRE_WRITE_LOCK(&pTable->IT_RWL);


    dwErr = ConfigureIfEntry(pTable, InterfaceIndex, InterfaceInfo);


    RELEASE_WRITE_LOCK(&pTable->IT_RWL);


    TRACE1(LEAVE, "leaving SetInterfaceConfigInfo: %d", dwErr);

    LEAVE_BOOTP_API();

    return dwErr;
}


DWORD WINAPI
InterfaceStatus(
    ULONG    InterfaceIndex,
    BOOL     InterfaceActive,
    DWORD    StatusType,
    PVOID    StatusInfo
    )
{
    DWORD   dwResult;

    if (!ENTER_BOOTP_API()) { return ERROR_CAN_NOT_COMPLETE; }

    switch(StatusType)
    {
        case RIS_INTERFACE_ADDRESS_CHANGE:
        {
            PIP_ADAPTER_BINDING_INFO    pBindInfo;

            pBindInfo = (PIP_ADAPTER_BINDING_INFO)StatusInfo;

            if(pBindInfo->AddressCount)
            {
                dwResult = BindInterface(InterfaceIndex,
                                         pBindInfo);
            }
            else
            {
                dwResult = UnBindInterface(InterfaceIndex);
            }

            break;
        }

        case RIS_INTERFACE_ENABLED:
        {
            dwResult = EnableInterface(InterfaceIndex);

            break;
        }

        case RIS_INTERFACE_DISABLED:
        {
            dwResult = DisableInterface(InterfaceIndex);

            break;

        }

        default:
        {
            RTASSERT(FALSE);

            dwResult = ERROR_INVALID_PARAMETER;
        }
    }

    LEAVE_BOOTP_API();

    return dwResult;
}



//----------------------------------------------------------------------------
// Function:    BindInterface
//
// Sets the IP address and network mask for the specified interface.
//----------------------------------------------------------------------------

DWORD
APIENTRY
BindInterface(
    IN DWORD dwIndex,
    IN PVOID pBinding
    ) {

    DWORD dwErr;
    PIF_TABLE pTable;


    TRACE2(ENTER, "entering BindInterface: %d 0x%08x", dwIndex, pBinding);


    pTable = ig.IG_IfTable;

    ACQUIRE_WRITE_LOCK(&pTable->IT_RWL);


    dwErr = BindIfEntry(pTable, dwIndex, pBinding);


    RELEASE_WRITE_LOCK(&pTable->IT_RWL);


    TRACE1(LEAVE, "leaving BindInterface: %d", dwErr);


    return dwErr;
}



//----------------------------------------------------------------------------
// Function:    UnBindInterface
//
// Removes the IP address associated with the specified interface
//----------------------------------------------------------------------------

DWORD
APIENTRY
UnBindInterface(
    IN DWORD dwIndex
    ) {

    DWORD dwErr;
    PIF_TABLE pTable;


    TRACE1(ENTER, "entering UnBindInterface: %d", dwIndex);


    pTable = ig.IG_IfTable;

    ACQUIRE_WRITE_LOCK(&pTable->IT_RWL);


    dwErr = UnBindIfEntry(pTable, dwIndex);


    RELEASE_WRITE_LOCK(&pTable->IT_RWL);


    TRACE1(LEAVE, "leaving UnBindInterface: %d", dwErr);

    return dwErr;
}




//----------------------------------------------------------------------------
// Function:    EnableInterface
//
//----------------------------------------------------------------------------

DWORD
APIENTRY
EnableInterface(
    IN DWORD dwIndex
    ) {

    DWORD dwErr;
    PIF_TABLE pTable;

    TRACE1(ENTER, "entering EnableInterface: %d", dwIndex);


    pTable = ig.IG_IfTable;

    ACQUIRE_WRITE_LOCK(&pTable->IT_RWL);


    dwErr = EnableIfEntry(pTable, dwIndex);


    RELEASE_WRITE_LOCK(&pTable->IT_RWL);


    TRACE1(LEAVE, "leaving EnableInterface: %d", dwErr);

    return dwErr;
}




//----------------------------------------------------------------------------
// Function:    DisableInterface
//
//----------------------------------------------------------------------------

DWORD
APIENTRY
DisableInterface(
    IN DWORD dwIndex
    ) {

    DWORD dwErr;
    PIF_TABLE pTable;
    PIF_TABLE_ENTRY pite;

    TRACE1(ENTER, "entering DisableInterface: %d", dwIndex);


    pTable = ig.IG_IfTable;

    ACQUIRE_WRITE_LOCK(&pTable->IT_RWL);


    dwErr = DisableIfEntry(pTable, dwIndex);


    RELEASE_WRITE_LOCK(&pTable->IT_RWL);


    TRACE1(LEAVE, "leaving DisableInterface: %d", dwErr);

    return dwErr;
}




//----------------------------------------------------------------------------
// Function:    DoUpdateRoutes
//
// This API is unsupported since BOOTP is not a routing protocol.
//----------------------------------------------------------------------------

DWORD
APIENTRY
DoUpdateRoutes(
    IN DWORD dwIndex
    ) {

    return ERROR_CAN_NOT_COMPLETE;
}



//----------------------------------------------------------------------------
// Function:    MibCreate
//
// BOOTP does not have create-able MIB fields.
//----------------------------------------------------------------------------

DWORD
APIENTRY
MibCreate(
    IN DWORD dwInputSize,
    IN PVOID pInputData
    ) {

    return ERROR_CAN_NOT_COMPLETE;
}



//----------------------------------------------------------------------------
// Function:    MibDelete
//
// BOOTP does not have delete-able MIB fields
//----------------------------------------------------------------------------

DWORD
APIENTRY
MibDelete(
    IN DWORD dwInputSize,
    IN PVOID pInputData
    ) {

    return ERROR_CAN_NOT_COMPLETE;
}



//----------------------------------------------------------------------------
// Function:    MibSet
//
// This is called to modify writable MIB variables.
// The writable entries are the global config and interface config.
//----------------------------------------------------------------------------

DWORD
APIENTRY
MibSet(
    IN DWORD dwInputSize,
    IN PVOID pInputData
    ) {

    DWORD dwErr;
    PIPBOOTP_MIB_SET_INPUT_DATA pimsid;

    if (!ENTER_BOOTP_API()) { return ERROR_CAN_NOT_COMPLETE; }

    TRACE2(ENTER, "entering MibSet: %d 0x%08x", dwInputSize, pInputData);


    dwErr = NO_ERROR;

    do { // breakout loop


        //
        // make certain the parameters are acceptable
        //

        if (pInputData == NULL ||
            dwInputSize < sizeof(IPBOOTP_MIB_SET_INPUT_DATA)) {

            dwErr = ERROR_INVALID_PARAMETER;
            break;
        }


        //
        // see which entry type is to be set
        //

        pimsid = (PIPBOOTP_MIB_SET_INPUT_DATA)pInputData;

        switch(pimsid->IMSID_TypeID) {


            case IPBOOTP_GLOBAL_CONFIG_ID: {

                PIPBOOTP_GLOBAL_CONFIG pigc;


                //
                // make sure the buffer is big enough
                // to hold a global config block
                //

                if (pimsid->IMSID_BufferSize < sizeof(IPBOOTP_GLOBAL_CONFIG)) {
                    dwErr = ERROR_INSUFFICIENT_BUFFER;
                    break;
                }


                //
                // call the router manager API to set the config
                //

                dwErr = SetGlobalInfo(pimsid->IMSID_Buffer,
                                      1,
                                      sizeof(IPBOOTP_GLOBAL_CONFIG),
                                      1);

                if (dwErr == NO_ERROR) {

                    //
                    // the set succeeded, so notify the router manager
                    // that the global config has changed and should be saved
                    //

                    MESSAGE msg = {0, 0, 0};

                    ACQUIRE_LIST_LOCK(ig.IG_EventQueue);
                    EnqueueEvent(
                        ig.IG_EventQueue,
                        SAVE_GLOBAL_CONFIG_INFO,
                        msg
                        );
                    SetEvent(ig.IG_EventEvent);
                    RELEASE_LIST_LOCK(ig.IG_EventQueue);
                }

                break;
            }

            case IPBOOTP_IF_CONFIG_ID: {

                DWORD dwSize;
                PIF_TABLE pTable;
                PIF_TABLE_ENTRY pite;
                PIPBOOTP_IF_CONFIG pic;


                //
                // make sure the buffer is big enough
                // to hold an interface config block
                //

                if (pimsid->IMSID_BufferSize < sizeof(IPBOOTP_IF_CONFIG)) {
                    dwErr = ERROR_INSUFFICIENT_BUFFER;
                    break;
                }


                pic = (PIPBOOTP_IF_CONFIG)pimsid->IMSID_Buffer;

                pTable = ig.IG_IfTable;


                ACQUIRE_WRITE_LOCK(&pTable->IT_RWL);


                //
                // find the interface and update its config
                //

                pite = GetIfByIndex(
                            pTable,
                            pimsid->IMSID_IfIndex
                            );

                if (pite == NULL) {

                    TRACE1(
                        CONFIG, "MibSet: could not find interface %d",
                        pimsid->IMSID_IfIndex
                        );

                    dwErr = ERROR_INVALID_PARAMETER;
                }
                else {

                    //
                    // configure the interface
                    //

                    dwErr = ConfigureIfEntry(pTable, pite->ITE_Index, pic);
                }


                if (dwErr == NO_ERROR) {

                    //
                    // notify Router manager that config has changed
                    //

                    MESSAGE msg = {0, 0, 0};

                    msg.InterfaceIndex = pite->ITE_Index;

                    ACQUIRE_LIST_LOCK(ig.IG_EventQueue);
                    EnqueueEvent(
                        ig.IG_EventQueue,
                        SAVE_INTERFACE_CONFIG_INFO,
                        msg
                        );
                    SetEvent(ig.IG_EventEvent);
                    RELEASE_LIST_LOCK(ig.IG_EventQueue);
                }


                RELEASE_WRITE_LOCK(&pTable->IT_RWL);

                break;
            }
            default: {
                dwErr = ERROR_INVALID_PARAMETER;
            }
        }
    } while(FALSE);

    LEAVE_BOOTP_API();

    return dwErr;
}




//----------------------------------------------------------------------------
// Function:    MibGet
//
// This function retrieves a MIB entry.
//----------------------------------------------------------------------------

DWORD
APIENTRY
MibGet(
    IN DWORD dwInputSize,
    IN PVOID pInputData,
    IN OUT PDWORD pdwOutputSize,
    OUT PVOID pOutputData
    ) {

    DWORD dwErr;
    PIPBOOTP_MIB_GET_INPUT_DATA pimgid;
    PIPBOOTP_MIB_GET_OUTPUT_DATA pimgod;

    if (!ENTER_BOOTP_API()) { return ERROR_CAN_NOT_COMPLETE; }

    TRACE4(
        ENTER, "entering MibGet: %d 0x%08x 0x%08x 0x%08x",
        dwInputSize, pInputData, pdwOutputSize, pOutputData
        );

    if (pInputData == NULL ||
        dwInputSize < sizeof(IPBOOTP_MIB_GET_INPUT_DATA) ||
        pdwOutputSize == NULL) {
        dwErr = ERROR_INVALID_PARAMETER;
    }
    else {

        //
        // invoke the internal function for retrieving the MIB
        //

        pimgid = (PIPBOOTP_MIB_GET_INPUT_DATA)pInputData;
        pimgod = (PIPBOOTP_MIB_GET_OUTPUT_DATA)pOutputData;

        dwErr = MibGetInternal(pimgid, pimgod, pdwOutputSize, GETMODE_EXACT);
    }


    TRACE1(LEAVE, "leaving MibGet: %d", dwErr);

    LEAVE_BOOTP_API();

    return dwErr;
}




//----------------------------------------------------------------------------
// Function:    MibGetFirst
//
// This function retrieve a MIB entry from one of the MIB tables,
// but it differs from MibGet in that it always returns the first entry
// in the table specified.
//----------------------------------------------------------------------------
DWORD
APIENTRY
MibGetFirst(
    IN DWORD dwInputSize,
    IN PVOID pInputData,
    IN OUT PDWORD pdwOutputSize,
    OUT PVOID pOutputData
    ) {

    DWORD dwErr;
    PIPBOOTP_MIB_GET_INPUT_DATA pimgid;
    PIPBOOTP_MIB_GET_OUTPUT_DATA pimgod;

    if (!ENTER_BOOTP_API()) { return ERROR_CAN_NOT_COMPLETE; }

    TRACE4(
        ENTER, "entering MibGetFirst: %d 0x%08x 0x%08x 0x%08x",
        dwInputSize, pInputData, pdwOutputSize, pOutputData
        );


    if (pInputData == NULL ||
        dwInputSize < sizeof(IPBOOTP_MIB_GET_INPUT_DATA) ||
        pdwOutputSize == NULL) {
        dwErr = ERROR_INVALID_PARAMETER;
    }
    else {

        pimgid = (PIPBOOTP_MIB_GET_INPUT_DATA)pInputData;
        pimgod = (PIPBOOTP_MIB_GET_OUTPUT_DATA)pOutputData;

        dwErr = MibGetInternal(pimgid, pimgod, pdwOutputSize, GETMODE_FIRST);
    }


    TRACE1(LEAVE, "leaving MibGetFirst: %d", dwErr);

    LEAVE_BOOTP_API();

    return dwErr;
}



//----------------------------------------------------------------------------
// Function:    MibGetNext
//
// This function retrieves a MIB entry from one of the MIB tables.
// It differs from MibGet() and MibGetFirst() in that the input
// specifies the index of a MIB entry, and this entry returns the MIB entry
// which is AFTER the entry whose index is specified.
//
// If the index specified is that of the last entry in the specified table,
// this function wraps to the FIRST entry in the NEXT table, where "NEXT"
// here means the table whose ID is one greater than the ID passed in.
// Thus calling MibGetNext() for the last entry in the interface stats table
// will return the first entry in the interface config table.
//----------------------------------------------------------------------------

DWORD
APIENTRY
MibGetNext(
    IN DWORD dwInputSize,
    IN PVOID pInputData,
    IN OUT PDWORD pdwOutputSize,
    OUT PVOID pOutputData
    ) {

    DWORD dwErr, dwOutSize = 0, dwBufSize = 0;
    PIPBOOTP_MIB_GET_INPUT_DATA pimgid;
    PIPBOOTP_MIB_GET_OUTPUT_DATA pimgod;

    if (!ENTER_BOOTP_API()) { return ERROR_CAN_NOT_COMPLETE; }

    TRACE4(
        ENTER, "entering MibGetNext: %d 0x%08x 0x%08x 0x%08x",
        dwInputSize, pInputData, pdwOutputSize, pOutputData
        );


    if (pInputData == NULL ||
        dwInputSize < sizeof(IPBOOTP_MIB_GET_INPUT_DATA) ||
        pdwOutputSize == NULL) {
        dwErr = ERROR_INVALID_PARAMETER;
    }
    else {

        pimgid = (PIPBOOTP_MIB_GET_INPUT_DATA)pInputData;
        pimgod = (PIPBOOTP_MIB_GET_OUTPUT_DATA)pOutputData;

        dwOutSize = *pdwOutputSize;

        dwErr = MibGetInternal(pimgid, pimgod, pdwOutputSize, GETMODE_NEXT);

        if (dwErr == ERROR_NO_MORE_ITEMS) {

            //
            // wrap to the first entry in the next table
            //

            TRACE1(
                CONFIG, "MibGetNext is wrapping to table %d",
                pimgid->IMGID_TypeID + 1
            );


            //
            // restore the size passed in
            //

            *pdwOutputSize = dwOutSize;


            //
            // wrap to the next table by incrementing the type ID
            //

            ++pimgid->IMGID_TypeID;
            dwErr = MibGetInternal(
                        pimgid, pimgod, pdwOutputSize, GETMODE_FIRST
                        );
            --pimgid->IMGID_TypeID;
        }
    }

    TRACE1(LEAVE, "leaving MibGetNext: %d", dwErr);

    LEAVE_BOOTP_API();

    return dwErr;
}




//----------------------------------------------------------------------------
// Function:    MibGetInternal
//
// This function handles the structure queries necessary to read MIB data.
// Each table exposed by IPBOOTP supports three types of queries:
// EXACT, FIRST, and NEXT, which correspond to the functions MibGet(),
// MibGetFirst(), and MibGetNext() respectively.
//----------------------------------------------------------------------------

DWORD
MibGetInternal(
    PIPBOOTP_MIB_GET_INPUT_DATA pimgid,
    PIPBOOTP_MIB_GET_OUTPUT_DATA pimgod,
    PDWORD pdwOutputSize,
    DWORD dwGetMode
    ) {

    DWORD dwErr, dwBufSize, dwSize;
    ULONG   ulVersion, ulSize,ulCount;

    dwErr = NO_ERROR;


    //
    // first we use pdwOutputSize  to compute the size of the buffer
    // available (i.e. the size of IMGOD_Buffer
    //

    if (pimgod == NULL ||
        *pdwOutputSize < sizeof(IPBOOTP_MIB_GET_OUTPUT_DATA)) {
        dwBufSize = 0;
    }
    else {
        dwBufSize = *pdwOutputSize - sizeof(IPBOOTP_MIB_GET_OUTPUT_DATA) + 1;
    }

    *pdwOutputSize = 0;


    //
    // determine which type of data is to be returned
    //

    switch (pimgid->IMGID_TypeID) {

        case IPBOOTP_GLOBAL_CONFIG_ID: {

            //
            // the global config struct is variable-length,
            // so we wait until it has been retrieved before setting the size;
            // GETMODE_NEXT is invalid since there is only one global config
            //

            if (pimgod) { pimgod->IMGOD_TypeID = IPBOOTP_GLOBAL_CONFIG_ID; }

            if (dwGetMode == GETMODE_NEXT) {
                dwErr = ERROR_NO_MORE_ITEMS;
                break;
            }


            //
            // Use GetGlobalInfo to retrieve the global information;
            // It will decide whether the buffer is large enough,
            // and it will set the required size. Then all we need to do
            // is write out the size set by GetGlobalInfo.
            //

            if (pimgod == NULL) {
                dwErr = GetGlobalInfo(NULL, &dwBufSize, NULL, NULL, NULL);
            }
            else {

                dwErr = GetGlobalInfo(
                            pimgod->IMGOD_Buffer, &dwBufSize, &ulVersion, &ulSize, &ulCount
                            );
            }

            *pdwOutputSize = sizeof(IPBOOTP_MIB_GET_OUTPUT_DATA) - 1 +
                             dwBufSize;

            break;
        }


        case IPBOOTP_IF_STATS_ID: {

            //
            // the interface stats struct is fixed-length,
            // with as many entries as there are interfaces
            //

            PIF_TABLE pTable;
            PIF_TABLE_ENTRY pite;
            PIPBOOTP_IF_STATS pissrc, pisdst;


            //
            // set the size needed right away
            //

            *pdwOutputSize = sizeof(IPBOOTP_MIB_GET_OUTPUT_DATA) - 1 +
                             sizeof(IPBOOTP_IF_STATS);
            if (pimgod) { pimgod->IMGOD_TypeID = IPBOOTP_IF_STATS_ID; }


            pTable = ig.IG_IfTable;


            ACQUIRE_READ_LOCK(&pTable->IT_RWL);

            pite  = GetIfByListIndex(
                        pTable,
                        pimgid->IMGID_IfIndex,
                        dwGetMode,
                        &dwErr
                        );

            //
            // if the interface was not found, it may mean
            // the specified index was invalid, or it may mean that
            // GETMODE_NEXT was attempted on the last interface,
            // in which case dwErr contains ERROR_NO_MORE_ITEMS.
            // In any case, we make sure dwErr contains an error code
            // and then return.
            //

            if (pite == NULL) {
                if (dwErr == NO_ERROR) { dwErr = ERROR_INVALID_PARAMETER; }
            }
            else
            if (pimgod == NULL) {
                dwErr = ERROR_INSUFFICIENT_BUFFER;
            }
            else {

                //
                // write the index of the interface
                // whose stats are to be returned
                //

                pimgod->IMGOD_IfIndex = pite->ITE_Index;


                //
                // if the buffer is large enough, copy the stats to it
                //

                if (dwBufSize < sizeof(IPBOOTP_IF_STATS)) {
                    dwErr = ERROR_INSUFFICIENT_BUFFER;
                }
                else {

                    //
                    // since access to this structure is not synchronized
                    // we must copy it field by field
                    //

                    pissrc = &pite->ITE_Stats;
                    pisdst = (PIPBOOTP_IF_STATS)pimgod->IMGOD_Buffer;

                    pisdst->IS_State = 0;

                    if (IF_IS_ENABLED(pite)) {
                        pisdst->IS_State |= IPBOOTP_STATE_ENABLED;
                    }

                    if (IF_IS_BOUND(pite)) {
                        pisdst->IS_State |= IPBOOTP_STATE_BOUND;
                    }

                    pisdst->IS_SendFailures =
                            pissrc->IS_SendFailures;
                    pisdst->IS_ReceiveFailures =
                            pissrc->IS_ReceiveFailures;
                    pisdst->IS_ArpUpdateFailures =
                            pissrc->IS_ArpUpdateFailures;
                    pisdst->IS_RequestsReceived =
                            pissrc->IS_RequestsReceived;
                    pisdst->IS_RequestsDiscarded =
                            pissrc->IS_RequestsDiscarded;
                    pisdst->IS_RepliesReceived =
                            pissrc->IS_RepliesReceived;
                    pisdst->IS_RepliesDiscarded =
                            pissrc->IS_RepliesDiscarded;
                }
            }

            RELEASE_READ_LOCK(&pTable->IT_RWL);


            break;
        }

        case IPBOOTP_IF_CONFIG_ID: {


            PIF_TABLE pTable;
            PIF_TABLE_ENTRY pite;
            PIPBOOTP_IF_CONFIG picsrc, picdst;

            if (pimgod) { pimgod->IMGOD_TypeID = IPBOOTP_IF_CONFIG_ID; }

            pTable = ig.IG_IfTable;

            ACQUIRE_READ_LOCK(&pTable->IT_RWL);


            //
            // retrieve the interface whose config is to be read
            //

            pite = GetIfByListIndex(
                        pTable, pimgid->IMGID_IfIndex, dwGetMode, &dwErr
                        );

            //
            // if the interface was not found, it may mean that the index
            // specified was invalid, or it may mean that a GETMODE_NEXT
            // retrieval was attempted on the last interface, in which case
            // ERROR_NO_MORE_ITEMS would have been returned
            //

            if (pite == NULL) {
                if (dwErr == NO_ERROR) { dwErr = ERROR_INVALID_PARAMETER; }
            }
            else {

                picsrc = pite->ITE_Config;
                dwSize = IC_SIZEOF(picsrc);
                *pdwOutputSize = sizeof(IPBOOTP_MIB_GET_OUTPUT_DATA) - 1 +
                                 dwSize;

                //
                // if no buffer was specified, indicate one should be allocated
                //

                if (pimgod == NULL) {
                    dwErr = ERROR_INSUFFICIENT_BUFFER;
                }
                else {

                    //
                    // if the buffer is not large enough,
                    // indicate that it should be enlarged
                    //

                    if (dwBufSize < dwSize) {
                        dwErr = ERROR_INSUFFICIENT_BUFFER;
                    }
                    else {

                        //
                        // copy the configuration
                        //

                        picdst = (PIPBOOTP_IF_CONFIG)pimgod->IMGOD_Buffer;
                        CopyMemory(picdst, picsrc, dwSize);

                        picdst->IC_State = 0;

                        if (IF_IS_ENABLED(pite)) {
                            picdst->IC_State |= IPBOOTP_STATE_ENABLED;
                        }

                        if (IF_IS_BOUND(pite)) {
                            picdst->IC_State |= IPBOOTP_STATE_BOUND;
                        }
                    }

                    pimgod->IMGOD_IfIndex = pite->ITE_Index;
                }

            }

            RELEASE_READ_LOCK(&pTable->IT_RWL);

            break;
        }

        case IPBOOTP_IF_BINDING_ID: {

            PIF_TABLE pTable;
            PIF_TABLE_ENTRY pite;
            PIPBOOTP_IF_BINDING pibsrc, pibdst;

            if (pimgod) { pimgod->IMGOD_TypeID = IPBOOTP_IF_BINDING_ID; }

            pTable = ig.IG_IfTable;

            ACQUIRE_READ_LOCK(&pTable->IT_RWL);


            //
            // retrieve the interface whose binding is to be read
            //

            pite = GetIfByListIndex(
                        pTable, pimgid->IMGID_IfIndex, dwGetMode, &dwErr
                        );

            //
            // if the interface was not found, it may mean that the index
            // specified was invalid, or it may mean that a GETMODE_NEXT
            // retrieval was attempted on the last interface, in which case
            // ERROR_NO_MORE_ITEMS would have been returned
            //

            if (pite == NULL) {
                if (dwErr == NO_ERROR) { dwErr = ERROR_INVALID_PARAMETER; }
            }
            else {

                pibsrc = pite->ITE_Binding;
                
                if (pibsrc == NULL ) {
                    TRACE1(
                        IF, "MibGetInternal: interface %d not bound", 
                        pimgid->IMGID_IfIndex
                        );

                    dwErr = ERROR_INVALID_PARAMETER;
                }
                else {

                    dwSize = (pibsrc ? IPBOOTP_IF_BINDING_SIZE(pibsrc)
                                     : sizeof(IPBOOTP_IF_BINDING));
                    *pdwOutputSize = sizeof(IPBOOTP_MIB_GET_OUTPUT_DATA) - 1 +
                                     dwSize;

                    //
                    // if no buffer was specified, indicate one should be allocated
                    //

                    if (pimgod == NULL) {
                        dwErr = ERROR_INSUFFICIENT_BUFFER;
                    }
                    else {

                        //
                        // if the buffer is not large enough,
                        // indicate that it should be enlarged
                        //

                        if (dwBufSize < dwSize) {
                            dwErr = ERROR_INSUFFICIENT_BUFFER;
                        }
                        else {

                            //
                            // copy the binding
                            //

                            pibdst = (PIPBOOTP_IF_BINDING)pimgod->IMGOD_Buffer;
                            if (pibsrc) { CopyMemory(pibdst, pibsrc, dwSize); }
                            else { pibdst->IB_AddrCount = 0; }

                            pibdst->IB_State = 0;

                            if (IF_IS_ENABLED(pite)) {
                                pibdst->IB_State |= IPBOOTP_STATE_ENABLED;
                            }

                            if (IF_IS_BOUND(pite)) {
                                pibdst->IB_State |= IPBOOTP_STATE_BOUND;
                            }
                        }

                        pimgod->IMGOD_IfIndex = pite->ITE_Index;
                    }

                }

            }

            RELEASE_READ_LOCK(&pTable->IT_RWL);

            break;
        }

        default: {
            dwErr = ERROR_INVALID_PARAMETER;
        }
    }

    return dwErr;
}


//----------------------------------------------------------------------------
// Function:    EnableDhcpInformServer
//
// Called to supply the address of a server to whom DHCP inform messages
// will be redirected. Note that this is an exported routine, invoked
// in the context of the caller's process, whatever that might be;
// the assumption is that it will be called from within the router process.
//
// If the relay-agent is configured, then this sets an address which will
// be picked up in 'ProcessRequest' for every incoming request.
// If the relay-agent is not configured, the routine has no effect.
// If the relay-agent is configured *after* this routine is called,
// then the DHCP inform server will be encountered as soon as the relay-agent
// starts, since it is saved directly into the relay-agents 'IPBOOTP_GLOBALS'.
//----------------------------------------------------------------------------

VOID APIENTRY
EnableDhcpInformServer(
    DWORD DhcpInformServer
    ) {
    InterlockedExchange(&ig.IG_DhcpInformServer, DhcpInformServer);
}

//----------------------------------------------------------------------------
// Function:    DisableDhcpInformServer
//
// Called to clear the previously-enabled DHCP inform server, if any.
//----------------------------------------------------------------------------

VOID APIENTRY
DisableDhcpInformServer(
    VOID
    ) {
    InterlockedExchange(&ig.IG_DhcpInformServer, 0);
}