#include #include #include "packeteer/l7/quic.hpp" using namespace packeteer::net; namespace { std::vector long_header(std::uint8_t type_bits, std::uint32_t version, std::vector dcid, std::vector scid) { std::vector bytes; bytes.push_back(static_cast(0xC0 | (type_bits << 4))); // long form, fixed bit bytes.push_back(static_cast(version >> 24)); bytes.push_back(static_cast(version >> 16)); bytes.push_back(static_cast(version >> 8)); bytes.push_back(static_cast(version)); bytes.push_back(static_cast(dcid.size())); bytes.insert(bytes.end(), dcid.begin(), dcid.end()); bytes.push_back(static_cast(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{0xAA, 0xBB, 0xCC, 0xDD}); CHECK(pkt->long_header->scid == std::vector{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 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 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 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 bytes = {0xC0, 0x00, 0x00, 0x00, 0x01, 20}; // claims 20-byte DCID CHECK_FALSE(parse_quic(bytes).has_value()); } TEST_CASE("parse_quic rejects a DCID/SCID length past RFC 9000's 20-byte cap even with room in " "the buffer") { // A real protocol bound, not a buffer-size check: plenty of bytes // are available here, the claimed length is just illegal for this // QUIC version. This is what actually stops a mid-record TLS // ciphertext continuation fragment (effectively random bytes to // this parser, since this project doesn't reassemble TCP by // default) from occasionally passing as a plausible QUIC header -- // found via live capture against real cloudflare.com traffic, not // by inspection. std::vector bytes = {0xC0, 0x00, 0x00, 0x00, 0x01, 21}; bytes.resize(bytes.size() + 21, 0xAA); // plenty of room for a 21-byte DCID CHECK_FALSE(parse_quic(bytes).has_value()); std::vector scid_bytes = {0xC0, 0x00, 0x00, 0x00, 0x01, 0, 21}; scid_bytes.resize(scid_bytes.size() + 21, 0xAA); // plenty of room for a 21-byte SCID CHECK_FALSE(parse_quic(scid_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 bytes = {0x40, 0xAA, 0xBB, 0xCC}; auto summary = dissector.summarize(bytes); REQUIRE(summary.has_value()); CHECK(*summary == "QUIC 1-RTT (short header)"); }