diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_dhcp.cpp | 111 | ||||
| -rw-r--r-- | tests/test_ntp.cpp | 64 |
2 files changed, 175 insertions, 0 deletions
diff --git a/tests/test_dhcp.cpp b/tests/test_dhcp.cpp new file mode 100644 index 0000000..e9cbc16 --- /dev/null +++ b/tests/test_dhcp.cpp @@ -0,0 +1,111 @@ +#include <doctest/doctest.h> + +#include <algorithm> +#include <array> +#include <vector> + +#include "packeteer/l7/dhcp.hpp" + +using namespace packeteer::net; + +namespace { + +// Builds a minimal BOOTP fixed header + magic cookie + a message-type +// option (53), optionally with yiaddr set. +std::vector<unsigned char> dhcp_message(std::uint8_t op, std::uint8_t message_type, + std::array<unsigned char, 4> yiaddr = {0, 0, 0, 0}) { + std::vector<unsigned char> bytes(240, 0); + bytes[0] = op; + std::copy(yiaddr.begin(), yiaddr.end(), bytes.begin() + 16); + bytes[236] = 0x63; + bytes[237] = 0x82; + bytes[238] = 0x53; + bytes[239] = 0x63; + + // Option 53 (message type), length 1, then End. + bytes.push_back(53); + bytes.push_back(1); + bytes.push_back(message_type); + bytes.push_back(0xFF); + return bytes; +} + +} // namespace + +TEST_CASE("parse_dhcp decodes a DISCOVER") { + auto msg = parse_dhcp(dhcp_message(1, kDhcpDiscover)); + REQUIRE(msg.has_value()); + CHECK(msg->op == 1); + REQUIRE(msg->message_type.has_value()); + CHECK(*msg->message_type == kDhcpDiscover); +} + +TEST_CASE("parse_dhcp decodes an OFFER with yiaddr set") { + auto msg = parse_dhcp(dhcp_message(2, kDhcpOffer, {192, 168, 1, 50})); + REQUIRE(msg.has_value()); + REQUIRE(msg->message_type.has_value()); + CHECK(*msg->message_type == kDhcpOffer); + CHECK(msg->yiaddr.bytes == std::array<unsigned char, 4>{192, 168, 1, 50}); +} + +TEST_CASE("parse_dhcp rejects a payload without the magic cookie") { + std::vector<unsigned char> bytes(240, 0); // right size, but cookie bytes are zero + CHECK_FALSE(parse_dhcp(bytes).has_value()); +} + +TEST_CASE("parse_dhcp rejects a payload shorter than the fixed header + cookie") { + std::vector<unsigned char> bytes(100, 0); + CHECK_FALSE(parse_dhcp(bytes).has_value()); +} + +TEST_CASE("parse_dhcp handles a message with no options gracefully") { + std::vector<unsigned char> bytes(240, 0); + bytes[0] = 1; + bytes[236] = 0x63; bytes[237] = 0x82; bytes[238] = 0x53; bytes[239] = 0x63; + auto msg = parse_dhcp(bytes); + REQUIRE(msg.has_value()); + CHECK_FALSE(msg->message_type.has_value()); +} + +TEST_CASE("parse_dhcp skips pad options while scanning for message type") { + std::vector<unsigned char> bytes(240, 0); + bytes[236] = 0x63; bytes[237] = 0x82; bytes[238] = 0x53; bytes[239] = 0x63; + bytes.push_back(0x00); // pad + bytes.push_back(0x00); // pad + bytes.push_back(53); + bytes.push_back(1); + bytes.push_back(kDhcpAck); + bytes.push_back(0xFF); + + auto msg = parse_dhcp(bytes); + REQUIRE(msg.has_value()); + REQUIRE(msg->message_type.has_value()); + CHECK(*msg->message_type == kDhcpAck); +} + +TEST_CASE("DhcpDissector claims port 67 and names message types") { + DhcpDissector dissector; + CHECK(dissector.port() == kDhcpServerPort); + + auto summary = dissector.summarize(dhcp_message(2, kDhcpOffer, {192, 168, 1, 50})); + REQUIRE(summary.has_value()); + CHECK(*summary == "DHCP OFFER yiaddr=192.168.1.50"); +} + +TEST_CASE("DhcpDissector omits yiaddr when it's all-zero") { + DhcpDissector dissector; + auto summary = dissector.summarize(dhcp_message(1, kDhcpDiscover)); + REQUIRE(summary.has_value()); + CHECK(*summary == "DHCP DISCOVER"); +} + +TEST_CASE("DhcpDissector falls back to op when message type is absent") { + DhcpDissector dissector; + std::vector<unsigned char> bytes(240, 0); + bytes[0] = 1; + bytes[236] = 0x63; bytes[237] = 0x82; bytes[238] = 0x53; bytes[239] = 0x63; + + auto summary = dissector.summarize(bytes); + REQUIRE(summary.has_value()); + CHECK(*summary == "DHCP request"); +} diff --git a/tests/test_ntp.cpp b/tests/test_ntp.cpp new file mode 100644 index 0000000..2c93302 --- /dev/null +++ b/tests/test_ntp.cpp @@ -0,0 +1,64 @@ +#include <doctest/doctest.h> + +#include <vector> + +#include "packeteer/l7/ntp.hpp" + +using namespace packeteer::net; + +namespace { + +std::vector<unsigned char> ntp_client_request() { + std::vector<unsigned char> bytes(48, 0); + bytes[0] = (4 << 3) | 3; // version 4, mode 3 (client) + bytes[1] = 0; // stratum: not applicable on a client request + return bytes; +} + +std::vector<unsigned char> ntp_server_reply() { + std::vector<unsigned char> bytes(48, 0); + bytes[0] = (4 << 3) | 4; // version 4, mode 4 (server) + bytes[1] = 2; // stratum 2 + return bytes; +} + +} // namespace + +TEST_CASE("parse_ntp decodes a client request") { + auto ntp = parse_ntp(ntp_client_request()); + REQUIRE(ntp.has_value()); + CHECK(ntp->version == 4); + CHECK(ntp->mode == 3); +} + +TEST_CASE("parse_ntp decodes a server reply with its stratum") { + auto ntp = parse_ntp(ntp_server_reply()); + REQUIRE(ntp.has_value()); + CHECK(ntp->mode == 4); + CHECK(ntp->stratum == 2); +} + +TEST_CASE("parse_ntp rejects a payload shorter than the fixed header") { + std::vector<unsigned char> bytes(20, 0); + CHECK_FALSE(parse_ntp(bytes).has_value()); +} + +TEST_CASE("NtpDissector claims port 123 and includes stratum for client/server modes") { + NtpDissector dissector; + CHECK(dissector.port() == kNtpPort); + + auto summary = dissector.summarize(ntp_server_reply()); + REQUIRE(summary.has_value()); + CHECK(*summary == "NTP v4 server stratum=2"); +} + +TEST_CASE("NtpDissector omits stratum for non-client/server modes") { + NtpDissector dissector; + std::vector<unsigned char> bytes(48, 0); + bytes[0] = (4 << 3) | 5; // mode 5: broadcast + bytes[1] = 1; + + auto summary = dissector.summarize(bytes); + REQUIRE(summary.has_value()); + CHECK(summary->find("stratum") == std::string::npos); +} |