//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// This source code is licensed under Microsoft Shared Source License
// Version 1.0 for Windows CE.
// For a copy of the license visit http://go.microsoft.com/fwlink/?LinkId=3223.
//
#include "StdAfx.h"
#include "NDTError.h"
#include "NDTLib.h"
#include "NDTLibEx.h"
#include "NDTNDIS.h"

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
About Mac address:
LAN & FDDI use 6-bytes MAC addresses. The first three BYTEs indicate a manufacturer ID, the last three bytes indicate a 
unique station ID. Ethernet transmit BYTEs with the least significant bit first (LSB fashion), 
whereas Token-Ring and FDDI do that in MSB fashion.
The first bit (LSB) shall be used in the Destination Address field as an address type designation bit to
identify the Destination Address either as an individual or as a group address. If this bit is 0, it shall
indicate that the address field contains an individual address. If this bit is 1, it shall indicate that the
address field contains a group address that identifies none, one or more, or all of the stations connected
to the LAN. In the Source Address field, the first bit is reserved and set to 0.
The second bit shall be used to distinguish between locally or globally administered addresses. For
globally administered (or U, universal) addresses, the bit is set to 0. If an address is to be assigned
locally, this bit shall be set to 1. Note that for the broadcast address, this bit is also a 1.
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/

//------------------------------------------------------------------------------

void NDTGetMediumInfo(NDIS_MEDIUM ndisMedium, UINT* pcbAddr, UINT* pcbHeader) 
{
   switch (ndisMedium) {
   case NdisMedium802_3:
      *pcbAddr = 6;
      *pcbHeader = 14;
      break;
   case NdisMedium802_5:
      *pcbAddr = 6;
      *pcbHeader = 14;
      break;
   case NdisMediumFddi:
      *pcbAddr = 6;
      *pcbHeader = 14;
      break;
   default:
      *pcbAddr = 0;
      *pcbHeader = 0;
      break;
   }
}

//------------------------------------------------------------------------------

UCHAR* NDTGetRandomAddr(NDIS_MEDIUM ndisMedium)
{
   UCHAR* pucAddr = NULL;
   
   switch (ndisMedium) {
   case NdisMedium802_3:
   case NdisMediumFddi:
   case NdisMedium802_5:
      pucAddr = new UCHAR[6];
      pucAddr[0] = 0x00; pucAddr[1] = 0x02; pucAddr[2] = 0x04;
      pucAddr[3] = 0x06; pucAddr[4] = 0x08; pucAddr[5] = 0x0A;
      break;
   }
   return pucAddr;
}

UCHAR* NDTGetRandomAddrEx(NDIS_MEDIUM ndisMedium, unsigned short int uiVar, PUCHAR pucAddr)
{
   if (pucAddr == NULL)
	   pucAddr = new UCHAR[6];
   
   switch (ndisMedium) {
   case NdisMedium802_3:
   case NdisMediumFddi:
   case NdisMedium802_5:
      pucAddr[0] = 0x00; pucAddr[1] = 0x02; pucAddr[2] = 0x04;
      pucAddr[3] = 0x06; *(unsigned short int *)&pucAddr[4] = uiVar;
      break;
   }
   return pucAddr;
}

//------------------------------------------------------------------------------

UCHAR* NDTGetBroadcastAddr(NDIS_MEDIUM ndisMedium)
{
   UCHAR* pucAddr = NULL;
   
   switch (ndisMedium) {
   case NdisMedium802_3:
   case NdisMediumFddi:
      pucAddr = new UCHAR[6];
      pucAddr[0] = 0xFF; pucAddr[1] = 0xFF; pucAddr[2] = 0xFF;
      pucAddr[3] = 0xFF; pucAddr[4] = 0xFF; pucAddr[5] = 0xFF;
      break;
   case NdisMedium802_5:
      pucAddr = new UCHAR[6];
      pucAddr[0] = 0xC0; pucAddr[1] = 0x00; pucAddr[2] = 0xFF;
      pucAddr[3] = 0xFF; pucAddr[4] = 0xFF; pucAddr[5] = 0xFF;
      break;
   }
   return pucAddr;
}

