/*++

/*****************************************************************************
 * Copyright (c) 1988-95 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 copyright work  Digi   International
 * Inc.    Ecept   as   permitted   by  federal  law,  17 USC 117, copying is
 * strictly prohibited.
 * 
 * Digi  International  Inc.  CONFIDENTIAL  -   (Digi    International    Inc.
 * Confidential  when   combined   with   the   aggregated  modules  for  this
 * product) OBJECT CODE ONLY SOURCE MATERIALS
 * 
 * (C) COPYRIGHT Digi International Inc. 1995
 * 
 * All Rights Reserved
 * 
 * US   Government   Users   Restricted   Rights   -   Use,   duplication   or
 * disclosure  restricted by GSA ADP Schedule Contract with Digi International
 * Inc.
 * 
 *****************************************************************************

Module Name:

   frtrace.c

Abstract:

   This module contains code to turn on tracing on a Digi Frame Relay adapter.
	Taken from Rik/Craig's Digi NT X25Trace code base.

Revision History:

--*/

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

#include "dxbtypes.h"
#include "dsioctl.h"
#include "dsuser.h"

#include "fr_cont.h"

//
//  VOID
//  InitializeListHead(
//      PLIST_ENTRY ListHead
//      );
//

#define InitializeListHead(ListHead) (\
    (ListHead)->Flink = (ListHead)->Blink = (ListHead))

//
//  BOOLEAN
//  IsListEmpty(
//      PLIST_ENTRY ListHead
//      );
//

#define IsListEmpty(ListHead) \
    ((ListHead)->Flink == (ListHead))

//
//  PLIST_ENTRY
//  RemoveHeadList(
//      PLIST_ENTRY ListHead
//      );
//

#define RemoveHeadList(ListHead) \
    (ListHead)->Flink;\
    {RemoveEntryList((ListHead)->Flink)}

//
//  PLIST_ENTRY
//  RemoveTailList(
//      PLIST_ENTRY ListHead
//      );
//

#define RemoveTailList(ListHead) \
    (ListHead)->Blink;\
    {RemoveEntryList((ListHead)->Blink)}

//
//  VOID
//  RemoveEntryList(
//      PLIST_ENTRY Entry
//      );
//

#define RemoveEntryList(Entry) {\
    PLIST_ENTRY _EX_Blink;\
    PLIST_ENTRY _EX_Flink;\
    _EX_Flink = (Entry)->Flink;\
    _EX_Blink = (Entry)->Blink;\
    _EX_Blink->Flink = _EX_Flink;\
    _EX_Flink->Blink = _EX_Blink;\
    }

//
//  VOID
//  InsertTailList(
//      PLIST_ENTRY ListHead,
//      PLIST_ENTRY Entry
//      );
//

#define InsertTailList(ListHead,Entry) {\
    PLIST_ENTRY _EX_Blink;\
    PLIST_ENTRY _EX_ListHead;\
    _EX_ListHead = (ListHead);\
    _EX_Blink = _EX_ListHead->Blink;\
    (Entry)->Flink = _EX_ListHead;\
    (Entry)->Blink = _EX_Blink;\
    _EX_Blink->Flink = (Entry);\
    _EX_ListHead->Blink = (Entry);\
    }

//
//  VOID
//  InsertHeadList(
//      PLIST_ENTRY ListHead,
//      PLIST_ENTRY Entry
//      );
//

#define InsertHeadList(ListHead,Entry) {\
    PLIST_ENTRY _EX_Flink;\
    PLIST_ENTRY _EX_ListHead;\
    _EX_ListHead = (ListHead);\
    _EX_Flink = _EX_ListHead->Flink;\
    (Entry)->Flink = _EX_Flink;\
    (Entry)->Blink = _EX_ListHead;\
    _EX_Flink->Blink = (Entry);\
    _EX_ListHead->Flink = (Entry);\
    }

