/*++
*******************************************************************************
* Copyright (c) 1996-99 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:

   config.cpp

Abstract:

   This file contains nearly all the lower edge code which knows about and 
   communicates with adapters using the DStreams API.

Author:

   Rodger Bernstein

--*/

#include "stdafx.h"
#include "Diagnostic.h"
#include "DataSource.h"
#include "DiagDoc.h"
#include "StatDoc.h"

#include "TraceStatView.h"
#include "SessionStatView.h"
#include "FRLineStatView.h"
#include "LAPBLineView.h"
#include "X25LineStatView.h"
#include "FRVCStatView.h"
#include "X25VCStatView.h"
#include "tracer.h"
#include "IsdnTrace.h"
#include "hlc.h"
#include "prime.h"
#include "FRDataSource.h"
#include "PPPDataSource.h"

#include "m32api.h"

#include "SummaryDlg.h"
#include "filter.h"
#include <math.h>
#include <typeInfo.h>

/////////////////////////////////////////////////////////////////////////////
class CFRTraceSheet : public CTraceSheet
{
	DECLARE_DYNAMIC(CFRTraceSheet)
	
	CTraceStatView m_BufferStats;
	CFRVCStatView m_FRVCStats;
	CFRLineStatView m_FRLineStats;

	virtual void AddTracePages();	

// Construction
public:
	CFRTraceSheet(CTraceData *, UINT nIDCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0);
	CFRTraceSheet(CTraceData *, LPCTSTR pszCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0);

// Operations
public:

// Overrides

// Implementation
public:
	virtual ~CFRTraceSheet() {};

	// Generated message map functions
protected:
	//{{AFX_MSG(CFRTraceSheet)
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////
// CFRTraceSheet

IMPLEMENT_DYNAMIC(CFRTraceSheet, CTraceSheet)

CFRTraceSheet::CFRTraceSheet(CTraceData *td, UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage)
	:CTraceSheet(td, nIDCaption, pParentWnd, iSelectPage)
{
}

CFRTraceSheet::CFRTraceSheet(CTraceData *td, LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage)
	:CTraceSheet(td, pszCaption, pParentWnd, iSelectPage)
{
}


BEGIN_MESSAGE_MAP(CFRTraceSheet, CTraceSheet)
	//{{AFX_MSG_MAP(CFRTraceSheet)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CFRTraceSheet message handlers


void CFRTraceSheet::AddTracePages()
{
	AddPage(&m_BufferStats);
	AddPage(&m_FRVCStats);
	AddPage(&m_FRLineStats);
}

/////////////////////////////////////////////////////////////////////////////
class CX25TraceSheet : public CTraceSheet
{
	DECLARE_DYNAMIC(CX25TraceSheet)
	
	CTraceStatView m_BufferStats;
	CSessionStatView m_X25Sessions;
	CX25VCStatView m_X25VCStats;
	CX25LineStatView m_X25LineStats;
	CLAPBLineView m_LAPBLineStats;

	virtual void AddTracePages();	

// Construction
public:
	CX25TraceSheet(CTraceData *, UINT nIDCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0);
	CX25TraceSheet(CTraceData *, LPCTSTR pszCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0);

// Operations
public:

// Overrides

// Implementation
public:
	virtual ~CX25TraceSheet() {};

	// Generated message map functions
protected:
	//{{AFX_MSG(CX25TraceSheet)
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////
// CX25TraceSheet

IMPLEMENT_DYNAMIC(CX25TraceSheet, CTraceSheet)

CX25TraceSheet::CX25TraceSheet(CTraceData *td, UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage)
	:CTraceSheet(td, nIDCaption, pParentWnd, iSelectPage)
{
}

CX25TraceSheet::CX25TraceSheet(CTraceData *td, LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage)
	:CTraceSheet(td, pszCaption, pParentWnd, iSelectPage)
{
}

BEGIN_MESSAGE_MAP(CX25TraceSheet, CTraceSheet)
	//{{AFX_MSG_MAP(CX25TraceSheet)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CX25TraceSheet message handlers

void CX25TraceSheet::AddTracePages()
{
	AddPage(&m_BufferStats);
	AddPage(&m_X25Sessions);
	AddPage(&m_X25VCStats);
	AddPage(&m_X25LineStats);
	AddPage(&m_LAPBLineStats);
}

IMPLEMENT_DYNCREATE(CAdapter, CObject);
IMPLEMENT_DYNCREATE(CSXBAdapter, CAdapter);
//IMPLEMENT_DYNCREATE(C570LLDAdapter, CAdapter);

IMPLEMENT_SERIAL(CTraceData, CObject, 3);
IMPLEMENT_SERIAL(CFRTraceData, CTraceData, 3);
IMPLEMENT_SERIAL(CX25TraceData, CTraceData, 3);

void AFXAPI SerializeElements(CArchive& ar, sDriverInfo *pElements, int nCount)
{
	for (int i = 0; i < nCount; i++)
		if (ar.IsStoring()) {
			ar << pElements[i].m_DriverName;
			ar << pElements[i].m_DriverVersion;
			ar << pElements[i].m_DriverBuildNumber;
			ar << pElements[i].m_DriverDate;
			}
		else {
			ar >> pElements[i].m_DriverName;
			ar >> pElements[i].m_DriverVersion;
			ar >> pElements[i].m_DriverBuildNumber;
			ar >> pElements[i].m_DriverDate;
			}		
}

void AFXAPI SerializeElements(CArchive& ar, sSession **pElements, int nCount)
{
	for (int i = 0; i < nCount; i++)
		if (ar.IsStoring()) {
			ar << pElements[i]->EndPoint;
			ar << pElements[i]->Frames[0] << pElements[i]->Frames[1];
			ar << pElements[i]->Remote;
			}
		else {
			pElements[i] = new sSession;

			ar >> pElements[i]->EndPoint;
			ar >> pElements[i]->Frames[0] >> pElements[i]->Frames[1];
			ar >> pElements[i]->Remote;
			}		
}

void CTraceData::Serialize(CArchive& ar)
{
	CObject::Serialize(ar);

	if (ar.IsStoring()) {
		ar << m_DateTime;
		ar << m_Machine << m_DataSourceName << m_Protocol;
		ar << m_FirmwareVersion << m_Comment;
		ar << m_FirstTimeStamp;
		ar << m_Elapsed;

// stuff in trace stats
		for (short i = 0; i < 2; i++) {
			ar << FC[i] << BC[i];
			ar << LU[i].Min << LU[i].Cur << LU[i].Max;
			ar << FPS[i].Min << FPS[i].Cur << FPS[i].Max;
			ar << Th[i].Min << Th[i].Cur << Th[i].Max;
			}
		}
	else {
		ar >> m_DateTime;
		ar >> m_Machine >> m_DataSourceName >> m_Protocol;
		ar >> m_FirmwareVersion >> m_Comment;
		ar >> m_FirstTimeStamp;
		ar >> m_Elapsed;
		m_ScratchFrame = CreateFrame();

// stuff in trace stats
		for (short i = 0; i < 2; i++) {
			ar >> FC[i] >> BC[i];
			ar >> LU[i].Min >> LU[i].Cur >> LU[i].Max;
			ar >> FPS[i].Min >> FPS[i].Cur >> FPS[i].Max;
			ar >> Th[i].Min >> Th[i].Cur >> Th[i].Max;
			}
		}

// stuff in trace stats
	m_Sessions.Serialize(ar);

	m_Drivers.Serialize(ar);

	m_FrameBuffer.Serialize(ar);
}

void CFRTraceData::Serialize(CArchive& ar)
{
	CTraceData::Serialize(ar);

	sFRVCStats *vc = NULL;
	DWORD ch = 0;

	if (ar.IsStoring()) {
		ar << DWORD(m_VCStats.GetCount());

		POSITION pos = m_VCStats.GetStartPosition(); 

		while (pos) {
			m_VCStats.GetNextAssoc(pos, ch, vc);

			ar << ch;
			ar.Write(vc, sizeof(sFRVCStats));
			}

		ar << (DWORD)m_LMIType;
		for (short i = 0; i < 2; i++) {
			ar << DE[i] << BECN[i] << FECN[i] << LMISM[i] << LMISE[i] << LMIUS[i];
			ar << IP[i] << IPX[i] << FRF9[i] << Ethernet[i] << Other[i];
			}
		}
	else {
		int NumVC;

		ar >> NumVC;
		for (; NumVC > 0; NumVC--) {
			vc = new sFRVCStats;

			ar >> ch;
			ar.Read(vc, sizeof(sFRVCStats));
			m_VCStats.SetAt(ch, vc);
			}

		ar >> (DWORD&)m_LMIType;
		for (short i = 0; i < 2; i++) {
			ar >> DE[i] >> BECN[i] >> FECN[i] >> LMISM[i] >> LMISE[i] >> LMIUS[i];
			ar >> IP[i] >> IPX[i] >> FRF9[i] >> Ethernet[i] >> Other[i];
			}
		}
}

void CX25TraceData::Serialize(CArchive& ar)
{
	CTraceData::Serialize(ar);

	m_ChannelPVC.Serialize(ar);

	sX25VCStats *vc = NULL;
	DWORD ch = 0;

	if (ar.IsStoring()) {
		ar << (DWORD)(m_VCStats.GetCount());

		POSITION pos = m_VCStats.GetStartPosition(); 

		while (pos) {
			m_VCStats.GetNextAssoc(pos, ch, vc);

			ar << ch;
			ar.Write(vc, sizeof(sX25VCStats));
			}

		ar << m_Modulus;
		ar << (DWORD)m_DXE;
		for (short i = 0; i < 2; i++) {
			ar << LAPBData[i] << LAPBRR[i] << LAPBRNR[i] << LAPBREJ[i] << LAPBFRMR[i];
			ar << LAPBUA[i] << LAPBDM[i] << LAPBDISC[i] << LAPBSABM[i] << LAPBSABME[i];

			ar << X25Data[i] << X25RR[i] << X25RNR[i] << X25Reject[i] << X25CallRq[i];
			ar << X25CallAc[i] << X25ClrRq[i] << X25ClrCf[i] << X25RstRq[i] << X25RstCf[i];
			ar << X25RrtRq[i] << X25RrtCf[i] << X25Int[i] << X25IntCf[i] << X25RegRq[i];
			ar << X25RegCf[i] << X25Diag[i] << X25Unkn[i];
			}
		}
	else {
		int NumVC;

		ar >> NumVC;
		for (; NumVC > 0; NumVC--) {
			vc = new sX25VCStats;

			ar >> ch;
			ar.Read(vc, sizeof(sX25VCStats));
			m_VCStats.SetAt(ch, vc);
			}

		ar >> m_Modulus;
		ar >> (DWORD&)m_DXE;
		for (short i = 0; i < 2; i++) {
			ar >> LAPBData[i] >> LAPBRR[i] >> LAPBRNR[i] >> LAPBREJ[i] >> LAPBFRMR[i];
			ar >> LAPBUA[i] >> LAPBDM[i] >> LAPBDISC[i] >> LAPBSABM[i] >> LAPBSABME[i];

			ar >> X25Data[i] >> X25RR[i] >> X25RNR[i] >> X25Reject[i] >> X25CallRq[i];
			ar >> X25CallAc[i] >> X25ClrRq[i] >> X25ClrCf[i] >> X25RstRq[i] >> X25RstCf[i];
			ar >> X25RrtRq[i] >> X25RrtCf[i] >> X25Int[i] >> X25IntCf[i] >> X25RegRq[i];
			ar >> X25RegCf[i] >> X25Diag[i] >> X25Unkn[i];
			}
		}
}

CTypedPtrMap<CMapStringToPtr,CString,CRuntimeClass *> CAdapter::m_Registry;

#define	ElementCount(a)	(sizeof (a) / sizeof (a)[0])

BOOL CAdapter::LoadConfig(const CString& strDeviceName, const CString& Option, const HKEY hkParams, const CString& ParamsPath )
{
	DWORD NumLines;
	if ( RegGetDWord(hkParams, DXB_REGVAL_NUMBEROFLINES, &NumLines) != ERROR_SUCCESS )
		return FALSE;

	CString VAdapterNumber;
	RegGetString( hkParams, DXB_REGVAL_NDIS_ADAPTER_INSTANCE, VAdapterNumber ) ;

	for (DWORD i = 0; i < NumLines; i++) 
	{
		HKEY hkLine;
		CString LineKeyName;
		LineKeyName.Format( "%s\\%s%c", ParamsPath, DXB_REGKEY_LINE_BASE, i + DXB_REGKEY_LINE_ENUM_FIRST );

 		if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, LineKeyName, &hkLine) != ERROR_SUCCESS)
			return FALSE;

		CDataSource *newDS = NULL;

		CString Protocol;
		if ( ERROR_SUCCESS == RegGetString( hkLine, DXB_REGVAL_PROTOCOL, Protocol ) )
		{
			// PRIme card?  Use CSU-based datasource
			if ( strcmp(Option, DXB_REGVAL_OPTION_ENUM_PRIME) == 0 )
			{
				if (strcmp(Protocol, DXB_REGVAL_PROTOCOL_ENUM_PRI) == 0)
					newDS = new CPrimeDataSource(hkLine, strDeviceName, Protocol);
				else if (strcmp(Protocol, DXB_REGVAL_PROTOCOL_ENUM_E1) == 0)
					newDS = new CPrimeDataSource(hkLine, strDeviceName, Protocol);
				else if (strcmp(Protocol, DXB_REGVAL_PROTOCOL_ENUM_E1R2) == 0)
					newDS = new CPrimeDataSource(hkLine, strDeviceName, Protocol);
				else if (strcmp(Protocol, DXB_REGVAL_PROTOCOL_ENUM_FRAMERELAY) == 0)
					newDS = new CFRCsuDataSource(hkLine, strDeviceName);
				else if (strcmp(Protocol, DXB_REGVAL_PROTOCOL_ENUM_PPP) == 0)
					newDS = new CT1PPPDataSource(hkLine, strDeviceName);
				else
					goto NextLine;
			}

			// Otherwise, it must be a sync-line (e.g. RS-232) card
			else if (strcmp(Protocol, DXB_REGVAL_PROTOCOL_ENUM_X25) == 0)
				newDS = new CX25DataSource(hkLine, strDeviceName);
			else if (strcmp(Protocol, DXB_REGVAL_PROTOCOL_ENUM_FRAMERELAY) == 0)
				newDS = new CFRDataSource(hkLine, strDeviceName);
			else if (strcmp(Protocol, DXB_REGVAL_PROTOCOL_ENUM_PRI) == 0)
				newDS = new CHlcPriDataSource(hkLine, strDeviceName, Protocol, i);
			else if (strcmp(Protocol, DXB_REGVAL_PROTOCOL_ENUM_E1) == 0)
				newDS = new CHlcPriDataSource(hkLine, strDeviceName, Protocol, i);
			else if (strcmp(Protocol, DXB_REGVAL_PROTOCOL_ENUM_E1R2) == 0)
				newDS = new CHlcE1R2DataSource(hkLine, strDeviceName, Protocol, i);
			else if (strcmp(Protocol, DXB_REGVAL_PROTOCOL_ENUM_T1) == 0)
				newDS = new CHlcCt1DataSource(hkLine, strDeviceName, Protocol);
			else if (strcmp(Protocol, DXB_REGVAL_PROTOCOL_ENUM_BRI) == 0)
				newDS = new CHlcBriDataSource(hkLine, strDeviceName, Protocol);
			else if (strcmp(Protocol, DXB_REGVAL_PROTOCOL_ENUM_POTS) == 0)
				newDS = new CHlcModemDataSource(hkLine, strDeviceName, Protocol);
			else if (strcmp(Protocol, DXB_REGVAL_PROTOCOL_ENUM_SNA) == 0)
				newDS = new CSnaDataSource(hkLine, strDeviceName);
			else if (strcmp(Protocol, DXB_REGVAL_PROTOCOL_ENUM_PPP) == 0)
				newDS = new CPPPDataSource(hkLine, strDeviceName);
			else // no data source will be created for a non-supported protocol
				goto NextLine;
		}

