/*++

******************************************************************************
* Copyright (c) 1990-96 Digi International Inc., All Rights Reserved         *
*                                                                            *
* This software contains proprietary and confidential information of Digi    *
* International Inc.  By accepting transfer of this copy, Recipient agrees   *
* to retain this software in confidence, to prevent disclosure to others,    *
* and to make no use of this software other than that for which it was       *
* delivered.  This is an unpublished copyrighted work of Digi International  *
* Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly  *
* prohibited.                                                                *
*                                                                            *
* Restricted Rights Legend                                                   *
*                                                                            *
* Use, duplication, or disclosure by the Government is subject to            *
* restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in        *
* Technical Data and Computer Software clause at DFARS 252.227-7031 or       *
* subparagraphs (c)(1) and (2) of the Commercial Computer Software -         *
* Restricted Rights at 48 CFR 52.227-19, as applicable.                      *
*                                                                            *
* Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343         *
*                                                                            *
******************************************************************************

Module Name:

   call.c

Abstract:

   This module contains routines to alloc, free, and maintain call objects.  A 
   call object defines a connection between Ndis/Tapi at the upper edge and
	Sxb/X25/RemoteUser at the lower edge.

Author:

   Michael Zarns

Revision History:

   $History: call.c $
 * 
 * *****************  Version 13  *****************
 * User: Mikez        Date: 4/28/97    Time: 1:54p
 * Updated in $/Components/Windows/NT/Sync/Wan
 * Updated to use DsApi library.  Uses IoBuildDeviceIoControlRequest to
 * alloc Irps causing two problems.  Because ioctls are METHOD_BUFFERED, a
 * systemBuffer is alloc'd, but, because the completionroutines return
 * STATUS_MORE_PROCESSING_REQUIRED, never gets freed.  Also, SystemBuffer
 * is not copied to OutputBuffer until after completionRoutine returns
 * STATUS_SUCCESS.  Need to move from METHOD_BUFFER or from using
 * IoBuildDeviceIoControlRequest.
 * 
 * *****************  Version 12  *****************
 * User: Mikez        Date: 3/18/97    Time: 12:40p
 * Updated in $/Components/Windows/NT/Sync/Wan
 * Corrected memory leak.  Previously alloc'd Rx Buffers were lost when
 * New Call Object routine zeroed out the structure.
 * 
 * *****************  Version 11  *****************
 * User: Mikez        Date: 3/12/97    Time: 3:44p
 * Updated in $/Components/Windows/NT/Sync/Wan
 * Misc changes to get RAS dialout working.  Still bugchecks after dialout
 * connection completes on multiprocessor NT 3.51, sp5.
 * 
 * *****************  Version 10  *****************
 * User: Mikez        Date: 3/04/97    Time: 11:15p
 * Updated in $/Components/Windows/NT/Sync/Wan
 * Removed references to AdapterObject CallObjects field.
 * 
 * *****************  Version 9  *****************
 * User: Mikez        Date: 3/04/97    Time: 2:54p
 * Updated in $/Components/Windows/NT/Sync/Wan
 * Added hack to read RAS TapiDevices keys to determine which ports RAS
 * won't accept dialin calls.  Don't indicate incoming calls to these
 * ports.
 * 
 * *****************  Version 8  *****************
 * User: Mikez        Date: 3/03/97    Time: 9:23a
 * Updated in $/Components/Windows/NT/Sync/Wan
 * Updated to use async completions on MakeCall, Drop and Answer TAPI OID
 * requests.
 * 
 * *****************  Version 7  *****************
 * User: Mikez        Date: 2/26/97    Time: 10:33a
 * Updated in $/Components/Windows/NT/Sync/Wan
 * Added debug info.  Now correctly set the InUse flag when a requested
 * CallObject is assigned.
 * 
 * *****************  Version 6  *****************
 * User: Mikez        Date: 1/29/97    Time: 4:58p
 * Updated in $/Components/Windows/NT/Sync/Wan
 * Fixed handling of CallObjectTable in New and Free CallObject routines.
 * 
 * *****************  Version 5  *****************
 * User: Mikez        Date: 1/28/97    Time: 10:27a
 * Updated in $/Components/Windows/NT/Sync/Wan
 * Removed broken downlevel ras support.
 * 
 * *****************  Version 4  *****************
 * User: Mikez        Date: 12/17/96   Time: 5:27p
 * Updated in $/Components/Windows/NT/Sync/Wan
 * RAS/TAPI likes to reuse AddressIDs without actually closing the call
 * first.  Changed NewCallObject code to allow this when the
 * InternalCallState is idle.
 * 
 * *****************  Version 3  *****************
 * User: Mikez        Date: 12/06/96   Time: 2:28p
 * Updated in $/Components/Windows/NT/Sync/Wan
 * Modified to support new SXB paridigm of "one NT device per adapter"
 * rather than the older "one NT device per port".
 * 
 * *****************  Version 2  *****************
 * User: Mikez        Date: 10/28/96   Time: 1:25p
 * Updated in $/Components/Windows/NT/Sync/Wan
 * Updated to support latest firmware release, v1.1.2b.

--*/
#include <ntddk.h>
#include <ndis.h>
#include <ndiswan.h>
#include <ndistapi.h>

