#include #include #include "packeteer/net/lldp.hpp" using namespace packeteer::net; namespace { std::vector tlv(std::uint8_t type, const std::vector& value) { std::uint16_t header = static_cast((type << 9) | value.size()); std::vector out = {static_cast(header >> 8), static_cast(header & 0xFF)}; out.insert(out.end(), value.begin(), value.end()); return out; } std::vector lldpdu(std::optional> extra_tlv = {}) { std::vector bytes; auto chassis_id = tlv(1, {4, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}); // subtype 4: MAC auto port_id = tlv(2, {7, 'e', 't', 'h', '0'}); // subtype 7: locally assigned auto ttl = tlv(3, {0x00, 0x78}); // 120 seconds bytes.insert(bytes.end(), chassis_id.begin(), chassis_id.end()); bytes.insert(bytes.end(), port_id.begin(), port_id.end()); bytes.insert(bytes.end(), ttl.begin(), ttl.end()); if (extra_tlv) bytes.insert(bytes.end(), extra_tlv->begin(), extra_tlv->end()); auto end = tlv(0, {}); bytes.insert(bytes.end(), end.begin(), end.end()); return bytes; } } // namespace TEST_CASE("parse_lldp decodes the three mandatory TLVs") { auto msg = parse_lldp(lldpdu()); REQUIRE(msg.has_value()); REQUIRE(msg->chassis_id.has_value()); CHECK(*msg->chassis_id == "aa:bb:cc:dd:ee:ff"); REQUIRE(msg->port_id.has_value()); CHECK(*msg->port_id == "eth0"); REQUIRE(msg->ttl.has_value()); CHECK(*msg->ttl == 120); CHECK_FALSE(msg->system_name.has_value()); } TEST_CASE("parse_lldp decodes the System Name TLV when present") { auto msg = parse_lldp(lldpdu(tlv(5, {'s', 'w', 'i', 't', 'c', 'h', '1'}))); REQUIRE(msg.has_value()); REQUIRE(msg->system_name.has_value()); CHECK(*msg->system_name == "switch1"); } TEST_CASE("parse_lldp skips over an unrecognized TLV without losing later ones") { // Type 6 (System Description) isn't decoded, but must not break // parsing of the End TLV that follows it. auto msg = parse_lldp(lldpdu(tlv(6, {'d', 'e', 's', 'c'}))); REQUIRE(msg.has_value()); REQUIRE(msg->chassis_id.has_value()); REQUIRE(msg->port_id.has_value()); REQUIRE(msg->ttl.has_value()); } TEST_CASE("parse_lldp rejects a message missing a mandatory TLV") { // Chassis ID and Port ID only, no TTL - not a well-formed LLDPDU. std::vector bytes; auto chassis_id = tlv(1, {4, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}); auto port_id = tlv(2, {7, 'e', 't', 'h', '0'}); bytes.insert(bytes.end(), chassis_id.begin(), chassis_id.end()); bytes.insert(bytes.end(), port_id.begin(), port_id.end()); auto end = tlv(0, {}); bytes.insert(bytes.end(), end.begin(), end.end()); CHECK_FALSE(parse_lldp(bytes).has_value()); } TEST_CASE("parse_lldp rejects an empty buffer") { std::vector bytes; CHECK_FALSE(parse_lldp(bytes).has_value()); } TEST_CASE("format_lldp_id renders a MAC-address subtype as hex-colon") { std::vector mac = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; CHECK(format_lldp_id(4, mac) == "aa:bb:cc:dd:ee:ff"); } TEST_CASE("format_lldp_id renders a non-MAC subtype as plain text") { std::vector text = {'e', 't', 'h', '0'}; CHECK(format_lldp_id(7, text) == "eth0"); } TEST_CASE("lldp_summary formats chassis/port/ttl, and name only when present") { LldpInfo info{"aa:bb:cc:dd:ee:ff", "eth0", 120, std::nullopt}; CHECK(lldp_summary(info) == "LLDP chassis=aa:bb:cc:dd:ee:ff port=eth0 ttl=120"); info.system_name = "switch1"; CHECK(lldp_summary(info) == "LLDP chassis=aa:bb:cc:dd:ee:ff port=eth0 ttl=120 name=switch1"); }