Skip to main content

can_uds/
lib.rs

1//! `can-uds`: utilities for CAN identifier schemes commonly used with UDS (Unified Diagnostic
2//! Services).
3//!
4//! This crate intentionally does **not** implement ISO-TP. It only defines helpers for encoding
5//! and decoding CAN identifiers and building acceptance filters for those identifier schemes.
6//!
7//! # 29-bit "normal fixed" / physical addressing
8//! A widely-used diagnostics scheme uses 29-bit identifiers of the form:
9//! - `0x18DA_TA_SA` where `TA` is the **target address** and `SA` is the **source address**.
10//!
11//! The helpers in [`uds29`] are small and `no_std`-friendly.
12
13#![no_std]
14
15pub mod uds29 {
16    use embedded_can::{ExtendedId, Id as CanId};
17    use embedded_can_interface::{Id, IdMask, IdMaskFilter};
18
19    /// Max 29-bit CAN identifier value.
20    pub const EXT_ID_MAX: u32 = 0x1FFF_FFFF;
21
22    /// 29-bit UDS addressing kind (normal fixed).
23    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
24    pub enum Uds29Kind {
25        /// Physical addressing (PDU Format `0xDA`): `0x18DA_TA_SA`.
26        Physical,
27        /// Functional addressing (PDU Format `0xDB`): `0x18DB_TA_SA`.
28        Functional,
29    }
30
31    /// Parsed 29-bit UDS addressing identifier (`0x18DA/0x18DB _TA_SA`).
32    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
33    pub struct Uds29Id {
34        pub kind: Uds29Kind,
35        pub target: u8,
36        pub source: u8,
37    }
38
39    /// UDS physical addressing base for 29-bit "normal fixed" addressing.
40    pub const PHYS_BASE: u32 = 0x18DA_0000;
41    /// UDS functional addressing base for 29-bit "normal fixed" addressing.
42    pub const FUNC_BASE: u32 = 0x18DB_0000;
43
44    /// Mask that matches the fixed "base" bits for the `0x18DA_TA_SA` / `0x18DB_TA_SA` patterns.
45    pub const BASE_MASK: u32 = 0x1FFF_0000;
46    /// Mask that matches a single `TA` but ignores `SA` (useful for "accept all senders to me").
47    pub const TARGET_MASK: u32 = 0x1FFF_FF00;
48
49    /// Encode a 29-bit UDS identifier as a raw `u32`.
50    #[inline]
51    pub const fn encode_id_raw(kind: Uds29Kind, target: u8, source: u8) -> u32 {
52        let base = match kind {
53            Uds29Kind::Physical => PHYS_BASE,
54            Uds29Kind::Functional => FUNC_BASE,
55        };
56        base | ((target as u32) << 8) | (source as u32)
57    }
58
59    /// Decode a raw 29-bit CAN identifier of the form `0x18DA_TA_SA` / `0x18DB_TA_SA`.
60    #[inline]
61    pub const fn decode_id_raw(raw: u32) -> Option<Uds29Id> {
62        if (raw & !EXT_ID_MAX) != 0 {
63            return None;
64        }
65        let base = raw & BASE_MASK;
66        let kind = if base == PHYS_BASE {
67            Uds29Kind::Physical
68        } else if base == FUNC_BASE {
69            Uds29Kind::Functional
70        } else {
71            return None;
72        };
73
74        let target = ((raw >> 8) & 0xFF) as u8;
75        let source = (raw & 0xFF) as u8;
76        Some(Uds29Id {
77            kind,
78            target,
79            source,
80        })
81    }
82
83    /// Encode a 29-bit UDS identifier as an `embedded-can-interface` [`Id`].
84    #[inline]
85    pub fn encode_id(kind: Uds29Kind, target: u8, source: u8) -> Id {
86        let raw = encode_id_raw(kind, target, source);
87        Id::Extended(ExtendedId::new(raw).expect("UDS 29-bit ID must fit in 29 bits"))
88    }
89
90    /// Decode an `embedded-can` [`Id`] as a 29-bit UDS identifier.
91    #[inline]
92    pub fn decode_id(id: CanId) -> Option<Uds29Id> {
93        match id {
94            CanId::Extended(ext) => decode_id_raw(ext.as_raw()),
95            CanId::Standard(_) => None,
96        }
97    }
98
99    /// Encode a 29-bit physical-addressing CAN identifier as a raw `u32` (`0x18DA_TA_SA`).
100    #[inline]
101    pub const fn encode_phys_id_raw(target: u8, source: u8) -> u32 {
102        encode_id_raw(Uds29Kind::Physical, target, source)
103    }
104
105    /// Encode a 29-bit functional-addressing CAN identifier as a raw `u32` (`0x18DB_TA_SA`).
106    #[inline]
107    pub const fn encode_func_id_raw(target: u8, source: u8) -> u32 {
108        encode_id_raw(Uds29Kind::Functional, target, source)
109    }
110
111    /// Decode a raw 29-bit CAN identifier of the form `0x18DA_TA_SA`.
112    #[inline]
113    pub const fn decode_phys_id_raw(raw: u32) -> Option<(u8, u8)> {
114        match decode_id_raw(raw) {
115            Some(Uds29Id {
116                kind: Uds29Kind::Physical,
117                target,
118                source,
119            }) => Some((target, source)),
120            _ => None,
121        }
122    }
123
124    /// Decode a raw 29-bit CAN identifier of the form `0x18DB_TA_SA`.
125    #[inline]
126    pub const fn decode_func_id_raw(raw: u32) -> Option<(u8, u8)> {
127        match decode_id_raw(raw) {
128            Some(Uds29Id {
129                kind: Uds29Kind::Functional,
130                target,
131                source,
132            }) => Some((target, source)),
133            _ => None,
134        }
135    }
136
137    /// Encode a 29-bit physical-addressing CAN identifier as an `embedded-can-interface` [`Id`].
138    #[inline]
139    pub fn encode_phys_id(target: u8, source: u8) -> Id {
140        encode_id(Uds29Kind::Physical, target, source)
141    }
142
143    /// Encode a 29-bit functional-addressing CAN identifier as an `embedded-can-interface` [`Id`].
144    #[inline]
145    pub fn encode_func_id(target: u8, source: u8) -> Id {
146        encode_id(Uds29Kind::Functional, target, source)
147    }
148
149    /// Decode an `embedded-can` [`Id`] as a 29-bit UDS physical identifier.
150    #[inline]
151    pub fn decode_phys_id(id: CanId) -> Option<(u8, u8)> {
152        decode_id(id).and_then(|v| match v.kind {
153            Uds29Kind::Physical => Some((v.target, v.source)),
154            Uds29Kind::Functional => None,
155        })
156    }
157
158    /// Decode an `embedded-can` [`Id`] as a 29-bit UDS functional identifier.
159    #[inline]
160    pub fn decode_func_id(id: CanId) -> Option<(u8, u8)> {
161        decode_id(id).and_then(|v| match v.kind {
162            Uds29Kind::Functional => Some((v.target, v.source)),
163            Uds29Kind::Physical => None,
164        })
165    }
166
167    /// A small helper for returning 1 or 2 acceptance filters without allocation.
168    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
169    pub struct AcceptanceFilters {
170        filters: [IdMaskFilter; 2],
171        len: usize,
172    }
173
174    impl AcceptanceFilters {
175        #[inline]
176        pub fn as_slice(&self) -> &[IdMaskFilter] {
177            &self.filters[..self.len]
178        }
179    }
180
181    fn filter_for_target_kind(kind: Uds29Kind, target: u8) -> IdMaskFilter {
182        let base = match kind {
183            Uds29Kind::Physical => PHYS_BASE,
184            Uds29Kind::Functional => FUNC_BASE,
185        };
186        let raw = base | ((target as u32) << 8);
187        IdMaskFilter {
188            id: Id::Extended(ExtendedId::new(raw).expect("UDS base+target must fit in 29 bits")),
189            mask: IdMask::Extended(TARGET_MASK),
190        }
191    }
192
193    /// Build an acceptance filter that matches any physical-addressing ID with `target`.
194    #[inline]
195    pub fn filter_phys_for_target(target: u8) -> IdMaskFilter {
196        filter_for_target_kind(Uds29Kind::Physical, target)
197    }
198
199    /// Build an acceptance filter that matches any functional-addressing ID with `target`.
200    #[inline]
201    pub fn filter_func_for_target(target: u8) -> IdMaskFilter {
202        filter_for_target_kind(Uds29Kind::Functional, target)
203    }
204
205    /// Build acceptance filters for:
206    /// - all physical frames addressed to `phys_target`, and
207    /// - (optionally) all functional frames addressed to `func_target`.
208    #[inline]
209    pub fn filters_for_targets(phys_target: u8, func_target: Option<u8>) -> AcceptanceFilters {
210        let phys = filter_phys_for_target(phys_target);
211        if let Some(ft) = func_target {
212            let func = filter_func_for_target(ft);
213            AcceptanceFilters {
214                filters: [phys, func],
215                len: 2,
216            }
217        } else {
218            AcceptanceFilters {
219                filters: [phys, phys],
220                len: 1,
221            }
222        }
223    }
224}
225
226#[cfg(test)]
227mod tests {
228    use super::uds29;
229    use embedded_can::{ExtendedId, Id as CanId};
230
231    #[test]
232    fn uds29_round_trip_raw() {
233        let (ta, sa) = (0xF1u8, 0x33u8);
234        let raw = uds29::encode_phys_id_raw(ta, sa);
235        assert_eq!(raw, 0x18DA_F133);
236        assert_eq!(uds29::decode_phys_id_raw(raw), Some((ta, sa)));
237    }
238
239    #[test]
240    fn uds29_functional_round_trip_raw() {
241        let (ta, sa) = (0x33u8, 0xF1u8);
242        let raw = uds29::encode_func_id_raw(ta, sa);
243        assert_eq!(raw, 0x18DB_33F1);
244        assert_eq!(uds29::decode_func_id_raw(raw), Some((ta, sa)));
245    }
246
247    #[test]
248    fn uds29_decode_rejects_non_extended_range() {
249        let raw = 0xFFFF_FFFF;
250        assert_eq!(uds29::decode_phys_id_raw(raw), None);
251    }
252
253    #[test]
254    fn uds29_decode_rejects_wrong_base() {
255        let raw = 0x18DB_0000 | (0x12u32 << 8) | 0x34;
256        // This is functional, so physical decode must reject it.
257        assert_eq!(uds29::decode_phys_id_raw(raw), None);
258        assert_eq!(uds29::decode_func_id_raw(raw), Some((0x12, 0x34)));
259    }
260
261    #[test]
262    fn uds29_decode_from_embedded_can_id() {
263        let raw = uds29::encode_phys_id_raw(0xAA, 0x55);
264        let id = CanId::Extended(ExtendedId::new(raw).unwrap());
265        assert_eq!(uds29::decode_phys_id(id), Some((0xAA, 0x55)));
266    }
267}