/*++

Copyright (c) 1991-1992  Microsoft Corporation

Module Name:

    brdevice.c

Abstract:

    This module contains the support routines for the APIs that call
    into the browser or the datagram receiver.

Author:

    Rita Wong (ritaw) 20-Feb-1991
    Larry Osterman (larryo) 23-Mar-1992

Revision History:

--*/

#include "precomp.h"
#pragma hdrstop

//-------------------------------------------------------------------//
//                                                                   //
// Local Function Prototypes                                         //
//                                                                   //
//-------------------------------------------------------------------//


//-------------------------------------------------------------------//
//                                                                   //
// Global variables                                                  //
//                                                                   //
//-------------------------------------------------------------------//

// Event for synchronization of asynchronous I/O completion against the
// datagram receiver

HANDLE           BrDgAsyncIOShutDownEvent;
HANDLE           BrDgAsyncIOThreadShutDownEvent;
BOOL             BrDgShutDownInitiated = FALSE;
DWORD            BrDgAsyncIOsOutstanding = 0;
DWORD            BrDgWorkerThreadsOutstanding = 0;
CRITICAL_SECTION BrAsyncIOCriticalSection;


//
// Handle to the Datagram Receiver DD
//
HANDLE BrDgReceiverDeviceHandle = NULL;

VOID
CompleteAsyncBrowserIoControl(
                             IN PVOID ApcContext,
                             IN PIO_STATUS_BLOCK IoStatusBlock,
                             IN ULONG Reserved
                             );

VOID
BrDecrementOutstandingIos()
/*++

Routine Description:

    Decrements the outstanding IO count, and signals the event if necessary

Arguments:

    None.

Return Value:

    VOID

--*/
{
    BOOL SignalAsyncIOShutDownEvent = FALSE;

    EnterCriticalSection(&BrAsyncIOCriticalSection);

    BrDgAsyncIOsOutstanding -= 1;

    if (BrDgAsyncIOsOutstanding == 0 &&
        BrDgShutDownInitiated) {
        SignalAsyncIOShutDownEvent = TRUE;
    }

    LeaveCriticalSection(&BrAsyncIOCriticalSection);

    if (SignalAsyncIOShutDownEvent) {
        SetEvent(BrDgAsyncIOShutDownEvent);
    }
}

NET_API_STATUS
BrOpenDgReceiver (
                 VOID
                 )
/*++

Routine Description:

    This routine opens the NT LAN Man Datagram Receiver driver.

Arguments:

    None.

Return Value:

    NET_API_STATUS - NERR_Success or reason for failure.

--*/
{
    NET_API_STATUS  Status;
    NTSTATUS ntstatus;

    UNICODE_STRING DeviceName;

    IO_STATUS_BLOCK IoStatusBlock;
    OBJECT_ATTRIBUTES ObjectAttributes;

    //
    // Open the redirector device.
    //
    RtlInitUnicodeString(&DeviceName, DD_BROWSER_DEVICE_NAME_U);

    InitializeObjectAttributes(
                              &ObjectAttributes,
                              &DeviceName,
                              OBJ_CASE_INSENSITIVE,
                              NULL,
                              NULL
                              );

    ntstatus = NtOpenFile(
                         &BrDgReceiverDeviceHandle,
                         SYNCHRONIZE,
                         &ObjectAttributes,
                         &IoStatusBlock,
                         0,
                         0
                         );

    if (NT_SUCCESS(ntstatus)) {
        ntstatus = IoStatusBlock.Status;
    }

    if (! NT_SUCCESS(ntstatus)) {
        BrPrint(( BR_CRITICAL,"NtOpenFile browser driver failed: 0x%08lx\n",
                  ntstatus));
    }

    Status = NetpNtStatusToApiStatus(ntstatus);

    if (NT_SUCCESS(ntstatus)) {
        // Initialize the event and the critical section used for async I/O

        try {
            BrDgShutDownInitiated = FALSE;
            BrDgAsyncIOsOutstanding = 0;
            BrDgWorkerThreadsOutstanding = 0;

            InitializeCriticalSection( &BrAsyncIOCriticalSection );

            BrDgAsyncIOShutDownEvent =
            CreateEvent(
                       NULL,                // Event attributes
                       TRUE,                // Event must be manually reset
                       FALSE,
                       NULL             // Initial state not signalled
                       );

            if (BrDgAsyncIOShutDownEvent == NULL) {
                DeleteCriticalSection(&BrAsyncIOCriticalSection);
                Status = GetLastError();
            }

            BrDgAsyncIOThreadShutDownEvent =
            CreateEvent(
                       NULL,
                       TRUE,
                       FALSE,
                       NULL
                       );
            if( BrDgAsyncIOThreadShutDownEvent == NULL ) {
                CloseHandle( BrDgAsyncIOShutDownEvent );
                BrDgAsyncIOShutDownEvent = NULL;
                DeleteCriticalSection(&BrAsyncIOCriticalSection);
                Status = GetLastError();
            }
        }
        except ( EXCEPTION_EXECUTE_HANDLER ) {
            Status = ERROR_NO_SYSTEM_RESOURCES;
        }
    }

    return Status;
}



VOID
BrShutdownDgReceiver(
                    VOID
                    )
/*++

Routine Description:

    This routine close the LAN Man Redirector device.

Arguments:

    None.

Return Value:

    None.

--*/
{
    IO_STATUS_BLOCK IoSb;
    LARGE_INTEGER   timeout;
    BOOL            WaitForAsyncIOCompletion = FALSE;
    DWORD           waitResult = 0;

    EnterCriticalSection(&BrAsyncIOCriticalSection);

    BrDgShutDownInitiated = TRUE;

    if (BrDgAsyncIOsOutstanding != 0) {
        WaitForAsyncIOCompletion = TRUE;
    }

    LeaveCriticalSection(&BrAsyncIOCriticalSection);

    if (WaitForAsyncIOCompletion) {
        //
        //  Cancel the I/O operations outstanding on the browser.
        //  Then wait for the shutdown event to be signalled, but allow
        //  APC's to be called to call our completion routine.
        //

        NtCancelIoFile(BrDgReceiverDeviceHandle, &IoSb);

        do {
            waitResult = WaitForSingleObjectEx(BrDgAsyncIOShutDownEvent,0xffffffff, TRUE);
        }
        while( waitResult == WAIT_IO_COMPLETION );
    }

    ASSERT(BrDgAsyncIOsOutstanding == 0);

    EnterCriticalSection(&BrAsyncIOCriticalSection);

    // Wait for the final worker thread to exit if necessary
    if( BrDgWorkerThreadsOutstanding > 0 )
    {
        WaitForAsyncIOCompletion = TRUE;
    }
    else
    {
        WaitForAsyncIOCompletion = FALSE;
    }
        
    LeaveCriticalSection(&BrAsyncIOCriticalSection);

    if( WaitForAsyncIOCompletion )
    {
        // This will either be signalled from before, or the final worker thread will signal it.
        WaitForSingleObject( BrDgAsyncIOThreadShutDownEvent, 0xffffffff );
    }

    if (BrDgAsyncIOShutDownEvent != NULL) {
        CloseHandle(BrDgAsyncIOShutDownEvent);
        CloseHandle(BrDgAsyncIOThreadShutDownEvent);

        DeleteCriticalSection(&BrAsyncIOCriticalSection);
    }
}


