Coverage Report

Created: 2026-09-28 07:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/PcapPlusPlus/Packet++/header/QuicLayer.h
Line
Count
Source
1
#pragma once
2
#include "Layer.h"
3
#include "Packet.h"
4
#include <vector>
5
#include <string>
6
7
/// @file
8
9
/// @namespace pcpp
10
/// @brief The main namespace for the PcapPlusPlus lib
11
namespace pcpp
12
{
13
  /// @class QuicV1Layer
14
  /// Represents a QUIC v1 (RFC 9000/9001) protocol layer.
15
  class QuicV1Layer : public Layer
16
  {
17
  public:
18
    /// @enum QuicPacketType
19
    /// Identifies the kind of QUIC v1 packet, as carried by the Long Packet Type field for
20
    /// long-header packets (RFC 9000 Section 17.2), or inferred for short-header (1-RTT) and
21
    /// Version Negotiation packets, which don't carry this field on the wire
22
    enum class QuicPacketType : uint8_t
23
    {
24
      /// Initial packet - carries the start of the cryptographic handshake, plus an
25
      /// optional address-validation Token
26
      Initial = 0,
27
      /// 0-RTT packet - carries application data sent before the handshake completes
28
      ZeroRTT = 1,
29
      /// Handshake packet - carries the remainder of the cryptographic handshake
30
      Handshake = 2,
31
      /// Retry packet - sent by a server to perform address validation before committing
32
      /// state to a connection
33
      Retry = 3,
34
      /// Version Negotiation packet - sent by a server that doesn't support the client's
35
      /// requested QUIC version
36
      VersionNegotiation = 253,
37
      /// 1-RTT packet - a short-header packet carrying post-handshake application data
38
      OneRtt = 254,
39
    };
40
41
    /// @enum QuicHeaderForm
42
    /// Identifies whether a QUIC packet uses the long or short header form (RFC 9000 Section
43
    /// 17.2 vs. 17.3)
44
    enum class QuicHeaderForm : uint8_t
45
    {
46
      /// Short header - used for 1-RTT packets once the connection ID length is known out
47
      /// of band
48
      ShortHeader = 0,
49
      /// Long header - used for Initial, 0-RTT, Handshake, Retry and Version Negotiation
50
      /// packets, all of which are exchanged before that connection ID length is known
51
      LongHeader = 1
52
    };
53
54
    /// @struct ProtectedPayload
55
    /// A non-owning view of the protected portion of a QUIC packet
56
    struct ProtectedPayload
57
    {
58
      /// Pointer to the beginning of the protected data
59
      const uint8_t* data;
60
      /// Length of the protected data in bytes
61
      size_t length;
62
    };
63
64
    /// A static method that creates a QUIC v11 layer from packet raw data. Returns nullptr if
65
    /// data is not valid.
66
    /// @param[in] data A pointer to the raw data
67
    /// @param[in] dataLen Size of the data in bytes
68
    /// @param[in] prevLayer A pointer to the previous layer
69
    /// @param[in] packet A pointer to the Packet instance where layer will be stored
70
    /// @return The newly allocated layer or nullptr if the data isn't valid
71
    static QuicV1Layer* parseQuicLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet);
72
73
    /// @return The type of this QUIC packet
74
    virtual QuicPacketType getPacketType() const = 0;
75
76
    /// @return The header form (long or short) of this QUIC packet, as read straight off the
77
    /// wire
78
    QuicHeaderForm getHeaderForm() const;
79
80
    /// @return The value of the Fixed Bit field. Per RFC 9000 this is always 1
81
    uint8_t getFixedBit() const;
82
83
    /// A static method that checks whether the port is considered as QUIC
84
    /// @param[in] port The port number to be checked
85
    /// @return True if the port is considered as QUIC, false otherwise
86
    static bool isQuicPort(uint16_t port)
87
570k
    {
88
570k
      return port == 443;
89
570k
    }
90
91
    // implement abstract methods
92
93
    /// Wraps any bytes remaining after this packet's header/payload as a subsequent
94
    /// QuicV1Layer if getHeaderLen() leaves data unconsumed - QUIC
95
    /// datagrams commonly coalesce multiple packets back to back - falling back to a
96
    /// PayloadLayer if what follows can't be parsed as QUIC
97
    void parseNextLayer() override;
98
99
    /// Does nothing for this layer
100
    void computeCalculateFields() override
101
168
    {}
102
103
    /// @return A string representation of the packet, e.g. "QUIC v1 Layer, Initial message"
104
    std::string toString() const override;
105
106
    /// @return @ref OsiModelTransportLayer
107
    OsiModelLayer getOsiModelLayer() const override
108
168
    {
109
168
      return OsiModelTransportLayer;
110
168
    }
111
112
  protected:
113
    QuicV1Layer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet)