//------------------------------------------------------------------------------

UCHAR* NDTGetMulticastAddr(NDIS_MEDIUM ndisMedium)
{
   UCHAR* pucAddr = NULL;
   
   switch (ndisMedium) {
   case NdisMedium802_3:
   case NdisMediumFddi:
      pucAddr = new UCHAR[6];
      pucAddr[0] = 0x01; pucAddr[1] = 0x02; pucAddr[2] = 0x03;
      pucAddr[3] = 0x04; pucAddr[4] = 0x05; pucAddr[5] = 0x06;
      break;
   case NdisMedium802_5:
      pucAddr = new UCHAR[6];
      pucAddr[0] = 0xC0; pucAddr[1] = 0x00; pucAddr[2] = 0x01;
      pucAddr[3] = 0x02; pucAddr[4] = 0x03; pucAddr[5] = 0x04;
      break;
   }
   return pucAddr;
}

//------------------------------------------------------------------------------

UCHAR* NDTGetMulticastAddrEx(NDIS_MEDIUM ndisMedium, UINT uiVar)
{
   UCHAR* pucAddr = NULL;
   
   switch (ndisMedium) {
   case NdisMedium802_3:
   case NdisMediumFddi:
      pucAddr = new UCHAR[6];
      pucAddr[0] = 0x01; pucAddr[1] = 0x02; pucAddr[2] = 0x03;
      pucAddr[3] = 0x04; pucAddr[4] = (UCHAR)(uiVar/256);
      pucAddr[5] = (UCHAR)uiVar;
      break;
   case NdisMedium802_5:
      pucAddr = new UCHAR[6];
      pucAddr[0] = 0xC0; pucAddr[1] = 0x00; pucAddr[2] = 0x01;
      pucAddr[3] = 0x02; pucAddr[4] = (UCHAR)(uiVar/256);
      pucAddr[5] = (UCHAR)uiVar;
      break;
   }
      
   return pucAddr;
}

//------------------------------------------------------------------------------

UCHAR* NDTGetBadMulticastAddr(NDIS_MEDIUM ndisMedium)
{
   UCHAR *pucAddr = NULL;
   
   switch (ndisMedium) {
   case NdisMedium802_3:
   case NdisMedium802_5:
   case NdisMediumFddi:
      pucAddr = new UCHAR[6];
      pucAddr[0] = 0x01; pucAddr[1] = 0x02; pucAddr[2] = 0x03;
      pucAddr[3] = 0x04; pucAddr[4] = 0x05; pucAddr[5] = 0x00;
      break;
   }
   return pucAddr;
}

//------------------------------------------------------------------------------

UCHAR* NDTGetGroupAddr(NDIS_MEDIUM ndisMedium)
{
   UCHAR* pucAddr = NULL;
   
   switch (ndisMedium) {
   case NdisMedium802_5:
      pucAddr = new UCHAR[6];
      pucAddr[0] = 0xC0; pucAddr[1] = 0x00; pucAddr[2] = 0x81;
      pucAddr[3] = 0x02; pucAddr[4] = 0x03; pucAddr[5] = 0x04;
      break;
   }
   return pucAddr;
}

//------------------------------------------------------------------------------

HRESULT NDTGetPermanentAddr(
   HANDLE hAdapter, NDIS_MEDIUM ndisMedium, UCHAR** ppucAddress
)
{
   HRESULT hr = S_OK;
   UINT cbAddr = 0;
   NDIS_OID ndisOid = 0;
   
   switch (ndisMedium) {
   case NdisMedium802_3:
      cbAddr = 6;
      ndisOid = OID_802_3_PERMANENT_ADDRESS;
      break;
   case NdisMedium802_5:
      cbAddr = 6;
      ndisOid = OID_802_5_PERMANENT_ADDRESS;
      break;
   case NdisMediumFddi:
      cbAddr = 6;
      ndisOid = OID_FDDI_LONG_CURRENT_ADDR;
      break;
   }

   *ppucAddress = new UCHAR[cbAddr];
   if (*ppucAddress == NULL) {
      hr = E_OUTOFMEMORY;
      goto cleanUp;
   }

   hr = NDTQueryInfo(hAdapter, ndisOid, *ppucAddress, cbAddr, NULL, NULL);
   if (FAILED(hr)) goto cleanUp;
   
cleanUp:   
   return hr;
}

