1use crate::{protobuf as p, wire, AccountState, NativeClient};
4use anyhow::{bail, ensure, Result};
5use serde::{Deserialize, Serialize};
6use std::collections::HashSet;
7
8pub const CONTACT_SELECTOR: u64 = 0x20004;
9
10#[derive(Clone, Serialize, Deserialize)]
11pub struct ContactSyncBatch {
12 pub cursor: Vec<u8>,
13 pub continuation: Vec<u64>,
14 pub commands: Vec<wire::SyncCommand>,
15 pub raw: Vec<u8>,
16}
17
18pub fn validate_cursor(input: &[u8]) -> Result<()> {
21 ensure!(input.len() <= 65536, "Contact cursor exceeds limit");
22 let fields = p::decode(input)?;
23 let mut keys = HashSet::new();
24 for field in fields.iter().filter(|field| field.number == 1) {
25 let p::Value::Bytes(raw) = &field.value else {
26 bail!("Invalid contact cursor pair");
27 };
28 let pair = p::decode(raw)?;
29 let key = u32::try_from(p::scalar(&pair, 1)?)?;
30 p::scalar(&pair, 2)?;
31 ensure!(
32 keys.insert(key) && keys.len() <= 256,
33 "Invalid contact cursor keys"
34 );
35 }
36 Ok(())
37}
38
39pub fn request(
40 state: &AccountState,
41 cursor: &[u8],
42 message_cursor: &[u8],
43 continuation: bool,
44) -> Result<Vec<u8>> {
45 validate_cursor(cursor)?;
46 ensure!(
47 message_cursor.len() <= 65536,
48 "Message cursor exceeds limit"
49 );
50 ensure!(
51 !cursor.is_empty() || !message_cursor.is_empty(),
52 "Contact sync has no cursor context"
53 );
54 let mut fields = vec![
57 p::bytes(1, &wire::base_request(&state.device, state.uin, 0)?),
58 p::uint(2, if continuation { 4 } else { 3 }),
59 p::uint(3, CONTACT_SELECTOR),
60 p::bytes(4, cursor),
61 ];
62 fields.push(p::bytes(5, message_cursor));
63 p::message(&fields)
64}
65
66pub fn decode(input: &[u8]) -> Result<ContactSyncBatch> {
67 ensure!(
68 input.len() <= 16 * 1024 * 1024,
69 "Contact response exceeds limit"
70 );
71 let fields = p::decode(input)?;
72 let code = p::base_code(&fields)?;
73 ensure!(code == 0, "Contact sync service returned {code}");
74 let cursor = p::data(&fields, 3)?.to_vec();
75 validate_cursor(&cursor)?;
76 let mut continuation = Vec::new();
77 let mut commands = Vec::new();
78 for field in fields {
79 match (field.number, field.value) {
80 (2, p::Value::Varint(value)) => {
81 continuation.push(value);
82 ensure!(
83 continuation.len() <= 64,
84 "Contact continuation exceeds limit"
85 );
86 }
87 (4, p::Value::Bytes(raw)) => {
88 let item = p::decode(&raw)?;
89 commands.push(wire::SyncCommand {
91 id: i32::try_from(p::scalar(&item, 1)?)?,
92 payload: p::data(&item, 2)?.to_vec(),
93 });
94 ensure!(commands.len() <= 10000, "Contact command limit");
95 }
96 (2 | 4, _) => bail!("Invalid contact response field type"),
97 _ => {}
98 }
99 }
100 Ok(ContactSyncBatch {
101 cursor,
102 continuation,
103 commands,
104 raw: input.to_vec(),
105 })
106}
107
108impl NativeClient {
109 pub fn contact_sync(
110 &self,
111 state: &mut AccountState,
112 cursor: &[u8],
113 message_cursor: &[u8],
114 continuation: bool,
115 ) -> Result<ContactSyncBatch> {
116 let request = request(state, cursor, message_cursor, continuation)?;
117 decode(&self.authenticated_rpc(
118 state,
119 8674,
120 "/cgi-bin/micromsg-bin/contactsync",
121 &request,
122 )?)
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 fn pair(key: u64, value: u64) -> Vec<u8> {
130 p::bytes(
131 1,
132 &p::message(&[p::uint(1, key), p::uint(2, value)]).unwrap(),
133 )
134 }
135 #[test]
136 fn cursor_values_keep_u64_and_reject_duplicate_or_malformed_keys() {
137 validate_cursor(&pair(11, u64::MAX)).unwrap();
138 assert!(validate_cursor(&[pair(11, 1), pair(11, 2)].concat()).is_err());
139 assert!(validate_cursor(&p::uint(1, 11)).is_err());
140 assert!(validate_cursor(&pair(u32::MAX as u64 + 1, 0)).is_err());
141 }
142 #[test]
143 fn final_and_continuing_pages_retain_unknown_commands_and_raw_bytes() {
144 for flags in [vec![0], vec![0, CONTACT_SELECTOR]] {
145 let mut fields = vec![
146 p::bytes(1, &p::message(&[p::int32(1, 0)]).unwrap()),
147 p::bytes(3, &pair(11, 7)),
148 p::bytes(
149 4,
150 &p::message(&[p::uint(1, 912), p::bytes(2, b"unknown")]).unwrap(),
151 ),
152 ];
153 fields.extend(flags.iter().map(|value| p::uint(2, *value)));
154 let raw = p::message(&fields).unwrap();
155 let batch = decode(&raw).unwrap();
156 assert_eq!(batch.continuation, flags);
157 assert_eq!(batch.commands[0].id, 912);
158 assert_eq!(batch.commands[0].payload, b"unknown");
159 assert_eq!(batch.raw, raw);
160 }
161 }
162}