//--------------------------------------------------------------------
// NtpParser - implementation
// Copyright (C) Microsoft Corporation, 2000
//
// Created by: Louis Thomas (louisth), 2-29-00
// Based upon the parser created by kumarp, 23-June-1999
// 
// NTP parser for NetMon
//

#include <windows.h>
#include <netmon.h>
#include <parser.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "..\lib\EndianSwap.inl"

//#define MODULEPRIVATE static // so statics show up in VC
#define MODULEPRIVATE          // statics don't show up in ntsd either!

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


//--------------------------------------------------------------------
// Forward declarations

VOID WINAPIV Ntp_FormatSummary(LPPROPERTYINST pPropertyInst);
VOID WINAPIV Ntp_FormatNtpTime(LPPROPERTYINST pPropertyInst);
VOID WINAPIV Ntp_FormatStratum(LPPROPERTYINST pPropertyInst);
VOID WINAPIV Ntp_FormatPollInterval(LPPROPERTYINST pPropertyInst);
VOID WINAPIV Ntp_FormatPrecision(LPPROPERTYINST pPropertyInst);
VOID WINAPIV Ntp_FormatRootDelay(LPPROPERTYINST pPropertyInst);
VOID WINAPIV Ntp_FormatRootDispersion(LPPROPERTYINST pPropertyInst);
VOID WINAPIV Ntp_FormatRefId(LPPROPERTYINST pPropertyInst);


//--------------------------------------------------------------------
// Property Value Labels

// Leap Indicator 
LABELED_BYTE NtpLIVals[]={
    {0xc0, NULL},
    {0x00, "LI: no warning"},
    {0x40, "LI: last minute has 61 seconds"},
    {0x80, "LI: last minute has 59 seconds"},
    {0xc0, "LI: clock not synchronized"},
};
SET NtpLISet={ARRAYSIZE(NtpLIVals), NtpLIVals};

// Version
LABELED_BYTE NtpVersionVals[]={
    {0x38, NULL},
    {0x00, "Version: 0"},
    {0x08, "Version: 1"},
    {0x10, "Version: 2"},
    {0x18, "Version: 3"},
    {0x20, "Version: 4"},
    {0x28, "Version: 5"},
    {0x30, "Version: 6"},
    {0x38, "Version: 7"},
};
SET NtpVersionSet={ARRAYSIZE(NtpVersionVals), NtpVersionVals};

// Mode
LABELED_BYTE NtpModeVals[]={
    {7, NULL},
    {0, "Mode: reserved"},
    {1, "Mode: symmetric active"},
    {2, "Mode: symmetric passive"},
    {3, "Mode: client"},
    {4, "Mode: server"},
    {5, "Mode: broadcast"},
    {6, "Mode: reserved for NTP control message"},
    {7, "Mode: reserved for private use"},
};
SET NtpModeSet={ARRAYSIZE(NtpModeVals), NtpModeVals};

enum {
    NTP_MODE_Reserved=0,
    NTP_MODE_SymmetricActive,
    NTP_MODE_SymmetricPassive,
    NTP_MODE_Client,
    NTP_MODE_Server,
    NTP_MODE_Broadcast,
    NTP_MODE_Control,
    NTP_MODE_Private,
};

//--------------------------------------------------------------------
// property ordinals (These must be kept in sync with the contents of NtpPropertyTable)
enum {
    Ntp_Summary=0,
    Ntp_LeapIndicator,
    Ntp_Version,
    Ntp_Mode,
    Ntp_Stratum,
    Ntp_PollInterval,
    Ntp_Precision,
    Ntp_RootDelay,
    Ntp_RootDispersion,
    Ntp_RefId,
    Ntp_ReferenceTimeStamp,
    Ntp_OriginateTimeStamp,
    Ntp_ReceiveTimeStamp,
    Ntp_TransmitTimeStamp
};