		{
			typedef struct
			{
				const char const * match;
				const char const * title;
			}
			MATCH;

			CString Title("Unknown");
			CString Identity("Unknown");
			if (RegGetString( hkParams, DXB_REGVAL_IDENTITY, Identity) == ERROR_SUCCESS)
			{
				MATCH const identity_table[] =
				{
					{ DXB_REGVAL_ADAPTER_ENUM_SYNCPORT_ISA, "SyncPort ISA" },
					{ DXB_REGVAL_ADAPTER_ENUM_SYNCPORT_PCI, "SyncPort PCI" },
					{ DXB_REGVAL_ADAPTER_ENUM_PRIME_ISA, "DataFire PRIme ISA" },
					{ DXB_REGVAL_ADAPTER_ENUM_PRIME_T1_PCI, "DataFire PRIme T1 PCI" },
					{ DXB_REGVAL_ADAPTER_ENUM_PRIME_E1_PCI, "DataFire PRIme E1 PCI" },
					{ DXB_REGVAL_ADAPTER_ENUM_HOUSTON_T1_SINGLE, "DataFire RAS PT1" },
					{ DXB_REGVAL_ADAPTER_ENUM_HOUSTON_T1_DUAL, "DataFire RAS PT2" },
					{ DXB_REGVAL_ADAPTER_ENUM_HOUSTON_E1_SINGLE, "DataFire RAS PE1" },
					{ DXB_REGVAL_ADAPTER_ENUM_HOUSTON_E1_DUAL, "DataFire RAS PE2" },
					{ DXB_REGVAL_ADAPTER_ENUM_LONDON_ST, "DataFire RAS B4ST" },
					{ DXB_REGVAL_ADAPTER_ENUM_LONDON_U, "DataFire RAS B4U" },
					{ DXB_REGVAL_ADAPTER_ENUM_CHASKA_4, "AccelePort RAS A4" },
					{ DXB_REGVAL_ADAPTER_ENUM_CHASKA_8, "AccelePort RAS A8" },
					{ DXB_REGVAL_ADAPTER_ENUM_CHASKA_40, "AccelePort RAS A4" },
					{ DXB_REGVAL_ADAPTER_ENUM_CHASKA_41, "AccelePort RAS A4" },
					{ DXB_REGVAL_ADAPTER_ENUM_CHASKA_42, "AccelePort RAS A4" },
					{ DXB_REGVAL_ADAPTER_ENUM_CHASKA_43, "AccelePort RAS A4" },
					{ DXB_REGVAL_ADAPTER_ENUM_CHASKA_44, "AccelePort RAS A4" },
					{ DXB_REGVAL_ADAPTER_ENUM_CHASKA_45, "AccelePort RAS A4" },
					{ DXB_REGVAL_ADAPTER_ENUM_CHASKA_80, "AccelePort RAS A8" },
					{ DXB_REGVAL_ADAPTER_ENUM_CHASKA_81, "AccelePort RAS A8" },
					{ DXB_REGVAL_ADAPTER_ENUM_CHASKA_82, "AccelePort RAS A8" },
					{ DXB_REGVAL_ADAPTER_ENUM_CHASKA_83, "AccelePort RAS A8" },
					{ DXB_REGVAL_ADAPTER_ENUM_CHASKA_84, "AccelePort RAS A8" },
					{ DXB_REGVAL_ADAPTER_ENUM_CHASKA_85, "AccelePort RAS A8" },
					{ DXB_REGVAL_ADAPTER_ENUM_CLYDE_NONCSU_4, "DataFire Sync 2000 4P" },
					{ DXB_REGVAL_ADAPTER_ENUM_CLYDE_NONCSU_2, "DataFire Sync 2000 2P" },
					{ DXB_REGVAL_ADAPTER_ENUM_CLYDE_NONCSU_603_4, "DataFire Sync 2000 4P+" },
					{ DXB_REGVAL_ADAPTER_ENUM_CLYDE_NONCSU_603_2, "DataFire Sync 2000 2P" },
					{ DXB_REGVAL_ADAPTER_ENUM_SYNC570_PCI_2PORT, "DataFire SYNC/570i 2P PCI" },
					{ DXB_REGVAL_ADAPTER_ENUM_SYNC570_PCI_4PORT, "DataFire SYNC/570i 4P PCI" },
					{ DXB_REGVAL_ADAPTER_ENUM_SYNC570_ISA_2PORT, "DataFire SYNC/570i 2P ISA" },
					{ DXB_REGVAL_ADAPTER_ENUM_SYNC570_ISA_4PORT, "DataFire SYNC/570i 4P ISA" },
					{ DXB_REGVAL_ADAPTER_ENUM_ACCELEPORT_2R_2000, "AccelePort 2P 2000" },
					{ DXB_REGVAL_ADAPTER_ENUM_ACCELEPORT_4R_2000, "AccelePort 4P 2000" },
					{ DXB_REGVAL_ADAPTER_ENUM_ACCELEPORT_8R_2000, "AccelePort 8P 2000" },
					{ DXB_REGVAL_ADAPTER_ENUM_ACCELEPORT_16R_2000, "AccelePort 16P 2000" },
				};

				for (int t = 0; t < ElementCount(identity_table); t++)
				{
					if (strcmp(Identity, identity_table[t].match) == 0)
					{
						Title = identity_table[t].title;
						break;
					}
				}

			}
			else
			{
				/* 
					This was added because identity doesn't exist in NT4.  The old way we searched was
					by option.  If we want to get any more detailed, we will need to add an identity to
					NT4, or we will have to search on a different key (if one exists).
				*/
				MATCH const option_table[] =
				{
					{ DXB_REGVAL_OPTION_ENUM_SYNCPORT, "SyncPort" },
					{ DXB_REGVAL_OPTION_ENUM_PRIME, "DataFire PRIme" },
					{ DXB_REGVAL_OPTION_ENUM_HOUSTON, "DataFire RAS P/T/E" },
					{ DXB_REGVAL_OPTION_ENUM_LONDON, "DataFire RAS B Series" },
					{ DXB_REGVAL_OPTION_ENUM_CHASKA, "Acceleport RAS Series" },
					{ DXB_REGVAL_OPTION_ENUM_CLYDE, "DataFire Sync 2000" },
					{ DXB_REGVAL_OPTION_ENUM_SYNC570, "Sync 570" },
				};

				for (int t = 0; t < ElementCount(option_table); t++)
				{
					if (strcmp(Identity, option_table[t].match) == 0)
					{
						Title = option_table[t].title;
						break;
					}
				}
			}

			newDS->SetName( Title, VAdapterNumber, i+1 );
		}

		newDS->SetLineKeyName(LineKeyName);
		newDS->SetParametersKeyName(ParamsPath);
		newDS->SetLineNumber(i);
		newDS->GetStatus(newDS->GetDefaultFirmwareDevice());

		m_DataSource.Add(newDS);
		LoadSummaryInfo(newDS);

		{
			CString FirmwareVersion = "Unknown";
			if ( strcmp( Option, DXB_REGVAL_OPTION_ENUM_SYNCPORT ) == 0 )
			{
				BYTE feppath[32] = { 0 };
				DWORD size = sizeof feppath - 1;
				DWORD type = REG_SZ;
				RegQueryValueEx(hkParams, c_pszValueFepName, NULL, &type, feppath, &size);
				feppath[size] = 0;

				TCHAR fpath[256];
				TCHAR expand[256] = "%SystemRoot%\\System32\\drivers\\";

				strcat(expand, (LPCSTR)feppath);
				ExpandEnvironmentStrings((LPCTSTR)expand, fpath, 256);
				
				CFile firm;
				if (firm.Open(fpath, CFile::modeRead | CFile::shareCompat)) 
				{
					DWORD size = firm.GetLength();
					BYTE *file = new BYTE[size];
					
					firm.Read(file, size);
					firm.Close();

					BOOL found = FALSE;
					char a[256] = { 0 }, *b = a;
					for (DWORD i = 0; i < size - 4; i++) {
						if (memcmp(file + i, "@(#)", 4) == 0) {
							found = TRUE;
							b = a;
							}

						if (found) {
							*b++ = file[i];
							if (file[i] == 0) {
								found = FALSE;
								b = strstr(a, "Version");
								if (b != NULL) {
									FirmwareVersion = b;
									break;
									}
								}
							}
						}
					delete[] file;
				}
			}
			newDS->LoadSummaryInfo(FirmwareVersion);
		}

NextLine:
		RegCloseKey(hkLine);
	}

	UpdateDataSource();
	return m_DataSource.GetSize() > 0;
}

void CAdapter::UpdateDataSource()
{
	for (int i = 0; i < m_DataSource.GetSize(); i++)
		m_DataSource[i]->UpdateDataSourceStats();
}

CDataSource::CDataSource() :
	m_Event(FALSE, TRUE)
{
	m_Status = dssDisabled;

	m_Tracer = NULL;
	m_TraceData = NULL;
	m_TraceState = 0;
	m_TracePPPSNA = 0;
	m_BaudRate = 0;

	m_hFile = NULL;
	m_VCObject = NULL;
	m_LastFirmwareError = 0;

	m_Tx = m_Rx = m_Err = dseNotAvailable;
	m_ClearTx = m_ClearRx = m_ClearErr = dseNoError;
	m_Alarms = "N/A";
}

CDataSource::CDataSource(const HKEY hkLine, const CString& DeviceName) : 
	m_Circuits(),
	m_Event(FALSE, TRUE)
{
	m_Status = dssDisabled;
	m_Device = DeviceName;
	m_BaudRate = 0;
 
	RegGetDWord( hkLine, DXB_REGVAL_LINE_SNID, &m_SNID );

 	HKEY hWan;
	if (RegOpenKeyEx(hkLine, c_pszKeyWan, &hWan) == ERROR_SUCCESS) {
		RegGetDWord(hWan, c_pszGUIValueBaud, &m_BaudRate);
		RegCloseKey(hWan);
		}

	m_WanStats.SetSNID(GetSNID());
	m_WanLineStatus.SetSNID(GetSNID());

	m_Tracer = NULL;
	m_TraceData = NULL;
	m_TraceState = 0;
	m_TracePPPSNA = 0;

	m_hFile = NULL;
	m_VCObject = NULL;
	m_LastFirmwareError = 0;

	m_Tx = m_Rx = m_Err = dseNotAvailable;
	m_ClearTx = m_ClearRx = m_ClearErr = dseNoError;
}

CRuntimeClass *CDataSource::GetStatDocClass() const
{
	return NULL;
}

CDataSource::~CDataSource()
{
	delete m_Tracer;
	delete m_TraceData;
	StopStats(NORMAL);
}

void CDataSource::TraceDataToDoc(CDiagDoc *doc) const
{ 
	doc->SetTraceData(m_TraceData);
	const_cast<CDataSource *>(this)->m_TraceData = NULL;
}

sDriverInfo CDataSource::GetDriverInfo(const CString& keyName)
{
	sDriverInfo Info;
	HKEY hkService;
	CString path = c_pszNTServiceBase + ('\\' + keyName);

 	if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, path, &hkService) == ERROR_SUCCESS) 
 	{
		BYTE s[256];
		DWORD Type = REG_SZ, Size = sizeof s;
		RegQueryValueEx(hkService, "DisplayName", 0, &Type, s, &Size);
		Info.m_DriverName = (LPCSTR)s;

		TCHAR expand[256];
		Type = REG_EXPAND_SZ;
		Size = sizeof expand;
		RegQueryValueEx(hkService, "ImagePath", 0, &Type, (LPBYTE)expand, &Size);
		if (strstr(expand, "\\SystemRoot") == expand)
			strcpy(expand, CString("%SystemRoot%") + &expand[11]);
		else if (strstr(expand, "System32") == expand)
			strcpy(expand, CString("%SystemRoot%\\System32") + &expand[8]);

		TCHAR fpath[256];
		ExpandEnvironmentStrings((LPCTSTR)expand, fpath, sizeof fpath);
					
		CFileStatus status;
		if (CFile::GetStatus(fpath, status)) 
		{
			DWORD zero = 0, size = GetFileVersionInfoSize(fpath, &zero);
			if (size > 0 && zero == 0) 
			{
				BYTE *buf = new BYTE[size];
				if (GetFileVersionInfo(fpath, NULL, size, buf)) 
				{
					LPVOID lpVersion;
					LPVOID lpBuildNumber;
					UINT dwBytes;
					
					if (VerQueryValue(buf, TEXT("\\StringFileInfo\\040904B0\\FileVersion"), &lpVersion, &dwBytes))
						Info.m_DriverVersion = (LPCSTR)lpVersion;

					if (VerQueryValue(buf, TEXT("\\StringFileInfo\\040904B0\\Build Number"), &lpBuildNumber, &dwBytes))
						Info.m_DriverBuildNumber = (LPCSTR)lpBuildNumber;
				}  
				delete[] buf;
			}
			Info.m_DriverDate = status.m_mtime.Format("%c");
		}
		RegCloseKey(hkService);
	}
	return Info;
}

void CDataSource::StopStats(ENUM_STOP_TYPE eStopType)
{

	// A normal Stop brings things down in an orderly fashion.
	if (eStopType == NORMAL)
	{
		POSITION pos = m_VCObjectMap.GetStartPosition();
		while (pos)
		{
			HANDLE VCObject;
			eSourceType type;

			m_VCObjectMap.GetNextAssoc(pos, type, VCObject);
			CSClose SClose(VCObject);

			// If DeviceIoControl returns 0, then the close did not
			// finish in the required time.  Skip the rest of the
			// closes, and continued as if we were doing a forced
			// close.
			if (!DeviceIoControl(&SClose))
			{
				break;
			}
		}
	}

	m_VCObjectMap.RemoveAll();
	m_VCObject = NULL;

	if (m_hFile)
		CloseHandle(m_hFile);
	m_hFile = NULL;
}

void CDataSource::LoadSummaryInfo(const CString& frmvsn)
{
	m_FirmwareVersion = frmvsn;
}

BOOL CDataSource::TestDataSource(eSourceType type)
{
	if (m_hFile == NULL) {
		CString DeviceName = GetDeviceName();

		if( (m_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 )
			{
				m_hFile = NULL;
				TRACE("Couldn't open %s\n", DeviceName);
				return FALSE;
			}
		}

	if (!m_VCObjectMap.Lookup(type, m_VCObject) && !SOpen(type)) {
		TRACE("S_OPEN failed, GetLastError = 0x%X\n", GetLastError());
		return FALSE;
		}

	return TRUE;
}

void CDataSource::LoadConfigCircuits()
{
	CWANTUNE WanTune(GetSNID());

	if (GetData(&WanTune) == dssRunning && m_BaudRate == 0)
		m_BaudRate = WanTune->WAN_hd.WAN_baud;
}

eDataSourceStatus CDataSource::GetStatus(eSourceType FirmareDevice)
{
	if (m_Status == dssNotAvailable) {
		if (TestDataSource(FirmareDevice)) {
			m_Status = dssRunning;
			LoadConfigCircuits();
			}
		}
	else if (m_Status == dssRunning) {
		if (!TestDataSource(FirmareDevice)) {
			m_Status = dssNotAvailable;
			}
		}

	return m_Status;
}

void CX25DataSource::LoadSummaryInfo(const CString& frmvsn)
{
	CDataSource::LoadSummaryInfo(frmvsn);
	AddDriverInfo(c_pszKeyX25_SVC_ServiceName);
	AddDriverInfo(c_pszKeyX25_PVC_ServiceName);
}

void CSnaDataSource::LoadSummaryInfo(const CString& frmvsn)
{
	CDataSource::LoadSummaryInfo(frmvsn);
}

void CDataSource::DoSummaryDlg() const
{
	CSummaryDlg dlg;

	dlg.m_Name = GetName();
	dlg.m_pDrivers = &m_Drivers;
	dlg.m_FirmwareVersion = m_FirmwareVersion;
	dlg.m_Comment = AfxGetApp()->GetProfileString(
		c_pszKeySettings,
		GetName(),
		cDefaultDataSourceComment);

	if (dlg.DoModal() == IDOK)
		AfxGetApp()->WriteProfileString(
			c_pszKeySettings,
			GetName(),
			dlg.m_Comment);
}

BOOL CDataSource::StartTrace()
{
	if (m_TraceState == 2) {
		m_Tracer->Resume();
		m_TraceState = 1;
		}
	else if (m_TraceState == 0) {
		delete m_TraceData;
		
		BOOL fail = FALSE;
		try {
			m_TraceData = CreateTraceData(m_BaudRate, m_Circuits);

			m_Tracer = CreateTracer();
			fail = m_Tracer == NULL || m_Tracer->Fail();
			}
		catch (CException) {
			fail = TRUE;
			}

		if (fail) {
			delete m_TraceData;
			m_TraceData = NULL;
			delete m_Tracer;
			m_Tracer = NULL;
			return FALSE;
			}

		m_TraceState = 1;
		m_TraceData->SetCaptureSummaryInfo(this);
		}
	return TRUE;
}

void CDataSource::StopTrace()
{
	if (m_TraceState > 0) {
		delete m_Tracer;
		m_Tracer = NULL;
		if (!m_TraceData->RenumberFrames()) {
			delete m_TraceData;
			m_TraceData = NULL;
			}
		m_TraceState = 0;
		}
}

void CDataSource::ToggleTrace()
{
	if (m_TraceState == 1) {
		m_Tracer->Suspend();
		m_TraceState = 2;
		}
	else if (m_TraceState == 2) {
		m_Tracer->Resume();
		m_TraceState = 1;
		}
}

CSnaDataSource::CSnaDataSource(const HKEY hkLine, const CString& DeviceName) : 
	CDataSource(hkLine, DeviceName)
{
	m_Status = dssNotAvailable;
	m_TracePPPSNA = 1;
}

CRuntimeClass *CSnaDataSource::GetStatDocClass() const
{
	return RUNTIME_CLASS(CSnaStatDoc);
}


void CDataSource::ClearStatistics() // clear data source stats
{
	if (m_Tx != dseNotAvailable && m_Tx != dseLinkDown)
		m_ClearTx = -1;

	if (m_Rx != dseNotAvailable && m_Rx != dseLinkDown)
		m_ClearRx = -1;

	if (m_Err != dseNotAvailable && m_Err != dseLinkDown)
		m_ClearErr = -1;
}

BOOL CDataSource::UpdateDataSourceStats()
{

	m_Tx = m_Rx = m_Err = dseNotAvailable;
	if (m_Status == dssDisabled)
		return FALSE;

	_ftime(&m_UpdateTime);

	if (GetData (&m_WanLineStatus) == dssRunning)
	{
		IncrementError(m_Err, m_WanLineStatus->wan_linestatus_stats.hc_noclock +
			m_WanLineStatus->wan_linestatus_stats.hc_rxorun +
			m_WanLineStatus->wan_linestatus_stats.hc_rxcrc +
			m_WanLineStatus->wan_linestatus_stats.hc_rxbiterr +
			m_WanLineStatus->wan_linestatus_stats.hc_rxoflow +
			m_WanLineStatus->wan_linestatus_stats.hc_rxabort);

		switch ( m_WanLineStatus->wan_linestatus_cable_id )
		{
			case WAN_NO_CABLE:	
			case WAN_UNKNOWN:	m_Alarms = "No cable";	break;
			default:				m_Alarms = "";				break;
		}
	}

	if (GetData (&m_WanStats) == dssRunning)
	{
		m_Tx = m_WanStats->hc_txgood;
		m_Rx = m_WanStats->hc_rxgood;

		HandleClearStats(m_Tx, m_ClearTx);
		HandleClearStats(m_Rx, m_ClearRx);

		#ifdef _DEBUG
			// internal firmware errors
			if (m_WanStats->hc_txurun > 0)
				TRACE1("Number of transmit underruns %d\n", m_WanStats->hc_txurun);
			if (m_WanStats->hc_rxnobuf > 0)
				TRACE1("Number of Rx frames with no buffer %d\n", m_WanStats->hc_rxnobuf);
			if (m_WanStats->hc_txabort > 0)
				TRACE1("Number of tx aborts (hdplx only) %d\n", m_WanStats->hc_txabort);
			if (m_WanStats->hc_rxnflow > 0)
				TRACE1("Number of Rx frames with no flow ctl. %d\n", m_WanStats->hc_rxnflow);
			if (m_WanStats->hc_rxnomp > 0)
				TRACE1("Number of frames w/o buffers avail. %d\n", m_WanStats->hc_rxnomp);
		#endif
	}

	return m_Status == dssRunning;
}

const CSXB_USER_IOCTL *CDataSource::GetStatType(const type_info& Type, int) const
{
	if (Type == typeid(CWAN))
		return &m_WanStats;
	else if (Type == typeid(CWANLINESTATUS))
		return &m_WanLineStatus;
	else
		return NULL;
}

const CSXB_USER_IOCTL *CX25DataSource::GetStatType(const type_info& Type, int index) const
{
	const CSXB_USER_IOCTL *result = CDataSource::GetStatType(Type, index);

	if (result == NULL) {
		if (typeid(CLAPB) == Type)
			result =  &m_LAPBStats;
		else if (typeid(CX25) == Type)
			result = &m_X25Stats;
		}

	return result;
}

const CSXB_USER_IOCTL *CSnaDataSource::GetStatType(const type_info& Type, int index) const
{
	const CSXB_USER_IOCTL *result = CDataSource::GetStatType(Type, index);

	if (result == NULL)
	{
		if (typeid(CWANLINESTATUS) == Type)
			result =  &m_WanLineStatus;
	}

	return result;
}

BOOL CSnaDataSource::UpdateDataSourceStats()
{
	CDataSource::UpdateDataSourceStats();

	if (GetData (&m_WanLineStatus) == dssRunning)
	{
		IncrementError(m_Err, m_WanLineStatus->wan_linestatus_stats.hc_noclock +
			m_WanLineStatus->wan_linestatus_stats.hc_rxorun +
			m_WanLineStatus->wan_linestatus_stats.hc_rxcrc +
			m_WanLineStatus->wan_linestatus_stats.hc_rxbiterr +
			m_WanLineStatus->wan_linestatus_stats.hc_rxoflow +
			m_WanLineStatus->wan_linestatus_stats.hc_rxabort);
	}

	return m_Status == dssRunning;
}

CTracer *CSnaDataSource::CreateTracer()
{ 
	return new CSdlcTracer(this); 
}

CTraceData *CSnaDataSource::CreateTraceData(DWORD baud, CDWordArray& circuits)
{
	return new CSdlcTraceData(baud, circuits); 
}

CX25DataSource::CX25DataSource(const HKEY hkLine, const CString& DeviceName) :
	CDataSource(hkLine, DeviceName),
	m_LAPBStats(GetSNID()),
	m_X25Stats(GetSNID())
{
	m_Status = dssNotAvailable;
}

CRuntimeClass *CX25DataSource::GetStatDocClass() const
{
	return RUNTIME_CLASS(CX25StatDoc);
}

BOOL CX25DataSource::UpdateDataSourceStats()
{
	if (CDataSource::UpdateDataSourceStats()) {
		if (GetData(&m_LAPBStats) == dssRunning)
			if (m_LAPBStats.GetState() != "Normal")
				m_Err = dseLinkDown;

		if (GetData(&m_X25Stats) == dssRunning) {
			IncrementError(m_Err, m_X25Stats[p4_ferr] +
														m_X25Stats[rem_perr] +
														m_X25Stats[res_ferr]);
			}
		}

	HandleClearStats(m_Err, m_ClearErr);
	return m_Status == dssRunning;
}

void CX25DataSource::LoadConfigCircuits()
{
	CDataSource::LoadConfigCircuits();

	HKEY digisoft;
	CString path =	m_RegPath + _T('\\') + c_pszKeyX25_ProtocolMain;
 	if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, path, &digisoft) != ERROR_SUCCESS)
		return;
	
	DWORD low, high;

	RegGetDWord(digisoft, c_pszValueHPC, &high);
	RegGetDWord(digisoft, c_pszValueLPC, &low);
	for (DWORD i = low; i < high + 1 && low != 0; i++)
		m_Circuits.Add(i | cfX25PVC);

	RegGetDWord(digisoft, c_pszValueHIC, &high);
	RegGetDWord(digisoft, c_pszValueLIC, &low);
	for (i = low; i < high + 1 && low != 0; i++)
		m_Circuits.Add(i);

	RegGetDWord(digisoft, c_pszValueHTC, &high);
	RegGetDWord(digisoft, c_pszValueLTC, &low);
	for (i = low; i < high + 1 && low != 0; i++)
		m_Circuits.Add(i);

	RegGetDWord(digisoft, c_pszValueHOC, &high);
	RegGetDWord(digisoft, c_pszValueLOC, &low);
	for (i = low; i < high + 1 && low != 0; i++)
		m_Circuits.Add(i);

  DWORD llclass;
	RegGetDWord(digisoft, c_pszValueLLClass, &llclass);
	switch (llclass) {
		case LC_LAPBXDCE :
			m_Modulus = G_128;
			m_DXE = DCE;
			break;
		case LC_LAPBDCE :
			m_Modulus = G_8;
			m_DXE = DCE;
			break;
		case LC_LAPBXDTE :
			m_Modulus = G_128;
			m_DXE = DTE;
			break;
		case LC_LAPBDTE :
			m_Modulus = G_8;
			m_DXE = DTE;
			break;
		default : // Invalid
			;			
		}

	RegCloseKey(digisoft);
}