//
//  Retreive the list of bound transports from the bowser driver.
//

NET_API_STATUS
BrGetTransportList(
                  OUT PLMDR_TRANSPORT_LIST *TransportList
                  )
{
    NET_API_STATUS Status;
    LMDR_REQUEST_PACKET RequestPacket;

    //
    //  If we have a previous buffer that was too small, free it up.
    //

    RequestPacket.Version = LMDR_REQUEST_PACKET_VERSION_DOM;

    RequestPacket.Type = EnumerateXports;

    RtlInitUnicodeString(&RequestPacket.TransportName, NULL);
    RtlInitUnicodeString(&RequestPacket.EmulatedDomainName, NULL);

    Status = DeviceControlGetInfo(
                                 BrDgReceiverDeviceHandle,
                                 IOCTL_LMDR_ENUMERATE_TRANSPORTS,
                                 &RequestPacket,
                                 sizeof(RequestPacket),
                                 (LPVOID *)TransportList,
                                 0xffffffff,
                                 4096,
                                 NULL
                                 );

    return Status;
}

NET_API_STATUS
BrAnnounceDomain(
                IN PNETWORK Network,
                IN ULONG Periodicity
                )
{
    NET_API_STATUS Status;
    UCHAR AnnounceBuffer[sizeof(BROWSE_ANNOUNCE_PACKET)+LM20_CNLEN+1];
    PBROWSE_ANNOUNCE_PACKET Announcement = (PBROWSE_ANNOUNCE_PACKET )AnnounceBuffer;

    //
    //  We don't announce domains on direct host IPX.
    //

    if (Network->Flags & NETWORK_IPX) {
        return NERR_Success;
    }

    Announcement->BrowseType = WkGroupAnnouncement;

    Announcement->BrowseAnnouncement.Periodicity = Periodicity;

    Announcement->BrowseAnnouncement.UpdateCount = 0;

    Announcement->BrowseAnnouncement.VersionMajor = BROWSER_CONFIG_VERSION_MAJOR;

    Announcement->BrowseAnnouncement.VersionMinor = BROWSER_CONFIG_VERSION_MINOR;

    Announcement->BrowseAnnouncement.Type = SV_TYPE_DOMAIN_ENUM | SV_TYPE_NT;

    if (Network->Flags & NETWORK_PDC ) {
        Announcement->BrowseAnnouncement.Type |= SV_TYPE_DOMAIN_CTRL;
    }

    lstrcpyA(Announcement->BrowseAnnouncement.ServerName, Network->DomainInfo->DomOemDomainName);

    lstrcpyA(Announcement->BrowseAnnouncement.Comment, Network->DomainInfo->DomOemComputerName );

    Status = SendDatagram(BrDgReceiverDeviceHandle,
                          &Network->NetworkName,
                          &Network->DomainInfo->DomUnicodeDomainNameString,
                          Network->DomainInfo->DomUnicodeDomainName,
                          DomainAnnouncement,
                          Announcement,
                          FIELD_OFFSET(BROWSE_ANNOUNCE_PACKET, BrowseAnnouncement.Comment)+
                          Network->DomainInfo->DomOemComputerNameLength+sizeof(UCHAR)
                         );

    if (Status != NERR_Success) {

        BrPrint(( BR_CRITICAL,
                  "%ws: Unable to announce domain for network %wZ: %X\n",
                  Network->DomainInfo->DomUnicodeDomainName,
                  &Network->NetworkName,
                  Status));

    }

    return Status;

}



NET_API_STATUS
BrUpdateBrowserStatus (
                      IN PNETWORK Network,
                      IN DWORD ServiceStatus
                      )
{
    NET_API_STATUS Status;
    UCHAR PacketBuffer[sizeof(LMDR_REQUEST_PACKET)+(LM20_CNLEN+1)*sizeof(WCHAR)];
    PLMDR_REQUEST_PACKET RequestPacket = (PLMDR_REQUEST_PACKET)PacketBuffer;


    RequestPacket->Version = LMDR_REQUEST_PACKET_VERSION_DOM;

    RequestPacket->TransportName = Network->NetworkName;
    RequestPacket->EmulatedDomainName = Network->DomainInfo->DomUnicodeDomainNameString;

    RequestPacket->Parameters.UpdateStatus.NewStatus = ServiceStatus;

    RequestPacket->Parameters.UpdateStatus.IsLanmanNt = (BrInfo.IsLanmanNt != FALSE);

#ifdef ENABLE_PSEUDO_BROWSER
    RequestPacket->Parameters.UpdateStatus.PseudoServerLevel = (BOOL)(BrInfo.PseudoServerLevel);
#endif

    // RequestPacket->Parameters.UpdateStatus.IsMemberDomain = TRUE; // Not used
    // RequestPacket->Parameters.UpdateStatus.IsPrimaryDomainController = Network->DomainInfo->IsPrimaryDomainController;
    // RequestPacket->Parameters.UpdateStatus.IsDomainMaster = Network->DomainInfo->IsDomainMasterBrowser;

    RequestPacket->Parameters.UpdateStatus.MaintainServerList = (BrInfo.MaintainServerList == 1);

    //
    //  Tell the bowser the number of servers in the server table.
    //

    RequestPacket->Parameters.UpdateStatus.NumberOfServersInTable =
    NumberInterimServerListElements(&Network->BrowseTable) +
    NumberInterimServerListElements(&Network->DomainList) +
    Network->TotalBackupServerListEntries +
    Network->TotalBackupDomainListEntries;

    //
    //  This is a simple IoControl - It just updates the status.
    //

    Status = BrDgReceiverIoControl(BrDgReceiverDeviceHandle,
                                   IOCTL_LMDR_UPDATE_STATUS,
                                   RequestPacket,
                                   sizeof(LMDR_REQUEST_PACKET),
                                   NULL,
                                   0,
                                   NULL);

    return Status;
}