114
990
        : Layer(data, dataLen, prevLayer, packet, QUICv1)
115
990
    {}
116
117
    /// @struct quic_common_header
118
    /// The one-byte prefix shared by every QUIC v1 packet form, holding just the Header Form
119
    /// and Fixed Bit - enough to tell long-header and short-header packets apart before
120
    /// committing to either layout
121
    struct quic_common_header
122
    {
123
#if (BYTE_ORDER == LITTLE_ENDIAN)
124
      uint8_t : 6, fixedBit : 1, headerForm : 1;
125
#else
126
      uint8_t headerForm : 1, fixedBit : 1, : 6;
127
#endif
128
    };
129
130
#pragma pack(push, 1)
131
    /// @struct quic_long_header
132
    /// The fixed-size prefix of every long-header QUIC v1 packet (Initial, 0-RTT, Handshake,
133
    /// Retry and Version Negotiation): the common header byte, the 4-byte version, and the
134
    /// Destination Connection ID length. The variable-length fields that follow (DCID, SCID,
135
    /// Token, Length) are not part of this struct - they're read via pointer+length accessors
136
    /// on QuicV1LongHeaderLayer and its subclasses instead
137
    struct quic_long_header
138
    {
139
#if (BYTE_ORDER == LITTLE_ENDIAN)
140
      uint8_t packetNumberLength : 2, reserved : 2, longPacketType : 2, fixedBit : 1, headerForm : 1;
141
#else
142
      uint8_t headerForm : 1, fixedBit : 1, longPacketType : 2, reserved : 2, packetNumberLength : 2;
143
#endif
144
      uint32_t version;
145
      uint8_t destinationConnectionIdLength;
146
    };
147
#pragma pack(pop)
148
    static_assert(sizeof(quic_long_header) == 6, "quic_long_header size is not 6 bytes");
149
150
#pragma pack(push, 1)
151
    /// @struct quic_short_header
152
    /// The single-byte header of a 1-RTT (short-header) QUIC v1 packet. There is no
153
    /// Destination Connection ID length field here - a 1-RTT packet's connection ID has a
154
    /// length that's negotiated out of band during the handshake, so it can't be recovered
155
    /// from the packet alone
156
    struct quic_short_header
157
    {
158
#if (BYTE_ORDER == LITTLE_ENDIAN)
159
      uint8_t packetNumberLength : 2, keyPhase : 1, reserved : 2, spinBit : 1, fixedBit : 1, headerForm : 1;
160
#else
161
      uint8_t headerForm : 1, fixedBit : 1, spinBit : 1, reserved : 2, keyPhase : 1, packetNumberLength : 2;
162
#endif
163
    };
164
#pragma pack(pop)
165
    static_assert(sizeof(quic_short_header) == 1, "quic_short_header size is not 1 byte");
166
167
  private:
168
    quic_common_header* getCommonHeader() const
169
0
    {
170
0
      return reinterpret_cast<quic_common_header*>(m_Data);
171
0
    }
172
  };
173
174
  /// @class QuicV1LongHeaderLayer
175
  /// Base class for all QUIC v1 packet forms that use the long header (Initial, 0-RTT,
176
  /// Handshake, Retry and Version Negotiation). Provides parsing shared by all of them: the
177
  /// QUIC version and the Destination/Source Connection IDs, which sit at a fixed offset
178
  /// (Destination) or immediately after it (Source) in every long-header packet
179
  class QuicV1LongHeaderLayer : public QuicV1Layer