CTracer *CX25DataSource::CreateTracer()
{ return new CLAPBTracer(this); };

CTraceData *CX25DataSource::CreateTraceData(DWORD baud, CDWordArray& circuits)
{ return new CX25TraceData(baud, circuits); };

// Users of this member function must be careful not to call this
// function again if the previous call returned a FALSE.  A FALSE
// return means their is something wrong with the line, and the
// m_hFile must be closed.
//
// A gross timeout is used on the event to detect a dead line.
// For example, the firmware is hung and the IOCTL will never
// complete.  This condition returns a FALSE and the callers
// must the close the m_hFile.
BOOL CDataSource::DeviceIoControl(CSXB_IOCTL *IOCTL) const
{
	BOOL bSuccess;
	CDataSource *ncthis = const_cast<CDataSource *>(this);

	if (m_hFile == NULL) {
		return FALSE;
	}

	ncthis->m_Overlapped.Internal		= 0;
	ncthis->m_Overlapped.InternalHigh	= 0;
	ncthis->m_Overlapped.Offset			= 0;
	ncthis->m_Overlapped.OffsetHigh		= 0;
	ncthis->m_Overlapped.hEvent			= m_Event;

	bSuccess = IOCTL->DeviceIoControl(m_hFile, &ncthis->m_Overlapped); 
	if ( !bSuccess )
	{
		DWORD Error = GetLastError();
		if ( Error != ERROR_IO_PENDING )
		{
			TRACE( "DeviceIoControl failed, LastError 0x%x\n", Error );
			ncthis->m_Event.ResetEvent();
			return( FALSE );
		}
	}
		
	CSingleLock devIOComplete(&ncthis->m_Event);

	// A half second gross timeout is used.  This should be sufficent.
	// If lines are not active (e.g. SNA lines), then the screen will
	// paint slower.  We have observed that the previous timeout value
	// of 100 msec was not enough in certain cases:
	//	CASE 1: Bring up and down the SNA server.
	//  CASE 2: Running WASP on multiple high speed links.
	BOOL result = devIOComplete.Lock(800);

	ULONG dwBytesReturned = 0;
	bSuccess = GetOverlappedResult(m_hFile, &ncthis->m_Overlapped, &dwBytesReturned, FALSE);

	ncthis->m_Event.ResetEvent();
	return result && bSuccess && dwBytesReturned > 0;
}

CSOpen::CSOpen(eSourceType type)
{
	LPCSTR name = NULL;
	 
	m_Type = type;
	switch (m_Type) {
		case stIWAN: name = "IWAN"; break;
		case stWANS: name = "WANS"; break;
		case stFR:   name = "FR";   break;
		case stX25:  name = "X250"; break;
		case stLAPB: name = "LAPB"; break;
		case stLAPD: name = "LAPD"; break;
		case stMGMT: name = "MGMT"; break;
		case stT1:   name = "T1";   break;
		default: 
			ASSERT(FALSE);
		}

	ZeroMemory( (PVOID)&m_Open, sizeof( DS_OPEN ) );
	strcpy( (char *)m_Open.DeviceName, name );
}

BOOL CDataSource::SOpen(eSourceType type) const
{
	CSOpen SOpen(type);

	BOOL bOpenResult  = FALSE;
	BOOL bDevIoResult = DeviceIoControl(&SOpen);

	CDataSource *ncthis = const_cast<CDataSource *>(this);

	if (bDevIoResult)
	{
		// Was the Open Successful
		bOpenResult = !SOpen.GetError();
	}

	if (bDevIoResult && bOpenResult)
	{
		ASSERT( SOpen.GetVCObject() != NULL );
		ncthis->m_VCObject = SOpen.GetVCObject();
		ncthis->m_VCObjectMap.SetAt(type, m_VCObject);
	}
	else
	{
		// If the DeviceIoControl of Open fails we need to stop without
		// sending any more DeviceIoControls on the m_hFile. We
		// also need to make sure we Close the handle (to cancel
		// the open) while the SOpen stack variable is within scope.
		ncthis->m_VCObject = NULL;
		ncthis->StopStats(FORCE);
	}

	return bDevIoResult && bOpenResult;
}

eDataSourceStatus CDataSource::GetData(CSXB_USER_IOCTL *SIoctl)
{
	if (GetStatus(SIoctl->GetFirmwareType()) == dssRunning) 
	{
		ENUM_STOP_TYPE eStopType = NORMAL;
		ASSERT(m_VCObject != NULL);
		SIoctl->SetVCObject(m_VCObject);

		BOOL bSuccess = DeviceIoControl(SIoctl);

		// If the DeviceIoControl fails we need to stop without
		// sending any more DeviceIoControls on the m_hFile. We
		// also need to make sure we Close the handle (to cancel
		// the open) while the SOpen stack variable is within scope.
		if (bSuccess)
		{
			m_LastFirmwareError = SIoctl->GetReturnCode();
		}
		else
		{
			eStopType = FORCE;
		}

		if (!bSuccess || m_LastFirmwareError) 
		{
			TRACE("SXB_USER_IOCTL failed, ReturnCode = %#X, GetLastError = %#X\n",
						GetLastFirmwareError(),
						GetLastError());

			StopStats(eStopType);
			DeleteCircuits();
			m_Status = dssNotAvailable;
		}
	}

	return m_Status;
}

eLMIType CFRFrame::GetLMIType() const
{
	return ((CFRTraceData *)m_TraceData)->m_LMIType;
}

LPCSTR CFRFrame::GetProtocol() const
{
	if (FilterTest(fmLMI))
		return "LMI" ;
	else if (FilterTest(fmIP))
		return "1490";
	else if (FilterTest(fmCiscoIP))
		return "Cisco IP" ;
	else if (FilterTest(fmFRF9))
		return "FRF.9" ;
	else
		return "Unknown" ;
}

void CFRFrame::GetDescription(CString& txt) const
{
	if (FilterTest(fmLMI))
	{
		UCHAR MessageType = GetData()[5];

		switch (MessageType >> 4)
		{
			case 7 :
				switch (MessageType & 0xF)
				{
					case 0xD :
						txt = "Status Message"; break;
					case 0x5 :
						txt = "Status Enquiry"; break;
					case 0xB :
						txt = "Update Status"; break;
					default :
						txt = "Unknown Message";
				}
				break;
			default :
				txt = "LMI Message of Unknown Type";
		}
	}
	else if (FilterTest(fmCiscoIP))
	{
		txt = "Cisco Frame Relay IP Encapsulation";
	}
	else
	{
		PUCHAR data = GetData();

		int start = 3;
		if (data[1] & 1)
			start = 3;
		else if (data[2] & 1)
			start = 4;
		else if (data[3] & 1)
			start = 5;
		else
			ASSERT(FALSE);

		PUCHAR Pkt = data + start + (data[start] == 0);

		switch (Pkt[0])
		{
			case 0x08: txt = "Q.933"; 					break;
			case 0x81: txt = "ISO CLNP"; 				break;
			case 0x82: txt = "ISO ESIS"; 				break;
			case 0x83: txt = "ISO ISIS"; 				break;
			case 0xCC: txt = "IP"; 						break;
			case 0xB0:
			{
				if ( Pkt[1] & 0x01 )			txt += " Control";
				else if ( Pkt[1] & 0x40 )	txt = "Compressed";		// Compressed Data
				else								txt = "Uncompressed";	// Uncompressed Data
				break;	
			} 
			case 0x80:
			{
				txt = "SNAP/";
				int oui = (Pkt[1] << 16) | (Pkt[2] << 8) | Pkt[3];
				int pid = (Pkt[4] << 8) | Pkt[5];
				switch (oui)
				{
					case 0x000000:	/* Ethertype */
						switch (pid)
						{
							case 0x8137: txt += "IPX Ethertype"; break;
							default: txt += "Unknown Ethertype"; break;
						}
						break;
					case 0x0080C2:	/* 802.1 type */
						switch (pid)
						{
							case 0x0001: case 0x0007: txt += "802.3/Ethernet"; break;
							case 0x0002: case 0x0008: txt += "802.4"; break;
							case 0x0003: case 0x0009: txt += "802.5"; break;
							case 0x0004: case 0x000A: txt += "FDDI"; break;
							case 0x000B: txt += "802.6"; break;
							case 0x000E: txt += "BPDU"; break;
							case 0x000D: txt += "Packet Fragment"; break;
						}
						break;
					default: txt += "Unknown"; break;
				}
				break;
			}

			default: 
				txt.Format("Unknown (%2X)", Pkt[0]); 
				break;
		}
	}
}

DWORD CX25Frame::GetModulus() const
{ 
	return GetTraceData()->m_Modulus;
}

eDXE CX25Frame::GetDXE() const
{
	return GetTraceData()->m_DXE;
}

CString CX25Frame::GetX25Address(DWORD dir, eCallState state)
{
	PUCHAR dp = GetData() + 5 + (GetModulus() == G_128);
	CString calling, called, temp;
	BOOL toanpi = (GetData()[2 + (GetModulus() == G_128)] & 0x80) > 0;

	int called_dte_len  = toanpi ? dp[0] : (dp[0] & 0x0F);
	int calling_dte_len = toanpi ? dp[1] : (dp[0] >> 4);
	
	dp += toanpi ? 2 : 1;   

	for (int j = 0; j < called_dte_len; j++) {
		if (j & 0x01) {
			temp.Format("%1X", dp[0] & 0x0f);
			dp++;
			}
		else
			temp.Format("%1X", dp[0] >> 4);
		called += temp;
		}

	for (; j < (calling_dte_len+called_dte_len); j++) {
		if (j & 0x01) {
			temp.Format("%1X", dp[0] & 0x0f);
			dp++;
			}
		else 
			temp.Format("%1X", dp[0] >> 4);
		calling += temp;
		}

	return (dir == TRANSMIT) ^ (state == csCallRequest) ? calling : called;
}

void CX25Frame::GetLAPBDescription(CString& msg) const
{
	UCHAR LAPBControl = GetData()[1];

// clear P/F bit (bit 5)
	LAPBControl &= 0xEF; 

	if ((LAPBControl & 1) == 0)
		msg = "Information(I)";
	else
		switch (LAPBControl) {
			case 0x2F : msg = "Set Asynchronous Balanced Mode(SABM)"; break;
			case 0x6F : msg = "Set Asynchronous Balanced Mode Extended(SABME)"; break;
			case 0x43 : msg = "Disconnect(DISC)"; break;
			case 0x0F : msg = "Disconnect Mode(DM)"; break;
			case 0x63 : msg = "Unnumbered Acknowledgement(UA)"; break;
			case 0x87 : msg = "Frame Reject(FRMR)"; break;
			default :
				switch (LAPBControl & 0xF) {
					case 0x01 : msg = "Receiver Ready(RR)"; break;
					case 0x05:	msg = "Receiver Not Ready(RNR)"; break;
					case 0x09:	msg = "Reject(REJ)"; break;
					default :
						msg = "Invalid or Unsupported Frame"; // bad or unsupported frame
					}
			}
}

void CX25Frame::GetX25Description(CString& msg) const
{
// octet 3 in lapb I frames after lapb header
	UCHAR PacketType = GetData()[2 + (GetModulus() == G_128) + 2];
	BOOL DidTransmit = !GetTxRx();

	if ((PacketType & 1) == 0)
		msg = "Data";
	else
		switch (PacketType) {
			case 0x0B : msg = DidTransmit ? "Call Request" : "Incoming Call"; break;
			case 0x0F : msg = DidTransmit ? "Call Accepted" : "Call Connected"; break;
			case 0x13 : msg = DidTransmit ? "Clear Request" : "Clear Indication"; break;
			case 0x17 : msg = "Clear Confirmation"; break;
			case 0x23 : msg = "Interrupt"; break;
			case 0x27 : msg = "Interrupt Confirmation"; break;
			default :
				switch (PacketType & 0xF) {
					case 0x01 :
						msg = "Receiver Ready(RR)"; break;
					case 0x05 : 
						msg = "Receiver Not Ready(RNR)"; break;
					case 0x09 :
						msg = "Reject(REJ)"; break;
					default:
						msg.Format("Unknown Packet Type (%X)", PacketType);
					}
				break;
			case 0x1B : msg = msg = DidTransmit ? "Reset Request" : "Reset Indication"; break;
			case 0x1F : msg = "Reset Confirmation"; break; 
			case 0xFB : msg = msg = DidTransmit ? "Restart Request" : "Restart Indication"; break;
			case 0xFF : msg = "Restart Confirmation"; break;
			case 0xF1 : msg = "Diagnostic"; break;
			case 0xF3 : msg = "Registration Request"; break;
			case 0xF7 : msg = "Registration Confirmation"; break;
			}
}

CTraceData::CTraceData(DWORD baud) : m_Sessions()
{
	m_ByteRate = baud / 8;

	ClearTraceStats();
	m_ScratchFrame = NULL;
}

inline void AFXAPI DestructElements(CFrame **pElements, int nCount)
{
	for (int i = 0; i < nCount; i++)
		delete pElements[i];
}

