Coverage Report

Created: 2026-06-30 07:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/PcapPlusPlus/Packet++/src/NtpLayer.cpp
Line
Count
Source
1
1.43k
#define LOG_MODULE PacketLogModuleNtpLayer
2
3
#include "Logger.h"
4
#include "NtpLayer.h"
5
#include "SystemUtils.h"
6
#include "GeneralUtils.h"
7
#include <cmath>
8
9
/// 2^16 as a double
10
1.51k
#define NTP_FRIC 65536.
11
/// 2^32 as a double
12
4.53k
#define NTP_FRAC 4294967296.
13
/// Epoch offset between Unix time and NTP
14
4.53k
#define EPOCH_OFFSET 2208988800ULL
15
16
namespace pcpp
17
{
18
  NtpLayer::NtpLayer()
19
0
  {
20
0
    m_DataLen = sizeof(ntp_header);
21
0
    m_Data = new uint8_t[sizeof(ntp_header)];
22
0
    memset(m_Data, 0, sizeof(ntp_header));
23
0
    m_Protocol = NTP;
24
0
  }
25
26
  NtpLayer::LeapIndicator NtpLayer::getLeapIndicator() const
27
504
  {
28
504
    if (getNtpHeader()->leapIndicator < 4)  // Since leap indicator field is 2bit
29
504
      return static_cast<LeapIndicator>(getNtpHeader()->leapIndicator);
30
0
    PCPP_LOG_ERROR("Unknown NTP Leap Indicator");
31
0
    return Unknown;
32
504
  }
33
34
  void NtpLayer::setLeapIndicator(LeapIndicator val)
35
0
  {
36
0
    getNtpHeader()->leapIndicator = val;
37
0
  }
38
39
  uint8_t NtpLayer::getVersion() const
40
2.52k
  {
41
2.52k
    return getNtpHeader()->version;
42
2.52k
  }
43
44
  void NtpLayer::setVersion(uint8_t val)
45
0
  {
46
0
    getNtpHeader()->version = val;
47
0
  }
48
49
  NtpLayer::Mode NtpLayer::getMode() const
50
2.01k
  {
51
2.01k
    if (getNtpHeader()->mode < 8)  // Since mode field 3bit
52
2.01k
      return static_cast<Mode>(getNtpHeader()->mode);
53
0
    PCPP_LOG_ERROR("Unknown NTP Mode");
54
0
    return Reserved;
55
2.01k
  }
56
57
  std::string NtpLayer::getModeString() const
58
1.51k
  {
59
1.51k
    switch (getMode())
60
1.51k
    {
61
153
    case Reserved:
62
153
      return "Reserved";
63
15
    case SymActive:
64
15
      return "Symmetrically Active";
65
3
    case SymPassive:
66
3
      return "Symmetrically Passive";
67
495
    case Client:
68
495
      return "Client";
69
333
    case Server:
70
333
      return "Server";
71
279
    case Broadcast:
72
279
      return "Broadcast";
73
12
    case Control:
74
12
      return "Control";
75
222
    case PrivateUse:
76
222
      return "Private Use";
77
0
    default:
78
0
      PCPP_LOG_ERROR("Unknown NTP Mode");
79
0
      return std::string();
80
1.51k
    }
81
1.51k
  }
82
83
  void NtpLayer::setMode(Mode val)
84
0
  {
85
0
    getNtpHeader()->mode = val;
86
0
  }
87
88
  uint8_t NtpLayer::getStratum() const
89
1.00k
  {
90
1.00k
    return getNtpHeader()->stratum;
91
1.00k
  }
92
93
  void NtpLayer::setStratum(uint8_t val)
94
0
  {
95
0
    getNtpHeader()->stratum = val;
96
0
  }
97
98
  int8_t NtpLayer::getPollInterval() const
99
1.00k
  {
100
1.00k
    return getNtpHeader()->pollInterval;
101
1.00k
  }
102
103
  void NtpLayer::setPollInterval(int8_t val)
104
0
  {
105
0
    getNtpHeader()->pollInterval = val;
106
0
  }
107
108
  double NtpLayer::getPollIntervalInSecs() const
109
504
  {
110
504
    return pow(2, getPollInterval());
111
504
  }
112
113
  int8_t NtpLayer::getPrecision() const
114
1.00k
  {
115
1.00k
    return getNtpHeader()->precision;
116
1.00k
  }
117
118
  void NtpLayer::setPrecision(int8_t val)
119
0
  {
120
0
    getNtpHeader()->precision = val;
121
0
  }
122
123
  double NtpLayer::getPrecisionInSecs() const
124
504
  {
125
504
    return pow(2, getPrecision());
126
504
  }
127
128
  uint32_t NtpLayer::getRootDelay() const
129
1.00k
  {
130
1.00k
    return getNtpHeader()->rootDelay;
131
1.00k
  }
132
133
  void NtpLayer::setRootDelay(uint32_t val)
134
0
  {
135
0
    getNtpHeader()->rootDelay = val;
136
0
  }
137
138
  double NtpLayer::getRootDelayInSecs() const
139
504
  {
140
504
    return convertFromShortFormat(getRootDelay());
141
504
  }
142
143
  void NtpLayer::setRootDelayInSecs(double val)
144
504
  {
145
504
    getNtpHeader()->rootDelay = convertToShortFormat(val);
146
504
  }
147
148
  uint32_t NtpLayer::getRootDispersion() const
149
1.00k
  {
150
1.00k
    return getNtpHeader()->rootDispersion;
151
1.00k
  }
152
153
  void NtpLayer::setRootDispersion(uint32_t val)
154
0
  {
155
0
    getNtpHeader()->rootDispersion = val;
156
0
  }
157
158
  double NtpLayer::getRootDispersionInSecs() const
159
504
  {
160
504
    return convertFromShortFormat(getRootDispersion());
161
504
  }
162
163
  void NtpLayer::setRootDispersionInSecs(double val)
164
0
  {
165
0
    getNtpHeader()->rootDispersion = convertToShortFormat(val);
166
0
  }
167
168
  uint32_t NtpLayer::getReferenceIdentifier() const
169
1.17k
  {
170
1.17k
    return getNtpHeader()->referenceIdentifier;
171
1.17k
  }
172
173
  void NtpLayer::setReferenceIdentifier(IPv4Address val)
174
0
  {
175
0
    getNtpHeader()->referenceIdentifier = val.toInt();
176
0
  }
177
178
  void NtpLayer::setReferenceIdentifier(ClockSource val)
179
0
  {
180
0
    getNtpHeader()->referenceIdentifier = static_cast<uint32_t>(val);
181
0
  }
182
183
  void NtpLayer::setReferenceIdentifier(KissODeath val)
184
0
  {
185
0
    getNtpHeader()->referenceIdentifier = static_cast<uint32_t>(val);
186
0
  }
187
188
  std::string NtpLayer::getReferenceIdentifierString() const
189
504
  {
190
504
    uint8_t stratum = getStratum();
191
504
    uint8_t version = getVersion();
192
504
    uint32_t refID = getReferenceIdentifier();
193
194
504
    if (stratum == 0)
195
232
    {
196
232
      switch (version)
197
232
      {
198
10
      case 3:
199
10
      {
200
10
        switch (static_cast<ClockSource>(refID))
201
10
        {
202
0
        case ClockSource::DCN:
203
0
          return "DCN routing protocol";
204
0
        case ClockSource::NIST:
205
0
          return "NIST public modem";
206
0
        case ClockSource::TSP:
207
0
          return "TSP time protocol";
208
0
        case ClockSource::DTS:
209
0
          return "Digital Time Service";
210
10
        default:
211
10
          return "Unknown";
212
10
        }
213
10
      }
214
164
      case 4:
215
164
      {
216
164
        switch (static_cast<KissODeath>(refID))
217
164
        {
218
0
        case KissODeath::ACST:
219
0
          return "The association belongs to a anycast server";
220
0
        case KissODeath::AUTH:
221
0
          return "Server authentication failed";
222
0
        case KissODeath::AUTO:
223
0
          return "Autokey sequence failed";
224
0
        case KissODeath::BCST:
225
0
          return "The association belongs to a broadcast server";
226
0
        case KissODeath::CRYP:
227
0
          return "Cryptographic authentication or identification failed";
228
0
        case KissODeath::DENY:
229
0
          return "Access denied by remote server";
230
0
        case KissODeath::DROP:
231
0
          return "Lost peer in symmetric mode";
232
0
        case KissODeath::RSTR:
233
0
          return "Access denied due to local policy";
234
0
        case KissODeath::INIT:
235
0
          return "The association has not yet synchronized for the first time";
236
0
        case KissODeath::MCST:
237
0
          return "The association belongs to a manycast server";
238
0
        case KissODeath::NKEY:
239
0
          return "No key found.  Either the key was never installed or is not trusted";
240
0
        case KissODeath::RATE:
241
0
          return "Rate exceeded.  The server has temporarily denied access because the client exceeded the rate "
242
0
                 "threshold";
243
0
        case KissODeath::RMOT:
244
0
          return "Somebody is tinkering with the association from a remote host running ntpdc.  Not to worry "
245
0
                 "unless some rascal has stolen your keys";
246
0
        case KissODeath::STEP:
247
0
          return "A step change in system time has occurred, but the association has not yet resynchronized";
248
164
        default:
249
164
        {
250
          // clang-format off
251
164
          char arrBuff[5] = {
252
164
            static_cast<char>((refID >> 24) & 0xFF),
253
164
            static_cast<char>((refID >> 16) & 0xFF),
254
164
            static_cast<char>((refID >> 8) & 0xFF),
255
164
            static_cast<char>((refID) & 0xFF), '\0'
256
164
          };
257
          // clang-format on
258
164
          return arrBuff;
259
0
        }
260
164
        }
261
164
      }
262
232
      }
263
232
    }
264
272
    else if (stratum == 1)
265
102
    {
266
102
      switch (version)
267
102
      {
268
101
      case 3:
269
101
      {
270
101
        switch (static_cast<ClockSource>(refID))
271
101
        {
272
0
        case ClockSource::ATOM:
273
0
          return "Atomic clock";
274
0
        case ClockSource::VLF:
275
0
          return "VLF radio";
276
0
        case ClockSource::LORC:
277
0
          return "LORAN-C radionavigation";
278
0
        case ClockSource::GOES:
279
0
          return "GOES UHF environment satellite";
280
0
        case ClockSource::GPS:
281
0
          return "GPS UHF satellite positioning";
282
101
        default:
283
101
          return "Unknown";
284
101
        }
285
101
      }
286
1
      case 4:
287
1
      {
288
1
        switch (static_cast<ClockSource>(refID))
289
1
        {
290
0
        case ClockSource::GOES:
291
0
          return "Geosynchronous Orbit Environment Satellite";
292
0
        case ClockSource::GPS:
293
0
          return "Global Position System";
294
0
        case ClockSource::GAL:
295
0
          return "Galileo Positioning System";
296
0
        case ClockSource::PPS:
297
0
          return "Generic pulse-per-second";
298
0
        case ClockSource::IRIG:
299
0
          return "Inter-Range Instrumentation Group";
300
0
        case ClockSource::WWVB:
301
0
          return "LF Radio WWVB Ft. Collins, CO 60 kHz";
302
0
        case ClockSource::DCF:
303
0
          return "LF Radio DCF77 Mainflingen, DE 77.5 kHz";
304
0
        case ClockSource::HBG:
305
0
          return "LF Radio HBG Prangins, HB 75 kHz";
306
0
        case ClockSource::MSF:
307
0
          return "LF Radio MSF Anthorn, UK 60 kHz";
308
0
        case ClockSource::JJY:
309
0
          return "LF Radio JJY Fukushima, JP 40 kHz, Saga, JP 60 kHz";
310
0
        case ClockSource::LORC:
311
0
          return "MF Radio LORAN C station, 100 kHz";
312
0
        case ClockSource::TDF:
313
0
          return "MF Radio Allouis, FR 162 kHz";
314
0
        case ClockSource::CHU:
315
0
          return "HF Radio CHU Ottawa, Ontario";
316
0
        case ClockSource::WWV:
317
0
          return "HF Radio WWV Ft. Collins, CO";
318
0
        case ClockSource::WWVH:
319
0
          return "HF Radio WWVH Kauai, HI";
320
0
        case ClockSource::NIST:
321
0
          return "NIST telephone modem";
322
0
        case ClockSource::ACTS:
323
0
          return "NIST telephone modem";
324
0
        case ClockSource::USNO:
325
0
          return "USNO telephone modem";
326
0
        case ClockSource::PTB:
327
0
          return "European telephone modem";
328
0
        case ClockSource::MRS:
329
0
          return "Multi Reference Sources";
330
0
        case ClockSource::XFAC:
331
0
          return "Inter Face Association Changed";
332
0
        case ClockSource::STEP:
333
0
          return "Step time change";
334
0
        case ClockSource::GOOG:
335
0
          return "Google NTP servers";
336
0
        case ClockSource::DCFa:
337
0
          return "Meinberg DCF77 with amplitude modulation";
338
0
        case ClockSource::DCFp:
339
0
          return "Meinberg DCF77 with phase modulation)/pseudo random phase modulation";
340
0
        case ClockSource::GPSs:
341
0
          return "Meinberg GPS (with shared memory access)";
342
0
        case ClockSource::GPSi:
343
0
          return "Meinberg GPS (with interrupt based access)";
344
0
        case ClockSource::GLNs:
345
0
          return "Meinberg GPS/GLONASS (with shared memory access)";
346
0
        case ClockSource::GLNi:
347
0
          return "Meinberg GPS/GLONASS (with interrupt based access)";
348
0
        case ClockSource::LCL:
349
0
          return "Meinberg Undisciplined local clock";
350
0
        case ClockSource::LOCL:
351
0
          return "Meinberg Undisciplined local clock";
352
1
        default:
353
1
          return "Unknown";
354
1
        }
355
1
      }
356
102
      }
357
102
    }
358
170
    else
359
170
    {
360
      // TODO: Support IPv6 cases for NTPv4, it equals to MD5 hash of first four octets of IPv6 address
361
362
170
      pcpp::IPv4Address addr(getReferenceIdentifier());
363
170
      return addr.toString();
364
170
    }
365
366
58
    PCPP_LOG_ERROR("Unknown Stratum type");
367
58
    return std::string();
368
504
  }
369
370
  uint64_t NtpLayer::getReferenceTimestamp() const
371
1.51k
  {
372
1.51k
    return getNtpHeader()->referenceTimestamp;
373
1.51k
  }
374
375
  void NtpLayer::setReferenceTimestamp(uint64_t val)
376
0
  {
377
0
    getNtpHeader()->referenceTimestamp = val;
378
0
  }
379
380
  double NtpLayer::getReferenceTimestampInSecs() const
381
504
  {
382
504
    return convertFromTimestampFormat(getReferenceTimestamp());
383
504
  }
384
385
  void NtpLayer::setReferenceTimestampInSecs(double val)
386
504
  {
387
504
    getNtpHeader()->referenceTimestamp = convertToTimestampFormat(val);
388
504
  }
389
390
  std::string NtpLayer::getReferenceTimestampAsString()
391
504
  {
392
504
    return convertToIsoFormat(getReferenceTimestamp());
393
504
  }
394
395
  uint64_t NtpLayer::getOriginTimestamp() const
396
1.51k
  {
397
1.51k
    return getNtpHeader()->originTimestamp;
398
1.51k
  }
399
400
  void NtpLayer::setOriginTimestamp(uint64_t val)
401
0
  {
402
0
    getNtpHeader()->originTimestamp = val;
403
0
  }
404
405
  double NtpLayer::getOriginTimestampInSecs() const
406
504
  {
407
504
    return convertFromTimestampFormat(getOriginTimestamp());
408
504
  }
409
410
  void NtpLayer::setOriginTimestampInSecs(double val)
411
0
  {
412
0
    getNtpHeader()->originTimestamp = convertToTimestampFormat(val);
413
0
  }
414
415
  std::string NtpLayer::getOriginTimestampAsString()
416
504
  {
417
504
    return convertToIsoFormat(getOriginTimestamp());
418
504
  }
419
420
  uint64_t NtpLayer::getReceiveTimestamp() const
421
1.51k
  {
422
1.51k
    return getNtpHeader()->receiveTimestamp;
423
1.51k
  }
424
425
  void NtpLayer::setReceiveTimestamp(uint64_t val)
426
0
  {
427
0
    getNtpHeader()->receiveTimestamp = val;
428
0
  }
429
430
  double NtpLayer::getReceiveTimestampInSecs() const
431
504
  {
432
504
    return convertFromTimestampFormat(getReceiveTimestamp());
433
504
  }
434
435
  void NtpLayer::setReceiveTimestampInSecs(double val)
436
0
  {
437
0
    getNtpHeader()->receiveTimestamp = convertToTimestampFormat(val);
438
0
  }
439
440
  std::string NtpLayer::getReceiveTimestampAsString()
441
504
  {
442
504
    return convertToIsoFormat(getReceiveTimestamp());
443
504
  }
444
445
  uint64_t NtpLayer::getTransmitTimestamp() const
446
1.51k
  {
447
1.51k
    return getNtpHeader()->transmitTimestamp;
448
1.51k
  }
449
450
  void NtpLayer::setTransmitTimestamp(uint64_t val)
451
0
  {
452
0
    getNtpHeader()->transmitTimestamp = val;
453
0
  }
454
455
  double NtpLayer::getTransmitTimestampInSecs() const
456
504
  {
457
504
    return convertFromTimestampFormat(getTransmitTimestamp());
458
504
  }
459
460
  void NtpLayer::setTransmitTimestampInSecs(double val)
461
0
  {
462
0
    getNtpHeader()->transmitTimestamp = convertToTimestampFormat(val);
463
0
  }
464
465
  std::string NtpLayer::getTransmitTimestampAsString()
466
504
  {
467
504
    return convertToIsoFormat(getTransmitTimestamp());
468
504
  }
469
470
  uint32_t NtpLayer::getKeyID() const
471
504
  {
472
504
    switch (getVersion())
473
504
    {
474
111
    case 3:
475
111
    {
476
111
      if (m_DataLen < (sizeof(ntp_header) + sizeof(ntp_v3_auth)))
477
47
        return 0;
478
479
64
      ntp_v3_auth* header = (ntp_v3_auth*)(m_Data + sizeof(ntp_header));
480
64
      return header->keyID;
481
111
    }
482
168
    case 4:
483
168
    {
484
      // TODO: Add support for extension fields
485
168
      if (m_DataLen == (sizeof(ntp_header) + sizeof(ntp_v4_auth_md5)))
486
0
      {
487
0
        ntp_v4_auth_md5* header = (ntp_v4_auth_md5*)(m_Data + m_DataLen - sizeof(ntp_v4_auth_md5));
488
0
        return header->keyID;
489
0
      }
490
168
      if (m_DataLen == (sizeof(ntp_header) + sizeof(ntp_v4_auth_sha1)))
491
64
      {
492
64
        ntp_v4_auth_sha1* header = (ntp_v4_auth_sha1*)(m_Data + m_DataLen - sizeof(ntp_v4_auth_sha1));
493
64
        return header->keyID;
494
64
      }
495
496
104
      PCPP_LOG_ERROR("NTP authentication parsing with extension fields are not supported");
497
104
      return 0;
498
168
    }
499
225
    default:
500
225
    {
501
225
      PCPP_LOG_ERROR("NTP version not supported");
502
225
      return 0;
503
168
    }
504
504
    }
505
504
  }
506
507
  std::string NtpLayer::getDigest() const
508
504
  {
509
504
    switch (getVersion())
510
504
    {
511
111
    case 3:
512
111
    {
513
111
      if (m_DataLen < (sizeof(ntp_header) + sizeof(ntp_v3_auth)))
514
47
        return std::string();
515
516
64
      ntp_v3_auth* header = (ntp_v3_auth*)(m_Data + sizeof(ntp_header));
517
64
      return byteArrayToHexString(header->dgst, 8);
518
111
    }
519
168
    case 4:
520
168
    {
521
168
      if (m_DataLen == (sizeof(ntp_header) + sizeof(ntp_v4_auth_md5)))
522
0
      {
523
0
        ntp_v4_auth_md5* header = (ntp_v4_auth_md5*)(m_Data + m_DataLen - sizeof(ntp_v4_auth_md5));
524
0
        return byteArrayToHexString(header->dgst, 16);
525
0
      }
526
168
      if (m_DataLen == (sizeof(ntp_header) + sizeof(ntp_v4_auth_sha1)))
527
64
      {
528
64
        ntp_v4_auth_sha1* header = (ntp_v4_auth_sha1*)(m_Data + m_DataLen - sizeof(ntp_v4_auth_sha1));
529
64
        return byteArrayToHexString(header->dgst, 20);
530
64
      }
531
532
104
      PCPP_LOG_ERROR("NTP authentication parsing with extension fields are not supported");
533
104
      return std::string();
534
168
    }
535
225
    default:
536
225
      PCPP_LOG_ERROR("NTP version not supported");
537
225
      return std::string();
538
504
    }
539
504
  }
540
541
  double NtpLayer::convertFromShortFormat(const uint32_t val)
542
1.00k
  {
543
1.00k
    double integerPart = netToHost16(val & 0xFFFF);
544
1.00k
    double fractionPart = netToHost16(((val & 0xFFFF0000) >> 16)) / NTP_FRIC;
545
546
1.00k
    return integerPart + fractionPart;
547
1.00k
  }
548
549
  double NtpLayer::convertFromTimestampFormat(const uint64_t val)
550
4.03k
  {
551
4.03k
    double integerPart = netToHost32(val & 0xFFFFFFFF);
552
4.03k
    double fractionPart = netToHost32(((val & 0xFFFFFFFF00000000) >> 32)) / NTP_FRAC;
553
554
    // TODO: Return integer and fraction parts as struct to increase precision
555
    // Offset change should be done here because of overflow
556
4.03k
    return integerPart + fractionPart - EPOCH_OFFSET;
557
4.03k
  }
558
559
  uint32_t NtpLayer::convertToShortFormat(const double val)
560
504
  {
561
504
    double integerPart;
562
504
    double fractionPart = modf(val, &integerPart);
563
564
    // Cast values to 16bit
565
504
    uint32_t integerPartInt = hostToNet16(integerPart);
566
504
    uint32_t fractionPartInt = hostToNet16(fractionPart * NTP_FRIC);
567
568
504
    return integerPartInt | (fractionPartInt << 16);
569
504
  }
570
571
  uint64_t NtpLayer::convertToTimestampFormat(const double val)
572
504
  {
573
504
    double integerPart;
574
504
    double fractionPart = modf(val, &integerPart);
575
576
    // Cast values to 32bit
577
504
    uint64_t integerPartInt = hostToNet32(integerPart + EPOCH_OFFSET);
578
504
    uint64_t fractionPartInt = hostToNet32(fractionPart * NTP_FRAC);
579
580
504
    return integerPartInt | (fractionPartInt << 32);
581
504
  }
582
583
  std::string NtpLayer::convertToIsoFormat(const double timestamp)
584
2.01k
  {
585
2.01k
    double integerPart;
586
2.01k
    double fractionPart = modf(timestamp, &integerPart);
587
588
2.01k
    struct tm* timer;
589
2.01k
    time_t timeStruct = integerPart;
590
#if defined(_WIN32)
591
    if (timeStruct < 0)
592
      timeStruct = 0;
593
    timer = gmtime(&timeStruct);
594
#else
595
2.01k
    struct tm timer_r;
596
2.01k
    timer = gmtime_r(&timeStruct, &timer_r);
597
598
2.01k
    if (timer != nullptr)
599
2.01k
      timer = &timer_r;
600
2.01k
#endif
601
2.01k
    if (timer == nullptr)
602
0
    {
603
0
      PCPP_LOG_ERROR("Can't convert time");
604
0
      return std::string();
605
0
    }
606
2.01k
    char buffer[50], bufferFraction[15];
607
2.01k
    strftime(buffer, sizeof(buffer) - sizeof(bufferFraction), "%Y-%m-%dT%H:%M:%S", timer);
608
609
2.01k
    snprintf(bufferFraction, sizeof(bufferFraction), "%.04lfZ", fabs(fractionPart));
610
2.01k
    strncat(buffer, &bufferFraction[1], sizeof(bufferFraction));
611
612
2.01k
    return std::string(buffer);
613
2.01k
  }
614
615
  std::string NtpLayer::convertToIsoFormat(const uint64_t timestampInNTPformat)
616
2.01k
  {
617
2.01k
    return convertToIsoFormat(convertFromTimestampFormat(timestampInNTPformat));
618
2.01k
  }
619
620
  bool NtpLayer::isDataValid(const uint8_t* data, size_t dataSize)
621
3.50k
  {
622
3.50k
    return data && dataSize >= sizeof(ntp_header);
623
3.50k
  }
624
625
  std::string NtpLayer::toString() const
626
1.00k
  {
627
1.00k
    return std::string("NTP Layer v") + std::to_string(getVersion()) + ", Mode: " + getModeString();
628
1.00k
  }
629
}  // namespace pcpp