NET_API_STATUS
BrIssueAsyncBrowserIoControl(
                            IN PNETWORK Network OPTIONAL,
                            IN ULONG ControlCode,
                            IN PBROWSER_WORKER_ROUTINE CompletionRoutine,
                            IN PVOID OptionalParameter
                            )
/*++

Routine Description:

    Issue an asynchronous Io Control to the browser.  Call the specified
    completion routine when the IO finishes.

Arguments:

    Network - Network the function applies to
        If this parameter is not supplied, the operation is not related to a
            particular network..

    ControlCode - IoControl function code

    CompletionRoutine - Routine to be called when the IO finishes.

    OptionalParameter - Function code specific information

Return Value:

    Status of the operation.

--*/

{
    ULONG PacketSize;
    PLMDR_REQUEST_PACKET RequestPacket = NULL;
    NTSTATUS NtStatus;

    PBROWSERASYNCCONTEXT Context = NULL;

    BOOL    IssueAsyncRequest = FALSE;

    // Check to see if it is OK to issue an async IO request. We do not want to
    // issue these request can be issued.

    EnterCriticalSection(&BrAsyncIOCriticalSection);

    if (!BrDgShutDownInitiated) {
        BrDgAsyncIOsOutstanding += 1;
        IssueAsyncRequest = TRUE;
    }

    LeaveCriticalSection(&BrAsyncIOCriticalSection);

    if (!IssueAsyncRequest) {
        return ERROR_REQ_NOT_ACCEP;
    }


    //
    // Allocate a buffer for the context and the request packet.
    //

    PacketSize = sizeof(LMDR_REQUEST_PACKET) +
                 MAXIMUM_FILENAME_LENGTH * sizeof(WCHAR);
    if ( ARGUMENT_PRESENT(Network) ) {
        PacketSize +=
        Network->NetworkName.MaximumLength +
        Network->DomainInfo->DomUnicodeDomainNameString.Length;
    }

    Context = MIDL_user_allocate(sizeof(BROWSERASYNCCONTEXT) + PacketSize );

    if (Context == NULL) {

        BrDecrementOutstandingIos();
        return(ERROR_NOT_ENOUGH_MEMORY);
    }

    RequestPacket = (PLMDR_REQUEST_PACKET)(Context + 1);

    RequestPacket->Version = LMDR_REQUEST_PACKET_VERSION_DOM;

    //
    //  Set level to FALSE to indicate that find master should not initiate
    //  a findmaster request, simply complete when a new master announces
    //  itself.
    //

    RequestPacket->Level = 0;

    //
    // Fill in the network specific information if it is specified.
    //
    if ( ARGUMENT_PRESENT(Network) ) {

        //
        //  Stick the name of the transport associated with this request at the
        //  end of the request packet.
        //

        RequestPacket->TransportName.MaximumLength = Network->NetworkName.MaximumLength;

        RequestPacket->TransportName.Buffer = (PWSTR)((PCHAR)RequestPacket+sizeof(LMDR_REQUEST_PACKET)+(MAXIMUM_FILENAME_LENGTH*sizeof(WCHAR)));

        RtlCopyUnicodeString(&RequestPacket->TransportName, &Network->NetworkName);

        //
        //  Stick the domain name associated with this request at the
        //  end of the request packet.
        //

        RequestPacket->EmulatedDomainName.MaximumLength = Network->DomainInfo->DomUnicodeDomainNameString.Length;
        RequestPacket->EmulatedDomainName.Length = 0;
        RequestPacket->EmulatedDomainName.Buffer = (PWSTR)(((PCHAR)RequestPacket->TransportName.Buffer) + RequestPacket->TransportName.MaximumLength);

        RtlAppendUnicodeToString(&RequestPacket->EmulatedDomainName, Network->DomainInfo->DomUnicodeDomainName );
    }


    //
    // Do opcode dependent initialization of the request packet.
    //

    switch ( ControlCode ) {
    case IOCTL_LMDR_NEW_MASTER_NAME:
        if (ARGUMENT_PRESENT(OptionalParameter)) {
            LPWSTR MasterName = (LPWSTR) OptionalParameter;

            RequestPacket->Parameters.GetMasterName.MasterNameLength =
            wcslen(MasterName+2)*sizeof(WCHAR);

            wcscpy( RequestPacket->Parameters.GetMasterName.Name, MasterName+2);

        } else {

            RequestPacket->Parameters.GetMasterName.MasterNameLength = 0;

        }
        break;
    }


    //
    // Send the request to the bowser.
    //

    BrInitializeWorkItem(&Context->WorkItem, CompletionRoutine, Context);

    Context->Network = Network;

    Context->RequestPacket = RequestPacket;

    NtStatus = NtDeviceIoControlFile(BrDgReceiverDeviceHandle,
                                     NULL,
                                     CompleteAsyncBrowserIoControl,
                                     Context,
                                     &Context->IoStatusBlock,
                                     ControlCode,
                                     RequestPacket,
                                     PacketSize,
                                     RequestPacket,
                                     sizeof(LMDR_REQUEST_PACKET)+MAXIMUM_FILENAME_LENGTH*sizeof(WCHAR)
                                    );

    if (NT_ERROR(NtStatus)) {

        BrPrint(( BR_CRITICAL,
                  "Unable to issue browser IoControl: %X\n", NtStatus));

        MIDL_user_free(Context);

        BrDecrementOutstandingIos();

        return(BrMapStatus(NtStatus));
    }

    return NERR_Success;

}

VOID
CompleteAsyncBrowserIoControl(
                             IN PVOID ApcContext,
                             IN PIO_STATUS_BLOCK IoStatusBlock,
                             IN ULONG Reserved
                             )
{

    PBROWSERASYNCCONTEXT Context = ApcContext;

    //
    //  If this request was canceled, we're stopping the browser, so we
    //  want to clean up our allocated pool.  In addition, don't bother
    //  calling into the routine - the threads are gone by now.
    //

    if (IoStatusBlock->Status == STATUS_CANCELLED) {

        MIDL_user_free(Context);

        // Signal the thread waiting on the completion in case of shut down
        // and reset the flag.

        BrDecrementOutstandingIos();

        return;

    }

    //
    //  Timestamp when this request was completed.  This allows us to tell
    //  where a request spent its time.
    //

    NtQueryPerformanceCounter(&Context->TimeCompleted, NULL);

    BrQueueWorkItem(&Context->WorkItem);

    // Signal the thread waiting on the completion in case of shut down
    // and reset the flag.

    BrDecrementOutstandingIos();
}

