use std::net::IpAddr;
use bitflags::bitflags;
use bytes::Bytes;
use crate::MacAddr;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub struct LldpMessage {
pub chassis_id: ChassisId,
pub port_id: PortId,
pub ttl_seconds: u16,
pub port_description: Option<Bytes>,
pub system_name: Option<Bytes>,
pub system_description: Option<Bytes>,
pub capabilities: Option<SystemCapabilities>,
pub management_addresses: Vec<LldpManagementAddress>,
pub vendor_tlvs: Vec<LldpVendorTlv>,
}
impl LldpMessage {
#[inline]
pub fn is_shutdown_announce(&self) -> bool {
self.ttl_seconds == 0
}
#[inline]
pub fn chassis_id_matches_src(&self, src: MacAddr) -> bool {
matches!(&self.chassis_id, ChassisId::MacAddress(m) if *m == src)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serde",
serde(tag = "kind", content = "value", rename_all = "snake_case")
)]
#[non_exhaustive]
pub enum ChassisId {
ChassisComponent(Bytes),
InterfaceAlias(Bytes),
PortComponent(Bytes),
MacAddress(MacAddr),
NetworkAddress(IpAddr),
InterfaceName(Bytes),
Local(Bytes),
Other { subtype: u8, value: Bytes },
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serde",
serde(tag = "kind", content = "value", rename_all = "snake_case")
)]
#[non_exhaustive]
pub enum PortId {
InterfaceAlias(Bytes),
PortComponent(Bytes),
MacAddress(MacAddr),
NetworkAddress(IpAddr),
InterfaceName(Bytes),
AgentCircuitId(Bytes),
Local(Bytes),
Other { subtype: u8, value: Bytes },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub struct SystemCapabilities {
pub system: CapabilityBits,
pub enabled: CapabilityBits,
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CapabilityBits: u16 {
const OTHER = 1 << 0;
const REPEATER = 1 << 1;
const BRIDGE = 1 << 2;
const WLAN_AP = 1 << 3;
const ROUTER = 1 << 4;
const TELEPHONE = 1 << 5;
const DOCSIS_CABLE = 1 << 6;
const STATION_ONLY = 1 << 7;
const C_VLAN = 1 << 8;
const S_VLAN = 1 << 9;
const TWO_PORT_MAC_RELAY = 1 << 10;
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for CapabilityBits {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_u16(self.bits())
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for CapabilityBits {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let bits = <u16 as serde::Deserialize>::deserialize(deserializer)?;
Ok(CapabilityBits::from_bits_truncate(bits))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub struct LldpManagementAddress {
pub address_family: u8,
pub ip: Option<IpAddr>,
pub raw_address: Bytes,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub struct LldpVendorTlv {
pub oui: [u8; 3],
pub subtype: u8,
pub value: Bytes,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shutdown_announce_predicate() {
let m = LldpMessage {
chassis_id: ChassisId::Local(Bytes::new()),
port_id: PortId::Local(Bytes::new()),
ttl_seconds: 0,
port_description: None,
system_name: None,
system_description: None,
capabilities: None,
management_addresses: Vec::new(),
vendor_tlvs: Vec::new(),
};
assert!(m.is_shutdown_announce());
let m = LldpMessage {
ttl_seconds: 120,
..m
};
assert!(!m.is_shutdown_announce());
}
#[test]
fn chassis_id_matches_src_only_for_mac_form() {
let mac = MacAddr([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]);
let m = LldpMessage {
chassis_id: ChassisId::MacAddress(mac),
port_id: PortId::Local(Bytes::new()),
ttl_seconds: 60,
port_description: None,
system_name: None,
system_description: None,
capabilities: None,
management_addresses: Vec::new(),
vendor_tlvs: Vec::new(),
};
assert!(m.chassis_id_matches_src(mac));
assert!(!m.chassis_id_matches_src(MacAddr([0; 6])));
let m = LldpMessage {
chassis_id: ChassisId::Local(Bytes::from_static(b"sw-01")),
..m
};
assert!(!m.chassis_id_matches_src(mac));
}
#[test]
fn capability_bits_match_spec() {
assert_eq!(CapabilityBits::BRIDGE.bits(), 0x0004);
assert_eq!(CapabilityBits::ROUTER.bits(), 0x0010);
assert_eq!(CapabilityBits::TELEPHONE.bits(), 0x0020);
assert_eq!(CapabilityBits::WLAN_AP.bits(), 0x0008);
}
}