180
  {
181
  public:
182
    /// @return The packet type, read from the Long Packet Type field of the long header
183
    QuicPacketType getPacketType() const override;
184
185
    /// @return The QUIC version
186
    uint32_t getVersion() const;
187
188
    /// @return The Destination Connection ID, or an empty vector if the packet doesn't
189
    /// contain enough data to read it
190
    std::vector<uint8_t> getDestinationConnectionId() const;
191
192
    /// @return The Destination Connection ID as hex string, or an empty string if the packet doesn't
193
    /// contain enough data to read it
194
    std::string getDestinationConnectionIdAsString() const;
195
196
    /// @return The Source Connection ID, or an empty vector if the packet doesn't contain
197
    /// enough data to read it
198
    std::vector<uint8_t> getSourceConnectionId() const;
199
200
    /// @return The Source Connection ID as hex string, or an empty string if the packet doesn't contain
201
    /// enough data to read it
202
    std::string getSourceConnectionIdAsString() const;
203
204
  protected:
205
    using QuicV1Layer::QuicV1Layer;
206
207
    /// @struct OffsetAndLength
208
    /// The offset (from the start of the packet) and length, in bytes, of a variable-length
209
    /// field - used internally while walking the packet to locate the Source Connection ID,
210
    /// Token, and Length fields that follow the Destination Connection ID
211
    struct OffsetAndLength
212
    {
213
      /// The field's length, in bytes
214
      size_t length;
215
      /// The field's offset from the start of the packet, in bytes
216
      size_t offset;
217
218
      /// A constructor that creates an instance from a length and offset
219
      /// @param[in] length The field's length, in bytes
220
      /// @param[in] offset The field's offset from the start of the packet, in bytes
221
1.25k
      OffsetAndLength(size_t length, size_t offset) : length(length), offset(offset)
222
1.25k
      {}
223
    };
224
225
    OffsetAndLength getDestConIdOffsetAndLength() const;
226
227
    OffsetAndLength getSrcConIdOffsetAndLength() const;
228
229
  private:
230
    static constexpr int destinationConnectionIdOffset = sizeof(quic_long_header);
231
232
    static bool isDataValid(const uint8_t* data, size_t dataLen);
233
234
    quic_long_header* getLongHeader() const
235
835
    {
236
835
      return reinterpret_cast<quic_long_header*>(m_Data);
237
835
    }
238
239
    friend class QuicV1Layer;
240
  };
241
242
  /// @class QuicV1EstablishmentLayer
243
  /// Base class for the long-header packet forms that carry cryptographic handshake data and a
244
  /// Length field (Initial, 0-RTT and Handshake). Adds parsing of the QUIC variable-length
245
  /// integer ("varint") encoding (RFC 9000 Section 16) used for the Length field and, in
246
  /// QuicV1InitialLayer, the Token Length field as well
247
  class QuicV1EstablishmentLayer : public QuicV1LongHeaderLayer
248
  {
249
  public:
250
    /// @return The value of the Length field: the number of bytes in the Packet Number and
251
    /// Payload fields combined, or 0 if the packet doesn't contain enough data to read it
252
    uint64_t getLength() const;
253
254
    // implement abstract methods
255
256
    /// @return The header length, computed as the offset of the Length field plus the
257
    /// varint's own encoded size plus the value of the Length field itself, clamped to the
258
    /// amount of data actually captured
259
    size_t getHeaderLen() const override;
260
261
    /// Get the protected portion of the QUIC packet.
262
    /// The returned data points directly into the packet buffer and is not copied.
263
    /// This includes the Packet Number and the protected payload (including the AEAD authentication tag).
264
    /// @return A non-owning view of the protected portion of the packet
265
    ProtectedPayload getProtectedPayload() const;
266
267
  protected:
268
    /// @struct VarintValueAndSize
269
    /// The decoded value of a QUIC variable-length integer, together with the number of
270
    /// bytes it occupied on the wire (1, 2, 4 or 8, per RFC 9000 Section 16) - returned by
271
    /// getVarintValueAndSize so callers can advance past the field without re-decoding
272
    /// its length
273
    struct VarintValueAndSize
274
    {
275
      /// The varint's decoded value
276
      uint64_t value;
277
      /// The varint's encoded size on the wire, in bytes (1, 2, 4 or 8)
278
      size_t size;
279
280
      /// A constructor that creates an instance from a decoded value and its encoded size
281
      /// @param[in] value The varint's decoded value
282
      /// @param[in] size The varint's encoded size on the wire, in bytes
283
920
      VarintValueAndSize(uint64_t value, size_t size) : value(value), size(size)
284
920
      {}
285
    };
286
287
    virtual size_t getLengthOffset() const;
288
289
    VarintValueAndSize getVarintValueAndSize(size_t offset) const;
290
291
  private:
292
    using QuicV1LongHeaderLayer::QuicV1LongHeaderLayer;
293
  };
