Krolyxon
Commit 4c49f6c

Branches

Refs →

budctlcratesbudctl-coresrcdevicesrealme_t310link.rs

233 lines · 9.0 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 /// Like [`Self::pump_until`], but stop at the first frame `wanted` picks and return what it
75 /// returned. `None` when `deadline` passes first.
76 pub(super) fn wait_for<T>(&mut self, deadline: Instant, wanted: impl Fn(&Frame) -> Option<T>) -> Result<Option<T>> {
77 while let Some(frame) = self.next_frame(deadline)? {
78 if let Some(found) = wanted(&frame) {
79 return Ok(Some(found));
80 }
81 self.handle_unsolicited(frame)?;
82 }
83 Ok(None)
84 }
85
86 /// Send a request and return the payload of its reply.
87 pub(super) fn request(&mut self, cmd: u16, payload: &[u8]) -> Result<Vec<u8>> {
88 let seq = self.send(cmd, payload)?;
89 let deadline = Instant::now() + REPLY_TIMEOUT;
90 loop {
91 match self.next_frame(deadline)? {
92 None => return Err(Error::Timeout(format!("no reply to command 0x{cmd:04X}"))),
93 Some(frame) if frame.cmd == cmd | REPLY && frame.seq == seq => return Ok(frame.payload),
94 Some(frame) => self.handle_unsolicited(frame)?,
95 }
96 }
97 }
98
99 /// Send a request without waiting for its reply. A reply that comes later is skipped.
100 pub(super) fn send(&mut self, cmd: u16, payload: &[u8]) -> Result<u8> {
101 if let Some(last) = self.last_send {
102 self.pump_until(last + SEND_GAP)?;
103 }
104 let seq = self.next_seq;
105 self.next_seq = if seq >= LAST_SEQ { FIRST_SEQ } else { seq + 1 };
106 debug::note(|| format!("send 0x{cmd:04X} seq {seq:02X} [{}]", debug::hex(payload)));
107 self.transport.send(&frame::encode(cmd, seq, payload)?)?;
108 self.last_send = Some(Instant::now());
109 Ok(seq)
110 }
111
112 fn next_frame(&mut self, deadline: Instant) -> Result<Option<Frame>> {
113 loop {
114 if let Some(frame) = self.frames.next_frame() {
115 debug::note(|| {
116 format!(
117 "recv 0x{:04X} seq {:02X} [{}]",
118 frame.cmd,
119 frame.seq,
120 debug::hex(&frame.payload)
121 )
122 });
123 return Ok(Some(frame));
124 }
125 let discarded = self.frames.take_discarded();
126 if discarded > 0 {
127 debug::note(|| format!("discarded {discarded} bytes that were not a frame"));
128 }
129 match self.transport.recv(deadline)? {
130 Some(bytes) => self.frames.push(&bytes),
131 None => return Ok(None),
132 }
133 }
134 }
135
136 /// A frame nobody is waiting for: answer it if the buds expect an answer, else skip it.
137 /// Spec: realme_t310.md#buds-requests, #pushes
138 fn handle_unsolicited(&mut self, frame: Frame) -> Result<()> {
139 if frame.cmd & REPLY != 0 {
140 debug::note(|| format!("skipping late reply 0x{:04X}", frame.cmd));
141 return Ok(());
142 }
143 let answer = match frame.cmd {
144 TIME_REQUEST => {
145 let mut answer = vec![0x00];
146 answer.extend((self.clock)().to_le_bytes());
147 answer
148 }
149 APP_LIST_REQUEST => APP_LIST_ANSWER.to_vec(),
150 CAPABILITIES_REQUEST => CAPABILITIES_ANSWER.to_vec(),
151 SPATIAL_MODE_REQUEST => SPATIAL_MODE_ANSWER.to_vec(),
152 cmd if !KNOWN_GROUPS.contains(&(cmd >> 8)) => UNKNOWN_ANSWER.to_vec(),
153 _ => return Ok(()), // pushes and messages that need no answer
154 };
155 debug::note(|| format!("answer 0x{:04X} with [{}]", frame.cmd, debug::hex(&answer)));
156 // Answered at once: some firmware drops the link when this takes too long.
157 self.transport
158 .send(&frame::encode(frame.cmd | REPLY, frame.seq, &answer)?)
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165 use crate::transport::mock::{Script, Step, hex};
166
167 fn fixed_clock() -> u32 {
168 1_750_000_000
169 }
170
171 #[test]
172 fn answers_the_time_request_while_waiting() {
173 // Vector: realme_t310.md#buds-requests
174 let script = Script::new(vec![
175 Step::Tx(hex("AA 07 00 00 06 01 01 00 00")),
176 Step::Rx(hex("AA 07 00 00 00 05 02 00 00")),
177 Step::Tx(hex("AA 0C 00 00 00 85 02 05 00 00 80 E1 4E 68")),
178 Step::Rx(hex("AA 0A 00 00 06 81 01 03 00 00 01 01")),
179 ]);
180 let mut link = Link::new(script.transport(), fixed_clock);
181 assert_eq!(link.request(0x0106, &[]).unwrap(), hex("00 01 01"));
182 script.assert_done();
183 }
184
185 #[test]
186 fn skips_pushes_and_stale_replies() {
187 let script = Script::new(vec![
188 Step::Tx(hex("AA 07 00 00 06 01 01 00 00")),
189 Step::Rx(hex("AA 0C 00 00 04 02 FF 05 00 03 01 01 08 00")),
190 Step::Rx(hex("AA 08 00 00 06 81 77 01 00 00")),
191 Step::Rx(hex("AA 08 00 00 06 81 01 01 00 00")),
192 ]);
193 let mut link = Link::new(script.transport(), fixed_clock);
194 assert_eq!(link.request(0x0106, &[]).unwrap(), hex("00"));
195 script.assert_done();
196 }
197
198 #[test]
199 fn unknown_requests_get_the_unknown_answer() {
200 let script = Script::new(vec![
201 Step::Rx(hex("AA 07 00 00 34 12 09 00 00")),
202 Step::Tx(hex("AA 08 00 00 34 92 09 01 00 01")),
203 ]);
204 let mut link = Link::new(script.transport(), fixed_clock);
205 link.pump_until(Instant::now()).unwrap();
206 script.assert_done();
207 }
208
209 #[test]
210 fn silence_is_a_timeout_and_a_drop_is_not_connected() {
211 let script = Script::new(vec![Step::Tx(hex("AA 07 00 00 06 01 01 00 00"))]);
212 let mut link = Link::new(script.transport(), fixed_clock);
213 assert!(matches!(link.request(0x0106, &[]), Err(Error::Timeout(_))));
214
215 let script = Script::new(vec![Step::Tx(hex("AA 07 00 00 06 01 01 00 00")), Step::Close]);
216 let mut link = Link::new(script.transport(), fixed_clock);
217 assert!(matches!(link.request(0x0106, &[]), Err(Error::NotConnected(_))));
218 }
219
220 #[test]
221 fn sequence_numbers_wrap_inside_budctls_range() {
222 let mut steps = Vec::new();
223 for seq in (FIRST_SEQ..=LAST_SEQ).chain([FIRST_SEQ]) {
224 steps.push(Step::Tx(frame::encode(0x0100, seq, &[]).unwrap()));
225 }
226 let script = Script::new(steps);
227 let mut link = Link::new(script.transport(), fixed_clock);
228 for _ in FIRST_SEQ..=LAST_SEQ + 1 {
229 link.send(0x0100, &[]).unwrap();
230 }
231 script.assert_done();
232 }
233}