1use crate::{crypto::random, protobuf as p, AccountState, DeviceProfile};
3use anyhow::{bail, ensure, Result};
4use serde::{Deserialize, Serialize};
5use std::{
6 collections::{BTreeMap, HashSet},
7 time::{SystemTime, UNIX_EPOCH},
8};
9
10pub fn unix_time() -> Result<u32> {
11 Ok(u32::try_from(
12 SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(),
13 )?)
14}
15fn text(value: &str, max: usize, empty: bool) -> Result<&[u8]> {
16 ensure!(
17 (empty || !value.is_empty()) && !value.contains('\0') && value.len() <= max,
18 "Invalid text input"
19 );
20 Ok(value.as_bytes())
21}
22pub fn base_request(profile: &DeviceProfile, uin: u32, task: u32) -> Result<Vec<u8>> {
23 profile.validate()?;
24 p::message(&[
25 p::bytes(1, &[0]),
26 p::uint(2, uin as u64),
27 p::bytes(3, &profile.device_id),
28 p::int32(4, profile.client_version),
29 p::bytes(5, text(&profile.device_type, 1024, false)?),
30 p::uint(6, task as u64),
31 ])
32}
33pub fn qr_request(profile: &DeviceProfile, computer_name: &str) -> Result<Vec<u8>> {
34 p::message(&[
35 p::bytes(1, &base_request(profile, 0, 0)?),
36 p::bytes(2, &p::buffer(&random::<16>()?)),
37 p::uint(3, 0),
38 p::bytes(4, text(computer_name, 255, false)?),
39 ])
40}
41pub fn poll_request(profile: &DeviceProfile, identifier: &[u8]) -> Result<Vec<u8>> {
42 ensure!(
43 !identifier.is_empty() && identifier.len() <= 4096,
44 "Invalid login identifier"
45 );
46 let ms = SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis();
47 let timestamp = ((ms & 0xffffffff) / 1000) as u64;
48 p::message(&[
49 p::bytes(1, &base_request(profile, 0, 0)?),
50 p::bytes(2, &p::buffer(&random::<16>()?)),
51 p::bytes(3, identifier),
52 p::uint(4, timestamp),
53 p::uint(5, 0),
54 ])
55}
56pub struct QrResponse {
57 pub image: Vec<u8>,
58 pub identifier: Vec<u8>,
59 pub notify_key: Vec<u8>,
60 pub width: u32,
61 pub height: u32,
62}
63pub fn decode_qr(input: &[u8]) -> Result<QrResponse> {
64 let f = p::decode(input)?;
65 let code = p::base_code(&f)?;
66 ensure!(code == 0, "QR service returned {code}");
67 let image = p::unbuffer(p::data(&f, 2)?)?;
68 let identifier = p::data(&f, 3)?.to_vec();
69 p::scalar(&f, 4)?;
70 let notify_key = p::unbuffer(p::data(&f, 5)?)?;
71 ensure!(
72 !identifier.is_empty()
73 && identifier.len() <= 4096
74 && !notify_key.is_empty()
75 && notify_key.len() <= 1024,
76 "Invalid QR state"
77 );
78 ensure!(
79 image.len() >= 33 && image[..8] == *b"\x89PNG\r\n\x1a\n" && image[12..16] == *b"IHDR",
80 "Invalid QR image"
81 );
82 let width = u32::from_be_bytes(image[16..20].try_into()?);
83 let height = u32::from_be_bytes(image[20..24].try_into()?);
84 ensure!(
85 (21..=4096).contains(&width) && (21..=4096).contains(&height),
86 "QR image dimensions invalid"
87 );
88 Ok(QrResponse {
89 image,
90 identifier,
91 notify_key,
92 width,
93 height,
94 })
95}
96pub struct PollResponse {
97 pub status: Option<u32>,
98 pub credential3: Option<Vec<u8>>,
99 pub credential4: Option<Vec<u8>>,
100 pub lan_challenge: bool,
101}
102pub fn decode_poll(input: &[u8], notify_key: &[u8]) -> Result<PollResponse> {
103 let f = p::decode(input)?;
104 let code = p::base_code(&f)?;
105 ensure!(code == 0, "Login poll returned {code}");
106 let Some(pkg) = p::optional_data(&f, 3)? else {
107 return Ok(PollResponse {
108 status: None,
109 credential3: None,
110 credential4: None,
111 lan_challenge: false,
112 });
113 };
114 let pkg = p::decode(pkg)?;
115 let ciphertext = p::unbuffer(p::data(&pkg, 1)?)?;
116 p::scalar(&pkg, 2)?;
117 let plain = crate::crypto::cbc(&ciphertext, notify_key, openssl::symm::Mode::Decrypt)?;
118 let notify = p::decode(&plain)?;
119 let lan_challenge = match p::optional_data(¬ify, 17)? {
122 Some(buffer) => {
123 ensure!(buffer.len() <= 4096, "Login risk extension too large");
124 let extension = p::unbuffer(buffer)?;
125 let extension = p::decode(&extension)?;
126 match p::optional(&extension, 1)? {
127 Some(p::Value::Varint(state)) => *state == 11,
128 None => false,
129 _ => bail!("Invalid login risk state"),
130 }
131 }
132 None => false,
133 };
134 let status = u32::try_from(p::scalar(¬ify, 2)?)?;
135 let credential3 = p::optional_data(¬ify, 3)?.map(Vec::from);
136 let credential4 = p::optional_data(¬ify, 4)?.map(Vec::from);
137 if status == 2 && !lan_challenge {
138 ensure!(
139 credential3
140 .as_ref()
141 .is_some_and(|b| !b.is_empty() && b.len() <= 65536)
142 && credential4
143 .as_ref()
144 .is_some_and(|b| !b.is_empty() && b.len() <= 65536),
145 "Approved login lacks credentials"
146 );
147 }
148 Ok(PollResponse {
149 status: Some(status),
150 credential3,
151 credential4,
152 lan_challenge,
153 })
154}
155pub fn manual_auth(
156 profile: &DeviceProfile,
157 computer_name: &str,
158 c3: &[u8],
159 c4: &[u8],
160 point: &[u8],
161 key_info: &[u8],
162) -> Result<Vec<u8>> {
163 ensure!(
164 !c3.is_empty()
165 && !c4.is_empty()
166 && c3.len() <= 65536
167 && c4.len() <= 65536
168 && point.len() == 57
169 && point[0] == 4
170 && key_info.len() <= 1024 * 1024,
171 "Invalid manual auth inputs"
172 );
173 let rsa = p::message(&[
174 p::bytes(1, &p::buffer(&random::<16>()?)),
175 p::bytes(
176 2,
177 &p::message(&[p::int32(1, 713), p::bytes(2, &p::buffer(point))])?,
178 ),
179 p::bytes(3, c3),
180 p::bytes(4, c4),
181 p::bytes(5, &[]),
182 ])?;
183 let wt = p::message(&[p::bytes(1, &[]), p::bytes(4, &p::buffer(&[0]))])?;
184 let mut base = vec![p::bytes(2, &wt)];
185 if !key_info.is_empty() {
186 base.push(p::bytes(5, &p::buffer(key_info)));
187 }
188 let aes = p::message(&[
189 p::bytes(1, &base_request(profile, 0, 1)?),
190 p::bytes(2, &p::message(&base)?),
191 p::uint(5, 0),
192 p::bytes(8, text(computer_name, 255, false)?),
193 p::bytes(9, profile.device_type.as_bytes()),
194 p::bytes(11, &[]),
195 p::int32(13, 1000),
196 p::bytes(19, b"0"),
197 p::uint(22, 2),
198 ])?;
199 p::message(&[p::bytes(1, &rsa), p::bytes(2, &aes)])
200}
201pub fn auto_auth(state: &AccountState, point: &[u8], computer_name: &str) -> Result<Vec<u8>> {
202 state.validate()?;
203 ensure!(point.len() == 57 && point[0] == 4, "Invalid P224 point");
204 let token = p::decode(&state.auto_auth_key)?;
205 let part1 = p::unbuffer(p::data(&token, 1)?)?;
206 let part2 = p::unbuffer(p::data(&token, 2)?)?;
207 ensure!(
208 part1.len() == 32 && !part2.is_empty(),
209 "Unsupported saved auto-auth token"
210 );
211 let wt = p::message(&[p::bytes(4, &p::buffer(&[0]))])?;
212 let mut base_auth = vec![p::bytes(2, &wt)];
213 if !state.key_info.is_empty() {
214 base_auth.push(p::bytes(5, &p::buffer(&state.key_info)));
215 }
216 let aes = p::message(&[
217 p::bytes(1, &base_request(&state.device, state.uin, 12)?),
218 p::bytes(2, &p::message(&base_auth)?),
219 p::bytes(3, &p::buffer(&state.auto_auth_key)),
220 p::uint(6, 0),
221 p::bytes(9, text(computer_name, 255, true)?),
222 p::bytes(10, state.device.device_type.as_bytes()),
223 p::bytes(11, b"en"),
224 p::bytes(12, &[]),
225 p::bytes(19, state.device.device_type.as_bytes()),
226 ])?;
227 let rsa = p::message(&[
228 p::bytes(2, &p::buffer(&part1)),
229 p::bytes(
230 3,
231 &p::message(&[p::int32(1, 713), p::bytes(2, &p::buffer(point))])?,
232 ),
233 ])?;
234 p::message(&[p::bytes(1, &rsa), p::bytes(2, &aes)])
235}
236pub fn push_login(
237 profile: &DeviceProfile,
238 username: &str,
239 token: &[u8],
240 public_key: &[u8],
241) -> Result<Vec<u8>> {
242 ensure!(
243 !token.is_empty() && token.len() <= 1024 * 1024,
244 "Missing saved token"
245 );
246 p::message(&[
247 p::bytes(1, &base_request(profile, 0, 0)?),
248 p::bytes(2, text(username, 128, false)?),
249 p::bytes(4, &random::<32>()?),
250 p::bytes(5, &p::buffer(&random::<16>()?)),
251 p::uint(6, 3),
252 p::bytes(8, &p::buffer(token)),
253 p::bytes(10, &p::buffer(public_key)),
254 p::uint(12, 1),
255 p::bytes(13, b"en"),
256 p::uint(14, 0),
257 p::uint(15, 0),
258 ])
259}
260pub fn decode_push(input: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
261 let f = p::decode(input)?;
262 let code = p::base_code(&f)?;
263 ensure!(code == 0, "Saved-device login returned {code}");
264 let id = p::data(&f, 2)?.to_vec();
265 let key = p::unbuffer(p::data(&f, 3)?)?;
266 ensure!(
267 id.len() == 20 && key.len() == 16,
268 "Unsupported saved-device login state"
269 );
270 Ok((id, key))
271}
272
273#[derive(Clone, Serialize, Deserialize)]
274pub struct SyncCommand {
275 pub id: i32,
276 pub payload: Vec<u8>,
277}
278#[derive(Clone, Serialize, Deserialize)]
279pub struct SyncBatch {
280 pub code: i32,
281 pub flags: u32,
282 pub cursor: Vec<u8>,
283 pub commands: Vec<SyncCommand>,
284 pub continuation: bool,
285}
286pub fn sync_request(cursor: &[u8], device_type: &str, scene: u32) -> Result<Vec<u8>> {
287 ensure!(
288 !cursor.is_empty() && cursor.len() <= 65536,
289 "Invalid sync cursor"
290 );
291 p::message(&[
292 p::bytes(1, &p::uint(1, 0)),
293 p::uint(2, 7),
294 p::bytes(3, &p::buffer(cursor)),
295 p::uint(4, scene as u64),
296 p::bytes(5, text(device_type, 1024, false)?),
297 p::uint(9, 0),
298 ])
299}
300pub fn decode_sync(input: &[u8], prior: &[u8]) -> Result<SyncBatch> {
301 let f = p::decode(input)?;
302 let code = p::scalar(&f, 1)? as u32 as i32;
303 ensure!(code == 0 || code == -2006, "Sync service returned {code}");
304 let list = p::decode(p::data(&f, 2)?)?;
305 let flags = u32::try_from(p::scalar(&f, 3)?)?;
306 let incoming = p::unbuffer(p::data(&f, 4)?)?;
307 if !incoming.is_empty() {
308 decode_cursor(&incoming)?;
309 }
310 let mut commands = vec![];
311 for field in list.iter().filter(|f| f.number == 2) {
312 let p::Value::Bytes(v) = &field.value else {
313 bail!("Invalid sync command");
314 };
315 let item = p::decode(v)?;
316 commands.push(SyncCommand {
317 id: p::scalar(&item, 1)? as u32 as i32,
318 payload: p::unbuffer(p::data(&item, 2)?)?,
319 });
320 }
321 ensure!(
322 p::scalar(&list, 1)? == commands.len() as u64,
323 "Sync command count mismatch"
324 );
325 let cursor = apply_cursor(prior, &incoming, code)?;
326 let sent = if prior.is_empty() { &[0][..] } else { prior };
327 let continuation = flags & 7 != 0 && incoming != sent;
328 Ok(SyncBatch {
329 code,
330 flags,
331 cursor,
332 commands,
333 continuation,
334 })
335}
336#[derive(Clone, Serialize, Deserialize)]
337pub struct AddMessage {
338 pub from: String,
339 pub to: String,
340 pub peer: String,
341 pub sender_id: Option<String>,
342 pub outgoing: bool,
343 pub server_message_id: Option<String>,
344 pub numbered1: i32,
345 pub message_type: i32,
346 pub content: Vec<u8>,
347 pub content_text: Option<String>,
348 pub numbered6: u32,
349 pub numbered7: u32,
350 pub inline_data: Vec<u8>,
351 pub created_at: u32,
352 pub msgsource: Option<Vec<u8>>,
353 pub opaque11: Option<Vec<u8>>,
354 pub numbered13: Option<u32>,
355 pub server_sequence: Option<u32>,
359 pub raw: Vec<u8>,
360}
361pub fn decode_add_message(input: &[u8], own_username: &str) -> Result<AddMessage> {
366 let f = p::decode(input)?;
367 let sk = |n| -> Result<Vec<u8>> {
368 let nested = p::decode(p::data(&f, n)?)?;
369 Ok(p::optional_data(&nested, 1)?.unwrap_or_default().to_vec())
370 };
371 let from = String::from_utf8(sk(2)?)?;
372 let to = String::from_utf8(sk(3)?)?;
373 ensure!(
374 !from.is_empty()
375 && !to.is_empty()
376 && from.len() <= 512
377 && to.len() <= 512
378 && !own_username.is_empty(),
379 "Invalid message identities"
380 );
381 let message_type = p::scalar(&f, 4)? as u32 as i32;
382 let peer = if message_type == 10000 {
383 from.clone()
384 } else if from == own_username {
385 to.clone()
386 } else {
387 from.clone()
388 };
389 ensure!(
390 from == own_username || to == own_username || peer.ends_with("@chatroom"),
391 "Message does not match account"
392 );
393 let content = sk(5)?;
394 let content_text = String::from_utf8(content.clone()).ok();
395 let server_message_id = match p::optional(&f, 12)? {
396 Some(p::Value::Varint(v)) => {
397 if *v == 0 {
398 None
399 } else {
400 Some(v.to_string())
401 }
402 }
403 None => None,
404 _ => bail!("Invalid message server ID"),
405 };
406 let numbered13 = match p::optional(&f, 13)? {
407 Some(p::Value::Varint(v)) => Some(u32::try_from(*v)?),
408 None => None,
409 _ => bail!("Invalid numbered field13"),
410 };
411 Ok(AddMessage {
412 sender_id: (!from.ends_with("@chatroom")).then(|| from.clone()),
413 outgoing: from == own_username,
414 from,
415 to,
416 peer,
417 server_message_id,
418 numbered1: p::scalar(&f, 1)? as u32 as i32,
419 message_type,
420 content,
421 content_text,
422 numbered6: u32::try_from(p::scalar(&f, 6)?)?,
423 numbered7: u32::try_from(p::scalar(&f, 7)?)?,
424 inline_data: p::unbuffer(p::data(&f, 8)?)?,
425 created_at: u32::try_from(p::scalar(&f, 9)?)?,
426 msgsource: p::optional_data(&f, 10)?.map(Vec::from),
427 opaque11: p::optional_data(&f, 11)?.map(Vec::from),
428 numbered13,
429 server_sequence: numbered13,
430 raw: input.to_vec(),
431 })
432}
433pub fn decode_cursor(input: &[u8]) -> Result<Vec<(u32, u32)>> {
434 let f = p::decode(input)?;
435 u32::try_from(p::scalar(&f, 1)?)?;
436 let mut out = vec![];
437 for item in f.iter().filter(|f| f.number == 2) {
438 let p::Value::Bytes(v) = &item.value else {
439 bail!("Invalid cursor entry");
440 };
441 let nested = p::decode(v)?;
442 out.push((
443 u32::try_from(p::scalar(&nested, 1)?)?,
444 u32::try_from(p::scalar(&nested, 2)?)?,
445 ));
446 }
447 Ok(out)
448}
449pub fn apply_cursor(prior: &[u8], incoming: &[u8], code: i32) -> Result<Vec<u8>> {
450 if code == -2006 {
451 return Ok(incoming.to_vec());
452 }
453 ensure!(code == 0, "Unsupported sync status");
454 if incoming.is_empty() {
455 return Ok(prior.to_vec());
456 }
457 if prior.is_empty() {
458 return Ok(incoming.to_vec());
459 }
460 let mut merged = BTreeMap::new();
461 for (k, v) in decode_cursor(prior)? {
462 merged.entry(k).or_insert(v);
463 }
464 for (k, v) in decode_cursor(incoming)? {
465 let value = merged.entry(k).or_insert(0);
466 *value = (*value).max(v);
467 }
468 let mut out = vec![p::uint(1, merged.len() as u64)];
469 for (k, v) in merged {
470 out.push(p::bytes(
471 2,
472 &p::message(&[p::uint(1, k as u64), p::uint(2, v as u64)])?,
473 ));
474 }
475 p::message(&out)
476}
477#[derive(Clone, Serialize, Deserialize, Debug)]
478pub struct Heartbeat {
479 pub next_interval: u32,
480 pub selector: u32,
481}
482pub fn heartbeat_request(state: &AccountState, cursor: &[u8]) -> Result<Vec<u8>> {
483 p::message(&[
484 p::bytes(1, &base_request(&state.device, state.uin, 0)?),
485 p::uint(2, unix_time()? as u64),
486 p::bytes(3, &p::buffer(cursor)),
487 ])
488}
489pub fn decode_heartbeat(input: &[u8]) -> Result<Heartbeat> {
490 let f = p::decode(input)?;
491 let code = p::base_code(&f)?;
492 ensure!(code == 0, "Heartbeat returned {code}");
493 Ok(Heartbeat {
494 next_interval: u32::try_from(p::scalar(&f, 2)?)?,
495 selector: u32::try_from(p::scalar(&f, 3)?)?,
496 })
497}
498
499pub fn text_send_request(
500 destination: &str,
501 content: &str,
502 created_at: u32,
503 client_id: u32,
504) -> Result<Vec<u8>> {
505 text_send_request_with_source(destination, content, created_at, client_id, None)
506}
507
508pub fn text_send_request_with_source(
509 destination: &str,
510 content: &str,
511 created_at: u32,
512 client_id: u32,
513 source: Option<&str>,
514) -> Result<Vec<u8>> {
515 ensure!(
516 created_at > 0 && client_id > 0,
517 "Invalid send timestamp/client ID"
518 );
519 let mut fields = vec![
520 p::bytes(1, &p::bytes(1, text(destination, 256, false)?)),
521 p::bytes(2, text(content, 64 * 1024, false)?),
522 p::uint(3, 1),
523 p::uint(4, created_at as u64),
524 p::uint(5, client_id as u64),
525 ];
526 if let Some(source) = source.filter(|value| !value.is_empty()) {
527 fields.push(p::bytes(6, text(source, 256 * 1024, false)?));
528 }
529 let item = p::message(&fields)?;
530 p::message(&[p::uint(1, 1), p::bytes(2, &item)])
531}
532pub fn client_message_id(data: &[u8]) -> u32 {
536 if data.is_empty() || data.len() > i32::MAX as usize {
537 return 0;
538 }
539 let word = |i| u16::from_le_bytes([data[i], data[i + 1]]) as u32;
540 let signed = |i| data[i] as i8 as i32 as u32;
541 let mut h = data.len() as u32;
542 let mut p = 0;
543 for _ in 0..data.len() / 4 {
544 h = h.wrapping_add(word(p));
545 let tmp = (word(p + 2) << 11) ^ h;
546 h = (h << 16) ^ tmp;
547 p += 4;
548 h = h.wrapping_add(h >> 11);
549 }
550 match data.len() & 3 {
551 3 => {
552 h = h.wrapping_add(word(p));
553 h = (h << 16) ^ h ^ (signed(p + 2) << 18);
554 h = h.wrapping_add(h >> 11);
555 }
556 2 => {
557 h = h.wrapping_add(word(p));
558 h ^= h << 11;
559 h = h.wrapping_add(h >> 17);
560 }
561 1 => {
562 h = h.wrapping_add(signed(p));
563 h ^= h << 10;
564 h = h.wrapping_add(h >> 1);
565 }
566 _ => {}
567 }
568 h ^= h << 3;
569 h = h.wrapping_add(h >> 5);
570 h ^= h << 4;
571 h = h.wrapping_add(h >> 17);
572 h ^= h << 25;
573 h.wrapping_add(h >> 6)
574}
575#[derive(Clone, Serialize, Deserialize, Debug)]
576pub struct TextSendResult {
577 pub accepted: bool,
578 pub outer_code: i32,
579 pub item_code: Option<i32>,
580 pub server_message_id: Option<String>,
581 pub returned_destination_matches: bool,
582 pub returned_client_id_matches: bool,
583}
584pub fn decode_text_send(input: &[u8], destination: &str, client_id: u32) -> Result<TextSendResult> {
585 let f = p::decode(input)?;
586 let code = p::base_code(&f)?;
587 let declared = p::scalar(&f, 2)?;
588 if code != 0 {
589 return Ok(TextSendResult {
590 accepted: false,
591 outer_code: code,
592 item_code: None,
593 server_message_id: None,
594 returned_destination_matches: false,
595 returned_client_id_matches: false,
596 });
597 }
598 let list: Vec<_> = f.iter().filter(|f| f.number == 3).collect();
599 ensure!(
600 declared == 1 && list.len() == 1,
601 "Send response cardinality differs"
602 );
603 let p::Value::Bytes(raw) = &list[0].value else {
604 bail!("Invalid send response");
605 };
606 let item = p::decode(raw)?;
607 let item_code = p::scalar(&item, 1)? as u32 as i32;
608 let returned = p::decode(p::data(&item, 2)?)?;
609 let dest = p::data(&returned, 1)?;
610 for n in 3..=7 {
611 u32::try_from(p::scalar(&item, n)?)?;
612 }
613 let id = if item_code == 0 {
614 let id = p::scalar(&item, 8)?;
615 ensure!(id != 0, "Accepted send has no server ID");
616 Some(id.to_string())
617 } else {
618 None
619 };
620 Ok(TextSendResult {
621 accepted: item_code == 0,
622 outer_code: code,
623 item_code: Some(item_code),
624 server_message_id: id,
625 returned_destination_matches: dest == destination.as_bytes(),
626 returned_client_id_matches: p::scalar(&item, 4)? == client_id as u64,
627 })
628}
629
630#[derive(Clone, Serialize, Deserialize)]
631pub struct ContactRecord {
632 pub username: String,
633 pub nickname: Option<String>,
634 pub remark: Option<String>,
635 pub alias: Option<String>,
636 pub large_avatar_url: Option<String>,
637 pub small_avatar_url: Option<String>,
638 pub raw: Vec<u8>,
639}
640pub fn contact_request(state: &AccountState, usernames: &[String]) -> Result<Vec<u8>> {
641 ensure!(
642 !usernames.is_empty() && usernames.len() <= 2,
643 "Supported contact batch is 1 or 2 identities"
644 );
645 let mut seen = HashSet::new();
646 let mut fields = vec![
647 p::bytes(1, &base_request(&state.device, state.uin, 0)?),
648 p::uint(2, usernames.len() as u64),
649 ];
650 for user in usernames {
651 ensure!(seen.insert(user), "Duplicate requested contact");
652 fields.push(p::bytes(3, &p::bytes(1, text(user, 256, false)?)));
653 }
654 fields.extend([p::uint(4, 0), p::uint(6, 0)]);
655 p::message(&fields)
656}
657fn raw_text(bytes: &[u8]) -> Result<String> {
658 Ok(std::str::from_utf8(bytes)?.to_string())
659}
660fn skstring(f: &[p::Field], n: u32) -> Result<Option<String>> {
661 let Some(v) = p::optional_data(f, n)? else {
662 return Ok(None);
663 };
664 let nested = p::decode(v)?;
665 Ok(Some(raw_text(
666 p::optional_data(&nested, 1)?.unwrap_or_default(),
667 )?))
668}
669pub fn decode_contact_record(raw: &[u8]) -> Result<ContactRecord> {
670 let f = p::decode(raw)?;
671 let username = skstring(&f, 1)?.ok_or_else(|| anyhow::anyhow!("Contact has no identity"))?;
672 ensure!(!username.is_empty(), "Empty contact identity");
673 let direct =
674 |n| -> Result<Option<String>> { p::optional_data(&f, n)?.map(raw_text).transpose() };
675 Ok(ContactRecord {
676 username,
677 nickname: skstring(&f, 2)?,
678 remark: skstring(&f, 10)?,
679 alias: direct(30)?,
680 large_avatar_url: direct(39)?,
681 small_avatar_url: direct(40)?,
682 raw: raw.to_vec(),
683 })
684}
685#[derive(Clone, Serialize, Deserialize)]
686pub struct ContactLookup {
687 pub contacts: Vec<ContactRecord>,
688 pub results: Vec<i32>,
689}
690fn contact_result_code(value: u64) -> Result<i32> {
691 let signed = value as u32 as i32;
692 ensure!(
693 value <= u32::MAX as u64 || (signed < 0 && value == signed as i64 as u64),
694 "Invalid per-contact int32 result"
695 );
696 Ok(signed)
697}
698pub fn decode_contacts(input: &[u8], requested: &[String]) -> Result<ContactLookup> {
699 ensure!(
700 input.len() <= crate::MAX_MESSAGE_BYTES,
701 "Contact response exceeds bound"
702 );
703 let f = p::decode(input)?;
704 let code = p::base_code(&f)?;
705 ensure!(code == 0, "Contact service returned {code}");
706 let mut slots = vec![];
707 let mut results = vec![];
708 for field in &f {
709 match (field.number, &field.value) {
710 (3, p::Value::Bytes(raw)) => slots.push(raw.as_slice()),
711 (4, p::Value::Varint(n)) => results.push(contact_result_code(*n)?),
712 (4, p::Value::Bytes(raw)) => {
713 let mut offset = 0;
714 while offset < raw.len() {
715 results.push(contact_result_code(p::read_varint(raw, &mut offset)?)?);
716 }
717 }
718 (3 | 4, _) => bail!("Invalid contact response field"),
719 _ => {}
720 }
721 }
722 ensure!(
725 p::scalar(&f, 2)? == slots.len() as u64 && slots.len() <= requested.len(),
726 "Contact count mismatch"
727 );
728 ensure!(
729 results.is_empty() || results.len() == requested.len(),
730 "Contact result count mismatch"
731 );
732 let aligned = slots.len() == requested.len() && results.len() == requested.len();
733 let mut contacts = vec![];
734 let mut seen = HashSet::new();
735 let mut omitted_unavailable = false;
736 for (index, raw) in slots.iter().enumerate() {
737 let fields = p::decode(raw)?;
738 let identity = p::optional_data(&fields, 1)?;
739 if identity == Some(&[][..]) && aligned && results[index] == 1 {
743 omitted_unavailable = true;
744 continue;
745 }
746 let record = decode_contact_record(raw)?;
747 ensure!(
748 requested.contains(&record.username) && seen.insert(record.username.clone()),
749 "Unrequested or duplicate contact"
750 );
751 contacts.push((index, record));
752 }
753 if omitted_unavailable {
754 ensure!(
755 contacts
756 .iter()
757 .all(|(index, record)| requested.get(*index) == Some(&record.username)),
758 "Unavailable contact slot alignment mismatch"
759 );
760 }
761 Ok(ContactLookup {
762 contacts: contacts.into_iter().map(|(_, record)| record).collect(),
763 results,
764 })
765}
766
767#[derive(Clone, Serialize, Deserialize)]
768pub struct RoomMember {
769 pub username: String,
770 pub nickname: Option<String>,
771 pub room_display_name: Option<String>,
772 pub raw: Vec<u8>,
773 #[serde(default)]
774 pub flags: u32,
775 #[serde(default)]
776 pub opaque: Option<Vec<u8>>,
777}
778#[derive(Clone, Serialize, Deserialize)]
779pub struct RoomRoster {
780 pub room_id: String,
781 pub version: u32,
782 pub members: Vec<RoomMember>,
783 #[serde(default)]
784 pub full_member_ids: Option<Vec<String>>,
785 #[serde(default)]
786 pub owner: Option<String>,
787 #[serde(default)]
789 pub administrator_ids: Option<Vec<String>>,
790}
791pub fn room_request(state: &AccountState, room_id: &str, version: u32) -> Result<Vec<u8>> {
792 ensure!(room_id.ends_with("@chatroom"), "Invalid room identity");
793 p::message(&[
794 p::bytes(1, &base_request(&state.device, state.uin, 0)?),
795 p::bytes(2, text(room_id, 256, false)?),
796 p::uint(3, version as u64),
797 ])
798}
799pub fn decode_room(input: &[u8], room_id: &str) -> Result<RoomRoster> {
800 let f = p::decode(input)?;
801 let code = p::base_code(&f)?;
802 ensure!(code == 0, "Room service returned {code}");
803 if let Some(echo) = p::optional_data(&f, 2)? {
804 ensure!(echo == room_id.as_bytes(), "Foreign room reply");
805 }
806 let version = u32::try_from(p::scalar(&f, 3)?)?;
807 let member_data = p::decode(p::data(&f, 4)?)?;
808 let mut members = vec![];
809 let mut seen = HashSet::new();
810 for field in member_data.iter().filter(|f| f.number == 2) {
811 let p::Value::Bytes(raw) = &field.value else {
812 bail!("Invalid member field");
813 };
814 let m = p::decode(raw)?;
815 let username = raw_text(p::data(&m, 1)?)?;
816 ensure!(
817 !username.is_empty() && seen.insert(username.clone()),
818 "Invalid member identity"
819 );
820 members.push(RoomMember {
821 username,
822 nickname: p::optional_data(&m, 2)?.map(raw_text).transpose()?,
823 room_display_name: p::optional_data(&m, 3)?.map(raw_text).transpose()?,
824 raw: raw.clone(),
825 flags: u32::try_from(p::scalar(&m, 6)?)?,
826 opaque: p::optional_data(&m, 7)?.map(Vec::from),
827 });
828 }
829 match p::optional(&member_data, 1)? {
830 Some(p::Value::Varint(n)) => ensure!(*n == members.len() as u64, "Member count mismatch"),
831 None => {}
832 _ => bail!("Invalid member count"),
833 }
834 let mut full_ids = vec![];
835 let mut full_seen = HashSet::new();
836 for field in f.iter().filter(|v| v.number == 7) {
837 let p::Value::Bytes(raw) = &field.value else {
838 bail!("Invalid full member identity");
839 };
840 let name = raw_text(p::data(&p::decode(raw)?, 1)?)?;
841 ensure!(
842 !name.is_empty()
843 && name.len() <= 256
844 && !name.chars().any(|c| c.is_control() || c.is_whitespace())
845 && full_seen.insert(name.clone())
846 && full_ids.len() < 10000,
847 "Invalid full member identity"
848 );
849 full_ids.push(name);
850 }
851 let full_member_ids = match p::optional(&f, 6)? {
852 Some(p::Value::Varint(n)) => {
853 ensure!(
854 u32::try_from(*n)? as usize == full_ids.len(),
855 "Full member count mismatch"
856 );
857 Some(full_ids)
858 }
859 None if full_ids.is_empty() => None,
860 _ => bail!("Invalid full member count"),
861 };
862 let administrator_control = match p::optional(&f, 8)? {
863 None => 0,
864 Some(p::Value::Varint(value)) => u32::try_from(*value)?,
865 _ => bail!("Invalid administrator control"),
866 };
867 let mut administrator_ids = vec![];
868 let mut administrator_seen = HashSet::new();
869 for (index, field) in f.iter().filter(|v| v.number == 9).enumerate() {
870 ensure!(index < 10000, "Excessive administrator identities");
871 let p::Value::Bytes(raw) = &field.value else {
872 bail!("Invalid administrator identity");
873 };
874 let nested = p::decode(raw)?;
875 let name = raw_text(p::optional_data(&nested, 1)?.unwrap_or_default())?;
876 if name.is_empty() {
878 continue;
879 }
880 ensure!(
881 name.len() <= 256
882 && !name.chars().any(|c| c.is_control() || c.is_whitespace())
883 && administrator_seen.insert(name.clone()),
884 "Invalid administrator identity"
885 );
886 administrator_ids.push(name);
887 }
888 let administrator_ids = (administrator_control != 0).then_some(administrator_ids);
891 let owner = p::optional_data(&f, 5)?
892 .map(raw_text)
893 .transpose()?
894 .filter(|s| !s.is_empty());
895 Ok(RoomRoster {
896 room_id: room_id.to_string(),
897 version,
898 members,
899 full_member_ids,
900 owner,
901 administrator_ids,
902 })
903}
904
905#[derive(Clone, Serialize, Deserialize)]
906pub struct InitContactPage {
907 pub contact_sequence: u32,
908 pub room_sequence: u32,
909 pub more: bool,
910 pub usernames: Vec<String>,
911}
912pub fn init_contacts_request(
913 username: &str,
914 contact_sequence: u32,
915 room_sequence: u32,
916) -> Result<Vec<u8>> {
917 p::message(&[
918 p::bytes(1, text(username, 256, false)?),
919 p::uint(2, contact_sequence as u64),
920 p::uint(3, room_sequence as u64),
921 ])
922}
923pub fn decode_init_contacts(input: &[u8]) -> Result<InitContactPage> {
924 let f = p::decode(input)?;
925 let code = p::base_code(&f)?;
926 ensure!(code == 0, "Contact bootstrap returned {code}");
927 let optional_scalar = |n| -> Result<u32> {
928 match p::optional(&f, n)? {
929 Some(p::Value::Varint(v)) => Ok(u32::try_from(*v)?),
930 None => Ok(0),
931 _ => bail!("Invalid contact cursor field"),
932 }
933 };
934 let mut seen = HashSet::new();
935 let mut usernames = vec![];
936 for field in f.iter().filter(|f| f.number == 5) {
937 let p::Value::Bytes(raw) = &field.value else {
938 bail!("Invalid contact identifier");
939 };
940 let user = raw_text(raw)?;
941 ensure!(
942 !user.is_empty()
943 && user.len() <= 512
944 && !user.chars().any(|c| c.is_control() || c.is_whitespace())
945 && seen.insert(user.clone())
946 && usernames.len() < 10000,
947 "Invalid contact bootstrap identity"
948 );
949 usernames.push(user);
950 }
951 Ok(InitContactPage {
952 contact_sequence: optional_scalar(2)?,
953 room_sequence: optional_scalar(3)?,
954 more: optional_scalar(4)? != 0,
955 usernames,
956 })
957}
958#[derive(Clone, Serialize, Deserialize)]
959pub struct AvatarBytes {
960 pub username: String,
961 pub bytes: Vec<u8>,
962}
963pub fn avatar_request(state: &AccountState, usernames: &[String]) -> Result<Vec<u8>> {
964 ensure!(
965 !usernames.is_empty() && usernames.len() <= 2,
966 "Supported avatar batch is 1 or 2 identities"
967 );
968 let mut parts = vec![
969 p::bytes(1, &base_request(&state.device, state.uin, 0)?),
970 p::uint(2, usernames.len() as u64),
971 ];
972 let mut seen = HashSet::new();
973 for user in usernames {
974 ensure!(seen.insert(user), "Duplicate avatar identity");
975 parts.push(p::bytes(3, &p::bytes(1, text(user, 256, false)?)));
976 }
977 p::message(&parts)
978}
979pub fn decode_avatars(input: &[u8], requested: &[String]) -> Result<Vec<AvatarBytes>> {
982 let f = p::decode(input)?;
983 let code = p::base_code(&f)?;
984 ensure!(code == 0, "Avatar service returned {code}");
985 let mut out = vec![];
986 let mut seen = HashSet::new();
987 for field in f.iter().filter(|f| f.number == 3) {
988 let p::Value::Bytes(raw) = &field.value else {
989 bail!("Invalid avatar item");
990 };
991 let pair = p::decode(raw)?;
992 let username =
993 skstring(&pair, 2)?.ok_or_else(|| anyhow::anyhow!("Missing avatar identity"))?;
994 ensure!(
995 requested.contains(&username) && seen.insert(username.clone()),
996 "Foreign or duplicate avatar"
997 );
998 let bytes = p::unbuffer(p::data(&pair, 1)?)?;
999 ensure!(
1000 !bytes.is_empty() && bytes.len() <= 2 * 1024 * 1024,
1001 "Avatar bytes outside limit"
1002 );
1003 out.push(AvatarBytes { username, bytes });
1004 }
1005 ensure!(
1006 p::scalar(&f, 2)? == out.len() as u64 && out.len() <= requested.len(),
1007 "Avatar count mismatch"
1008 );
1009 Ok(out)
1010}
1011#[derive(Clone, Serialize, Deserialize)]
1012pub struct VoiceChunk {
1013 pub offset: u32,
1014 pub next_offset: u32,
1015 pub total_length: u32,
1016 pub complete: bool,
1017 pub bytes: Vec<u8>,
1018 pub echoed_server_id: Option<String>,
1019 pub offset_matches: bool,
1020}
1021fn voice_range(total: u32, offset: u32) -> Result<u32> {
1022 ensure!(
1023 total > 0 && total <= crate::MAX_MESSAGE_BYTES as u32 && offset < total,
1024 "Invalid voice range"
1025 );
1026 Ok((total - offset).min(32768))
1027}
1028pub fn voice_request(
1029 state: &AccountState,
1030 server_id: u64,
1031 total: u32,
1032 offset: u32,
1033) -> Result<Vec<u8>> {
1034 ensure!(server_id != 0, "Missing voice server ID");
1035 let length = voice_range(total, offset)?;
1036 p::message(&[
1037 p::uint(1, 0),
1038 p::uint(2, offset as u64),
1039 p::uint(3, length as u64),
1040 p::bytes(5, &base_request(&state.device, state.uin, 0)?),
1041 p::uint(6, server_id),
1042 ])
1043}
1044pub fn decode_voice(input: &[u8], server_id: u64, total: u32, offset: u32) -> Result<VoiceChunk> {
1045 let requested = voice_range(total, offset)?;
1046 let f = p::decode(input)?;
1047 let base = p::decode(p::data(&f, 9)?)?;
1048 let code = p::scalar(&base, 1)? as u32 as i32;
1049 ensure!(code == 0, "Voice service returned {code}");
1050 for n in [1, 2, 3, 5, 8] {
1051 u32::try_from(p::scalar(&f, n)?)?;
1052 }
1053 let bytes = p::unbuffer(p::data(&f, 7)?)?;
1054 ensure!(
1055 p::scalar(&f, 3)? == bytes.len() as u64
1056 && !bytes.is_empty()
1057 && bytes.len() <= requested as usize,
1058 "Voice response length invalid"
1059 );
1060 let echoed = match p::optional(&f, 11)? {
1061 Some(p::Value::Varint(id)) => Some(*id),
1062 None => None,
1063 _ => bail!("Invalid voice server ID echo"),
1064 };
1065 ensure!(
1066 echoed.is_none_or(|id| id == server_id),
1067 "Foreign voice server ID"
1068 );
1069 let next_offset = offset + bytes.len() as u32;
1070 ensure!(next_offset <= total, "Voice chunk overflow");
1071 Ok(VoiceChunk {
1072 offset,
1073 next_offset,
1074 total_length: total,
1075 complete: next_offset == total,
1076 bytes,
1077 echoed_server_id: echoed.map(|v| v.to_string()),
1078 offset_matches: p::scalar(&f, 2)? == offset as u64,
1079 })
1080}
1081
1082#[cfg(test)]
1083mod tests {
1084 use super::*;
1085 #[test]
1086 fn room_delta_count_is_independent_of_complete_identity_list() {
1087 let member = p::message(&[
1088 p::bytes(1, b"changed"),
1089 p::uint(6, 0x2000),
1090 p::bytes(7, b"opaque"),
1091 ])
1092 .unwrap();
1093 let roster = |nested: Vec<u8>, extra: Vec<Vec<u8>>| {
1094 let mut fields = vec![
1095 p::bytes(1, &p::uint(1, 0)),
1096 p::uint(3, 7),
1097 p::bytes(4, &nested),
1098 ];
1099 fields.extend(extra);
1100 p::message(&fields).unwrap()
1101 };
1102 let nested = p::message(&[p::uint(1, 1), p::bytes(2, &member)]).unwrap();
1103 let full = vec![
1104 p::uint(6, 2),
1105 p::bytes(7, &p::bytes(1, b"changed")),
1106 p::bytes(7, &p::bytes(1, b"unchanged")),
1107 ];
1108 let decoded = decode_room(&roster(nested.clone(), full.clone()), "room@chatroom").unwrap();
1109 assert_eq!(decoded.members.len(), 1);
1110 assert_eq!(decoded.members[0].flags, 0x2000);
1111 assert_eq!(
1112 decoded.members[0].opaque.as_deref(),
1113 Some(b"opaque".as_slice())
1114 );
1115 assert_eq!(decoded.full_member_ids.unwrap(), ["changed", "unchanged"]);
1116 assert!(decode_room(
1117 &roster(nested.clone(), vec![p::uint(6, 2)]),
1118 "room@chatroom"
1119 )
1120 .is_err());
1121 assert!(decode_room(&roster(p::bytes(1, b"wrong type"), vec![]), "room@chatroom").is_err());
1122 assert!(decode_room(
1123 &roster(p::bytes(2, &p::bytes(1, b"missing-flags")), vec![]),
1124 "room@chatroom"
1125 )
1126 .is_err());
1127 let mut administrator_clear = full;
1128 administrator_clear.push(p::uint(8, 1));
1129 assert_eq!(
1130 decode_room(
1131 &roster(nested.clone(), administrator_clear),
1132 "room@chatroom"
1133 )
1134 .unwrap()
1135 .administrator_ids,
1136 Some(vec![])
1137 );
1138 assert!(decode_room(&roster(nested, vec![]), "room@chatroom")
1139 .unwrap()
1140 .full_member_ids
1141 .is_none());
1142 }
1143
1144 fn room_administrator_fixture(extra: &[u8]) -> Vec<u8> {
1145 let mut raw = vec![0x0a, 0x02, 0x08, 0x00, 0x18, 0x07, 0x22, 0x02, 0x08, 0x00];
1149 raw.extend_from_slice(extra);
1150 raw
1151 }
1152
1153 #[test]
1154 fn room_administrator_control_replaces_list_without_count_semantics() {
1155 let raw = room_administrator_fixture(&[
1159 0x40, 0x2a, 0x4a, 0x07, 0x0a, 0x05, b'a', b'l', b'i', b'c', b'e', 0x4a, 0x00, 0x4a,
1160 0x05, 0x0a, 0x03, b'b', b'o', b'b',
1161 ]);
1162 let roster = decode_room(&raw, "room@chatroom").unwrap();
1163 assert_eq!(roster.administrator_ids.unwrap(), ["alice", "bob"]);
1164 assert!(roster.members.is_empty());
1165 assert!(roster.full_member_ids.is_none());
1166 }
1167
1168 #[test]
1169 fn room_administrator_absent_or_zero_control_preserves_list() {
1170 for extra in [
1171 vec![],
1172 vec![0x40, 0x00],
1173 vec![0x4a, 0x07, 0x0a, 0x05, b'a', b'l', b'i', b'c', b'e'],
1174 vec![
1175 0x40, 0x00, 0x4a, 0x07, 0x0a, 0x05, b'a', b'l', b'i', b'c', b'e',
1176 ],
1177 ] {
1178 assert!(
1179 decode_room(&room_administrator_fixture(&extra), "room@chatroom")
1180 .unwrap()
1181 .administrator_ids
1182 .is_none()
1183 );
1184 }
1185 let old: RoomRoster = serde_json::from_value(serde_json::json!({
1188 "room_id":"room@chatroom", "version":7, "members":[]
1189 }))
1190 .unwrap();
1191 assert!(old.administrator_ids.is_none());
1192 }
1193
1194 #[test]
1195 fn room_administrator_control_clears_absent_and_empty_entries() {
1196 for extra in [
1197 vec![0x40, 0x01],
1198 vec![0x40, 0x01, 0x4a, 0x00],
1199 vec![0x40, 0x01, 0x4a, 0x02, 0x0a, 0x00],
1200 ] {
1201 assert_eq!(
1202 decode_room(&room_administrator_fixture(&extra), "room@chatroom")
1203 .unwrap()
1204 .administrator_ids,
1205 Some(vec![])
1206 );
1207 }
1208 }
1209
1210 #[test]
1211 fn room_administrator_fields_reject_malformed_and_unbounded_values() {
1212 let string = |value: &[u8]| p::bytes(9, &p::bytes(1, value));
1213 let repeated = string(b"alice");
1214 for extra in [
1215 p::bytes(8, &[]),
1216 p::uint(8, u64::from(u32::MAX) + 1),
1217 [p::uint(8, 1), p::uint(8, 1)].concat(),
1218 p::uint(9, 1),
1219 p::bytes(9, &[0x0a, 0x01]),
1220 p::bytes(9, &p::uint(1, 1)),
1221 p::bytes(9, &[p::bytes(1, b"a"), p::bytes(1, b"b")].concat()),
1222 string(&[0xff]),
1223 string(b"alice bob"),
1224 string(b"alice\n"),
1225 string(&[b'a'; 257]),
1226 [repeated.clone(), repeated].concat(),
1227 p::bytes(9, &[]).repeat(10001),
1228 ] {
1229 assert!(decode_room(&room_administrator_fixture(&extra), "room@chatroom").is_err());
1230 }
1231 }
1232 fn cursor(k: u64, v: u64) -> Vec<u8> {
1233 p::message(&[
1234 p::uint(1, 1),
1235 p::bytes(2, &p::message(&[p::uint(1, k), p::uint(2, v)]).unwrap()),
1236 ])
1237 .unwrap()
1238 }
1239 #[test]
1240 fn explicit_code1_contact_slot_does_not_hide_successful_neighbor() {
1241 let requested = vec!["unavailable".into(), "available".into()];
1242 let empty = p::bytes(1, &[]);
1243 let available = p::bytes(1, &p::bytes(1, b"available"));
1244 for codes in [
1245 p::bytes(4, &[1, 0]),
1246 p::message(&[p::uint(4, 1), p::uint(4, 0)]).unwrap(),
1247 ] {
1248 let raw = p::message(&[
1249 p::bytes(1, &p::uint(1, 0)),
1250 p::uint(2, 2),
1251 p::bytes(3, &empty),
1252 p::bytes(3, &available),
1253 codes,
1254 ])
1255 .unwrap();
1256 let result = decode_contacts(&raw, &requested).unwrap();
1257 assert_eq!(result.results, vec![1, 0]);
1258 assert_eq!(result.contacts.len(), 1);
1259 assert_eq!(result.contacts[0].username, "available");
1260 assert!(decode_contact_record(&empty).is_err());
1261 }
1262 }
1263 #[test]
1264 fn contact_result_overflow_cannot_alias_success_or_unavailable() {
1265 let requested = vec!["unavailable".into(), "available".into()];
1266 let empty = p::bytes(1, &[]);
1267 let available = p::bytes(1, &p::bytes(1, b"available"));
1268 for overflow in [1u64 << 32, (1u64 << 32) + 1, (1u64 << 32) + u32::MAX as u64] {
1269 let packed = p::uint(1, overflow)[1..].to_vec();
1270 for codes in [
1271 p::message(&[p::uint(4, overflow), p::uint(4, 0)]).unwrap(),
1272 p::bytes(4, &[packed.as_slice(), &[0]].concat()),
1273 ] {
1274 let raw = p::message(&[
1275 p::bytes(1, &p::uint(1, 0)),
1276 p::uint(2, 2),
1277 p::bytes(3, &empty),
1278 p::bytes(3, &available),
1279 codes,
1280 ])
1281 .unwrap();
1282 assert!(decode_contacts(&raw, &requested).is_err());
1283 }
1284 }
1285 assert_eq!(contact_result_code(0).unwrap(), 0);
1286 assert_eq!(contact_result_code(1).unwrap(), 1);
1287 assert_eq!(contact_result_code(u32::MAX as u64).unwrap(), -1);
1288 assert_eq!(contact_result_code(u64::MAX).unwrap(), -1);
1289 assert_eq!(
1290 contact_result_code(i32::MIN as i64 as u64).unwrap(),
1291 i32::MIN
1292 );
1293 }
1294 #[test]
1295 fn unexplained_empty_contact_slots_and_unproved_alignment_remain_errors() {
1296 let ids = vec!["unavailable".into(), "available".into()];
1297 let empty = p::bytes(1, &[]);
1298 let available = p::bytes(1, &p::bytes(1, b"available"));
1299 for codes in [vec![], p::bytes(4, &[0, 0]), p::bytes(4, &[2, 0])] {
1300 let raw = p::message(&[
1301 p::bytes(1, &p::uint(1, 0)),
1302 p::uint(2, 2),
1303 p::bytes(3, &empty),
1304 p::bytes(3, &available),
1305 codes,
1306 ])
1307 .unwrap();
1308 assert!(decode_contacts(&raw, &ids).is_err());
1309 }
1310 let partial = p::message(&[
1311 p::bytes(1, &p::uint(1, 0)),
1312 p::uint(2, 1),
1313 p::bytes(3, &empty),
1314 p::bytes(4, &[1, 0]),
1315 ])
1316 .unwrap();
1317 assert!(decode_contacts(&partial, &ids).is_err());
1318 let wrong = p::message(&[
1319 p::bytes(1, &p::uint(1, 0)),
1320 p::uint(2, 2),
1321 p::bytes(3, &empty),
1322 p::bytes(3, &p::bytes(1, &p::bytes(1, b"unavailable"))),
1323 p::bytes(4, &[1, 0]),
1324 ])
1325 .unwrap();
1326 assert!(decode_contacts(&wrong, &ids).is_err());
1327 let absent = p::message(&[
1328 p::bytes(1, &p::uint(1, 0)),
1329 p::uint(2, 2),
1330 p::bytes(3, &[]),
1331 p::bytes(3, &available),
1332 p::bytes(4, &[1, 0]),
1333 ])
1334 .unwrap();
1335 assert!(decode_contacts(&absent, &ids).is_err());
1336 }
1337 #[test]
1338 fn cursor_merge_and_reset() {
1339 assert_eq!(
1340 decode_cursor(&apply_cursor(&cursor(1, 9), &cursor(1, 2), 0).unwrap()).unwrap(),
1341 vec![(1, 9)]
1342 );
1343 assert_eq!(
1344 apply_cursor(&cursor(1, 9), &cursor(1, 2), -2006).unwrap(),
1345 cursor(1, 2)
1346 );
1347 assert!(apply_cursor(&[], &[], 3).is_err());
1348 }
1349 #[test]
1350 fn send_u64_ids_lossless() {
1351 let item = p::message(&[
1352 p::uint(1, 0),
1353 p::bytes(2, &p::bytes(1, b"filehelper")),
1354 p::uint(3, 0),
1355 p::uint(4, 99),
1356 p::uint(5, 0),
1357 p::uint(6, 0),
1358 p::uint(7, 0),
1359 p::uint(8, u64::MAX),
1360 ])
1361 .unwrap();
1362 let response = p::message(&[
1363 p::bytes(1, &p::uint(1, 0)),
1364 p::uint(2, 1),
1365 p::bytes(3, &item),
1366 ])
1367 .unwrap();
1368 assert_eq!(
1369 decode_text_send(&response, "filehelper", 99)
1370 .unwrap()
1371 .server_message_id,
1372 Some(u64::MAX.to_string())
1373 );
1374 }
1375 #[test]
1376 fn foreign_room_rejected() {
1377 let response = p::message(&[
1378 p::bytes(1, &p::uint(1, 0)),
1379 p::bytes(2, b"foreign@chatroom"),
1380 p::uint(3, 0),
1381 p::bytes(4, &p::uint(1, 0)),
1382 ])
1383 .unwrap();
1384 assert!(decode_room(&response, "known@chatroom").is_err());
1385 }
1386 #[test]
1387 fn native_client_id_vectors_include_signed_tail_bytes() {
1388 for (data, expected) in [
1389 (b"".to_vec(), 0),
1390 (b"a".to_vec(), 291415938),
1391 (b"abc".to_vec(), 3535673738),
1392 (b"native_test_0123456789".to_vec(), 483784200),
1393 (vec![255], 0),
1394 (vec![1, 2, 255], 2571803832),
1395 ((0..=255).collect(), 3840866583),
1396 ] {
1397 assert_eq!(client_message_id(&data), expected);
1398 }
1399 }
1400 #[test]
1401 fn text_source_is_optional_length_delimited_item_field_six() {
1402 let request = text_send_request_with_source("r", "x", 1, 2, Some("<msgsource/>")).unwrap();
1403 assert_eq!(
1406 hex::encode(request),
1407 "0801121c0a030a0172120178180120012802320c3c6d7367736f757263652f3e"
1408 );
1409 let plain = text_send_request("r", "x", 1, 2).unwrap();
1410 assert_eq!(
1411 plain,
1412 text_send_request_with_source("r", "x", 1, 2, None).unwrap()
1413 );
1414 assert_eq!(
1415 plain,
1416 text_send_request_with_source("r", "x", 1, 2, Some("")).unwrap()
1417 );
1418 assert!(
1419 text_send_request_with_source("r", "x", 1, 2, Some(&"x".repeat(256 * 1024 + 1)))
1420 .is_err()
1421 );
1422 }
1423 #[test]
1424 fn addmsg_preserves_numbered_fields_and_opaque_source() {
1425 let payload = p::message(&[
1426 p::uint(1, 7),
1427 p::bytes(2, &p::bytes(1, b"room@chatroom")),
1428 p::bytes(3, &p::bytes(1, b"synthetic-self")),
1429 p::uint(4, 1),
1430 p::bytes(5, &p::bytes(1, b"synthetic content")),
1431 p::uint(6, 123),
1432 p::uint(7, 456),
1433 p::bytes(8, &p::buffer(&[])),
1434 p::uint(9, 999),
1435 p::bytes(10, b"<msgsource/>"),
1436 p::bytes(11, &[255, 0]),
1437 p::uint(12, u64::MAX),
1438 p::uint(13, 789),
1439 ])
1440 .unwrap();
1441 let decoded = decode_add_message(&payload, "synthetic-self").unwrap();
1442 assert_eq!(decoded.peer, "room@chatroom");
1443 assert!(decoded.sender_id.is_none());
1444 assert_eq!(decoded.server_message_id, Some(u64::MAX.to_string()));
1445 assert_eq!(decoded.numbered13, Some(789));
1446 assert_eq!(decoded.opaque11, Some(vec![255, 0]));
1447 assert_eq!(decoded.raw, payload);
1448 }
1449}