294
295
  /// @class QuicV1InitialLayer
296
  /// Represents a QUIC v1 Initial packet - the first packet of a connection, carrying the
297
  /// start of the cryptographic handshake and, optionally, an address-validation Token
298
  /// (RFC 9000 Section 17.2.2)
299
  class QuicV1InitialLayer : public QuicV1EstablishmentLayer
300
  {
301
  public:
302
    /// @return The address-validation Token, or an empty vector if the packet carries no
303
    /// token or doesn't contain enough data to read it
304
    std::vector<uint8_t> getToken() const;
305
306
    /// @return The address-validation Token as hex string, or an empty string if the packet carries no
307
    /// token or doesn't contain enough data to read it
308
    std::string getTokenAsString() const;
309
310
  private:
311
    using QuicV1EstablishmentLayer::QuicV1EstablishmentLayer;
312
313
    size_t getLengthOffset() const override;
314
315
    size_t getTokenLengthOffset() const;
316
317
    friend class QuicV1Layer;
318
  };
319
320
  /// @class QuicV1ZeroRttLayer
321
  /// Represents a QUIC v1 0-RTT packet - carries application data sent before the
322
  /// cryptographic handshake completes (RFC 9000 Section 17.2.3). Adds no parsing beyond what
323
  /// QuicV1EstablishmentLayer already provides
324
  class QuicV1ZeroRttLayer : public QuicV1EstablishmentLayer
325
  {
326
    using QuicV1EstablishmentLayer::QuicV1EstablishmentLayer;
327
328
    friend class QuicV1Layer;
329
  };
330
331
  /// @class QuicV1HandshakeLayer
332
  /// Represents a QUIC v1 Handshake packet - carries the remainder of the cryptographic
333
  /// handshake after the Initial packet (RFC 9000 Section 17.2.4). Adds no parsing beyond what
334
  /// QuicV1EstablishmentLayer already provides
335
  class QuicV1HandshakeLayer : public QuicV1EstablishmentLayer
336
  {
337
    using QuicV1EstablishmentLayer::QuicV1EstablishmentLayer;
338
339
    friend class QuicV1Layer;
340
  };
341
342
  /// @class QuicV1RetryLayer
343
  /// Represents a QUIC v1 Retry packet - sent by a server to perform address validation before
344
  /// committing state to a connection (RFC 9000 Section 17.2.5). Carries a Retry Token and,
345
  /// unlike the other long-header forms, a fixed-size 16-byte integrity tag rather than a
346
  /// varint-prefixed Length field
347
  class QuicV1RetryLayer : public QuicV1LongHeaderLayer
348
  {
349
  public:
350
    /// @return The Retry Token, or an empty vector if the packet doesn't contain enough
351
    /// data - beyond the Source Connection ID - to also hold the 16-byte integrity tag
352
    std::vector<uint8_t> getRetryToken() const;
353
354
    /// @return The Retry Token as hex string, or an empty string if the packet doesn't contain enough
355
    /// data - beyond the Source Connection ID - to also hold the 16-byte integrity tag
356
    std::string getRetryTokenAsString() const;
357
358
    /// @return The 16-byte Retry Integrity Tag, or an empty vector if the packet doesn't
359
    /// contain enough data - beyond the Source Connection ID - to hold it
360
    std::vector<uint8_t> getRetryIntegrityTag() const;
361
362
    /// @return The 16-byte Retry Integrity Tag as hex string, or an empty string if the packet doesn't
363
    /// contain enough data - beyond the Source Connection ID - to hold it
364
    std::string getRetryIntegrityTagAsString() const;
365
366
    // implement abstract methods
367
368
    /// @return sizeof(m_DataLen) - a Retry packet has no Length field, so its header is
369
    /// considered to span the entire packet
370
    size_t getHeaderLen() const override
371
48
    {
372
48
      return m_DataLen;
373
48
    }
374
375
  private:
376
    using QuicV1LongHeaderLayer::QuicV1LongHeaderLayer;
377
378
    static constexpr size_t retryIntegritySize = 16;
379
380
    size_t getRetryTokenOffset() const;
381
382
    friend class QuicV1Layer;
383
  };
