/**********************************************************************/
/** Microsoft Windows **/
/** Copyright(c) Microsoft Corp., 1994 **/
/**********************************************************************/
/*
chic.c
Contains VxD code that is specific to Chicago
FILE HISTORY:
Johnl 14-Mar-1994 Created
*/
#include <nbtprocs.h>
#include <tdiinfo.h>
#include <llinfo.h>
#include <ipinfo.h>
#include <dhcpinfo.h>
#include <nbtinfo.h>
#ifdef CHICAGO
//******************* Pageable Routine Declarations ****************
//
// any digit 0 to 9 and '.' are legal characters in an ipaddr
//
#define IS_IPADDR_CHAR( ch ) ( (ch >= '0' && ch <= '9') || (ch == '.') )
#define MAX_ADAPTER_DESCRIPTION_LENGTH 128
const char szXportName[] = "MSTCP";
//
// asking ndis to open,read,close for every single parameter slows down
// bootup: don't open if it's already open
//
NDIS_HANDLE GlobalNdisHandle = NULL;
//
// This flag is set to TRUE when the first adapter is initialized. It
// indicates that NBT globals (such as node type, scode ID etc) have
// had the opportunity to be set by DHCP.
//
BOOL fGlobalsInitialized = FALSE;
//
// As each adapter gets added, the Lana offset is added
//
UCHAR iLanaOffset = 0;
VOID GetMacAddr( ULONG IpAddress, UCHAR MacAddr[] );
BOOL StopAllNameQueries( tDEVICECONTEXT *pDeviceContext );
BOOL CancelAllDelayedEvents( tDEVICECONTEXT *pDeviceContext );
extern tTIMERQ TimerQ;
BOOL GetNdisParam( LPSTR pszKey,
ULONG * pVal,
NDIS_PARAMETER_TYPE ParameterType );
//******************* Pageable Routine Declarations ****************
#ifdef ALLOC_PRAGMA
#pragma CTEMakePageable(PAGE, IPNotification)
#pragma CTEMakePageable(PAGE, DestroyDeviceObject)
#pragma CTEMakePageable(PAGE, SaveNameDnsServerAddrs)
#pragma CTEMakePageable(PAGE, GetDnsServerAddress)
#pragma CTEMakePageable(PAGE, GetNameServerAddress)
#pragma CTEMakePageable(PAGE, GetMacAddr)
#pragma CTEMakePageable(PAGE, VxdReadIniString)
#pragma CTEMakePageable(PAGE, GetProfileInt)
#pragma CTEMakePageable(PAGE, VxdOpenNdis)
#pragma CTEMakePageable(PAGE, GetNdisParam)
#pragma CTEMakePageable(PAGE, VxdCloseNdis)
#pragma CTEMakePageable(PAGE, StopAllNameQueries)
#pragma CTEMakePageable(PAGE, VxdUnload)
#pragma CTEMakePageable(PAGE, ReleaseNbtConfigMem)
#endif
/*******************************************************************
NAME: IPNotification
SYNOPSIS: Called by the IP driver when a new Lana needs to be created
or destroyed for an IP address.
ENTRY: pDevNode - Plug'n'Play context
IpAddress - New ip address
IpMask - New ip mask
fNew - Are we creating or destroying this Lana?
NOTES: This routine is only used by Chicago
HISTORY:
Johnl 17-Mar-1994 Created
********************************************************************/
TDI_STATUS IPNotification( ULONG IpAddress,
ULONG IpMask,
PVOID pDevNode,
USHORT IPContext,
BOOL fNew )
{
NTSTATUS status = STATUS_SUCCESS;
ULONG IpNS[COUNT_NS_ADDR];
ULONG IpDns[COUNT_NS_ADDR];
int iLana;
UCHAR RequestedLana;
int i;
int iEmpty;
UCHAR PreviousNodeType;
UCHAR MacAddr[6];
CTEPagedCode();
KdPrint(("IPNotification entered\r\n"));
if ( !IpAddress )
{
return TDI_SUCCESS ;
}
if ( fNew )
{
//
// parameters nodetype, scope and bcastaddr are systemwide, not per
// adapter: so read only once.
//
if ( !fGlobalsInitialized )
{
PreviousNodeType = NodeType;
//
// This will re-read the DHCPable parameters now that we have
// a potential DHCP source
//
VxdOpenNdis();
ReadParameters2( pNbtGlobConfig, NULL );
VxdCloseNdis();
if (PreviousNodeType & PROXY)
{
NodeType |= PROXY;
}
fGlobalsInitialized = TRUE;
}
//
// Get the name servers for this device context (ip address)
//
GetNameServerAddress( IpAddress, IpNS);
//
// Get the DNS servers for this device context (ip address)
//
GetDnsServerAddress( IpAddress, IpDns);
//
// Find a free spot in our Lana table
//
for ( iEmpty = 0; iEmpty < NBT_MAX_LANAS; iEmpty++)
{
if (LanaTable[iEmpty].pDeviceContext == NULL)
goto Found;
}
//
// Lana table is full so bail
//
CDbgPrint(DBGFLAG_ERROR,("IPNotification: LanaTable full\r\n"));
return STATUS_INSUFFICIENT_RESOURCES;
Found:
GetMacAddr( IpAddress, MacAddr );
status = CreateDeviceObject( pNbtGlobConfig,
htonl( IpAddress ),
htonl( IpMask ),
IpNS[0],
IpNS[1],
IpDns[0],
IpDns[1],
MacAddr,
0 );
if (status != STATUS_SUCCESS)
{
CDbgPrint(DBGFLAG_ERROR,("IPNotification: CreateDeviceObject Failed\r\n"));
return status;
}
//
// We first try and ask for a specific Lana from vnetbios based on
// our Lanabase and how many other Lanas we've already added. If
// this fails, then we will ask for Any Lana. If the LANABASE
// parameter is not specified, then request Any Lana.
//
if ( LanaBase != VXD_ANY_LANA )
RequestedLana = LanaBase + iLanaOffset++ ;
else
RequestedLana = VXD_ANY_LANA;
RetryRegister:
if ( (iLana = RegisterLana2( pDevNode, RequestedLana )) == 0xff )
{
if ( RequestedLana == VXD_ANY_LANA )
{
//
// We couldn't get *any* lanas so bail
//
CDbgPrint(DBGFLAG_ERROR,("IPNotification: RegisterLana2 Failed\r\n"));
DestroyDeviceObject( pNbtGlobConfig, htonl(IpAddress));
return STATUS_INSUFFICIENT_RESOURCES;
}
else
{
//
// Somebody may already have this Lana so beg for another one
//
RequestedLana = VXD_ANY_LANA;
goto RetryRegister;
}
}
KdPrint(("IPNotification: using Lana %d\r\n", iLana ));
LanaTable[iEmpty].pDeviceContext =
(tDEVICECONTEXT*)pNbtGlobConfig->DeviceContexts.Blink ;
LanaTable[iEmpty].pDeviceContext->iLana = iLana;
//
// remove our child (redir, that is!) and reenumerate our devnode
// so redir knows we are there!
//
ReconfigureDevnode( pDevNode );
}
else
{
status = DestroyDeviceObject( pNbtGlobConfig,
htonl(IpAddress) );
}
return status;
}
/*******************************************************************
NAME: DestroyDeviceObject
SYNOPSIS: Destroys the specified device
ENTRY: pConfig - Global config structure
IpAddr - Destroy the adapter with this address
NOTES: This routine is only used by Chicago
HISTORY:
Johnl 17-Mar-1994 Created
********************************************************************/
NTSTATUS DestroyDeviceObject(
tNBTCONFIG *pConfig,
ULONG IpAddr
)
{
LIST_ENTRY * pEntry;
LIST_ENTRY * pHead;
tDEVICECONTEXT * pDeviceContext;
tDEVICECONTEXT * pTmpDeviceContext;
tDEVICECONTEXT * pNextDeviceContext;
tCLIENTELE * pClientEle;
tADDRESSELE * pAddress;
tNAMEADDR * pNameAddr;
tCONNECTELE * pConnEle;
tLOWERCONNECTION * pLowerConn;
PRCV_CONTEXT prcvCont;
tRCVELE * pRcvEle ;
tTIMERQENTRY * pTimer;
COMPLETIONCLIENT pClientCompletion;
PVOID Context;
tDGRAM_SEND_TRACKING * pTracker;
CTELockHandle OldIrq;
int i;
CTEPagedCode();
//
// Find which device is going away
// Also, find out a device object that is still active: we need that info
// to update some of the address ele's.
//
pDeviceContext = NULL;
pNextDeviceContext = NULL;
for ( pEntry = pConfig->DeviceContexts.Flink;
pEntry != &pConfig->DeviceContexts;
pEntry = pEntry->Flink )
{
pTmpDeviceContext = CONTAINING_RECORD( pEntry, tDEVICECONTEXT, Linkage);
if ( pTmpDeviceContext->IpAddress == IpAddr )
pDeviceContext = pTmpDeviceContext;
else
pNextDeviceContext = pTmpDeviceContext;
}
if (pDeviceContext == NULL)
return STATUS_INVALID_PARAMETER;
//
// don't accept anymore ncbs on this device
//
pDeviceContext->fDeviceUp = FALSE;
//
// Close all the connections
//
NbtNewDhcpAddress( pDeviceContext, 0, 0);
if ( --NbtConfig.AdapterCount == 1)
NbtConfig.MultiHomed = FALSE;
ASSERT(IsListEmpty(&pDeviceContext->LowerConnFreeHead));
//
// if we are destroying the last device then
//
if ( NbtConfig.AdapterCount == 0)
{
//
// Kill off all of the Receive any from any NCBs
//
while ( !IsListEmpty( &pDeviceContext->RcvAnyFromAnyHead ))
{
pEntry = RemoveHeadList( &pDeviceContext->RcvAnyFromAnyHead ) ;
prcvCont = CONTAINING_RECORD( pEntry, RCV_CONTEXT, ListEntry ) ;
ASSERT( prcvCont->Signature == RCVCONT_SIGN ) ;
CTEIoComplete( prcvCont->pNCB, STATUS_NETWORK_NAME_DELETED, 0 ) ;
}
//
// Kill off all of the Receive any datagrams from any
//
while ( !IsListEmpty(&pDeviceContext->RcvDGAnyFromAnyHead))
{
pEntry = RemoveHeadList( &pDeviceContext->RcvDGAnyFromAnyHead ) ;
pRcvEle = CONTAINING_RECORD( pEntry, tRCVELE, Linkage ) ;
CTEIoComplete( pRcvEle->pIrp, STATUS_NETWORK_NAME_DELETED, 0 ) ;
CTEMemFree( pRcvEle ) ;
}
}
//
// if any name queries are in progress, stop them now
//
StopAllNameQueries( pDeviceContext );
CancelAllDelayedEvents( pDeviceContext );
//
// walk through all names and see if any is being registered on this
// device context: if so, stop and complete it!
//
for (i=0;i < NbtConfig.pLocalHashTbl->lNumBuckets ;i++ )
{
pHead = &NbtConfig.pLocalHashTbl->Bucket[i];
pEntry = pHead->Flink;
while (pEntry != pHead)
{
pNameAddr = CONTAINING_RECORD(pEntry,tNAMEADDR,Linkage);
pEntry = pEntry->Flink;
if (pNameAddr->NameTypeState & STATE_RESOLVING)
{
pTimer = pNameAddr->pTimer;
//
// if the name registration was started for this name on this device
// context, stop the timer. (Completion routine will take care of
// doing registration on other device contexts if applicable)
//
if (pTimer)
{
pTracker = pTimer->Context;
ASSERT(pTracker->pDeviceContext->Verify == NBT_VERIFY_DEVCONTEXT);
if (pTracker->pDeviceContext == pDeviceContext)
{
ASSERT(pTracker->pNameAddr == pNameAddr)
pNameAddr->pTimer = NULL;
CTESpinLock(&NbtConfig.JointLock,OldIrq);
StopTimer(pTimer,&pClientCompletion,&Context);
CTESpinFree(&NbtConfig.JointLock,OldIrq);
if (pClientCompletion)
{
(*pClientCompletion)(Context,STATUS_NETWORK_NAME_DELETED);
}
DbgPrint("DestroyDeviceObject: stopped name reg timer") ;
}
}
}
}
}
//
// walk through all the client ele's this device context has and clean
// up all the mess this clientele created!
//
for ( i = 0 ; i <= pDeviceContext->cMaxNames ; i++ )
{
pClientEle = pDeviceContext->pNameTable[i];
if ( !pClientEle )
continue;
VxdCleanupAddress( pDeviceContext,
NULL,
pClientEle,
(UCHAR)i,
TRUE );
}
//
// close all the TDI handles
//
CloseAddressesWithTransport(pDeviceContext);
//
// if a call was started, but aborted then we could have some memory here!
//
while (!IsListEmpty(&pDeviceContext->UpConnectionInUse))
{
pEntry = RemoveHeadList(&pDeviceContext->UpConnectionInUse);
pConnEle = CONTAINING_RECORD(pEntry,tCONNECTELE,Linkage);
CTEMemFree( pConnEle );
}
while (!IsListEmpty(&pDeviceContext->LowerConnection))
{
pEntry = RemoveHeadList(&pDeviceContext->LowerConnection);
pLowerConn = CONTAINING_RECORD(pEntry,tLOWERCONNECTION,Linkage);
CTEMemFree( pLowerConn );
}
//
// Remove the device from our Lana table and Vnetbios
//
for ( i = 0; i < NBT_MAX_LANAS; i++)
{
if (LanaTable[i].pDeviceContext == pDeviceContext)
{
DeregisterLana(LanaTable[i].pDeviceContext->iLana);
LanaTable[i].pDeviceContext = NULL;
KdPrint(("DestroyDeviceObject: deregistered Lana %d\r\n",pDeviceContext->iLana));
break;
}
}
RemoveEntryList( &pDeviceContext->Linkage);
//
// Walk through the AddressHead list. If any addresses exist and they
// point to old device context, put the next device context. Also, update
// adapter mask to reflect that this device context is now gone.
//
KdPrint(("DestroyDeviceObject: setting AddrEle,NameAddr fields\r\n"));
pHead = pEntry = &NbtConfig.AddressHead;
while ((pEntry = pEntry->Flink) != pHead)
{
pAddress = CONTAINING_RECORD(pEntry,tADDRESSELE,Linkage);
ASSERT (pAddress->Verify == NBT_VERIFY_ADDRESS);
if (pAddress->pDeviceContext == pDeviceContext)
{
pAddress->pDeviceContext = pNextDeviceContext;
}
pAddress->pNameAddr->AdapterMask &= ~pDeviceContext->AdapterNumber;
}
if (pDeviceContext->pNameTable)
CTEMemFree( pDeviceContext->pNameTable );
if (pDeviceContext->pSessionTable)
CTEMemFree( pDeviceContext->pSessionTable );
CTEMemFree( pDeviceContext );
return STATUS_SUCCESS;
}
/*******************************************************************
NAME: SaveNameDnsServerAddrs
SYNOPSIS: Get the name server and dns server addrs from the registry
and save it in Nbtconfig. We do this so that when a new
adapter comes in or lease gets renewed (particularly the
latter), we don't have to go read the registry becuase it
may not be safe to do so.
This routine will have to be modified if setup ever changes
to allow configuration of name/dns servers per lana.
ENTRY: Nothing
HISTORY:
Koti 20-Nov-1994 Created
********************************************************************/
VOID SaveNameDnsServerAddrs( VOID )
{
UCHAR i ;
PUCHAR pchSrv = "NameServer$" ;
PUCHAR pchDnsSrv = "NameServer" ;
PUCHAR pchSrvNum;
LPTSTR pchString ;
PUCHAR pchCurrent, pchNext;
BOOL fOneMore=TRUE;
ULONG IpNameServers[COUNT_NS_ADDR];
CTEPagedCode();
//
// Get Name Server ipaddrs
//
pchSrvNum = pchSrv + 10 ; // to overwrite '$' with 1,2,3 etc.
for ( i = 0; i < COUNT_NS_ADDR; i++)
{
IpNameServers[i] = LOOP_BACK;;
*pchSrvNum = '1' + i;
// call GetNdisParam directly so that we don't query dhcp here
// if ( !CTEReadIniString( NULL, pchSrv, &pchString ) )
//
if ( GetNdisParam( pchSrv, (ULONG *)&pchString, NdisParameterString ) )
{
if ( ConvertDottedDecimalToUlong( pchString, &IpNameServers[i] ))
{
DbgPrint("SaveNameDnsServerAddrs: bad name srv addr\r\n") ;
IpNameServers[i] = LOOP_BACK;
}
CTEFreeMem( pchString ) ;
}
}
//
// store the name server ipaddrs (potentially 0 if not defined in registry)
//
NbtConfig.lRegistryNameServerAddress = IpNameServers[0];
NbtConfig.lRegistryBackupServer = IpNameServers[1];
//
// Now get Dns Server ipaddrs
//
//
// initialize all of them to worst case
//
for ( i = 0; i < COUNT_NS_ADDR; i++)
{
IpNameServers[i] = LOOP_BACK;
}
// call GetNdisParam directly so that we don't query dhcp here
// if ( !CTEReadIniString( NULL, pchDnsSrv, &pchString ) )
//
if ( GetNdisParam( pchDnsSrv, (ULONG *)&pchString, NdisParameterString ) )
{
//
// we are generating (upto) COUNT_NS_ADDR pointers each pointing to
// one ipaddr. The string in system.ini looks like:
// NameServer = 11.101.4.26,200.200.200.200,1.2.3.4,1.91.245.10
//
pchNext = pchCurrent = pchString;
if ( IS_IPADDR_CHAR(*pchCurrent) ) // make sure at least one ipaddr defnd
{
i = 0;
while( (i < COUNT_NS_ADDR) && fOneMore )
{
while( IS_IPADDR_CHAR(*pchNext) )
pchNext++;
if ( *pchNext == ',' ) // ',' is the separator between 2 addrs
{
*pchNext = '\0';
pchNext++;
}
else
{
fOneMore = FALSE; // reached end of line
}
//
// as long as at least the first one ipaddr gets converted properly,
// ignore errors in others
//
if ( ConvertDottedDecimalToUlong( pchCurrent, &IpNameServers[i] ))
{
DbgPrint("SaveNameDnsServerAddrs: bad dns srv addr\r\n") ;
IpNameServers[i] = LOOP_BACK;
}
i++;
pchCurrent = pchNext; // go, convert the next one
}
}
if( pchString != NULL )
{
CTEFreeMem( pchString ) ;
}
}
//
// store the dns server ipaddrs (potentially 0 if not defined in registry)
//
NbtConfig.lRegistryDnsServerAddress = IpNameServers[0];
NbtConfig.lRegistryDnsBackupServer = IpNameServers[1];
}
/*******************************************************************
NAME: GetDnsServerAddress
SYNOPSIS: Gets the DNS server ipaddrs from the registry.
Or, if DHCP is installed and the DNS server addresses aren't
found, we get them from DHCP
ENTRY: IpAddr - If we can get from DHCP, get form this address
pIpDnsServer - Receives addresses if found (otherwise 0)
NOTES: This routine is only used by Snowball
HISTORY:
Koti 18-Oct-1994 Created
********************************************************************/
void GetDnsServerAddress( ULONG IpAddr, PULONG pIpDnsServer)
{
UCHAR i ;
UINT OptId;
TDI_STATUS tdistatus ;
ULONG Buff[COUNT_NS_ADDR] ;
ULONG Size;
CTEPagedCode();
for ( i = 0; i < COUNT_NS_ADDR; i++)
{
pIpDnsServer[i] = LOOP_BACK ;
}
pIpDnsServer[0] = NbtConfig.lRegistryDnsServerAddress;
pIpDnsServer[1] = NbtConfig.lRegistryDnsBackupServer;
//
// if it was defined in the registry, we are done
//
if ( pIpDnsServer[0] != LOOP_BACK || pIpDnsServer[1] != LOOP_BACK )
{
return;
}
//
// it was not defined in the registry: try getting them from DHCP
//
Size = sizeof( Buff ) ;
OptId = 6; // DNS Option
tdistatus = DhcpQueryOption( IpAddr,
OptId,
&Buff,
&Size ) ;
switch ( tdistatus )
{
case TDI_SUCCESS:
case TDI_BUFFER_OVERFLOW: // May be more then one our buffer will hold
for ( i = 0; i < COUNT_NS_ADDR; i++ )
{
if ( Size >= (sizeof(ULONG)*(i+1)))
pIpDnsServer[i] = htonl(Buff[i]) ;
}
break ;
case TDI_INVALID_PARAMETER: // Option not found
break;
default:
ASSERT( FALSE ) ;
break ;
}
KdPrint(("GetDnsServerAddress: Primary: %x, backup: %x\r\n",
pIpDnsServer[0], pIpDnsServer[1] )) ;
}
/*******************************************************************
NAME: GetNameServerAddress
SYNOPSIS: Gets the Win server for the specified Lana.
Or, if DHCP is installed and the Name server addresses aren't
found, we get them from DHCP
ENTRY: IpAddr - If we can get from DHCP, get form this address
pIpNameServer - Receives addresses if found (otherwise 0)
NOTES: This routine is only used by Snowball
HISTORY:
Johnl 21-Oct-1993 Created
********************************************************************/
void GetNameServerAddress( ULONG IpAddr,
PULONG pIpNameServer)
{
UCHAR i ;
UINT OptId;
TDI_STATUS tdistatus ;
ULONG Buff[COUNT_NS_ADDR] ;
ULONG Size;
CTEPagedCode();
for ( i = 0; i < COUNT_NS_ADDR; i++)
{
pIpNameServer[i] = LOOP_BACK ;
}
pIpNameServer[0] = NbtConfig.lRegistryNameServerAddress;
pIpNameServer[1] = NbtConfig.lRegistryBackupServer;
//
// if it was defined in the registry, we are done
//
if ( pIpNameServer[0] != LOOP_BACK || pIpNameServer[1] != LOOP_BACK )
{
return;
}
//
// not defined in the registry: try to get it from dhcp
//
OptId = 44; // NBNS Option
Size = sizeof( Buff ) ;
tdistatus = DhcpQueryOption( IpAddr,
OptId,
&Buff,
&Size ) ;
switch ( tdistatus )
{
case TDI_SUCCESS:
case TDI_BUFFER_OVERFLOW: // May be more then one our buffer will hold
for ( i = 0; i < COUNT_NS_ADDR; i++ )
{
if ( Size >= (sizeof(ULONG)*(i+1)))
pIpNameServer[i] = htonl(Buff[i]) ;
}
break ;
case TDI_INVALID_PARAMETER: // Option not found
break ;
default:
ASSERT( FALSE ) ;
break ;
}
KdPrint(("GetNameServerAddress: Primary: %x, backup: %x\r\n",
pIpNameServer[0], pIpNameServer[1] )) ;
}
/*******************************************************************
NAME: GetMacAddr
SYNOPSIS: Gets the mac address for the give ipaddr
ENTRY: IpAddress - the address for which to find mac addr
MacAddr - the array where to store mac addr
HISTORY:
Koti 19-Oct-1994 Created
********************************************************************/
VOID GetMacAddr( ULONG IpAddress, UCHAR MacAddr[] )
{
TDI_STATUS tdistatus ;
int i, j, k ;
uchar Context[CONTEXT_SIZE] ;
TDIObjectID ID ;
TDIEntityID EList[MAX_TDI_ENTITIES] ;
ULONG Size ;
UINT NumReturned ;
NDIS_BUFFER ndisbuff ;
IFEntry *ifeAdapterInfo[MAX_TDI_ENTITIES];
UINT AdptNum;
CTEPagedCode();
//
// initialize to 0, in case things don't work out
//
memset( MacAddr, 0, 6 ) ;
//
// The first thing to do is get the list of available entities, and make
// sure that there are some interface entities present.
//
ID.toi_entity.tei_entity = GENERIC_ENTITY;
ID.toi_entity.tei_instance = 0;
ID.toi_class = INFO_CLASS_GENERIC;
ID.toi_type = INFO_TYPE_PROVIDER;
ID.toi_id = ENTITY_LIST_ID;
Size = sizeof(EList);
InitNDISBuff( &ndisbuff, &EList, Size, NULL ) ;
memset(Context, 0, CONTEXT_SIZE);
tdistatus = TdiVxdQueryInformationEx( 0,
&ID,
&ndisbuff,
&Size,
Context);
if (tdistatus != TDI_SUCCESS)
{
CDbgPrint( DBGFLAG_ERROR, ( "GetMacAddr: Querying entity list failed\r\n")) ;
return;
}
NumReturned = (uint)Size/sizeof(TDIEntityID);
AdptNum = 0;
//
// first find out info about the adapters
//
for (i = 0; i < NumReturned; i++)
{
//
// if this entity/instance describes an adapter
//
if ( EList[i].tei_entity == IF_ENTITY )
{
DWORD isMib;
ID.toi_entity.tei_entity = EList[i].tei_entity ;
ID.toi_entity.tei_instance = EList[i].tei_instance;
ID.toi_class = INFO_CLASS_GENERIC ;
ID.toi_type = INFO_TYPE_PROVIDER;
ID.toi_id = ENTITY_TYPE_ID ;
Size = sizeof( isMib );
InitNDISBuff( &ndisbuff, &isMib, Size, NULL ) ;
memset(Context, 0, CONTEXT_SIZE);
tdistatus = TdiVxdQueryInformationEx( 0,
&ID,
&ndisbuff,
&Size,
Context);
if ( tdistatus != TDI_SUCCESS )
{
CDbgPrint( DBGFLAG_ERROR, ( "GetMacAddr: Getting isMib failed\r\n")) ;
return ;
}
//
// Does this entity support MIB
//
if (isMib != IF_MIB)
{
CDbgPrint( DBGFLAG_ERROR, ( "GetMacAddr: skipping non-MIB entity\r\n")) ;
continue;
}
//
// MIB requests supported - query the adapter info
//
Size = sizeof(IFEntry) + MAX_ADAPTER_DESCRIPTION_LENGTH + 1;
ifeAdapterInfo[AdptNum] = (IFEntry *)CTEAllocInitMem(Size);
if ( ifeAdapterInfo[AdptNum] == NULL )
{
CDbgPrint( DBGFLAG_ERROR, ( "GetMacAddr: Couldn't allocate AdapterInfo buffer\r\n")) ;
for ( k=0; k<AdptNum; k++ )
{
CTEFreeMem( ifeAdapterInfo[k] ) ;
}
return;
}
ID.toi_class = INFO_CLASS_PROTOCOL;;
ID.toi_id = IF_MIB_STATS_ID;
Size = sizeof(IFEntry) + MAX_ADAPTER_DESCRIPTION_LENGTH + 1;
InitNDISBuff( &ndisbuff, ifeAdapterInfo[AdptNum], Size, NULL ) ;
memset(Context, 0, CONTEXT_SIZE);
tdistatus = TdiVxdQueryInformationEx( 0,
&ID,
&ndisbuff,
&Size,
Context);
if ( tdistatus != TDI_SUCCESS )
{
CDbgPrint( DBGFLAG_ERROR, ( "GetMacAddr: Getting IF type failed\r\n")) ;
for ( k=0; k<AdptNum; k++ )
{
CTEFreeMem( ifeAdapterInfo[k] ) ;
}
return ;
}
AdptNum++;
}
}
//
// now that we know about the adapters, get the ipaddrs
//
for (i = 0; i < NumReturned; i++)
{
if ( EList[i].tei_entity == CL_NL_ENTITY )
{
IPSNMPInfo IPStats ;
IPAddrEntry * pIAE ;
ULONG NLType ;
ULONG IpNameServer[COUNT_NS_ADDR];
ULONG IpDnsServer[COUNT_NS_ADDR];
//
// Does this entity support IP?
//
ID.toi_entity.tei_entity = EList[i].tei_entity ;
ID.toi_entity.tei_instance = EList[i].tei_instance;
ID.toi_class = INFO_CLASS_GENERIC ;
ID.toi_type = INFO_TYPE_PROVIDER;
ID.toi_id = ENTITY_TYPE_ID ;
Size = sizeof( NLType );
InitNDISBuff( &ndisbuff, &NLType, Size, NULL ) ;
memset(Context, 0, CONTEXT_SIZE);
tdistatus = TdiVxdQueryInformationEx( 0,
&ID,
&ndisbuff,
&Size,
Context);
if ( tdistatus != TDI_SUCCESS )
{
CDbgPrint( DBGFLAG_ERROR, ( "GetMacAddr: Getting NL type failed\r\n")) ;
for ( k=0; k<AdptNum; k++ )
{
CTEFreeMem( ifeAdapterInfo[k] ) ;
}
return ;
}
if ( NLType != CL_NL_IP )
continue ;
//
// We've got an IP driver so get it's address table
//
ID.toi_class = INFO_CLASS_PROTOCOL ;
ID.toi_id = IP_MIB_STATS_ID;
Size = sizeof(IPStats);
InitNDISBuff( &ndisbuff, &IPStats, Size, NULL ) ;
memset(Context, 0, CONTEXT_SIZE);
tdistatus = TdiVxdQueryInformationEx( 0,
&ID,
&ndisbuff,
&Size,
Context);
if ( tdistatus != TDI_SUCCESS )
{
CDbgPrint( DBGFLAG_ERROR, ( "GetMacAddr: Getting IPStats failed\r\n")) ;
continue ;
}
if ( IPStats.ipsi_numaddr < 1 )
{
CDbgPrint( DBGFLAG_ERROR, ( "GetMacAddr: No IP Addresses installed\r\n")) ;
continue ;
}
Size = sizeof(IPAddrEntry) * IPStats.ipsi_numaddr ;
if ( !(pIAE = (IPAddrEntry*) CTEAllocInitMem( Size )) )
{
CDbgPrint( DBGFLAG_ERROR, ( "GetMacAddr: Couldn't allocate IP table buffer\r\n")) ;
continue ;
}
ID.toi_id = IP_MIB_ADDRTABLE_ENTRY_ID ;
InitNDISBuff( &ndisbuff, pIAE, Size, NULL ) ;
memset( Context, 0, CONTEXT_SIZE ) ;
tdistatus = TdiVxdQueryInformationEx( 0,
&ID,
&ndisbuff,
&Size,
Context);
if ( tdistatus != TDI_SUCCESS )
{
CDbgPrint( DBGFLAG_ERROR, ( "GetMacAddr: Getting IP address table failed\r\n")) ;
CTEFreeMem( pIAE ) ;
continue ;
}
// ASSERT( Size/sizeof(IPAddrEntry) >= IPStats.ipsi_numaddr ) ;
//
// We have the IP address table for this IP driver. Look for
// our IP address
//
for ( j = 0 ; j < IPStats.ipsi_numaddr ; j++ )
{
//
// find our ipaddress
//
if ( pIAE[j].iae_addr != IpAddress )
{
continue ;
}
//
// now find out the mac address for this ipaddr
//
for ( k=0; k<AdptNum; k++ )
{
if ( ifeAdapterInfo[k]->if_index == pIAE[j].iae_index )
{
CTEMemCopy( MacAddr, ifeAdapterInfo[k]->if_physaddr, 6 );
break;
}
}
}
if (pIAE)
{
CTEFreeMem( pIAE ) ;
}
}
}
for ( k=0; k<AdptNum; k++ )
{
CTEFreeMem( ifeAdapterInfo[k] ) ;
}
}
/*******************************************************************
NAME: VxdReadIniString
SYNOPSIS: Vxd stub for CTEReadIniString
ENTRY: pchKey - Key value to look for in the NBT section
ppchString - Pointer to buffer found string is returned in
EXIT: ppchString will point to an allocated buffer
RETURNS: STATUS_SUCCESS if found
NOTES: The client must free ppchString when done with it
HISTORY:
Johnl 30-Aug-1993 Created
********************************************************************/
NTSTATUS VxdReadIniString( LPSTR pchKey, LPSTR * ppchString )
{
CTEPagedCode();
if ( GetNdisParam( pchKey, (ULONG *)ppchString, NdisParameterString ) )
{
return STATUS_SUCCESS ;
}
else
{
//
// Does DHCP have it?
//
if ( *ppchString = (char *) GetDhcpOption( pchKey, 0 ) )
{
return STATUS_SUCCESS ;
}
}
return STATUS_UNSUCCESSFUL ;
}
/*******************************************************************
NAME: GetProfileInt
SYNOPSIS: Gets the specified value from the registry or DHCP
ENTRY: pchKey - Key value to look for in the NBT section
Default - Default value if not in registry or DHCP
Min - Minimum value can be
RETURNS: Registry Value or Dhcp value or default value
NOTES:
HISTORY:
Johnl 23-Mar-1994 Created
********************************************************************/
ULONG GetProfileInt( PVOID p, LPSTR pchKey, ULONG Default, ULONG Min )
{
ULONG Val = Default;
CTEPagedCode();
//
// Is the value in the registry?
//
if ( !GetNdisParam( pchKey, &Val, NdisParameterInteger ) )
{
//
// No, Check DHCP
//
Val = GetDhcpOption( pchKey, Default );
}
if ( Val < Min )
{
Val = Min;
}
return Val;
}
ULONG GetProfileHex( PVOID p, LPSTR pchKey, ULONG Default, ULONG Min )
{
return GetProfileInt( p, pchKey, Default, Min);
}
/*******************************************************************
NAME: VxdOpenNdis
SYNOPSIS: Prepare Ndis to read entries from the registry. Ndis
basically opens the registry and gives a handle back
which we store in GlobalNdisHandle.
ENTRY: Nothing (GlobalNdisHandle is global)
RETURNS: TRUE if things worked well
FALSE if some error occured
HISTORY:
Koti Nov. 20, 94
********************************************************************/
BOOL VxdOpenNdis( VOID )
{
NDIS_STATUS Status;
NDIS_STRING Name;
CTEPagedCode();
ASSERT( !GlobalNdisHandle );
// Open the config information.
Name.Length = strlen(szXportName) + 1;
Name.MaximumLength = Name.Length;
Name.Buffer = (PUCHAR)szXportName;
NdisOpenProtocolConfiguration(&Status, &GlobalNdisHandle, &Name);
if (Status != NDIS_STATUS_SUCCESS)
{
// Unable to open the configuration. Fail now.
GlobalNdisHandle = NULL;
ASSERT(0);
return FALSE;
}
return TRUE;
}
/*******************************************************************
NAME: GetNdisParam
SYNOPSIS: Gets the value from the MSTCP protocol sectio of the registry
ENTRY: pchKey - Key value to look for in the NBT section
pVal - Retrieved parameter
ParameterType - Type of parameter (string, int)
RETURNS: TRUE if the value was found, FALSE otherwise
NOTES: If the parameter is a string parameter, then this routine
will allocate memory which the client is responsible for
freeing.
HISTORY:
Johnl 23-Mar-1994 Created
********************************************************************/
BOOL GetNdisParam( LPSTR pszKey,
ULONG * pVal,
NDIS_PARAMETER_TYPE ParameterType )
{
NDIS_STATUS Status;
NDIS_STRING Name;
uint i;
PNDIS_CONFIGURATION_PARAMETER Param;
BOOL fRet = FALSE;
CTEPagedCode();
ASSERT( GlobalNdisHandle );
Name.Length = strlen(pszKey) + 1;
Name.MaximumLength = Name.Length;
Name.Buffer = pszKey;
NdisReadConfiguration(&Status, &Param, GlobalNdisHandle, &Name,
ParameterType);
if (Status == NDIS_STATUS_SUCCESS)
{
if ( ParameterType == NdisParameterString)
{
LPSTR lpstr = CTEAllocInitMem( Param->ParameterData.StringData.Length + 1 ) ;
if ( lpstr )
{
strcpy( lpstr, Param->ParameterData.StringData.Buffer );
*pVal = (ULONG) lpstr;
fRet = TRUE;
}
}
else
{
*pVal = Param->ParameterData.IntegerData;
fRet = TRUE;
}
}
return fRet ;
}
/*******************************************************************
NAME: VxdCloseNdis
SYNOPSIS: Close the handle that we opened in VxdOpenNdis
ENTRY: Nothing (GlobalNdisHandle is global)
HISTORY:
Koti Nov. 20, 94
********************************************************************/
VOID VxdCloseNdis( VOID )
{
CTEPagedCode();
ASSERT(GlobalNdisHandle);
if (GlobalNdisHandle != NULL)
{
NdisCloseConfiguration(GlobalNdisHandle);
GlobalNdisHandle = NULL;
}
}
/*******************************************************************
NAME: StopAllNameQueries
SYNOPSIS: This routine is called when the device context is going
away. It finds out all the name queries that are in
progress (started by someone on this device context) and
stops them.
ENTRY: pDeviceContext - the device context that is going away.
RETURNS: TRUE if at least one name query was found and stopped
FALSE if there wasn't any query in progress
HISTORY:
Koti Nov. 19, 94
********************************************************************/
BOOL
StopAllNameQueries( tDEVICECONTEXT *pDeviceContext )
{
tDGRAM_SEND_TRACKING *pTracker;
NBT_WORK_ITEM_CONTEXT *Context;
PVOID pClientCompletion;
PVOID pClientContext;
tNAMEADDR *pNameAddr;
tTIMERQENTRY *pTimer;
PLIST_ENTRY pHead;
PLIST_ENTRY pEntry;
CTELockHandle OldIrq;
CTEPagedCode();
//
// first check to see if any names are on the pending list: all name
// queries over the network will be here (WINS, broadcast, DNS)
//
CTESpinLock(&NbtConfig.JointLock,OldIrq);
pHead = &NbtConfig.PendingNameQueries;
pEntry = pHead->Flink;
while (pEntry != pHead)
{
pNameAddr = CONTAINING_RECORD(pEntry,tNAMEADDR,Linkage);
pEntry = pEntry->Flink;
pTimer = pNameAddr->pTimer;
if (pTimer)
{
pTracker = (tDGRAM_SEND_TRACKING *)pTimer->Context;
ASSERT (pTracker->pDeviceContext->Verify == NBT_VERIFY_DEVCONTEXT);
if (pTracker->pDeviceContext == pDeviceContext)
{
StopTimer(pTimer,(COMPLETIONCLIENT *)&pClientCompletion,&Context);
if (pClientCompletion)
{
//
// Remove from the pending list
//
RemoveEntryList(&pNameAddr->Linkage);
InitializeListHead(&pNameAddr->Linkage);
pNameAddr->pTimer = NULL;
NbtDereferenceName(pNameAddr);
//
// complete client's ncb. If requests were queued on
// the same name by other clients, this will start new
// name queries for those clients
//
CTESpinFree(&NbtConfig.JointLock,OldIrq);
CompleteClientReq(pClientCompletion,
(tDGRAM_SEND_TRACKING *)Context,
STATUS_NETWORK_NAME_DELETED);
CTESpinLock(&NbtConfig.JointLock,OldIrq);
DbgPrint("StopAllNameQueries: stopped net name query") ;
}
}
}
}
CTESpinFree(&NbtConfig.JointLock,OldIrq);
//
// now check if any names are on the lmhosts queue: all name queries
// waiting for lmhosts (and/or hosts) parsing will be here
//
Context = NULL;
if (LmHostQueries.ResolvingNow && LmHostQueries.Context)
{
Context = (NBT_WORK_ITEM_CONTEXT *)LmHostQueries.Context;
pTracker = Context->pTracker;
ASSERT (pTracker->pDeviceContext->Verify == NBT_VERIFY_DEVCONTEXT);
if (pTracker->pDeviceContext == pDeviceContext)
{
LmHostQueries.Context = NULL;
pClientCompletion = Context->ClientCompletion;
pClientContext = Context->pClientContext;
// name did not resolve, so delete from table
RemoveName(pTracker->pNameAddr);
DereferenceTracker(pTracker);
CompleteClientReq(pClientCompletion,
pClientContext,
STATUS_NETWORK_NAME_DELETED);
DbgPrint("StopAllNameQueries: stopped lmhosts query in progress") ;
}
}
//
// now walk through the queued items and cancel all the relevant ones
//
pHead = &LmHostQueries.ToResolve;
pEntry = pHead->Flink;
while (pEntry != pHead)
{
Context = CONTAINING_RECORD(pEntry,NBT_WORK_ITEM_CONTEXT,Item.List);
pEntry = pEntry->Flink;
pTracker = Context->pTracker;
ASSERT (pTracker->pDeviceContext->Verify == NBT_VERIFY_DEVCONTEXT);
if (pTracker->pDeviceContext == pDeviceContext)
{
RemoveEntryList(&Context->Item.List);
pClientCompletion = Context->ClientCompletion;
pClientContext = Context->pClientContext;
// name did not resolve, so delete from table
RemoveName(pTracker->pNameAddr);
DereferenceTracker(pTracker);
CompleteClientReq(pClientCompletion,
pClientContext,
STATUS_NETWORK_NAME_DELETED);
DbgPrint("StopAllNameQueries: cancelled lmhosts queries in queue") ;
}
}
}
/*******************************************************************
NAME: VxdUnload
SYNOPSIS: This is where we are asked to unload. We destroy all
the device objects and then unload ourselves if the
message came from vtcp.
ENTRY: pchModuleName - name of the module that is going away.
We take action only if vtcp is going away
(in which case, pchModuleName is "MSTCP")
RETURNS: STATUS_SUCCESS if things went ok
HISTORY:
Koti Oct 5, 94
********************************************************************/
NTSTATUS
VxdUnload( LPSTR pchModuleName )
{
LIST_ENTRY * pEntry;
tDEVICECONTEXT * pDeviceContext;
NTSTATUS status;
CTEPagedCode();
KdPrint(("VxdUnload entered\r\n"));
if ( (pchModuleName == NULL) ||
(strcmp(pchModuleName,szXportName) != 0) )
{
CDbgPrint(DBGFLAG_ERROR,("VxdUnload: Unload msg not from MSTCP\r\n"));
return STATUS_SUCCESS;
}
//
// all devices will be destroyed by the time VxdUnload is called: this is
// just being paranoid
//
for ( pEntry = pNbtGlobConfig->DeviceContexts.Flink;
pEntry != &pNbtGlobConfig->DeviceContexts;
)
{
pDeviceContext = CONTAINING_RECORD( pEntry, tDEVICECONTEXT, Linkage);
pEntry = pEntry->Flink;
status = DestroyDeviceObject(pNbtGlobConfig, pDeviceContext->IpAddress);
if (status != STATUS_SUCCESS)
{
ASSERT(0);
}
}
//
// events such as name refresh etc. have been scheduled to be executed
// later: cancel all those
//
CancelAllDelayedEvents( NULL );
//
// Tell IP not to use the handler anymore
//
IPRegisterAddrChangeHandler( IPNotification, FALSE);
ReleaseNbtConfigMem();
//
// we don't provide any service, so don't pass our name
//
CTEUnload( (char *)NULL );
}
/*******************************************************************
NAME: ReleaseNbtConfigMem
SYNOPSIS: This is where we release all the memory that was allocated
at init/run time via ifs mgr.
We also stop the various timers.
HISTORY:
Koti Oct 14, 94
********************************************************************/
VOID ReleaseNbtConfigMem( VOID )
{
PLIST_ENTRY pHead, pEntry;
tTIMERQENTRY *pTimerEntry;
tDGRAM_SEND_TRACKING *pTrack;
tADDRESSELE *pAddress;
tCLIENTELE *pClientEle;
tCONNECTELE *pConnEle;
tLOWERCONNECTION *pLowerConn;
tNAMEADDR *pNameAddr;
CTELockHandle OldIrq;
int i;
KdPrint(("ReleaseNbtConfigMem entered\r\n"));
CTEPagedCode();
//
// stop the timer that used to look for timed-out ncb's
//
StopTimeoutTimer();
//
// if any other timers are active, stop them
//
if (!IsListEmpty(&TimerQ.ActiveHead))
{
pHead = &TimerQ.ActiveHead;
pEntry = pHead->Flink;
while( pEntry != pHead )
{
pTimerEntry = CONTAINING_RECORD(pEntry,tTIMERQENTRY,Linkage);
pEntry = pEntry->Flink;
CTESpinLock(&NbtConfig.JointLock,OldIrq);
StopTimer(pTimerEntry,NULL,NULL);
CTESpinFree(&NbtConfig.JointLock,OldIrq);
}
}
//
// free all the timers on the free list
//
while (!IsListEmpty(&TimerQ.FreeHead))
{
pEntry = RemoveHeadList(&TimerQ.FreeHead);
pTimerEntry = CONTAINING_RECORD(pEntry,tTIMERQENTRY,Linkage);
CTEMemFree(pTimerEntry);
}
// free the various buffers
if ( pFileBuff )
CTEMemFree( pFileBuff );
if ( NbtConfig.pHosts )
CTEMemFree( NbtConfig.pHosts );
if (NbtConfig.pScope)
CTEMemFree( NbtConfig.pScope );
if (NbtConfig.pLmHosts)
CTEMemFree(NbtConfig.pLmHosts);
if (NbtConfig.pDomainName)
CTEMemFree(NbtConfig.pDomainName);
if (NbtConfig.pDNSDomains)
CTEMemFree(NbtConfig.pDNSDomains);
//
// NbtDereferenceAddress might have freed the addresses. But if
// ReleaseNameOnNet returned pending we wouldn't have freed the addressele
// yet, waiting for NameReleaseDone to be called. Well, we are about to
// be unloaded so free such instances now!
//
while (!IsListEmpty(&NbtConfig.AddressHead))
{
pEntry = RemoveHeadList(&NbtConfig.AddressHead);
pAddress = CONTAINING_RECORD(pEntry,tADDRESSELE,Linkage);
while (!IsListEmpty(&pAddress->ClientHead))
{
pEntry = RemoveHeadList(&pAddress->ClientHead);
pClientEle = CONTAINING_RECORD(pEntry,tCLIENTELE,Linkage);
while (!IsListEmpty(&pClientEle->ConnectActive))
{
pEntry = RemoveHeadList(&pClientEle->ConnectActive);
pConnEle = CONTAINING_RECORD(pEntry,tCONNECTELE,Linkage);
pLowerConn = pConnEle->pLowerConnId;
if (pLowerConn)
CTEMemFree(pLowerConn);
CTEMemFree(pConnEle);
}
CTEMemFree(pClientEle);
}
CTEMemFree( pAddress );
}
// free the DomainList
while (!IsListEmpty(&DomainNames.DomainList))
{
pEntry = RemoveHeadList(&DomainNames.DomainList);
pNameAddr = CONTAINING_RECORD(pEntry,tNAMEADDR,Linkage);
if (pNameAddr->pIpList)
CTEMemFree(pNameAddr->pIpList);
CTEMemFree(pNameAddr);
}
// free pLocalHashTbl
if (NbtConfig.pLocalHashTbl)
{
//
// we should have freed all the entries by now. It's possible though
// that we sent a name release which returned pending, so we are
// still hanging on to the nameaddr. If there is any such instance,
// free it now (when else? we are about to be unloaded now!)
//
for(i=0;i<NbtConfig.pLocalHashTbl->lNumBuckets;i++)
{
while (!IsListEmpty(&(NbtConfig.pLocalHashTbl->Bucket[i])))
{
pEntry = RemoveHeadList(&(NbtConfig.pLocalHashTbl->Bucket[i]));
pNameAddr = CONTAINING_RECORD(pEntry,tNAMEADDR,Linkage);
CTEMemFree(pNameAddr);
}
}
CTEMemFree( NbtConfig.pLocalHashTbl );
}
// free pRemoteHashTbl
if (NbtConfig.pRemoteHashTbl)
{
for(i=0;i<NbtConfig.pRemoteHashTbl->lNumBuckets;i++)
{
while (!IsListEmpty(&(NbtConfig.pRemoteHashTbl->Bucket[i])))
{
pEntry = RemoveHeadList(&(NbtConfig.pRemoteHashTbl->Bucket[i]));
pNameAddr = CONTAINING_RECORD(pEntry,tNAMEADDR,Linkage);
CTEMemFree(pNameAddr);
}
}
CTEMemFree( NbtConfig.pRemoteHashTbl );
}
// free up the DgramTrackerFreeQ
while (!IsListEmpty(&NbtConfig.DgramTrackerFreeQ))
{
pEntry = RemoveHeadList(&NbtConfig.DgramTrackerFreeQ);
pTrack = CONTAINING_RECORD(pEntry,tDGRAM_SEND_TRACKING,Linkage);
CTEMemFree( pTrack );
}
//
// shouldn't see any pending name queries at this point, but just in case!
//
while (!IsListEmpty(&NbtConfig.PendingNameQueries))
{
pEntry = RemoveHeadList(&NbtConfig.PendingNameQueries);
pNameAddr = CONTAINING_RECORD(pEntry,tNAMEADDR,Linkage);
CTEMemFree( pNameAddr );
}
// free the SessionBufferFreeList list (allocated, not used)
while( !IsListEmpty(&NbtConfig.SessionBufferFreeList) )
{
pEntry = RemoveHeadList(&NbtConfig.SessionBufferFreeList);
CTEMemFree( pEntry );
}
// free SendContextFreeList (allocated, not used)
while( !IsListEmpty(&NbtConfig.SendContextFreeList) )
{
pEntry = RemoveHeadList(&NbtConfig.SendContextFreeList);
CTEMemFree( pEntry );
}
// free RcvContextFreeList (allocated, not used)
while( !IsListEmpty(&NbtConfig.RcvContextFreeList) )
{
pEntry = RemoveHeadList(&NbtConfig.RcvContextFreeList);
CTEMemFree( pEntry );
}
#ifdef DBG
if( !IsListEmpty(&DbgMemList) )
{
KdPrint(("ReleaseNbtConfigMem: memory leak!\r\n"));
if (DbgLeakCheck)
{
ASSERT(0);
}
}
#endif
}
#endif // CHICAGO