| 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 | |
| 7 | use std::time::{Duration, Instant}; |
| 8 | |
| 9 | use super::frame::{self, Frame, Reassembler}; |
| 10 | use crate::debug; |
| 11 | use crate::error::{Error, Result}; |
| 12 | use crate::transport::Transport; |
| 13 | |
| 14 | /// A reply's command id is the request's with this bit set. Spec: realme_t310.md#seq |
| 15 | const REPLY: u16 = 0x8000; |
| 16 | /// budctl's sequence numbers. Spec: realme_t310.md#seq |
| 17 | const FIRST_SEQ: u8 = 0x01; |
| 18 | /// Spec: realme_t310.md#seq |
| 19 | const LAST_SEQ: u8 = 0xBF; |
| 20 | /// At least this long between two frames budctl sends. Spec: realme_t310.md#init |
| 21 | const SEND_GAP: Duration = Duration::from_millis(200); |
| 22 | /// How long to wait for a reply. |
| 23 | const REPLY_TIMEOUT: Duration = Duration::from_secs(3); |
| 24 | |
| 25 | /// Spec: realme_t310.md#time-request |
| 26 | const TIME_REQUEST: u16 = 0x0500; |
| 27 | /// Spec: realme_t310.md#buds-requests |
| 28 | const APP_LIST_REQUEST: u16 = 0x050C; |
| 29 | /// Spec: realme_t310.md#buds-requests |
| 30 | const APP_LIST_ANSWER: &[u8] = &[0x00, 0x01, 0x00]; |
| 31 | /// Spec: realme_t310.md#buds-requests |
| 32 | const CAPABILITIES_REQUEST: u16 = 0x051C; |
| 33 | /// Spec: realme_t310.md#buds-requests |
| 34 | const CAPABILITIES_ANSWER: &[u8] = &[0x00, 0x00, 0x00]; |
| 35 | /// Spec: realme_t310.md#buds-requests |
| 36 | const SPATIAL_MODE_REQUEST: u16 = 0x051D; |
| 37 | /// Spec: realme_t310.md#buds-requests |
| 38 | const 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 |
| 41 | const KNOWN_GROUPS: [u16; 6] = [0x01, 0x02, 0x03, 0x04, 0x05, 0x0F]; |
| 42 | /// Spec: realme_t310.md#buds-requests |
| 43 | const UNKNOWN_ANSWER: &[u8] = &[0x01]; |
| 44 | |
| 45 | pub(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 | |
| 54 | impl 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)] |
| 151 | mod 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 | } |