CTraceData::~CTraceData()
{
	delete m_ScratchFrame;
	for (int i = 0; i < m_Sessions.GetSize(); i++)
		delete m_Sessions[i];
}

CTraceSheet *CTraceData::CreateTraceSheet()
{
	return new CTraceSheet(this, _T(""));
}

BOOL CTraceData::RenumberFrames()
{
	CWaitCursor wait;
	BOOL result = m_FrameBuffer.RenumberFrames(m_ScratchFrame);

	return result;
}

void CTraceData::GetFilteredFrames(CDWordArray& Filtered, DWORD mask, CDWordArray *circuit)
{ m_FrameBuffer.NewFilter(m_ScratchFrame, Filtered, mask, circuit); }

void CTraceData::ClearTraceStats()
{
	BC[0] = BC[1] = 0;
	FC[0] = FC[1] = 0;

	sMCM empty = { 0 };
	LU[0] = LU[1] = empty;
	FPS[0] = FPS[1] = empty;
	Th[0] = Th[1] = empty;

	m_Section.Lock();
	for (short i = 0; i < m_Sessions.GetSize(); i++)
		delete m_Sessions[i];
	m_Sessions.RemoveAll();
	m_LiveSessions.RemoveAll();
	m_Section.Unlock();

	_ftime(&m_LastTime);
}

void CTraceData::SetCaptureSummaryInfo(const CDataSource *ds)
{
	DWORD sz = 31;
	char buf[32];
	::GetComputerName(buf, &sz);
	m_Machine = buf;
	m_DataSourceName = ds->GetName();
	m_Protocol = ds->GetProtocol();
	m_DateTime = CTime::GetCurrentTime();
	m_Drivers.RemoveAll();
	m_Drivers.InsertAt(0, &(const_cast<CDataSource *>(ds))->m_Drivers);
	m_FirmwareVersion = ds->m_FirmwareVersion;
	m_Comment = AfxGetApp()->GetProfileString( c_pszKeySettings, m_DataSourceName, 
	                                           cDefaultDataSourceComment);
}

BOOL CTraceData::DoCaptureSummaryDlg(CWnd *pParent)
{
	CCapSumInfoDlg dlg(pParent);

	dlg.m_Machine = m_Machine;
	dlg.m_DataSource = m_DataSourceName;
	dlg.m_Protocol = m_Protocol;
	dlg.m_DateTime = m_DateTime.Format("%c");
	dlg.m_pDrivers = &m_Drivers;
	dlg.m_FirmVersion = m_FirmwareVersion;
	dlg.m_Comment = m_Comment;

	BOOL modified = FALSE;

	if (dlg.DoModal() == IDOK) {
		modified = m_Comment != dlg.m_Comment;
		m_Comment = dlg.m_Comment;
		}

	return modified;
}

void CTraceData::ProcessFrame(const strbuf& Control, const strbuf& Data)
{
	m_ScratchFrame->SetData(m_FrameBuffer.AddFrame(Control, Data));

	if (m_FrameBuffer.GetNumFrames() == 1)
		m_FirstTimeStamp = m_ScratchFrame->GetTimeStamp();

	DWORD dir = m_ScratchFrame->GetTxRx();
	FC[dir]++;
	BC[dir] += Data.len;
}

void CTraceData::NextSecond()
{
	_timeb now;
	_ftime(&now);
	DWORD deltat = 1000 * (now.time - m_LastTime.time) + (now.millitm - m_LastTime.millitm);

	if (deltat >= 1000) {
		for (int i = TRANSMIT; i < 2; i++) {
			Th[i].Cur = deltat == 0 ? 0 : round(1000.0 * BC[i] / deltat);
			Th[i].Min = __min(Th[i].Cur, Th[i].Min == 0 ? Th[i].Cur : Th[i].Min);
			Th[i].Max = __max(Th[i].Cur, Th[i].Max);

			LU[i].Cur = m_ByteRate ? round(100.0 * Th[i].Cur / m_ByteRate) : 0;	// Don't divide-by-zero!
			LU[i].Min = __min(LU[i].Cur, LU[i].Min == 0 ? LU[i].Cur : LU[i].Min);
			LU[i].Max = __max(LU[i].Cur, LU[i].Max);
			
			FPS[i].Cur = deltat == 0 ? 0 : round(1000.0 * FC[i] / deltat);
			FPS[i].Min = __min(FPS[i].Cur, FPS[i].Min == 0 ? FPS[i].Cur : FPS[i].Min);
			FPS[i].Max = __max(FPS[i].Cur, FPS[i].Max);
			}

		DoVCNextSecond(deltat);
		}

	m_Elapsed = CTime::GetCurrentTime() - m_DateTime;
}

CFRTraceData::CFRTraceData(DWORD baud, CDWordArray& circuits) : CTraceData(baud)
{
	m_ScratchFrame = CreateFrame();
	for (short i = 0; i < circuits.GetSize(); i++)
		m_VCStats.SetAt(circuits[i] & ChannelMask, new sFRVCStats());
	ClearTraceStats();
}

CFRTraceData::~CFRTraceData()
{
	POSITION pos = m_VCStats.GetStartPosition();
	while (pos) {
		DWORD ch;
		sFRVCStats *vc;

		m_VCStats.GetNextAssoc(pos, ch, vc);
		delete vc;
		}
}

CTraceSheet *CFRTraceData::CreateTraceSheet()
{
	return new CFRTraceSheet(this, _T(""));
}

eFilterMask CFRTraceData::GetFRProtocol(PUCHAR data) const
{
	eFilterMask mask = fmEth;
	PUCHAR Pkt = data[3] ? data+3 : data+4;

	/* Special Case: Cisco IP Encapsulation */
	if ((data[2] == 0x08) && (data[3] == 0x00))
	{
		return fmCiscoIP ;
	}

	switch (Pkt[0])
	{
		case 0xCC:
			mask = fmIP;
			break;
		case 0xB0:
			mask = fmFRF9;
			break;
		case 0x80:
		{
			int oui = (Pkt[1] << 16) | (Pkt[2] << 8) | Pkt[3];
			int pid = (Pkt[4] << 8) | Pkt[5];

			if (oui == 0x000000 && pid == 0x8137)
				mask = fmIPX;
			else if (oui == 0x0080C2)
				mask = fmEth;
			else
				mask = fmALLPROTO;
		}
			break;
		default:
			mask = fmALLPROTO;
			break;
	}

	return mask;
}

BOOL CFRTraceData::BumpFRPktType(CFRFrame *frame)
{
	PUCHAR Payload = frame->GetData();
	DWORD dir = frame->GetTxRx();
	BOOL data = TRUE;

	FECN[dir] += (Payload[1] & 8) > 0;
	BECN[dir] += (Payload[1] & 4) > 0;
	DE[dir] += (Payload[1] & 2) > 0;
	
	if (frame->GetDLCI() == (frame->GetLMI() & 0x3ff))
	{
		switch (Payload[5])
		{
			case 0x7D : LMISM[dir]++; break;
			case 0x75 : LMISE[dir]++; break;
			case 0x7B : LMIUS[dir]++; break;
		}
		data = FALSE;
	}
	else
	{
		PUCHAR Pkt = Payload[3] ? Payload+3 : Payload+4;

		/* Special Case: Cisco IP Encapsulation */
		if ((Payload[2] == 0x08) && (Payload[3] == 0x00))
		{
			CiscoIP[dir]++ ;
			return data ;
		}

		switch (*Pkt)
		{
			case 0xCC : IP[dir]++; break;
			case 0xB0 : FRF9[dir]++; break;
			case 0x80 :	/* Ethertype */
			{
				int oui = (Pkt[1] << 16) | (Pkt[2] << 8) | Pkt[3];
				int pid = (Pkt[4] << 8) | Pkt[5];
				
				if (oui == 0 && pid == 0x8137) 
					IPX[dir]++;
				else if (oui == 0x80C2 && (pid == 1 || pid == 7)) /* 802.1 type */
					Ethernet[dir]++; // 802.3
				else
					Other[dir]++;
			}
			break;
			default :
				Other[dir]++;
		}
	}

	return data;
}

void CFRTraceData::ProcessFrame(const strbuf& control, const strbuf& data)
{
	CTraceData::ProcessFrame(control, data);
	CFRFrame *frame = (CFRFrame *)m_ScratchFrame;

	if (BumpFRPktType(frame))
	{
		eFilterMask mask = GetFRProtocol(frame->GetData());

		frame->SetFilterMask(mask);

		DWORD ch = frame->GetCircuit();

		sFRVCStats *vc;
		if (!m_VCStats.Lookup(ch, vc)) {
			TRACE("Incoming frame has an unexpected DLCI %d\n", ch);
			return;
			}

		DWORD dir = frame->GetTxRx();
		vc->Frames[dir]++;
		vc->Bytes[dir] += data.len;
		UCHAR info = frame->GetData()[1];
		vc->FECN[dir] += (info & 8) > 0;
		vc->BECN[dir] += (info & 4) > 0;
		vc->DE[dir] += (info & 2) > 0;
	}
	else
		frame->SetFilterMask(fmLMI);
}

void CFRTraceData::SetCaptureSummaryInfo(const CDataSource *m_DataSource)
{
	CTraceData::SetCaptureSummaryInfo(m_DataSource);
	m_LMIType = ((CFRDataSource *)m_DataSource)->GetLMIType();
}

void CFRTraceData::DoVCNextSecond(DWORD deltat)
{
	POSITION pos = m_VCStats.GetStartPosition();
	while (pos) {
		sFRVCStats *vc;
		DWORD ch;

		m_VCStats.GetNextAssoc(pos, ch, vc);

		for (int i = TRANSMIT; i < 2; i++) {
			vc->Th[i].Cur = deltat == 0 ? 0 : round(1000.0 * vc->Bytes[i] / deltat);
			vc->Th[i].Min = __min(vc->Th[i].Cur, (vc->Th[i].Min == 0 ? vc->Th[i].Cur : vc->Th[i].Min));
			vc->Th[i].Max = __max(vc->Th[i].Cur, vc->Th[i].Max);

			vc->LU[i].Cur = round(100.0 * vc->Th[i].Cur / m_ByteRate);
			vc->LU[i].Min = __min(vc->LU[i].Cur, (vc->LU[i].Min == 0 ? vc->LU[i].Cur : vc->LU[i].Min));
			vc->LU[i].Max = __max(vc->LU[i].Cur, vc->LU[i].Max);
			
			vc->FPS[i].Cur = deltat == 0 ? 0 : round(1000.0 * vc->Frames[i] / deltat);
			vc->FPS[i].Min = __min(vc->FPS[i].Cur, (vc->FPS[i].Min == 0 ? vc->FPS[i].Cur : vc->FPS[i].Min));
			vc->FPS[i].Max = __max(vc->FPS[i].Cur, vc->FPS[i].Max);
			}
		}
}

BOOL operator!=(const CDWordArray& a1, const CDWordArray& a2)
{
	if (a1.GetSize() != a2.GetSize())
		return TRUE;

	for (int i = 0; i < a1.GetSize(); i++)
		if (a1[i] != a2[i])
			return TRUE;

	return FALSE;
}

BOOL CFRTraceData::DoFilterOptions(DWORD& mask, CDWordArray *circuit)
{
	CFRFilter dlg;

	dlg.m_FilterCircuit.InsertAt(0, circuit);
	POSITION pos = m_VCStats.GetStartPosition();
	while (pos)
	{
		DWORD ch;
		sFRVCStats *pvc;

		m_VCStats.GetNextAssoc(pos, ch, pvc);
		dlg.m_Circuits.Add(ch);
	}

	if (mask == fmALL)
		dlg.m_AllRadio = 0;
	else
		dlg.m_AllRadio = 2 - (mask & fmLMI);

	switch (mask)
	{
		default :
		case fmALLPROTO :
			dlg.m_1490Radio = 0;
			break;
		case fmIP :
			dlg.m_1490Radio = 1;
			break;
		case fmIPX :
			dlg.m_1490Radio = 2;
			break;
		case fmEth :
			dlg.m_1490Radio = 3;
			break;
		case fmFRF9 :
			dlg.m_1490Radio = 4;
			break;
		case fmCiscoIP :
			dlg.m_1490Radio = 5;
			break;
	}

	DWORD oldmask = mask;
	CDWordArray oldcircuit;
	
	oldcircuit.InsertAt(0, circuit);

	BOOL ok = dlg.DoModal() == IDOK;
	if (ok)
	{
		circuit->RemoveAll();

		switch (dlg.m_AllRadio)
		{
			case 0 : mask = fmALL; break;
			case 1 : mask = fmLMI; break;
			case 2 : 
				circuit->InsertAt(0, &dlg.m_FilterCircuit);
				switch (dlg.m_1490Radio)
				{
					case 0 : mask = fmALLPROTO; break;
					case 1 : mask = fmIP; break;
					case 2 : mask = fmIPX; break;
					case 3 : mask = fmEth; break;
					case 4 : mask = fmFRF9; break;
					case 5 : mask = fmCiscoIP; break;
				}
		}
	}

	return ok && (oldmask != mask || oldcircuit != *circuit);
}

void CFRTraceData::ClearTraceStats()
{
	CTraceData::ClearTraceStats();

	for (short i = 0; i < 2; i++)
	{
		DE[i] = BECN[i] = FECN[i] = LMISE[i] = LMISM[i] = LMIUS[i] = 0;
		IP[i] = IPX[i] = FRF9[i] = CiscoIP[i] = Ethernet[i] = Other[i] = 0;
	}

	POSITION pos = m_VCStats.GetStartPosition();
	while (pos)
	{
		DWORD ch;
		sFRVCStats *pvc;

		m_VCStats.GetNextAssoc(pos, ch, pvc);
		memset(pvc, 0, sizeof(sFRVCStats));
	}
}

CX25TraceData::CX25TraceData(DWORD baud, CDWordArray& circuits) : 
	CTraceData(baud), m_ChannelPVC()
{
	m_ScratchFrame = CreateFrame();
	for (short i = 0; i < circuits.GetSize(); i++) {
		if ((circuits[i] & cfX25PVC) > 0)
			m_ChannelPVC.Add(circuits[i] & ChannelMask);

		m_VCStats.SetAt(circuits[i] & ChannelMask, new sX25VCStats());
		}
	ClearTraceStats();
}

CX25TraceData::~CX25TraceData()
{
	POSITION pos = m_VCStats.GetStartPosition();
	while (pos) {
		DWORD ch;
		sX25VCStats *vc;

		m_VCStats.GetNextAssoc(pos, ch, vc);
		delete vc;
		}
}

CTraceSheet *CX25TraceData::CreateTraceSheet()
{
	return new CX25TraceSheet(this, _T(""));
}

double CX25TraceData::TimeInSeconds(DWORD t) const
{ 
	return (t - m_FirstTimeStamp) / 50.0f;
} 

double CFRTraceData::TimeInSeconds(DWORD t) const
{ 
	return (t - m_FirstTimeStamp) / 1000.0f;
} 

void CX25TraceData::ProcessFrame(const strbuf& control, const strbuf& data)
{
	CTraceData::ProcessFrame(control, data);
	CX25Frame *frame = (CX25Frame *)m_ScratchFrame;

	DWORD dir = frame->GetTxRx();

	if (BumpLAPBControl(dir, frame->GetData()[1])) {
		BOOL isdata = FALSE;
		eCallState state = BumpX25PktType(dir, frame->GetData()[4 + (m_Modulus == G_128)], isdata);

		frame->SetFilterMask(isdata ? fmUSERDATA : fmX25PROTO);

		DWORD ch = frame->GetCircuit();

		sX25VCStats *vc;
		if (!m_VCStats.Lookup(ch, vc)) {
			TRACE("Incomming frame has an unexpected Channel number %d\n", ch);
			return;
			}

		vc->Frames[dir]++;
		vc->Bytes[dir] += data.len;

		for (short i = 0; i < m_ChannelPVC.GetSize(); i++)
			if (m_ChannelPVC[i] == ch)
				break;

		if (i == m_ChannelPVC.GetSize()) {
			sSession *session;
	
			m_Section.Lock();
			if (m_LiveSessions.Lookup(ch, session)) {
				session->Frames[dir]++;
				if (state == csClearAccept)
					m_LiveSessions.RemoveKey(ch);
				}
			else if (state != csClearRequest && state != csClearAccept) {
				sSession *session = new sSession();
				
				session->Frames[0] = session->Frames[1] = 0;
				session->EndPoint = ch;
				session->Frames[dir]++;

				if (state == csCallRequest || state == csCallAccept)
					session->Remote = frame->GetX25Address(dir, state);

				m_LiveSessions.SetAt(ch, session);
				m_Sessions.Add(session);
				}

			m_Section.Unlock();
			}
		}
	else
		frame->SetFilterMask(fmLAPB);
}

void CX25TraceData::SetCaptureSummaryInfo(const CDataSource *m_DataSource)
{
	CTraceData::SetCaptureSummaryInfo(m_DataSource);
	m_Modulus = ((CX25DataSource *)m_DataSource)->GetModulus();
	m_DXE = ((CX25DataSource *)m_DataSource)->GetDXE();
}

void CX25TraceData::DoVCNextSecond(DWORD deltat)
{
	POSITION pos = m_VCStats.GetStartPosition();
	while (pos) {
		sX25VCStats *vc;
		DWORD ch;

		m_VCStats.GetNextAssoc(pos, ch, vc);

		for (int i = TRANSMIT; i < 2; i++) {
			vc->Th[i].Cur = deltat == 0 ? 0 : round(1000.0 * vc->Bytes[i] / deltat);
			vc->Th[i].Min = __min(vc->Th[i].Cur, vc->Th[i].Min == 0 ? vc->Th[i].Cur : vc->Th[i].Min);
			vc->Th[i].Max = __max(vc->Th[i].Cur, vc->Th[i].Max);

			vc->LU[i].Cur = round(100.0 * vc->Th[i].Cur / m_ByteRate);
			vc->LU[i].Min = __min(vc->LU[i].Cur, vc->LU[i].Min == 0 ? vc->LU[i].Cur : vc->LU[i].Min);
			vc->LU[i].Max = __max(vc->LU[i].Cur, vc->LU[i].Max);
			
			vc->FPS[i].Cur = deltat == 0 ? 0 : round(1000.0 * vc->Frames[i] / deltat);
			vc->FPS[i].Min = __min(vc->FPS[i].Cur, vc->FPS[i].Min == 0 ? vc->FPS[i].Cur : vc->FPS[i].Min);
			vc->FPS[i].Max = __max(vc->FPS[i].Cur, vc->FPS[i].Max);
			}
		}
}