#define WAN    0x00000001
#define LAPB   0x00000002
#define X25    0x00000004
#define FR     0x00000008

#define PrintUsage() \
   printf( "usage:\n"   \
           "   frtrace <device> <Snid>\n"   \
           "      e.g. frtrace digidxb2 A\n" );

//------------------------------------------------------------ MLZ 03/20/96 {
// Revised SXB stuff
#define XIOCTL_DATA_SIZE 512
typedef struct X_IOCTL
{
	DS_IOCTL SxbIoctl;
	UCHAR Data[512];
} X_IOCTL, *PX_IOCTL;

#define XGETMSG_CONTROL_SIZE 512
#define XGETMSG_DATA_SIZE 3300

typedef struct X_GETMSG
{
	DS_GETMSG SxbGetMsg;
	UCHAR Control[XGETMSG_CONTROL_SIZE];
	UCHAR Data[XGETMSG_DATA_SIZE];
} X_GETMSG, *PX_GETMSG;
//------------------------------------------------------------ MLZ 03/20/96 }

typedef struct _PRINTF_QUEUE_INFO
{
   LIST_ENTRY ListEntry;
   OVERLAPPED Ol;
   DWORD QueryThreadId;
//   SXB_S_PUTGETMSG SGetMsg;
	X_GETMSG XGetMsg;
//   struct strbuf GetControl;
//   struct strbuf GetData;
//   UCHAR ControlBuffer[512];
//   UCHAR DataBuffer[4096];
} PRINTF_QUEUE_INFO, *PPRINTF_QUEUE_INFO;

CHAR DeviceName[32];
HANDLE hFile;
UCHAR Snid;
HANDLE hStream = NULL;
BOOL ExitProgram;

HANDLE ProtectQueuesSemaphore;

LIST_ENTRY PrintfQueue;
HANDLE PrintfQueueEvent;

LIST_ENTRY QueryQueue;
HANDLE QueryQueueEvent;

BOOL WINAPI MyCtrlHandler( DWORD dwCtrlType );
DWORD WINAPI PrintfThread( LPVOID ThreadContext );

int StrToWStr( LPWSTR wszDest, LPSTR szSrc, LONG count );
USHORT WstrLength( IN PWSTR Wstr );

void print_fr( PX_GETMSG pXGetMsg );
void print_lmi( PUCHAR Payload, ULONG Length, fr_trc_msg_t * TraceMsg );
void print_iframe( PUCHAR Payload, ULONG Length, fr_trc_msg_t * TraceMsg );
void print_user( PUCHAR dp, int len );
void print_ip( PUCHAR dp, ULONG length );
void print_ipx( PUCHAR dp );
void make_ipx_str( PUCHAR buffer, PUCHAR addr );
void make_ip_str( PUCHAR buffer, PUCHAR addr );


DWORD WINAPI PrintfThread( LPVOID ThreadContext )
{
   PLIST_ENTRY PQueue = &PrintfQueue;
   PLIST_ENTRY QQueue = &QueryQueue;
   PPRINTF_QUEUE_INFO Info;
   fr_trc_msg_t * TraceMsg;

   while( TRUE )
   {
		//
		// Wait for notification of something to do.
		//
		WaitForSingleObject( PrintfQueueEvent, INFINITE );

		if( ExitProgram )                 // user hit ^c
		break;

		ResetEvent( PrintfQueueEvent );

		while( !IsListEmpty( PQueue ) )
		{
			//
			// Okay, we should have something to do!
			//
			Info = CONTAINING_RECORD( PQueue->Flink,
					   PRINTF_QUEUE_INFO,
					   ListEntry );

			TraceMsg = (PVOID) &Info->XGetMsg.Control;

			printf( "\n%06d %08d '%c' %s\n",
				TraceMsg->fr_trc_seq,
				TraceMsg->fr_trc_time,
				Snid,
				(TraceMsg->fr_trc_rcv) ? "Rx":"Tx" );

			print_fr( &Info->XGetMsg );

			//
			// Okay, we have printed out the information, take it off the
			// printf queue and put on the query queue.
			//

			WaitForSingleObject( ProtectQueuesSemaphore, INFINITE );

			RemoveHeadList( PQueue );

			InsertTailList( QQueue, &Info->ListEntry );

			ReleaseSemaphore( ProtectQueuesSemaphore, 1, NULL );
      }
   }

   return( TRUE );

}  // end PrintfThread


