Coverage Report

Created: 2025-07-12 07:37

/src/PcapPlusPlus/Packet++/src/NtpLayer.cpp
Line
Count
Source (jump to first uncovered line)
1
2.33k
#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
2.14k
#define NTP_FRIC 65536.
11
/// 2^32 as a double
12
6.44k
#define NTP_FRAC 4294967296.
13
/// Epoch offset between Unix time and NTP
14
6.44k
#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
716
  {
28
716
    if (getNtpHeader()->leapIndicator < 4)  // Since leap indicator field is 2bit
29
716
      return static_cast<LeapIndicator>(getNtpHeader()->leapIndicator);
30
0
    PCPP_LOG_ERROR("Unknown NTP Leap Indicator");
31
0
    return Unknown;
32
716
  }
33
34
  void NtpLayer::setLeapIndicator(LeapIndicator val)
35
0
  {
36
0
    getNtpHeader()->leapIndicator = val;
37
0
  }
38
39
  uint8_t NtpLayer::getVersion() const
40
3.58k
  {
41
3.58k
    return getNtpHeader()->version;
42
3.58k
  }
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.86k
  {
51
2.86k
    if (getNtpHeader()->mode < 8)  // Since mode field 3bit
52
2.86k
      return static_cast<Mode>(getNtpHeader()->mode);
53
0
    PCPP_LOG_ERROR("Unknown NTP Mode");
54
0
    return Reserved;
55
2.86k
  }
56
57
  std::string NtpLayer::getModeString() const
58
2.14k
  {
59
2.14k
    switch (getMode())
60
2.14k
    {
61
12
    case Reserved:
62
12
      return "Reserved";
63
120
    case SymActive:
64
120
      return "Symmetrically Active";
65
291
    case SymPassive:
66
291
      return "Symmetrically Passive";
67
969
    case Client:
68
969
      return "Client";
69
609
    case Server:
70
609
      return "Server";
71
51
    case Broadcast:
72
51
      return "Broadcast";
73
0
    case Control:
74
0
      return "Control";
75
96
    case PrivateUse:
76
96
      return "Private Use";
77
0
    default:
78
0
      PCPP_LOG_ERROR("Unknown NTP Mode");
79
0
      return std::string();
80
2.14k
    }
81
2.14k
  }
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.43k
  {
90
1.43k
    return getNtpHeader()->stratum;
91
1.43k
  }
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.43k
  {
100
1.43k
    return getNtpHeader()->pollInterval;
101
1.43k
  }
102
103
  void NtpLayer::setPollInterval(int8_t val)
104
0
  {
105
0
    getNtpHeader()->pollInterval = val;
106
0
  }
107
108
  double NtpLayer::getPollIntervalInSecs() const
109
716
  {
110
716
    return pow(2, getPollInterval());
111
716
  }
112
113
  int8_t NtpLayer::getPrecision() const
114
1.43k
  {
115
1.43k
    return getNtpHeader()->precision;
116
1.43k
  }
117
118
  void NtpLayer::setPrecision(int8_t val)
119
0
  {
120
0
    getNtpHeader()->precision = val;
121
0
  }
122
123
  double NtpLayer::getPrecisionInSecs() const
124
716
  {
125
716
    return pow(2, getPrecision());
126
716
  }
127
128
  uint32_t NtpLayer::getRootDelay() const
129
1.43k
  {
130
1.43k
    return getNtpHeader()->rootDelay;
131
1.43k
  }
132
133
  void NtpLayer::setRootDelay(uint32_t val)
134
0
  {
135
0
    getNtpHeader()->rootDelay = val;
136
0
  }
137
138
  double NtpLayer::getRootDelayInSecs() const
139
716
  {
140
716
    return convertFromShortFormat(getRootDelay());
141
716
  }
142
143
  void NtpLayer::setRootDelayInSecs(double val)
144
716
  {
145
716
    getNtpHeader()->rootDelay = convertToShortFormat(val);
146
716
  }
147
148
  uint32_t NtpLayer::getRootDispersion() const
149
1.43k
  {
150
1.43k
    return getNtpHeader()->rootDispersion;
151
1.43k
  }
152
153
  void NtpLayer::setRootDispersion(uint32_t val)
154
0
  {
155
0
    getNtpHeader()->rootDispersion = val;
156
0
  }
