Skip to content

Commit d549230

Browse files
authored
Fix inclusive parseUntil condition in the main packet parse loop. (#1964)
* Refactored the main packet parse loop. - The `parseUntil` is now fully inclusive and parses until it exhausts a sequence of target protocol types. This is to allow parsing until recursive layers that are followed by a layer of the same type. - Readability improvements. * Use auto. * Add save resource utilities to ResourceProvider. This is so resource data can be generated easily for test creation. By default all ResourceProvider objects are created as frozen, and attempting to save to them will throw an exception. * Add test case for parsing packet with multiple layers of the same type. * Removed old code.
1 parent 0a6241a commit d549230

File tree

6 files changed

+131
-25
lines changed

6 files changed

+131
-25
lines changed

Packet++/src/Packet.cpp

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -63,39 +63,50 @@ namespace pcpp
6363

6464
m_FirstLayer = createFirstLayer(linkType);
6565

66-
m_LastLayer = m_FirstLayer;
67-
Layer* curLayer = m_FirstLayer;
68-
while (curLayer != nullptr &&
69-
(parseUntil == UnknownProtocol || !curLayer->isMemberOfProtocolFamily(parseUntil)) &&
70-
curLayer->getOsiModelLayer() <= parseUntilLayer)
66+
// As the stop conditions are inclusive, the parse must go one layer further and then roll back if needed
67+
bool rollbackLastLayer = false;
68+
bool foundTargetProtocol = false;
69+
for (auto* curLayer = m_FirstLayer; curLayer != nullptr; curLayer = curLayer->getNextLayer())
7170
{
72-
curLayer->parseNextLayer();
71+
// Mark the current layer as allocated in the packet
7372
curLayer->m_IsAllocatedInPacket = true;
74-
curLayer = curLayer->getNextLayer();
75-
if (curLayer != nullptr)
76-
m_LastLayer = curLayer;
77-
}
73+
m_LastLayer = curLayer; // Update last layer to current layer
7874

79-
if (curLayer != nullptr && curLayer->isMemberOfProtocolFamily(parseUntil))
80-
{
81-
curLayer->m_IsAllocatedInPacket = true;
82-
}
75+
// If the current layer is of a higher OSI layer than the target, stop parsing
76+
if (curLayer->getOsiModelLayer() > parseUntilLayer)
77+
{
78+
rollbackLastLayer = true;
79+
break;
80+
}
8381

84-
if (curLayer != nullptr && curLayer->getOsiModelLayer() > parseUntilLayer)
85-
{
86-
// don't delete the first layer. If already past the target layer, treat the same as if the layer was found.
87-
if (curLayer == m_FirstLayer)
82+
// If we are searching for a specific layer protocol, record when we find at least one target.
83+
const bool matchesTarget = curLayer->isMemberOfProtocolFamily(parseUntil);
84+
if (parseUntil != UnknownProtocol && matchesTarget)
8885
{
89-
curLayer->m_IsAllocatedInPacket = true;
86+
foundTargetProtocol = true;
9087
}
91-
else
88+
89+
// If we have found the target protocol already, we are parsing until we find a different protocol
90+
if (foundTargetProtocol && !matchesTarget)
9291
{
93-
m_LastLayer = curLayer->getPrevLayer();
94-
delete curLayer;
95-
m_LastLayer->m_NextLayer = nullptr;
92+
rollbackLastLayer = true;
93+
break;
9694
}
95+
96+
// Parse the next layer. This will update the next layer pointer of the current layer.
97+
curLayer->parseNextLayer();
98+
}
99+
100+
// Roll back one layer, if parsing with search condition as the conditions are inclusive.
101+
// Don't delete the first layer. If already past the target layer, treat the same as if the layer was found.
102+
if (rollbackLastLayer && m_LastLayer != m_FirstLayer)
103+
{
104+
m_LastLayer = m_LastLayer->getPrevLayer();
105+
delete m_LastLayer->m_NextLayer;
106+
m_LastLayer->m_NextLayer = nullptr;
97107
}
98108

109+
// If there is data left in the raw packet that doesn't belong to any layer, create a PacketTrailerLayer
99110
if (m_LastLayer != nullptr && parseUntil == UnknownProtocol && parseUntilLayer == OsiModelLayerUnknown)
100111
{
101112
// find if there is data left in the raw packet that doesn't belong to any layer. In that case it's probably

Tests/Packet++Test/TestDefinition.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ PTF_TEST_CASE(ResizeLayerTest);
6060
PTF_TEST_CASE(PrintPacketAndLayersTest);
6161
PTF_TEST_CASE(ProtocolFamilyMembershipTest);
6262
PTF_TEST_CASE(PacketParseLayerLimitTest);
63+
PTF_TEST_CASE(PacketParseMultiLayerTest);
6364

6465
// Implemented in HttpTests.cpp
6566
PTF_TEST_CASE(HttpRequestParseMethodTest);

Tests/Packet++Test/Tests/PacketTests.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "PayloadLayer.h"
2121
#include "GeneralUtils.h"
2222
#include "SystemUtils.h"
23+
#include "BgpLayer.h"
2324

2425
using pcpp_tests::utils::createPacketFromHexResource;
2526

@@ -1064,3 +1065,33 @@ PTF_TEST_CASE(PacketParseLayerLimitTest)
10641065
pcpp::Packet packet1(rawPacket1.get(), pcpp::OsiModelTransportLayer);
10651066
PTF_ASSERT_EQUAL(packet1.getLastLayer()->getOsiModelLayer(), pcpp::OsiModelTransportLayer);
10661067
}
1068+
1069+
PTF_TEST_CASE(PacketParseMultiLayerTest)
1070+
{
1071+
// The BGP packet has 4 BGP messages inside.
1072+
auto rawPacket = createPacketFromHexResource("PacketExamples/Bgp_update2.dat");
1073+
1074+
// Limit to BGP layer
1075+
pcpp::Packet packet(rawPacket.get(), pcpp::BGP);
1076+
1077+
const size_t expectedNumOfBgpMessages = 4;
1078+
size_t actualNumOfBgpMessages = 0;
1079+
1080+
pcpp::BgpLayer* bgpLayer = packet.getLayerOfType<pcpp::BgpLayer>();
1081+
if (bgpLayer != nullptr)
1082+
{
1083+
++actualNumOfBgpMessages;
1084+
}
1085+
1086+
// The fallback iteration uses expected * 2, just to be sure we won't get into an infinite loop
1087+
for (; bgpLayer != nullptr && actualNumOfBgpMessages < expectedNumOfBgpMessages * 2;)
1088+
{
1089+
bgpLayer = packet.getNextLayerOfType<pcpp::BgpLayer>(bgpLayer);
1090+
if (bgpLayer != nullptr)
1091+
{
1092+
++actualNumOfBgpMessages;
1093+
}
1094+
}
1095+
1096+
PTF_ASSERT_EQUAL(actualNumOfBgpMessages, expectedNumOfBgpMessages);
1097+
}

Tests/Packet++Test/main.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ int main(int argc, char* argv[])
169169
PTF_RUN_TEST(PrintPacketAndLayersTest, "packet;print");
170170
PTF_RUN_TEST(ProtocolFamilyMembershipTest, "packet");
171171
PTF_RUN_TEST(PacketParseLayerLimitTest, "packet");
172+
PTF_RUN_TEST(PacketParseMultiLayerTest, "packet");
172173

173174
PTF_RUN_TEST(HttpRequestParseMethodTest, "http");
174175
PTF_RUN_TEST(HttpRequestLayerParsingTest, "http");

Tests/PcppTestUtilities/Resources.cpp

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ namespace pcpp_tests
5151
}
5252
} // namespace
5353