BOOL WINAPI MyCtrlHandler( DWORD dwCtrlType )
{
   BOOL bSuccess;
	FRSTRACE_Disable_t TraceDisable;

   switch( dwCtrlType )
   {
		case CTRL_C_EVENT:
		case CTRL_BREAK_EVENT:
			ExitProgram = TRUE;                    // indicate user hit ^C
			SetEvent( PrintfQueueEvent );

			bSuccess = DsPurge( hFile, hStream, DS_PURGE_ALL );
			TraceDisable.snid = Snid;

			bSuccess = DsIoctl( hFile, hStream, FRI_FRSTRACE_Disable, NULL, 0 );
			bSuccess = DsClose( hFile, hStream );

			// Always close the file handle.
			printf( "Closing device %s\n", DeviceName );
			CloseHandle( hFile );

		default:
			return( FALSE );
   }
}



void _cdecl 
main( int argc, char *argv[] )
{
   char **device=NULL;
   int errflg=0;
   BOOL bSuccess;

   OVERLAPPED Ol;
   DWORD ThreadId;
   
   int i;

   if( argc != 3 )
   {
      PrintUsage();
      return;
   }

   device = &argv[1];
   if( device == NULL )
   {
      PrintUsage();
      return;
   }
   else if( device[0] == NULL )
   {
      PrintUsage();
      return;
   }

   SetConsoleCtrlHandler( MyCtrlHandler, TRUE );

	Snid = *argv[2];

	strcpy( DeviceName, "\\\\.\\" );
   strcat( DeviceName, device[0] );

   // Make sure the byte is upper case
   if( Snid > 'a' && Snid < 'z' )
      Snid -= ('a' - 'A');

	printf( "device = %s\n", DeviceName );
	printf( "Snid   = 0x%x\n", Snid );
	printf( "Trace type = FR\n" );

   if( (hFile = CreateFile( DeviceName,
			    GENERIC_READ | GENERIC_WRITE,
			    FILE_SHARE_READ | FILE_SHARE_WRITE,
			    NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL )) == INVALID_HANDLE_VALUE )
   {
      printf( "Couldn't open %s\n", DeviceName );
      return;
   }

   ProtectQueuesSemaphore = CreateSemaphore( NULL, 1, 1, NULL );
   QueryQueueEvent = CreateEvent( NULL, TRUE, FALSE, NULL );

   PrintfQueueEvent = CreateEvent( NULL, TRUE, FALSE, NULL );

   CreateThread( NULL, 0, PrintfThread, NULL, 0, &ThreadId );

   bSuccess = DsOpen( hFile, "FR", &hStream );
   if( !bSuccess  )
   {
      printf( "DsOpen failed, GetLastError = 0x%x\n", GetLastError() );
      return;
   }
   
	{
		FRSTRACE_Enable_t TraceEnable = {0};
		TraceEnable.snid = Snid;

		bSuccess = DsIoctl( hFile, hStream, FRI_FRSTRACE_Enable, &TraceEnable, sizeof TraceEnable );
	   if( !bSuccess  )
	   {
	      printf( "DsIoctl FRI_FRSTRACE_Enable failed, GetLastError = 0x%x\n", GetLastError() );
	      return;
	   }
	   
	   if( TraceEnable.snid )
	   {
	      printf( "Fr tracing active on Snid 0x%x\n", Snid );
	   }
	}


   //
   // Okay, lets prime the QueryQueue
   //
   InitializeListHead( &QueryQueue );
   InitializeListHead( &PrintfQueue );

   Ol.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
      
   Ol.Internal     = 0;
   Ol.InternalHigh = 0;
   Ol.Offset       = 0;
   Ol.OffsetHigh   = 0;

   for( i=0; i<50; i++ )
   {
      PPRINTF_QUEUE_INFO Info;

      Info = (PPRINTF_QUEUE_INFO)malloc( sizeof(PRINTF_QUEUE_INFO) );

		ZeroMemory( Info, sizeof( PRINTF_QUEUE_INFO ) );

		Info->XGetMsg.SxbGetMsg.pControl = &Info->XGetMsg.Control;
      Info->XGetMsg.SxbGetMsg.cbMaxControl = sizeof Info->XGetMsg.Control;
		Info->XGetMsg.SxbGetMsg.pData = &Info->XGetMsg.Data;
		Info->XGetMsg.SxbGetMsg.cbMaxData    = sizeof Info->XGetMsg.Data;
      Info->XGetMsg.SxbGetMsg.hStream = hStream;
      Info->XGetMsg.SxbGetMsg.Priority = DS_PRIORITY_ANY;
      
      Info->Ol.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );

      WaitForSingleObject( ProtectQueuesSemaphore, INFINITE );
      InsertTailList( &QueryQueue, &Info->ListEntry );
      ReleaseSemaphore( ProtectQueuesSemaphore, 1, NULL );
   }
   
   for( ;; )
   {
      PPRINTF_QUEUE_INFO Info;

      //
      // Okay, we now start getting the trace info from the controller.
      //
      while( !IsListEmpty( &QueryQueue ) )
      {
			DWORD cbWritten;
			Info = CONTAINING_RECORD( QueryQueue.Flink, PRINTF_QUEUE_INFO, ListEntry );

			bSuccess = DeviceIoControl( hFile, IOCTL_DS_GETMSG,
							(LPVOID)&Info->XGetMsg, sizeof(X_GETMSG),
							(LPVOID)&Info->XGetMsg, sizeof(X_GETMSG), 
							&cbWritten, NULL );

			if( ExitProgram )      // User  hit ^C
				break;

			WaitForSingleObject( ProtectQueuesSemaphore, INFINITE );
			RemoveHeadList( &QueryQueue );
			InsertTailList( &PrintfQueue, &Info->ListEntry );
			ReleaseSemaphore( ProtectQueuesSemaphore, 1, NULL );

			SetEvent( PrintfQueueEvent );
      }

      if( ExitProgram )         // user hit ^C
			break;

      //
      // The QueryQueue went empty, allocate another PRINTF_QUEUE_INFO for use.
      //

      Info = (PPRINTF_QUEUE_INFO)malloc( sizeof(PRINTF_QUEUE_INFO) );

		ZeroMemory( Info, sizeof( PRINTF_QUEUE_INFO ) );
		Info->XGetMsg.SxbGetMsg.pControl = &Info->XGetMsg.Control;
      Info->XGetMsg.SxbGetMsg.cbMaxControl = sizeof Info->XGetMsg.Control;
		Info->XGetMsg.SxbGetMsg.pData = &Info->XGetMsg.Data;
		Info->XGetMsg.SxbGetMsg.cbMaxData = sizeof Info->XGetMsg.Data;
      Info->XGetMsg.SxbGetMsg.hStream = hStream;
      Info->XGetMsg.SxbGetMsg.Priority = DS_PRIORITY_ANY;
      
      Info->Ol.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );

      WaitForSingleObject( ProtectQueuesSemaphore, INFINITE );
      InsertTailList( &QueryQueue, &Info->ListEntry );
      ReleaseSemaphore( ProtectQueuesSemaphore, 1, NULL );
   } // end for(;;)

}  // end main



