/******************************************************************************************************

11 April 2019

    This program demonstrates loading the SDK DLL dynamically, finding the entry points for various
    routines within the DLL, and testing some of the functions.


*******************************************************************************************************/

#include "apitest.h"                    // all headers and defines, including windows, winsock, and dthres.h
                                        // the definition of the constant "SDKDLL_NAME" is based on whether
                                        // this is a 32-bit or 64-bit build. Look at the code in apitest.h
                                        // to understand.

#pragma comment(lib,"ws2_32.lib")       // winsock; for WSAAddressToString
#pragma comment(lib,"version.lib")      // use "mincore.lib" if you are targeting only newer OSes

//
// Function pointers to routines within the SDK DLL. Put here in file scope global for convenience
// in understanding how loading function pointers works. Your own application should encapsulte 
// data according to best programming practices. These are just pointers, and don't need to be
// deleted or changed when the program ends.
//

PIS_DOMAINTIME_RUNNING          pIsDomainTimeRunning        = NULL;
PGET_DOMAINTIME_VERSION         pGetDomainTimeVersion       = NULL;
PGET_DOMAINTIME_ROLE            pGetDomainTimeRole          = NULL;
PGET_DOMAINTIME_STATUS          pGetDomainTimeStatus        = NULL;
PGET_PTPSTATS                   pGetPTPStats                = NULL; // new as of 5.2.b.20190401
PGET_SDKDLL_VERSION             pGetSDKDLLVersion           = NULL; // new as of 5.2.b.20190401

//
// Handles to events we want to monitor. In file scope for the same reasons as given above.
// Your own applications should encapsulate them so they can be closed when a dtor is called
// or an exception occurs. This program closes the handles at the end of main(). The events
// are only used when the version of the SDK DLL is out of date and the GetPTPStats() function
// does not exist.
//

HANDLE      hPTPRunning     = NULL;         // manual-reset; always signalled if PTP is running
HANDLE      hPTPSlave       = NULL;         // manual-reset; always signalled if PTP is in the SLAVE state
HANDLE      hPTPMaster      = NULL;         // manual-reset; always signalled if PTP is in the MASTER state


void GetDLLVersion(char* pVersion,const size_t cbMax)
{
    //
    // Since we want this demo to be backward-compatible all the way to 32-bit XP, we must use the
    // Win32 functions without wrappers. Modern programs should use more modern methods. Note that
    // although all this C code is cumbersome and error-prone, it works on all operating systems,
    // 32- or 64-bit, ANSI or UNICODE, from XP through Server 2022.
    //
    // On input, pVersion must point to a char array, and cbMax must be at least 32.
    //

    memset(pVersion,0,cbMax);

    DWORD   dwUnused;
    DWORD   cbVerInfoSize   = GetFileVersionInfoSizeA(SDKDLL_NAME,&dwUnused);

    if (cbVerInfoSize)
    {
        PVOID pBuffer = HeapAlloc(GetProcessHeap(),0,cbVerInfoSize);
        if (pBuffer)
        {
            if (GetFileVersionInfoA(SDKDLL_NAME,dwUnused,cbVerInfoSize,pBuffer) )
            {
                LPLANGANDCP         lpTranslate;
                UINT                uiLen;
                char                szSubblock[256];

                if (VerQueryValueA(pBuffer,"\\VarFileInfo\\Translation",(LPVOID*)&lpTranslate,&uiLen) )
                {
                    for (UINT i = 0; i < (uiLen / sizeof(LANGANDCP)); ++i)
                    {
                        sprintf_s(szSubblock,sizeof(szSubblock),"\\StringFileInfo\\%04x%04x\\FileVersion",lpTranslate[i].wLanguage,lpTranslate[i].wCodePage);
                        LPSTR pVerString;
                        UINT  uiVerStringLen;

                        if (VerQueryValueA(pBuffer,szSubblock,(LPVOID*)&pVerString,&uiVerStringLen) )
                        {
                            if (uiVerStringLen > cbMax - 1) memcpy(pVersion,pVerString,cbMax - 1);
                            else                            memcpy(pVersion,pVerString,uiVerStringLen);
                            break;
                        }
                    }
                }

            }

            HeapFree(GetProcessHeap(),0,pBuffer);
        }
    }

    if (!*pVersion)
    {
        strcpy_s(pVersion,cbMax-1,"Unable to find version string");
    }
}