54-
ResourceProvider::ResourceProvider(std::string dataRoot) : m_DataRoot(std::move(dataRoot))
54+
ResourceProvider::ResourceProvider(std::string dataRoot, bool frozen)
55+
: m_DataRoot(std::move(dataRoot)), m_Frozen(frozen)
5556
{}
5657

5758
Resource ResourceProvider::loadResource(const char* filename, ResourceType resourceType) const
@@ -101,6 +102,56 @@ namespace pcpp_tests
101102
throw std::invalid_argument("Unsupported resource type");
102103
}
103104
}
105+
106+
void ResourceProvider::saveResource(ResourceType resourceType, const char* filename, const uint8_t* data,
107+
size_t length) const
108+
{
109+
if (m_Frozen)
110+
{
111+
throw std::runtime_error("Resource provider is frozen and does not allow saving");
112+
}
113+
114+
if (data == nullptr || length == 0)
115+
{
116+
throw std::invalid_argument("Data is null or length is zero");
117+
}
118+
119+
std::string fullPath;
120+
if (!m_DataRoot.empty())
121+
{
122+
fullPath = m_DataRoot + getOsPathSeparator() + filename;
123+
}
124+
else
125+
{
126+
fullPath = filename;
127+
}
128+
129+
auto const requireOpen = [filename](std::ofstream const& fileStream) {
130+
if (!fileStream)
131+
{
132+
throw std::runtime_error(std::string("Failed to open file: ") + filename);
133+
}
134+
};
135+
136+
switch (resourceType)
137+
{
138+
case ResourceType::HexData:
139+
{
140+
std::ofstream fileStream(fullPath);
141+
requireOpen(fileStream);
142+
for (size_t i = 0; i < length; ++i)
143+
{
144+
fileStream << std::hex;
145+
fileStream.width(2);
146+
fileStream.fill('0');
147+
fileStream << static_cast<int>(data[i]);
148+
}
149+
break;
150+
}
151+
default:
152+
throw std::invalid_argument("Unsupported resource type");
153+
}
154+
}
104155
} // namespace utils
105156

106157
namespace

Tests/PcppTestUtilities/Resources.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ namespace pcpp_tests
2929
public:
3030
/// @brief Constructs a ResourceProvider with a specified data root directory.
3131
/// @param dataRoot The root directory from which resources will be loaded.
32-
explicit ResourceProvider(std::string dataRoot);
32+
/// @param frozen If true, the provider is read-only and does not allow saving resources.
33+
explicit ResourceProvider(std::string dataRoot, bool frozen = true);
3334

3435
/// @brief Loads a resource from resource provider.
3536
/// @param filename The name of the resource file to load.
@@ -43,8 +44,18 @@ namespace pcpp_tests
4344
/// @return A vector containing the loaded data.
4445
std::vector<uint8_t> loadResourceToVector(const char* filename, ResourceType resourceType) const;
4546

47+
/// @brief Saves a resource to the resource provider.
48+
/// @param resourceType The type of the resource being saved.
49+
/// @param filename The name of the file to save the resource to.
50+
/// @param data Pointer to the data to be saved.
51+
/// @param length The length of the data in bytes.
52+
/// @throw std::runtime_error if the provider is frozen and does not allow saving.
53+
void saveResource(ResourceType resourceType, const char* filename, const uint8_t* data,
54+
size_t length) const;
55+
4656
private:
4757
std::string m_DataRoot; ///< The root directory for test data files
58+
bool m_Frozen = true; ///< Indicates if the provider is frozen (no modifications allowed)
4859
};
4960

5061
} // namespace utils

0 commit comments

Comments
 (0)