Skip to main content

ace_can/isotp/
segmenter.rs

1use crate::error::IsoTpError;
2use crate::isotp::address::IsoTpAddressingMode;
3use crate::isotp::pci::{FlowStatus, PciFrame};
4
5// region: SegmenterConfig
6
7/// Configuration for an ISO-TP segmenter session.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct SegmenterConfig {
10    pub addressing_mode: IsoTpAddressingMode,
11
12    /// Maximum data bytes per CAN frame - 8 for classic CAN, up to 64 for CAN FD.
13    pub max_frame_data_len: usize,
14}
15
16impl SegmenterConfig {
17    pub fn classic(addressing_mode: IsoTpAddressingMode) -> Self {
18        Self {
19            addressing_mode,
20            max_frame_data_len: 8,
21        }
22    }
23
24    pub fn fd(addressing_mode: IsoTpAddressingMode) -> Self {
25        Self {
26            addressing_mode,
27            max_frame_data_len: 64,
28        }
29    }
30}
31
32// endregion: SegmenterConfig
33
34// region: SegmenterState
35
36#[derive(Debug, Clone, PartialEq, Eq)]
37enum SegmenterState {
38    Idle,
39
40    WaitingForFlowControl {
41        next_sequence: u8,
42        sent: usize,
43    },
44
45    Sending {
46        next_sequence: u8,
47        sent: usize,
48        /// 0 = send all remaining without waiting.
49        block_remaining: u8,
50        st_min: u8,
51    },
52}
53
54// endregion: SegmenterState
55
56// region: SegmentResult
57
58/// Outcome of calling [`Segmenter::next_frame`].
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub enum SegmentResult {
61    /// Raw PCI bytes written into `out_buf` - `len` bytes are valid.
62    ///
63    /// For extended/mixed addressing the caller prepends N_TA or N_AE
64    /// before putting the frame on the wire.
65    Frame { len: usize },
66
67    /// All frames sent - session complete.
68    Complete,
69
70    /// Waiting for flow control - call [`Segmenter::handle_flow_control`]
71    /// before calling [`Segmenter::next_frame`] again.
72    WaitForFlowControl,
73}
74
75// endregion: SegmentResult
76
77// region: Segmenter
78
79/// Stateful ISO-TP segmenter.
80///
81/// Produces raw PCI bytes - the caller prepends the addressing prefix byte
82/// (N_TA for extended, N_AE for mixed) before putting each frame on the wire.
83///
84/// `handle_flow_control` similarly expects raw PCI bytes - the caller strips
85/// the addressing prefix byte before passing to this function.
86///
87/// `st_min` from received FC frames is stored but not enforced - the caller
88/// is responsible for honouring the inter-frame delay on bare metal targets.
89pub struct Segmenter<const N: usize = 4096> {
90    config: SegmenterConfig,
91    payload: heapless::Vec<u8, N>,
92    state: SegmenterState,
93}
94
95impl<const N: usize> Segmenter<N> {
96    pub fn new(config: SegmenterConfig) -> Self {
97        Self {
98            config,
99            payload: heapless::Vec::new(),
100            state: SegmenterState::Idle,
101        }
102    }
103
104    /// Begins a new segmentation session with the given payload.
105    pub fn start(&mut self, payload: &[u8]) -> Result<(), IsoTpError> {
106        if payload.is_empty() {
107            return Err(IsoTpError::InvalidLength);
108        }
109
110        if payload.len() > N {
111            return Err(IsoTpError::PayloadTooLarge);
112        }
113
114        if payload.len() > u32::MAX as usize {
115            return Err(IsoTpError::PayloadTooLarge);
116        }
117
118        self.payload.clear();
119        self.payload
120            .extend_from_slice(payload)
121            .map_err(|_| IsoTpError::PayloadTooLarge)?;
122
123        self.state = SegmenterState::Sending {
124            next_sequence: 1,
125            sent: 0,
126            block_remaining: 0,
127            st_min: 0,
128        };
129
130        Ok(())
131    }
132
133    /// Resets to idle, discarding any active session.
134    pub fn reset(&mut self) {
135        self.payload.clear();
136        self.state = SegmenterState::Idle;
137    }
138
139    /// Handles an incoming flow control frame from the receiver.
140    ///
141    /// `pci_bytes` must be raw PCI bytes - the caller strips the addressing
142    /// prefix byte before passing to this function.
143    pub fn handle_flow_control(&mut self, pci_bytes: &[u8]) -> Result<(), IsoTpError> {
144        let pci = PciFrame::parse(pci_bytes)?;
145
146        let (next_sequence, sent) = match &self.state {
147            SegmenterState::WaitingForFlowControl {
148                next_sequence,
149                sent,
150            } => (*next_sequence, *sent),
151            _ => return Err(IsoTpError::UnknownFrameType(0x3)),
152        };
153
154        match pci {
155            PciFrame::FlowControl {
156                status,
157                block_size,
158                st_min,
159            } => match status {
160                FlowStatus::ContinueToSend => {
161                    self.state = SegmenterState::Sending {
162                        next_sequence,
163                        sent,
164                        block_remaining: block_size,
165                        st_min,
166                    };
167                    Ok(())
168                }
169                FlowStatus::Wait => Ok(()),
170                FlowStatus::Overflow => {
171                    self.reset();
172                    Err(IsoTpError::PayloadTooLarge)
173                }
174            },
175            _ => Err(IsoTpError::UnknownFrameType(0x0)),
176        }
177    }
178
179    /// Writes the next ISO-TP frame as raw PCI bytes into `out_buf`.
180    ///
181    /// For extended/mixed addressing the caller prepends N_TA or N_AE
182    /// before putting the frame on the wire.
183    pub fn next_frame(&mut self, out_buf: &mut [u8]) -> Result<SegmentResult, IsoTpError> {
184        match self.state.clone() {
185            SegmenterState::Idle => Ok(SegmentResult::Complete),
186
187            SegmenterState::WaitingForFlowControl { .. } => Ok(SegmentResult::WaitForFlowControl),
188
189            SegmenterState::Sending {
190                next_sequence,
191                sent,
192                block_remaining,
193                st_min,
194            } => {
195                let total = self.payload.len();
196                let max_data = self.config.max_frame_data_len;
197
198                // Max single frame payload depends on frame type and addressing mode
199                let max_sf = match max_data {
200                    8 => self.config.addressing_mode.max_sf_payload_classic(),
201                    _ => self.config.addressing_mode.max_sf_payload_fd(),
202                };
203
204                // region: Single frame
205
206                if sent == 0 && total <= max_sf {
207                    let pci = PciFrame::SingleFrame {
208                        len: total as u8,
209                        data: &self.payload,
210                    };
211
212                    let pci_len = pci.encode_header(out_buf)?;
213                    let end = pci_len + total;
214
215                    if out_buf.len() < end {
216                        return Err(IsoTpError::FrameTooShort {
217                            actual: out_buf.len(),
218                        });
219                    }
220                    out_buf[pci_len..end].copy_from_slice(&self.payload);
221
222                    self.state = SegmenterState::Idle;
223
224                    return Ok(SegmentResult::Frame { len: end });
225                }
226
227                // endregion: Single frame
228
229                // region: First frame
230
231                if sent == 0 {
232                    let pci = PciFrame::FirstFrame {
233                        total_len: total as u32,
234                        data: &self.payload,
235                    };
236
237                    let pci_len = pci.encode_header(out_buf)?;
238                    let data_in_ff = (max_data - pci_len).min(total);
239                    let end = pci_len + data_in_ff;
240
241                    if out_buf.len() < end {
242                        return Err(IsoTpError::FrameTooShort {
243                            actual: out_buf.len(),
244                        });
245                    }
246                    out_buf[pci_len..end].copy_from_slice(&self.payload[..data_in_ff]);
247
248                    self.state = SegmenterState::WaitingForFlowControl {
249                        next_sequence: 1,
250                        sent: data_in_ff,
251                    };
252
253                    return Ok(SegmentResult::Frame { len: end });
254                }
255
256                // endregion: First frame
257
258                // region: Consecutive frame
259
260                let remaining = &self.payload[sent..];
261                let pci = PciFrame::ConsecutiveFrame {
262                    sequence_number: next_sequence,
263                    data: remaining,
264                };
265
266                let pci_len = pci.encode_header(out_buf)?;
267                let data_in_cf = (max_data - pci_len).min(remaining.len());
268                let end = pci_len + data_in_cf;
269
270                if out_buf.len() < end {
271                    return Err(IsoTpError::FrameTooShort {
272                        actual: out_buf.len(),
273                    });
274                }
275
276                out_buf[pci_len..end].copy_from_slice(&remaining[..data_in_cf]);
277
278                let new_sent = sent + data_in_cf;
279                let new_sequence = (next_sequence + 1) & 0x0F;
280
281                if new_sent >= total {
282                    self.state = SegmenterState::Idle;
283                    return Ok(SegmentResult::Frame { len: end });
284                }
285
286                let new_block = if block_remaining == 0 {
287                    0
288                } else if block_remaining == 1 {
289                    self.state = SegmenterState::WaitingForFlowControl {
290                        next_sequence: new_sequence,
291                        sent: new_sent,
292                    };
293                    return Ok(SegmentResult::Frame { len: end });
294                } else {
295                    block_remaining - 1
296                };
297
298                self.state = SegmenterState::Sending {
299                    next_sequence: new_sequence,
300                    sent: new_sent,
301                    block_remaining: new_block,
302                    st_min,
303                };
304
305                Ok(SegmentResult::Frame { len: end })
306
307                // endregion: Consecutive frame
308            }
309        }
310    }
311}
312
313// endregion: Segmenter