int main(int argc, char* argv[])    // console program entry point; arguments not referenced
{
    //************************************************************************************************
    //
    // About programming style:
    //
    // This demo program purposely used GOTO in order to clarify the logic. GOTO is invoked when a fatal
    // error occurs. Modern programs should use proper error handling. In particular, not all errors
    // are truly fatal. If you are able to load the SDK DLL, you should retry rather than blowing up.
    //
    //************************************************************************************************

    printf("Domain Time API Test - C++\n\n");

    //************************************************************************************************
    //
    // About dynamic linking:
    //
    // This demo program shows how to load the SDK DLL dynamically, and use GetProcAddress() to
    // locate procedures. If you are not interested in dynamically loading the DLL, then you may
    // link using the .LIB file provided and call the various functions directly.
    //
    // For example:
    /*
        #ifdef _WIN64
            #pragma comment(lib,"../dthres64.lib")
        #else
            #pragma comment(lib,"../dthres32.lib")
        #endif
    */
    //
    //************************************************************************************************

    //************************************************************************************************
    //
    // About what this program demonstrates:
    //
    // The main purpose of this program is to demonstrate the new GetPTPStats() function. Older
    // versions of the SDK DLL won't have this function, so this program uses dynamic linking in
    // order to continue even without GetPTPStats().
    //
    // This demo program is far more complicated than you need just to check that Domain Time is
    // running and what the PTP state and offset are. If you only need this information, then
    // load the function pointer for GetPTPStats and call it. It returns an error directly if
    // statistics aren't available. Here is a minimal implementation:
    /*
    
        PTP_STATISTICS  stats;
        stats.cbSize = sizeof(PTP_STATISTICS);
        DWORD dwError = pGetPTPStats(&stats);   // if you have loaded the SDK DLL dynamically.....
        -or-
        DWORD dwError = GetPTPStats(&stats);    // if you are linking to the .LIB, you may call it directly

        if (dwError)
        {
            //
            // Handle error condition (most likely Domain Time is not running; see SDK.DOC for error numbers)
            // In particular, ERROR_FILE_NOT_FOUND means that the shared memory location used to transfer
            // information from Domain Time to the SDK DLL hasn't been created yet.
            //
        }

        else
        {
            if (stats.bRunning)                     // tells you if Domain Time has the PTP engine running
            {
                if (PTP_SLAVE == stats.portState)   // tells you if PTP is in the SLAVE state
                {
                    stats.hnsOffsetFromMaster       // tells you the current offset from the master (in hectonanoseconds)
                }
            }
        }
    */
    //
    // See the code below for how to extract more information from the PTP_STATISTICS structure.
    //
    //************************************************************************************************


    //************************************************************************************************
    //
    // About locating the SDK DLL:
    //
    // When working with this project from the SDK location, the SDK DLLs are in the folder one level
    // up, so we set the DLL load path to our parent folder. Release versions expect the DLLs to be
    // either in the same folder as the executable, or in the default DLL load path.
    //
    //************************************************************************************************

#ifdef _DEBUG
    SetDllDirectoryA("..\\");
#endif

    //************************************************************************************************
    //
    // Main demo program begin
    //
    //************************************************************************************************

    WSADATA     wsaData;
    DWORD       dwMajor;
    DWORD       dwMinor;
    DWORD       dwDate; 
    DWORD       dwRole;
    BOOL        bWinsockLoaded  = FALSE;
    DWORD       dwError         = NO_ERROR;
    HINSTANCE   hLib            = LoadLibraryA(SDKDLL_NAME);
    
    if (!hLib)
    {
        //
        // Library not loaded. The most likely error is that the SDK DLL wasn't in the path.
        //

        dwError = GetLastError();
        printf("Could not load %s; error %u\n",SDKDLL_NAME,dwError);
        goto ExitTest;
    }

    //
    // If you want to verify which exact DLL was loaded above (in case you have more than one
    // in your path and want to check that the proper one was loaded), use GetModuleFileName():
    //
    // char szFullLibraryPath [ MAX_PATH + 1 ];
    // GetModuleFileNameA ( hLib, szFullLibraryPath, MAX_PATH );
    //

    //
    // Library loaded successfully. 
    //
    // Dynamically load pointers to procedures we are testing. If any but GetPTPStats or GetSDKDLLVersion is missing,
    // this is a fatal error for this demo.
    //

    pIsDomainTimeRunning        = (PIS_DOMAINTIME_RUNNING)          GetProcAddress(hLib,IS_DOMAINTIME_RUNNING_NAME);
    pGetDomainTimeVersion       = (PGET_DOMAINTIME_VERSION)         GetProcAddress(hLib,GET_DOMAINTIME_VERSION_NAME);
    pGetDomainTimeRole          = (PGET_DOMAINTIME_ROLE)            GetProcAddress(hLib,GET_DOMAINTIME_ROLE_NAME);
    pGetDomainTimeStatus        = (PGET_DOMAINTIME_STATUS)          GetProcAddress(hLib,GET_DOMAINTIME_STATUS_NAME);

    //
    // These two functions will only exist in versions dated 01 Apr 2019 or later
    //

    pGetPTPStats                = (PGET_PTPSTATS)                   GetProcAddress(hLib,GET_PTP_STATS_NAME);
    pGetSDKDLLVersion           = (PGET_SDKDLL_VERSION)             GetProcAddress(hLib,GET_SDKDLL_VERSION_NAME);

    if (pGetSDKDLLVersion)
    {
        //
        // On versions of the SDK DLL later than 01 Apr 2019, we can retrieve the DLL's version number
        // directly from the DLL.
        //
        // The lack of error checking is intentional. This function cannot fail, since the information
        // it returns does not depend on any external source.
        //

        pGetSDKDLLVersion(&dwMajor,&dwMinor,&dwDate);

        printf("%s version %u.%u.b.%04u%04u loaded\n",
            SDKDLL_NAME,
            dwMajor,
            dwMinor,
            HIWORD(dwDate),
            LOWORD(dwDate));
    }

    else
    {
        printf("%s not found in %s; checking the DLL's resources\n",
            GET_SDKDLL_VERSION_NAME,
            SDKDLL_NAME);

        char szDLLVersion[32];
        GetDLLVersion(szDLLVersion,sizeof(szDLLVersion));

        printf("%s %s loaded\n",
            SDKDLL_NAME,
            szDLLVersion);
    }

    //
    // For compatibility back to XP, we use the Winsock subsystem for some convenience calls.
    // The program does not do any socket communication. If your application isn't using Winsock and
    // is targeting OS version >= Vista/2008, you may skip loading winsock and use inet_ntop or 
    // RtlIpv4StringToAddres or other convenience functions that don't require winsock to be loaded.
    //

    if (WSAStartup(MAKEWORD(2,2),&wsaData) )
    {
        dwError = GetLastError();
        printf("Could not initialize Winsock subsystem; error %u",dwError);
        goto ExitTest;
    }

    bWinsockLoaded = TRUE;  // so ExitTest knows to call WSACleanup()

    if (!pIsDomainTimeRunning || !pGetDomainTimeVersion || !pGetDomainTimeRole || !pGetDomainTimeStatus)
    {
        //
        // Fatal error: could not load procedure entry points from the library
        //

        dwError = GetLastError();
        if (NO_ERROR == dwError)
        {
            dwError = ERROR_PROC_NOT_FOUND;
        }
        printf("Could not load function pointer(s); error %u\n",dwError);
        goto ExitTest;
    }

    if (!pIsDomainTimeRunning() )
    {
        //
        // This is a fatal error for this test program, but is not necessarily a fatal error
        // in your own applications. If you are able to load the SDK DLL successfully, you
        // may call IsDomainTimeRunning() periodically until it returns TRUE. The Domain Time
        // service (either Domain Time Server or Domain Time Client) is set to auto-start,
        // and therefore should already be running by the time your application starts. 
        // However, if your application is also a system service, it may start before Domain
        // Time does. Alternately, Domain Time might be stopped/restarted using Services.
        //

        dwError = GetLastError();
        printf("The Domain Time service is not running\n");
        goto ExitTest;
    }

    //
    // Display the service type and version. We use two calls for this: GetDomainTimeVersion()
    // and GetDomainTimeRole(). Both are optional.
    //

    BOOL    bSuccess =  pGetDomainTimeVersion(&dwMajor,&dwMinor,&dwDate) && // get the version
                        pGetDomainTimeRole(&dwRole);                        // and the role

    if (!bSuccess)
    {
        dwError = GetLastError();
        printf("Could not obtain version and role; error %u\n",dwError);
        goto ExitTest;
    }

    //
    // Service is present, running, and providing information.
    //

    printf("Domain Time %s version %u.%u.b.%04u%04u\n",
        ROLE_CLIENT == dwRole ? "Client" : "Server",
        dwMajor,
        dwMinor,
        HIWORD(dwDate),
        LOWORD(dwDate));

    //
    // Check for the existence of GetPTPStats() function. We can still determine the PTP state using
    // older DLLs, but it requires checking to see if various events are set.
    //

    if (!pGetPTPStats)
    {
        //
        // Non-lethal error if proc not found; it just means the SDK DLL is out of date and GetPTPStats()
        // won't be available.
        //

        printf("%s not found in %s; you need a newer version of %s\n",
            GET_PTP_STATS_NAME,
            SDKDLL_NAME,
            SDKDLL_NAME);

        //
        // No support for the new GetPTPStats. Make an educated guess about status by examining
        // global events maintained by Domain Time. If Domain Time is running (checked above), then
        // any program, at any privilige level, should be able to open them for SYNCHRONIZE access. If
        // you try to open them with EVENT_ALL_ACCESS or EVENT_MODIFY_STATE, the DACL on the events
        // will generate an ERROR_ACCESS_DENIED error.
        //
        // Note that SYNC_STATUS_PTPV2_OFFSET_CHANGE_NAME is deprecated and should not be used.
        //

        hPTPRunning     = OpenEventA(SYNCHRONIZE,FALSE,SYNC_STATUS_PTPV2_IS_ENABLED_NAME);
        hPTPSlave       = OpenEventA(SYNCHRONIZE,FALSE,SYNC_STATUS_PTPV2_IS_SLAVE_NAME);
        hPTPMaster      = OpenEventA(SYNCHRONIZE,FALSE,SYNC_STATUS_PTPV2_IS_MASTER_NAME);
    }


    //
    // Demonstrate using GetDomainTimeStatus() and GetPTPStats() in a loop
    //

    for (;;)    // runs until user hits either Ctrl-C or 'x'
    {
        DWORD dwStatus;
        BOOL  bSuccess = pGetDomainTimeStatus(&dwStatus);

        if (!bSuccess)
        {
            dwError = GetLastError();
            printf("Could not obtain Domain Time status; error %u\n",dwError);
            goto ExitTest;
        }

        //
        // If you stop the Domain Time service while this program is running, then the shared
        // memory section will still exist, and the status will be SYNC_STATUS_UNKNOWN.
        //

        switch (dwStatus)
        {
        case SYNC_STATUS_INVALID:   printf("Sync Status invalid\n");                break;  // something tragically wrong; you should abort
        case SYNC_STATUS_UNKNOWN:   printf("Sync Status unknown\n");                break;  // probably the service has been stopped
        case SYNC_STATUS_CHECKING:  printf("Sync Status checking\n");               break;  // the service is checking/synchronizing the time
        case SYNC_STATUS_SYNCED:    printf("Sync Status synchronized\n");           break;  // the service believes the time is synchronized
        case SYNC_STATUS_UNSYNCED:  printf("Sync Status unsynchronized\n");         break;  // the service was unable to synchronize the time
        default:                    printf("Sync Status %u unknown\n",dwStatus);    break;  // an unknown condition...
        }

        //
        // Demonstrate getting the PTP status
        //

        if (pGetPTPStats)
        {
            //
            // Use the new GetPTPStats() API if present
            //

            PTP_STATISTICS stats;
            stats.cbSize = sizeof(PTP_STATISTICS);
            dwError = pGetPTPStats(&stats);

            if (dwError)
            {
                printf("Unable to use GetPTPStats; error %u\n",dwError);
                goto ExitTest;
            }

            printf("PTP running: %s\n",stats.bRunning ? "Yes" : "No");

            //
            // If not running, the rest of the information is meaningless
            //

            if (stats.bRunning)
            {
                printf("PTP portState: ");
                switch (stats.portState)
                {
                case PTP_OFFLINE:       printf("Offline\n");        break;
                case PTP_INITIALIZING:  printf("Initializing\n");   break;
                case PTP_FAULTY:        printf("Faulty\n");         break;
                case PTP_DISABLED:      printf("Disabled\n");       break;
                case PTP_LISTENING:     printf("Listening\n");      break;
                case PTP_PRE_MASTER:    printf("Pre-Master\n");     break;
                case PTP_MASTER:        printf("Master\n");         break;
                case PTP_PASSIVE:       printf("Passive\n");        break;
                case PTP_CALIBRATING:   printf("Calibrating\n");    break;
                case PTP_SLAVE:         printf("Slave\n");          break;
                default:                printf("Unknown\n");        break;
                }

                printf("PTP domainNumber: %u\n",stats.domainNumber);
                printf("PTP TAI-UTC offset: %d seconds\n",stats.utcOffset);
                printf("PTP portIdentity: %016I64x.%u\n",stats.piSelf.clockIdentity,stats.piSelf.portNumber);

                printf("PTP profileId: ");
                switch (stats.profildId)
                {
                case PTP_PROFILE_END_TO_END:    printf("End-to-End\n");     break;  // using E2E for delay measurement
                case PTP_PROFILE_PEER_TO_PEER:  printf("Peer-to-Peer\n");   break;  // using P2P for delay measurement
                case PTP_PROFILE_TELECOM:       printf("Telecom 2008\n");   break;  // using negotiated E2E for delay measurement
                case PTP_PROFILE_ENTERPRISE:    printf("Enterprise\n");     break;  // using unicast E2E for delay measurement
                case PTP_DELAY_IS_DISABLED:     printf("Delay disabled\n"); break;  // not a profileId; indicates delay measurement is disabled
                default:                        printf("Negotiable\n");     break;  // unknown, or set during operation
                }

                //
                // If not slave, the remaining members of PTP_STATISTICS are meaningless
                //

                if (PTP_SLAVE == stats.portState)
                {
                    //
                    // Current offset from master and meanPathDelay are expressed in hectonanoseconds
                    // (tenths of a microsecond). For a more meaningful display, we convert the values
                    // to seconds with seven significant digits. This is accomplished by casting
                    // the __int64 value to a double and dividing by ten million. For example, 42
                    // hectonanoseconds becomes 0.0000042 seconds.
                    //

                    double dSeconds = (double)stats.hnsOffsetFromMaster / 10000000.0;
                    printf("PTP current offset: %+1.7f seconds\n",dSeconds);

                    //
                    // If you want a whole number of microseconds or milliseconds, divide the hectos:
                    //
                    // __int64 microseconds = stats.hnsOffsetFromMaster / 10;     // 10 micros per hecto
                    // __int64 milliseconds = stats.hnsOffsetFromMaster / 10000;  // 10,000 ms per hecto
                    //

                    dSeconds = (double)stats.hnsMeanPathDelay / 10000000.0;
                    printf("PTP meanPathDelay: %1.7f seconds\n",dSeconds);

                    printf("Master portIdentity: %016I64x.%u\n",stats.piMaster.clockIdentity,stats.piMaster.portNumber);

                    //
                    // The master's IP address is contained in the MasterIP member of PTP_STATISTICS. This is
                    // a union of sockaddr_in (for IPv4) and sockaddr_in6 (for IPv6). The sin_family member is
                    // located in the same place in both structures, so can be used to determine what family.
                    //
                    // The sin_port member is located in the same place for both structures. We don't want to
                    // print the port number, so we zero it out. For IPv6, we also zero the scope.
                    //

                    stats.MasterIP.sin4.sin_port        = 0;    // so WSAAddressToString doesn't include :portNumber
                    stats.MasterIP.sin6.sin6_scope_id   = 0;    // so WSAAddressToString doesn't include the %scopeId

                    char szIP[128];
                    DWORD len = sizeof(szIP);

                    if (AF_INET == stats.MasterIP.sin4.sin_family)
                    {
                        WSAAddressToStringA((sockaddr*)&stats.MasterIP,sizeof(sockaddr_in),NULL,szIP,&len);
                    }
                    else
                    {
                        WSAAddressToStringA((sockaddr*)&stats.MasterIP,sizeof(sockaddr_in6),NULL,szIP,&len);
                    }

                    printf("Master IP address: %s\n",szIP);

                }
            }
        }

        else
        {
            //
            // GetPTPStats() is not available. Figure out the current PTP portState by checking the
            // signalled state of events maintained by Domain Time. These events are all manual-reset,
            // and the state is not changed by calling a wait function.
            //

            if (hPTPRunning && WAIT_OBJECT_0 == WaitForSingleObject(hPTPRunning,0) )
            {
                printf("PTP running: State: ");

                if (hPTPSlave && WAIT_OBJECT_0 == WaitForSingleObject(hPTPSlave,0) )
                {
                    printf("Slave\n");
                }

                else if (hPTPMaster && WAIT_OBJECT_0 == WaitForSingleObject(hPTPMaster,0) )
                {
                    printf("Master\n");
                }

                else
                {
                    printf("Listening\n");  // best guess if running but not Master or Slave
                }
            }

            else
            {
                printf("PTP not running\n");
            }
        }

        printf("\nPress X to exit, or any other key to repeat test...\n");
        int ch = _getch();
        if ('x' == ch || 'X' == ch) break;

    }

    //************************************************************************************************
    //
    // Main demo program end
    //
    //************************************************************************************************

ExitTest:   // for clarity, all errors (as well as successes) end up here, where we can clean up

    if (hPTPRunning)        CloseHandle(hPTPRunning);
    if (hPTPSlave)          CloseHandle(hPTPSlave);
    if (hPTPMaster)         CloseHandle(hPTPMaster);
    if (hLib)               FreeLibrary(hLib);
    if (bWinsockLoaded)     WSACleanup();

    return dwError;
}



