///|
/// LLDP EtherType.
pub let ether_type_lldp : Int = 0x88CC
///|
/// LLDP multicast destination MAC.
pub let lldp_multicast_mac : String = "01:80:C2:00:00:0E"
///|
/// LLDP TLV types.
pub let tlv_type_end : Int = 0
///|
pub let tlv_type_chassis_id : Int = 1
///|
pub let tlv_type_port_id : Int = 2
///|
pub let tlv_type_ttl : Int = 3
///|
pub let tlv_type_port_description : Int = 4
///|
pub let tlv_type_system_name : Int = 5
///|
pub let tlv_type_system_description : Int = 6
///|
pub let tlv_type_system_capabilities : Int = 7
///|
pub let tlv_type_management_address : Int = 8
///|
/// PROFINET-specific TLV organization OUI.
pub let oui_profinet : Bytes = Bytes::from_array([b'\x00', b'\x0E', b'\xCF'])
///|
pub let oui_ieee_802_1 : Bytes = Bytes::from_array([b'\x00', b'\x80', b'\xC2'])
///|
pub let oui_ieee_802_3 : Bytes = Bytes::from_array([b'\x30', b'\x00', b'\x00'])
///|
/// LLDP TLV parsed structure.
pub(all) struct LldpTlv {
tlv_type : Int
length : Int
value : Bytes
} derive(Eq, Debug)
///|
/// Parse LLDP TLVs from a frame payload (after Ethernet header).
pub fn parse_lldp_tlvs(
data : Bytes,
offset : Int,
) -> Array[LldpTlv] raise @frame.FrameError {
let tlvs : Array[LldpTlv] = []
let mut pos = offset
while pos + 2 <= data.length() {
let type_len = (data[pos].to_int() << 8) + data[pos + 1].to_int()
let tlv_type = (type_len >> 9) & 0x7F
let length = type_len & 0x01FF
pos = pos + 2
guard pos + length <= data.length() else {
raise @frame.FrameError::InvalidMacLength(data.length())
}
let value_bytes : Array[Byte] = []
for i = 0; i < length; i = i + 1 {
value_bytes.push(data[pos + i])
}
tlvs.push(LldpTlv::{
tlv_type,
length,
value: Bytes::from_array(value_bytes),
})
if tlv_type == tlv_type_end {
break
}
pos = pos + length
}
tlvs
}
///|
fn ascii_bytes_to_string(bytes : Bytes) -> String {
let mut s = ""
for b in bytes {
if b.to_int() == 0 {
break
}
s = s + b.to_int().unsafe_to_char().to_string()
}
s
}
///|
pub fn tlv_type_label(tlv_type : Int) -> String {
match tlv_type {
0 => "End"
1 => "ChassisID"
2 => "PortID"
3 => "TTL"
4 => "PortDescription"
5 => "SystemName"
6 => "SystemDescription"
7 => "SystemCapabilities"
8 => "ManagementAddress"
127 => "OrganizationSpecific"
_ => "Unknown(" + tlv_type.to_string() + ")"
}
}
///|
/// LLDP neighbor info extracted from TLVs.
pub(all) struct LldpNeighbor {
chassis_id : String
port_id : String
ttl : Int
system_name : String
system_description : String
port_description : String
management_address : String
} derive(Eq, Debug)
///|
fn read_u16_be(data : Bytes, offset : Int) -> Int {
(data[offset].to_int() << 8) + data[offset + 1].to_int()
}
///|
fn ipv4_bytes_to_string(data : Bytes, offset : Int) -> String {
data[offset].to_int().to_string() +
"." +
data[offset + 1].to_int().to_string() +
"." +
data[offset + 2].to_int().to_string() +
"." +
data[offset + 3].to_int().to_string()
}
///|
fn parse_management_address(value : Bytes) -> String {
if value.length() < 2 {
return ""
}
let address_len = value[0].to_int()
if address_len < 2 || value.length() < 1 + address_len {
return ""
}
let subtype = value[1].to_int()
if subtype == 1 && address_len == 5 {
ipv4_bytes_to_string(value, 2)
} else {
let address_bytes : Array[Byte] = []
for i = 1; i < 1 + address_len; i = i + 1 {
address_bytes.push(value[i])
}
@frame.bytes_to_hex(Bytes::from_array(address_bytes), separator=":")
}
}
///|
/// Extract neighbor info from parsed TLVs.
pub fn extract_neighbor(tlvs : Array[LldpTlv]) -> LldpNeighbor {
let mut chassis_id = ""
let mut port_id = ""
let mut ttl = 0
let mut system_name = ""
let mut system_description = ""
let mut port_description = ""
let mut management_address = ""
for tlv in tlvs {
match tlv.tlv_type {
1 =>
if tlv.value.length() > 1 {
// skip subtype byte
let sub : Array[Byte] = []
for i = 1; i < tlv.value.length(); i = i + 1 {
sub.push(tlv.value[i])
}
chassis_id = @frame.bytes_to_hex(
Bytes::from_array(sub),
separator=":",
)
}
2 =>
if tlv.value.length() > 1 {
let sub : Array[Byte] = []
for i = 1; i < tlv.value.length(); i = i + 1 {
sub.push(tlv.value[i])
}
port_id = ascii_bytes_to_string(Bytes::from_array(sub))
}
3 => if tlv.value.length() >= 2 { ttl = read_u16_be(tlv.value, 0) }
4 => port_description = ascii_bytes_to_string(tlv.value)
5 => system_name = ascii_bytes_to_string(tlv.value)
6 => system_description = ascii_bytes_to_string(tlv.value)
8 => management_address = parse_management_address(tlv.value)
_ => ()
}
}
LldpNeighbor::{
chassis_id,
port_id,
ttl,
system_name,
system_description,
port_description,
management_address,
}
}
///|
/// Format LLDP neighbor summary.
pub fn format_neighbor(n : LldpNeighbor) -> String {
let lines : Array[String] = []
lines.push("chassis_id=" + n.chassis_id)
lines.push("port_id=" + n.port_id)
lines.push("ttl=" + n.ttl.to_string())
lines.push("system_name=" + n.system_name)
lines.push("system_description=" + n.system_description)
lines.push("port_description=" + n.port_description)
lines.push("management_address=" + n.management_address)
lines.join("\n")
}
///|
fn push_u16_be(output : Array[Byte], value : Int) -> Unit {
output.push(((value >> 8) & 0xFF).to_byte())
output.push((value & 0xFF).to_byte())
}
///|
fn append_bytes(output : Array[Byte], data : Bytes) -> Unit {
for byte in data {
output.push(byte)
}
}
///|
/// Encode a single LLDP TLV.
pub fn encode_tlv(tlv_type : Int, value : Bytes) -> Bytes {
let output : Array[Byte] = []
let type_len = ((tlv_type & 0x7F) << 9) | (value.length() & 0x01FF)
push_u16_be(output, type_len)
append_bytes(output, value)
Bytes::from_array(output)
}
///|
/// Build a minimal LLDP frame with required TLVs.
pub fn build_lldp_frame(
chassis_id : Bytes,
port_id : Bytes,
ttl : Int,
system_name? : String = "",
) -> Bytes {
let output : Array[Byte] = []
// Chassis ID TLV (subtype 4 = MAC address)
let chassis_val : Array[Byte] = [b'\x04']
append_bytes(chassis_val, chassis_id)
append_bytes(
output,
encode_tlv(tlv_type_chassis_id, Bytes::from_array(chassis_val)),
)
// Port ID TLV (subtype 7 = locally assigned)
let port_val : Array[Byte] = [b'\x07']
for c in port_id {
port_val.push(c)
}
append_bytes(
output,
encode_tlv(tlv_type_port_id, Bytes::from_array(port_val)),
)
// TTL TLV
let ttl_bytes : Array[Byte] = []
push_u16_be(ttl_bytes, ttl)
append_bytes(output, encode_tlv(tlv_type_ttl, Bytes::from_array(ttl_bytes)))
// System Name TLV (optional)
if system_name != "" {
let name_bytes = system_name.to_array().map(fn(c) { c.to_int().to_byte() })
append_bytes(
output,
encode_tlv(tlv_type_system_name, Bytes::from_array(name_bytes)),
)
}
// End TLV
append_bytes(output, encode_tlv(tlv_type_end, Bytes::default()))
Bytes::from_array(output)
}