// Properties
PROPERTYINFO NtpPropertyTable[]={
    {
        0, 0,
        "Summary",
        "Summary of the NTP Packet",
        PROP_TYPE_SUMMARY,
        PROP_QUAL_NONE,
        NULL,
        80,                     // max string size
        Ntp_FormatSummary
    }, {
        0, 0,
        "LI",
        "Leap Indicator",
        PROP_TYPE_BYTE,
        PROP_QUAL_LABELED_BITFIELD,
        &NtpLISet,
        80,
        FormatPropertyInstance
    }, {
        0, 0,
        "Version",
        "NTP Version",
        PROP_TYPE_BYTE,
        PROP_QUAL_LABELED_BITFIELD,
        &NtpVersionSet,
        80,
        FormatPropertyInstance
    }, {
        0, 0,
        "Mode",
        "Mode",
        PROP_TYPE_BYTE,
        PROP_QUAL_LABELED_BITFIELD,
        &NtpModeSet,
        80,
        FormatPropertyInstance
    }, {
        0, 0,
        "Stratum",
        "Stratum",
        PROP_TYPE_BYTE,
        PROP_QUAL_NONE,
        NULL,
        80,
        Ntp_FormatStratum
    }, {
        0, 0,
        "Poll Interval",
        "Maximum interval between two successive messages",
        PROP_TYPE_BYTE,
        PROP_QUAL_NONE,
        NULL,
        80,
        Ntp_FormatPollInterval
    }, {
        0, 0,
        "Precision",
        "Precision of the local clock",
        PROP_TYPE_BYTE,
        PROP_QUAL_NONE,
        NULL,
        80,
        Ntp_FormatPrecision
    }, {
        0, 0,
        "Root Delay",
        "Total roundtrip delay to the primary reference source",
        PROP_TYPE_DWORD,
        PROP_QUAL_NONE,
        NULL,
        80,
        Ntp_FormatRootDelay
    }, {
        0, 0,
        "Root Dispersion",
        "Nominal error relative to the primary reference source",
        PROP_TYPE_DWORD,
        PROP_QUAL_NONE,
        NULL,
        80,
        Ntp_FormatRootDispersion
    }, {
        0, 0,
        "Reference Identifier",
        "Reference source identifier",
        PROP_TYPE_DWORD,
        PROP_QUAL_NONE,
        NULL,
        80,
        Ntp_FormatRefId
    }, {
        0, 0,
        "Reference Timestamp",
        "Time server was last synchronized",
        PROP_TYPE_LARGEINT,
        PROP_QUAL_NONE,
        NULL,
        150,
        Ntp_FormatNtpTime
    }, {
        0, 0,
        "Originate Timestamp",
        "Time at client when packet was transmitted",
        PROP_TYPE_LARGEINT,
        PROP_QUAL_NONE,
        NULL,
        150,
        Ntp_FormatNtpTime
    }, {
        0, 0,
        "Receive   Timestamp",
        "Time at server when packet was received",
        PROP_TYPE_LARGEINT,
        PROP_QUAL_NONE,
        NULL,
        150,
        Ntp_FormatNtpTime
   }, {
        0, 0,
        "Transmit  Timestamp",
        "Time at server when packet was transmitted",
        PROP_TYPE_LARGEINT,
        PROP_QUAL_NONE,
        NULL,
        150,
        Ntp_FormatNtpTime
    },
};

//####################################################################

//--------------------------------------------------------------------
VOID WINAPIV Ntp_FormatSummary(LPPROPERTYINST pPropertyInst) {

    BYTE bMode=(*pPropertyInst->lpByte)&7;

    switch (bMode) {
    case NTP_MODE_Client:
        lstrcpy(pPropertyInst->szPropertyText, "Client request");
        break;
            
    case NTP_MODE_Server:
        lstrcpy(pPropertyInst->szPropertyText, "Server response");
        break;

    case NTP_MODE_SymmetricActive:
        lstrcpy(pPropertyInst->szPropertyText, "Active request");
        break;

    case NTP_MODE_SymmetricPassive:
        lstrcpy(pPropertyInst->szPropertyText, "Passive reponse");
        break;

    case NTP_MODE_Broadcast:
        lstrcpy(pPropertyInst->szPropertyText, "Time broadcast");
        break;

    default:
        lstrcpy(pPropertyInst->szPropertyText, "Other NTP packet");
        break;
   }
}

//--------------------------------------------------------------------
VOID WINAPIV Ntp_FormatStratum(LPPROPERTYINST pPropertyInst) {

    unsigned __int8 nStratum=(*pPropertyInst->lpByte);

    char * szMeaning;
    if (0==nStratum) {
        szMeaning="unspecified or unavailable";
    } else if (1==nStratum) {
        szMeaning="primary reference (syncd by radio clock)";
    } else if (nStratum<16) {
        szMeaning="secondary reference (syncd by NTP)";
    } else {
        szMeaning="reserved";
    }
    wsprintf(pPropertyInst->szPropertyText, "Stratum: 0x%02X = %u = %s", nStratum, nStratum, szMeaning);
}