BOOL CX25TraceData::DoFilterOptions(DWORD& mask, CDWordArray *circuit)
{
	CX25Filter dlg;

	dlg.m_FilterCircuit.InsertAt(0, circuit);
	POSITION pos = m_VCStats.GetStartPosition();
	while (pos) {
		DWORD ch;
		sX25VCStats *vc;

		m_VCStats.GetNextAssoc(pos, ch, vc);
		dlg.m_Circuits.Add(ch);
		}

	if (mask == fmALL)
		dlg.m_AllRadio = 0;
	else
		dlg.m_AllRadio = 2 - (mask & fmLAPB);

	dlg.m_X25All = 3 - ((mask & fmX25PROTO) > 0)*2 - ((mask & fmUSERDATA) > 0);
	if (dlg.m_X25All == 3)
		dlg.m_X25All = 0;

	DWORD oldmask = mask;
	CDWordArray oldcircuit;

	oldcircuit.InsertAt(0, circuit);

	BOOL ok = dlg.DoModal() == IDOK;
	if (ok) {
		circuit->RemoveAll();

		switch (dlg.m_AllRadio) {
			case 0 : mask = fmALL; break;
			case 1 : mask = fmLAPB; break;
			case 2 : 
				circuit->InsertAt(0, &dlg.m_FilterCircuit);
				switch (dlg.m_X25All) {
					case 0 : mask = fmX25PROTO | fmUSERDATA; break;
					case 1 : mask = fmX25PROTO; break;
					case 2 : mask = fmUSERDATA; break;
					}
			}
		}

	return ok && (oldmask != mask || oldcircuit != *circuit);
}

void CX25TraceData::ClearTraceStats()
{
	CTraceData::ClearTraceStats();

	LAPBData[0] = LAPBData[1] = 0;
	LAPBRR[0] = LAPBRR[1] = 0;
	LAPBRNR[0] = LAPBRNR[1] = 0;
	LAPBREJ[0] = LAPBREJ[1] = 0;
	LAPBFRMR[0] = LAPBFRMR[1] = 0;
	LAPBUA[0] = LAPBUA[1] = 0;
	LAPBDM[0] = LAPBDM[1] = 0;
	LAPBDISC[0] = LAPBDISC[1] = 0;
	LAPBSABM[0] = LAPBSABM[1] = 0;
	LAPBSABME[0] = LAPBSABME[1] = 0;

	X25Data[0] = X25Data[1] = 0;
	X25RR[0] = X25RR[1] = 0;
	X25RNR[0] = X25RNR[1] = 0;
	X25Reject[0] = X25Reject[1] = 0;
	X25CallRq[0] = X25CallRq[1] = 0;
	X25CallAc[0] = X25CallAc[1] = 0;
	X25ClrRq[0] = X25ClrRq[1] = 0;
	X25ClrCf[0] = X25ClrCf[1] = 0;
	X25RstRq[0] = X25RstRq[1] = 0;
	X25RstCf[0] = X25RstCf[1] = 0;
	X25RrtRq[0] = X25RrtRq[1] = 0;
	X25RrtCf[0] = X25RrtCf[1] = 0;
	X25Int[0] = X25Int[1] = 0;
	X25IntCf[0] = X25IntCf[1] = 0;
	X25RegRq[0] = X25RegRq[1] = 0;
	X25RegCf[0] = X25RegCf[1] = 0;
	X25Diag[0] = X25Diag[1] = 0;

	POSITION pos = m_VCStats.GetStartPosition();
	while (pos) {
		DWORD ch;
		sX25VCStats *vc;

		m_VCStats.GetNextAssoc(pos, ch, vc);
		memset(vc, 0, sizeof(sX25VCStats));
		}
}

eCallState CX25TraceData::BumpX25PktType(DWORD dir, UCHAR PacketType, BOOL& IsData)
{
	eCallState state = csNothing;

	IsData = (PacketType & 1) == 0;
	if (IsData)
		X25Data[dir]++;
	else
		switch (PacketType) {
			case 0x0B : X25CallRq[dir]++; state = csCallRequest; break;
			case 0x0F : X25CallAc[dir]++; state = csCallAccept; break;
			case 0x13 : X25ClrRq[dir]++; state = csClearRequest; break;
			case 0x17 : X25ClrCf[dir]++; state = csClearAccept; break;
			case 0x23 : X25Int[dir]++; break;
			case 0x27 : X25IntCf[dir]++; break;
			case 0x1B : X25RstRq[dir]++; break;
			case 0x1F : X25RstCf[dir]++; break; 
			case 0xFB : X25RrtRq[dir]++; break;
			case 0xFF : X25RrtCf[dir]++; break;
			case 0xF1 : X25Diag[dir]++; break;
			case 0xF3 : X25RegRq[dir]++; break;
			case 0xF7 : X25RegCf[dir]++; break;
			default:
				switch (PacketType & 0xF) {
					case 0x01 : X25RR[dir]++; break;
					case 0x05 : X25RNR[dir]++; break;
					case 0x09 : X25Reject[dir]++; break;
					default :
						X25Unkn[dir]++;
					}
			}

	return state;
}

BOOL CX25TraceData::BumpLAPBControl(DWORD dir, UCHAR LAPBCommand)
{
	BOOL IFrame = (LAPBCommand & 1) == 0; // is it an information frame

	LAPBCommand &= ~0x10; // strip off Poll/Final bit;

	if (IFrame)
		LAPBData[dir]++;
	else
		switch(LAPBCommand) {
			case 0x2F : LAPBSABM[dir]++; break;
			case 0x43 : LAPBDISC[dir]++; break;
			case 0x63 : LAPBUA[dir]++; break;
			case 0x0F : LAPBDM[dir]++; break;
			case 0x6F : LAPBSABME[dir]++;  break;
			case 0x87 : LAPBFRMR[dir]++; break;
			default : 
				switch (LAPBCommand & 0xF) {
					case 0x01 : LAPBRR[dir]++; break;
					case 0x05:	LAPBRNR[dir]++; break;
					case 0x09:	LAPBREJ[dir]++; break;
					default:		ASSERT(FALSE);
					}
			}

	return IFrame;
}

BOOL CFrame::FilterTest(DWORD mask, CDWordArray *circuit) const
{ 
	BOOL InList = circuit == NULL || circuit->GetSize() == 0;

	if (!InList)
		for (short i = 0; i < circuit->GetSize(); i++)
			if (circuit->GetAt(i) == GetCircuit())
				InList = TRUE;

	return	((mask & m_FrameData->m_TraceFlag) > 0 && InList) ;
}

void CFrame::DisplayTreeData(CTreeCtrl& tree, const CTraceData *td, CFrame *pFramePrev)
{
	CString msg;

	HTREEITEM item = InsertTreeItem(tree, "Trace");
	int FrameNum = GetFrameNum();

	msg.Format("Frame Number: %d", FrameNum);
	InsertTreeItem(tree, msg, item);

	double secs = td->TimeInSeconds(GetTimeStamp());
	CString when = (td->GetStartTime() + CTimeSpan((time_t)secs)).Format("%c");
	msg.Format("%-.3f", secs - int(secs));
	InsertTreeItem(tree, "Time Stamp: " + when + msg.Mid(1), item);

	msg.Format("Source: %s", GetTxRx() ? "Remote" : "Local");
	InsertTreeItem(tree, msg, item);

	if (pFramePrev != NULL)
	{
	// - 2 because frames are self numbered from 1..n and frames in buffer
	// are numbered 0..n-1
		double DeltaSecs = secs - td->TimeInSeconds(pFramePrev->GetTimeStamp());
		when = CTimeSpan((time_t)(DeltaSecs)).Format("%H:%M:%S");
		msg.Format("%-.3f", DeltaSecs - int(DeltaSecs));

		msg = "Time since previous frame: " + when + msg.Mid(1);
		InsertTreeItem(tree, msg, item);

		delete pFramePrev;
	}

	msg.Format("Total frame size: %d", GetDataLen());
	InsertTreeItem(tree, msg, item);
}

void CX25Frame::DisplayTreeData(CTreeCtrl& tree, const CTraceData *td, CFrame *pFramePrev)
{
	CFrame::DisplayTreeData(tree, td, pFramePrev);	 
	TreeDataLAPB(tree);
}

void CFRFrame::DisplayTreeData(CTreeCtrl& tree, const CTraceData *td, CFrame *pFramePrev)
{
	CFrame::DisplayTreeData(tree, td, pFramePrev);	 
	TreeDataFR(tree);
}

void CFRFrame::TreeDataFR(CTreeCtrl& tree)
{
	PUCHAR Payload = GetData();
	CString msg;
	DWORD Dlci;

	int PacketStart;
	if (Payload[1] & 1) // two byte address format
		PacketStart = 3;
	else if (Payload[2] & 1) // three byte address format
		PacketStart = 4;
	else if (Payload[3] & 1) // four byte address format
		PacketStart = 5;
	else
		ASSERT(FALSE);

	HTREEITEM item = InsertTreeItem(tree, "Frame Relay", TVI_ROOT, 0, PacketStart);

	if (Payload[1] & 1)			// two byte address format
	{
		Dlci = ((Payload[0] & 0xFC) << 2) + ((Payload[1] & 0xF0) >> 4);
		msg.Format("Data Link Connection Identifier(DLCI) ..xxxxxxx xxxx.... : %d", Dlci);
		InsertTreeItem(tree, msg, item, 0, 2);
	}
	else if (Payload[2] & 1)	// three byte address format
	{
		Dlci =	((Payload[0] & 0xFC) << 10) +
				((Payload[1] & 0xF0) << 6) +
				(Payload[2] >> 2);
		msg.Format("Data Link Connection Identifier(DLCI) ..xxxxxxx xxxx.... xxxxxx.. : %d", Dlci);
		InsertTreeItem(tree, msg, item, 0, 3);
	}
	else if (Payload[3] & 1)	// four byte address format
	{
		Dlci =	((Payload[0] & 0xFC) << 17) + 
				((Payload[1] & 0xF0) << 13) +
				((Payload[2] & 0xFE) << 6) +
				(Payload[3] >> 2);
		msg.Format("Data Link Connection Identifier(DLCI) ..xxxxxxx xxxx.... xxxxxxx. xxxxxx.. : %d", Dlci);
		InsertTreeItem(tree, msg, item, 0, 4);
	}
	else
		ASSERT(FALSE);

	msg = "Forward Congestion Notification(FECN) ....x... : ";
	msg += (Payload[1] & 8) > 0 ? "Set" : "Clear";
	InsertTreeItem(tree, msg, item, 1, 2);

	msg = "Backward Congestion Notification(BECN) .....x.. : ";
	msg += (Payload[1] & 4) > 0 ? "Set" : "Clear";
	InsertTreeItem(tree, msg, item, 1, 2);

	msg = "Discard Eligable(DE) ......x. : ";
	msg += (Payload[1] & 2) > 0 ? "Set" : "Clear";
	InsertTreeItem(tree, msg, item, 1, 2);

	if (Payload[1] & 1)
	{
		;
	}
	else if (Payload[2] & 1)	// three byte address format
	{
		msg = "D/C ......x. : ";
		msg += (Payload[2] & 2) > 0 ? "Set" : "Clear";
		InsertTreeItem(tree, msg, item, 2, 3);
	}
	else if (Payload[3] & 1)	// four byte address format
	{
		msg = "D/C ......x. : ";
		msg += (Payload[3] & 2) > 0 ? "Set" : "Clear";
		InsertTreeItem(tree, msg, item, 3, 3);
	}

	msg.Format("Unnumbered Frame Indicator: %d", Payload[2]);
	InsertTreeItem(tree, msg, item, PacketStart - 1, PacketStart);

	if (Dlci != GetDLCI())
	{
		TRACE("Data Link Connection Identifier(DLCI) does not match internal DLCI (%d)\n", GetDLCI());
		return;
	}

	if (GetDLCI() == (GetLMI() & 0x3ff))
		TreeDataLMI(tree, Payload, PacketStart, TVI_ROOT);
	else if (!(GetOptions() & 0x2000)) 
		 /* Not IETF encapsulated frame...just dump the data */
		InsertTreeItem(tree, "DXE Data", TVI_ROOT, PacketStart, GetDataLen());
	else
		TreeDataIFrame(tree, Payload, PacketStart, TVI_ROOT);
}  // end print_fr

void CFRFrame::TreeDataIFrame(CTreeCtrl& tree, PUCHAR Payload, int selStart, HTREEITEM pitem)
{
	int Length = GetDataLen(); 
	CString msg, pt;

//	selStart += Payload[selStart] == 0;
	int PacketStart = selStart;

	GetDescription(pt);
	HTREEITEM root = InsertTreeItem(tree, "Encapsulated Packet Type: " + pt, pitem);

	PUCHAR Packet = Payload + selStart;
	selStart++;

	if (*Packet == 0xB0)
		selStart += TreeDataFRF9(tree, Packet+1, Length - selStart, root, selStart);
	else if ((*Packet == 0xCC) || (*Packet == 0x00))	/* normal IP and Cisco FR IP Encap */
		selStart += TreeDataIP(tree, Packet+1, Length - selStart, root, selStart);
	else if (*Packet == 0x80)
	{
		msg.Format("OUI: %X-%X-%X", Packet[1], Packet[2], Packet[3]);
		InsertTreeItem(tree, msg, root, selStart, selStart + 3);
		msg.Format("PID: %X-%X", Packet[4], Packet[5]);
		InsertTreeItem(tree, msg, root, selStart + 3, selStart + 5);
		selStart += 5;

		int oui = (Packet[1] << 16) | (Packet[2] << 8) | Packet[3];
		int pid = (Packet[4] << 8) | Packet[5];
		if (oui == 0 && pid == 0x8137)
		{
			selStart += TreeDataIPX(tree, Packet+6, root, selStart);
		}
		else if (oui == 0x0080C2 && pid == 0x000D)	/* 802.1 type */
		{
			msg.Format("Sequence Number: 0x%4X", (Packet[6] << 8) | Packet[7]);
			InsertTreeItem(tree, msg, root, selStart, selStart+2);
			msg.Format("Offset: .xxxxxxxxxxxxxxx %4d", (((Packet[8] & 0x7) << 8) | Packet[9])*32);
			InsertTreeItem(tree, msg, root, selStart+2, selStart+4);
			msg.Format("Final: x............... %s", Packet[8] & 0x80 ? "Yes" : "No");
			InsertTreeItem(tree, msg, root, selStart+2, selStart+3);
			selStart += 4;
		}
	}

	tree.SetItemData(root, (selStart << 16) + PacketStart);

	if (Length - selStart > 0)
	{
		HTREEITEM item = InsertTreeItem(tree, pt + " Data", TVI_ROOT, selStart, Length);
		msg.Format("Length %d", Length - selStart);
		InsertTreeItem(tree, msg, item);
	}
}	// end print_iframe

enum eInfoType
{
	itUnknown,
	itReport,
	itLinkIntegrity,
	itPVCStatus,
	itMulticast
};

eInfoType GetInfoElementType(UCHAR Type, CString& msg)
{
	eInfoType type = itUnknown;

	switch (Type) {// information element identifier
		case 0x51 : // CCITT Annex A report type info element
		case 0x01 : // ANSI Annex D report type info element
			msg = "Report";
			type = itReport;
			break;
		case 0x53 : // CCITT Annex A Link integrity
		case 0x19 : // ??????? ANSI Annex D
		case 0x03 : // ANSI Annex D
			msg = "Link Integrity";
			type = itLinkIntegrity;
			break;
		case 0x57 :  // CCITT Annex PVC status
		case 0x07 : // ANSI Annex D
			msg = "Permanent Virtual Circuit(PVC) Status";
			type = itPVCStatus;
			break;
		case 0x05 : // Statacom multicast
			msg = "Multicast";
			type = itMulticast;
			break;
		}	

	return type;
}