384
385
  /// @class QuicV1VersionNegotiationLayer
386
  /// Represents a QUIC Version Negotiation packet - sent by a server that doesn't support the
387
  /// QUIC version a client requested, listing the versions it does support (RFC 9000 Section
388
  /// 17.2.1). Identified by a version field of 0 rather than a Long Packet Type value, so
389
  /// getPacketType() always returns QuicPacketType::VersionNegotiation
390
  class QuicV1VersionNegotiationLayer : public QuicV1LongHeaderLayer
391
  {
392
  public:
393
    /// @return QuicPacketType::VersionNegotiation
394
    QuicPacketType getPacketType() const override
395
18
    {
396
18
      return QuicPacketType::VersionNegotiation;
397
18
    }
398
399
    /// @return The list of QUIC versions the server supports, in host byte order. Returns
400
    /// an empty list if a trailing partial version is encountered
401
    std::vector<uint32_t> getSupportedVersions() const;
402
403
    // implement abstract methods
404
405
    /// @return sizeof(m_DataLen) - a Version Negotiation packet has no further layers beyond
406
    /// the list of supported versions, so its header is considered to span the entire packet
407
    size_t getHeaderLen() const override
408
54
    {
409
54
      return m_DataLen;
410
54
    }
411
412
  private:
413
    using QuicV1LongHeaderLayer::QuicV1LongHeaderLayer;
414
415
    friend class QuicV1Layer;
416
  };
417
418
  /// @class QuicV1OneRttLayer
419
  /// Represents a QUIC v1 1-RTT packet - a short-header packet carrying post-handshake
420
  /// application data (RFC 9000 Section 17.3.1). Since the short header carries no explicit
421
  /// Length field, getHeaderLen() treats the header as spanning the entire packet
422
  class QuicV1OneRttLayer : public QuicV1Layer
423
  {
424
  public:
425
    /// @return QuicPacketType::OneRtt
426
    QuicPacketType getPacketType() const override
427
120
    {
428
120
      return QuicPacketType::OneRtt;
429
120
    }
430
431
    /// @return The value of the Spin Bit, used for passive latency measurement along the
432
    /// connection's path
433
    bool getSpinBit() const
434
0
    {
435
0
      return getShortHeader()->spinBit;
436
0
    }
437
438
    /// @return The value of the Key Phase bit, used to identify which packet protection keys
439
    /// were used to protect this packet
440
    bool getKeyPhaseBit() const
441
0
    {
442
0
      return getShortHeader()->keyPhase;
443
0
    }
444
445
    /// Get the protected portion of the QUIC packet.
446
    /// The returned data points directly into the packet buffer and is not copied.
447
    /// @return A non-owning view of the protected portion of the packet
448
    ProtectedPayload getProtectedPayload() const;
449
450
    // implement abstract methods
451
452
    /// @return sizeof(m_DataLen) - a 1-RTT packet's short header carries no Length field, so
453
    /// its header is considered to span the entire packet
454
    size_t getHeaderLen() const override
455
419
    {
456
419
      return m_DataLen;
457
419
    }
458
459
  private:
460
    using QuicV1Layer::QuicV1Layer;
461
462
    static bool isDataValid(const uint8_t* data, size_t dataLen);
463
464
    quic_short_header* getShortHeader() const
465
0
    {
466
0
      return reinterpret_cast<quic_short_header*>(m_Data);
467
0
    }
468
469
    friend class QuicV1Layer;
470
  };
471
}  // namespace pcpp