157
158
  double NtpLayer::getRootDispersionInSecs() const
159
716
  {
160
716
    return convertFromShortFormat(getRootDispersion());
161
716
  }
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.71k
  {
170
1.71k
    return getNtpHeader()->referenceIdentifier;
171
1.71k
  }
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
716
  {
190
716
    uint8_t stratum = getStratum();
191
716
    uint8_t version = getVersion();
192
716
    uint32_t refID = getReferenceIdentifier();
193
194
716
    if (stratum == 0)
195
86
    {
196
86
      switch (version)
197
86
      {
198
25
      case 3:
199
25
      {
200
25
        switch (static_cast<ClockSource>(refID))
201
25
        {
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
25
        default:
211
25
          return "Unknown";
212
25
        }
213
25
      }
214
59
      case 4:
215
59
      {
216
59
        switch (static_cast<KissODeath>(refID))
217
59
        {
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
59
        default:
249
59
        {
250
          // clang-format off
251
59
          char arrBuff[5] = {
252
59
            static_cast<char>((refID >> 24) & 0xFF),
253
59
            static_cast<char>((refID >> 16) & 0xFF),
254
59
            static_cast<char>((refID >> 8) & 0xFF),
255
59
            static_cast<char>((refID) & 0xFF), '\0'
256
59
          };
257
          // clang-format on
258
59
          return arrBuff;
259
0
        }
260
59
        }
261
59
      }
262
86
      }
263
86
    }
264
630
    else if (stratum == 1)
265
347
    {
266
347
      switch (version)
267
347
      {
268
110
      case 3:
269
110
      {
270
110
        switch (static_cast<ClockSource>(refID))
271
110
        {
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
110
        default:
283
110
          return "Unknown";
284
110
        }
285
110
      }
286
203
      case 4:
287
203
      {
288
203
        switch (static_cast<ClockSource>(refID))
289
203
        {
290
0
        case ClockSource::GOES:
291
0
          return "Geosynchronous Orbit Environment Satellite";
292
1
        case ClockSource::GPS:
293
1
          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
115
        case ClockSource::GPSs:
341
115
          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
87
        default:
353
87
          return "Unknown";
354
203
        }
355
203
      }
356
347
      }
357
347
    }
358
283
    else
359
283
    {
360
      // TODO: Support IPv6 cases for NTPv4, it equals to MD5 hash of first four octets of IPv6 address
361
362
283
      pcpp::IPv4Address addr(getReferenceIdentifier());
363
283
      return addr.toString();
364
283
    }
365
366
36
    PCPP_LOG_ERROR("Unknown Stratum type");
367
36
    return std::string();
368
716
  }
369
370
  uint64_t NtpLayer::getReferenceTimestamp() const
371
2.14k
  {
372
2.14k
    return getNtpHeader()->referenceTimestamp;
373
2.14k
  }
374
375
  void NtpLayer::setReferenceTimestamp(uint64_t val)
376
0
  {
377
0
    getNtpHeader()->referenceTimestamp = val;
378
0
  }
379
380
  double NtpLayer::getReferenceTimestampInSecs() const
381
716
  {
382
716
    return convertFromTimestampFormat(getReferenceTimestamp());
383
716
  }
384
385
  void NtpLayer::setReferenceTimestampInSecs(double val)
386
716
  {
387
716
    getNtpHeader()->referenceTimestamp = convertToTimestampFormat(val);
388
716
  }
389
390
  std::string NtpLayer::getReferenceTimestampAsString()
391
716
  {
392
716
    return convertToIsoFormat(getReferenceTimestamp());
393
716
  }
394
395
  uint64_t NtpLayer::getOriginTimestamp() const
396
2.14k
  {
397
2.14k
    return getNtpHeader()->originTimestamp;
398
2.14k
  }
399
400
  void NtpLayer::setOriginTimestamp(uint64_t val)
401
0
  {
402
0
    getNtpHeader()->originTimestamp = val;
403
0
  }
404
405
  double NtpLayer::getOriginTimestampInSecs() const
406
716
  {
407
716
    return convertFromTimestampFormat(getOriginTimestamp());
408
716
  }
409
410
  void NtpLayer::setOriginTimestampInSecs(double val)
411
0
  {
412
0
    getNtpHeader()->originTimestamp = convertToTimestampFormat(val);
413
0
  }
