srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_summarize.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_summarize.cpp')
-rw-r--r--tests/test_summarize.cpp185
1 files changed, 185 insertions, 0 deletions
diff --git a/tests/test_summarize.cpp b/tests/test_summarize.cpp
new file mode 100644
index 0000000..d10053d
--- /dev/null
+++ b/tests/test_summarize.cpp
@@ -0,0 +1,185 @@
+#include <doctest/doctest.h>
+#include <pcap.h>
+
+#include <string_view>
+#include <vector>
+
+#include "wireframe/summarize.hpp"
+
+namespace {
+
+// Ethernet + IPv4 + UDP + DNS query for "example.com", assembled the
+// same way the real capture path hands bytes to summarize_packet: one
+// contiguous frame, no struct-casting.
+std::vector<unsigned char> ethernet_ipv4_udp_dns_frame() {
+ std::vector<unsigned char> dns = {
+ 0x12, 0x9d, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0,
+ 0x00, 0x01, 0x00, 0x01,
+ };
+
+ std::vector<unsigned char> udp(8, 0);
+ udp[0] = 0xD4; udp[1] = 0x31; // src port 54321
+ udp[2] = 0x00; udp[3] = 0x35; // dst port 53
+ std::uint16_t udp_len = static_cast<std::uint16_t>(8 + dns.size());
+ udp[4] = static_cast<unsigned char>(udp_len >> 8);
+ udp[5] = static_cast<unsigned char>(udp_len & 0xFF);
+
+ std::vector<unsigned char> ip(20, 0);
+ ip[0] = 0x45;
+ ip[8] = 64; // ttl
+ ip[9] = wireframe::net::kProtoUdp; // proto
+ ip[12] = 10; ip[13] = 0; ip[14] = 0; ip[15] = 1; // src 10.0.0.1
+ ip[16] = 10; ip[17] = 0; ip[18] = 0; ip[19] = 2; // dst 10.0.0.2
+
+ std::vector<unsigned char> eth = {
+ 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, // dst mac
+ 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, // src mac
+ 0x08, 0x00, // ethertype IPv4
+ };
+
+ std::vector<unsigned char> frame = eth;
+ frame.insert(frame.end(), ip.begin(), ip.end());
+ frame.insert(frame.end(), udp.begin(), udp.end());
+ frame.insert(frame.end(), dns.begin(), dns.end());
+ return frame;
+}
+
+// Ethernet + IPv4 + TCP + an HTTP GET request. This is the only test
+// exercising L7Registry's TCP-payload path with a real registered
+// dissector - DNS only ever runs over UDP, so summarize_packet's TCP
+// branch calling into l7_summarize() was otherwise unverified.
+std::vector<unsigned char> ethernet_ipv4_tcp_http_frame() {
+ std::string_view request = "GET /index.html HTTP/1.1\r\nHost: example.com\r\n\r\n";
+ std::vector<unsigned char> http(request.begin(), request.end());
+
+ std::vector<unsigned char> tcp(20, 0);
+ tcp[0] = 0xC3; tcp[1] = 0x50; // src port 50000
+ tcp[2] = 0x00; tcp[3] = 0x50; // dst port 80
+ tcp[12] = 5 << 4; // data_offset = 5 (20-byte header)
+ tcp[13] = 0x18; // PSH | ACK
+
+ std::vector<unsigned char> ip(20, 0);
+ ip[0] = 0x45;
+ ip[8] = 64; // ttl
+ ip[9] = wireframe::net::kProtoTcp; // proto
+ ip[12] = 10; ip[13] = 0; ip[14] = 0; ip[15] = 1; // src 10.0.0.1
+ ip[16] = 10; ip[17] = 0; ip[18] = 0; ip[19] = 2; // dst 10.0.0.2
+
+ std::vector<unsigned char> eth = {
+ 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, // dst mac
+ 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, // src mac
+ 0x08, 0x00, // ethertype IPv4
+ };
+
+ std::vector<unsigned char> frame = eth;
+ frame.insert(frame.end(), ip.begin(), ip.end());
+ frame.insert(frame.end(), tcp.begin(), tcp.end());
+ frame.insert(frame.end(), http.begin(), http.end());
+ return frame;
+}
+
+// Ethernet + IPv6 + a Hop-by-Hop Options extension header + TCP. Proves
+// walk_ipv6_extension_headers() is actually wired into summarize_packet's
+// IPv6 branch, not just unit-tested in isolation - without it, this
+// packet's TCP layer (and any L7 behind it) would be silently invisible.
+std::vector<unsigned char> ethernet_ipv6_hopbyhop_tcp_frame() {
+ std::vector<unsigned char> tcp(20, 0);
+ tcp[0] = 0x00; tcp[1] = 0x50; // src port 80
+ tcp[2] = 0x00; tcp[3] = 0x51; // dst port 81
+ tcp[12] = 5 << 4; // data_offset = 5
+ tcp[13] = 0x02; // SYN
+
+ std::vector<unsigned char> hop_by_hop = {
+ static_cast<unsigned char>(wireframe::net::kProtoTcp),
+ 0x00, // hdr_ext_len = 0 -> total length (0+1)*8 = 8 bytes
+ 0, 0, 0, 0, 0, 0, // option padding
+ };
+
+ std::vector<unsigned char> ip6(40, 0);
+ ip6[0] = 0x60; // version 6
+ std::uint16_t payload_len = static_cast<std::uint16_t>(hop_by_hop.size() + tcp.size());
+ ip6[4] = static_cast<unsigned char>(payload_len >> 8);
+ ip6[5] = static_cast<unsigned char>(payload_len & 0xFF);
+ ip6[6] = wireframe::net::kNextHeaderHopByHop;
+ ip6[7] = 64; // hop_limit
+ ip6[23] = 0x01; // src = ::1
+ ip6[39] = 0x01; // dst = ::1
+
+ std::vector<unsigned char> eth = {
+ 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, // dst mac
+ 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, // src mac
+ 0x86, 0xDD, // ethertype IPv6
+ };
+
+ std::vector<unsigned char> frame = eth;
+ frame.insert(frame.end(), ip6.begin(), ip6.end());
+ frame.insert(frame.end(), hop_by_hop.begin(), hop_by_hop.end());
+ frame.insert(frame.end(), tcp.begin(), tcp.end());
+ return frame;
+}
+
+} // namespace
+
+TEST_CASE("summarize_packet walks a Hop-by-Hop extension header to reach TCP") {
+ auto line = wireframe::summarize_packet(ethernet_ipv6_hopbyhop_tcp_frame(), DLT_EN10MB);
+ CHECK(line ==
+ "ETH aa:bb:cc:dd:ee:ff -> 11:22:33:44:55:66 ethertype=0x86dd"
+ " | IPv6 ::1 -> ::1 ttl=64 proto=6"
+ " | TCP 80 -> 81 [S] seq=0 ack=0 win=0");
+}
+
+TEST_CASE("summarize_packet decodes a full Ethernet/IPv4/TCP/HTTP frame end to end") {
+ auto line = wireframe::summarize_packet(ethernet_ipv4_tcp_http_frame(), DLT_EN10MB);
+ CHECK(line ==
+ "ETH aa:bb:cc:dd:ee:ff -> 11:22:33:44:55:66 ethertype=0x0800"
+ " | IPv4 10.0.0.1 -> 10.0.0.2 ttl=64 proto=6"
+ " | TCP 50000 -> 80 [AP] seq=0 ack=0 win=0"
+ " | HTTP GET /index.html Host: example.com");
+}
+
+TEST_CASE("summarize_packet decodes a full Ethernet/IPv4/UDP/DNS frame end to end") {
+ auto line = wireframe::summarize_packet(ethernet_ipv4_udp_dns_frame(), DLT_EN10MB);
+ CHECK(line ==
+ "ETH aa:bb:cc:dd:ee:ff -> 11:22:33:44:55:66 ethertype=0x0800"
+ " | IPv4 10.0.0.1 -> 10.0.0.2 ttl=64 proto=17"
+ " | UDP 54321 -> 53 len=37"
+ " | DNS query id=4765 example.com type=1");
+}
+
+TEST_CASE("summarize_packet on DLT_RAW skips the Ethernet line entirely") {
+ auto frame = ethernet_ipv4_udp_dns_frame();
+ std::vector<unsigned char> raw(frame.begin() + wireframe::net::kEthernetHeaderLen, frame.end());
+
+ auto line = wireframe::summarize_packet(raw, DLT_RAW);
+ CHECK(line.substr(0, 3) == "RAW");
+ CHECK(line.find("ETH") == std::string::npos);
+ CHECK(line.find("IPv4 10.0.0.1 -> 10.0.0.2") != std::string::npos);
+}
+
+TEST_CASE("summarize_packet reports a truncated Ethernet frame without decoding further") {
+ std::vector<unsigned char> bytes(10, 0); // shorter than the 14-byte header
+ auto line = wireframe::summarize_packet(bytes, DLT_EN10MB);
+ CHECK(line == "[10 bytes] truncated ethernet frame");
+}
+
+TEST_CASE("summarize_packet stops after the Ethernet line for a non-IP ethertype") {
+ std::vector<unsigned char> bytes = {
+ 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
+ 0x08, 0x06, // ARP, not IPv4/IPv6
+ };
+ auto line = wireframe::summarize_packet(bytes, DLT_EN10MB);
+ CHECK(line == "ETH aa:bb:cc:dd:ee:ff -> 11:22:33:44:55:66 ethertype=0x0806");
+}
+
+TEST_CASE("hex_dump_lines produces one line per 16 bytes, with the right byte count") {
+ std::vector<unsigned char> bytes(20, 0);
+ for (std::size_t i = 0; i < bytes.size(); ++i) bytes[i] = static_cast<unsigned char>(i);
+
+ auto lines = wireframe::hex_dump_lines(bytes);
+ REQUIRE(lines.size() == 2);
+ CHECK(lines[0].substr(0, 6) == "000000");
+ CHECK(lines[1].substr(0, 6) == "000010");
+ CHECK(lines[0].find("00 01 02 03") != std::string::npos);
+ CHECK(lines[0].find('|') != std::string::npos);
+}