Krolyxon
Commit 52014a4

Branches

Refs →

budctlcratesbudctl-coresrcdevicesrealme_t310link.rs

221 lines · 8.4 KB · Rust

1//! Requests and replies over one link, for the OPPO/OnePlus/realme protocol family.
2//! Spec: realme_t310.md#seq, #buds-requests
3//!
4//! Holds no T310 tables. If a second model of this family is added, this file moves to
5//! `devices/oppo_family/` together with `frame.rs`.
6
7use std::time::{Duration, Instant};
8
9use super::frame::{self, Frame, Reassembler};
10use crate::debug;
11use crate::error::{Error, Result};
12use crate::transport::Transport;
13
14/// A reply's command id is the request's with this bit set. Spec: realme_t310.md#seq
15const REPLY: u16 = 0x8000;
16/// budctl's sequence numbers. Spec: realme_t310.md#seq
17const FIRST_SEQ: u8 = 0x01;
18/// Spec: realme_t310.md#seq
19const LAST_SEQ: u8 = 0xBF;
20/// At least this long between two frames budctl sends. Spec: realme_t310.md#init
21const SEND_GAP: Duration = Duration::from_millis(200);
22/// How long to wait for a reply.
23const REPLY_TIMEOUT: Duration = Duration::from_secs(3);
24
25/// Spec: realme_t310.md#time-request
26const TIME_REQUEST: u16 = 0x0500;
27/// Spec: realme_t310.md#buds-requests
28const APP_LIST_REQUEST: u16 = 0x050C;
29/// Spec: realme_t310.md#buds-requests
30const APP_LIST_ANSWER: &[u8] = &[0x00, 0x01, 0x00];
31/// Spec: realme_t310.md#buds-requests
32const CAPABILITIES_REQUEST: u16 = 0x051C;
33/// Spec: realme_t310.md#buds-requests
34const CAPABILITIES_ANSWER: &[u8] = &[0x00, 0x00, 0x00];
35/// Spec: realme_t310.md#buds-requests
36const SPATIAL_MODE_REQUEST: u16 = 0x051D;
37/// Spec: realme_t310.md#buds-requests
38const SPATIAL_MODE_ANSWER: &[u8] = &[0x01];
39/// Command groups the buds use; a request outside them gets `UNKNOWN_ANSWER`.
40/// Spec: realme_t310.md#seq, #buds-requests
41const KNOWN_GROUPS: [u16; 6] = [0x01, 0x02, 0x03, 0x04, 0x05, 0x0F];
42/// Spec: realme_t310.md#buds-requests
43const UNKNOWN_ANSWER: &[u8] = &[0x01];
44
45pub(super) struct Link {
46 transport: Box<dyn Transport>,
47 frames: Reassembler,
48 next_seq: u8,
49 last_send: Option<Instant>,
50 /// Unix time in seconds, for the buds' time request. Replaced in tests.
51 clock: fn() -> u32,
52}
53
54impl Link {
55 pub(super) fn new(transport: Box<dyn Transport>, clock: fn() -> u32) -> Self {
56 Self {
57 transport,
58 frames: Reassembler::default(),
59 next_seq: FIRST_SEQ,
60 last_send: None,
61 clock,
62 }
63 }
64
65 /// Handle whatever the buds send until `deadline`, answering their requests.
66 /// This is how budctl waits: never with a plain sleep.
67 pub(super) fn pump_until(&mut self, deadline: Instant) -> Result<()> {
68 while let Some(frame) = self.next_frame(deadline)? {
69 self.handle_unsolicited(frame)?;
70 }
71 Ok(())
72 }
73
74 /// Send a request and return the payload of its reply.
75 pub(super) fn request(&mut self, cmd: u16, payload: &[u8]) -> Result<Vec<u8>> {
76 let seq = self.send(cmd, payload)?;
77 let deadline = Instant::now() + REPLY_TIMEOUT;
78 loop {
79 match self.next_frame(deadline)? {
80 None => return Err(Error::Timeout(format!("no reply to command 0x{cmd:04X}"))),
81 Some(frame) if frame.cmd == cmd | REPLY && frame.seq == seq => return Ok(frame.payload),
82 Some(frame) => self.handle_unsolicited(frame)?,
83 }
84 }
85 }
86
87 /// Send a request without waiting for its reply. A reply that comes later is skipped.
88 pub(super) fn send(&mut self, cmd: u16, payload: &[u8]) -> Result<u8> {
89 if let Some(last) = self.last_send {
90 self.pump_until(last + SEND_GAP)?;
91 }
92 let seq = self.next_seq;
93 self.next_seq = if seq >= LAST_SEQ { FIRST_SEQ } else { seq + 1 };
94 debug::note(|| format!("send 0x{cmd:04X} seq {seq:02X} [{}]", debug::hex(payload)));
95 self.transport.send(&frame::encode(cmd, seq, payload)?)?;
96 self.last_send = Some(Instant::now());
97 Ok(seq)
98 }
99
100 fn next_frame(&mut self, deadline: Instant) -> Result<Option<Frame>> {
101 loop {
102 if let Some(frame) = self.frames.next_frame() {
103 debug::note(|| {
104 format!(
105 "recv 0x{:04X} seq {:02X} [{}]",
106 frame.cmd,
107 frame.seq,
108 debug::hex(&frame.payload)
109 )
110 });
111 return Ok(Some(frame));
112 }
113 let discarded = self.frames.take_discarded();
114 if discarded > 0 {
115 debug::note(|| format!("discarded {discarded} bytes that were not a frame"));
116 }
117 match self.transport.recv(deadline)? {
118 Some(bytes) => self.frames.push(&bytes),
119 None => return Ok(None),
120 }
121 }
122 }
123
124 /// A frame nobody is waiting for: answer it if the buds expect an answer, else skip it.
125 /// Spec: realme_t310.md#buds-requests, #pushes
126 fn handle_unsolicited(&mut self, frame: Frame) -> Result<()> {
127 if frame.cmd & REPLY != 0 {
128 debug::note(|| format!("skipping late reply 0x{:04X}", frame.cmd));
129 return Ok(());
130 }
131 let answer = match frame.cmd {
132 TIME_REQUEST => {
133 let mut answer = vec![0x00];
134 answer.extend((self.clock)().to_le_bytes());
135 answer
136 }
137 APP_LIST_REQUEST => APP_LIST_ANSWER.to_vec(),
138 CAPABILITIES_REQUEST => CAPABILITIES_ANSWER.to_vec(),
139 SPATIAL_MODE_REQUEST => SPATIAL_MODE_ANSWER.to_vec(),
140 cmd if !KNOWN_GROUPS.contains(&(cmd >> 8)) => UNKNOWN_ANSWER.to_vec(),
141 _ => return Ok(()), // pushes and messages that need no answer
142 };
143 debug::note(|| format!("answer 0x{:04X} with [{}]", frame.cmd, debug::hex(&answer)));
144 // Answered at once: some firmware drops the link when this takes too long.
145 self.transport
146 .send(&frame::encode(frame.cmd | REPLY, frame.seq, &answer)?)
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153 use crate::transport::mock::{Script, Step, hex};
154
155 fn fixed_clock() -> u32 {
156 1_750_000_000
157 }
158
159 #[test]
160 fn answers_the_time_request_while_waiting() {
161 // Vector: realme_t310.md#buds-requests
162 let script = Script::new(vec![
163 Step::Tx(hex("AA 07 00 00 06 01 01 00 00")),
164 Step::Rx(hex("AA 07 00 00 00 05 02 00 00")),
165 Step::Tx(hex("AA 0C 00 00 00 85 02 05 00 00 80 E1 4E 68")),
166 Step::Rx(hex("AA 0A 00 00 06 81 01 03 00 00 01 01")),
167 ]);
168 let mut link = Link::new(script.transport(), fixed_clock);
169 assert_eq!(link.request(0x0106, &[]).unwrap(), hex("00 01 01"));
170 script.assert_done();
171 }
172
173 #[test]
174 fn skips_pushes_and_stale_replies() {
175 let script = Script::new(vec![
176 Step::Tx(hex("AA 07 00 00 06 01 01 00 00")),
177 Step::Rx(hex("AA 0C 00 00 04 02 FF 05 00 03 01 01 08 00")),
178 Step::Rx(hex("AA 08 00 00 06 81 77 01 00 00")),
179 Step::Rx(hex("AA 08 00 00 06 81 01 01 00 00")),
180 ]);
181 let mut link = Link::new(script.transport(), fixed_clock);
182 assert_eq!(link.request(0x0106, &[]).unwrap(), hex("00"));
183 script.assert_done();
184 }
185
186 #[test]
187 fn unknown_requests_get_the_unknown_answer() {
188 let script = Script::new(vec![
189 Step::Rx(hex("AA 07 00 00 34 12 09 00 00")),
190 Step::Tx(hex("AA 08 00 00 34 92 09 01 00 01")),
191 ]);
192 let mut link = Link::new(script.transport(), fixed_clock);
193 link.pump_until(Instant::now()).unwrap();
194 script.assert_done();
195 }
196
197 #[test]
198 fn silence_is_a_timeout_and_a_drop_is_not_connected() {
199 let script = Script::new(vec![Step::Tx(hex("AA 07 00 00 06 01 01 00 00"))]);
200 let mut link = Link::new(script.transport(), fixed_clock);
201 assert!(matches!(link.request(0x0106, &[]), Err(Error::Timeout(_))));
202
203 let script = Script::new(vec![Step::Tx(hex("AA 07 00 00 06 01 01 00 00")), Step::Close]);
204 let mut link = Link::new(script.transport(), fixed_clock);
205 assert!(matches!(link.request(0x0106, &[]), Err(Error::NotConnected(_))));
206 }
207
208 #[test]
209 fn sequence_numbers_wrap_inside_budctls_range() {
210 let mut steps = Vec::new();
211 for seq in (FIRST_SEQ..=LAST_SEQ).chain([FIRST_SEQ]) {
212 steps.push(Step::Tx(frame::encode(0x0100, seq, &[]).unwrap()));
213 }
214 let script = Script::new(steps);
215 let mut link = Link::new(script.transport(), fixed_clock);
216 for _ in FIRST_SEQ..=LAST_SEQ + 1 {
217 link.send(0x0100, &[]).unwrap();
218 }
219 script.assert_done();
220 }
221}