diff options
Diffstat (limited to 'tests/test_lldp.cpp')
| -rw-r--r-- | tests/test_lldp.cpp | 98 |
1 files changed, 98 insertions, 0 deletions
diff --git a/tests/test_lldp.cpp b/tests/test_lldp.cpp new file mode 100644 index 0000000..3d48970 --- /dev/null +++ b/tests/test_lldp.cpp @@ -0,0 +1,98 @@ +#include <doctest/doctest.h> + +#include <vector> + +#include "packeteer/net/lldp.hpp" + +using namespace packeteer::net; + +namespace { + +std::vector<unsigned char> tlv(std::uint8_t type, const std::vector<unsigned char>& value) { + std::uint16_t header = static_cast<std::uint16_t>((type << 9) | value.size()); + std::vector<unsigned char> out = {static_cast<unsigned char>(header >> 8), + static_cast<unsigned char>(header & 0xFF)}; + out.insert(out.end(), value.begin(), value.end()); + return out; +} + +std::vector<unsigned char> lldpdu(std::optional<std::vector<unsigned char>> extra_tlv = {}) { + std::vector<unsigned char> 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<unsigned char> 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<unsigned char> bytes; + CHECK_FALSE(parse_lldp(bytes).has_value()); +} + +TEST_CASE("format_lldp_id renders a MAC-address subtype as hex-colon") { + std::vector<unsigned char> 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<unsigned char> 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"); +} |