#include "DsApi.h"
#include "digix25.h"
#include "digix25p.h"
#include "digifile.h"
#include <digialloc.h>

// regnames used DWORD
#define DWORD ULONG
#include "regnames.h"

///////////////////////////////////////////////////////////////////////////////
//
// Type definitions 
//

typedef struct TABLE_ENTRY
{
	BOOLEAN bInUse;
	BOOLEAN bRasDialoutOnly;
	PDIGIWAN_CALL_OBJECT CallObject;
} TABLE_ENTRY, *PTABLE_ENTRY;


///////////////////////////////////////////////////////////////////////////////
//
// Local Data
//

// Total number of call objects
ULONG nCallObjects = 0;

// Array of CallObject pointers with flag indicating availability
PTABLE_ENTRY CallObjectTable;

// Spinlock to protect the CallObjectTable list.
KSPIN_LOCK TableLock;

// Has table been initialized?
BOOLEAN bTableInitialized = FALSE;


///////////////////////////////////////////////////////////////////////////////
//
// Prototypes
//

VOID GetRasDialoutPorts();


NDIS_STATUS
InitializeCallList( 
	ULONG nTotalCalls, 
   HANDLE MiniportAdapterHandle )
/*++

Description:

	This routine is called to initialize the CallObjectTable and FreeCalls pool.

--*/
{
	ULONG i;
	ULONG TableLength;

	// Alloc table
	
	nCallObjects = nTotalCalls;
	TableLength = nCallObjects * sizeof( TABLE_ENTRY );
	CallObjectTable = DigiAllocMem( NonPagedPool, TableLength );

	if ( !CallObjectTable )
		return( NDIS_STATUS_RESOURCES );

	NdisZeroMemory( CallObjectTable, nTotalCalls * sizeof( TABLE_ENTRY ) );

	// Alloc call objects and init their table entries

	for ( i = 0; i < nCallObjects; i++ )
	{
		PDIGIWAN_CALL_OBJECT CallObject;
		CallObject = DigiAllocMem( NonPagedPool, sizeof( DIGIWAN_CALL_OBJECT ) );
		if ( !CallObject )
		{
			// Couldn't alloc memory.  Cleanup and exit error.
			ULONG j;
			for ( j = 0; j < i; j++ )
				DigiFreeMem( CallObjectTable[j].CallObject );
			DigiFreeMem( CallObjectTable );
			return( NDIS_STATUS_RESOURCES );
		}
		
		NdisZeroMemory( CallObject, sizeof( DIGIWAN_CALL_OBJECT ) );
		CallObject->Type = DIGIWAN_TYPE_CALL_OBJECT;
		CallObject->AddressID = i;
		NdisMInitializeTimer( &CallObject->OfferingTimeout, 
		                      MiniportAdapterHandle,
		                      CallOfferingTimeout, CallObject );
		
		CallObjectTable[i].CallObject = CallObject;
		CallObjectTable[i].bInUse = FALSE;
	}
	
	GetRasDialoutPorts();

	KeInitializeSpinLock( &TableLock );
	bTableInitialized = TRUE;

	return( NDIS_STATUS_SUCCESS );
}



VOID
FreeCallList()
/*++

Description:

	This routine is called to free resources used in the FreeCalls pool.

--*/
{
	KIRQL OldIrql;
	ULONG i;

	if ( !bTableInitialized )
		return;

	bTableInitialized = FALSE;

	KeAcquireSpinLock( &TableLock, &OldIrql );

	// Free alloc'd resources

	for ( i = 0; i < nCallObjects; i++ )
	{
		ASSERT( !CallObjectTable[i].bInUse );
		DigiFreeMem( CallObjectTable[i].CallObject );
	}

	DigiFreeMem( CallObjectTable );

	KeReleaseSpinLock( &TableLock, OldIrql );
}