414
415
  std::string NtpLayer::getOriginTimestampAsString()
416
716
  {
417
716
    return convertToIsoFormat(getOriginTimestamp());
418
716
  }
419
420
  uint64_t NtpLayer::getReceiveTimestamp() const
421
2.14k
  {
422
2.14k
    return getNtpHeader()->receiveTimestamp;
423
2.14k
  }
424
425
  void NtpLayer::setReceiveTimestamp(uint64_t val)
426
0
  {
427
0
    getNtpHeader()->receiveTimestamp = val;
428
0
  }
429
430
  double NtpLayer::getReceiveTimestampInSecs() const
431
716
  {
432
716
    return convertFromTimestampFormat(getReceiveTimestamp());
433
716
  }
434
435
  void NtpLayer::setReceiveTimestampInSecs(double val)
436
0
  {
437
0
    getNtpHeader()->receiveTimestamp = convertToTimestampFormat(val);
438
0
  }
439
440
  std::string NtpLayer::getReceiveTimestampAsString()
441
716
  {
442
716
    return convertToIsoFormat(getReceiveTimestamp());
443
716
  }
444
445
  uint64_t NtpLayer::getTransmitTimestamp() const
446
2.14k
  {
447
2.14k
    return getNtpHeader()->transmitTimestamp;
448
2.14k
  }
449
450
  void NtpLayer::setTransmitTimestamp(uint64_t val)
451
0
  {
452
0
    getNtpHeader()->transmitTimestamp = val;
453
0
  }
454
455
  double NtpLayer::getTransmitTimestampInSecs() const
456
716
  {
457
716
    return convertFromTimestampFormat(getTransmitTimestamp());
458
716
  }
459
460
  void NtpLayer::setTransmitTimestampInSecs(double val)
461
0
  {
462
0
    getNtpHeader()->transmitTimestamp = convertToTimestampFormat(val);
463
0
  }
464
465
  std::string NtpLayer::getTransmitTimestampAsString()
466
716
  {
467
716
    return convertToIsoFormat(getTransmitTimestamp());
468
716
  }
469
470
  uint32_t NtpLayer::getKeyID() const
471
716
  {
472
716
    switch (getVersion())
473
716
    {
474
150
    case 3:
475
150
    {
476
150
      if (m_DataLen < (sizeof(ntp_header) + sizeof(ntp_v3_auth)))
477
150
        return 0;
478
479
0
      ntp_v3_auth* header = (ntp_v3_auth*)(m_Data + sizeof(ntp_header));
480
0
      return header->keyID;
481
150
    }
482
529
    case 4:
483
529
    {
484
      // TODO: Add support for extension fields
485
529
      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
529
      if (m_DataLen == (sizeof(ntp_header) + sizeof(ntp_v4_auth_sha1)))
491
0
      {
492
0
        ntp_v4_auth_sha1* header = (ntp_v4_auth_sha1*)(m_Data + m_DataLen - sizeof(ntp_v4_auth_sha1));
493
0
        return header->keyID;
494
0
      }
495
496
529
      PCPP_LOG_ERROR("NTP authentication parsing with extension fields are not supported");
497
529
      return 0;
498
529
    }
499
37
    default:
500
37
    {
501
37
      PCPP_LOG_ERROR("NTP version not supported");
502
37
      return 0;
503
529
    }
504
716
    }
505
716
  }
506
507
  std::string NtpLayer::getDigest() const
508
716
  {
509
716
    switch (getVersion())
510
716
    {
511
150
    case 3:
512
150
    {
513
150
      if (m_DataLen < (sizeof(ntp_header) + sizeof(ntp_v3_auth)))
514
150
        return std::string();
515
516
0
      ntp_v3_auth* header = (ntp_v3_auth*)(m_Data + sizeof(ntp_header));
517
0
      return byteArrayToHexString(header->dgst, 8);
518
150
    }
519
529
    case 4:
520
529
    {
521
529
      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
529
      if (m_DataLen == (sizeof(ntp_header) + sizeof(ntp_v4_auth_sha1)))
527
0
      {
528
0
        ntp_v4_auth_sha1* header = (ntp_v4_auth_sha1*)(m_Data + m_DataLen - sizeof(ntp_v4_auth_sha1));
529
0
        return byteArrayToHexString(header->dgst, 20);
530
0
      }
531
532
529
      PCPP_LOG_ERROR("NTP authentication parsing with extension fields are not supported");
533
529
      return std::string();
534
529
    }
535
37
    default:
536
37
      PCPP_LOG_ERROR("NTP version not supported");
537
37
      return std::string();
538
716
    }
539
716
  }