void print_fr( PX_GETMSG pXGetMsg )
{
   ULONG r;
   int len = 0;
   char  *dp;
	fr_trc_msg_t * pTraceMsg;

   dp = (PVOID)&pXGetMsg->Data;
     
   for ( r = 0; r < pXGetMsg->SxbGetMsg.cbData; r++ )
   {
		printf( "%02x",(unsigned char)dp[r]);
		if( ++len == 78 )
		{
			printf( "\n");
			len = 0;
		}
   }

   printf( "\n");
	
	pTraceMsg = (PVOID)&pXGetMsg->Control;
	
	if ( pTraceMsg->fr_trc_dlci == ( pTraceMsg->fr_trc_lmi & 0x3ff ) )
		print_lmi( dp, pXGetMsg->SxbGetMsg.cbData, pTraceMsg );
	else
		print_iframe( dp, pXGetMsg->SxbGetMsg.cbData, pTraceMsg );
   
   return;
}  // end print_fr

void 
print_iframe( 
	PUCHAR Payload, 
	ULONG Length, 
	fr_trc_msg_t * TraceMsg 
	)
{
	USHORT Dlci;
	PUCHAR Packet;
	ULONG oui;
	USHORT pid;

	Dlci = ( ( (USHORT)Payload[0] & 0xfc ) << 2 ) | 
			 ( ( (USHORT)Payload[1] & 0xf0 ) >> 4 );

	printf( "DLCI=%d C/R=%d ", Dlci, (Payload[0] & 2) > 1) ;
	printf( "FECN=%d BECN=%d DE=%d EA=%d\n", 
						Payload[1] & 8 ? 1 : 0 ,
						Payload[1] & 4 ? 1 : 0 ,
						Payload[1] & 2 ? 1 : 0 ,
						Payload[1] & 1 ? 1 : 0 );

	if ( Dlci != TraceMsg->fr_trc_dlci )
		printf( "*** DLCI does not match internal dlci (%d) ***\n",
		TraceMsg->fr_trc_dlci);

	if ( !(TraceMsg->fr_trc_opts & 0x2000) ) 
	{
		/* Not IETF encapsulated frame... just dump the data */
		printf( "DXE DATA\n");
		print_user( Payload+2, Length-2 );
		return;
	}

	printf( "%sFRAME\nEncapsulated Packet Type: ",
	(Payload[2] == 0x03 ? "UI-" : "RAW I-"));

	/*
	 * Deep breath... and... decode!
	 * Skip possible padding, start at NLPID
	 */
	Packet = (PUCHAR) (Payload[3] ? Payload+3 : Payload+4);
	Length -= (Payload[3] ? 3 : 4);

	switch (Packet[0]) {
	case 0x08:	/* Q.933 */
		printf( "Q.933\n");
		print_user(Packet+1, Length-1);
		break;
	case 0x81:	/* CLNP */
		printf( "ISO CLNP\n");
		print_user(Packet+1, Length-1);
		break;
	case 0x82:	/* ESIS */
		printf( "ISO ESIS\n");
		print_user(Packet+1, Length-1);
		break;
	case 0x83:	/* ISIS */
		printf( "ISO ISIS\n");
		print_user(Packet+1, Length-1);
		break;
	case 0xCC:	/* IP */
		printf( "IP\n");
		print_ip(Packet+1, Length-1);
		break;
	case 0x80:	/* SNAP */
		printf( "SNAP  OUI: %02x-%02x-%02x    PID: %02x-%02x\n",
			Packet[1], Packet[2], Packet[3], Packet[4], Packet[5]);
		oui = (((ULONG) Packet[1]) << 16) |
				(((ULONG) Packet[2]) << 8) |
				((ULONG) Packet[3]);
		pid = (((USHORT) Packet[4]) << 8) | ((USHORT) Packet[5]);
		switch (oui) 
		{
			case 0x000000:	/* Ethertype */
				switch (pid) 
				{
					case 0x8137:	/* IPX */
						printf( "IPX Ethertype\n");
						print_ipx(Packet+6);
						print_user(Packet+6, Length-6);
						break;
					default:
						printf( "Unknown Ethertype\n");
						print_user(Packet+6, Length-6);
						break;
				}
				break;

			case 0x0080C2:	/* 802.1 type */
				switch (pid) 
				{
					case 0x0001:
					case 0x0007:
						printf( "802.3/Ethernet\n");
						print_user(Packet+6, Length-6);
						break;
					case 0x0002:
					case 0x0008:
						printf( "802.4\n");
						print_user(Packet+6, Length-6);
						break;
					case 0x0003:
					case 0x0009:
						printf( "802.5\n");
						print_user(Packet+6, Length-6);
						break;
					case 0x0004:
					case 0x000A:
						printf( "FDDI\n");
						print_user(Packet+6, Length-6);
						break;
					case 0x000B:
						printf( "802.6\n");
						print_user(Packet+6, Length-6);
						break;
					case 0x000E:
						printf( "BPDU\n");
						print_user(Packet+6, Length-6);
						break;
					case 0x000D:
						printf( "Packet Fragment:   Sequence Number: 0x%04x   Offset: %4d   Final -> %s\n",
						((u_short) Packet[6] << 8) | ((u_short) Packet[7]),
						((((u_short) Packet[8] & 0x7) << 8) | ((u_short) Packet[9]))*32,
						Packet[8] & 0x80 ? "Yes" : "No");
						print_user(Packet+10, Length-10);
						break;
				}
				break;

			default:
				printf( "Unknown OUI\n");
				print_user(Packet+6, Length-6);
				break;
		}
		break;

	default:
		printf( "UNKNOWN (%02x)\n", Packet[0]);
		print_user(Packet+1, Length-1);
		break;
	}

}	// end print_iframe



