1use sim_kernel::{CodecId, Error, Result};
4
5#[derive(Clone, Debug, PartialEq, Eq)]
7pub enum UdsFrame {
8 ReadDataByIdentifierRequest {
10 did: u16,
12 },
13 ReadDataByIdentifierResponse {
15 did: u16,
17 data: Vec<u8>,
19 },
20 ObdModeRequest {
22 mode: u8,
24 pid: Option<u8>,
26 },
27 ReadDtcRequest {
29 subfunction: u8,
31 status_mask: Option<u8>,
33 },
34 ReadDtcResponse {
36 subfunction: u8,
38 status_availability_mask: u8,
40 dtcs: Vec<DtcFrame>,
42 },
43}
44
45#[derive(Clone, Debug, PartialEq, Eq)]
47pub struct DtcFrame {
48 pub raw_code: [u8; 3],
50 pub status: u8,
52}
53
54pub 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
72pub 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}