540
541
  double NtpLayer::convertFromShortFormat(const uint32_t val)
542
1.43k
  {
543
1.43k
    double integerPart = netToHost16(val & 0xFFFF);
544
1.43k
    double fractionPart = netToHost16(((val & 0xFFFF0000) >> 16)) / NTP_FRIC;
545
546
1.43k
    return integerPart + fractionPart;
547
1.43k
  }
548
549
  double NtpLayer::convertFromTimestampFormat(const uint64_t val)
550
5.72k
  {
551
5.72k
    double integerPart = netToHost32(val & 0xFFFFFFFF);
552
5.72k
    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
5.72k
    return integerPart + fractionPart - EPOCH_OFFSET;
557
5.72k
  }
558
559
  uint32_t NtpLayer::convertToShortFormat(const double val)
560
716
  {
561
716
    double integerPart;
562
716
    double fractionPart = modf(val, &integerPart);
563
564
    // Cast values to 16bit
565
716
    uint32_t integerPartInt = hostToNet16(integerPart);
566
716
    uint32_t fractionPartInt = hostToNet16(fractionPart * NTP_FRIC);
567
568
716
    return integerPartInt | (fractionPartInt << 16);
569
716
  }
570
571
  uint64_t NtpLayer::convertToTimestampFormat(const double val)
572
716
  {
573
716
    double integerPart;
574
716
    double fractionPart = modf(val, &integerPart);
575
576
    // Cast values to 32bit
577
716
    uint64_t integerPartInt = hostToNet32(integerPart + EPOCH_OFFSET);
578
716
    uint64_t fractionPartInt = hostToNet32(fractionPart * NTP_FRAC);
579
580
716
    return integerPartInt | (fractionPartInt << 32);
581
716
  }
582
583
  std::string NtpLayer::convertToIsoFormat(const double timestamp)
584
2.86k
  {
585
2.86k
    double integerPart;
586
2.86k
    double fractionPart = modf(timestamp, &integerPart);
587
588
2.86k
    struct tm* timer;
589
2.86k
    time_t timeStruct = integerPart;
590
#if defined(_WIN32)
591
    if (timeStruct < 0)
592
      timeStruct = 0;
593
    timer = gmtime(&timeStruct);
594
#else
595
2.86k
    struct tm timer_r;
596
2.86k
    timer = gmtime_r(&timeStruct, &timer_r);
597
598
2.86k
    if (timer != nullptr)
599
2.86k
      timer = &timer_r;
600
2.86k
#endif
601
2.86k
    if (timer == nullptr)
602
0
    {
603
0
      PCPP_LOG_ERROR("Can't convert time");
604
0
      return std::string();
605
0
    }
606
2.86k
    char buffer[50], bufferFraction[15];
607
2.86k
    strftime(buffer, sizeof(buffer) - sizeof(bufferFraction), "%Y-%m-%dT%H:%M:%S", timer);
608
609
2.86k
    snprintf(bufferFraction, sizeof(bufferFraction), "%.04lfZ", fabs(fractionPart));
610
2.86k
    strncat(buffer, &bufferFraction[1], sizeof(bufferFraction));
611
612
2.86k
    return std::string(buffer);
613
2.86k
  }
614
615
  std::string NtpLayer::convertToIsoFormat(const uint64_t timestampInNTPformat)
616
2.86k
  {
617
2.86k
    return convertToIsoFormat(convertFromTimestampFormat(timestampInNTPformat));
618
2.86k
  }
619
620
  bool NtpLayer::isDataValid(const uint8_t* data, size_t dataSize)
621
3.58k
  {
622
3.58k
    return data && dataSize >= sizeof(ntp_header);
623
3.58k
  }
624
625
  std::string NtpLayer::toString() const
626
1.43k
  {
627
1.43k
    return std::string("NTP Layer v") + std::to_string(getVersion()) + ", Mode: " + getModeString();
628
1.43k
  }
629
}  // namespace pcpp