Skip to main content

can_isotp_interface/
lib.rs

1//! Shared UDS-over-ISO-TP transport traits.
2//!
3//! This crate defines dependency-light interfaces for 29-bit UDS-style ISO-TP transports where
4//! messages are always addressed to a peer (`u8` source/target addresses).
5
6#![no_std]
7#![allow(async_fn_in_trait)]
8
9use core::time::Duration;
10
11/// Receive-side ISO-TP flow-control parameters.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct RxFlowControl {
14    /// Block size (BS) to advertise (0 = unlimited).
15    pub block_size: u8,
16    /// Minimum separation time (STmin) to advertise between consecutive frames.
17    pub st_min: Duration,
18}
19
20/// Optional extension trait: runtime-configurable RX flow-control.
21pub trait IsoTpRxFlowControlConfig {
22    /// Backend-specific error type.
23    type Error;
24
25    /// Update receive-side FlowControl parameters.
26    fn set_rx_flow_control(&mut self, fc: RxFlowControl) -> Result<(), Self::Error>;
27}
28
29/// Result of a receive attempt.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum RecvStatus {
32    /// No payload arrived before the timeout elapsed.
33    TimedOut,
34    /// One payload was delivered to the callback.
35    DeliveredOne,
36}
37
38/// Control signal for receive callbacks.
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum RecvControl {
41    /// Continue processing future payloads.
42    Continue,
43    /// Stop processing after the current payload.
44    Stop,
45}
46
47/// Metadata accompanying a received payload.
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub struct RecvMeta {
50    /// Address to reply to.
51    pub reply_to: u8,
52}
53
54/// Error for a send attempt.
55#[derive(Debug)]
56pub enum SendError<E> {
57    /// The send did not complete before the timeout elapsed.
58    Timeout,
59    /// Backend-specific error.
60    Backend(E),
61}
62
63/// Error for a receive attempt.
64#[derive(Debug)]
65pub enum RecvError<E> {
66    /// Caller-provided buffer was too small to hold the received payload.
67    BufferTooSmall {
68        /// Needed payload length in bytes.
69        needed: usize,
70        /// Provided buffer length in bytes.
71        got: usize,
72    },
73    /// Backend-specific error.
74    Backend(E),
75}
76
77/// Result of a receive-into attempt (multi-peer with reply-to metadata).
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub enum RecvMetaIntoStatus {
80    /// No payload arrived before the timeout elapsed.
81    TimedOut,
82    /// One payload was delivered into the provided buffer.
83    DeliveredOne {
84        /// Reply-to metadata (sender identity).
85        meta: RecvMeta,
86        /// Number of payload bytes written into the output buffer.
87        len: usize,
88    },
89}
90
91/// UDS-aware ISO-TP endpoint.
92///
93/// This trait is addressed: sends target a peer address and receives report `reply_to` metadata.
94pub trait IsoTpEndpoint {
95    /// Backend-specific error type.
96    type Error;
97
98    /// Send a payload to a specific peer address.
99    fn send_to(
100        &mut self,
101        to: u8,
102        payload: &[u8],
103        timeout: Duration,
104    ) -> Result<(), SendError<Self::Error>>;
105
106    /// Send a functional-addressing payload to a target functional address.
107    fn send_functional_to(
108        &mut self,
109        functional_to: u8,
110        payload: &[u8],
111        timeout: Duration,
112    ) -> Result<(), SendError<Self::Error>>;
113
114    /// Receive at most one payload and deliver it with reply-to metadata.
115    fn recv_one<Cb>(
116        &mut self,
117        timeout: Duration,
118        on_payload: Cb,
119    ) -> Result<RecvStatus, RecvError<Self::Error>>
120    where
121        Cb: FnMut(RecvMeta, &[u8]) -> Result<RecvControl, Self::Error>;
122}
123
124/// Async UDS-aware ISO-TP endpoint.
125pub trait IsoTpAsyncEndpoint {
126    /// Backend-specific error type.
127    type Error;
128
129    /// Send a payload to a specific peer address.
130    async fn send_to(
131        &mut self,
132        to: u8,
133        payload: &[u8],
134        timeout: Duration,
135    ) -> Result<(), SendError<Self::Error>>;
136
137    /// Send a functional-addressing payload to a target functional address.
138    async fn send_functional_to(
139        &mut self,
140        functional_to: u8,
141        payload: &[u8],
142        timeout: Duration,
143    ) -> Result<(), SendError<Self::Error>>;
144
145    /// Receive at most one payload and deliver it with reply-to metadata.
146    async fn recv_one<Cb>(
147        &mut self,
148        timeout: Duration,
149        on_payload: Cb,
150    ) -> Result<RecvStatus, RecvError<Self::Error>>
151    where
152        Cb: FnMut(RecvMeta, &[u8]) -> Result<RecvControl, Self::Error>;
153}
154
155/// Async UDS-aware ISO-TP endpoint (recv-into API).
156pub trait IsoTpAsyncEndpointRecvInto {
157    /// Backend-specific error type.
158    type Error;
159
160    /// Receive at most one payload and copy it into `out`.
161    async fn recv_one_into(
162        &mut self,
163        timeout: Duration,
164        out: &mut [u8],
165    ) -> Result<RecvMetaIntoStatus, RecvError<Self::Error>>;
166}
167
168/// Backward-compatible alias trait name for addressed sync endpoints.
169pub trait IsoTpEndpointMeta: IsoTpEndpoint {}
170
171impl<T> IsoTpEndpointMeta for T where T: IsoTpEndpoint {}
172
173/// Backward-compatible alias trait name for addressed async endpoints.
174pub trait IsoTpAsyncEndpointMeta: IsoTpAsyncEndpoint {}
175
176impl<T> IsoTpAsyncEndpointMeta for T where T: IsoTpAsyncEndpoint {}
177
178/// Backward-compatible alias trait name for addressed async recv-into endpoints.
179pub trait IsoTpAsyncEndpointMetaRecvInto: IsoTpAsyncEndpointRecvInto {}
180
181impl<T> IsoTpAsyncEndpointMetaRecvInto for T where T: IsoTpAsyncEndpointRecvInto {}
182
183#[cfg(test)]
184mod tests {
185    extern crate std;
186
187    use super::*;
188    use core::future::Future;
189    use core::pin::Pin;
190    use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
191    use std::vec;
192    use std::vec::Vec;
193
194    fn block_on<F: Future>(mut fut: F) -> F::Output {
195        fn raw_waker() -> RawWaker {
196            fn clone(_: *const ()) -> RawWaker {
197                raw_waker()
198            }
199            fn wake(_: *const ()) {}
200            fn wake_by_ref(_: *const ()) {}
201            fn drop(_: *const ()) {}
202            let vtable = &RawWakerVTable::new(clone, wake, wake_by_ref, drop);
203            RawWaker::new(core::ptr::null(), vtable)
204        }
205
206        // SAFETY: no-op waker is sufficient for these immediately-ready futures.
207        let waker = unsafe { Waker::from_raw(raw_waker()) };
208        let mut cx = Context::from_waker(&waker);
209        // SAFETY: we do not move `fut` after pinning.
210        let mut fut = unsafe { Pin::new_unchecked(&mut fut) };
211        loop {
212            match fut.as_mut().poll(&mut cx) {
213                Poll::Ready(v) => return v,
214                Poll::Pending => {}
215            }
216        }
217    }
218
219    #[derive(Default)]
220    struct Dummy {
221        sent_to: Vec<u8>,
222        sent_payloads: Vec<Vec<u8>>,
223        inbox: Vec<(u8, Vec<u8>)>,
224    }
225
226    impl IsoTpEndpoint for Dummy {
227        type Error = ();
228
229        fn send_to(
230            &mut self,
231            to: u8,
232            payload: &[u8],
233            _timeout: Duration,
234        ) -> Result<(), SendError<Self::Error>> {
235            self.sent_to.push(to);
236            self.sent_payloads.push(payload.to_vec());
237            Ok(())
238        }
239
240        fn send_functional_to(
241            &mut self,
242            functional_to: u8,
243            payload: &[u8],
244            timeout: Duration,
245        ) -> Result<(), SendError<Self::Error>> {
246            IsoTpEndpoint::send_to(self, functional_to, payload, timeout)
247        }
248
249        fn recv_one<Cb>(
250            &mut self,
251            _timeout: Duration,
252            mut on_payload: Cb,
253        ) -> Result<RecvStatus, RecvError<Self::Error>>
254        where
255            Cb: FnMut(RecvMeta, &[u8]) -> Result<RecvControl, Self::Error>,
256        {
257            if let Some((reply_to, data)) = self.inbox.pop() {
258                let _ = on_payload(RecvMeta { reply_to }, &data).map_err(RecvError::Backend)?;
259                return Ok(RecvStatus::DeliveredOne);
260            }
261            Ok(RecvStatus::TimedOut)
262        }
263    }
264
265    impl IsoTpAsyncEndpoint for Dummy {
266        type Error = ();
267
268        async fn send_to(
269            &mut self,
270            to: u8,
271            payload: &[u8],
272            timeout: Duration,
273        ) -> Result<(), SendError<Self::Error>> {
274            IsoTpEndpoint::send_to(self, to, payload, timeout)
275        }
276
277        async fn send_functional_to(
278            &mut self,
279            functional_to: u8,
280            payload: &[u8],
281            timeout: Duration,
282        ) -> Result<(), SendError<Self::Error>> {
283            IsoTpEndpoint::send_functional_to(self, functional_to, payload, timeout)
284        }
285
286        async fn recv_one<Cb>(
287            &mut self,
288            timeout: Duration,
289            on_payload: Cb,
290        ) -> Result<RecvStatus, RecvError<Self::Error>>
291        where
292            Cb: FnMut(RecvMeta, &[u8]) -> Result<RecvControl, Self::Error>,
293        {
294            IsoTpEndpoint::recv_one(self, timeout, on_payload)
295        }
296    }
297
298    impl IsoTpAsyncEndpointRecvInto for Dummy {
299        type Error = ();
300
301        async fn recv_one_into(
302            &mut self,
303            _timeout: Duration,
304            out: &mut [u8],
305        ) -> Result<RecvMetaIntoStatus, RecvError<Self::Error>> {
306            if let Some((reply_to, data)) = self.inbox.pop() {
307                if data.len() > out.len() {
308                    return Err(RecvError::BufferTooSmall {
309                        needed: data.len(),
310                        got: out.len(),
311                    });
312                }
313                out[..data.len()].copy_from_slice(&data);
314                return Ok(RecvMetaIntoStatus::DeliveredOne {
315                    meta: RecvMeta { reply_to },
316                    len: data.len(),
317                });
318            }
319            Ok(RecvMetaIntoStatus::TimedOut)
320        }
321    }
322
323    #[test]
324    fn sync_trait_is_addressed() {
325        let mut d = Dummy::default();
326        IsoTpEndpoint::send_to(&mut d, 0x33, b"abc", Duration::from_millis(1)).unwrap();
327        assert_eq!(d.sent_to, vec![0x33]);
328        assert_eq!(d.sent_payloads, vec![b"abc".to_vec()]);
329    }
330
331    #[test]
332    fn async_trait_is_addressed() {
333        let mut d = Dummy::default();
334        block_on(IsoTpAsyncEndpoint::send_to(
335            &mut d,
336            0x44,
337            b"xyz",
338            Duration::from_millis(1),
339        ))
340        .unwrap();
341        assert_eq!(d.sent_to, vec![0x44]);
342        assert_eq!(d.sent_payloads, vec![b"xyz".to_vec()]);
343    }
344}