Krolyxon
Commit 698a1df

Branches

Refs →

budctlcratesbudctl-coresrcdevicesrealme_t310frame.rs

363 lines · 11.9 KB · Rust

1//! Framing of the OPPO/OnePlus/realme protocol family. Spec: realme_t310.md#frame
2//!
3//! Holds no T310 tables. If a second model of this family is added, this file moves to
4//! `devices/oppo_family/` (CLAUDE.md, "Device plugin contract").
5
6use crate::error::{Error, Result};
7use crate::reader::Reader;
8
9/// Spec: realme_t310.md#frame
10const START: u8 = 0xAA;
11/// Spec: realme_t310.md#frame
12const WHOLE: u8 = 0x00;
13/// What the T310 puts in the slice byte of frames it starts itself (requests and pushes). The
14/// rest is laid out as a whole message. Spec: realme_t310.md#frame-from-buds
15const WHOLE_FROM_BUDS: u8 = 0x08;
16/// Spec: realme_t310.md#frame-slices
17const FIRST_SLICE: u8 = 0x01;
18/// Spec: realme_t310.md#frame-slices
19const MIDDLE_SLICE: u8 = 0x02;
20/// Spec: realme_t310.md#frame-slices
21const LAST_SLICE: u8 = 0x03;
22/// Bytes after the length in a whole frame, before the payload: slice, 00, cmd (2), seq,
23/// paylen (2). Spec: realme_t310.md#frame-whole
24const WHOLE_HEADER: usize = 7;
25/// Shortest slice frame: slice, 00, fsn, at least one byte. Spec: realme_t310.md#frame-receive
26const MIN_SLICE_LEN: usize = 4;
27/// Spec: realme_t310.md#frame-receive
28const MAX_LEN: usize = 4096;
29/// The length field is at most 3 bytes. Spec: realme_t310.md#frame-receive
30const MAX_LEN_BYTES: usize = 3;
31/// Senders slice messages that do not fit in this many bytes. budctl never slices, so it
32/// refuses to build a bigger frame. Spec: realme_t310.md#frame-slices
33const FRAME_BUDGET: usize = 660;
34
35/// One message: a command id, its sequence byte and its payload.
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub(super) struct Frame {
38 pub cmd: u16,
39 pub seq: u8,
40 pub payload: Vec<u8>,
41}
42
43/// One whole (unsliced) frame. Spec: realme_t310.md#frame-whole
44pub(super) fn encode(cmd: u16, seq: u8, payload: &[u8]) -> Result<Vec<u8>> {
45 let too_big = || Error::Protocol(format!("a {} byte payload does not fit in one frame", payload.len()));
46 let paylen = u16::try_from(payload.len()).map_err(|_| too_big())?;
47 let mut frame = vec![START];
48 push_leb128(WHOLE_HEADER + payload.len(), &mut frame);
49 frame.extend([WHOLE, 0x00]);
50 frame.extend(cmd.to_le_bytes());
51 frame.push(seq);
52 frame.extend(paylen.to_le_bytes());
53 frame.extend(payload);
54 if frame.len() > FRAME_BUDGET {
55 return Err(too_big());
56 }
57 Ok(frame)
58}
59
60/// 7 bits per byte, low bits first; the top bit says another byte follows.
61fn push_leb128(mut value: usize, out: &mut Vec<u8>) {
62 loop {
63 let low = u8::try_from(value & 0x7F).unwrap_or_default();
64 value >>= 7;
65 if value == 0 {
66 out.push(low);
67 return;
68 }
69 out.push(low | 0x80);
70 }
71}
72
73enum Length {
74 /// The length and how many bytes it took.
75 Value(usize, usize),
76 /// More bytes are needed to finish the length.
77 Incomplete,
78 /// Not a valid length: more than three bytes.
79 Invalid,
80}
81
82fn read_leb128(bytes: &[u8]) -> Length {
83 let mut value = 0;
84 for (i, &byte) in bytes.iter().take(MAX_LEN_BYTES).enumerate() {
85 value |= usize::from(byte & 0x7F) << (7 * i);
86 if byte & 0x80 == 0 {
87 return Length::Value(value, i + 1);
88 }
89 }
90 if bytes.len() < MAX_LEN_BYTES {
91 Length::Incomplete
92 } else {
93 Length::Invalid
94 }
95}
96
97/// Turns the byte stream from the buds back into messages. Spec: realme_t310.md#frame-receive
98#[derive(Default)]
99pub(super) struct Reassembler {
100 buffer: Vec<u8>,
101 /// Parts of a sliced message received so far.
102 sliced: Option<Vec<u8>>,
103 /// Bytes thrown away while looking for a frame start (shown with `--debug`).
104 discarded: usize,
105}
106
107impl Reassembler {
108 pub(super) fn push(&mut self, bytes: &[u8]) {
109 self.buffer.extend_from_slice(bytes);
110 }
111
112 /// How many bytes were discarded since the last call.
113 pub(super) fn take_discarded(&mut self) -> usize {
114 std::mem::take(&mut self.discarded)
115 }
116
117 /// The next complete message, if one is buffered.
118 pub(super) fn next_frame(&mut self) -> Option<Frame> {
119 loop {
120 // Throw away everything before the next frame start.
121 let start = self.buffer.iter().position(|&b| b == START);
122 let skip = start.unwrap_or(self.buffer.len());
123 self.discarded += skip;
124 self.buffer.drain(..skip);
125 if self.buffer.is_empty() {
126 return None;
127 }
128
129 let (len, len_bytes) = match read_leb128(self.buffer.get(1..).unwrap_or_default()) {
130 Length::Value(len, len_bytes) => (len, len_bytes),
131 Length::Incomplete => return None,
132 Length::Invalid => {
133 self.drop_start();
134 continue;
135 }
136 };
137 let &slice = self.buffer.get(1 + len_bytes)?;
138 let min_len = if is_whole(slice) { WHOLE_HEADER } else { MIN_SLICE_LEN };
139 if len < min_len || len > MAX_LEN {
140 self.drop_start();
141 continue;
142 }
143
144 let total = 1 + len_bytes + len;
145 if self.buffer.len() < total {
146 return None;
147 }
148 let body: Vec<u8> = self.buffer.drain(..total).skip(1 + len_bytes).collect();
149 if let Some(frame) = self.body(&body) {
150 return Some(frame);
151 }
152 }
153 }
154
155 /// Drop a false frame start and search on.
156 fn drop_start(&mut self) {
157 self.buffer.drain(..1);
158 self.discarded += 1;
159 }
160
161 /// Spec: realme_t310.md#frame, #frame-slices
162 fn body(&mut self, body: &[u8]) -> Option<Frame> {
163 let mut r = Reader::new(body, "frame");
164 let slice = r.u8().ok()?;
165 let _zero = r.u8().ok()?;
166 if is_whole(slice) {
167 return parse_message(r.rest());
168 }
169 let _slice_number = r.u8().ok()?;
170 let part = r.rest();
171 match slice {
172 FIRST_SLICE => self.sliced = Some(part.to_vec()),
173 MIDDLE_SLICE => {
174 if let Some(joined) = &mut self.sliced {
175 joined.extend_from_slice(part);
176 }
177 }
178 LAST_SLICE => {
179 let mut joined = self.sliced.take()?;
180 joined.extend_from_slice(part);
181 return parse_message(&joined);
182 }
183 _ => {}
184 }
185 None
186 }
187}
188
189/// Spec: realme_t310.md#frame-whole, #frame-from-buds
190fn is_whole(slice: u8) -> bool {
191 slice == WHOLE || slice == WHOLE_FROM_BUDS
192}
193
194/// `cmd(2, LE) seq paylen(2, LE) payload`. A payload shorter than `paylen` is kept as it is.
195/// Spec: realme_t310.md#frame-receive
196fn parse_message(bytes: &[u8]) -> Option<Frame> {
197 let mut r = Reader::new(bytes, "message");
198 let cmd = r.u16_le().ok()?;
199 let seq = r.u8().ok()?;
200 let paylen = usize::from(r.u16_le().ok()?);
201 let rest = r.rest();
202 let payload = rest.get(..paylen).unwrap_or(rest).to_vec();
203 Some(Frame { cmd, seq, payload })
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209 use crate::transport::mock::hex;
210
211 fn frames(input: &[u8]) -> Vec<Frame> {
212 let mut r = Reassembler::default();
213 r.push(input);
214 std::iter::from_fn(|| r.next_frame()).collect()
215 }
216
217 fn frame(cmd: u16, seq: u8, payload: &str) -> Frame {
218 Frame {
219 cmd,
220 seq,
221 payload: hex(payload),
222 }
223 }
224
225 // Vectors: realme_t310.md#frame-whole and #frame-long.
226
227 #[test]
228 fn encodes_the_documented_examples() {
229 assert_eq!(encode(0x0100, 0x01, &[]).unwrap(), hex("AA 07 00 00 00 01 01 00 00"));
230 assert_eq!(
231 encode(0x0404, 0x06, &hex("01 01 01")).unwrap(),
232 hex("AA 0A 00 00 04 04 06 03 00 01 01 01")
233 );
234 }
235
236 #[test]
237 fn long_lengths_use_leb128() {
238 let payload = vec![0u8; 124];
239 let encoded = encode(0x0418, 0x03, &payload).unwrap();
240 assert_eq!(encoded.get(..3), Some(&hex("AA 83 01")[..]));
241 assert_eq!(
242 frames(&encoded),
243 vec![Frame {
244 cmd: 0x0418,
245 seq: 0x03,
246 payload
247 }]
248 );
249
250 let mut reply = hex("AA 81 02 00 00 22 81 05 FA 00");
251 reply.extend(vec![0x11; 250]);
252 let decoded = frames(&reply);
253 assert_eq!(decoded.len(), 1);
254 assert_eq!(decoded.first().map(|f| f.payload.len()), Some(250));
255 }
256
257 #[test]
258 fn refuses_frames_that_would_need_slicing() {
259 assert!(encode(0x0418, 1, &[0; 700]).is_err());
260 }
261
262 #[test]
263 fn round_trips_every_payload_size() {
264 for len in 0..=640 {
265 let payload: Vec<u8> = (0..len).map(|i| u8::try_from(i % 251).unwrap()).collect();
266 let encoded = encode(0x0106, 0x42, &payload).unwrap();
267 assert_eq!(
268 frames(&encoded),
269 vec![Frame {
270 cmd: 0x0106,
271 seq: 0x42,
272 payload
273 }]
274 );
275 }
276 }
277
278 #[test]
279 fn decodes_a_push() {
280 assert_eq!(
281 frames(&hex("AA 0C 00 00 04 02 FF 05 00 03 01 01 08 00")),
282 vec![frame(0x0204, 0xFF, "03 01 01 08 00")]
283 );
284 }
285
286 /// Captured on the owner's T310 (realme_t310.md#frame-from-buds): a reply and a request
287 /// from the buds in one read, then an ack and a battery push in another.
288 #[test]
289 fn decodes_frames_the_buds_start() {
290 assert_eq!(
291 frames(&hex(
292 "AA 10 00 00 00 81 01 09 00 00 BF 1F 5A 20 45 04 40 00 AA 07 08 00 01 05 0F 00 00"
293 )),
294 vec![
295 frame(0x8100, 0x01, "00 BF 1F 5A 20 45 04 40 00"),
296 frame(0x0501, 0x0F, "")
297 ]
298 );
299 assert_eq!(
300 frames(&hex(
301 "AA 11 00 00 05 82 04 0A 00 00 04 01 00 02 00 03 00 04 00 AA 0F 08 00 04 02 10 08 00 01 03 02 64 01 64 03 00"
302 )),
303 vec![
304 frame(0x8205, 0x04, "00 04 01 00 02 00 03 00 04 00"),
305 frame(0x0204, 0x10, "01 03 02 64 01 64 03 00")
306 ]
307 );
308 }
309
310 #[test]
311 fn splits_and_joins_reads() {
312 let two =
313 hex("AA 0D 00 00 04 02 FF 06 00 01 02 01 64 02 64 AA 0F 00 00 04 02 0C 08 00 02 03 01 07 02 07 03 04");
314 assert_eq!(
315 frames(&two),
316 vec![
317 frame(0x0204, 0xFF, "01 02 01 64 02 64"),
318 frame(0x0204, 0x0C, "02 03 01 07 02 07 03 04")
319 ]
320 );
321
322 // The same bytes, one at a time.
323 let mut r = Reassembler::default();
324 let mut out = Vec::new();
325 for byte in &two {
326 r.push(&[*byte]);
327 out.extend(std::iter::from_fn(|| r.next_frame()));
328 }
329 assert_eq!(out.len(), 2);
330 }
331
332 #[test]
333 fn skips_noise_and_bad_lengths() {
334 let real = "AA 0D 00 00 04 02 FF 06 00 01 02 01 64 02 64";
335 let mut r = Reassembler::default();
336 r.push(&hex(&format!("13 37 {real}")));
337 assert_eq!(r.next_frame(), Some(frame(0x0204, 0xFF, "01 02 01 64 02 64")));
338 assert_eq!(r.take_discarded(), 2);
339
340 assert_eq!(frames(&hex(&format!("AA 80 80 80 {real}"))).len(), 1);
341 assert!(frames(&hex("AA 00")).is_empty());
342 assert!(frames(&hex("AA 05 00 00 23 81 00")).is_empty());
343 }
344
345 #[test]
346 fn short_payload_is_cut_to_what_is_present() {
347 assert_eq!(
348 frames(&hex("AA 09 00 00 06 81 01 09 00 07 07")),
349 vec![frame(0x8106, 0x01, "07 07")]
350 );
351 }
352
353 #[test]
354 fn joins_slices_and_drops_orphans() {
355 // cmd 0x8108, seq 05, paylen 4, payload 00 01 02 03, split in three slices.
356 let sliced = hex("AA 07 01 00 00 08 81 05 04 AA 05 02 00 01 00 00 AA 06 03 00 02 01 02 03");
357 assert_eq!(frames(&sliced), vec![frame(0x8108, 0x05, "00 01 02 03")]);
358
359 // A last slice with no first slice is dropped; the next whole frame still decodes.
360 let orphan = hex("AA 06 03 00 02 00 02 03 AA 07 00 00 00 01 01 00 00");
361 assert_eq!(frames(&orphan), vec![frame(0x0100, 0x01, "")]);
362 }
363}