use crate::event::Event;
use crate::state::State;
use crate::types::{MacAddress, NetworkPortStatus, UtpCableStatus, UtpLinkErrors, VctStatus};
use crate::wire::{cstr, UtpLinkSpeed};
use super::handlers::{byte, ipv4_at, u32_at};
use super::Rx;
const MODERN_LAYOUT: u16 = 0x22;
fn vct_status(v: u8) -> [VctStatus; 4] {
let mut out = [VctStatus::Healthy; 4];
for (pair, slot) in out.iter_mut().enumerate() {
if v & (1 << pair) != 0 {
*slot = VctStatus::Warning;
}
}
out
}
fn cable_pairs(d: &[u8], offsets: &[usize]) -> Vec<UtpCableStatus> {
let mut out = Vec::with_capacity(offsets.len());
for &o in offsets {
if o + 12 > d.len() {
break;
}
out.push(UtpCableStatus {
polarity: d[o] == 1,
pair: d[o + 1],
skew: u32_at(d, o + 4),
length: u32_at(d, o + 8),
});
}
out
}
fn mac_at(d: &[u8], idx: usize) -> Option<MacAddress> {
d.get(idx..idx + 6)
.and_then(|b| <[u8; 6]>::try_from(b).ok())
.map(MacAddress)
}
const ADDRESSES_FROM: u16 = 0x12;
const MAC_FROM: u16 = 0x21;
const LEGACY_SIZE: usize = 136;
const LEGACY_SIZE_WITH_ADDRESSES: usize = 144;
const LEGACY_SIZE_WITH_MAC: usize = 152;
fn legacy_size(protocol: u16) -> usize {
if protocol >= MAC_FROM {
LEGACY_SIZE_WITH_MAC
} else if protocol >= ADDRESSES_FROM {
LEGACY_SIZE_WITH_ADDRESSES
} else {
LEGACY_SIZE
}
}
pub(super) fn parse_legacy(d: &[u8], protocol: u16) -> Option<NetworkPortStatus> {
if d.len() < legacy_size(protocol) {
return None;
}
let mut status = NetworkPortStatus {
port: u16::from(d[0]),
name: cstr(&d[112..128]),
link_speed: UtpLinkSpeed::from_wire(d[3] & 0x7),
link_full_duplex: d[3] & (1 << 3) != 0,
ip: None,
querier: None,
mac_address: None,
errors: Some(UtpLinkErrors::from_wire(d[1])),
vct_status: Some(vct_status(d[2])),
cable_status: cable_pairs(d, &[8, 20, 32, 44]),
};
if protocol >= ADDRESSES_FROM {
status.ip = Some(ipv4_at(d, 132));
status.querier = Some(ipv4_at(d, 136));
}
if protocol >= MAC_FROM {
status.mac_address = mac_at(d, 140);
}
Some(status)
}
pub(super) fn parse_modern(d: &[u8]) -> Option<NetworkPortStatus> {
if d.len() < 39 {
return None;
}
let features = d[2];
let support_status = features & (1 << 0) != 0;
let support_cable = features & (1 << 1) != 0;
let support_igmp = features & (1 << 3) != 0;
let port_uplink = features & (1 << 6) != 0;
const NAME_OFF: usize = 4;
const MAC_OFF: usize = 21;
const IP_OFF: usize = 28;
const QUERIER_OFF: usize = 32;
let port = u16::from_le_bytes([d[0], d[1]]);
let mut status = NetworkPortStatus {
port,
name: cstr(d.get(NAME_OFF..NAME_OFF + 17).unwrap_or_default()),
link_speed: UtpLinkSpeed::from_wire(d[38] & 0x7),
link_full_duplex: d[38] & (1 << 3) != 0,
ip: None,
querier: None,
mac_address: None,
errors: None,
vct_status: None,
cable_status: Vec::new(),
};
if port_uplink && d.len() >= QUERIER_OFF {
status.mac_address = mac_at(d, MAC_OFF);
status.ip = Some(ipv4_at(d, IP_OFF));
if support_igmp && d.len() >= QUERIER_OFF + 4 {
status.querier = Some(ipv4_at(d, QUERIER_OFF));
}
}
if support_status {
status.errors = Some(UtpLinkErrors::from_wire(byte(d, 36)));
status.vct_status = Some(vct_status(byte(d, 37)));
}
if support_cable {
status.cable_status = cable_pairs(d, &[40, 52, 64, 76]);
}
Some(status)
}
pub(super) fn network_status(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let protocol = rx.frame.protocol();
let parsed = if protocol < MODERN_LAYOUT {
parse_legacy(rx.frame.payload(), protocol)
} else {
parse_modern(rx.frame.payload())
};
if let (Some(status), Some(device)) = (parsed, state.device_mut(rx.sender())) {
device.update_network_status(status, ev);
}
}