//--------------------------------------------------------------------
VOID WINAPIV Ntp_FormatPollInterval(LPPROPERTYINST pPropertyInst) {
    char * szMeaning;
    char szBuf[30];

    signed __int8 nPollInterval=(*pPropertyInst->lpByte);

    if (0==nPollInterval) {
        szMeaning="unspecified";
    } else if (nPollInterval<4 || nPollInterval>14) {
        szMeaning="out of valid range";
    } else {
        wsprintf(szBuf, "%ds", 1<<nPollInterval);
        szMeaning=szBuf;
    }
    wsprintf(pPropertyInst->szPropertyText, "Poll Interval: 0x%02X = %d = %s", (unsigned __int8)nPollInterval, nPollInterval, szMeaning);
}

//--------------------------------------------------------------------
VOID WINAPIV Ntp_FormatPrecision(LPPROPERTYINST pPropertyInst) {
    char * szMeaning;
    char szBuf[30];

    signed __int8 nPrecision=(*pPropertyInst->lpByte);

    if (0==nPrecision) {
        szMeaning="unspecified";
    } else if (nPrecision>-2 || nPrecision<-31) {
        szMeaning="out of valid range";
    } else {
        szMeaning=szBuf;
        char * szUnit="s";
        double dTickInterval=1.0/(1<<(-nPrecision));
        if (dTickInterval<1) {
            dTickInterval*=1000;
            szUnit="ms";
        }
        if (dTickInterval<1) {
            dTickInterval*=1000;
            szUnit="�s";
        }
        if (dTickInterval<1) {
            dTickInterval*=1000;
            szUnit="ns";
        }
        sprintf(szBuf, "%g%s per tick", dTickInterval, szUnit);
    }
    wsprintf(pPropertyInst->szPropertyText, "Precision: 0x%02X = %d = %s", (unsigned __int8)nPrecision, nPrecision, szMeaning);
}

//--------------------------------------------------------------------
VOID WINAPIV Ntp_FormatRootDelay(LPPROPERTYINST pPropertyInst) {
    char * szMeaning;
    char szBuf[30];

    DWORD dwRootDelay=EndianSwap((unsigned __int32)*pPropertyInst->lpDword);

    if (0==dwRootDelay) {
        szMeaning="unspecified";
    } else {
        szMeaning=szBuf;
        sprintf(szBuf, "%gs", ((double)((signed __int32)dwRootDelay))/0x00010000);
    }

    wsprintf(pPropertyInst->szPropertyText, "Root Delay: 0x%04X.%04Xs = %s", dwRootDelay>>16, dwRootDelay&0x0000FFFF, szMeaning);
}

//--------------------------------------------------------------------
VOID WINAPIV Ntp_FormatRootDispersion(LPPROPERTYINST pPropertyInst) {
    char * szMeaning;
    char szBuf[30];

    DWORD dwRootDispersion=EndianSwap((unsigned __int32)*pPropertyInst->lpDword);

    if (0==dwRootDispersion) {
        szMeaning="unspecified";
    } else {
        szMeaning=szBuf;
        sprintf(szBuf, "%gs", ((double)((signed __int32)dwRootDispersion))/0x00010000);
    }

    wsprintf(pPropertyInst->szPropertyText, "Root Dispersion: 0x%04X.%04Xs = %s", dwRootDispersion>>16, dwRootDispersion&0x0000FFFF, szMeaning);
}

//--------------------------------------------------------------------
VOID WINAPIV Ntp_FormatRefId(LPPROPERTYINST pPropertyInst) {
    char * szMeaning;
    char szBuf[30];

    DWORD dwRefID=EndianSwap((unsigned __int32)*pPropertyInst->lpDword);
    unsigned __int8 nStratum=*(pPropertyInst->lpByte-11);
    unsigned int nVersion=*(pPropertyInst->lpByte-12);
    nVersion&=0x38;
    nVersion>>=3;


    if (0==dwRefID) {
        szMeaning="unspecified";
    } else if (0==nStratum || 1==nStratum) {
        szMeaning=szBuf;
        char szId[5];
        szId[0]=pPropertyInst->lpByte[0];
        szId[1]=pPropertyInst->lpByte[1];
        szId[2]=pPropertyInst->lpByte[2];
        szId[3]=pPropertyInst->lpByte[3];
        szId[4]='\0';
        sprintf(szBuf, "source name: \"%s\"", szId);
    } else if (nVersion<4) {
        szMeaning=szBuf;
        sprintf(szBuf, "source IP: %u.%u.%u.%u", 
                pPropertyInst->lpByte[0], pPropertyInst->lpByte[1],
                pPropertyInst->lpByte[2], pPropertyInst->lpByte[3]);
    } else {
        szMeaning=szBuf;
        sprintf(szBuf, "last reference timestamp fraction: %gs", ((double)dwRefID)/(4294967296.0));
    }

    wsprintf(pPropertyInst->szPropertyText, "Reference Identifier: 0x%08X = %s", dwRefID, szMeaning);

}



