use crate::error::{Mf4Error, Result};
use crate::model::{Channel, ChannelGroup};
use crate::Mf4File;
const ETH_PREFIX: &str = "ETH_Frame";
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct EthFrame<'a> {
pub timestamp: f64,
pub source: Option<[u8; 6]>,
pub destination: Option<[u8; 6]>,
pub ether_type: u16,
pub bus_channel: u8,
pub data: &'a [u8],
}
#[derive(Debug, Clone)]
pub struct EthFrames {
pub(crate) timestamps: Vec<f64>,
pub(crate) sources: Option<Vec<[u8; 6]>>,
pub(crate) destinations: Option<Vec<[u8; 6]>>,
pub(crate) ether_types: Vec<u16>,
pub(crate) bus_channels: Vec<u8>,
pub(crate) payloads: Vec<u8>,
pub(crate) payload_starts: Vec<usize>,
}
impl EthFrames {
pub fn len(&self) -> usize {
self.ether_types.len()
}
pub fn is_empty(&self) -> bool {
self.ether_types.is_empty()
}
pub fn get(&self, index: usize) -> Option<EthFrame<'_>> {
let ether_type = *self.ether_types.get(index)?;
let from = *self.payload_starts.get(index)?;
let to = *self.payload_starts.get(index + 1)?;
Some(EthFrame {
timestamp: *self.timestamps.get(index)?,
source: match &self.sources {
Some(sources) => sources.get(index).copied(),
None => None,
},
destination: match &self.destinations {
Some(destinations) => destinations.get(index).copied(),
None => None,
},
ether_type,
bus_channel: self.bus_channels.get(index).copied().unwrap_or(0),
data: self.payloads.get(from..to)?,
})
}
pub fn iter(&self) -> impl Iterator<Item = EthFrame<'_>> + '_ {
(0..self.len()).filter_map(|i| self.get(i))
}
}
fn field<'a>(group: &'a ChannelGroup, suffix: &str) -> Option<&'a Channel> {
let qualified = format!("{ETH_PREFIX}.{suffix}");
group.find_channel(&qualified)
}
fn require<'a>(group: &'a ChannelGroup, suffix: &str) -> Result<&'a Channel> {
field(group, suffix).ok_or_else(|| Mf4Error::ChannelNotFound {
name: format!("{ETH_PREFIX}.{suffix}"),
})
}
fn scalars(file: &Mf4File, channel: &Channel) -> Result<Vec<f64>> {
Ok(file.signal(channel)?.values()?.to_f64())
}
fn read_macs(
file: &Mf4File,
channel: &Channel,
suffix: &str,
group_name: &str,
) -> Result<Vec<[u8; 6]>> {
let values = file.signal(channel)?.values()?;
let mut macs = Vec::with_capacity(values.len());
for i in 0..values.len() {
let bytes = values.bytes_at(i).ok_or_else(|| {
Mf4Error::parse_error(format!(
"MAC address channel '{ETH_PREFIX}.{suffix}' of bus group '{group_name}' did not decode to bytes",
))
})?;
if bytes.len() < 6 {
return Err(Mf4Error::parse_error(format!(
"MAC address channel '{ETH_PREFIX}.{suffix}' of bus group '{group_name}' sample {i} has {} bytes, expected 6",
bytes.len()
)));
}
let mut mac = [0u8; 6];
mac.copy_from_slice(&bytes[..6]);
macs.push(mac);
}
Ok(macs)
}
pub(crate) fn read_eth_frames(file: &Mf4File, group: &ChannelGroup) -> Result<EthFrames> {
let ether_type_channel = require(group, "EtherType")?;
let length_channel = require(group, "DataLength")?;
let payload_channel = require(group, "DataBytes")?;
let master = group
.master_channel()
.ok_or_else(|| Mf4Error::ChannelNotFound {
name: format!("master channel of bus group '{}'", group.acquisition_name),
})?;
let ether_types: Vec<u16> = scalars(file, ether_type_channel)?
.into_iter()
.map(|et| et as u16)
.collect();
let timestamps = scalars(file, master)?;
let lengths: Vec<usize> = scalars(file, length_channel)?
.into_iter()
.map(|len| len as usize)
.collect();
let payload_values = file.signal(payload_channel)?.values()?;
if timestamps.len() != ether_types.len()
|| lengths.len() != ether_types.len()
|| payload_values.len() != ether_types.len()
{
return Err(Mf4Error::parse_error(format!(
"bus group '{}' has {} EtherTypes, {} timestamps, {} lengths and {} payloads; \
frame fields must agree sample for sample",
group.acquisition_name,
ether_types.len(),
timestamps.len(),
lengths.len(),
payload_values.len()
)));
}
let sources = match field(group, "Source") {
Some(channel) => {
let macs = read_macs(file, channel, "Source", &group.acquisition_name)?;
if macs.len() != ether_types.len() {
return Err(Mf4Error::parse_error(format!(
"bus group '{}' has {} source MACs and {} EtherTypes; \
frame fields must agree sample for sample",
group.acquisition_name,
macs.len(),
ether_types.len()
)));
}
Some(macs)
}
None => None,
};
let destinations = match field(group, "Destination") {
Some(channel) => {
let macs = read_macs(file, channel, "Destination", &group.acquisition_name)?;
if macs.len() != ether_types.len() {
return Err(Mf4Error::parse_error(format!(
"bus group '{}' has {} destination MACs and {} EtherTypes; \
frame fields must agree sample for sample",
group.acquisition_name,
macs.len(),
ether_types.len()
)));
}
Some(macs)
}
None => None,
};
let bus_channels = match field(group, "BusChannel") {
Some(channel) => {
let buses: Vec<u8> = scalars(file, channel)?
.into_iter()
.map(|bus| bus as u8)
.collect();
if buses.len() != ether_types.len() {
return Err(Mf4Error::parse_error(format!(
"bus group '{}' has {} bus channels and {} EtherTypes; \
frame fields must agree sample for sample",
group.acquisition_name,
buses.len(),
ether_types.len()
)));
}
buses
}
None => Vec::new(),
};
let mut payloads = Vec::new();
let mut payload_starts = Vec::with_capacity(ether_types.len() + 1);
for (index, &len) in lengths.iter().enumerate() {
let sample = payload_values.bytes_at(index).ok_or_else(|| {
Mf4Error::parse_error(format!(
"payload channel '{ETH_PREFIX}.DataBytes' of bus group '{}' did not decode to bytes",
group.acquisition_name
))
})?;
payload_starts.push(payloads.len());
payloads.extend_from_slice(&sample[..len.min(sample.len())]);
}
payload_starts.push(payloads.len());
Ok(EthFrames {
timestamps,
sources,
destinations,
ether_types,
bus_channels,
payloads,
payload_starts,
})
}
pub(crate) fn is_eth_frame_group(group: &ChannelGroup) -> bool {
field(group, "EtherType").is_some() && field(group, "DataBytes").is_some()
}