diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_dissector.cpp | 74 | ||||
| -rw-r--r-- | tests/test_quic.cpp | 97 |
2 files changed, 171 insertions, 0 deletions
diff --git a/tests/test_dissector.cpp b/tests/test_dissector.cpp new file mode 100644 index 0000000..9dd4135 --- /dev/null +++ b/tests/test_dissector.cpp @@ -0,0 +1,74 @@ +#include <doctest/doctest.h> + +#include "packeteer/l7/dissector.hpp" + +using namespace packeteer::net; + +namespace { + +// A dissector that claims a port but never actually matches any +// payload - stands in for e.g. TlsSniDissector receiving QUIC bytes +// on port 443: same port, wrong protocol, never succeeds. +class NeverMatchesDissector : public L7Dissector { +public: + explicit NeverMatchesDissector(std::uint16_t port) : port_(port) {} + std::uint16_t port() const override { return port_; } + std::optional<std::string> summarize(std::span<const unsigned char>) const override { + return std::nullopt; + } + +private: + std::uint16_t port_; +}; + +class AlwaysMatchesDissector : public L7Dissector { +public: + AlwaysMatchesDissector(std::uint16_t port, std::string label) + : port_(port), label_(std::move(label)) {} + std::uint16_t port() const override { return port_; } + std::optional<std::string> summarize(std::span<const unsigned char>) const override { + return label_; + } + +private: + std::uint16_t port_; + std::string label_; +}; + +} // namespace + +TEST_CASE("L7Registry falls through to a later dissector on the same port " + "when an earlier one fails to match") { + NeverMatchesDissector tls_like(443); + AlwaysMatchesDissector quic_like(443, "QUIC something"); + + L7Registry registry; + registry.add(&tls_like); + registry.add(&quic_like); + + auto result = registry.dissect(443, {}); + REQUIRE(result.has_value()); + CHECK(*result == "QUIC something"); +} + +TEST_CASE("L7Registry still returns the first dissector to succeed, not the last") { + AlwaysMatchesDissector first(80, "first"); + AlwaysMatchesDissector second(80, "second"); + + L7Registry registry; + registry.add(&first); + registry.add(&second); + + auto result = registry.dissect(80, {}); + REQUIRE(result.has_value()); + CHECK(*result == "first"); +} + +TEST_CASE("L7Registry returns nullopt when no dissector on the port matches") { + NeverMatchesDissector only(443); + + L7Registry registry; + registry.add(&only); + + CHECK_FALSE(registry.dissect(443, {}).has_value()); +} diff --git a/tests/test_quic.cpp b/tests/test_quic.cpp new file mode 100644 index 0000000..2366176 --- /dev/null +++ b/tests/test_quic.cpp @@ -0,0 +1,97 @@ +#include <doctest/doctest.h> + +#include <vector> + +#include "packeteer/l7/quic.hpp" + +using namespace packeteer::net; + +namespace { + +std::vector<unsigned char> long_header(std::uint8_t type_bits, std::uint32_t version, + std::vector<unsigned char> dcid, + std::vector<unsigned char> scid) { + std::vector<unsigned char> bytes; + bytes.push_back(static_cast<unsigned char>(0xC0 | (type_bits << 4))); // long form, fixed bit + bytes.push_back(static_cast<unsigned char>(version >> 24)); + bytes.push_back(static_cast<unsigned char>(version >> 16)); + bytes.push_back(static_cast<unsigned char>(version >> 8)); + bytes.push_back(static_cast<unsigned char>(version)); + bytes.push_back(static_cast<unsigned char>(dcid.size())); + bytes.insert(bytes.end(), dcid.begin(), dcid.end()); + bytes.push_back(static_cast<unsigned char>(scid.size())); + bytes.insert(bytes.end(), scid.begin(), scid.end()); + return bytes; +} + +} // namespace + +TEST_CASE("parse_quic decodes a long-header Initial packet's version and connection IDs") { + auto bytes = long_header(0x00, 0x00000001, {0xAA, 0xBB, 0xCC, 0xDD}, {0x11, 0x22}); + auto pkt = parse_quic(bytes); + REQUIRE(pkt.has_value()); + CHECK(pkt->is_long_header); + REQUIRE(pkt->long_header.has_value()); + CHECK(pkt->long_header->type == QuicLongPacketType::kInitial); + CHECK(pkt->long_header->version == 0x00000001); + CHECK(pkt->long_header->dcid == std::vector<unsigned char>{0xAA, 0xBB, 0xCC, 0xDD}); + CHECK(pkt->long_header->scid == std::vector<unsigned char>{0x11, 0x22}); +} + +TEST_CASE("parse_quic decodes each long-packet type from its type bits") { + CHECK(parse_quic(long_header(0x00, 1, {}, {}))->long_header->type == + QuicLongPacketType::kInitial); + CHECK(parse_quic(long_header(0x01, 1, {}, {}))->long_header->type == + QuicLongPacketType::kZeroRtt); + CHECK(parse_quic(long_header(0x02, 1, {}, {}))->long_header->type == + QuicLongPacketType::kHandshake); + CHECK(parse_quic(long_header(0x03, 1, {}, {}))->long_header->type == + QuicLongPacketType::kRetry); +} + +TEST_CASE("parse_quic treats version 0 as Version Negotiation regardless of type bits") { + auto pkt = parse_quic(long_header(0x02, 0x00000000, {0xAA}, {})); + REQUIRE(pkt.has_value()); + CHECK(pkt->long_header->type == QuicLongPacketType::kVersionNegotiation); +} + +TEST_CASE("parse_quic recognizes a short-header packet without decoding past the first byte") { + std::vector<unsigned char> bytes = {0x40, 0xAA, 0xBB, 0xCC}; // short form, fixed bit set + auto pkt = parse_quic(bytes); + REQUIRE(pkt.has_value()); + CHECK_FALSE(pkt->is_long_header); + CHECK_FALSE(pkt->long_header.has_value()); +} + +TEST_CASE("parse_quic rejects a packet with the Fixed Bit clear") { + std::vector<unsigned char> bytes = {0x00, 0xAA, 0xBB, 0xCC}; + CHECK_FALSE(parse_quic(bytes).has_value()); +} + +TEST_CASE("parse_quic rejects a long-header packet truncated before the version field") { + std::vector<unsigned char> bytes = {0xC0, 0x00, 0x00}; + CHECK_FALSE(parse_quic(bytes).has_value()); +} + +TEST_CASE("parse_quic rejects a long-header packet whose DCID length exceeds the buffer") { + std::vector<unsigned char> bytes = {0xC0, 0x00, 0x00, 0x00, 0x01, 20}; // claims 20-byte DCID + CHECK_FALSE(parse_quic(bytes).has_value()); +} + +TEST_CASE("QuicDissector claims port 443 and formats an Initial packet") { + QuicDissector dissector; + CHECK(dissector.port() == kQuicPort); + + auto bytes = long_header(0x00, 0x00000001, {0xAA, 0xBB}, {}); + auto summary = dissector.summarize(bytes); + REQUIRE(summary.has_value()); + CHECK(*summary == "QUIC Initial v=0x00000001 dcid=aabb"); +} + +TEST_CASE("QuicDissector formats a short-header packet distinctly, without a fake dcid") { + QuicDissector dissector; + std::vector<unsigned char> bytes = {0x40, 0xAA, 0xBB, 0xCC}; + auto summary = dissector.summarize(bytes); + REQUIRE(summary.has_value()); + CHECK(*summary == "QUIC 1-RTT (short header)"); +} |