/*++

Copyright (c) 1992  Microsoft Corporation

Module Name:

    ndisapi.c

Abstract:

    NDIS User-Mode apis to support PnP from the network UI

Author:

    JameelH

Environment:

    Kernel mode, FSD

Revision History:

    Aug 1997     JameelH    Initial version

--*/

#include <windows.h>
#include <wtypes.h>
#include <ntddndis.h>
#include <ndisprv.h>
#include <devioctl.h>

#ifndef UNICODE_STRING

typedef struct _UNICODE_STRING
{
    USHORT  Length;
    USHORT  MaximumLength;
    PWSTR   Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

#endif

#include <ndispnp.h>
#include <ndisprv.h>
#define MAC_ADDRESS_SIZE    6
#define VENDOR_ID_SIZE      3

extern
VOID
InitUnicodeString(
    IN  PUNICODE_STRING     DestinationString,
    IN  PCWSTR              SourceString
    );

extern
LONG
AppendUnicodeStringToString(
    IN  PUNICODE_STRING     Destination,
    IN  PUNICODE_STRING     Source
    );

extern
HANDLE
OpenDevice(
    IN  PUNICODE_STRING     DeviceName
    );

//
// UNICODE_STRING_SIZE calculates the size of the buffer needed to
// store a given UNICODE_STRING including an appended null-terminator.
//
// ULONG
// UNICODE_STRING_SIZE(
//     PUNICODE_STRING String
// );
//

#define UNICODE_STRING_SIZE(x) \
    ((((x) == NULL) ? 0 : (x)->Length) + sizeof(WCHAR))

VOID
NdispUnicodeStringToVar(
    IN     PVOID Base,
    IN     PUNICODE_STRING String,
    IN OUT PNDIS_VAR_DATA_DESC NdisVar
    )

/*++

Routine Description:

    This function copies the contents of a UNICODE_STRING to an
    NDIS_VAR_DATA structure.  NdisVar->Offset is treated as an input parameter
    and represents the offset into Base that the string characters should be
    copied to.

Arguments:

    Base - Specifies the base address of the IOCTL buffer.

    String - Supplies a pointer to the UNICODE_STRING that should be copied.

    NdisVar - Supplies a pointer to the target NDIS_VAR_DATA_DESC.  Its Offset
              field is taken as input, and its Length and MaximumLength fields
              are treated as output.

Return Value:

    None.

--*/

{
    PWCHAR destination;

    //
    // NdisVar->Offset is assumed to be filled in and is treated
    // as an input parameter.
    // 

    destination = (PWCHAR)(((PCHAR)Base) + NdisVar->Offset);

    //
    // Copy over the UNICODE_STRING, if any, and set NdisVar->Length
    //

    if ((String != NULL) && (String->Length > 0)) {
        NdisVar->Length = String->Length;
        memcpy(destination, String->Buffer, NdisVar->Length );
    } else {
        NdisVar->Length = 0;
    }

    //
    // Null-terminate, fill in MaxiumLength and we're done.
    //

    *(destination + NdisVar->Length / sizeof(WCHAR)) = L'\0';
    NdisVar->MaximumLength = NdisVar->Length + sizeof(WCHAR);
}

UINT
NdisHandlePnPEvent(
    IN  UINT            Layer,
    IN  UINT            Operation,
    IN  PUNICODE_STRING LowerComponent      OPTIONAL,
    IN  PUNICODE_STRING UpperComponent      OPTIONAL,
    IN  PUNICODE_STRING BindList            OPTIONAL,
    IN  PVOID           ReConfigBuffer      OPTIONAL,
    IN  UINT            ReConfigBufferSize  OPTIONAL
    )
{
    PNDIS_PNP_OPERATION Op;
    NDIS_PNP_OPERATION  tempOp;
    HANDLE              hDevice;
    BOOL                fResult = FALSE;
    UINT                cb, Size;
    DWORD               Error;
    ULONG               padding;

    do
    {
        //
        // Validate Layer & Operation
        //
        if (((Layer != NDIS) && (Layer != TDI)) ||
            ((Operation != BIND) && (Operation != UNBIND) && (Operation != RECONFIGURE) &&
             (Operation != UNLOAD) && (Operation != REMOVE_DEVICE) &&
             (Operation != ADD_IGNORE_BINDING) &&
             (Operation != DEL_IGNORE_BINDING) &&
             (Operation != BIND_LIST)))
        {
            Error = ERROR_INVALID_PARAMETER;
            break;
        }

        //
        // Allocate and initialize memory for the block to be passed down.  The buffer
        // will look like this:
        //
        //
        //         +=================================+
        //         | NDIS_PNP_OPERATION              |
        //         |     ReConfigBufferOff           | ----+
        //    +--- |     LowerComponent.Offset       |     |
        //    |    |     UpperComponent.Offset       | --+ |
        //  +-|--- |     BindList.Offset             |   | |
        //  | +--> +---------------------------------+   | |
        //  |      | LowerComponentStringBuffer      |   | |
        //  |      +---------------------------------+ <-+ |
        //  |      | UpperComponentStringBuffer      |     |
        //  +----> +---------------------------------+     |
        //         | BindListStringBuffer            |     |
        //         +---------------------------------+     |
        //         | Padding to ensure ULONG_PTR     |     |
        //         |     alignment of ReConfigBuffer |     |
        //         +---------------------------------+ <---+
        //         | ReConfigBuffer                  | 
        //         +=================================+
        //
        // tempOp is a temporary structure into which we will store offsets as
        // they are calculated.  This temporary structure will be moved to
        // the head of the real buffer once its size is known and it is
        // allocated.
        //

        Size = sizeof(NDIS_PNP_OPERATION);
        tempOp.LowerComponent.Offset = Size;

        Size += UNICODE_STRING_SIZE(LowerComponent);
        tempOp.UpperComponent.Offset = Size;

        Size += UNICODE_STRING_SIZE(UpperComponent);
        tempOp.BindList.Offset = Size;

        Size += UNICODE_STRING_SIZE(BindList);

        padding = (sizeof(ULONG_PTR) - (Size & (sizeof(ULONG_PTR) - 1))) &
                    (sizeof(ULONG_PTR) - 1);

        Size += padding;
        tempOp.ReConfigBufferOff = Size;

        Size += ReConfigBufferSize + 1;

        Op = (PNDIS_PNP_OPERATION)LocalAlloc(LPTR, Size);
        if (Op == NULL)
        {
            Error = ERROR_NOT_ENOUGH_MEMORY;
            break;
        }

        //
        // We have a buffer of the necessary size.  Copy in the partially-
        // filled in tempOp, then fill in the remaining fields and copy the
        // data into the buffer.
        // 

        *Op = tempOp;

        Op->Layer = Layer;
        Op->Operation = Operation;

        //
        // Copy over the three unicode strings
        //

        NdispUnicodeStringToVar( Op, LowerComponent, &Op->LowerComponent );
        NdispUnicodeStringToVar( Op, UpperComponent, &Op->UpperComponent );
        NdispUnicodeStringToVar( Op, BindList, &Op->BindList );

        //
        // Finally, copy over the ReConfigBuffer
        //

        Op->ReConfigBufferSize = ReConfigBufferSize;
        if (ReConfigBufferSize > 0)
        {
            memcpy((PUCHAR)Op + Op->ReConfigBufferOff,
                   ReConfigBuffer,
                   ReConfigBufferSize);
        }
        *((PUCHAR)Op + Op->ReConfigBufferOff + ReConfigBufferSize) = 0;

        hDevice = CreateFile(L"\\\\.\\NDIS",
                             GENERIC_READ | GENERIC_WRITE,
                             0,                 // sharing mode - not significant
                             NULL,              // security attributes
                             OPEN_EXISTING,
                             0,                 // file attributes and flags
                             NULL);             // handle to template file

        if (hDevice != INVALID_HANDLE_VALUE)
        {
            fResult = DeviceIoControl(hDevice,
                                      IOCTL_NDIS_DO_PNP_OPERATION,
                                      Op,                                   // input buffer
                                      Size,                                 // input buffer size
                                      NULL,                                 // output buffer
                                      0,                                    // output buffer size
                                      &cb,                                  // bytes returned
                                      NULL);                                // OVERLAPPED structure
            Error = GetLastError();
            CloseHandle(hDevice);
        }
        else
        {
            Error = GetLastError();
        }

        LocalFree(Op);

    } while (FALSE);

    SetLastError(Error);

    return(fResult);
}


NDIS_OID    StatsOidList[] =
    {
        OID_GEN_LINK_SPEED,
        OID_GEN_MEDIA_IN_USE | NDIS_OID_PRIVATE,
        OID_GEN_MEDIA_CONNECT_STATUS | NDIS_OID_PRIVATE,
        OID_GEN_XMIT_OK,
        OID_GEN_RCV_OK,
        OID_GEN_XMIT_ERROR,
        OID_GEN_RCV_ERROR,
        OID_GEN_DIRECTED_FRAMES_RCV | NDIS_OID_PRIVATE,
        OID_GEN_DIRECTED_BYTES_XMIT | NDIS_OID_PRIVATE,
        OID_GEN_DIRECTED_BYTES_RCV | NDIS_OID_PRIVATE,
        OID_GEN_ELAPSED_TIME | NDIS_OID_PRIVATE,
        OID_GEN_INIT_TIME_MS | NDIS_OID_PRIVATE,
        OID_GEN_RESET_COUNTS | NDIS_OID_PRIVATE,
        OID_GEN_MEDIA_SENSE_COUNTS | NDIS_OID_PRIVATE,
        OID_GEN_PHYSICAL_MEDIUM | NDIS_OID_PRIVATE
    };
UINT    NumOidsInList = sizeof(StatsOidList)/sizeof(NDIS_OID);

UINT
NdisQueryStatistics(
    IN  PUNICODE_STRING     Device,
    OUT PNIC_STATISTICS     Statistics
    )
{
    NDIS_STATISTICS_VALUE   StatsBuf[4*sizeof(StatsOidList)/sizeof(NDIS_OID)];
    PNDIS_STATISTICS_VALUE  pStatsBuf;
    HANDLE                  hDevice;
    BOOL                    fResult = FALSE;
    UINT                    cb, Size, Index;
    DWORD                   Error;

    if (Statistics->Size != sizeof(NIC_STATISTICS))
    {
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
        return(FALSE);
    }

    memset(Statistics, 0, sizeof(NIC_STATISTICS));
    Statistics->DeviceState = DEVICE_STATE_DISCONNECTED;
    Statistics->MediaState  = MEDIA_STATE_UNKNOWN;
    hDevice = OpenDevice(Device);

    if (hDevice != NULL)
    {
        Statistics->MediaState  = MEDIA_STATE_CONNECTED;                // default, if the device does not support
        Statistics->DeviceState = DEVICE_STATE_CONNECTED;
        fResult = DeviceIoControl(hDevice,
                                  IOCTL_NDIS_QUERY_SELECTED_STATS,
                                  StatsOidList,                         // input buffer
                                  sizeof(StatsOidList),                 // input buffer size
                                  StatsBuf,                             // output buffer
                                  sizeof(StatsBuf),                     // output buffer size
                                  &cb,                                  // bytes returned
                                  NULL);                                // OVERLAPPED structure
        Error = GetLastError();
        CloseHandle(hDevice);

        if (fResult)
        {
            Error = NO_ERROR;

            for (Index = Size = 0, pStatsBuf = StatsBuf; Size < cb; Index++)
            {
                LARGE_INTEGER   Value;
                NDIS_OID        Oid;

                Value.QuadPart = 0;
                if (pStatsBuf->DataLength == sizeof(LARGE_INTEGER))
                {
                    // Use memcpy rather than assignment to avoid unalignment
                    // faults on ia64.
                    //
                    memcpy(&Value.QuadPart, &pStatsBuf->Data[0], pStatsBuf->DataLength);
                }
                else
                {
                    Value.LowPart = *(PULONG)(&pStatsBuf->Data[0]);
                }
                Size += (pStatsBuf->DataLength + FIELD_OFFSET(NDIS_STATISTICS_VALUE, Data));
                Oid = pStatsBuf->Oid;
                pStatsBuf = (PNDIS_STATISTICS_VALUE)((PUCHAR)pStatsBuf +
                                                     FIELD_OFFSET(NDIS_STATISTICS_VALUE, Data) +
                                                     pStatsBuf->DataLength);

                switch (Oid & ~NDIS_OID_PRIVATE)
                {
                  case OID_GEN_LINK_SPEED:
                    Statistics->LinkSpeed = Value.LowPart;
                    break;

                  case OID_GEN_MEDIA_CONNECT_STATUS:
                    Statistics->MediaState = (Value.LowPart == NdisMediaStateConnected) ?
                                                MEDIA_STATE_CONNECTED : MEDIA_STATE_DISCONNECTED;
                    break;

                  case OID_GEN_MEDIA_IN_USE:
                    Statistics->MediaType = Value.LowPart;
                    break;

                  case OID_GEN_XMIT_OK:
                    Statistics->PacketsSent = Value.QuadPart;
                    break;

                  case OID_GEN_RCV_OK:
                    Statistics->PacketsReceived = Value.QuadPart;
                    break;

                  case OID_GEN_XMIT_ERROR:
                    Statistics->PacketsSendErrors = Value.LowPart;
                    break;

                  case OID_GEN_RCV_ERROR:
                    Statistics->PacketsReceiveErrors = Value.LowPart;
                    break;

                  case OID_GEN_DIRECTED_BYTES_XMIT:
                    Statistics->BytesSent += Value.QuadPart;
                    break;

                  case OID_GEN_MULTICAST_BYTES_XMIT:
                    Statistics->BytesSent += Value.QuadPart;
                    break;

                  case OID_GEN_BROADCAST_BYTES_XMIT:
                    Statistics->BytesSent += Value.QuadPart;
                    break;

                  case OID_GEN_DIRECTED_BYTES_RCV:
                    Statistics->BytesReceived += Value.QuadPart;
                    Statistics->DirectedBytesReceived = Value.QuadPart;
                    break;

                  case OID_GEN_DIRECTED_FRAMES_RCV:
                    Statistics->DirectedPacketsReceived = Value.QuadPart;
                    break;

                  case OID_GEN_MULTICAST_BYTES_RCV:
                    Statistics->BytesReceived += Value.QuadPart;
                    break;

                  case OID_GEN_BROADCAST_BYTES_RCV:
                    Statistics->BytesReceived += Value.QuadPart;
                    break;

                  case OID_GEN_ELAPSED_TIME:
                    Statistics->ConnectTime = Value.LowPart;
                    break;

                  case OID_GEN_INIT_TIME_MS:
                    Statistics->InitTime = Value.LowPart;
                    break;

                  case OID_GEN_RESET_COUNTS:
                    Statistics->ResetCount = Value.LowPart;
                    break;

                  case OID_GEN_MEDIA_SENSE_COUNTS:
                    Statistics->MediaSenseConnectCount = Value.LowPart >> 16;
                    Statistics->MediaSenseDisconnectCount = Value.LowPart & 0xFFFF;
                    break;

                  case OID_GEN_PHYSICAL_MEDIUM:
                    Statistics->PhysicalMediaType = Value.LowPart;
                    break;

                  default:
                    // ASSERT(0);
                    break;
                }
            }
        }
        else
        {
            Error = GetLastError();
        }
    }
    else
    {
        Error = GetLastError();
    }

    SetLastError(Error);

    return(fResult);
}


UINT
NdisEnumerateInterfaces(
    IN  PNDIS_ENUM_INTF Interfaces,
    IN  UINT            Size
    )
{
    HANDLE              hDevice;
    BOOL                fResult = FALSE;
    UINT                cb;
    DWORD               Error = NO_ERROR;

    do
    {
        hDevice = CreateFile(L"\\\\.\\NDIS",
                             GENERIC_READ | GENERIC_WRITE,
                             0,                 // sharing mode - not significant
                             NULL,              // security attributes
                             OPEN_EXISTING,
                             0,                 // file attributes and flags
                             NULL);             // handle to template file

        if (hDevice != INVALID_HANDLE_VALUE)
        {
            fResult = DeviceIoControl(hDevice,
                                      IOCTL_NDIS_ENUMERATE_INTERFACES,
                                      NULL,                                 // input buffer
                                      0,                                    // input buffer size
                                      Interfaces,                           // output buffer
                                      Size,                                 // output buffer size
                                      &cb,                                  // bytes returned
                                      NULL);                                // OVERLAPPED structure
            Error = GetLastError();
            CloseHandle(hDevice);

            if (Error == NO_ERROR)
            {
                UINT    i;

                //
                // Fix-up pointers
                //
                for (i = 0; i < Interfaces->TotalInterfaces; i++)
                {
                    OFFSET_TO_POINTER(Interfaces->Interface[i].DeviceName.Buffer, Interfaces);
                    OFFSET_TO_POINTER(Interfaces->Interface[i].DeviceDescription.Buffer, Interfaces);
                }
            }
        }
        else
        {
            Error = GetLastError();
        }
    } while (FALSE);

    SetLastError(Error);

    return(fResult);
}

#if 0
UINT
NdisQueryDeviceBundle(
    IN  PUNICODE_STRING Device,
    OUT PDEVICE_BUNDLE  BundleBuffer,
    IN  UINT            BufferSize
    )
{
    HANDLE              hDevice;
    BOOL                fResult = FALSE;
    UINT                cb;
    DWORD               Error = NO_ERROR;

    do
    {
        if (BufferSize < (sizeof(DEVICE_BUNDLE) + Device->MaximumLength))
        {
            Error = ERROR_INSUFFICIENT_BUFFER;
            break;
        }

        hDevice = OpenDevice(Device);
        if (hDevice != NULL)
        {
            fResult = DeviceIoControl(hDevice,
                                      IOCTL_NDIS_GET_DEVICE_BUNDLE,
                                      NULL,                                 // input buffer
                                      0,                                    // input buffer size
                                      BundleBuffer,                         // output buffer
                                      BufferSize,                           // output buffer size
                                      &cb,                                  // bytes returned
                                      NULL);                                // OVERLAPPED structure
            Error = GetLastError();
            CloseHandle(hDevice);

            if (Error == NO_ERROR)
            {
                UINT    i;

                //
                // Fix-up pointers
                //
                for (i = 0; i < BundleBuffer->TotalEntries; i++)
                {
                    OFFSET_TO_POINTER(BundleBuffer->Entries[i].Name.Buffer, BundleBuffer);
                }
            }
        }
        else
        {
            Error = ERROR_FILE_NOT_FOUND;
        }
    } while (FALSE);

    SetLastError(Error);

    return(fResult);
}

#endif

UINT
NdisQueryHwAddress(
    IN  PUNICODE_STRING Device,
    OUT PUCHAR          CurrentAddress,
    OUT PUCHAR          PermanentAddress,
    OUT PUCHAR          VendorId
    )
{
    UCHAR                   Buf[3*sizeof(NDIS_STATISTICS_VALUE) + 48];
    PNDIS_STATISTICS_VALUE  pBuf;
    NDIS_OID                Oids[] = { OID_802_3_CURRENT_ADDRESS, OID_802_3_PERMANENT_ADDRESS, OID_GEN_VENDOR_ID };
    HANDLE                  hDevice;
    BOOL                    fResult = FALSE;
    UINT                    cb;
    DWORD                   Error;

    memset(CurrentAddress, 0, MAC_ADDRESS_SIZE);
    memset(PermanentAddress, 0, MAC_ADDRESS_SIZE);
    memset(VendorId, 0, VENDOR_ID_SIZE);
    hDevice = OpenDevice(Device);

    if (hDevice != NULL)
    {
        fResult = DeviceIoControl(hDevice,
                                  IOCTL_NDIS_QUERY_SELECTED_STATS,
                                  &Oids,                                // input buffer
                                  sizeof(Oids),                         // input buffer size
                                  Buf,                                  // output buffer
                                  sizeof(Buf),                          // output buffer size
                                  &cb,                                  // bytes returned
                                  NULL);                                // OVERLAPPED structure
        Error = GetLastError();
        CloseHandle(hDevice);

        if (fResult)
        {
            UINT        Size, tmp;

            Error = NO_ERROR;

            pBuf = (PNDIS_STATISTICS_VALUE)Buf;
            for (Size = 0; Size < cb; )
            {
                tmp = (pBuf->DataLength + FIELD_OFFSET(NDIS_STATISTICS_VALUE, Data));
                Size += tmp;

                switch (pBuf->Oid)
                {
                    case OID_802_3_CURRENT_ADDRESS:
                        memcpy(CurrentAddress, pBuf->Data, MAC_ADDRESS_SIZE);
                        break;

                    case OID_802_3_PERMANENT_ADDRESS:
                        memcpy(PermanentAddress, pBuf->Data, MAC_ADDRESS_SIZE);
                        break;

                    case OID_GEN_VENDOR_ID:
                        memcpy(VendorId, pBuf->Data, VENDOR_ID_SIZE);
                }
                pBuf = (PNDIS_STATISTICS_VALUE)((PUCHAR)pBuf + tmp);
            }
        }
        else
        {
            Error = GetLastError();
        }
    }
    else
    {
        Error = ERROR_FILE_NOT_FOUND;
    }

    SetLastError(Error);

    return(fResult);
}

VOID
XSetLastError(
    IN  ULONG       Error
    )
{
    SetLastError(Error);
}