use crate::ast::*;
use crate::builders::WireContext;
use crate::error::CompilerError;
use crate::packet::build_ethernet_and_l2;
use alloc::vec::Vec;
fn encode_bridge_id(id_str: Option<&str>, default_mac: [u8; 6]) -> [u8; 8] {
let mut out = [0u8; 8];
out[0..2].copy_from_slice(&0x8000_u16.to_be_bytes());
out[2..8].copy_from_slice(&default_mac);
let Some(raw) = id_str else {
return out;
};
let s = raw.trim();
if s.is_empty() {
return out;
}
if let Some((prio_part, mac_part)) = s.split_once('/').or_else(|| {
s.split_once(':')
.filter(|(_, rest)| parse_mac_str(rest).is_some())
}) {
let prio = if let Some(hex) = prio_part.strip_prefix("0x").or_else(|| prio_part.strip_prefix("0X")) {
u16::from_str_radix(hex, 16).unwrap_or(0x8000)
} else {
prio_part.parse::<u16>().unwrap_or(0x8000)
};
out[0..2].copy_from_slice(&prio.to_be_bytes());
if let Some(mac) = parse_mac_str(mac_part) {
out[2..8].copy_from_slice(&mac);
}
return out;
}
if let Some(mac) = parse_mac_str(s) {
out[2..8].copy_from_slice(&mac);
return out;
}
let hex_candidate = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")).unwrap_or(s);
if hex_candidate.len() <= 16 && hex_candidate.chars().all(|c| c.is_ascii_hexdigit()) {
if let Ok(val) = u64::from_str_radix(hex_candidate, 16) {
return val.to_be_bytes();
}
}
let bytes = s.as_bytes();
let len = bytes.len().min(8);
out[..len].copy_from_slice(&bytes[..len]);
out
}
fn parse_mac_str(s: &str) -> Option<[u8; 6]> {
let trimmed = s.trim();
if trimmed.contains(':') || trimmed.contains('-') {
let mut mac = [0u8; 6];
let mut count = 0usize;
for part in trimmed.split([':', '-']) {
if count >= 6 {
return None;
}
mac[count] = u8::from_str_radix(part.trim(), 16).ok()?;
count += 1;
}
(count == 6).then_some(mac)
} else if trimmed.contains('.') {
let mut mac = [0u8; 6];
let mut count = 0usize;
for part in trimmed.split('.') {
let p = part.trim();
if count >= 3 || p.len() != 4 {
return None;
}
let word = u16::from_str_radix(p, 16).ok()?;
let [hi, lo] = word.to_be_bytes();
mac[count * 2] = hi;
mac[count * 2 + 1] = lo;
count += 1;
}
(count == 3).then_some(mac)
} else {
None
}
}
pub fn build_stp_frame(
ctx: &WireContext,
spec: &StpSpec,
payload: &[u8],
) -> Result<Vec<u8>, CompilerError> {
let mut bpdu = Vec::with_capacity(36 + payload.len());
bpdu.extend_from_slice(&0_u16.to_be_bytes());
bpdu.push(if spec.is_rstp { 2 } else { 0 });
bpdu.push(if spec.is_rstp { 2 } else { 0 });
bpdu.push(spec.flags.unwrap_or(0));
let root_id = encode_bridge_id(spec.root_id, ctx.src_mac);
bpdu.extend_from_slice(&root_id);
bpdu.extend_from_slice(&spec.cost.unwrap_or(0).to_be_bytes());
let bridge_id = encode_bridge_id(spec.bridge_id, ctx.src_mac);
bpdu.extend_from_slice(&bridge_id);
bpdu.extend_from_slice(&spec.port_id.unwrap_or(0x8001).to_be_bytes());
bpdu.extend_from_slice(&0_u16.to_be_bytes());
let max_age = spec.max_age.unwrap_or(20) << 8;
bpdu.extend_from_slice(&max_age.to_be_bytes());
let hello = spec.hello_time.unwrap_or(2) << 8;
bpdu.extend_from_slice(&hello.to_be_bytes());
let fwd_delay = spec.forward_delay.unwrap_or(15) << 8;
bpdu.extend_from_slice(&fwd_delay.to_be_bytes());
if spec.is_rstp {
bpdu.push(0x00);
}
bpdu.extend_from_slice(payload);
let mut llc_packet = Vec::with_capacity(3 + bpdu.len());
llc_packet.push(0x42);
llc_packet.push(0x42);
llc_packet.push(0x03);
llc_packet.extend_from_slice(&bpdu);
let length_field = llc_packet.len() as u16;
let dst_mac = ctx.l2.dst_mac.unwrap_or([0x01, 0x80, 0xc2, 0x00, 0x00, 0x00]);
Ok(build_ethernet_and_l2(ctx.src_mac, dst_mac, ctx.l2, length_field, &llc_packet))
}