//--------------------------------------------------------------------
// conversion constants
#define NTPTIMEOFFSET (0x014F373BFDE04000)
#define FIVETOTHESEVETH (0x001312D)

//--------------------------------------------------------------------
// convert from big-endian NTP-stye timestamp to little-endian NT-style timestamp
unsigned __int64 NtTimeFromNtpTime(unsigned __int64 qwNtpTime) {
    //return (qwNtpTime*(10**7)/(2**32))+NTPTIMEOFFSET
    // ==>
    //return (qwNtpTime*(5**7)/(2**25))+NTPTIMEOFFSET
    // ==>
    //return ((qwNTPtime*FIVETOTHESEVETH)>>25)+NTPTIMEOFFSET;  
    // ==>
    // Note: 'After' division, we round (instead of truncate) the result for better precision
    unsigned __int64 qwTemp;
    qwNtpTime=EndianSwap(qwNtpTime);

    qwTemp=((qwNtpTime&0x00000000FFFFFFFF)*FIVETOTHESEVETH);
    qwTemp += qwTemp&0x0000000001000000; //rounding step: if 25th bit is set, round up
    return (qwTemp>>25) + (((qwNtpTime>>32)*FIVETOTHESEVETH)<<7) + NTPTIMEOFFSET;
}

//--------------------------------------------------------------------
void FormatNtTimeStr(unsigned __int64 qwNtTime, char * szTime) {
    DWORD dwNanoSecs, dwSecs, dwMins, dwHours, dwDays;

    dwNanoSecs=(DWORD)(qwNtTime%10000000);
    qwNtTime/=10000000;

    dwSecs=(DWORD)(qwNtTime%60);
    qwNtTime/=60;

    dwMins=(DWORD)(qwNtTime%60);
    qwNtTime/=60;

    dwHours=(DWORD)(qwNtTime%24);

    dwDays=(DWORD)(qwNtTime/24);

    wsprintf(szTime, "%u %02u:%02u:%02u.%07us",
             dwDays, dwHours, dwMins, dwSecs, dwNanoSecs);
}


//--------------------------------------------------------------------
VOID WINAPIV Ntp_FormatNtpTime(LPPROPERTYINST pPropertyInst) {
    LARGE_INTEGER liNtpTime;
    unsigned __int64 qwNtTime;
    unsigned __int64 qwNtTimeEpoch; 
    char  szTime[64];
    char  szTimeEpoch[64]; 

    
    liNtpTime=*pPropertyInst->lpLargeInt;
    qwNtTime=NtTimeFromNtpTime((((unsigned __int64) liNtpTime.HighPart) << 32) |
                                 liNtpTime.LowPart);

    if (liNtpTime.HighPart || liNtpTime.LowPart) {
        FormatNtTimeStr(qwNtTime, szTime);
    } else {
        lstrcpy(szTime, "(not specified)");
    }

    wsprintf(szTimeEpoch, " -- %I64d00ns", 
             ((((unsigned __int64)liNtpTime.HighPart) << 32) | liNtpTime.LowPart));;

    wsprintf(pPropertyInst->szPropertyText, "%s: 0x%08X.%08Xs %s = %s", 
             pPropertyInst->lpPropertyInfo->Label,
             EndianSwap((unsigned __int32)liNtpTime.LowPart),
             EndianSwap((unsigned __int32)liNtpTime.HighPart),
	     szTimeEpoch, 
	     szTime);
}

//####################################################################

//--------------------------------------------------------------------
// Create our property database and handoff sets.
void BHAPI Ntp_Register(HPROTOCOL hNtp) {
    unsigned int nIndex;

    // tell netmon to make reserve some space for our property table
    CreatePropertyDatabase(hNtp, ARRAYSIZE(NtpPropertyTable));

    // add our properties to netmon's database
    for(nIndex=0; nIndex<ARRAYSIZE(NtpPropertyTable); nIndex++) {
        AddProperty(hNtp, &NtpPropertyTable[nIndex]);
    }
}