NET_API_STATUS
BrGetLocalBrowseList(
                    IN PNETWORK Network,
                    IN LPWSTR DomainName OPTIONAL,
                    IN ULONG Level,
                    IN ULONG ServerType,
                    OUT PVOID *ServerList,
                    OUT PULONG EntriesRead,
                    OUT PULONG TotalEntries
                    )
{
    NET_API_STATUS status;
    PLMDR_REQUEST_PACKET Drp;            // Datagram receiver request packet
    ULONG DrpSize;
    ULONG DomainNameSize;

    //
    // Allocate the request packet large enough to hold the variable length
    // domain name.
    //

    DomainNameSize = ARGUMENT_PRESENT(DomainName) ? (wcslen(DomainName) + 1) * sizeof(WCHAR) : 0;


    DrpSize = sizeof(LMDR_REQUEST_PACKET) +
              DomainNameSize +
              Network->NetworkName.MaximumLength +
              Network->DomainInfo->DomUnicodeDomainNameString.Length;

    if ((Drp = MIDL_user_allocate(DrpSize)) == NULL) {

        return GetLastError();
    }

    //
    // Set up request packet.  Output buffer structure is of enumerate
    // servers type.
    //

    Drp->Version = LMDR_REQUEST_PACKET_VERSION_DOM;
    Drp->Type = EnumerateServers;

    Drp->Level = Level;

    Drp->Parameters.EnumerateServers.ServerType = ServerType;
    Drp->Parameters.EnumerateServers.ResumeHandle = 0;

    //
    // Copy the transport name into the buffer.
    //
    Drp->TransportName.Buffer = (PWSTR)((PCHAR)Drp+
                                        sizeof(LMDR_REQUEST_PACKET) +
                                        DomainNameSize);

    Drp->TransportName.MaximumLength = Network->NetworkName.MaximumLength;

    RtlCopyUnicodeString(&Drp->TransportName, &Network->NetworkName);

    //
    // Copy the enumalated domain name into the buffer.
    //

    Drp->EmulatedDomainName.MaximumLength = Network->DomainInfo->DomUnicodeDomainNameString.Length;
    Drp->EmulatedDomainName.Length = 0;
    Drp->EmulatedDomainName.Buffer = (PWSTR)(((PCHAR)Drp->TransportName.Buffer) + Drp->TransportName.MaximumLength);

    RtlAppendUnicodeToString(&Drp->EmulatedDomainName, Network->DomainInfo->DomUnicodeDomainName );

    //
    // Copy the queried domain name into the buffer.
    //

    if (ARGUMENT_PRESENT(DomainName)) {

        Drp->Parameters.EnumerateServers.DomainNameLength = DomainNameSize - sizeof(WCHAR);
        wcscpy(Drp->Parameters.EnumerateServers.DomainName, DomainName);

    } else {
        Drp->Parameters.EnumerateServers.DomainNameLength = 0;
        Drp->Parameters.EnumerateServers.DomainName[0] = '\0';
    }

    //
    // Ask the datagram receiver to enumerate the servers
    //

    status = DeviceControlGetInfo(
                                 BrDgReceiverDeviceHandle,
                                 IOCTL_LMDR_ENUMERATE_SERVERS,
                                 Drp,
                                 DrpSize,
                                 ServerList,
                                 0xffffffff,
                                 4096,
                                 NULL
                                 );

    *EntriesRead = Drp->Parameters.EnumerateServers.EntriesRead;
    *TotalEntries = Drp->Parameters.EnumerateServers.TotalEntries;

    (void) MIDL_user_free(Drp);

    return status;

}

NET_API_STATUS
BrRemoveOtherDomain(
                   IN PNETWORK Network,
                   IN LPTSTR ServerName
                   )
{
    NET_API_STATUS Status;
    UCHAR PacketBuffer[sizeof(LMDR_REQUEST_PACKET)+(LM20_CNLEN+1)*sizeof(WCHAR)];
    PLMDR_REQUEST_PACKET RequestPacket = (PLMDR_REQUEST_PACKET)PacketBuffer;


    RequestPacket->Version = LMDR_REQUEST_PACKET_VERSION_DOM;

    RequestPacket->TransportName = Network->NetworkName;
    RequestPacket->EmulatedDomainName = Network->DomainInfo->DomUnicodeDomainNameString;

    RequestPacket->Parameters.AddDelName.DgReceiverNameLength = STRLEN(ServerName)*sizeof(TCHAR);

    RequestPacket->Parameters.AddDelName.Type = OtherDomain;

    STRCPY(RequestPacket->Parameters.AddDelName.Name,ServerName);

    //
    //  This is a simple IoControl - It just updates the status.
    //

    Status = BrDgReceiverIoControl(BrDgReceiverDeviceHandle,
                                   IOCTL_LMDR_DELETE_NAME_DOM,
                                   RequestPacket,
                                   sizeof(LMDR_REQUEST_PACKET),
                                   NULL,
                                   0,
                                   NULL);

    return Status;
}

NET_API_STATUS
BrAddName(
         IN PNETWORK Network,
         IN LPTSTR Name,
         IN DGRECEIVER_NAME_TYPE NameType
         )
/*++

Routine Description:

    Add a single name to a single transport.

Arguments:

    Network - Transport to add the name to

    Name - Name to add

    NameType - Type of the name to add

Return Value:

    None.

--*/
{
    NET_API_STATUS Status;
    UCHAR PacketBuffer[sizeof(LMDR_REQUEST_PACKET)+(LM20_CNLEN+1)*sizeof(WCHAR)];
    PLMDR_REQUEST_PACKET RequestPacket = (PLMDR_REQUEST_PACKET)PacketBuffer;


    RequestPacket->Version = LMDR_REQUEST_PACKET_VERSION_DOM;

    RequestPacket->TransportName = Network->NetworkName;
    RequestPacket->EmulatedDomainName = Network->DomainInfo->DomUnicodeDomainNameString;

    RequestPacket->Parameters.AddDelName.DgReceiverNameLength = STRLEN(Name)*sizeof(TCHAR);

    RequestPacket->Parameters.AddDelName.Type = NameType;

    STRCPY(RequestPacket->Parameters.AddDelName.Name,Name);

    //
    //  This is a simple IoControl - It just updates the status.
    //

    Status = BrDgReceiverIoControl(BrDgReceiverDeviceHandle,
                                   IOCTL_LMDR_ADD_NAME_DOM,
                                   RequestPacket,
                                   sizeof(LMDR_REQUEST_PACKET),
                                   NULL,
                                   0,
                                   NULL);

    return Status;
}