void CFRFrame::TreeDataLMI(CTreeCtrl& tree, PUCHAR payload, int PacketStart, HTREEITEM pitem)
{
	PUCHAR LMIStart = payload + PacketStart;
	int length = GetDataLen();
	CString msg;

	GetDescription(msg);
	HTREEITEM rootitem = InsertTreeItem(tree, msg, pitem, PacketStart + 2, PacketStart + 3);

	msg.Format("Protocol Discriminator: %d", LMIStart[0]);
	InsertTreeItem(tree, msg, rootitem, PacketStart, PacketStart + 1);
	msg.Format("Call Reference: %d", LMIStart[1]);
	InsertTreeItem(tree, msg, rootitem, PacketStart + 1, PacketStart + 2);

	UCHAR MessageType = LMIStart[2];
	switch (MessageType >> 4) {
		case 7 : { // miscellaneous messages
			PUCHAR LMIData = LMIStart + 3;
			int offset = PacketStart + 3;

			if (GetLMIType() == lmiAnnexD) {
				InsertTreeItem(tree, "Annex D Lock Shift Byte", rootitem, offset, offset + 1);
				LMIData++;
				offset++;
				}

			int PVCInfoCount = 0; // count of pvc info elements

			while (LMIData < payload + length) {
				CString msgType;
				eInfoType InfoType = GetInfoElementType(LMIData[0], msgType);

				msgType += " Information Element";
				
				if (InfoType == itPVCStatus)
					msg.Format(" #%d", ++PVCInfoCount);
				else
					msg = "";

				HTREEITEM item = InsertTreeItem(tree, msgType + msg, pitem, offset, offset + 1);
				
				msg.Format(" Size: %d", LMIData[1]);
				InsertTreeItem(tree, msgType + msg, item, offset + 1, offset + 2);
				
				switch (InfoType) {// information element identifier
					case itReport :
						switch (LMIData[2]) {
							case 0x00 :
								msg = "Full Status";
								break;
							case 0x01 :
								msg = "Link Integrity Verification Only"; break;
							case 0x02 :
								msg = "Single Permanent Virtual Circuit(PVC) Only"; break;
							default :
								msg = "Unknown";
							}

						InsertTreeItem(tree, "Report Type: " + msg, item, offset + 2, offset + 3);
						break;
					case itLinkIntegrity :
						msg.Format("Send Sequence Number: %d", LMIData[2]);
						InsertTreeItem(tree, msg, item, offset + 2, offset + 3);

						msg.Format("Receive Sequence Number: %d", LMIData[3]);
						InsertTreeItem(tree, msg, item, offset + 3, offset + 4);
						break;
					case itPVCStatus :
						if (GetLMIType() == lmiStrataCom || GetLMIType() == lmiRevision1) { // stratacom/Revision1 formats are different
							int DLCI = (LMIData[2] << 8) + LMIData[3];
							msg.Format("Data Link Connection Indentifier(DLCI) 000000xx xxxxxxxx : %d", DLCI);
							InsertTreeItem(tree, msg, item, offset + 2, offset + 4);

							msg = "New ....x... : ";
							msg += (LMIData[4] & 0x08) > 0 ? "Yes" : "No";
							InsertTreeItem(tree, msg, item, offset + 4, offset + 5);

							msg = "Deleted .....x.. : ";
							msg += (LMIData[4] & 0x04) > 0 ? "Yes" : "No";
							InsertTreeItem(tree, msg, item, offset + 4, offset + 5);

							msg = "Active ......x. : ";
							msg += (LMIData[4] & 0x02) > 0 ? "Yes" : "No";
							InsertTreeItem(tree, msg, item, offset + 4, offset + 5);
	
							msg = "Buffer Allocation Indicator(R) .......x : ";
							msg += (LMIData[4] & 0x01) > 0 ? "Yes" : "No";
							InsertTreeItem(tree, msg, item, offset + 4, offset + 5);

							int bps = (LMIData[5] << 14) + (LMIData[6] << 7) + LMIData[7];
							msg.Format("PVC Bandwidth bits/second: %d", bps);
							InsertTreeItem(tree, msg, item, offset + 5, offset + 8);
							}
						else {
							int DLCI = (LMIData[2] << 4) + ((LMIData[3] >> 3) & 0x7);
							msg.Format("Data Link Connection Indentifier(DLCI) 00xxxxxx 1xxxx000 : %d", DLCI);
							InsertTreeItem(tree, msg, item, offset + 2, offset + 4);
							
							msg = "New ....x... : ";
							msg += (LMIData[4] & 0x08) > 0 ? "Yes" : "No";
							InsertTreeItem(tree, msg, item, offset + 4, offset + 5);

							msg = "Active ......x. : ";
							msg += (LMIData[4] & 0x02) > 0 ? "Yes" : "No";
							InsertTreeItem(tree, msg, item, offset + 4, offset + 5);
							}
						break;
					case itMulticast : { // Statacom multicast
						msg.Format("Multicast Group ID: %d", LMIData[2]);
						InsertTreeItem(tree, msg, item, offset + 2, offset + 3);

						int DLCI = (LMIData[3] << 8) + LMIData[4];
						msg.Format("Multicast Data Link Connection Indentifier(DLCI) 000000xx xxxxxxxx : %d", DLCI);
						InsertTreeItem(tree, msg, item, offset + 3, offset + 5);
					
						DLCI = (LMIData[5] << 8) + LMIData[6];
						msg.Format("Source Data Link Connection Indentifier(DLCI) 000000xx xxxxxxxx : %d", DLCI);
						InsertTreeItem(tree, msg, item, offset + 5, offset + 7);

						msg = "New ....x... : ";
						msg += (LMIData[7] & 0x08) > 0 ? "Yes" : "No";
						InsertTreeItem(tree, msg, item, offset + 7, offset + 8);

						msg = "Deleted .....x.. : ";
						msg += (LMIData[7] & 0x04) > 0 ? "Yes" : "No";
						InsertTreeItem(tree, msg, item, offset + 7, offset + 8);

						msg = "Active ......x. : ";
						msg += (LMIData[7] & 0x02) > 0 ? "Yes" : "No";
						InsertTreeItem(tree, msg, item, offset + 7, offset + 8);
						} break;
					case itUnknown :
						ASSERT(FALSE);
					}	
				
				offset += 2 + LMIData[1];
				LMIData += 2 + LMIData[1];
				}
			} break;
//		case ? : // Call Establishment messages
//		case ? :	// Call Clearing messages
		default :
			InsertTreeItem(tree, "Unknown LMI Frame", pitem, 5, 6);			
			InsertTreeItem(tree, "Remaining Data", pitem, 6, length);
			break;
		}
}	// end print_lmi

int CFRFrame::TreeDataFRF9(CTreeCtrl& tree, PUCHAR dp, int /*length*/, HTREEITEM pitem, int selStart)
{
	PUCHAR DCPHeader = dp;
	CString msg;

	BOOL Bit = (DCPHeader[0] & 0x80) > 0;
	msg = "Extension (E) bit x....... : ";
	msg += Bit == TRUE ? "Set" : "Clear";
	InsertTreeItem(tree, msg, pitem, selStart, selStart + 1);

	Bit = (DCPHeader[0] & 0x40) > 0;
	msg = "Compression mode (C/U) bit .x...... : ";
	msg += Bit == TRUE ? "Compressed" : "Uncompressed";
	InsertTreeItem(tree, msg, pitem, selStart, selStart + 1);

	Bit = (DCPHeader[0] & 0x20) > 0;
	msg = "Reset Acknowledge (R-A) bit ..x..... : ";
	msg += Bit == TRUE ? "Set" : "Clear";
	InsertTreeItem(tree, msg, pitem, selStart, selStart + 1);

	Bit = (DCPHeader[0] & 0x10) > 0;
	msg = "Reset Request (R-R) bit ...x.... : ";
	msg += Bit == TRUE ? "Set" : "Clear";
	InsertTreeItem(tree, msg, pitem, selStart, selStart + 1);

	Bit = (DCPHeader[0] & 0x01) > 0;
	msg = "Control or Data (C/D) bit .......x : ";
	msg += Bit == TRUE ? "Control" : "Data";
	InsertTreeItem(tree, msg, pitem, selStart, selStart + 1);

	if (Bit) 
	{ // DCP Control
	}
	else 
	{ // DCP Data
		UCHAR Sequence = DCPHeader[1];
		msg.Format( "Sequence number: %d (0x%x)", Sequence, Sequence );
		InsertTreeItem(tree, msg, pitem, selStart+1, selStart+2);
	}

	return 2;	// Count of bytes decoded
}

int CIP::TreeDataIP(CTreeCtrl& tree, PUCHAR dp, int length, HTREEITEM pitem, int selStart)
{
	int header_size = (dp[0] & 0x0F) * 4;
	int packet_size = (dp[2] << 8) | dp[3];
	int min = header_size < 20 ? header_size : 20;
	CString msg;

	if (length < min)
	{
		InsertTreeItem(tree, "Garbage IP packet", pitem, selStart, selStart + length);
		return header_size;
	}

	msg.Format("Header Size ....xxxx : %d", header_size);
	InsertTreeItem(tree, msg, pitem, selStart, selStart + 1);

	msg.Format("Packet Size: %d", packet_size);
	InsertTreeItem(tree, msg, pitem, selStart + 2, selStart + 4);

	CString src_addr = MakeIPStr(dp+12);
	InsertTreeItem(tree, "Source: " + src_addr, pitem, selStart+12, selStart+16);

	CString dst_addr = MakeIPStr(dp+16);
	InsertTreeItem(tree, "Destination: " + dst_addr, pitem, selStart+16, selStart+20);

	return header_size;
}

int CIPX::TreeDataIPX(CTreeCtrl& tree, PUCHAR dp, HTREEITEM pitem, int selStart)
{
	int length = (dp[2] << 8) | dp[3];
	CString msg;

	msg.Format("Packet Length: %d", length);
	InsertTreeItem(tree, msg, pitem, selStart+2, selStart+4);
	CString dst_addr = MakeIPXStr(dp+6);
	InsertTreeItem(tree, "Destination: " + dst_addr, pitem, selStart+6, selStart+18);
	CString src_addr = MakeIPXStr(dp+18);
	InsertTreeItem(tree, "Source: " + src_addr, pitem, selStart+18, selStart+30);

	return 30;
}

CString CIP::MakeIPStr(PUCHAR addr)
{
	/* addr must point to the first byte of the IP address */
	CString buffer;

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

CString CIPX::MakeIPXStr(PUCHAR addr)
{
	/* addr must point to the first byte of the IPX address */
	CString buffer;

	buffer.Format("%X%X%X%X/%X%X%X%X%X%X/%X%X",
	addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7], addr[8], addr[9], addr[10], addr[11]);

	return buffer;
}

eLABPCR CX25Frame::GetLAPBCommandOrResponce() const // true if command
{
	UCHAR LAPBAddress = GetData()[0]; // Address Field is BYTE
	BOOL DidReceive = GetTxRx(); // Did we receive this frame
	eLABPCR Command = lapbcrNone; // Command Frame or Responce Frame

	switch (LAPBAddress) {
		case 0x3 : // Address A
		case 0xF : // Address C
			if (GetDXE() == DTE)
				if (DidReceive)
					Command = lapbcrCommand; // command from DCE -> DTE
				else
					Command = lapbcrResponse;// responce from DTE -> DCE
			else
				if (DidReceive)
					Command = lapbcrResponse;// responce from DTE -> DCE
				else
					Command = lapbcrCommand;// command from DCE -> DTE
			break;
		case 0x1 : // Address B
		case 0x7 : // Address D
			if (GetDXE() == DTE)
				if (DidReceive)
					Command = lapbcrResponse; // responce from DCE -> DTE
				else
					Command = lapbcrCommand;// command from DTE -> DCE
			else
				if (DidReceive)
					Command = lapbcrCommand;// command from DTE -> DCE
				else
					Command = lapbcrResponse;// responce from DCE -> DTE
			break;
		default : // Bad Frame
			ASSERT(FALSE); // bad frame
		}

	return Command;
}

void CX25Frame::TreeDataLAPB(CTreeCtrl& tree)
{
	CString msg;

	eLAPBFrameFormat FrameFormat;
	PUCHAR LAPBControl = GetData() + 1; // starts on second byte but is variable length

	if ((LAPBControl[0] & 1) == 0)
		FrameFormat = lapbffI;
	else if ((LAPBControl[0] & 3) == 1)
		FrameFormat = lapbffS;
	else if ((LAPBControl[0] & 3) == 3)
		FrameFormat = lapbffU;
	else
		ASSERT(FALSE); // bad frame

// LAPBSize = size of address + control fields
	int LAPBSize = GetModulus() == G_8 || FrameFormat == lapbffU ? 2 : 3;
// insert main LAPB parent item

	GetLAPBDescription(msg);
	HTREEITEM pitem = InsertTreeItem(tree, "LAPB: " + msg, TVI_ROOT, 0, LAPBSize);

// address field
	eLABPCR Command = GetLAPBCommandOrResponce();

	msg = "Address Field: ";
	msg += Command == lapbcrCommand ? "Command" : "Response";
//	msg += ((GetDXE() == DTE ^ GetTxRx()) ? "DTE -> DCE" : "DCE -> DTE");
	InsertTreeItem(tree, msg, pitem, 0, 1);

// control field
	HTREEITEM item = InsertTreeItem(tree, "Control Field", pitem, 1, LAPBSize);

	UCHAR Oct1 = LAPBControl[0];
	UCHAR Oct2 = LAPBControl[1]; // only has a second octet with Mod 128 I or S frame

	DWORD NofS = (GetModulus() == G_8 ? (Oct1 & 0xF) : Oct1) >> 1;
	DWORD NofR = GetModulus() == G_8 ? (Oct1 >> 5) : (Oct2 >> 1);

	BOOL PollFinal;
	if (GetModulus() == G_8 || FrameFormat == lapbffU)
		PollFinal = (Oct1 & 0x10) > 0;
	else
		PollFinal = (Oct2 & 1) > 0;

	switch (FrameFormat) {
		case lapbffS : // Supervisory frames
		case lapbffI : // Information frames
			if (GetModulus() == G_8) {
				if (FrameFormat == lapbffI) {
					msg.Format("Transmitter Send Sequence Number(N(S)) ....xxx. : %d", NofS);
					InsertTreeItem(tree, msg, item, 1, 2);
					}

				if (Command == TRUE)
					msg = "Poll bit(P) ...x.... : ";
				else
					msg = "Final bit(F) ...x.... : ";
				msg += PollFinal == TRUE ? "Set" : "Clear";
				InsertTreeItem(tree, msg, item, 1, 2);

				msg.Format("Transmitter Receive Sequence Number(N(R)) xxx..... : %d", NofR);
				InsertTreeItem(tree, msg, item, 1, 2);
				}
			else {
				if (FrameFormat == lapbffI) {
					msg.Format("Transmitter Send Sequence Number(N(S)) xxxxxxx. : %d", NofS);
					InsertTreeItem(tree, msg, item, 1, 2);
					}

				if (Command == TRUE)
					msg = "Poll bit(P) .......x : ";
				else
					msg = "Final bit(F) .......x : ";
				msg += PollFinal == TRUE ? "Set" : "Clear";
				InsertTreeItem(tree, msg, item, 2, 3);

				msg.Format("Transmitter Receive Sequence Number(N(R)) xxxxxxx. : %d", NofR);
				InsertTreeItem(tree, msg, item, 2, 3);
				}

			if (FrameFormat == lapbffI)
				TreeDataX25(tree, TVI_ROOT, LAPBSize);

			break;
		case lapbffU : // unnumbered frames
			Oct1 &= 0xEF; //  clear P/F bit

			if (Oct1 == 0x6F || Oct1 == 0x43)
				msg = "Poll bit(P) .......x : ";
			else
				msg = "Final bit(F) .......x : ";
			msg += PollFinal == TRUE ? "Set" : "Clear";
			InsertTreeItem(tree, msg, item, 1, 2);
			
			if (Oct1 == 0x87) { // FRMR
				PUCHAR FRMRInfo = GetData() + LAPBSize;

				msg = "Frame Reject(FRMR) Information(I) Field";
				HTREEITEM item = InsertTreeItem(tree, msg, pitem, LAPBSize, LAPBSize + (GetModulus() == G_8 ? 3 : 5));

				if (GetModulus() == G_8) {
					msg.Format("Rejected Frame Control Field: %#2X", FRMRInfo[0]);
					InsertTreeItem(tree, msg, item, LAPBSize, LAPBSize + 1);

					msg.Format("Current Send State Variable(V(S)) ....xxx. : %d", (FRMRInfo[1] & 0xF) >> 1);
					InsertTreeItem(tree, msg, item, LAPBSize + 1, LAPBSize + 2);

					msg = "C/R ...x.... : ";
					msg += (FRMRInfo[1] & 0x10) >> 4 ? "Response" : "Command";
					InsertTreeItem(tree, msg, item, LAPBSize + 1, LAPBSize + 2);

					msg.Format("Current Receive State Variable(V(R)) xxx..... : %d", (FRMRInfo[1] & 0xE0) >> 5);
					InsertTreeItem(tree, msg, item, LAPBSize + 1, LAPBSize + 2);

					msg.Format("Reason: %#2X", FRMRInfo[2]);
					InsertTreeItem(tree, msg, item, LAPBSize + 2, LAPBSize + 3);
					}
				else {
					msg.Format("Rejected Frame Control Field: %#2X", FRMRInfo[0]);
					InsertTreeItem(tree, msg, item, LAPBSize, LAPBSize + 1);
					msg.Format("Extented Rejected Frame Control Field: %#2X", FRMRInfo[1]);
					InsertTreeItem(tree, msg, item, LAPBSize + 1, LAPBSize + 2);

					msg.Format("Current Send State Variable(V(S)) xxxxxxx. : %d", (FRMRInfo[2] & 0xFE) >> 1);
					InsertTreeItem(tree, msg, item, LAPBSize + 2, LAPBSize + 3);

					msg = "C/R .......x : ";
					msg += FRMRInfo[3] & 0x1 ? "Response" : "Command";
					InsertTreeItem(tree, msg, item, LAPBSize + 3, LAPBSize + 4);

					msg.Format("Current Receive State Variable(V(R)) xxxxxxx. : %d", (FRMRInfo[3] & 0xFE) >> 1);
					InsertTreeItem(tree, msg, item, LAPBSize + 3, LAPBSize + 4);

					msg.Format("Reason: %#2X", FRMRInfo[4]);
					InsertTreeItem(tree, msg, item, LAPBSize + 4, LAPBSize + 5);
					}
				}
			break;
		default :
			ASSERT(FALSE); // bad frame
		}
}  // end print_lapb

void CX25Frame::TreeDataGFI(CTreeCtrl& tree, HTREEITEM pitem, int offset)
{
	CString msg;
	PUCHAR X25Packet = GetData() + offset;
	UCHAR X25GFI = X25Packet[0] >> 4; // GFI
	UCHAR X25PacketType = X25Packet[2]; // packet type

	HTREEITEM item = InsertTreeItem(tree, "General Format Identifier", pitem, offset, offset + 1);

	BOOL bit = (X25GFI & 8) > 0;
	if ((X25PacketType & 1) == 0) // Q bit in data packets
		msg = "Qualifier(Q) bit x....... : ";
	else // A bit in call setup clearing packets
		msg = "Address(A) bit x....... : ";
	msg += bit ? "Set" : "Clear";
	InsertTreeItem(tree, msg, item, offset, offset + 1);

	if (X25PacketType != 0x13 && X25PacketType != 0x17) {
		bit = (X25GFI & 4) > 0;
		msg = "Delivery Confirmation Procedure bit .x...... : ";
		msg += bit ? "Set" : "Clear";
		InsertTreeItem(tree, msg, item, offset, offset + 1);
		}

	bit = X25GFI & 3;
	if (bit == 2)
		msg = "Sequence Numbering: ..10.... : Modulo 128";
	else if (bit == 1)
		msg = "Sequence Numbering: ..01.... : Modulo 8";
	else
		ASSERT(FALSE);
	InsertTreeItem(tree, msg, item, offset, offset + 1);
}

