1use crate::{
4 business::AuthenticatedPacket,
5 mmtls::{Longlink, Session},
6 wire, AccountState,
7};
8use anyhow::{ensure, Result};
9use std::collections::VecDeque;
10use std::time::{Duration, Instant};
11
12const REPLY_OFFSET: u32 = 1_000_000_000;
13const NOOP: u32 = 6;
14const NOOP_SEQUENCE: u32 = u32::MAX;
15const KEEPALIVE_INTERVAL: Duration = Duration::from_secs(30);
16const KEEPALIVE_TIMEOUT: Duration = Duration::from_secs(15);
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub struct Notification {
20 pub command: u32,
21 pub selector: u32,
22}
23
24fn notification(packet: &Longlink) -> Result<Option<Notification>> {
27 if packet.sequence != 0 {
28 return Ok(None);
29 }
30 if packet.command == 356 {
31 use crate::protobuf as p;
32 ensure!(
33 packet.payload.len() <= 128 * 1024,
34 "Contact hint exceeds limit"
35 );
36 let fields = p::decode(&packet.payload)?;
37 if p::scalar(&fields, 2)? != 11 {
38 return Ok(None);
39 }
40 let notice = p::decode(p::data(&fields, 3)?)?;
41 if p::scalar(¬ice, 1)? != 2048 {
42 return Ok(None);
43 }
44 let Some(raw_selectors) = p::optional_data(¬ice, 2)? else {
47 return Ok(None);
48 };
49 let selectors = p::decode(raw_selectors)?;
50 if p::optional(&selectors, 1)?.is_none() {
51 return Ok(None);
52 }
53 let selector = u32::try_from(p::scalar(&selectors, 1)?)?;
54 return Ok(
55 (selector == crate::contact_sync::CONTACT_SELECTOR as u32).then_some(Notification {
56 command: 356,
57 selector,
58 }),
59 );
60 }
61 if packet.command != 24 {
62 return Ok(None);
63 }
64 ensure!(packet.payload.len() == 4, "Invalid native push selector");
65 let selector = u32::from_be_bytes(packet.payload.as_slice().try_into()?);
66 Ok((selector & 7 != 0).then_some(Notification {
67 command: 24,
68 selector,
69 }))
70}
71
72#[derive(Default)]
73struct Inbox {
74 pending: VecDeque<Notification>,
75 noop_sent: Option<Instant>,
76}
77impl Inbox {
78 fn observe(&mut self, packet: &Longlink) -> Result<()> {
79 if packet.command == NOOP + REPLY_OFFSET && packet.sequence == NOOP_SEQUENCE {
80 ensure!(packet.payload.is_empty(), "Invalid keepalive reply");
81 self.noop_sent = None;
82 } else if let Some(next) = notification(packet)? {
83 match self
86 .pending
87 .iter_mut()
88 .find(|prior| prior.command == next.command)
89 {
90 Some(prior) => prior.selector |= next.selector,
91 None => self.pending.push_back(next),
92 }
93 }
94 Ok(())
95 }
96}
97
98pub struct Connection {
99 session: Session,
100 timeout: Duration,
101 sequence: u32,
102 last_keepalive: Instant,
103 inbox: Inbox,
104}
105impl Connection {
106 pub(crate) fn open(
107 state: &AccountState,
108 timeout: Duration,
109 receive_budget: usize,
110 ) -> Result<Self> {
111 state.validate()?;
112 Ok(Self {
113 session: Session::connect(&state.long_host, timeout, receive_budget)?,
114 timeout,
115 sequence: 1,
116 last_keepalive: Instant::now(),
117 inbox: Inbox::default(),
118 })
119 }
120 fn keepalive(&mut self) -> Result<()> {
121 let now = Instant::now();
122 ensure!(
123 self.inbox
124 .noop_sent
125 .is_none_or(|sent| now.duration_since(sent) < KEEPALIVE_TIMEOUT),
126 "Native keepalive timed out"
127 );
128 if now.duration_since(self.last_keepalive) >= KEEPALIVE_INTERVAL
129 && self.inbox.noop_sent.is_none()
130 {
131 self.session.send_application(NOOP, NOOP_SEQUENCE, &[])?;
132 self.inbox.noop_sent = Some(now);
133 self.last_keepalive = now;
134 }
135 Ok(())
136 }
137 fn rpc(
138 &mut self,
139 state: &mut AccountState,
140 cgi: u16,
141 command: u32,
142 plain: &[u8],
143 ) -> Result<Vec<u8>> {
144 self.keepalive()?;
145 let sequence = self.sequence;
146 self.sequence = self
147 .sequence
148 .checked_add(1)
149 .filter(|next| *next != NOOP_SEQUENCE)
150 .ok_or_else(|| anyhow::anyhow!("Longlink sequence exhausted"))?;
151 let packet = AuthenticatedPacket::prepare(state, cgi, plain)?;
152 self.session
153 .send_application(command, sequence, &packet.packet)?;
154 let deadline = Instant::now() + self.timeout;
155 for _ in 0..64 {
156 let remaining = deadline.saturating_duration_since(Instant::now());
157 ensure!(!remaining.is_zero(), "Native longlink response timed out");
158 let reply = self
159 .session
160 .poll_application(remaining)?
161 .ok_or_else(|| anyhow::anyhow!("Native longlink response timed out"))?;
162 if reply.sequence != sequence {
163 self.inbox.observe(&reply)?;
164 continue;
165 }
166 ensure!(
167 reply.command == command + REPLY_OFFSET,
168 "Unexpected longlink response command"
169 );
170 let (plain, cookie) = packet.decode_verified_transport_reply(&reply.payload)?;
171 if !cookie.is_empty() {
172 state.cookie = cookie;
173 }
174 return Ok(plain);
175 }
176 anyhow::bail!("Native longlink response frame limit")
177 }
178 pub fn heartbeat(
180 &mut self,
181 state: &mut AccountState,
182 cursor: &[u8],
183 ) -> Result<wire::Heartbeat> {
184 let request = wire::heartbeat_request(state, cursor)?;
185 wire::decode_heartbeat(&self.rpc(state, 518, 238, &request)?)
186 }
187 pub fn sync(
188 &mut self,
189 state: &mut AccountState,
190 cursor: &[u8],
191 scene: u32,
192 ) -> Result<wire::SyncBatch> {
193 ensure!([1, 6, 7].contains(&scene), "Unsupported sync scene");
194 let sent = if cursor.is_empty() { &[0][..] } else { cursor };
195 let request = wire::sync_request(sent, &state.device.device_type, scene)?;
196 wire::decode_sync(&self.rpc(state, 138, 121, &request)?, cursor)
197 }
198 pub fn poll(&mut self, timeout: Duration) -> Result<Option<Notification>> {
201 self.keepalive()?;
202 if !self.inbox.pending.is_empty() {
203 return Ok(self.inbox.pending.pop_front());
204 }
205 let deadline = Instant::now() + timeout;
206 for _ in 0..64 {
207 let remaining = deadline.saturating_duration_since(Instant::now());
208 if remaining.is_zero() {
209 return Ok(None);
210 }
211 let Some(packet) = self.session.poll_application(remaining)? else {
212 return Ok(None);
213 };
214 self.inbox.observe(&packet)?;
215 if !self.inbox.pending.is_empty() {
216 return Ok(self.inbox.pending.pop_front());
217 }
218 }
219 anyhow::bail!("Native notification frame limit")
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226 fn push(sequence: u32, selector: &[u8]) -> Longlink {
227 Longlink {
228 command: 24,
229 sequence,
230 payload: selector.to_vec(),
231 }
232 }
233 #[test]
234 fn selectors_are_strict_big_endian_work_hints() {
235 assert_eq!(
236 notification(&push(0, &6u32.to_be_bytes())).unwrap(),
237 Some(Notification {
238 command: 24,
239 selector: 6
240 })
241 );
242 assert!(notification(&push(0, &8u32.to_be_bytes()))
243 .unwrap()
244 .is_none());
245 assert!(notification(&push(12, &6u32.to_be_bytes()))
246 .unwrap()
247 .is_none());
248 for invalid in [&[][..], &[6][..], &[0, 0, 6][..], &[0; 5][..]] {
249 assert!(notification(&push(0, invalid)).is_err());
250 }
251 }
252 #[test]
253 fn unmapped_contact_notices_without_selectors_leave_the_inbox_intact() {
254 use crate::protobuf as p;
255 let mut inbox = Inbox::default();
256 inbox.observe(&push(0, &2u32.to_be_bytes())).unwrap();
257 for notice in [
258 p::uint(1, 2048),
259 p::message(&[p::uint(1, 2048), p::bytes(2, b"")]).unwrap(),
260 ] {
261 inbox
262 .observe(&Longlink {
263 command: 356,
264 sequence: 0,
265 payload: p::message(&[p::uint(2, 11), p::bytes(3, ¬ice)]).unwrap(),
266 })
267 .unwrap();
268 }
269 assert_eq!(
270 inbox.pending.pop_front().unwrap(),
271 Notification {
272 command: 24,
273 selector: 2
274 }
275 );
276 assert!(inbox.pending.is_empty());
277 }
278 #[test]
279 fn contact_hint_coalesces_separately_from_message_work() {
280 use crate::protobuf as p;
281 let hint = Longlink {
282 command: 356,
283 sequence: 0,
284 payload: p::message(&[
285 p::bytes(1, b"opaque"),
286 p::uint(2, 11),
287 p::bytes(
288 3,
289 &p::message(&[
290 p::uint(1, 2048),
291 p::bytes(2, &p::uint(1, crate::contact_sync::CONTACT_SELECTOR)),
292 ])
293 .unwrap(),
294 ),
295 ])
296 .unwrap(),
297 };
298 let mut inbox = Inbox::default();
299 inbox.observe(&push(0, &2u32.to_be_bytes())).unwrap();
300 inbox.observe(&hint).unwrap();
301 inbox.observe(&push(0, &4u32.to_be_bytes())).unwrap();
302 inbox.observe(&hint).unwrap();
303 assert_eq!(inbox.pending.len(), 2);
304 assert_eq!(
305 inbox.pending.pop_front().unwrap(),
306 Notification {
307 command: 24,
308 selector: 6
309 }
310 );
311 assert_eq!(
312 inbox.pending.pop_front().unwrap(),
313 Notification {
314 command: 356,
315 selector: 0x20004
316 }
317 );
318 let mut malformed = hint;
319 malformed.payload = p::message(&[p::uint(2, 11), p::bytes(3, b"invalid")]).unwrap();
320 assert!(notification(&malformed).is_err());
321 }
322 #[test]
323 fn interleaved_pushes_coalesce_without_confusing_keepalive_or_unknown_commands() {
324 let mut inbox = Inbox {
325 noop_sent: Some(Instant::now()),
326 ..Default::default()
327 };
328 inbox.observe(&push(0, &2u32.to_be_bytes())).unwrap();
329 inbox
330 .observe(&Longlink {
331 command: REPLY_OFFSET + NOOP,
332 sequence: NOOP_SEQUENCE,
333 payload: vec![],
334 })
335 .unwrap();
336 inbox
337 .observe(&Longlink {
338 command: 912,
339 sequence: 0,
340 payload: vec![1, 2, 3],
341 })
342 .unwrap();
343 inbox.observe(&push(0, &4u32.to_be_bytes())).unwrap();
344 assert!(inbox.noop_sent.is_none());
345 assert_eq!(inbox.pending.pop_front().unwrap().selector, 6);
346 assert!(inbox.pending.is_empty());
347 assert!(inbox
348 .observe(&Longlink {
349 command: REPLY_OFFSET + NOOP,
350 sequence: NOOP_SEQUENCE,
351 payload: vec![1]
352 })
353 .is_err());
354 }
355}