void 
print_lmi( 
	PUCHAR payload, 
	ULONG length, 
	fr_trc_msg_t * TraceMsg 
	)
{
	u_short dlci = (((u_short)payload[0] & 0xfc) << 2) | (((u_short)payload[1] & 0xF0) >> 4)  ;

	printf( "DLCI=%d C/R=%d ", dlci, (payload[0] & 2) > 1) ;
	printf( "FECN=%d BECN=%d DE=%d EA=%d\n", 
						payload[1] & 8 ? 1 : 0 ,
						payload[1] & 4 ? 1 : 0 ,
						payload[1] & 2 ? 1 : 0 ,
						payload[1] & 1 ? 1 : 0 ) ;


	printf( "Un-Number Frame(Control)=%d\n",payload[2] );
	printf( "Protocol Discriminator=%d\n",payload[3] );
	printf( "Call Reference=%d\n",payload[4] );

	switch( payload[5] )
	{
		case 0x7D :
			printf( "LMI Status Message\n") ;
//			fr_print_lmi_status( &payload[6], length - 6 ) ;
			break ;

		case 0x75 :
			printf( "LMI Status Enquiry\n" ) ;
//			fr_print_lmi_status_enquiry( &payload[6] );
			break ;

		case 0x7B :
			printf( "LMI Update Status\n" ) ;
//			fr_print_lmi_update_status( &payload[6], length - 6 ) ;
			break ;

		default:
			break ;	
	}
}	// end print_lmi