void CX25Frame::TreeDataChannelNumber(CTreeCtrl& tree, HTREEITEM pitem, int offset)
{
	CString msg;
	PUCHAR X25Packet = GetData() + offset;
	DWORD X25Channel = ((X25Packet[0] & 0xF) << 8) + X25Packet[1];
	UCHAR X25PacketType = X25Packet[2]; // packet type

	msg.Format("Channel: %d", X25Channel);
	HTREEITEM item = InsertTreeItem(tree, msg, pitem, offset, offset + 2);

	msg.Format("Logical Channel Group Number ....xxxx : %d", X25Packet[0] & 0x0F);
	InsertTreeItem(tree, msg, item, offset, offset + 1);

	msg.Format("Logical Channel Number: %d", X25Packet[1]);
	InsertTreeItem(tree, msg, item, offset + 1, offset + 2);
}

CString CX25Frame::GetX25Diagnostic(UCHAR DiagCode) const
{
	BOOL load = TRUE;

	CString temp;

	if (DiagCode == 0 || (DiagCode & 0x80) > 1) {
		if (GetTxRx() == FALSE) // transmitting Spider Specific Diag
			load = temp.LoadString(IDS_SpiderDiagBase + DiagCode);
		else
			temp = "Vendor Specific";
		}
	else
		load = temp.LoadString(IDS_DiagnosticBase + DiagCode);

	if (!load)
		temp = "Unknown Diagnostic Code";

	return temp;
}

void CX25Frame::TreeDataX25(CTreeCtrl& tree, HTREEITEM pitem, int offset)
{
	CString msg;
	PUCHAR X25Packet = GetData() + offset; // x.25 packet start
	UCHAR X25PacketType = X25Packet[2]; // X.25 packet type
	BOOL IsDataPacket = (X25PacketType & 1) == 0;
	UCHAR X25SeqNum = (X25Packet[0] >> 4) & 3; // GFI
	UCHAR X25Modulus;
	if (X25SeqNum == 2) // not same as LAPB Modulus 8/128
		X25Modulus = G_128;
	else if (X25SeqNum == 1)
		X25Modulus = G_8;
	else
		ASSERT(FALSE);

// main X.25 tree item
	GetX25Description(msg);
	HTREEITEM item = InsertTreeItem(	tree,
																		"X.25: " + msg,
																		pitem,
																		offset,
																		IsDataPacket ? offset + 3 + (X25Modulus == G_128): GetDataLen());

	if (IsDataPacket) { // Data packet
		TreeDataGFI(tree, item, offset);
		TreeDataChannelNumber(tree, item, offset);

		if (X25Modulus == G_8) {
			msg.Format("Packet Send Sequence Number(P(S)) ....xxx. : %2d", (X25Packet[2] & 0x0F) >> 1);
			InsertTreeItem(tree, msg, item, offset + 2, offset + 3);

			BOOL Bit = (X25Packet[2] & 0x10) > 0;
			msg = "More data bit ...x.... : ";
			msg += Bit == TRUE ? "Set" : "Clear";
			InsertTreeItem(tree, msg, item, offset + 2, offset + 3);

			msg.Format("Packet Receive Sequence Number(P(R)) xxx..... : %2d", (X25Packet[2]&0xE0) >> 5);
			InsertTreeItem(tree, msg, item, offset + 2, offset + 3);
			}
		else {
			msg.Format("Packet Send Sequence Number(P(S)) xxxxxxx. : %2d", X25Packet[2] >> 1);
			InsertTreeItem(tree, msg, item, offset + 2, offset + 3);
	
			BOOL Bit = (X25Packet[3] & 1) > 0;
			msg = "More data bit .......x : ";
			msg += Bit == TRUE ? "Set" : "Clear";
			InsertTreeItem(tree, msg, item, offset + 3, offset + 4);

			msg.Format("Packet Receive Sequence Number(P(R)) xxxxxxx. : %2d", X25Packet[3] >> 1);
			InsertTreeItem(tree, msg, item, offset + 3, offset + 4);
			}

		BOOL QBit = (X25Packet[0] & 0x80) > 0;
		if (QBit) // Qualifier bit set
			TreeDataX29(tree, GetData(), 3 + offset, pitem);
		else {
			int X25DataOffset = (offset + 3) + (X25Modulus == G_128);

/*			
			for (short j = 0; j < GetTraceData()->m_ChannelPVC.GetSize(); j++)
				if (GetTraceData()->m_ChannelPVC[j] == GetCircuit())
					break;

			if (j == GetTraceData()->m_ChannelPVC.GetSize())
*/
				TreeDataUserData(tree, pitem, X25DataOffset, X25PacketType);
/*
			else {
				int Length = GetDataLen();
				int Start = X25DataOffset;

				HTREEITEM pitem = InsertTreeItem(tree, "IP");
				X25DataOffset += TreeDataIP(tree, GetData() + X25DataOffset, Length - X25DataOffset, pitem, X25DataOffset);

				tree.SetItemData(pitem, (X25DataOffset << 16) + Start);

				if (Length - X25DataOffset > 0) {
					HTREEITEM item = InsertTreeItem(tree, "IP Data", TVI_ROOT, X25DataOffset, Length);
					msg.Format("Length %d", Length - X25DataOffset);
					InsertTreeItem(tree, msg, item);
					}
				}
*/
			}
		}
	else 
		switch (X25PacketType) {
			case 0x0F : // call accept
			case 0x0B : // call request
				{
				TreeDataGFI(tree, item, offset);
				TreeDataChannelNumber(tree, item, offset);

				BOOL ABit = (X25Packet[0] & 0x80) > 0; // extended address
				PUCHAR X25AddressBlock = X25Packet + 3;
				int AddressBlockLength = TreeDataAddr(tree, X25AddressBlock, item, offset + 3, ABit, X25PacketType);

				PUCHAR X25Facilities = X25AddressBlock + AddressBlockLength;
				int FacilitiesLength = TreeDataFacility(tree, X25Facilities, item, offset + 3 + AddressBlockLength);

				PUCHAR X25CallUserData = X25Facilities + FacilitiesLength;
				int CUDOffset = offset + 3 + AddressBlockLength + FacilitiesLength;
				TreeDataUserData(tree, item, CUDOffset, X25PacketType);
				}
				break;
			case 0x13 :// clear request
				{
				TreeDataGFI(tree, item, offset);
				TreeDataChannelNumber(tree, item, offset);

				PUCHAR X25ClearReq = X25Packet + 3;
				
				msg = "Clearing Cause: ";
				if ((X25ClearReq[0] & 0x80) > 0)
					msg += "DTE Originated";
				else
					switch (X25ClearReq[0]) {
						case 0x00 : msg += "DTE Originated"; break;
						case NUMBER_BUSY : msg += "Number Busy"; break;
						case OUT_OF_ORDER : msg += "Out of Order"; break;
						case REMOTE_PROCEDURE_ERROR : msg += "Remote Procedure Error"; break;
						case REVERSE_CHARGING_NOT_SUBSCRIBED : msg += "Reverse Charging Acceptance Not Subscribed"; break;
						case INCOMPATIBLE_DESTINATION : msg += "Incompatible Destination"; break;
						case FS_ACCEPTANCE_NOT_SUBSCRIBED : msg += "Fast Select Acceptance Not Subscribed"; break; 
						case SHIP_ABSENT : msg += "Ship Absent"; break;
						case INVALID_FACILITY_REQUEST : msg += "Invalid Facility Request"; break;
						case ACCESS_BARRED : msg += "Access Barred"; break;
						case LOCAL_PROCEDURE_ERROR : msg += "Local Procedure Error"; break;
						case NETWORK_CONGESTION : msg += "Network Congestion"; break;
						case NOT_OBTAINABLE : msg += "Not Obtainable"; break;
						case RPOA_OUT_OF_ORDER : msg += "ROA Out of Order"; break;
						default:
								msg += "Cause Out of Range";
						}
				InsertTreeItem(tree, msg, item, offset + 3, offset + 4);

				if (GetDataLen() > 4 + offset) {
					msg = "Diagnostic Code: " + GetX25Diagnostic(X25ClearReq[1]);
					InsertTreeItem(tree, msg, item, offset + 4, offset + 5);
					}

				if (GetDataLen() > 5 + offset) {
					BOOL ABit = (X25Packet[0] & 0x80) > 0; // extended address
					PUCHAR X25AddressBlock = X25Packet + 5;
					int AddressBlockLength = TreeDataAddr(tree, X25AddressBlock, item, offset + 5, ABit, X25PacketType);

					PUCHAR X25Facilities = X25AddressBlock + AddressBlockLength;
					int FacilitiesLength = TreeDataFacility(tree, X25Facilities, item, offset + 5 + AddressBlockLength);

					PUCHAR X25CallUserData = X25Facilities + FacilitiesLength;
					int CUDOffset = offset + 5 + AddressBlockLength + FacilitiesLength;
					if (GetDataLen() - CUDOffset > 0)
						TreeDataUserData(tree, item, CUDOffset, X25PacketType);
					}
				}
				break;
			case 0x17 : // clear confirm
				{
				TreeDataGFI(tree, item, offset);
				TreeDataChannelNumber(tree, item, offset);

				if (GetDataLen() > 3 + offset) {
					BOOL ABit = (X25Packet[0] & 0x80) > 0; // extended address
					PUCHAR X25AddressBlock = X25Packet + 3;
					int AddressBlockLength = TreeDataAddr(tree, X25AddressBlock, item, offset + 3, ABit, X25PacketType);

					PUCHAR X25Facilities = X25AddressBlock + AddressBlockLength;
					int FacilitiesLength = TreeDataFacility(tree, X25Facilities, item, offset + 3 + AddressBlockLength);
					}
				}
				break;
			case 0x23 : // interrupt packet
				TreeDataChannelNumber(tree, item, offset);
				TreeDataUserData(tree, item, offset + 3, X25PacketType);
				break;
			case 0x1F : // reset confirm
			case 0x27 : // interrupt confirm
				TreeDataChannelNumber(tree, item, offset);
				break;
			case 0x1B : // reset request
				msg = "Resetting Cause: ";
				if (X25Packet[3] & 0x80)
					msg += "DTE Originated"; 
				else 
					switch (X25Packet[3]) {
						case 0x00 : msg += "DTE Originated"; break;
						case ROUT_OF_ORDER : msg += "Out of Order"; break;
						case RREMOTE_PROCEDURE_ERROR : msg += "Remote Procedure Error"; break;
						case RLOCAL_PROCEDURE_ERROR : msg += "Local Procedure Error"; break;
						case RNETWORK_CONGESTION : msg += "Network Congestion"; break;
						case RRMT_DTE_OPERATIONAL : msg += "Remote DTE Operational"; break;
						case RNET_OPERATIONAL : msg += "Network Operational"; break; 
						case RINCOMPATIBLE_DESTINATION : msg += "Incompatible Destination"; break;
						case RNET_OUT_OF_ORDER : msg += "Network Out of Order"; break;
						default:		msg += "Cause Out of Range"; break;
						}
				InsertTreeItem(tree, msg, item, offset + 3, offset + 4);

				if (GetDataLen() > 4 + offset) {
					msg = "Diagnostic Code: " + GetX25Diagnostic(X25Packet[4]);
					InsertTreeItem(tree, msg, item, offset + 4, offset + 5);
					}
				break;
			case 0xFB : // restart request
				msg = "Restarting Cause: ";
				switch(X25Packet[3]) {
					case 0x01 : msg += "Local Procedure Error"; break;
					case 0x03 : msg += "Network Congestion"; break;
					case 0x07 : msg += "Network Operational"; break;
					case 0x7F : msg += "Registration/Cancellation Confirmed"; break;
					default:		msg += "Cause Out of Range"; break;
					}
				InsertTreeItem(tree, msg, item, offset + 3, offset + 4);

				msg = "Diagnostic Code: " + GetX25Diagnostic(X25Packet[4]);
				InsertTreeItem(tree, msg, item, offset + 4, offset + 5);
				break;
			case 0xFF : // restart confirm
				break;
			case 0xF1 : // diagnostic
				{
				msg = "Diagnostic Code: " + GetX25Diagnostic(X25Packet[3]);
				InsertTreeItem(tree, msg, item,  offset + 3, offset + 4);

				msg = "Diagnostic Explanation: ";
				for(int i = 1; i < __min(GetDataLen(), 17); i++) {
					CString temp;

					temp.Format("%X ", X25Packet[3 + i]);
					msg += temp;
					}
				InsertTreeItem(tree, msg, item, offset + 4, offset + 4 + __min(GetDataLen(), 17));
				}
				break;
			case 0xF3 : // Registration request
				{
				PUCHAR X25AddressBlock = X25Packet + 3;
				int AddressBlockLength = TreeDataAddr(tree, X25AddressBlock, item, offset + 3, FALSE, X25PacketType);

				PUCHAR X25RegBlock = X25AddressBlock + AddressBlockLength;
				int RegStart = offset + 3 + AddressBlockLength;

				msg.Format("Registration Length: %d", X25RegBlock[0]);
				InsertTreeItem(tree, msg, item, RegStart, RegStart + 1);
				
				msg = "Registration Field: ";
				for(int i = 1; i < X25RegBlock[0]; i++) {
					CString temp;

					temp.Format("%X ",X25RegBlock[i]);
					msg += temp;
					}
				InsertTreeItem(tree, msg, item, RegStart + 1, RegStart + 1 + X25RegBlock[0]);
				}
				break;
			case 0xF7 : // Registration confirm
				{
				msg = "Cause: ";
				switch (X25Packet[3]) {
					case 0x7F : msg += "Registration/Cancellation Confirmed"; break;
					case 0x03 : msg += "Invalid Facility Request"; break;
					case 0x13 : msg += "Local Procedure Error"; break;
					case 0x05 : msg += "Network Congestion"; break;
					default:		msg += "Cause Out of Range"; break;
					}
				InsertTreeItem(tree, msg, item, offset + 3, offset + 4);

				msg = "Diagnostic Code: " + GetX25Diagnostic(X25Packet[4]);
				InsertTreeItem(tree, msg, item, offset + 4, offset + 5);

				PUCHAR X25AddressBlock = X25Packet + 5;
				int AddressBlockLength = TreeDataAddr(tree, X25AddressBlock, item, offset + 5, FALSE, X25PacketType);

				PUCHAR X25RegBlock = X25AddressBlock + AddressBlockLength;
				int RegStart = offset + 5 + AddressBlockLength;

				msg.Format("Registration Length: %d", X25RegBlock[0]);
				InsertTreeItem(tree, msg, item, RegStart, RegStart + 1);
				
				msg = "Registration Field: ";
				for(int i = 1; i < X25RegBlock[0]; i++) {
					CString temp;

					temp.Format("%X ",X25RegBlock[i]);
					msg += temp;
					}
				InsertTreeItem(tree, msg, item, RegStart + 1, RegStart + 1 + X25RegBlock[0]);
				}
				break;	
			default :
				switch (X25PacketType & 0xF) {
					case 0x1 : // RR
					case 0x5 : // RNR
					case 0x9 : // REJ
						TreeDataChannelNumber(tree, item, offset);
		
						if (X25Modulus == G_8) {
							msg.Format("Packet Receive Sequence Number(P(R)) xxx..... : %d", X25Packet[2] >> 5);
							InsertTreeItem(tree, msg, item, offset + 2, offset + 3);
							}
						else {
							msg.Format("Packet Receive Sequence Number(P(R)) xxxxxxx. : %d", X25Packet[3] >> 1);
							InsertTreeItem(tree, msg, item, offset + 3, offset + 4);
							}
						break;
					default :
						ASSERT(FALSE);
					}
			}
}  // end print_x25

CString GetX29Code(unsigned char c)
{
	CString msg;

	switch (c) {
		case 0x00 : msg = "Parameter Indication"; break;
		case 0x02 : msg = "Set Parameters"; break;
		case 0x04 : msg = "Read Parameters"; break;
		case 0x06 : msg = "Set and Read Parameters"; break;     
		case 0x01 : msg = "Indication to Clear Message"; break;   
		case 0x03 : msg = "Indication of Break Message"; break;
		case 0x07 : msg = "Reselection PAD Message"; break;
		case 0x05 : msg = "Error PAD Message"; break; 
		case 0x08 : msg = "Reselected TOA/NPI message"; break; 
		default   :	msg = "Unknown message type"; break;    
		}

	return msg;
}

void CX25Frame::TreeDataX29(CTreeCtrl& tree, PUCHAR dp, int offset, HTREEITEM pitem)
{
	int len = GetDataLen() - offset;
	CString msg;

	msg.Format("X.29 Code: %s", (LPCSTR)GetX29Code(dp[3]));
	HTREEITEM item = InsertTreeItem(tree, msg, pitem, 3, 4);

	switch (dp[3]) {
		case 0x01 :  case 0x03 : case 0x07 : case 0x08 : default : return;    
		case 0x05 :
			switch (dp[4]) {
				case 0x00 : msg = "No additional Information"; break;
				case 0x01 : msg = "The parameter reference does not exist or has not been implimented in the pad"; break;
				case 0x02 : msg = "The parameter is invalid or has not been implemented in the pad"; break;
				case 0x03 : msg = "The parameter cannot be altered from the current setting"; break;
				case 0x04 : msg = "The parameter is read only"; break;
				case 0x05 : msg = "The parameter follows an invalid parameter separator"; break;
				default :		msg.Format("Value %X is unknown", dp[1]); break;
				}
			InsertTreeItem(tree, msg, item, 4, 5);
			if (dp[4] == 0 || dp[4] > 5)
				return;
			
			switch(dp[5]) {
				case 0x00 : msg = "Received PAD Message containing less than eight bits"; break;
				case 0x01 : msg = "Unrecognized code received in PAD message"; break;
				case 0x02 : msg = "Parameter field format of received PAD message was incorrect or incompatible with message code"; break;
				case 0x03 : msg = "Received PAD message did not contain an integral number of octets"; break;
				case 0x04 : msg = "Received parameter indication PAD message was unsolicited"; break;
				case 0x05 : msg = "Received PAD message was too long"; break;
				case 0x06 : msg = "Unauthorized reselection PAD message"; break;
				default:		msg = "Reason unknown"; break;
				}
			InsertTreeItem(tree, msg, item, 5, 6);
			return; 
		}
	
	len--;
	for(int i = 4; i < len/2 + 4; i+=2) {
		msg.Format("Parameter Number: %2X = %2X", dp[i], dp[i+1]);
		InsertTreeItem(tree, msg, item, i, i+2);
		}
}  // end print_X29