//------------------------------------------------------------------------------

DWORD NDTGetReceiveDelay(NDIS_MEDIUM ndisMedium)
{
   DWORD dwDelay = 0;
   
   switch (ndisMedium) {
   case NdisMedium802_3:
      dwDelay = 2000;
      break;
   case NdisMedium802_5:
      dwDelay = 2000;
      break;
   case NdisMediumFddi:
      dwDelay = 400;
      break;
   }
   return dwDelay;
}

//------------------------------------------------------------------------------

HRESULT NDTGetMaximumFrameSize(HANDLE hAdapter, UINT* puiMaximumFrameSize)
{
   return NDTQueryInfo(
      hAdapter, OID_GEN_MAXIMUM_FRAME_SIZE, puiMaximumFrameSize, 
      sizeof(UINT), NULL, NULL
   );
}

//------------------------------------------------------------------------------

HRESULT NDTGetMaximumTotalSize(HANDLE hAdapter, UINT* puiMaximumTotalSize)
{
   return NDTQueryInfo(
      hAdapter, OID_GEN_MAXIMUM_TOTAL_SIZE, puiMaximumTotalSize, 
      sizeof(UINT), NULL, NULL
   );
}

//------------------------------------------------------------------------------

HRESULT NDTGetPhysicalMedium(HANDLE hAdapter, UINT* puiMedium)
{
   HRESULT hr = S_OK;

   hr = NDTQueryInfo(
      hAdapter, OID_GEN_PHYSICAL_MEDIUM, puiMedium, sizeof(UINT), NULL, NULL
   );
   if (hr == NDT_STATUS_INVALID_OID || hr == NDT_STATUS_NOT_SUPPORTED) {
      *puiMedium = 0;
      hr = S_OK;
   }
   return hr;
}

//------------------------------------------------------------------------------

HRESULT NDTGetLinkSpeed(HANDLE hAdapter, UINT *puiLinkSpeed)
{
   return NDTQueryInfo(
      hAdapter, OID_GEN_LINK_SPEED, puiLinkSpeed, sizeof(UINT), NULL, NULL
   );
}

//------------------------------------------------------------------------------

HRESULT NDTSetPacketFilter(HANDLE hAdapter, UINT uiFilter)
{
   return NDTSetInfo(
      hAdapter, OID_GEN_CURRENT_PACKET_FILTER, &uiFilter, sizeof(uiFilter),
      NULL, NULL
   );
}

//------------------------------------------------------------------------------