//--------------------------------------------------------------------
// Destroy our property database and handoff set
VOID WINAPI Ntp_Deregister(HPROTOCOL hNtp) {

    // tell netmon that it may now free our database
    DestroyPropertyDatabase(hNtp);
}


//--------------------------------------------------------------------
// Determine whether we exist in the frame at the spot 
// indicated. We also indicate who (if anyone) follows us
// and how much of the frame we claim.
LPBYTE BHAPI Ntp_RecognizeFrame(HFRAME hFrame, ULPBYTE pMacFrame, ULPBYTE pNtpFrame, DWORD MacType, DWORD BytesLeft, HPROTOCOL hPrevProtocol, DWORD nPrevProtOffset, LPDWORD pProtocolStatus, LPHPROTOCOL phNextProtocol, PDWORD_PTR InstData) {

    // For now, just assume that if we got called,
    // then the packet does contain us and we go to the end of the frame
    *pProtocolStatus=PROTOCOL_STATUS_CLAIMED;
    return NULL;
}


//--------------------------------------------------------------------
// Indicate where in the frame each of our properties live.
LPBYTE BHAPI Ntp_AttachProperties(HFRAME hFrame, ULPBYTE pMacFrame, ULPBYTE pNtpFrame, DWORD MacType, DWORD BytesLeft, HPROTOCOL hPrevProtocol, DWORD nPrevProtOffset, DWORD_PTR InstData) {

    AttachPropertyInstance(hFrame, NtpPropertyTable[Ntp_Summary].hProperty, (WORD)BytesLeft, (LPBYTE)pNtpFrame, 0, 0, 0);
    AttachPropertyInstance(hFrame, NtpPropertyTable[Ntp_LeapIndicator].hProperty, (WORD)1, (LPBYTE) pNtpFrame, 0, 1, 0);
    AttachPropertyInstance(hFrame, NtpPropertyTable[Ntp_Version].hProperty, (WORD)1, (LPBYTE) pNtpFrame, 0, 1, 0);
    AttachPropertyInstance(hFrame, NtpPropertyTable[Ntp_Mode].hProperty, (WORD)1, (LPBYTE) pNtpFrame, 0, 1, 0);
    AttachPropertyInstance(hFrame, NtpPropertyTable[Ntp_Stratum].hProperty, (WORD)1, (LPBYTE) pNtpFrame+1, 0, 1, 0);
    AttachPropertyInstance(hFrame, NtpPropertyTable[Ntp_PollInterval].hProperty, (WORD)1, (LPBYTE) pNtpFrame+2, 0, 1, 0);
    AttachPropertyInstance(hFrame, NtpPropertyTable[Ntp_Precision].hProperty, (WORD)1, (LPBYTE) pNtpFrame+3, 0, 1, 0);
    AttachPropertyInstance(hFrame, NtpPropertyTable[Ntp_RootDelay].hProperty, (WORD)4, (LPBYTE) pNtpFrame+4, 0, 1, 0);
    AttachPropertyInstance(hFrame, NtpPropertyTable[Ntp_RootDispersion].hProperty, (WORD)4, (LPBYTE) pNtpFrame+8, 0, 1, 0);
    AttachPropertyInstance(hFrame, NtpPropertyTable[Ntp_RefId].hProperty, (WORD)4, (LPBYTE) pNtpFrame+12, 0, 1, 0);
    AttachPropertyInstance(hFrame, NtpPropertyTable[Ntp_ReferenceTimeStamp].hProperty, (WORD)8, (LPBYTE) pNtpFrame+16, 0, 1, 0);
    AttachPropertyInstance(hFrame, NtpPropertyTable[Ntp_OriginateTimeStamp].hProperty, (WORD) 8, (LPBYTE) pNtpFrame+24, 0, 1, 0);
    AttachPropertyInstance(hFrame, NtpPropertyTable[Ntp_ReceiveTimeStamp].hProperty, (WORD) 8, (LPBYTE) pNtpFrame+32, 0, 1, 0);
    AttachPropertyInstance(hFrame, NtpPropertyTable[Ntp_TransmitTimeStamp].hProperty, (WORD) 8, (LPBYTE) pNtpFrame+40, 0, 1, 0);

    return NULL;
}