void
print_ip(PUCHAR dp, ULONG length)
{
	u_short header_size;
	u_short packet_size;
	u_short true_packet_size = (u_short) length;
	u_short min;
	char src_addr[16], dst_addr[16];

	header_size = (dp[0] & 0x0F) * 4;
	packet_size = ((u_short) dp[2] << 8) | ((u_short) dp[3]);
	make_ip_str(src_addr, dp+12);
	make_ip_str(dst_addr, dp+16);

	min = ( header_size < 20 ) ? header_size : 20;

	if (true_packet_size < min ) 
	{
		printf(  "Garbaged IP packet\n");
		return;
	}

	if (true_packet_size != packet_size) {
		printf(  "IP packet has incorrect size values... Adjusting data size to %d\n", (packet_size = true_packet_size) - header_size);
	}

	printf(  "Header Size: %d bytes;  Source: %s     Dest: %s\n",
	header_size, src_addr, dst_addr);

	print_user(dp, header_size);

	printf(  "IP Data:\n");

	print_user(dp+header_size, packet_size - header_size);
}


void
print_ipx( PUCHAR dp )
{
	u_short length;
	char src_addr[30], dst_addr[30];

	length = (((u_short) dp[2]) << 8) | ((u_short) dp[3]);
	make_ipx_str(dst_addr, dp+6);
	make_ipx_str(src_addr, dp+18);

	printf(  
		"Packet Length: %d\nSource: %s\nDest: %s\n", 
		length,
		src_addr, 
		dst_addr
		);

	print_user(dp, 30);

	if (length > 30) 
	{
		printf(  "Data:\n" );
		print_user(dp+30, length-30);
	} 
	else
		printf( "No data in packet\n" );
}