HRESULT NDTGetFilters(HANDLE hAdapter, NDIS_MEDIUM ndisMedium, UINT *puiFilters)
{
   HRESULT hr = S_OK;
   UINT uiFilters = 0;
   
   switch (ndisMedium) {
   case NdisMedium802_3:
      uiFilters  = NDT_FILTER_DIRECTED | NDT_FILTER_BROADCAST;
      uiFilters |= NDT_FILTER_MULTICAST;
      hr = NDTSetPacketFilter(hAdapter, NDT_FILTER_ALL_MULTICAST);
      if (SUCCEEDED(hr)) uiFilters |= NDT_FILTER_ALL_MULTICAST;
      break;
   case NdisMedium802_5:
      uiFilters  = NDT_FILTER_DIRECTED | NDT_FILTER_BROADCAST;
      uiFilters |= NDT_FILTER_FUNCTIONAL | NDT_FILTER_GROUP_PKT;
      hr = NDTSetPacketFilter(hAdapter, NDT_FILTER_ALL_FUNCTIONAL);
      if (SUCCEEDED(hr)) uiFilters |= NDT_FILTER_ALL_FUNCTIONAL;
      hr = NDTSetPacketFilter(hAdapter, NDT_FILTER_SOURCE_ROUTING);
      if (SUCCEEDED(hr)) uiFilters |= NDT_FILTER_SOURCE_ROUTING;
      hr = NDTSetPacketFilter(hAdapter, NDT_FILTER_MAC_FRAME);
      if (SUCCEEDED(hr)) uiFilters |= NDT_FILTER_MAC_FRAME;
      break;
   case NdisMediumFddi:
      uiFilters  = NDT_FILTER_DIRECTED | NDT_FILTER_BROADCAST;
      uiFilters |= NDT_FILTER_MULTICAST;
      hr = NDTSetPacketFilter(hAdapter, NDT_FILTER_ALL_MULTICAST);
      if (SUCCEEDED(hr)) uiFilters |= NDT_FILTER_ALL_MULTICAST;
      hr = NDTSetPacketFilter(hAdapter, NDT_FILTER_SOURCE_ROUTING);
      if (SUCCEEDED(hr)) uiFilters |= NDT_FILTER_SOURCE_ROUTING;
      hr = NDTSetPacketFilter(hAdapter, NDT_FILTER_MAC_FRAME);
      if (SUCCEEDED(hr)) uiFilters |= NDT_FILTER_MAC_FRAME;
      hr = NDTSetPacketFilter(hAdapter, NDT_FILTER_SMT);
      if (SUCCEEDED(hr)) uiFilters |= NDT_FILTER_SMT;
      break;
   }

   hr = NDTSetPacketFilter(hAdapter, NDT_FILTER_PROMISCUOUS);
   if (SUCCEEDED(hr)) uiFilters |= NDT_FILTER_PROMISCUOUS;
   
   hr = NDTSetPacketFilter(hAdapter, NDT_FILTER_ALL_LOCAL);
   if (SUCCEEDED(hr)) uiFilters |= NDT_FILTER_ALL_LOCAL;

   *puiFilters = uiFilters;
   hr = NDTSetPacketFilter(hAdapter, 0);

   return hr;
}

//------------------------------------------------------------------------------

HRESULT NDTAddMulticastAddr(
   HANDLE hAdapter, NDIS_MEDIUM ndisMedium, UCHAR* pucAddr

)
{
   HRESULT hr = S_OK;
   UCHAR* pucMultiAddrs = NULL;
   UINT cMultiAddrs = 0;
   NDIS_OID oid = 0;
   NDIS_OID oidSize = 0;
   UINT cbAddr = 0;
   UINT uiSize = 0;
   UINT uiNeeded = 0;
   UINT ui = 0;

   switch (ndisMedium) {
   case NdisMedium802_3:
   case NdisMediumDix:
      cbAddr = 6;
      oid = OID_802_3_MULTICAST_LIST;
      oidSize = OID_802_3_MAXIMUM_LIST_SIZE;
      break;
   case NdisMediumFddi:
      cbAddr = 6;
      oid = OID_FDDI_LONG_MULTICAST_LIST;
      oidSize = OID_FDDI_LONG_MAX_LIST_SIZE;
      break;
   default:
      hr = E_UNEXPECTED;
      goto cleanUp;
   }

   // Get a list size
   hr = NDTQueryInfo(hAdapter, oidSize, &uiSize, sizeof(UINT), NULL, NULL);
   if (FAILED(hr)) goto cleanUp;
   
   // Allocate a memory
   pucMultiAddrs = new UCHAR[uiSize * cbAddr];
   if (pucMultiAddrs == NULL) {
      hr = E_OUTOFMEMORY;
      goto cleanUp;
   }
   
   // Get a multicast addresses
   hr = NDTQueryInfo(
      hAdapter, oid, pucMultiAddrs, uiSize * cbAddr, &uiNeeded, NULL
   );
   if (FAILED(hr)) goto cleanUp;

   // We get such amount of multicast address 
   cMultiAddrs = uiNeeded / cbAddr;

   // May be an address is already set
   for (ui = 0; ui < cMultiAddrs; ui++) {
      if (memcmp(pucAddr, pucMultiAddrs + ui*cbAddr, cbAddr) == 0) break;
   }

   // Address is already in a list, so we don't have to set it
   if (ui < cMultiAddrs) goto cleanUp;

   // There should be a room for next address
   if (cMultiAddrs >= uiSize) {
      hr = NDT_STATUS_RESOURCES;
      goto cleanUp;
   }

   // And now append address to current list
   memcpy(pucMultiAddrs + uiNeeded, pucAddr, cbAddr);
   uiNeeded += cbAddr;

   // And set it
   hr = NDTSetInfo(hAdapter, oid, pucMultiAddrs, uiNeeded, NULL, NULL);
   if (FAILED(hr)) goto cleanUp;
   
cleanUp:
   delete [] pucMultiAddrs;
   return hr;
}