PDIGIWAN_CALL_OBJECT
NewCallObject(
	PDIGIWAN_ADAPTER_OBJECT AdapterObject, 
	ULONG AddressID
	)
/*++

Description:

	This routine returns a new CallObject, either from the FreeCalls pool, or  
	newly allocated if the pool is empty.

Parameters:

	AdapterObject - Pointer to NdisWan virtual adapter.

	AddressID - tapi address id for the tapi endpoint which this call defines.

Return Value:

	Pointer to CallObject, or NULL if one could not be created.

--*/
{
	PDIGIWAN_CALL_OBJECT CallObject = NULL;
	KIRQL OldIrql;
	ULONG TempAddressID;
	NDIS_MINIPORT_TIMER TempTimeout;
	PUCHAR TempRxBuffer;

	if ( !bTableInitialized )
		return( NULL );

	// Range check
	if ( AddressID != DIGI_ADDRESSID_ANY && AddressID >= nCallObjects )
	{
		DigiDump( DIGIERRORS, ("NewCallObject: AddressID out of range 0x%x\n", AddressID ) );
		return( NULL );
	}

	// Get a CallObject
	KeAcquireSpinLock( &TableLock, &OldIrql );
	if ( AddressID == DIGI_ADDRESSID_ANY )
	{
		// Use any AddressID, so get the first available CallObject.
		ULONG i;

		// Walk the array looking for an available CallObject.
		for ( i = 0; i < nCallObjects; i++ )
		{
			if ( !CallObjectTable[i].bInUse && !CallObjectTable[i].bRasDialoutOnly )
			{
				// Available, grab it!
				CallObject = CallObjectTable[i].CallObject;
				ASSERT( CallObject->AddressID == i );
				CallObjectTable[i].bInUse = TRUE;
				break;
			}
		}
		
		// Find an available Address?
		if ( !CallObject )
		{
			// Nope.  Reject the request
			KeReleaseSpinLock( &TableLock, OldIrql );
			return( NULL );
		}
	}
	else
	{
		// Requested a specific AddressID.  Is it available?
		CallObject = CallObjectTable[ AddressID ].CallObject;
		if ( CallObjectTable[ AddressID ].bInUse )
		{
			// CallObject already in use and active.  Complain.
			DigiDump( DIGIERRORS, 
			          ("NewCallObject: AddressID already in use 0x%x\n", AddressID ) );

			// Allow it only if the current state is IDLE.
			if ( CallObject->InternalCallState != INTERNAL_CALL_STATE_IDLE )
			{
				KeReleaseSpinLock( &TableLock, OldIrql );
				return( NULL );
			}
		}
		else
		{
			// Available, grab it!
			CallObjectTable[AddressID].bInUse = TRUE;
		}
	}

	ASSERT( CallObject );
	ASSERT( CallObject->hStream == NULL );
	DigiDump( DIGICALLOBJ, ( "New CallObject: 0x%x, AddressID 0x%x\n",
	                       CallObject, CallObject->AddressID ) );
	KeReleaseSpinLock( &TableLock, OldIrql );

	// Initialize the call object, maintaining its AddressID and timout!
	TempAddressID = CallObject->AddressID;
	TempTimeout = CallObject->OfferingTimeout;
	TempRxBuffer = CallObject->RxBuffer;
	NdisZeroMemory( CallObject, sizeof( DIGIWAN_CALL_OBJECT ) );
	CallObject->AddressID = TempAddressID;
	CallObject->OfferingTimeout = TempTimeout;
	CallObject->RxBuffer = TempRxBuffer;
	CallObject->Type = DIGIWAN_TYPE_CALL_OBJECT;

	if ( !CallObject->RxBuffer )
	{
		CallObject->RxBuffer = DigiAllocMem( NonPagedPool, MAX_MTU_SIZE );
		if ( !CallObject->RxBuffer )
		{
			// Cleanup and exit
			DigiDump( DIGIERRORS, 
			          ("NewCallObject: Couldn't alloc rxbuffer. AddressID 0x%x\n", 
			          AddressID ) );
			KeAcquireSpinLock( &TableLock, &OldIrql );
			CallObjectTable[ CallObject->AddressID ].bInUse = FALSE;
			KeReleaseSpinLock( &TableLock, OldIrql );
			return( NULL );
		}
	}

	KeInitializeSpinLock( &CallObject->Lock );
	KeInitializeDpc( &CallObject->Dpc, DigiWanDeferredGetMsg, CallObject );

	CallObject->pAdapter = AdapterObject;
	CallObject->CurrentMaxMtuSize = MAX_MTU_SIZE;
	CallObject->PreamblePadding = AdapterObject->HeaderPadding;
	CallObject->PostamblePadding = AdapterObject->TailPadding;
	
	CallObject->BearerModes = LINEBEARERMODE_DATA;
	CallObject->MediaModes = LINEMEDIAMODE_DIGITALDATA;

	CallObject->MaxSendFrameSize = MAX_MTU_SIZE;
	CallObject->MaxRecvFrameSize = MAX_MTU_SIZE;

	CallObject->SendFramingBits = PPP_FRAMING | 
	                              PPP_COMPRESS_PROTOCOL_FIELD |
											RFC_1356_FRAMING |
											TAPI_PROVIDER;

	CallObject->RecvFramingBits = PPP_FRAMING | 
	                              PPP_COMPRESS_PROTOCOL_FIELD |
											RFC_1356_FRAMING |
											TAPI_PROVIDER;

	CallObject->SendACCM = 0xFFFFFFFF;
	CallObject->RecvACCM = 0xFFFFFFFF;

	// CallObject->SendCompressionBits = 0;
	// CallObject->SendCompressionBits = 0;
	// CallObject->SendFeatureBits = 0;
	// CallObject->RecvFeatureBits = 0;

	CallObject->RemoteLinkSpeed = 2048000;
	CallObject->LocalLinkSpeed = 2048000;

	CallObject->TapiCallState = LINECALLSTATE_IDLE;
	CallObject->InternalCallState = INTERNAL_CALL_STATE_IDLE;

	return( CallObject );
}



