Skip to main content

sim_codec_uds/
frame.rs

1//! UDS and OBD-II byte-frame parsing.
2
3use sim_kernel::{CodecId, Error, Result};
4
5/// A decoded diagnostic frame supported by `codec/uds`.
6#[derive(Clone, Debug, PartialEq, Eq)]
7pub enum UdsFrame {
8    /// UDS ReadDataByIdentifier request, service `0x22`.
9    ReadDataByIdentifierRequest {
10        /// Data identifier.
11        did: u16,
12    },
13    /// UDS positive ReadDataByIdentifier response, service `0x62`.
14    ReadDataByIdentifierResponse {
15        /// Data identifier.
16        did: u16,
17        /// Opaque payload bytes.
18        data: Vec<u8>,
19    },
20    /// OBD-II mode request with an optional PID.
21    ObdModeRequest {
22        /// OBD-II mode byte.
23        mode: u8,
24        /// Optional PID byte.
25        pid: Option<u8>,
26    },
27    /// UDS ReadDTCInformation request, service `0x19`.
28    ReadDtcRequest {
29        /// Subfunction byte.
30        subfunction: u8,
31        /// Optional status mask byte.
32        status_mask: Option<u8>,
33    },
34    /// UDS positive ReadDTCInformation response, service `0x59`.
35    ReadDtcResponse {
36        /// Response subfunction byte.
37        subfunction: u8,
38        /// Status availability mask byte.
39        status_availability_mask: u8,
40        /// Decoded DTC entries.
41        dtcs: Vec<DtcFrame>,
42    },
43}
44
45/// A raw DTC plus its status byte.
46#[derive(Clone, Debug, PartialEq, Eq)]
47pub struct DtcFrame {
48    /// Three raw UDS DTC bytes.
49    pub raw_code: [u8; 3],
50    /// Standard UDS DTC status byte.
51    pub status: u8,
52}
53
54/// Decodes supported diagnostic bytes into a frame.
55pub fn decode_frame(codec: CodecId, bytes: &[u8]) -> Result<UdsFrame> {
56    let Some((&service, rest)) = bytes.split_first() else {
57        return Err(codec_error(codec, "empty UDS frame"));
58    };
59    match service {
60        0x22 => read_did_request(codec, rest),
61        0x62 => read_did_response(codec, rest),
62        0x19 => read_dtc_request(codec, rest),
63        0x59 => read_dtc_response(codec, rest),
64        mode if is_obd_mode(mode) => obd_mode_request(codec, mode, rest),
65        _ => Err(codec_error(
66            codec,
67            format!("unsupported UDS/OBD-II service 0x{service:02X}"),
68        )),
69    }
70}
71
72/// Encodes a supported diagnostic frame to bytes.
73pub fn encode_frame(frame: &UdsFrame) -> Vec<u8> {
74    match frame {
75        UdsFrame::ReadDataByIdentifierRequest { did } => {
76            let [high, low] = did.to_be_bytes();
77            vec![0x22, high, low]
78        }
79        UdsFrame::ReadDataByIdentifierResponse { did, data } => {
80            let [high, low] = did.to_be_bytes();
81            let mut bytes = vec![0x62, high, low];
82            bytes.extend(data);
83            bytes
84        }
85        UdsFrame::ObdModeRequest { mode, pid } => {
86            let mut bytes = vec![*mode];
87            if let Some(pid) = pid {
88                bytes.push(*pid);
89            }
90            bytes
91        }
92        UdsFrame::ReadDtcRequest {
93            subfunction,
94            status_mask,
95        } => {
96            let mut bytes = vec![0x19, *subfunction];
97            if let Some(mask) = status_mask {
98                bytes.push(*mask);
99            }
100            bytes
101        }
102        UdsFrame::ReadDtcResponse {
103            subfunction,
104            status_availability_mask,
105            dtcs,
106        } => {
107            let mut bytes = vec![0x59, *subfunction, *status_availability_mask];
108            for dtc in dtcs {
109                bytes.extend(dtc.raw_code);
110                bytes.push(dtc.status);
111            }
112            bytes
113        }
114    }
115}
116
117fn read_did_request(codec: CodecId, rest: &[u8]) -> Result<UdsFrame> {
118    if rest.len() != 2 {
119        return Err(codec_error(
120            codec,
121            "read-DID request must be exactly three bytes",
122        ));
123    }
124    Ok(UdsFrame::ReadDataByIdentifierRequest {
125        did: u16::from_be_bytes([rest[0], rest[1]]),
126    })
127}
128
129fn read_did_response(codec: CodecId, rest: &[u8]) -> Result<UdsFrame> {
130    if rest.len() < 2 {
131        return Err(codec_error(
132            codec,
133            "read-DID response must include a two-byte DID",
134        ));
135    }
136    Ok(UdsFrame::ReadDataByIdentifierResponse {
137        did: u16::from_be_bytes([rest[0], rest[1]]),
138        data: rest[2..].to_vec(),
139    })
140}
141
142fn obd_mode_request(codec: CodecId, mode: u8, rest: &[u8]) -> Result<UdsFrame> {
143    match rest {
144        [] => Ok(UdsFrame::ObdModeRequest { mode, pid: None }),
145        [pid] => Ok(UdsFrame::ObdModeRequest {
146            mode,
147            pid: Some(*pid),
148        }),
149        _ => Err(codec_error(
150            codec,
151            "OBD-II mode request supports at most one PID byte",
152        )),
153    }
154}
155
156fn read_dtc_request(codec: CodecId, rest: &[u8]) -> Result<UdsFrame> {
157    match rest {
158        [subfunction] => Ok(UdsFrame::ReadDtcRequest {
159            subfunction: *subfunction,
160            status_mask: None,
161        }),
162        [subfunction, mask] => Ok(UdsFrame::ReadDtcRequest {
163            subfunction: *subfunction,
164            status_mask: Some(*mask),
165        }),
166        _ => Err(codec_error(
167            codec,
168            "read-DTC request must be service, subfunction, and optional status mask",
169        )),
170    }
171}
172
173fn read_dtc_response(codec: CodecId, rest: &[u8]) -> Result<UdsFrame> {
174    if rest.len() < 2 {
175        return Err(codec_error(
176            codec,
177            "read-DTC response must include subfunction and availability mask",
178        ));
179    }
180    let payload = &rest[2..];
181    if !payload.len().is_multiple_of(4) {
182        return Err(codec_error(
183            codec,
184            "read-DTC response DTC payload must be four-byte entries",
185        ));
186    }
187    let dtcs = payload
188        .chunks_exact(4)
189        .map(|chunk| DtcFrame {
190            raw_code: [chunk[0], chunk[1], chunk[2]],
191            status: chunk[3],
192        })
193        .collect();
194    Ok(UdsFrame::ReadDtcResponse {
195        subfunction: rest[0],
196        status_availability_mask: rest[1],
197        dtcs,
198    })
199}
200
201fn is_obd_mode(service: u8) -> bool {
202    matches!(service, 0x01..=0x0A)
203}
204
205pub(crate) fn codec_error(codec: CodecId, message: impl Into<String>) -> Error {
206    Error::CodecError {
207        codec,
208        message: message.into(),
209    }
210}