//--------------------------------------------------------------------
// Format the given properties on the given frame.
DWORD BHAPI Ntp_FormatProperties(HFRAME hFrame, ULPBYTE pMacFrame, ULPBYTE pNtpFrame, DWORD nPropertyInsts, LPPROPERTYINST p) {

    // loop through the property instances
    while(nPropertyInsts-->0) {
        // and call the formatter for each
        ((FORMAT)(p->lpPropertyInfo->InstanceData))(p);
        p++;
    }

    return NMERR_SUCCESS;
}


//####################################################################

//--------------------------------------------------------------------
//  AutoInstall - return all of the information neede to install us
PPF_PARSERDLLINFO WINAPI ParserAutoInstallInfo() {
    PPF_PARSERDLLINFO pParserDllInfo; 
    PPF_PARSERINFO    pParserInfo;
    DWORD NumProtocols;

    DWORD NumHandoffs;
    PPF_HANDOFFSET    pHandoffSet;
    PPF_HANDOFFENTRY  pHandoffEntry;

    // Allocate memory for parser info:
    NumProtocols=1;
    pParserDllInfo=(PPF_PARSERDLLINFO)HeapAlloc(GetProcessHeap(),
                                                   HEAP_ZERO_MEMORY,
                                                   sizeof(PF_PARSERDLLINFO) +
                                                   NumProtocols * sizeof(PF_PARSERINFO));
    if(pParserDllInfo==NULL) {
        return NULL;
    }       
    
    // fill in the parser DLL info
    pParserDllInfo->nParsers=NumProtocols;

    // fill in the individual parser infos...

    // Ntp ==============================================================
    pParserInfo=&(pParserDllInfo->ParserInfo[0]);
    wsprintf(pParserInfo->szProtocolName, "NTP");
    wsprintf(pParserInfo->szComment,      "Network Time Protocol");
    wsprintf(pParserInfo->szHelpFile,     "");

    // the incoming handoff set ----------------------------------------------
    // allocate
    NumHandoffs = 1;
    pHandoffSet = (PPF_HANDOFFSET)HeapAlloc( GetProcessHeap(),
                                             HEAP_ZERO_MEMORY,
                                             sizeof( PF_HANDOFFSET ) +
                                             NumHandoffs * sizeof( PF_HANDOFFENTRY) );
    if( pHandoffSet == NULL )
    {
        // just return early
        return pParserDllInfo;
    }

    // fill in the incoming handoff set
    pParserInfo->pWhoHandsOffToMe = pHandoffSet;
    pHandoffSet->nEntries = NumHandoffs;

    pHandoffEntry = &(pHandoffSet->Entry[0]);
    wsprintf( pHandoffEntry->szIniFile,    "TCPIP.INI" );
    wsprintf( pHandoffEntry->szIniSection, "UDP_HandoffSet" );
    wsprintf( pHandoffEntry->szProtocol,   "NTP" );
    pHandoffEntry->dwHandOffValue =        123;
    pHandoffEntry->ValueFormatBase =       HANDOFF_VALUE_FORMAT_BASE_DECIMAL;

    return pParserDllInfo;
}

//--------------------------------------------------------------------
// Tell netmon about our entry points.
extern "C" BOOL WINAPI DllMain(HANDLE hInstance, ULONG Command, LPVOID Reserved) {

    //MessageBox(NULL, "DLLEntry", "NTP ha ha", MB_OK);
    static HPROTOCOL hNtp=NULL;
    static unsigned int nAttached=0;
    
    // what type of call is this
    switch(Command) {

    case DLL_PROCESS_ATTACH:
        // are we loading for the first time?
        if (nAttached==0) {
            // the first time in we need to tell netmon 
            // about ourselves

            ENTRYPOINTS NtpEntryPoints={
                Ntp_Register,
                Ntp_Deregister,
                Ntp_RecognizeFrame,
                Ntp_AttachProperties,
                Ntp_FormatProperties
            };

            hNtp=CreateProtocol("NTP", &NtpEntryPoints, ENTRYPOINTS_SIZE);
        }
        nAttached++;
        break;

    case DLL_PROCESS_DETACH:
        nAttached--;
        // are we detaching our last instance?
        if (nAttached==0) {
            // last guy out needs to clean up
            DestroyProtocol(hNtp);
        }
        break;
    }

    // Netmon parsers ALWAYS return TRUE.
    return TRUE;
}