VOID
FreeCallObject(
	PDIGIWAN_CALL_OBJECT CallObject
	)
/*++

Description:

	This routine returns the call object to the FreeCall pool up to a maximum
	of 20 entries.  This maximum keeps the size of the list, and consequently 
	our memory usage, down.

Parameters:

	CallObject - pointer to the call object to be released.

--*/
{
	KIRQL OldIrql;

	if ( !bTableInitialized )
		return;

	if ( CallObject->RxBuffer )
	{
		DigiFreeMem( CallObject->RxBuffer );
		CallObject->RxBuffer = 0;
	}

	KeAcquireSpinLock( &TableLock, &OldIrql );
	CallObjectTable[ CallObject->AddressID ].bInUse = FALSE;
	DigiDump( DIGICALLOBJ, ( "Free CallObject:    0x%x,     AddressID 0x%x\n",
	                       CallObject, CallObject->AddressID ) );
	KeReleaseSpinLock( &TableLock, OldIrql );
}



ULONG
DigiWanCountActiveCalls( VOID )
/*++

Description:

	Walks the CallObjectTable counting the number of callobjects currently
	in use.

Return Value:

	Number of active calls.

--*/
{
	ULONG Count = 0;
	ULONG i;

	if ( bTableInitialized )
	{
		for ( i = 0; i < nCallObjects; i++ )
		{
			if ( CallObjectTable[i].bInUse )
				Count++;
		}
	}
	return( Count );
}



typedef enum QUERY_TABLE_INDEX
{
	QUERY_INDEX_ADDRESS = 0,
	QUERY_INDEX_USAGE,
	QUERY_INDEX_MAX
} QUERY_TABLE_INDEX;


NTSTATUS
DigiRegQueryX( 
	IN PUNICODE_STRING pKey,
	IN PWSTR pValue,
	IN ULONG uType,
	OUT PVOID pBuffer
	)
