1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
|
#include <doctest/doctest.h>
#include <vector>
#include "packeteer/net/ethernet.hpp"
#include "packeteer/net/ipv4.hpp"
#include "packeteer/net/tcp.hpp"
#include "packeteer/net/udp.hpp"
using namespace packeteer::net;
TEST_CASE("parse_ethernet decodes header fields and leaves the right payload") {
std::vector<unsigned char> bytes = {
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, // dst mac
0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, // src mac
0x08, 0x00, // ethertype: IPv4
0xDE, 0xAD, 0xBE, 0xEF, // payload
};
auto frame = parse_ethernet(bytes);
REQUIRE(frame.has_value());
CHECK(frame->header.dst.bytes == std::array<unsigned char, 6>{0x11, 0x22, 0x33, 0x44, 0x55, 0x66});
CHECK(frame->header.src.bytes == std::array<unsigned char, 6>{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF});
CHECK(frame->header.ethertype == kEthertypeIPv4);
REQUIRE(frame->payload.size() == 4);
CHECK(frame->payload[0] == 0xDE);
}
TEST_CASE("parse_ethernet rejects a frame shorter than the header") {
std::vector<unsigned char> bytes(10, 0); // header is 14 bytes
CHECK_FALSE(parse_ethernet(bytes).has_value());
}
TEST_CASE("walk_vlan_tags passes an untagged ethertype through unchanged") {
std::vector<unsigned char> payload = {0xDE, 0xAD, 0xBE, 0xEF};
auto result = walk_vlan_tags(kEthertypeIPv4, payload);
CHECK(result.vlan_ids.empty());
CHECK(result.ethertype == kEthertypeIPv4);
REQUIRE(result.payload.size() == 4);
CHECK(result.payload[0] == 0xDE);
}
TEST_CASE("walk_vlan_tags unwraps a single 802.1Q tag") {
std::vector<unsigned char> payload = {
0x00, 42, // TCI: VLAN ID 42 (PCP/DEI bits left zero)
0x08, 0x00, // real ethertype: IPv4
0xDE, 0xAD, // real payload
};
auto result = walk_vlan_tags(kEthertypeVlan, payload);
REQUIRE(result.vlan_ids.size() == 1);
CHECK(result.vlan_ids[0] == 42);
CHECK(result.ethertype == kEthertypeIPv4);
REQUIRE(result.payload.size() == 2);
CHECK(result.payload[0] == 0xDE);
}
TEST_CASE("walk_vlan_tags unwraps a stacked QinQ pair, outer to inner") {
std::vector<unsigned char> payload = {
0x00, 100, // outer TCI: VLAN 100
0x81, 0x00, // inner tag's TPID
0x00, 42, // inner TCI: VLAN 42
0x08, 0x00, // real ethertype: IPv4
0xBE, 0xEF,
};
auto result = walk_vlan_tags(kEthertypeVlanQinQ, payload);
REQUIRE(result.vlan_ids.size() == 2);
CHECK(result.vlan_ids[0] == 100);
CHECK(result.vlan_ids[1] == 42);
CHECK(result.ethertype == kEthertypeIPv4);
}
TEST_CASE("walk_vlan_tags stops at a truncated tag rather than reading past it") {
std::vector<unsigned char> payload = {0x00, 42}; // 2 bytes: not a full 4-byte tag
auto result = walk_vlan_tags(kEthertypeVlan, payload);
CHECK(result.vlan_ids.empty());
CHECK(result.ethertype == kEthertypeVlan); // unchanged: nothing was actually unwrapped
}
TEST_CASE("walk_vlan_tags is bounded against a claimed unbounded tag chain") {
// Each 4-byte block claims "the next ethertype is another VLAN
// tag" - a corrupt/hostile frame that never actually reaches a
// real ethertype. Must stop, not loop indefinitely.
std::vector<unsigned char> payload;
for (int i = 0; i < 100; ++i) {
payload.insert(payload.end(), {0x00, 0x01, 0x81, 0x00});
}
auto result = walk_vlan_tags(kEthertypeVlan, payload);
CHECK(result.vlan_ids.size() <= 4);
}
TEST_CASE("parse_ipv4 decodes header fields and leaves the right payload") {
std::vector<unsigned char> bytes(20, 0);
bytes[0] = 0x45; // version 4, IHL 5 (20-byte header, no options)
bytes[2] = 0x00;
bytes[3] = 0x28; // total_length = 40
bytes[8] = 64; // ttl
bytes[9] = kProtoTcp;
bytes[12] = 10; bytes[13] = 0; bytes[14] = 0; bytes[15] = 1; // src 10.0.0.1
bytes[16] = 10; bytes[17] = 0; bytes[18] = 0; bytes[19] = 2; // dst 10.0.0.2
bytes.push_back(0x01);
bytes.push_back(0x02);
auto ip = parse_ipv4(bytes);
REQUIRE(ip.has_value());
CHECK(ip->header.version == 4);
CHECK(ip->header.ihl == 5);
CHECK(ip->header.total_length == 40);
CHECK(ip->header.ttl == 64);
CHECK(ip->header.protocol == kProtoTcp);
CHECK(ip->header.src.bytes == std::array<unsigned char, 4>{10, 0, 0, 1});
CHECK(ip->header.dst.bytes == std::array<unsigned char, 4>{10, 0, 0, 2});
REQUIRE(ip->payload.size() == 2);
CHECK(ip->payload[0] == 0x01);
}
TEST_CASE("parse_ipv4 rejects a non-IPv4 version") {
std::vector<unsigned char> bytes(20, 0);
bytes[0] = 0x65; // version 6
CHECK_FALSE(parse_ipv4(bytes).has_value());
}
TEST_CASE("parse_ipv4 rejects a buffer shorter than the header") {
std::vector<unsigned char> bytes(10, 0);
CHECK_FALSE(parse_ipv4(bytes).has_value());
}
TEST_CASE("parse_ipv4 honors IHL > 5 (options present)") {
std::vector<unsigned char> bytes(24, 0); // IHL=6 -> 24-byte header
bytes[0] = 0x46;
bytes[9] = kProtoUdp;
auto ip = parse_ipv4(bytes);
REQUIRE(ip.has_value());
CHECK(ip->header.ihl == 6);
CHECK(ip->payload.empty());
}
TEST_CASE("parse_tcp decodes header fields and flags") {
std::vector<unsigned char> bytes(20, 0);
bytes[0] = 0x00; bytes[1] = 0x50; // src port 80
bytes[2] = 0x1F; bytes[3] = 0x90; // dst port 8080
bytes[4] = 0; bytes[5] = 0; bytes[6] = 0; bytes[7] = 1; // seq = 1
bytes[8] = 0; bytes[9] = 0; bytes[10] = 0; bytes[11] = 2; // ack = 2
bytes[12] = 5 << 4; // data_offset = 5 (20-byte header, no options)
bytes[13] = 0x12; // SYN | ACK
bytes[14] = 0xFF; bytes[15] = 0xFF; // window 65535
auto tcp = parse_tcp(bytes);
REQUIRE(tcp.has_value());
CHECK(tcp->header.src_port == 80);
CHECK(tcp->header.dst_port == 8080);
CHECK(tcp->header.seq == 1);
CHECK(tcp->header.ack == 2);
CHECK(tcp->header.data_offset == 5);
CHECK((tcp->header.flags & kTcpSyn) != 0);
CHECK((tcp->header.flags & kTcpAck) != 0);
CHECK((tcp->header.flags & kTcpFin) == 0);
CHECK(tcp->header.window == 65535);
CHECK(tcp->payload.empty());
}
TEST_CASE("parse_tcp rejects a buffer shorter than the header") {
std::vector<unsigned char> bytes(10, 0);
CHECK_FALSE(parse_tcp(bytes).has_value());
}
TEST_CASE("parse_udp decodes header fields and leaves the right payload") {
std::vector<unsigned char> bytes = {0x00, 0x35, 0x1F, 0x90, 0x00, 0x0A,
0x00, 0x00, 'h', 'i'};
auto udp = parse_udp(bytes);
REQUIRE(udp.has_value());
CHECK(udp->header.src_port == 53);
CHECK(udp->header.dst_port == 8080);
CHECK(udp->header.length == 10);
REQUIRE(udp->payload.size() == 2);
CHECK(udp->payload[0] == 'h');
}
TEST_CASE("parse_udp rejects a buffer shorter than the header") {
std::vector<unsigned char> bytes(4, 0);
CHECK_FALSE(parse_udp(bytes).has_value());
}
|