use crate::constants::NIBBLE_MASK;
use crate::error::IsoTpError;
use crate::isotp::address::IsoTpAddressingMode;
use crate::isotp::pci::{FlowStatus, PciFrame};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReassemblerConfig {
pub addressing_mode: IsoTpAddressingMode,
pub block_size: u8,
pub st_min: u8,
}
impl ReassemblerConfig {
pub fn new(addressing_mode: IsoTpAddressingMode) -> Self {
Self {
addressing_mode,
block_size: 0,
st_min: 0,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum ReassemblerState {
Idle,
Active {
total_len: u32,
received: usize,
next_sequence: u8,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ReassembleResult {
InProgress,
Complete { len: usize },
FlowControl { frame: [u8; 3], len: usize },
SessionAborted {
flow_control: [u8; 3],
fc_len: usize,
},
}
pub struct Reassembler<const N: usize> {
config: ReassemblerConfig,
buf: [u8; N],
state: ReassemblerState,
}
impl<const N: usize> Reassembler<N> {
pub fn new(config: ReassemblerConfig) -> Self {
Self {
config,
buf: [0u8; N],
state: ReassemblerState::Idle,
}
}
pub fn reset(&mut self) {
self.state = ReassemblerState::Idle;
}
pub fn message(&self, len: usize) -> Option<&[u8]> {
match &self.state {
ReassemblerState::Idle => Some(&self.buf[..len]),
ReassemblerState::Active { .. } => None,
}
}
pub fn feed(&mut self, pci_bytes: &[u8]) -> Result<ReassembleResult, IsoTpError> {
let pci = PciFrame::parse(pci_bytes)?;
match pci {
PciFrame::SingleFrame { len, data } => {
let len = len as usize;
if len == 0 {
return Err(IsoTpError::EmptySingleFrame);
}
if len > N {
return Err(IsoTpError::PayloadTooLarge);
}
let copy_len = data.len().min(len).min(N);
self.buf[..copy_len].copy_from_slice(&data[..copy_len]);
self.state = ReassemblerState::Idle;
Ok(ReassembleResult::Complete { len })
}
PciFrame::FirstFrame { total_len, data } => {
let total = total_len as usize;
if total == 0 {
return Err(IsoTpError::InvalidLength);
}
if total > N {
return Err(IsoTpError::PayloadTooLarge);
}
let was_active = matches!(self.state, ReassemblerState::Active { .. });
let copy_len = data.len().min(total).min(N);
self.buf[..copy_len].copy_from_slice(&data[..copy_len]);
self.state = ReassemblerState::Active {
total_len,
received: copy_len,
next_sequence: 1,
};
let fc_pci = PciFrame::FlowControl {
status: FlowStatus::ContinueToSend,
block_size: self.config.block_size,
st_min: self.config.st_min,
};
let mut fc_buf = [0u8; 3];
fc_pci.encode_header(&mut fc_buf)?;
if was_active {
Ok(ReassembleResult::SessionAborted {
flow_control: fc_buf,
fc_len: 3,
})
} else {
Ok(ReassembleResult::FlowControl {
frame: fc_buf,
len: 3,
})
}
}
PciFrame::ConsecutiveFrame {
sequence_number,
data,
} => {
let (total_len, received, next_sequence) = match &self.state {
ReassemblerState::Active {
total_len,
received,
next_sequence,
} => (*total_len, *received, *next_sequence),
ReassemblerState::Idle => return Err(IsoTpError::UnexpectedConsecutiveFrame),
};
if sequence_number != next_sequence {
self.reset();
return Err(IsoTpError::SequenceError {
expected: next_sequence,
actual: sequence_number,
});
}
let total = total_len as usize;
let remaining = total - received;
let copy_len = data.len().min(remaining);
self.buf[received..received + copy_len].copy_from_slice(&data[..copy_len]);
let new_received = received + copy_len;
let new_sequence = (next_sequence + 1) & NIBBLE_MASK;
if new_received >= total {
self.state = ReassemblerState::Idle;
Ok(ReassembleResult::Complete { len: total })
} else {
self.state = ReassemblerState::Active {
total_len,
received: new_received,
next_sequence: new_sequence,
};
Ok(ReassembleResult::InProgress)
}
}
PciFrame::FlowControl { .. } => Err(IsoTpError::UnknownFrameType(0x3)),
}
}
}