/*++

Description:

	Get a registry value.

Parameters:

	PUNICODE_STRING pKey
		Name of registry Key in which to look.

	PWSTR pValue
		Name of Value to query.

	ULONG uType
		Type of data being queried (i.e. REG_DWORD).

	PVOID pBuffer
		Points to output buffer.

Return Value:

	RtlQueryRegistryValues return value.

--*/
{
	NTSTATUS Status;

#define QUERY_COUNT 1
#define QUERY_TABLE_ENTRIES (QUERY_COUNT+1)

	RTL_QUERY_REGISTRY_TABLE Table[QUERY_TABLE_ENTRIES];
	ULONG zero = 0;

	DigiDump( DIGIVERYLOUD, 
	          ( "DigiRegQueryDirect:\n"
	          "   Key     = %S\n"
	          "   Value   = %S\n"
	          "   pBuffer = 0x%x\n"
	          "   uType   = %s\n",
	          pKey->Buffer,
	          pValue,
	          pBuffer,
	          uType == REG_DWORD ? "REG_DWORD" : 
				 uType == REG_SZ ? "REG_SZ" :
				 uType == REG_MULTI_SZ ? "REG_MULTI_SZ" : "Other"
	          ) );
								   
	// This properly terminates the table.
	RtlZeroMemory( Table, 
	               QUERY_TABLE_ENTRIES*sizeof( RTL_QUERY_REGISTRY_TABLE ) );
	
	Table[0].Flags = RTL_QUERY_REGISTRY_DIRECT | 
	                 RTL_QUERY_REGISTRY_REQUIRED |
	                 RTL_QUERY_REGISTRY_NOEXPAND;
	Table[0].Name = pValue;
	Table[0].EntryContext = pBuffer;
	Table[0].DefaultType = uType,
	Table[0].DefaultData = &zero;
	Table[0].DefaultLength = sizeof( ULONG );

	Status = RtlQueryRegistryValues( RTL_REGISTRY_ABSOLUTE, 
	                                 pKey->Buffer, 
	                                 Table, 
	                                 NULL, 
	                                 NULL 
	                                 );

	DigiDump( DIGIVERYLOUD, ("   Status 0x%x\n", Status ) );

#if DBG
	switch ( uType )
	{
		case REG_DWORD:
			DigiDump( DIGIVERYLOUD, ( "   REG_DWORD: 0x%x\n", pBuffer ) );
			break;	

		case REG_SZ:
			DigiDump( DIGIVERYLOUD, ( "   REG_SZ: %s\n", pBuffer ) );
			break;

		case REG_MULTI_SZ:
			DigiDump( DIGIVERYLOUD, ( "   REG_MULTI_SZ: %wZ", pBuffer ) );
			DigiDump( DIGIVERYLOUD, ( "\n" ) );
			break;

		default:
			// don't print anything if we don't know what type it is!
			break;
	}
#endif

	return( Status );
}

NTSTATUS
DigiUnicodeStringToInteger( 
	PUNICODE_STRING SrcString, 
	PULONG DestInteger )
/*++

Description:

	Converts unicode string to integer.

--*/
{
	ULONG Offset = 0;

	*DestInteger = 0;
	while ( Offset < SrcString->Length/sizeof( WCHAR ) )
	{
		if ( SrcString->Buffer[Offset] >= '0' && 
		     SrcString->Buffer[Offset] <= '9' )
		{
			*DestInteger *= 10;
			*DestInteger += SrcString->Buffer[Offset] - '0';
			Offset++;
		}
		else
			return( STATUS_UNSUCCESSFUL );
	}
	return( STATUS_SUCCESS );
}



