#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum RouteHop {
Backplane { port: u8, slot: u8 },
Ethernet { port: u8, address: String },
}
#[derive(Debug, Clone)]
pub struct RoutePath {
hops: Vec<RouteHop>,
}
impl RoutePath {
const DEFAULT_BACKPLANE_PORT: u8 = 1;
const DEFAULT_ETHERNET_PORT: u8 = 2;
#[must_use]
pub fn new() -> Self {
Self { hops: Vec::new() }
}
#[must_use]
pub fn add_slot(mut self, slot: u8) -> Self {
self.hops.push(RouteHop::Backplane {
port: Self::DEFAULT_BACKPLANE_PORT,
slot,
});
self
}
#[must_use]
pub fn add_port(mut self, port: u8) -> Self {
let port_index = self
.hops
.iter()
.filter(|hop| matches!(hop, RouteHop::Ethernet { .. }))
.count()
.saturating_sub(1);
self.update_ethernet_hop_port(port_index, port);
self
}
#[must_use]
pub fn add_address(mut self, address: String) -> Self {
let port = self
.pending_ethernet_port()
.unwrap_or(Self::DEFAULT_ETHERNET_PORT);
self.hops.push(RouteHop::Ethernet { port, address });
self
}
#[must_use]
pub fn add_backplane(mut self, port: u8, slot: u8) -> Self {
self.hops.push(RouteHop::Backplane { port, slot });
self
}
#[must_use]
pub fn add_ethernet(self, address: impl Into<String>) -> Self {
self.add_ethernet_with_port(Self::DEFAULT_ETHERNET_PORT, address)
}
#[must_use]
pub fn add_ethernet_with_port(mut self, port: u8, address: impl Into<String>) -> Self {
let address = address.into();
self.hops.push(RouteHop::Ethernet { port, address });
self
}
#[must_use]
pub fn hops(&self) -> &[RouteHop] {
&self.hops
}
#[must_use]
pub fn slots(&self) -> Vec<u8> {
self.hops
.iter()
.filter_map(|hop| match hop {
RouteHop::Backplane { slot, .. } => Some(*slot),
RouteHop::Ethernet { .. } => None,
})
.collect()
}
#[must_use]
pub fn ports(&self) -> Vec<u8> {
self.hops
.iter()
.filter_map(|hop| match hop {
RouteHop::Backplane { .. } => None,
RouteHop::Ethernet { port, .. } => Some(*port),
})
.collect()
}
#[must_use]
pub fn addresses(&self) -> Vec<String> {
self.hops
.iter()
.filter_map(|hop| match hop {
RouteHop::Backplane { .. } => None,
RouteHop::Ethernet { address, .. } => Some(address.clone()),
})
.collect()
}
#[must_use]
pub fn to_cip_bytes(&self) -> Vec<u8> {
let mut path = Vec::new();
for hop in &self.hops {
Self::append_hop(&mut path, hop);
}
path
}
fn append_hop(path: &mut Vec<u8>, hop: &RouteHop) {
match hop {
RouteHop::Backplane { port, slot } => {
path.push(*port);
path.push(*slot);
}
RouteHop::Ethernet { port, address } => {
Self::append_extended_link_address_segment(path, *port, address);
}
}
}
fn append_extended_link_address_segment(path: &mut Vec<u8>, port: u8, address: &str) {
path.push(0x10 | (port & 0x0F));
path.push(address.len().saturating_add(1) as u8);
path.extend_from_slice(address.as_bytes());
path.push(0x00);
if !(address.len() + 1).is_multiple_of(2) {
path.push(0x00);
}
}
fn update_ethernet_hop_port(&mut self, port_index: usize, port: u8) -> bool {
if let Some(RouteHop::Ethernet { port: hop_port, .. }) = self
.hops
.iter_mut()
.filter(|hop| matches!(hop, RouteHop::Ethernet { .. }))
.nth(port_index)
{
*hop_port = port;
true
} else {
false
}
}
fn pending_ethernet_port(&self) -> Option<u8> {
self.hops
.iter()
.filter_map(|hop| match hop {
RouteHop::Ethernet { port, .. } => Some(*port),
RouteHop::Backplane { .. } => None,
})
.next_back()
}
}
impl Default for RoutePath {
fn default() -> Self {
Self::new()
}
}