Coverage Report

Created: 2026-02-14 06:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/PcapPlusPlus/Packet++/header/Packet.h
Line
Count
Source
1
#pragma once
2
3
#include "RawPacket.h"
4
#include "Layer.h"
5
#include <vector>
6
7
/// @file
8
9
/// @namespace pcpp
10
/// @brief The main namespace for the PcapPlusPlus lib
11
namespace pcpp
12
{
13
  /// @brief Options struct for configuring packet parsing behavior.
14
  ///
15
  /// The parsing options are combined. If multiple stop conditions are specified, parsing stops
16
  /// as soon as any condition is met.
17
  struct PacketParseOptions
18
  {
19
    /// @brief Defines the protocol type up to and including which the packet should be parsed (inclusive).
20
    ///
21
    /// For large packets where only the lower layers are of interest, this option can be used to limit the parsing
22
    /// procedure to the desired protocol. The packet is parsed until the specified protocol is reached and
23
    /// consumed. Layers beyond this protocol are not parsed, which can improve performance and reduce
24
    /// memory usage.
25
    ///
26
    /// @remarks If multiple consecutive layers of the same protocol type exist, parsing stops after exhausting the
27
    /// first contiguous sequence of that protocol type. This matters for protocols that may appear multiple times
28
    /// in succession within a packet (e.g. BgpLayer).
29
    ProtocolTypeFamily parseUntilProtocol = UnknownProtocol;
30
31
    /// @brief Defines an OSI model layer up to and including which the packet should be parsed (inclusive).
32
    ///
33
    /// For large packets where only the lower layers are of interest, this option can be used to limit the parsing
34
    /// procedure to the desired OSI layer. The packet is parsed until all layers belonging to the specified OSI
35
    /// layer are consumed. Layers beyond that are ignored, which can improve performance and reduce memory usage.
36
    OsiModelLayer parseUntilLayer = OsiModelLayerUnknown;
37
  };
38
39
  /// @class Packet
40
  /// This class represents a parsed packet. It contains the raw data (RawPacket instance), and a linked list of
41
  /// layers, each layer is a parsed protocol that this packet contains. The layers linked list is ordered where the
42
  /// first layer is the lowest in the packet (currently it's always Ethernet protocol as PcapPlusPlus supports only
43
  /// Ethernet packets), the next layer will be L2.5 or L3 (e.g VLAN, IPv4, IPv6, etc.), and so on. etc.), etc. The
44
  /// last layer in the linked list will be the highest in the packet. For example: for a standard HTTP request packet
45
  /// the layer will look like this: EthLayer -> IPv4Layer -> TcpLayer -> HttpRequestLayer <BR> Packet instance isn't
46
  /// read only. The user can add or remove layers, update current layer, etc.
47
  class Packet
48
  {
49
    friend class Layer;
50
51
  private:
52
    RawPacket* m_RawPacket;
53
    Layer* m_FirstLayer;
54
    Layer* m_LastLayer;
55
    size_t m_MaxPacketLen;
56
    bool m_FreeRawPacket;
57
    bool m_CanReallocateData;
58
59
  public:
60
    /// A constructor for creating a new packet (with no layers).
61
    /// When using this constructor an empty raw buffer is allocated (with the size of maxPacketLen) and a new
62
    /// RawPacket is created
63
    /// @param[in] maxPacketLen The expected packet length in bytes
64
    /// @param[in] linkType The link type to use for this packet (the default is Ethernet)
65
    explicit Packet(size_t maxPacketLen = 1, LinkLayerType linkType = LINKTYPE_ETHERNET);
66
67
    /// A constructor for creating a new packet with a buffer that is pre-allocated by the user.
68
    /// The packet is created empty (with no layers), which means the constructor doesn't parse the data in the
69
    /// buffer. Instead, all of the raw data of this packet it written to this buffer: whenever a layer is added,
70
    /// it's data is written to this buffer. The buffer isn't freed and it's content isn't erased when the packet
71
    /// object is deleted. This constructor is useful when you already have a memory buffer and you want to create
72
    /// packet data in it.
73
    /// @param[in] buffer A pointer to a pre-allocated memory buffer
74
    /// @param[in] bufferSize The size of the buffer
75
    /// @param[in] linkType The link type to use for this packet (the default is Ethernet)
76
    Packet(uint8_t* buffer, size_t bufferSize, LinkLayerType linkType = LINKTYPE_ETHERNET);
77
78
    /// A constructor for creating a packet out of already allocated RawPacket. Very useful when parsing packets
79
    /// that came from the network. When using this constructor a pointer to the RawPacket is saved (data isn't
80
    /// copied) and the RawPacket is parsed, meaning all layers are created and linked to each other in the right
81
    /// order. In this overload of the constructor the user can specify whether to free the instance of raw packet
82
    /// when the Packet is free or not
83
    /// @param[in] rawPacket A pointer to the raw packet
84
    /// @param[in] freeRawPacket Optional parameter. A flag indicating if the destructor should also call the raw
85
    /// packet destructor or not. Default value is false
86
    /// @param[in] parseUntil Optional parameter. Parse the packet until you reach a certain protocol (inclusive).
87
    /// Can be useful for cases when you need to parse only up to a certain layer and want to avoid the performance
88
    /// impact and memory consumption of parsing the whole packet. Default value is ::UnknownProtocol which means
89
    /// don't take this parameter into account
90
    /// @param[in] parseUntilLayer Optional parameter. Parse the packet until you reach a certain layer in the OSI
91
    /// model (inclusive). Can be useful for cases when you need to parse only up to a certain OSI layer (for
92
    /// example transport layer) and want to avoid the performance impact and memory consumption of parsing the
93
    /// whole packet. Default value is ::OsiModelLayerUnknown which means don't take this parameter into account
94
    explicit Packet(RawPacket* rawPacket, bool freeRawPacket = false, ProtocolType parseUntil = UnknownProtocol,
95
                    OsiModelLayer parseUntilLayer = OsiModelLayerUnknown);
96
97
    /// A constructor for creating a packet out of already allocated RawPacket. Very useful when parsing packets
98
    /// that came from the network. When using this constructor a pointer to the RawPacket is saved (data isn't
99
    /// copied) and the RawPacket is parsed, meaning all layers are created and linked to each other in the right
100
    /// order. In this overload of the constructor the user can specify whether to free the instance of raw packet
101
    /// when the Packet is free or not. This constructor should be used to parse the packet up to a certain layer
102
    /// @param[in] rawPacket A pointer to the raw packet
103
    /// @param[in] parseUntil Parse the packet until you reach a certain protocol (inclusive). Can be useful for
104
    /// cases when you need to parse only up to a certain layer and want to avoid the performance impact and memory
105
    /// consumption of parsing the whole packet
106
    explicit Packet(RawPacket* rawPacket, ProtocolType parseUntil);
107
108
    /// A constructor for creating a packet out of already allocated RawPacket. Very useful when parsing packets
109
    /// that came from the network. When using this constructor a pointer to the RawPacket is saved (data isn't
110
    /// copied) and the RawPacket is parsed, meaning all layers are created and linked to each other in the right
111
    /// order. In this overload of the constructor the user can specify whether to free the instance of raw packet
112
    /// when the Packet is free or not. This constructor should be used to parse the packet up to a certain layer
113
    /// @param[in] rawPacket A pointer to the raw packet
114
    /// @param[in] parseUntilFamily Parse the packet until you reach a certain protocol family (inclusive). Can be
115
    /// useful for cases when you need to parse only up to a certain layer and want to avoid the performance impact
116
    /// and memory consumption of parsing the whole packet
117
    explicit Packet(RawPacket* rawPacket, ProtocolTypeFamily parseUntilFamily);
118
119
    /// A constructor for creating a packet out of already allocated RawPacket. Very useful when parsing packets
120
    /// that came from the network. When using this constructor a pointer to the RawPacket is saved (data isn't
121
    /// copied) and the RawPacket is parsed, meaning all layers are created and linked to each other in the right
122
    /// order. In this overload of the constructor the user can specify whether to free the instance of raw packet
123
    /// when the Packet is free or not. This constructor should be used to parse the packet up to a certain layer in
124
    /// the OSI model
125
    /// @param[in] rawPacket A pointer to the raw packet
126
    /// @param[in] parseUntilLayer Optional parameter. Parse the packet until you reach a certain layer in the OSI
127
    /// model (inclusive). Can be useful for cases when you need to parse only up to a certain OSI layer (for
128
    /// example transport layer) and want to avoid the performance impact and memory consumption of parsing the
129
    /// whole packet
130
    explicit Packet(RawPacket* rawPacket, OsiModelLayer parseUntilLayer);
131
132
    /// A destructor for this class. Frees all layers allocated by this instance (Notice: it doesn't free layers
133
    /// that weren't allocated by this class, for example layers that were added by addLayer() or insertLayer() ).
134
    /// In addition it frees the raw packet if it was allocated by this instance (meaning if it was allocated by
135
    /// this instance constructor)
136
    virtual ~Packet()
137
0
    {
138
0
      destructPacketData();
139
0
    }
140
141
    /// A copy constructor for this class. This copy constructor copies all the raw data and re-create all layers.
142
    /// So when the original Packet is being freed, no data will be lost in the copied instance
143
    /// @param[in] other The instance to copy from
144
    Packet(const Packet& other)
145
0
    {
146
0
      copyDataFrom(other);
147
0
    }
148
149
    /// Assignment operator overloading. It first frees all layers allocated by this instance (Notice: it doesn't
150
    /// free layers that weren't allocated by this class, for example layers that were added by addLayer() or
151
    /// insertLayer() ). In addition it frees the raw packet if it was allocated by this instance (meaning if it was
152
    /// allocated by this instance constructor). Afterwards it copies the data from the other packet in the same way
153
    /// used in the copy constructor.
154
    /// @param[in] other The instance to copy from
155
    Packet& operator=(const Packet& other);
156
157
    /// Get a pointer to the Packet's RawPacket
158
    /// @return A pointer to the Packet's RawPacket
159
    RawPacket* getRawPacket() const
160
0
    {
161
0
      return m_RawPacket;
162
0
    }
163
164
    /// Set a RawPacket and re-construct all packet layers
165
    /// @param[in] rawPacket Raw packet to set
166
    /// @param[in] freeRawPacket A flag indicating if the destructor should also call the raw packet destructor or
167
    /// not
168
    /// @param[in] parseUntil Parse the packet until it reaches this protocol. Can be useful for cases when you need
169
    /// to parse only up to a certain layer and want to avoid the performance impact and memory consumption of
170
    /// parsing the whole packet. Default value is ::UnknownProtocol which means don't take this parameter into
171
    /// account
172
    /// @param[in] parseUntilLayer Parse the packet until certain layer in OSI model. Can be useful for cases when
173
    /// you need to parse only up to a certain layer and want to avoid the performance impact and memory consumption
174
    /// of parsing the whole packet. Default value is ::OsiModelLayerUnknown which means don't take this parameter
175
    /// into account
176
    void setRawPacket(RawPacket* rawPacket, bool freeRawPacket, ProtocolTypeFamily parseUntil = UnknownProtocol,
177
                      OsiModelLayer parseUntilLayer = OsiModelLayerUnknown);
178
179
    /// @brief Parse the packet according to the provided options.
180
    ///
181
    /// The packet is parsed according to the specified options, constructing the layer hierarchy if it does not
182
    /// exist. If the packet has already been parsed, it will be re-parsed based on the new options.
183
    ///
184
    /// If @p incrementalParsing is true, the procedure will reuse existing layers where possible, only creating new
185
    /// layers for previously unparsed data. Otherwise, all existing layers are discarded and the packet is parsed
186
    /// from scratch.
187
    ///
188
    /// @param[in] options Parsing options to configure the parsing behavior.
189
    /// @param[in] incrementalParsing If 'true', incremental parsing is performed, reusing existing layers where
190
    /// possible.
191
    /// @throws std::runtime_error if there is no RawPacket associated with this Packet instance.
192
    void parsePacket(PacketParseOptions options, bool incrementalParsing = true);
193
194
    /// Get a pointer to the Packet's RawPacket in a read-only manner
195
    /// @return A pointer to the Packet's RawPacket
196
    const RawPacket* getRawPacketReadOnly() const
197
0
    {
198
0
      return m_RawPacket;
199
0
    }
200
201
    /// Get a pointer to the first (lowest) layer in the packet
202
    /// @return A pointer to the first (lowest) layer in the packet
203
    Layer* getFirstLayer() const
204
0
    {
205
0
      return m_FirstLayer;
206
0
    }
207
208
    /// Get a pointer to the last (highest) layer in the packet
209
    /// @return A pointer to the last (highest) layer in the packet
210
    Layer* getLastLayer() const
211
0
    {
212
0
      return m_LastLayer;
213
0
    }
214
215
    /// Add a new layer as the last layer in the packet. This method gets a pointer to the new layer as a parameter
216
    /// and attaches it to the packet. Notice after calling this method the input layer is attached to the packet so
217
    /// every change you make in it affect the packet; Also it cannot be attached to other packets
218
    /// @param[in] newLayer A pointer to the new layer to be added to the packet
219
    /// @param[in] ownInPacket If true, Packet fully owns newLayer, including memory deletion upon destruct. Default
220
    /// is false.
221
    /// @return True if everything went well or false otherwise (an appropriate error log message will be printed in
222
    /// such cases)
223
    bool addLayer(Layer* newLayer, bool ownInPacket = false)
224
0
    {
225
0
      return insertLayer(m_LastLayer, newLayer, ownInPacket);
226
0
    }
227
228
    /// Insert a new layer after an existing layer in the packet. This method gets a pointer to the new layer as a
229
    /// parameter and attaches it to the packet. Notice after calling this method the input layer is attached to the
230
    /// packet so every change you make in it affect the packet; Also it cannot be attached to other packets
231
    /// @param[in] prevLayer A pointer to an existing layer in the packet which the new layer should followed by. If
232
    /// this layer isn't attached to a packet and error will be printed to log and false will be returned
233
    /// @param[in] newLayer A pointer to the new layer to be added to the packet
234
    /// @param[in] ownInPacket If true, Packet fully owns newLayer, including memory deletion upon destruct. Default
235
    /// is false.
236
    /// @return True if everything went well or false otherwise (an appropriate error log message will be printed in
237
    /// such cases)
238
    bool insertLayer(Layer* prevLayer, Layer* newLayer, bool ownInPacket = false);
239
240
    /// Remove an existing layer from the packet. The layer to removed is identified by its type (protocol). If the
241
    /// packet has multiple layers of the same type in the packet the user may specify the index of the layer to
242
    /// remove (the default index is 0 - remove the first layer of this type). If the layer was allocated during
243
    /// packet creation it will be deleted and any pointer to it will get invalid. However if the layer was
244
    /// allocated by the user and manually added to the packet it will simply get detached from the packet, meaning
245
    /// the pointer to it will stay valid and its data (that was removed from the packet) will be copied back to the
246
    /// layer. In that case it's the user's responsibility to delete the layer instance
247
    /// @param[in] layerType The layer type (protocol) to remove
248
    /// @param[in] index If there are multiple layers of the same type, indicate which instance to remove. The
249
    /// default value is 0, meaning remove the first layer of this type
250
    /// @return True if everything went well or false otherwise (an appropriate error log message will be printed in
251
    /// such cases)
252
    bool removeLayer(ProtocolType layerType, int index = 0);
253
254
    /// Remove the first layer in the packet. The layer will be deleted if it was allocated during packet creation,
255
    /// or detached if was allocated outside of the packet. Please refer to removeLayer() to get more info
256
    /// @return True if layer removed successfully, or false if removing the layer failed or if there are no layers
257
    /// in the packet. In any case of failure an appropriate error log message will be printed
258
    bool removeFirstLayer();
259
260
    /// Remove the last layer in the packet. The layer will be deleted if it was allocated during packet creation,
261
    /// or detached if was allocated outside of the packet. Please refer to removeLayer() to get more info
262
    /// @return True if layer removed successfully, or false if removing the layer failed or if there are no layers
263
    /// in the packet. In any case of failure an appropriate error log message will be printed
264
    bool removeLastLayer();
265
266
    /// Remove all layers that come after a certain layer. All layers removed will be deleted if they were allocated
267
    /// during packet creation or detached if were allocated outside of the packet, please refer to removeLayer() to
268
    /// get more info
269
    /// @param[in] layer A pointer to the layer to begin removing from. Please note this layer will not be removed,
270
    /// only the layers that come after it will be removed. Also, if removal of one layer failed, the method will
271
    /// return immediately and the following layers won't be deleted
272
    /// @return True if all layers were removed successfully, or false if failed to remove at least one layer. In
273
    /// any case of failure an appropriate error log message will be printed
274
    bool removeAllLayersAfter(Layer* layer);
275
276
    /// Detach a layer from the packet. Detaching means the layer instance will not be deleted, but rather separated
277
    /// from the packet - e.g it will be removed from the layer chain of the packet and its data will be copied from
278
    /// the packet buffer into an internal layer buffer. After a layer is detached, it can be added into another
279
    /// packet (but it's impossible to attach a layer to multiple packets in the same time). After layer is
280
    /// detached, it's the user's responsibility to delete it when it's not needed anymore
281
    /// @param[in] layerType The layer type (protocol) to detach from the packet
282
    /// @param[in] index If there are multiple layers of the same type, indicate which instance to detach. The
283
    /// default value is 0, meaning detach the first layer of this type
284
    /// @return A pointer to the detached layer or nullptr if detaching process failed. In any case of failure an
285
    /// appropriate error log message will be printed
286
    Layer* detachLayer(ProtocolType layerType, int index = 0);
287
288
    /// Detach a layer from the packet. Detaching means the layer instance will not be deleted, but rather separated
289
    /// from the packet - e.g it will be removed from the layer chain of the packet and its data will be copied from
290
    /// the packet buffer into an internal layer buffer. After a layer is detached, it can be added into another
291
    /// packet (but it's impossible to attach a layer to multiple packets at the same time). After layer is
292
    /// detached, it's the user's responsibility to delete it when it's not needed anymore
293
    /// @param[in] layer A pointer to the layer to detach
294
    /// @return True if the layer was detached successfully, or false if something went wrong. In any case of
295
    /// failure an appropriate error log message will be printed
296
    bool detachLayer(Layer* layer)
297
0
    {
298
0
      return removeLayer(layer, false);
299
0
    }
300
301
    /// Get a pointer to the layer of a certain type (protocol). This method goes through the layers and returns a
302
    /// layer that matches the give protocol type
303
    /// @param[in] layerType The layer type (protocol) to fetch
304
    /// @param[in] index If there are multiple layers of the same type, indicate which instance to fetch. The
305
    /// default value is 0, meaning fetch the first layer of this type
306
    /// @return A pointer to the layer or nullptr if no such layer was found
307
    Layer* getLayerOfType(ProtocolType layerType, int index = 0) const;
308
309
    /// A templated method to get a layer of a certain type (protocol). If no layer of such type is found, nullptr
310
    /// is returned
311
    /// @param[in] reverseOrder The optional parameter that indicates that the lookup should run in reverse order,
312
    /// the default value is false
313
    /// @return A pointer to the layer of the requested type, nullptr if not found
314
    template <class TLayer> TLayer* getLayerOfType(bool reverseOrder = false) const;
315
316
    /// A templated method to get the first layer of a certain type (protocol), start searching from a certain
317
    /// layer. For example: if a packet looks like: EthLayer -> VlanLayer(1) -> VlanLayer(2) -> VlanLayer(3) ->
318
    /// IPv4Layer and the user put VlanLayer(2) as a parameter and wishes to search for a VlanLayer, VlanLayer(3)
319
    /// will be returned If no layer of such type is found, nullptr is returned
320
    /// @param[in] startLayer A pointer to the layer to start search from
321
    /// @return A pointer to the layer of the requested type, nullptr if not found
322
    template <class TLayer> TLayer* getNextLayerOfType(Layer* startLayer) const;
323
324
    /// A templated method to get the first layer of a certain type (protocol), start searching from a certain
325
    /// layer. For example: if a packet looks like: EthLayer -> VlanLayer(1) -> VlanLayer(2) -> VlanLayer(3) ->
326
    /// IPv4Layer and the user put VlanLayer(2) as a parameter and wishes to search for a VlanLayer, VlanLayer(1)
327
    /// will be returned If no layer of such type is found, nullptr is returned
328
    /// @param[in] startLayer A pointer to the layer to start search from
329
    /// @return A pointer to the layer of the requested type, nullptr if not found
330
    template <class TLayer> TLayer* getPrevLayerOfType(Layer* startLayer) const;
331
332
    /// Check whether the packet contains a layer of a certain protocol
333
    /// @param[in] protocolType The protocol type to search
334
    /// @return True if the packet contains a layer of a certain protocol, false otherwise
335
    bool isPacketOfType(ProtocolType protocolType) const;
336
337
    /// Check whether the packet contains a layer of a certain protocol family
338
    /// @param[in] protocolTypeFamily The protocol type family to search
339
    /// @return True if the packet contains a layer of a certain protocol family, false otherwise
340
    bool isPacketOfType(ProtocolTypeFamily protocolTypeFamily) const;
341
342
    /// Each layer can have fields that can be calculate automatically from other fields using
343
    /// Layer#computeCalculateFields(). This method forces all layers to calculate these fields values
344
    void computeCalculateFields();
345
346
    /// Each layer can print a string representation of the layer most important data using Layer#toString(). This
347
    /// method aggregates this string from all layers and print it to a complete string containing all packet's
348
    /// relevant data
349
    /// @param[in] timeAsLocalTime Print time as local time or GMT. Default (true value) is local time, for GMT set
350
    /// to false
351
    /// @return A string containing most relevant data from all layers (looks like the packet description in
352
    /// Wireshark)
353
    std::string toString(bool timeAsLocalTime = true) const;
354
355
    /// Similar to toString(), but instead of one string it outputs a list of strings, one string for every layer
356
    /// @param[out] result A string vector that will contain all strings
357
    /// @param[in] timeAsLocalTime Print time as local time or GMT. Default (true value) is local time, for GMT set
358
    /// to false
359
    void toStringList(std::vector<std::string>& result, bool timeAsLocalTime = true) const;
360
361
  private:
362
    void copyDataFrom(const Packet& other);
363
364
    void destructPacketData();
365
    void destroyAllLayers();
366
367
    bool extendLayer(Layer* layer, int offsetInLayer, size_t numOfBytesToExtend);
368
    bool shortenLayer(Layer* layer, int offsetInLayer, size_t numOfBytesToShorten);
369
370
    void reallocateRawData(size_t newSize);
371
372
    bool removeLayer(Layer* layer, bool tryToDelete);
373
374
    std::string printPacketInfo(bool timeAsLocalTime) const;
375
376
    Layer* createFirstLayer(LinkLayerType linkType);
377
378
    template <typename TLayer, typename NextLayerFn>
379
    static TLayer* searchLayerStackForType(Layer* startLayer, NextLayerFn nextLayerFn, bool skipFirst);
380
  };  // class Packet
381
382
  // implementation of inline methods
383
384
  template <class TLayer> TLayer* Packet::getLayerOfType(bool reverse) const
385
  {
386
    if (!reverse)
387
    {
388
      return searchLayerStackForType<TLayer>(
389
          m_FirstLayer, [](Layer* layer) { return layer->getNextLayer(); }, false);
390
    }
391
392
    // lookup in reverse order
393
    return searchLayerStackForType<TLayer>(m_LastLayer, [](Layer* layer) { return layer->getPrevLayer(); }, false);
394
  }
395
396
  template <class TLayer> TLayer* Packet::getNextLayerOfType(Layer* curLayer) const
397
  {
398
    return searchLayerStackForType<TLayer>(curLayer, [](Layer* layer) { return layer->getNextLayer(); }, true);
399
  }
400
401
  template <class TLayer> TLayer* Packet::getPrevLayerOfType(Layer* curLayer) const
402
  {
403
    return searchLayerStackForType<TLayer>(curLayer, [](Layer* layer) { return layer->getPrevLayer(); }, true);
404
  }
405
406
  template <typename TLayer, typename NextLayerFn>
407
  TLayer* Packet::searchLayerStackForType(Layer* curLayer, NextLayerFn nextLayerFn, bool skipFirst)
408
  {
409
    if (curLayer == nullptr)
410
      return nullptr;
411
412
    if (skipFirst)
413
    {
414
      curLayer = nextLayerFn(curLayer);
415
    }
416
417
    while (curLayer != nullptr)
418
    {
419
      auto* curLayerCasted = dynamic_cast<TLayer*>(curLayer);
420
      if (curLayerCasted != nullptr)
421
        return curLayerCasted;
422
423
      curLayer = nextLayerFn(curLayer);
424
    }
425
426
    return nullptr;
427
  }
428
429
  inline std::ostream& operator<<(std::ostream& os, const pcpp::Packet& packet)
430
0
  {
431
0
    os << packet.toString();
432
0
    return os;
433
0
  }
434
}  // namespace pcpp