VOID 
GetRasDialoutPorts( )
/*++

Description:

	Hack to read through RAS configuration to determine which Tapi devices (or
	Addresses) allow dialin.  This to help us know which Tapi devices we should
	indicate incoming calls on.

--*/
{
   PWCHAR RegPathBase = L"\\Registry\\Machine\\SOFTWARE\\Microsoft\\RAS\\TAPI DEVICES\\";
	ANSI_STRING ServiceName;
	UNICODE_STRING uServiceName;
	UNICODE_STRING RegPath, Address, Usage;
	ULONG AddrOffset, UsageOffset;
	ULONG AddrLen, UsageLen;
	ULONG EntriesProcessed;
	NTSTATUS Status;

	// Build service name, i.e. DigiWanX
	RtlInitAnsiString( &ServiceName, c_pszKeyX25_SVC_ServiceName );
	RtlInitUnicodeString( &uServiceName, NULL );
	RtlAnsiStringToUnicodeString( &uServiceName, &ServiceName, TRUE );

	RegPath.MaximumLength = DigiWstrLength( RegPathBase ) +
	                        uServiceName.Length +
	                        sizeof( WCHAR );	// Terminating NULL
	RegPath.Length = 0;
	RegPath.Buffer = DigiAllocMem( NonPagedPool, RegPath.MaximumLength );
	if ( !RegPath.Buffer )
		goto GetRasPortsExitAllocErr;

	// Create registry path, 
	// i.e. \Registry\Machine\SOFTWARE\Microsoft\RAS\TAPI Devices\DigiWanX
	RtlZeroMemory( RegPath.Buffer, RegPath.MaximumLength );
	RtlAppendUnicodeToString( &RegPath, RegPathBase );
	RtlAppendUnicodeStringToString( &RegPath, &uServiceName );

	// Get Address list.  It's a MULTI_SZ of form: 1000-0-1,1000-0-2, etc.
	RtlZeroMemory( &Address, sizeof( Address ) );
	Status = DigiRegQueryX( &RegPath, L"Address", REG_MULTI_SZ, &Address );
	if ( Status != STATUS_SUCCESS )
	{
		DigiDump( DIGIERRORS, ("DigiWanX: RAS Address registry key not found\n") );
		goto GetRasPortsExitErr;
	}

	// Get Usage list.  It's a MULTI_SZ of form: Client, Server, ClientAndServer.
	RtlZeroMemory( &Usage, sizeof( Usage ) );
	DigiRegQueryX( &RegPath, L"Usage", REG_MULTI_SZ, &Usage );
	if ( Status != STATUS_SUCCESS )
	{
		DigiDump( DIGIERRORS, ("DigiWanX: RAS Usage registry key not found\n") );
		goto GetRasPortsExitErr;
	}

	ASSERT( Address.Buffer && Address.Length );
	ASSERT( Usage.Buffer && Usage.Length );

	// Compare apples to apples (or WCHAR offsets to WCHAR offsets)
	AddrLen = Address.Length / sizeof( WCHAR ) - 1;
	UsageLen = Usage.Length / sizeof( WCHAR ) - 1;

	if ( Address.Buffer[AddrLen] != 0 || Usage.Buffer[UsageLen] != 0 )
	{
		// Invalid reg values, get out!
		DigiDump( DIGIERRORS, ("DigiWanX: Invalid RAS tapi devices keys!\n") );
		goto GetRasPortsExitAllocErr;
	}

	UsageOffset = AddrOffset = EntriesProcessed = 0;
	while ( AddrOffset < AddrLen && UsageOffset < UsageLen )
	{
		UNICODE_STRING AddressID, UsageString, UsageClient;
		ULONG ulAddressID;
		NTSTATUS Status;

		RtlInitUnicodeString( &UsageClient, L"Client" );

		// Get AddressID
		while ( AddrOffset < AddrLen && Address.Buffer[AddrOffset] != '-' )
			AddrOffset++;
		AddrOffset++;
		while ( AddrOffset < AddrLen && Address.Buffer[AddrOffset] != '-' )
			AddrOffset++;
		AddrOffset++;
		if ( AddrOffset >= AddrLen )
		{
			DigiDump( DIGIERRORS, ("DigiWanX: Invalid RAS Address key!\n") );
			goto GetRasPortsExitErr;
		}

		RtlInitUnicodeString( &AddressID, &Address.Buffer[AddrOffset] );
		Status = DigiUnicodeStringToInteger( &AddressID, &ulAddressID );
		if ( !NT_SUCCESS( Status ) || ulAddressID >= nCallObjects )
		{
			DigiDump( DIGIERRORS, ("DigiWanX: Bad RAS Tapi Address!\n") );
			goto GetRasPortsExitErr;
		}

		// Get Usage string
		RtlInitUnicodeString( &UsageString, &Usage.Buffer[UsageOffset] );

		// This device is Dialout only if the usage string is "Client"
		CallObjectTable[ulAddressID].bRasDialoutOnly = 
			RtlEqualUnicodeString( &UsageString, &UsageClient, TRUE ) ? TRUE : FALSE;

		// Done with this entry...
		EntriesProcessed++;

		// ...Increment for next entry
		UsageOffset += UsageString.Length/sizeof( WCHAR ) + 1;
		if ( UsageOffset >= UsageLen )
			break;

		while ( AddrOffset < Address.Length )
		{
			if ( Address.Buffer[AddrOffset++] == '\0' )
				break;
		}
		if ( AddrOffset >= AddrLen )
			break;
	}

	DigiDump( DIGIVERYLOUD, 
	          ("DigiWanX: GetRasDialoutPorts processed 0x%x entries\n", 
	          EntriesProcessed ) );
GetRasPortsExitErr:;
	DigiFreeMem( RegPath.Buffer );
GetRasPortsExitAllocErr:;
	RtlFreeUnicodeString( &uServiceName );
}
