mod frame_size;
mod raw_eth_frame;
pub(crate) use frame_size::FrameSize;
pub(crate) use raw_eth_frame::RawEthFrame;
use alloc::string::String;
use alloc::vec::Vec;
use super::frame::{ETH_HEADER_SIZE, EthernetFlags, EthernetFrame, MacAddress};
use crate::bus_logging::{BusLoggerConfig, init_bus_channel_group};
pub struct RawEthernetLogger<W: crate::writer::MdfWrite> {
writer: crate::MdfWriter<W>,
source_name: String,
buffers: alloc::collections::BTreeMap<FrameSize, Vec<RawEthFrame>>,
channel_groups: alloc::collections::BTreeMap<FrameSize, String>,
initialized: bool,
}
impl RawEthernetLogger<crate::writer::VecWriter> {
pub fn new() -> crate::Result<Self> {
Self::with_source_name("ETH")
}
pub fn with_source_name(source_name: &str) -> crate::Result<Self> {
let writer = crate::MdfWriter::from_writer(crate::writer::VecWriter::new());
Ok(Self {
writer,
source_name: String::from(source_name),
buffers: alloc::collections::BTreeMap::new(),
channel_groups: alloc::collections::BTreeMap::new(),
initialized: false,
})
}
pub fn with_interface_name(interface_name: &str) -> crate::Result<Self> {
Self::with_source_name(interface_name)
}
pub fn with_capacity(capacity: usize) -> crate::Result<Self> {
let writer =
crate::MdfWriter::from_writer(crate::writer::VecWriter::with_capacity(capacity));
Ok(Self {
writer,
source_name: String::from("ETH"),
buffers: alloc::collections::BTreeMap::new(),
channel_groups: alloc::collections::BTreeMap::new(),
initialized: false,
})
}
pub fn finalize(mut self) -> crate::Result<Vec<u8>> {
self.flush_and_finalize()?;
Ok(self.writer.into_inner().into_inner())
}
}
#[cfg(feature = "std")]
impl RawEthernetLogger<crate::writer::FileWriter> {
pub fn new_file(path: &str) -> crate::Result<Self> {
Self::new_file_with_source_name(path, "ETH")
}
pub fn new_file_with_source_name(path: &str, source_name: &str) -> crate::Result<Self> {
let writer = crate::MdfWriter::new(path)?;
Ok(Self {
writer,
source_name: String::from(source_name),
buffers: alloc::collections::BTreeMap::new(),
channel_groups: alloc::collections::BTreeMap::new(),
initialized: false,
})
}
pub fn new_file_with_interface_name(path: &str, interface_name: &str) -> crate::Result<Self> {
Self::new_file_with_source_name(path, interface_name)
}
pub fn finalize_file(mut self) -> crate::Result<()> {
self.flush_and_finalize()
}
}
impl<W: crate::writer::MdfWrite> RawEthernetLogger<W> {
pub fn set_source_name(&mut self, name: &str) {
self.source_name = String::from(name);
}
pub fn set_interface_name(&mut self, name: &str) {
self.set_source_name(name);
}
pub fn log(&mut self, timestamp_us: u64, frame_bytes: &[u8]) -> bool {
self.log_with_flags(timestamp_us, frame_bytes, EthernetFlags::rx())
}
pub fn log_with_flags(
&mut self,
timestamp_us: u64,
frame_bytes: &[u8],
flags: EthernetFlags,
) -> bool {
if frame_bytes.len() < ETH_HEADER_SIZE {
return false;
}
let frame = RawEthFrame::new(timestamp_us, frame_bytes.to_vec(), flags);
self.buffers
.entry(frame.frame_size())
.or_default()
.push(frame);
true
}
pub fn log_tx(&mut self, timestamp_us: u64, frame_bytes: &[u8]) -> bool {
self.log_with_flags(timestamp_us, frame_bytes, EthernetFlags::tx())
}
pub fn log_rx(&mut self, timestamp_us: u64, frame_bytes: &[u8]) -> bool {
self.log_with_flags(timestamp_us, frame_bytes, EthernetFlags::rx())
}
pub fn log_frame(&mut self, timestamp_us: u64, frame: EthernetFrame) -> bool {
let bytes = frame.to_bytes();
self.log_with_flags(timestamp_us, &bytes, frame.flags)
}
pub fn log_frame_ref(&mut self, timestamp_us: u64, frame: &EthernetFrame) -> bool {
let bytes = frame.to_bytes();
self.log_with_flags(timestamp_us, &bytes, frame.flags)
}
pub fn log_components(
&mut self,
timestamp_us: u64,
dst_mac: MacAddress,
src_mac: MacAddress,
ethertype: u16,
payload: &[u8],
) -> bool {
self.log_frame(
timestamp_us,
EthernetFrame::new(dst_mac, src_mac, ethertype, payload.to_vec()),
)
}
pub fn flush(&mut self) -> crate::Result<()> {
if !self.initialized {
self.initialize_mdf()?;
}
for frame_size in FrameSize::ALL {
if self.buffers.contains_key(&frame_size) {
self.write_frames(frame_size)?;
}
}
for buffer in self.buffers.values_mut() {
buffer.clear();
}
Ok(())
}
fn initialize_mdf(&mut self) -> crate::Result<()> {
self.writer.init_mdf_file()?;
for &frame_size in self.buffers.keys() {
let max_frame_size = frame_size.max_frame_size();
let frame_channel_size = 3 + max_frame_size;
let config = BusLoggerConfig {
source_name: self.source_name.clone(),
group_name: frame_size.group_name(&self.source_name),
data_channel_name: String::from("ETH_Frame"),
data_channel_bits: (frame_channel_size * 8) as u32,
source_block: crate::blocks::SourceBlock::ethernet(),
};
let (cg, _data_ch) = init_bus_channel_group(&mut self.writer, &config)?;
self.channel_groups.insert(frame_size, cg);
}
self.initialized = true;
Ok(())
}
fn write_frames(&mut self, frame_size: FrameSize) -> crate::Result<()> {
use crate::DecodedValue;
let cg = match self.channel_groups.get(&frame_size) {
Some(cg) => cg.clone(),
None => return Ok(()),
};
let frames = match self.buffers.get(&frame_size) {
Some(f) if !f.is_empty() => f,
_ => return Ok(()),
};
let max_size = frame_size.max_frame_size();
self.writer.start_data_block_for_cg(&cg, 0)?;
for frame in frames {
let values = [
DecodedValue::Float(frame.timestamp_s),
DecodedValue::ByteArray(frame.to_frame_bytes(max_size)),
];
self.writer.write_record(&cg, &values)?;
}
self.writer.finish_data_block(&cg)?;
Ok(())
}
fn flush_and_finalize(&mut self) -> crate::Result<()> {
self.flush()?;
self.writer.finalize()
}
pub fn total_frame_count(&self) -> usize {
self.buffers.values().map(|b| b.len()).sum()
}
pub fn standard_frame_count(&self) -> usize {
self.buffers
.get(&FrameSize::Standard)
.map(|b| b.len())
.unwrap_or(0)
}
pub fn jumbo_frame_count(&self) -> usize {
self.buffers
.get(&FrameSize::Jumbo)
.map(|b| b.len())
.unwrap_or(0)
}
pub fn has_jumbo_frames(&self) -> bool {
self.jumbo_frame_count() > 0
}
pub fn tx_frame_count(&self) -> usize {
self.buffers
.values()
.flat_map(|frames| frames.iter())
.filter(|f| f.flags.is_tx())
.count()
}
pub fn rx_frame_count(&self) -> usize {
self.buffers
.values()
.flat_map(|frames| frames.iter())
.filter(|f| f.flags.is_rx())
.count()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ethernet::frame::MAX_ETHERNET_FRAME;
use crate::ethernet::frame::ethertype;
fn create_test_frame(payload_len: usize) -> Vec<u8> {
let mut frame = Vec::with_capacity(ETH_HEADER_SIZE + payload_len);
frame.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]);
frame.extend_from_slice(&[0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
frame.extend_from_slice(&[0x08, 0x00]);
frame.extend(core::iter::repeat_n(0xAA, payload_len));
frame
}
#[test]
fn test_raw_ethernet_logger_basic() {
let mut logger = RawEthernetLogger::new().unwrap();
let frame1 = create_test_frame(100);
let frame2 = create_test_frame(200);
let frame3 = create_test_frame(50);
assert!(logger.log(1000, &frame1));
assert!(logger.log(2000, &frame2));
assert!(logger.log(1500, &frame3));
assert_eq!(logger.total_frame_count(), 3);
assert_eq!(logger.standard_frame_count(), 3);
assert_eq!(logger.jumbo_frame_count(), 0);
let mdf_bytes = logger.finalize().unwrap();
assert!(!mdf_bytes.is_empty());
assert_eq!(&mdf_bytes[0..3], b"MDF");
}
#[test]
fn test_raw_ethernet_logger_tx_rx() {
let mut logger = RawEthernetLogger::new().unwrap();
let frame = create_test_frame(100);
assert!(logger.log_tx(1000, &frame));
assert!(logger.log_rx(2000, &frame));
assert!(logger.log_tx(3000, &frame));
assert_eq!(logger.total_frame_count(), 3);
assert_eq!(logger.tx_frame_count(), 2);
assert_eq!(logger.rx_frame_count(), 1);
let mdf_bytes = logger.finalize().unwrap();
assert!(!mdf_bytes.is_empty());
}
#[test]
fn test_raw_ethernet_logger_empty() {
let logger = RawEthernetLogger::new().unwrap();
assert_eq!(logger.total_frame_count(), 0);
let mdf_bytes = logger.finalize().unwrap();
assert!(!mdf_bytes.is_empty());
}
#[test]
fn test_raw_ethernet_logger_short_frame() {
let mut logger = RawEthernetLogger::new().unwrap();
let short_frame = [0u8; 10];
assert!(!logger.log(1000, &short_frame));
assert_eq!(logger.total_frame_count(), 0);
}
#[test]
fn test_raw_ethernet_logger_frame_struct() {
let mut logger = RawEthernetLogger::new().unwrap();
let frame = EthernetFrame::new(
MacAddress::broadcast(),
MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]),
ethertype::IPV4,
vec![0x45, 0x00, 0x00, 0x28, 0x00, 0x00],
);
assert!(logger.log_frame(1000, frame));
assert_eq!(logger.total_frame_count(), 1);
let mdf_bytes = logger.finalize().unwrap();
assert!(!mdf_bytes.is_empty());
}
#[test]
fn test_raw_ethernet_logger_components() {
let mut logger = RawEthernetLogger::new().unwrap();
assert!(logger.log_components(
1000,
MacAddress::broadcast(),
MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]),
ethertype::ARP,
&[0x00, 0x01, 0x08, 0x00, 0x06, 0x04],
));
assert_eq!(logger.total_frame_count(), 1);
let mdf_bytes = logger.finalize().unwrap();
assert!(!mdf_bytes.is_empty());
}
#[test]
fn test_source_name() {
let logger = RawEthernetLogger::with_source_name("eth0").unwrap();
assert_eq!(logger.source_name, "eth0");
}
#[test]
fn test_interface_name_alias() {
let logger = RawEthernetLogger::with_interface_name("eth0").unwrap();
assert_eq!(logger.source_name, "eth0");
}
#[test]
fn test_frame_bytes_format() {
let frame_data = create_test_frame(10);
let raw_frame = RawEthFrame::new(1_000_000, frame_data.clone(), EthernetFlags::tx());
let bytes = raw_frame.to_frame_bytes(MAX_ETHERNET_FRAME);
assert_eq!(bytes[0], EthernetFlags::TX);
let len = u16::from_le_bytes([bytes[1], bytes[2]]);
assert_eq!(len, frame_data.len() as u16);
assert_eq!(&bytes[3..3 + frame_data.len()], &frame_data[..]);
}
}