//------------------------------------------------------------------------------

HRESULT NDTDeleteMulticastAddr(
   HANDLE hAdapter, NDIS_MEDIUM ndisMedium, UCHAR *pucAddr
)
{
   HRESULT hr = S_OK;
   UCHAR* pucMultiAddrs = NULL;
   UINT cMultiAddrs = 0;
   NDIS_OID oid = 0;
   NDIS_OID oidSize = 0;
   UINT cbAddr = 0;
   UINT uiSize = 0;
   UINT uiNeeded = 0;
   UINT ui = 0;


   switch (ndisMedium) {
   case NdisMedium802_3:
   case NdisMediumDix:
      cbAddr = 6;
      oid = OID_802_3_MULTICAST_LIST;
      oidSize = OID_802_3_MAXIMUM_LIST_SIZE;
      break;
   case NdisMediumFddi:
      cbAddr = 6;
      oid = OID_FDDI_LONG_MULTICAST_LIST;
      oidSize = OID_FDDI_LONG_MAX_LIST_SIZE;
      break;
   default:
      hr = E_UNEXPECTED;
      goto cleanUp;
   }

   // Get a list size
   hr = NDTQueryInfo(hAdapter, oidSize, &uiSize, sizeof(UINT), NULL, NULL);
   if (FAILED(hr)) goto cleanUp;

   // Allocate a memory
   pucMultiAddrs = new UCHAR[uiSize * cbAddr];
   if (pucMultiAddrs == NULL) {
      hr = E_OUTOFMEMORY;
      goto cleanUp;
   }
   
   // Get a multicast addresses
   hr = NDTQueryInfo(
      hAdapter, oid, pucMultiAddrs, uiSize * cbAddr, &uiNeeded, NULL
   );
   if (FAILED(hr)) goto cleanUp;

   // We get such amount of multicast address 
   cMultiAddrs = uiNeeded / cbAddr;

   // The address should be set
   for (ui = 0; ui < cMultiAddrs; ui++) {
      if (memcmp(pucAddr, pucMultiAddrs + ui*cbAddr, cbAddr) == 0) break;
   }

   // Address isn't in a list, so we don't have to remove it
   if (ui >= cMultiAddrs) goto cleanUp;

   // And now remove address from list (ok this can be unsafe in some cases)
   if ((ui + 1) < cMultiAddrs) memcpy(
      pucMultiAddrs + ui * cbAddr, pucMultiAddrs + (ui + 1) * cbAddr, 
      cbAddr * (cMultiAddrs - ui - 1)
   );
   uiNeeded -= cbAddr;

   // And set it
   hr = NDTSetInfo(hAdapter, oid, pucMultiAddrs, uiNeeded, NULL, NULL);
   if (FAILED(hr)) goto cleanUp;
      
cleanUp:
   delete [] pucMultiAddrs;
   return hr;
}

//------------------------------------------------------------------------------

ULONG NDTGetRandom(ULONG ulLow, ULONG ulHigh)
{
   static ULONG ulRandomNumber = -1;
   const  ULONG ulRandomMultiplier =  9301;
   const  ULONG ulRandomAddend = 49297;

   if (ulRandomNumber == -1) ulRandomNumber = GetTickCount();
   ulRandomNumber = (ulRandomNumber * ulRandomMultiplier) + ulRandomAddend;
   return ulRandomNumber % (ulHigh - ulLow + 1) + ulLow;
}

//------------------------------------------------------------------------------