int CX25Frame::TreeDataAddr(CTreeCtrl& tree, PUCHAR dp, HTREEITEM pitem,
														int selStart, BOOL abit, UCHAR PacketType)
{
	CString msg, temp;
	int called_dte_len  = abit ? dp[0] : (dp[0] & 0x0F);
	int calling_dte_len = abit ? dp[1] : (dp[0] >> 4);
	
	int addrstart = abit ? 2 : 1;
	dp += addrstart;   

	if (PacketType == 0xF7 || PacketType == 0xF3)
		msg = "DTE Adresss: ";
	else
		msg = "Called DTE Address: ";

	for (int j = 0; j < called_dte_len; j++) {
		if (j & 0x01) {
			temp.Format("%1X", dp[0] & 0x0f);
			dp++;
			}
		else
			temp.Format("%1X", dp[0] >> 4);
		msg += temp;
		}

	if (called_dte_len == 0)
		InsertTreeItem(tree, msg, pitem);
	else
		InsertTreeItem(	tree, msg, pitem,
										selStart + addrstart,
										selStart + addrstart + (called_dte_len + 1) / 2); 

	if (PacketType == 0xF7 || PacketType == 0xF3)
		msg = "DCE Adresss: ";
	else
		msg = "Calling DTE Address : "; 

	for (; j < (calling_dte_len+called_dte_len); j++) {
		if (j & 0x01) {
			temp.Format("%1X", dp[0] & 0x0f);
			dp++;
			}
		else 
			temp.Format("%1X", dp[0] >> 4);
		msg += temp;
		}
	
	if (calling_dte_len == 0)
		InsertTreeItem(tree, msg, pitem);
	else
		InsertTreeItem(tree, msg, pitem,
								selStart + addrstart + called_dte_len / 2, 
								selStart + addrstart + (calling_dte_len+called_dte_len + 1) / 2); 

	return (calling_dte_len + called_dte_len + 1) / 2 + addrstart;
}  // end print_addr

typedef void (*PROCESSFACILITYBYTE)(CString&, UINT, PUCHAR, int&);

void ChargeReqProcessFacilityByte(CString& msg, UINT, PUCHAR CurrentFacility, int& index)
{
	msg = "Charging Information: ";
	if ((CurrentFacility[index] & 1) > 0)
		msg += "Requested";
	else
		msg += "Not Requested";
}

void SpecialProcessFacilityByte(CString& msg, UINT, PUCHAR CurrentFacility, int& index)
{
	msg = "Reverse Charging: ";
	msg += (CurrentFacility[index] & 1) > 0 ? "Requested" : "Not Requested";

	switch (CurrentFacility[index] & 0x30) {
		case 0x00 : msg += "; ICRD: Not Selected"; break;
		case 0x01 : msg += "; ICRD: Prevention Requested"; break;
		case 0x10 : msg += "; ICRD: Allowance Requested"; break;
		case 0x11 : msg += "; ICRD: Not Allowed"; break;
		}

	switch (CurrentFacility[index] >> 6) {
		case 0 : msg += "; Fast Select: Not Requested"; break;
		case 2 : msg += "; Fast Select: No Response Restriction" ; break; 
		case 3 : msg += "; Fast Select: Response Restricted"; break; 
		} 
}

void UnknownProcessFacilityByte(CString& msg, UINT, PUCHAR CurrentFacility, int& index)
{
	msg.Format("Octet %d: %#2X", index, CurrentFacility[index]);
}

void BasicThClassProcessFacilityByte(CString& msg, UINT, PUCHAR CurrentFacility, int& index)
{
	CString temp;

	temp.LoadString(IDS_ThClassBase + (CurrentFacility[index] >> 4));
	msg = "Throughput Class: Called DTE --> " + temp;

	temp.LoadString(IDS_ThClassBase + (CurrentFacility[index] & 0xF));
	msg += " Calling DTE --> " + temp;
}

void ExtendedThClassProcessFacilityByte(CString& msg, UINT, PUCHAR CurrentFacility, int& index)
{
	msg = "Throughput Class: ";
	
	if (index == 1)
		msg += "Calling DTE --> ";
	else
		msg += "Called DTE --> ";
	
	CString temp;
	temp.LoadString(IDS_ThClassBase + CurrentFacility[index]);
	msg += temp;
}

void ExtendedROAProcessFacilityByte(CString& msg, UINT MsgID, PUCHAR CurrentFacility, int& index)
{
	msg.Format(MsgID + 1, (1 + index - 1) / 2, CurrentFacility[index], CurrentFacility[index]+1);
	index++;
}

void TransitDelayProcessFacilityByte(CString& msg, UINT MsgID, PUCHAR CurrentFacility, int& index)
{
	msg.Format(MsgID + 1, (CurrentFacility[index] << 8) + CurrentFacility[index]+1);
	index++;
}

void DoubleByteProcessFacilityByte(CString& msg, UINT MsgID, PUCHAR CurrentFacility, int& index)
{
	msg.Format(MsgID + 1, CurrentFacility[index], CurrentFacility[index]+1);
	index++;
}

void PacketProcessFacilityByte(CString& msg, UINT MsgID, PUCHAR CurrentFacility, int& index)
{
	msg.Format(MsgID + index, 1 << CurrentFacility[index]);
}

void GenericProcessFacilityByte(CString& msg, UINT MsgID, PUCHAR CurrentFacility, int& index)
{
	msg.Format(MsgID + index, CurrentFacility[index]);
}

void TreeDataFacilityClass(	CTreeCtrl& tree, PUCHAR& CurrentFacility, HTREEITEM pitem, DWORD& CurrentOffset,
														int FacilitySize, UINT MsgID, PROCESSFACILITYBYTE pPFB)
{
	CString msg;

	msg.LoadString(MsgID);
	HTREEITEM item = InsertTreeItem(tree, msg, pitem, CurrentOffset, CurrentOffset + 1); 

	if (FacilitySize == 0) {
		FacilitySize = CurrentFacility[1];
		CurrentFacility++;
		CurrentOffset++;
		}

	for (int i = 1; i < FacilitySize; i++) {
		(*pPFB)(msg, MsgID, CurrentFacility, i);
		InsertTreeItem(tree, msg, item, CurrentOffset + i, CurrentOffset + i + 1); 
		}

	CurrentFacility += FacilitySize;
	CurrentOffset += FacilitySize;
}

int CX25Frame::TreeDataFacility(CTreeCtrl& tree, PUCHAR FacilityBase, HTREEITEM pitem, int selStart)
{
	CString msg, temp;
	int FacilitiesLength = FacilityBase[0];

	HTREEITEM item = InsertTreeItem(tree, "Facilities", pitem, selStart, selStart + FacilitiesLength + 1); 

	msg.Format("Facilities Length: %d", FacilitiesLength);
	InsertTreeItem(tree, msg, item, selStart, selStart + 1); 

	PUCHAR CurrentFacility = FacilityBase + 1; // pointer to first byte in facility
	DWORD CurrentOffset = selStart + 1; // offset to byte byte in facility in the frame
	
	while (CurrentFacility < FacilityBase + FacilitiesLength + 1) {
		UINT MsgID;
		PROCESSFACILITYBYTE pPFB = GenericProcessFacilityByte;

		int FacilitySize;
		switch (CurrentFacility[0] >> 6) {
			case 0 : FacilitySize = 2; break; // Class A
			case 1 : FacilitySize = 3; break; // Class B
			case 2 : FacilitySize = 4; break; // Class C
			case 3 : FacilitySize = 0; break; // Class D
			}

		switch (CurrentFacility[0]) {
			case 0x42 : // packet size
				MsgID = IDS_FacilityPacket; pPFB = PacketProcessFacilityByte; break;
			case 0x43 : // window size
				MsgID = IDS_FacilityWindow; break;
			case 0x02 : // Basic Throughput Class neg.
				MsgID = IDS_FacilityBasicThClassNeg; pPFB = BasicThClassProcessFacilityByte; break;
			case 0x4C : // Extended Throughput Class neg.
				MsgID = IDS_FacilityExtendedThClassNeg; pPFB = ExtendedThClassProcessFacilityByte; break;
			case 0x03 : // Basic CUG
				MsgID = IDS_FacilityBasicCUG; break;
			case 0x47 : // Extended CUG
				MsgID = IDS_FacilityExtendedCUG; pPFB = DoubleByteProcessFacilityByte; break;
			case 0x09 : // Basic GUG with outgoing access
				MsgID = IDS_FacilityBasicCUGOutgoing; break;
			case 0x48 : // Extended GUG with outgoing access
				MsgID = IDS_FacilityExtendedCUGOutgoing; pPFB = DoubleByteProcessFacilityByte; break;
			case 0x41 : // Bilateral CUG
				MsgID = IDS_FacilityBilateralCUG; pPFB = DoubleByteProcessFacilityByte; break;
			case 0x01 : // Reverse charging, Fast Select, ICRD status selection
				MsgID = IDS_FacilitySpecial; pPFB = SpecialProcessFacilityByte; break;
			case 0xC6 : // NUI selection
				MsgID = IDS_FacilityNUI; pPFB = UnknownProcessFacilityByte; break;
			case 0x04 : // charging information request
				MsgID = IDS_FacilityChargingInfoRequest; pPFB = ChargeReqProcessFacilityByte; break;
			case 0xC5 : // receiving charging information: monetary unit
				MsgID = IDS_FacilityChargingInfoMU; pPFB = UnknownProcessFacilityByte; break;
			case 0xC2 : // receiving charging information: segment count
				MsgID = IDS_FacilityChargingInfoSC; pPFB = UnknownProcessFacilityByte; break;
			case 0xC1 : // receiving charging information: call duration
				MsgID = IDS_FacilityChargingInfoCD; pPFB = UnknownProcessFacilityByte; break;
			case 0x44 : // basic format ROA selection
				MsgID = IDS_FacilityROA; break;
			case 0xC4 : // extended format ROA selection
				MsgID = IDS_FacilityExtendedROA; pPFB = ExtendedROAProcessFacilityByte; break;
			case 0xD1 : // call deflection selection
				MsgID = IDS_FacilityCallDeflection; break;
			case 0xC3 : // call redirection or deflection notification
				MsgID = IDS_FacilityCallRedirection; break;
			case 0x08 : // called line address modified notification
				MsgID = IDS_FacilityCalledLineAddress; break;
			case 0x49 : // transit delay selection and indication
				MsgID = IDS_FacilityTransitDelay; pPFB = TransitDelayProcessFacilityByte; break;
			case 0x00 : // marker
				MsgID = IDS_FacilityUnknown; break;
			case 0xFF : // reserved
				CurrentFacility++;
				CurrentOffset++;
				continue;
			default : // unknown
				MsgID = IDS_FacilityUnknown; pPFB = UnknownProcessFacilityByte; break;
			}
		
		TreeDataFacilityClass(tree, CurrentFacility, item, CurrentOffset, FacilitySize, MsgID, pPFB);
		}

	return FacilitiesLength + 1;
}  // end print_facility

void CX25Frame::TreeDataUserData(	CTreeCtrl& tree, HTREEITEM pitem,
																	int selStart, UCHAR PacketType)
{
	int len = GetDataLen() - selStart;

	if (len > 0) {
		CString msg;

		switch (PacketType) {
			case 0x0B : case 0x0F : msg = "Call "; break;
			case 0x13 : msg = "Clear "; break;
			case 0x23 : msg = "Interrupt "; break;
			}
		msg += "User Data";

		HTREEITEM item = InsertTreeItem(tree, msg, pitem, selStart, selStart + len); 
		msg.Format("Length: %d", len);
		InsertTreeItem(tree, msg, item);
		}
}

CTraceFrameBuffer::CTraceFrameBuffer() : m_FrameOffset()
{
	m_BufferSize = AfxGetApp()->GetProfileInt(
		c_pszKeySettings,
		c_pszValueBufferSize,
		cDefaultBufferSize);
	m_FrameSize = AfxGetApp()->GetProfileInt(
		c_pszKeySettings,
		c_pszValueFrameSize,
		cDefaultFrameSize);

	m_BytesInBuffer = 0;
	
	m_Buffer = (PUCHAR)malloc(m_BufferSize);
	if (m_Buffer == NULL)
		AfxThrowMemoryException();

	m_FrameOffset.SetSize(1, 100);
	m_FrameOffset[0] = 0;
}

CTraceFrameBuffer::~CTraceFrameBuffer()
{
	free(m_Buffer);
}

void CTraceFrameBuffer::NewFilter(CFrame *pFrame, CDWordArray& Filtered, DWORD mask, CDWordArray *circuit)
{
	if (mask == fmALL) {
		Filtered.SetSize(GetNumFrames(), 100);
		for (int i = 0; i < GetNumFrames(); i++)
			Filtered.SetAt(i, i);
		}
	else {
		CWaitCursor wait;

		Filtered.SetSize(0, 100);
		for (int i = 0; i < GetNumFrames(); i++) {
			pFrame->SetData(GetFrameData(i));

			if (pFrame->FilterTest(mask, circuit))
				Filtered.Add(i);
			}
		}
}

sFrameBase *CTraceFrameBuffer::AddFrame(const strbuf& Control, const strbuf& data)
{
	strbuf Data = data;

	Data.len = m_FrameSize == 0 ? Data.len : __min((int)m_FrameSize, Data.len);
	WORD newSize = sFrameBaseSize + Data.len + Control.len;
	DWORD newOffset = m_FrameOffset[m_FrameOffset.GetUpperBound()];

// make room for the new frame
// circular buffer only works if we wrap
	while (m_FrameOffset.GetSize() > 1) {
		if (newOffset <= m_FrameOffset[0]) {
			int i = 0;
			while (newOffset + newSize > m_FrameOffset[i] && (m_FrameOffset[i] > 0 || newOffset == 0)) {
				sFrameBase *delFrame = (sFrameBase *)&m_Buffer[m_FrameOffset[i]];

				m_BytesInBuffer -= delFrame->m_DataLen;
				i++;
				}
			if (i > 0)
				m_FrameOffset.RemoveAt(0, i);
			}

		if (newOffset + newSize >= m_BufferSize)
			newOffset = m_FrameOffset[m_FrameOffset.GetUpperBound()] = 0;
		else
			break;
		}
	
// insert new frame
	sFrameBase *newFrame = (sFrameBase *)&m_Buffer[newOffset];
	newFrame->m_DataLen = Data.len;
	newFrame->m_ControlLen = Control.len;

	memcpy(newFrame->m_Data, Data.buf, Data.len);
	memcpy(&newFrame->m_Data[Data.len], Control.buf, Control.len);

	m_FrameOffset.Add(newOffset + newSize);
	m_BytesInBuffer += Data.len;

	return newFrame;
}

void CTraceFrameBuffer::Serialize(CArchive& ar)
{
	DWORD NumFrames = GetNumFrames();
	if (ar.IsStoring()) {
		ar << NumFrames;
		for (DWORD i = 0; i < NumFrames; i++) {
			sFrameBase *frame = GetFrameData(i);
			ar.Write(frame, sFrameBaseSize + frame->m_DataLen + frame->m_ControlLen);
			}
		}
	else {
		DWORD fsize =  ar.GetFile()->GetLength();
// the 32k is for the part of the file that is not the frame buffer
		if (m_BufferSize < fsize - (32 * 1024))
			m_Buffer = (PUCHAR)realloc(m_Buffer, fsize);

		if (m_Buffer == NULL)
			AfxThrowMemoryException();

		ar >> NumFrames;

// just a panic check		
		if (NumFrames > fsize / (sFrameBaseSize + 18))
			AfxThrowArchiveException(CArchiveException::badClass, ar.GetFile()->GetFileName());

		DWORD frameend = 0;

		m_FrameOffset.SetSize(NumFrames);
		for (DWORD i = 0; i < NumFrames; i++) {
			m_FrameOffset.SetAt(i, frameend);

			sFrameBase *frame = (sFrameBase *)&m_Buffer[frameend];
			frameend += ar.Read(frame, sFrameBaseSize);
			frameend += ar.Read(&frame->m_Data, frame->m_DataLen + frame->m_ControlLen);
			m_BytesInBuffer += frame->m_DataLen;
			}
		m_FrameOffset.Add(frameend);

// only makes buffer smaller
		m_Buffer = (PUCHAR)realloc(m_Buffer, frameend);
		}
}

BOOL CTraceFrameBuffer::RenumberFrames(CFrame *Scratch)
{
	DWORD last,
				StartSeqNum,
				RealBufferSize = 0;

	for (int i = 0; i < GetNumFrames(); i++) {
		sFrameBase *Frame = GetFrameData(i);

		RealBufferSize += Frame->m_DataLen + Frame->m_ControlLen + sFrameBaseSize;
		Scratch->SetData(Frame);

		if (i == 0) {
			last = StartSeqNum = Scratch->GetSeqNum();
			StartSeqNum--;
			}

		if ((last & 0x8000) && !(Scratch->GetSeqNum() & 0x8000))
			StartSeqNum -= 0x10000;
		
		Scratch->SetFrameNum(Scratch->GetSeqNum() - StartSeqNum);

		last = Scratch->GetSeqNum();
		}

// if there is enough extra room in the frame buffer let it go
// there is a per frame overhead not counted in m_BytesInBuffer
// subtrace an extra 32k for error
// we only want to realloc if the buffer was never filled to capacity
// only makes buffer smaller
	if (RealBufferSize < m_BufferSize - (32 * 1024))
		m_Buffer = (PUCHAR)realloc(m_Buffer, RealBufferSize);

	return i > 0;
}

