1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
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");
}
|