NET_API_STATUS
BrQueryOtherDomains(
                   OUT LPSERVER_INFO_100 *ReturnedBuffer,
                   OUT LPDWORD TotalEntries
                   )

/*++

Routine Description:

    This routine returns the list of "other domains" configured for this
    machine.

Arguments:

    ReturnedBuffer - Returns the list of other domains as a SERVER_INFO_100 structure.

    TotalEntries - Returns the total number of other domains.

Return Value:

    NET_API_STATUS - The status of this request.

--*/

{
    NET_API_STATUS Status;
    LMDR_REQUEST_PACKET RequestPacket;
    PDGRECEIVE_NAMES NameTable;
    PVOID Buffer;
    LPTSTR BufferEnd;
    PSERVER_INFO_100 ServerInfo;
    ULONG NumberOfOtherDomains;
    ULONG BufferSizeNeeded;
    ULONG i;

    RequestPacket.Type = EnumerateNames;
    RequestPacket.Version = LMDR_REQUEST_PACKET_VERSION_DOM;
    RequestPacket.Level = 0;
    RequestPacket.TransportName.Length = 0;
    RequestPacket.TransportName.Buffer = NULL;
    RtlInitUnicodeString( &RequestPacket.EmulatedDomainName, NULL );
    RequestPacket.Parameters.EnumerateNames.ResumeHandle = 0;

    Status = DeviceControlGetInfo(BrDgReceiverDeviceHandle,
                                  IOCTL_LMDR_ENUMERATE_NAMES,
                                  &RequestPacket,
                                  sizeof(RequestPacket),
                                  (LPVOID *)&NameTable,
                                  0xffffffff,
                                  0,
                                  NULL);
    if (Status != NERR_Success) {
        return Status;
    }

    NumberOfOtherDomains = 0;
    BufferSizeNeeded = 0;

    for (i = 0;i < RequestPacket.Parameters.EnumerateNames.EntriesRead ; i++) {
        if (NameTable[i].Type == OtherDomain) {
            NumberOfOtherDomains += 1;
            BufferSizeNeeded += sizeof(SERVER_INFO_100)+NameTable[i].DGReceiverName.Length+sizeof(TCHAR);
        }
    }

    *TotalEntries = NumberOfOtherDomains;

    Buffer = MIDL_user_allocate(BufferSizeNeeded);

    if (Buffer == NULL) {
        MIDL_user_free(NameTable);
        return(ERROR_NOT_ENOUGH_MEMORY);
    }

    ServerInfo = Buffer;
    BufferEnd = (LPTSTR)((PCHAR)Buffer+BufferSizeNeeded);

    for (i = 0;i < RequestPacket.Parameters.EnumerateNames.EntriesRead ; i++) {

        // Copy only OtherDomain names.
        // Protect from empty entries (in case transport name is empty).
        if (NameTable[i].Type == OtherDomain &&
            NameTable[i].DGReceiverName.Length != 0) {
            WCHAR NameBuffer[DNLEN+1];

            //
            //  The name from the browser is not null terminated, so copy it
            //  to a local buffer and null terminate it.
            //

            RtlCopyMemory(NameBuffer, NameTable[i].DGReceiverName.Buffer, NameTable[i].DGReceiverName.Length);

            NameBuffer[(NameTable[i].DGReceiverName.Length) / sizeof(TCHAR)] = UNICODE_NULL;

            ServerInfo->sv100_platform_id = PLATFORM_ID_OS2;

            ServerInfo->sv100_name = NameBuffer;

            if (!NetpPackString(&ServerInfo->sv100_name,
                                (LPBYTE)(ServerInfo+1),
                                &BufferEnd)) {
                MIDL_user_free(NameTable);
                return(NERR_InternalError);
            }
            ServerInfo += 1;
        }
    }

    MIDL_user_free(NameTable);

    *ReturnedBuffer = (LPSERVER_INFO_100) Buffer;

    Status = NERR_Success;

    return Status;

}

NET_API_STATUS
BrAddOtherDomain(
                IN PNETWORK Network,
                IN LPTSTR ServerName
                )
{
    return BrAddName( Network, ServerName, OtherDomain );
}

NET_API_STATUS
BrBindToTransport(
                 IN LPWSTR TransportName,
                 IN LPWSTR EmulatedDomainName,
                 IN LPWSTR EmulatedComputerName
                 )
{
    NET_API_STATUS Status;
    UCHAR PacketBuffer[sizeof(LMDR_REQUEST_PACKET)+(MAXIMUM_FILENAME_LENGTH+1+CNLEN+1)*sizeof(WCHAR)];
    PLMDR_REQUEST_PACKET RequestPacket = (PLMDR_REQUEST_PACKET)PacketBuffer;


    RequestPacket->Version = LMDR_REQUEST_PACKET_VERSION_DOM;
    RequestPacket->Level = TRUE;    // EmulatedComputerName follows transport name

    RequestPacket->TransportName.Length = 0;
    RequestPacket->TransportName.MaximumLength = 0;
    RtlInitUnicodeString( &RequestPacket->EmulatedDomainName, EmulatedDomainName );

    RequestPacket->Parameters.Bind.TransportNameLength = STRLEN(TransportName)*sizeof(TCHAR);

    STRCPY(RequestPacket->Parameters.Bind.TransportName, TransportName);
    STRCAT(RequestPacket->Parameters.Bind.TransportName, EmulatedComputerName );

    BrPrint(( BR_NETWORK,
              "%ws: %ws: bind from transport sent to bowser driver\n",
              EmulatedDomainName,
              TransportName));

    //
    //  This is a simple IoControl - It just updates the status.
    //

    Status = BrDgReceiverIoControl(BrDgReceiverDeviceHandle,
                                   IOCTL_LMDR_BIND_TO_TRANSPORT_DOM,
                                   RequestPacket,
                                   FIELD_OFFSET(LMDR_REQUEST_PACKET, Parameters.Bind.TransportName) +
                                   RequestPacket->Parameters.Bind.TransportNameLength +
                                   wcslen(EmulatedComputerName) * sizeof(WCHAR) + sizeof(WCHAR),
                                   NULL,
                                   0,
                                   NULL);

    return Status;
}