void
print_user(PUCHAR dp, int len )
{
	int i, k;
	
	k = 0 ;
	while(k <= len)
	{
		if((len - k) > 16)
		{
			for(i = 0 ; i < 16 ; i++)
			{
				printf( "%02x ",dp[i]) ;
			    k++ ;
			}
			
			printf( " ") ;
	
			for(i = 0 ; i < 16 ; i++)
			{
				if(dp[i] >= 0x20 && dp[i] <= 0x7E)
					printf( "%c",dp[i]) ;
				else
					printf( ".") ;
			}
			printf( "\n") ;
			dp += i ;
		}
		else
		{
			for(i = 0; i < (len - k); i++)
			{
				printf( "%02x ",dp[i]) ;
			}

			for(i = 0; i < (16 - (len - k)); i++) 
				printf( "   ") ;			
			
			printf( " ") ;
	
			for(i = 0 ; i < (len - k); i++)
			{
				if(dp[i] >= 0x20 && dp[i] <= 0x7E)
					printf( "%c",dp[i]) ;
				else
					printf( ".") ;
			}
			printf( "\n") ;
			return ;
		}
	}	
}

void
make_ip_str( PUCHAR buffer, PUCHAR addr)
{
	/* buffer MUST be 16 characters or greater */
	/* addr must point to the first byte of the IP address */

	sprintf(buffer, "%3d.%3d.%3d.%3d", addr[0], addr[1], addr[2], addr[3]);
}

void
make_ipx_str(PUCHAR buffer, PUCHAR addr)
{
	/* buffer MUST be 27 characters or greater */
	/* addr must point to the first byte of the IPX address */

	/*
	 * This is a long sprintf, yes, but it is hopefully also
	 * endian independent
	 */
	sprintf(buffer, "%02x%02x%02x%02x/%02x%02x%02x%02x%02x%02x/%02x%02x",
	addr[0], addr[1], addr[2], addr[3],
	addr[4], addr[5], addr[6], addr[7], addr[8], addr[9],
	addr[10], addr[11]);
}




int StrToWStr( LPWSTR wszDest, LPSTR szSrc, LONG count )
{
   LONG i;

   for( i = 0; i < count; i++ )
   {
      wszDest[i] = szSrc[i];
   }

   return( i-1 );
}



USHORT WstrLength( IN PWSTR Wstr )   
/*++

Routine Description:

   Determines the number of bytes used by the passed in Wstr

Arguments:

   Wstr - Pointer to wide character string.

Return Value:

   Length, in bytes.
   
--*/
{
   USHORT Length=0;

   while( *Wstr++ )
   {
      Length += sizeof(WCHAR);
   }
   return( Length );
}  // end DigiWstrLength
