Skip to main content

ace_can/isotp/
reassembler.rs

1use crate::constants::NIBBLE_MASK;
2use crate::error::IsoTpError;
3use crate::isotp::address::IsoTpAddressingMode;
4use crate::isotp::pci::{FlowStatus, PciFrame};
5
6// region: ReassemblerConfig
7
8/// Configuration for an ISO-TP reassembler session.
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct ReassemblerConfig {
11    pub addressing_mode: IsoTpAddressingMode,
12    /// Block size to advertise in flow control frames.
13    /// 0 = send all consecutive frames without waiting.
14    pub block_size: u8,
15    /// Minimum separation time to advertise in flow control frames (ms).
16    pub st_min: u8,
17}
18
19impl ReassemblerConfig {
20    pub fn new(addressing_mode: IsoTpAddressingMode) -> Self {
21        Self {
22            addressing_mode,
23            block_size: 0,
24            st_min: 0,
25        }
26    }
27}
28
29// endregion: ReassemblerConfig
30
31// region: ReassemblerState
32
33#[derive(Debug, Clone, PartialEq, Eq)]
34enum ReassemblerState {
35    Idle,
36    Active {
37        total_len: u32,
38        received: usize,
39        next_sequence: u8,
40    },
41}
42
43// endregion: ReassemblerState
44
45// region: ReassembleResult
46
47/// Outcome of feeding PCI bytes to the reassembler.
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub enum ReassembleResult {
50    /// Frame consumed, message not yet complete.
51    InProgress,
52    /// Message is complete - `len` bytes are valid in the buffer.
53    Complete { len: usize },
54    /// First frame received - caller must send this FC on the wire.
55    ///
56    /// Contains raw PCI bytes only (3 bytes, no address prefix).
57    /// For extended/mixed addressing the caller prepends N_TA or N_AE
58    /// before putting the frame on the wire.
59    FlowControl { frame: [u8; 3], len: usize },
60    /// New first frame arrived mid-session - previous session abandoned.
61    /// Caller must send this FC on the wire.
62    SessionAborted {
63        flow_control: [u8; 3],
64        fc_len: usize,
65    },
66}
67
68// endregion: ReassembleResult
69
70// region: Reassembler
71
72/// Stateful ISO-TP reassembler.
73///
74/// Operates on raw PCI bytes - the caller strips the addressing prefix byte
75/// (N_TA for extended, N_AE for mixed) before calling `feed`. Flow control
76/// frames returned contain raw PCI bytes only - the caller prepends the
77/// address prefix byte before putting them on the wire.
78///
79/// Buffer size `N` must be large enough to hold the complete reassembled
80/// message. For classic CAN ISO-TP the maximum is 4095 bytes.
81pub struct Reassembler<const N: usize> {
82    config: ReassemblerConfig,
83    buf: [u8; N],
84    state: ReassemblerState,
85}
86
87impl<const N: usize> Reassembler<N> {
88    pub fn new(config: ReassemblerConfig) -> Self {
89        Self {
90            config,
91            buf: [0u8; N],
92            state: ReassemblerState::Idle,
93        }
94    }
95
96    /// Resets to idle, discarding any in-progress session.
97    pub fn reset(&mut self) {
98        self.state = ReassemblerState::Idle;
99    }
100
101    /// Returns the reassembled message bytes after a `Complete` result.
102    /// Returns `None` if reassembly is still in progress.
103    pub fn message(&self, len: usize) -> Option<&[u8]> {
104        match &self.state {
105            ReassemblerState::Idle => Some(&self.buf[..len]),
106            ReassemblerState::Active { .. } => None,
107        }
108    }
109
110    /// Feeds raw PCI bytes into the reassembler.
111    ///
112    /// The caller must strip the addressing prefix byte before calling this
113    /// function - byte 0 of `pci_bytes` must be the PCI byte.
114    pub fn feed(&mut self, pci_bytes: &[u8]) -> Result<ReassembleResult, IsoTpError> {
115        let pci = PciFrame::parse(pci_bytes)?;
116
117        match pci {
118            // region: Single Frame
119            PciFrame::SingleFrame { len, data } => {
120                let len = len as usize;
121                if len == 0 {
122                    return Err(IsoTpError::EmptySingleFrame);
123                }
124                if len > N {
125                    return Err(IsoTpError::PayloadTooLarge);
126                }
127                let copy_len = data.len().min(len).min(N);
128                self.buf[..copy_len].copy_from_slice(&data[..copy_len]);
129                self.state = ReassemblerState::Idle;
130                Ok(ReassembleResult::Complete { len })
131            }
132            // endregion: Single Frame
133
134            // region: First Frame
135            PciFrame::FirstFrame { total_len, data } => {
136                let total = total_len as usize;
137                if total == 0 {
138                    return Err(IsoTpError::InvalidLength);
139                }
140                if total > N {
141                    return Err(IsoTpError::PayloadTooLarge);
142                }
143
144                let was_active = matches!(self.state, ReassemblerState::Active { .. });
145
146                let copy_len = data.len().min(total).min(N);
147                self.buf[..copy_len].copy_from_slice(&data[..copy_len]);
148
149                self.state = ReassemblerState::Active {
150                    total_len,
151                    received: copy_len,
152                    next_sequence: 1,
153                };
154
155                let fc_pci = PciFrame::FlowControl {
156                    status: FlowStatus::ContinueToSend,
157                    block_size: self.config.block_size,
158                    st_min: self.config.st_min,
159                };
160                let mut fc_buf = [0u8; 3];
161                fc_pci.encode_header(&mut fc_buf)?;
162
163                if was_active {
164                    Ok(ReassembleResult::SessionAborted {
165                        flow_control: fc_buf,
166                        fc_len: 3,
167                    })
168                } else {
169                    Ok(ReassembleResult::FlowControl {
170                        frame: fc_buf,
171                        len: 3,
172                    })
173                }
174            }
175            // endregion: First Frame
176
177            // region: Consecutive Frame
178            PciFrame::ConsecutiveFrame {
179                sequence_number,
180                data,
181            } => {
182                let (total_len, received, next_sequence) = match &self.state {
183                    ReassemblerState::Active {
184                        total_len,
185                        received,
186                        next_sequence,
187                    } => (*total_len, *received, *next_sequence),
188                    ReassemblerState::Idle => return Err(IsoTpError::UnexpectedConsecutiveFrame),
189                };
190
191                if sequence_number != next_sequence {
192                    self.reset();
193                    return Err(IsoTpError::SequenceError {
194                        expected: next_sequence,
195                        actual: sequence_number,
196                    });
197                }
198
199                let total = total_len as usize;
200                let remaining = total - received;
201                let copy_len = data.len().min(remaining);
202
203                self.buf[received..received + copy_len].copy_from_slice(&data[..copy_len]);
204
205                let new_received = received + copy_len;
206                let new_sequence = (next_sequence + 1) & NIBBLE_MASK;
207
208                if new_received >= total {
209                    self.state = ReassemblerState::Idle;
210                    Ok(ReassembleResult::Complete { len: total })
211                } else {
212                    self.state = ReassemblerState::Active {
213                        total_len,
214                        received: new_received,
215                        next_sequence: new_sequence,
216                    };
217                    Ok(ReassembleResult::InProgress)
218                }
219            }
220            // endregion: Consecutive Frame
221
222            // FC received by reassembler is unexpected
223            PciFrame::FlowControl { .. } => Err(IsoTpError::UnknownFrameType(0x3)),
224        }
225    }
226}
227
228// endregion: Reassembler