NET_API_STATUS
BrUnbindFromTransport(
                     IN LPWSTR TransportName,
                     IN LPWSTR EmulatedDomainName
                     )
{
    NET_API_STATUS Status;
    UCHAR PacketBuffer[sizeof(LMDR_REQUEST_PACKET)+(MAXIMUM_FILENAME_LENGTH+1)*sizeof(WCHAR)];
    PLMDR_REQUEST_PACKET RequestPacket = (PLMDR_REQUEST_PACKET)PacketBuffer;


    RequestPacket->Version = LMDR_REQUEST_PACKET_VERSION_DOM;

    RequestPacket->TransportName.Length = 0;
    RequestPacket->TransportName.MaximumLength = 0;
    RtlInitUnicodeString( &RequestPacket->EmulatedDomainName, EmulatedDomainName );

    RequestPacket->Parameters.Unbind.TransportNameLength = STRLEN(TransportName)*sizeof(TCHAR);

    STRCPY(RequestPacket->Parameters.Unbind.TransportName, TransportName);

    BrPrint(( BR_NETWORK,
              "%ws: %ws: unbind from transport sent to bowser driver\n",
              EmulatedDomainName,
              TransportName));

    //
    //  This is a simple IoControl - It just updates the status.
    //

    Status = BrDgReceiverIoControl(BrDgReceiverDeviceHandle,
                                   IOCTL_LMDR_UNBIND_FROM_TRANSPORT_DOM,
                                   RequestPacket,
                                   FIELD_OFFSET(LMDR_REQUEST_PACKET, Parameters.Bind.TransportName) +
                                   RequestPacket->Parameters.Bind.TransportNameLength,
                                   NULL,
                                   0,
                                   NULL);

    if (Status != NERR_Success) {

        BrPrint(( BR_CRITICAL,
                  "%ws: %ws: unbind from transport failed %ld\n",
                  EmulatedDomainName,
                  TransportName,
                  Status ));
    }
    return Status;
}

NET_API_STATUS
BrEnablePnp(
           BOOL Enable
           )
/*++

Routine Description:

    This routine enables or disables PNP messages from the bowser.

Arguments:

    Enable - TRUE if messages are to be enabled.

Return Value:

    None.

--*/
{
    NET_API_STATUS Status;
    UCHAR PacketBuffer[sizeof(LMDR_REQUEST_PACKET)];
    PLMDR_REQUEST_PACKET RequestPacket = (PLMDR_REQUEST_PACKET)PacketBuffer;


    RequestPacket->Version = LMDR_REQUEST_PACKET_VERSION_DOM;

    RtlInitUnicodeString( &RequestPacket->EmulatedDomainName, NULL );
    RtlInitUnicodeString( &RequestPacket->TransportName, NULL );

    RequestPacket->Parameters.NetlogonMailslotEnable.MaxMessageCount = Enable;

    //
    //  This is a simple IoControl - It just updates the status.
    //

    Status = BrDgReceiverIoControl(
                                  BrDgReceiverDeviceHandle,
                                  IOCTL_LMDR_BROWSER_PNP_ENABLE,
                                  RequestPacket,
                                  sizeof(LMDR_REQUEST_PACKET),
                                  NULL,
                                  0,
                                  NULL);

    if (Status != NERR_Success) {
        BrPrint(( BR_CRITICAL, "Enable PNP failed: %ld %ld\n", Enable, Status));
    }
    return Status;
}

VOID
HandlePnpMessage (
                 IN PVOID Ctx
                 )
/*++

Routine Description:

    This function handles a PNP message from the bowser driver.

Arguments:

    Ctx - Context block for request.

Return Value:

    None.

--*/


{
    NET_API_STATUS NetStatus;
    PBROWSERASYNCCONTEXT Context = Ctx;

    PNETLOGON_MAILSLOT NetlogonMailslot =
    (PNETLOGON_MAILSLOT) Context->RequestPacket;

    LPWSTR Transport;
    UNICODE_STRING TransportName;

    LPWSTR HostedDomain;
    UNICODE_STRING HostedDomainName;

    NETLOGON_PNP_OPCODE PnpOpcode;
    ULONG TransportFlags;

    PLIST_ENTRY DomainEntry;
    PDOMAIN_INFO DomainInfo;
    PNETWORK Network;


    try {

        //
        //  The request failed for some reason - just return immediately.
        //

        if (!NT_SUCCESS(Context->IoStatusBlock.Status)) {
            //
            // Sleep for a second to avoid consuming entire system.
            Sleep( 1000 );
            try_return(NOTHING);
        }

        //
        // If the message isn't a PNP message,
        //  someone is really confused.
        //

        if ( NetlogonMailslot->MailslotNameSize != 0 ) {
            BrPrint(( BR_CRITICAL,
                      "Got malformed PNP message\n" ));
            //
            // Sleep for a second to avoid consuming entire system.
            Sleep( 1000 );
            try_return(NOTHING);
        }


        //
        // Parse the message
        //

        PnpOpcode = NetlogonMailslot->MailslotNameOffset;
        TransportFlags = NetlogonMailslot->MailslotMessageOffset;

        if( NetlogonMailslot->TransportNameSize > 0 )
        {
            Transport = (LPWSTR) &(((LPBYTE)NetlogonMailslot)[
                                                             NetlogonMailslot->TransportNameOffset]);
            RtlInitUnicodeString( &TransportName, Transport );
        }
        else
        {
            RtlInitUnicodeString( &TransportName, NULL );
        }

        if( NetlogonMailslot->DestinationNameSize > 0 )
        {
            HostedDomain = (LPWSTR) &(((LPBYTE)NetlogonMailslot)[
                                                                NetlogonMailslot->DestinationNameOffset]);
            RtlInitUnicodeString( &HostedDomainName, HostedDomain );
        }
        else
        {
            RtlInitUnicodeString( &HostedDomainName, NULL );
        }

        //
        // Handle binding to a new network.
        //
        switch (PnpOpcode ) {
        case NlPnpTransportBind:
            BrPrint(( BR_NETWORK,
                      "Received bind PNP opcode 0x%lx on transport: %ws\n",
                      TransportFlags,
                      Transport ));

            //
            // Ignore the direct host IPX transport.
            //  The browser service created it so we don't need PNP notification.
            //

            if ( TransportFlags & LMDR_TRANSPORT_IPX ) {
                BrPrint(( BR_NETWORK,
                          "Ignoring PNP bind of direct host IPX transport\n" ));
                break;
            }

            NetStatus = BrChangeConfigValue(
                                           L"DirectHostBinding",
                                           MultiSzType,
                                           NULL,
                                           &(BrInfo.DirectHostBinding),
                                           TRUE );

            if ( NetStatus != NERR_Success ) {
                BrPrint(( BR_CRITICAL,
                          "Unbind failed to read Registry DirectHostBinding: %ws %ld\n",
                          Transport,
                          NetStatus ));
                //
                // Don't abort binding on failure to read DirectHostBinding, Our internal binding
                // info hasn't change so we'll use whatever we have.
                // Ignore error.
                //

                NetStatus = NERR_Success;
            } else {
                //
                // DirectHostBinding sepcified. Verify consistency & fail on
                // inconsistent setup (since it was setup, there was an intention resulted w/
                // a failure here).
                //

                EnterCriticalSection ( &BrInfo.ConfigCritSect );
                if (BrInfo.DirectHostBinding != NULL &&
                    !NetpIsTStrArrayEmpty(BrInfo.DirectHostBinding)) {
                    BrPrint(( BR_INIT,"DirectHostBinding length: %ld\n",NetpTStrArrayEntryCount(BrInfo.DirectHostBinding)));

                    if (NetpTStrArrayEntryCount(BrInfo.DirectHostBinding) % 2 != 0) {
                        NetApiBufferFree(BrInfo.DirectHostBinding);
                        BrInfo.DirectHostBinding = NULL;
                        // we fail on invalid specifications
                        NetStatus = ERROR_INVALID_PARAMETER;
                    }
                }
                LeaveCriticalSection ( &BrInfo.ConfigCritSect );
            }

            //
            // Loop creating the network for each emulated domain.
            //

            EnterCriticalSection(&NetworkCritSect);
            for (DomainEntry = ServicedDomains.Flink ;
                DomainEntry != &ServicedDomains;
                DomainEntry = DomainEntry->Flink ) {

                DomainInfo = CONTAINING_RECORD(DomainEntry, DOMAIN_INFO, Next);
                DomainInfo->PnpDone = FALSE;
            }

            for (DomainEntry = ServicedDomains.Flink ;
                DomainEntry != &ServicedDomains;
                ) {

                DomainInfo = CONTAINING_RECORD(DomainEntry, DOMAIN_INFO, Next);

                //
                // If this domain has already been processed,
                //  skip it.
                //

                if ( DomainInfo->PnpDone ) {
                    DomainEntry = DomainEntry->Flink;
                    continue;
                }
                DomainInfo->PnpDone = TRUE;


                //
                // Drop the crit sect while doing the lenghty PNP operation.
                //

                DomainInfo->ReferenceCount++;
                LeaveCriticalSection(&NetworkCritSect);

                //
                // Finally create the transport.
                //

                NetStatus = BrCreateNetwork(
                                           &TransportName,
                                           TransportFlags,
                                           NULL,
                                           DomainInfo );

                if ( NetStatus != NERR_Success ) {
                    BrPrint(( BR_CRITICAL,
                              "%ws: Bind failed on transport: %ws %ld\n",
                              DomainInfo->DomUnicodeDomainName,
                              Transport,
                              NetStatus ));
                    // ?? Anything else to do on failure
                }

                //
                // Finish process the emulated domains
                //  Start at the front of the list since we dropped the lock.
                //
                BrDereferenceDomain(DomainInfo);
                EnterCriticalSection(&NetworkCritSect);
                DomainEntry = ServicedDomains.Flink;
            }
            LeaveCriticalSection(&NetworkCritSect);

            break;


            //
            // Handle Unbinding from a network.
            //
        case NlPnpTransportUnbind:
            BrPrint(( BR_NETWORK,
                      "Received unbind PNP opcode 0x%lx on transport: %ws\n",
                      TransportFlags,
                      Transport ));

            //
            // Ignore the direct host IPX transport.
            //  The browser service created it so we don't need PNP notification.
            //

            if ( TransportFlags & LMDR_TRANSPORT_IPX ) {
                BrPrint(( BR_NETWORK,
                          "Ignoring PNP unbind of direct host IPX transport\n" ));
                break;
            }

            //
            // Loop deleting the network for each emulated domain.
            //

            EnterCriticalSection(&NetworkCritSect);
            for (DomainEntry = ServicedDomains.Flink ;
                DomainEntry != &ServicedDomains;
                DomainEntry = DomainEntry->Flink ) {

                DomainInfo = CONTAINING_RECORD(DomainEntry, DOMAIN_INFO, Next);
                DomainInfo->PnpDone = FALSE;
            }

            for (DomainEntry = ServicedDomains.Flink ;
                DomainEntry != &ServicedDomains;
                ) {

                DomainInfo = CONTAINING_RECORD(DomainEntry, DOMAIN_INFO, Next);

                //
                // If this domain has already been processed,
                //  skip it.
                //

                if ( DomainInfo->PnpDone ) {
                    DomainEntry = DomainEntry->Flink;
                    continue;
                }
                DomainInfo->PnpDone = TRUE;


                //
                // Drop the crit sect while doing the lenghty PNP operation.
                //

                DomainInfo->ReferenceCount++;
                LeaveCriticalSection(&NetworkCritSect);

                //
                // Finally delete the transport.
                //

                Network = BrFindNetwork( DomainInfo, &TransportName );

                if ( Network == NULL ) {
                    BrPrint(( BR_CRITICAL,
                              "%ws: Unbind cannot find transport: %ws\n",
                              DomainInfo->DomUnicodeDomainName,
                              Transport ));
                } else {
                    //
                    // If the network has an alternate network,
                    //  delete it first.
                    //

                    if ( Network->AlternateNetwork != NULL ) {
                        PNETWORK AlternateNetwork;


                        AlternateNetwork = BrReferenceNetwork( Network->AlternateNetwork );

                        if ( AlternateNetwork != NULL) {
                            BrPrint(( BR_NETWORK,
                                      "%ws: %ws: Unbind from alternate transport: %ws\n",
                                      DomainInfo->DomUnicodeDomainName,
                                      Transport,
                                      AlternateNetwork->NetworkName.Buffer ));

                            NetStatus = BrDeleteNetwork(
                                                       AlternateNetwork,
                                                       NULL );

                            if ( NetStatus != NERR_Success ) {
                                BrPrint(( BR_CRITICAL,
                                          "%ws: Unbind failed on transport: %ws %ld\n",
                                          DomainInfo->DomUnicodeDomainName,
                                          AlternateNetwork->NetworkName.Buffer,
                                          NetStatus ));
                                // ?? Anything else to do on failure
                            }

                            BrDereferenceNetwork( AlternateNetwork );
                        }

                    }

                    //
                    // Delete the network.
                    //
                    NetStatus = BrDeleteNetwork(
                                               Network,
                                               NULL );

                    if ( NetStatus != NERR_Success ) {
                        BrPrint(( BR_CRITICAL,
                                  "%ws: Unbind failed on transport: %ws %ld\n",
                                  DomainInfo->DomUnicodeDomainName,
                                  Transport,
                                  NetStatus ));
                        // ?? Anything else to do on failure
                    }

                    BrDereferenceNetwork( Network );
                }


                //
                // Finish process the emulated domains
                //  Start at the front of the list since we dropped the lock.
                //
                BrDereferenceDomain(DomainInfo);
                EnterCriticalSection(&NetworkCritSect);
                DomainEntry = ServicedDomains.Flink;
            }
            LeaveCriticalSection(&NetworkCritSect);
            break;

            //
            // Handle domain rename
            //
        case NlPnpDomainRename:
            BrPrint(( BR_NETWORK,
                      "Received Domain Rename PNP for domain: %ws\n", HostedDomain ));

            //
            // See if we're handling the specified domain.
            //

            DomainInfo = BrFindDomain( HostedDomain, FALSE );

            if ( DomainInfo == NULL ) {
                BrPrint(( BR_CRITICAL, "%ws: Renamed domain doesn't exist\n",
                          HostedDomain ));
            } else {

                //
                // If so,
                //  rename it.
                //
                BrRenameDomain( DomainInfo );
                BrDereferenceDomain( DomainInfo );
            }

            break;

            //
            // Handle PDC/BDC role change.
            //
        case NlPnpNewRole:
            BrPrint(( BR_NETWORK,
                      "%ws: Received role change PNP opcode 0x%lx on transport: %ws\n",
                      HostedDomain,
                      TransportFlags,
                      Transport ));

            //
            // Role can only change on lanman NT systems
            //
            if (!BrInfo.IsLanmanNt) {
                break;
            }

            //
            // See if we're handling the specified domain.
            //

            DomainInfo = BrFindDomain( HostedDomain, FALSE );

            if ( DomainInfo == NULL ) {
                BrPrint(( BR_CRITICAL, "%ws: Hosted domain doesn't exist\n",
                          HostedDomain ));
            } else {

                //
                // Find the specified network
                //

                Network = BrFindNetwork( DomainInfo, &TransportName );

                if ( Network == NULL ) {
                    BrPrint(( BR_CRITICAL,
                              "%ws: Unbind cannot find transport: %ws\n",
                              DomainInfo->DomUnicodeDomainName,
                              Transport ));
                } else {

                    if (LOCK_NETWORK(Network)) {

                        //
                        // Set the role to be PDC.
                        //
                        if ( TransportFlags & LMDR_TRANSPORT_PDC ) {

                            //
                            // If we think we're a BDC.  Update our information.
                            //
                            if ( (Network->Flags & NETWORK_PDC) == 0 ) {
                                Network->Flags |= NETWORK_PDC;

                                //
                                //  Make sure a GetMasterAnnouncement request is pending.
                                //

                                (VOID) PostGetMasterAnnouncement ( Network );

                                // Force an election to let the PDC win
                                (VOID) BrElectMasterOnNet( Network, (PVOID)EVENT_BROWSER_ELECTION_SENT_ROLE_CHANGED );
                            }


                            //
                            // Set the role to BDC.
                            //

                        } else {

                            //
                            // We think we're the PDC.  Update our information.
                            //

                            if ( Network->Flags & NETWORK_PDC ) {
                                Network->Flags &= ~NETWORK_PDC;

                                // Force an election to let the PDC win
                                (VOID) BrElectMasterOnNet( Network, (PVOID)EVENT_BROWSER_ELECTION_SENT_ROLE_CHANGED );
                            }
                        }

                        UNLOCK_NETWORK(Network);
                    }

                    BrDereferenceNetwork( Network );
                }

                BrDereferenceDomain( DomainInfo );
            }
            break;

            //
            // Ignore new Ip Addresses
            //
        case NlPnpNewIpAddress:
            BrPrint(( BR_NETWORK,
                      "Received IP address change PNP opcode 0x%lx on transport: %ws\n",
                      TransportFlags,
                      Transport ));
            break;

        default:
            BrPrint(( BR_CRITICAL,
                      "Received invalid PNP opcode 0x%x on transport: %ws\n",
                      PnpOpcode,
                      Transport ));
            break;
        }


        try_exit:NOTHING;
    } finally {

        MIDL_user_free(Context);

        //
        // Always finish by asking for another PNP message.
        //
        // For PNP, it is fine to only process a single PNP message at a time.
        // If this message mechanism starts being used for other purposes,
        //  we may want to immediately ask for another message upon receipt
        //  of this one.
        //

        while ((NetStatus = PostWaitForPnp()) != NERR_Success ) {
            BrPrint(( BR_CRITICAL,
                      "Unable to re-issue PostWaitForPnp request (waiting): %ld\n",
                      NetStatus));

            //
            // On error, wait a second before returning.  This ensures we don't
            //  consume the system in an infinite loop.  We don't shutdown
            //  because the error might be a temporary low memory condition.
            //

            NetStatus = WaitForSingleObject( BrGlobalData.TerminateNowEvent, 1000 );
            if ( NetStatus != WAIT_TIMEOUT ) {
                BrPrint(( BR_CRITICAL,
                          "Not re-issuing PostWaitForPnp request since we're terminating: %ld\n",
                          NetStatus));
                break;
            }
        }

    }

    return;

}

NET_API_STATUS
PostWaitForPnp (
               VOID
               )
/*++

Routine Description:

    This function issues and async call to the bowser driver asking
    it to inform us of PNP events.

Arguments:

    None.

Return Value:

    Status - The status of the operation.

--*/
{
    return BrIssueAsyncBrowserIoControl(
                                       NULL,
                                       IOCTL_LMDR_BROWSER_PNP_READ,
                                       HandlePnpMessage,
                                       NULL );
}