1use super::{
6 app_frame::{self, AppFrameParameters},
7 app_session::{AppAuthority, AppSession, PendingAppAuth},
8 host_policy::CallHostRoute,
9 personal::{self, VoiceInputs, VoiceRoom},
10 relay::SelectedRelay,
11 signaling::{CallOperation, CommonRequest, CommonResponse},
12};
13use crate::{
14 protobuf::{self as p, Field, Value},
15 wire, AccountState, NativeClient,
16};
17use anyhow::{bail, ensure, Context, Result};
18use std::collections::{BTreeMap, BTreeSet};
19use zeroize::Zeroizing;
20
21pub trait PersonalTransport {
25 fn call_rpc(
26 &self,
27 state: &mut AccountState,
28 route: CallHostRoute,
29 request: &[u8],
30 ) -> Result<Vec<u8>>;
31}
32#[derive(Debug)]
37pub struct CallRpcNotSubmitted;
38impl std::fmt::Display for CallRpcNotSubmitted {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 f.write_str("Call control request was not submitted")
41 }
42}
43impl std::error::Error for CallRpcNotSubmitted {}
44
45impl PersonalTransport for NativeClient {
46 fn call_rpc(
47 &self,
48 state: &mut AccountState,
49 route: CallHostRoute,
50 request: &[u8],
51 ) -> Result<Vec<u8>> {
52 self.experimental_call_rpc(state, route, request)
53 }
54}
55#[derive(Debug)]
59pub struct UnsupportedCallMedia;
60impl std::fmt::Display for UnsupportedCallMedia {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 f.write_str("Selected call media is unsupported")
63 }
64}
65impl std::error::Error for UnsupportedCallMedia {}
66
67mod incoming;
68mod termination;
69pub use super::personal::RemoteExitReason;
70pub use incoming::{IncomingPhase, NativeIncomingSession};
71pub use termination::{LocalStopReason, RemoteTermination};
72
73type AvSnapshot = BTreeMap<u16, (u32, Option<Vec<u8>>)>;
74
75#[derive(Clone)]
79pub struct VoiceParameters {
80 pub sdk: Vec<u8>,
81 pub group: Vec<u8>,
82 pub device: Vec<u8>,
83 pub engine_capability: Vec<u8>,
84 pub channel_capability: Vec<u8>,
85 pub network_type: u32,
86 pub sim_type: u32,
87 pub video_length: u32,
88 pub video_ratio: u32,
89 pub nics: Vec<Vec<u8>>,
90 pub cloud_device_id: Vec<u8>,
91 pub app_config_version: u32,
92}
93impl VoiceParameters {
94 pub fn inputs(&self) -> VoiceInputs<'_> {
95 VoiceInputs {
96 sdk: &self.sdk,
97 group: &self.group,
98 device: &self.device,
99 engine_capability: &self.engine_capability,
100 channel_capability: &self.channel_capability,
101 network_type: self.network_type,
102 sim_type: self.sim_type,
103 video_length: self.video_length,
104 video_ratio: self.video_ratio,
105 nics: &self.nics,
106 }
107 }
108 fn sdk_version(&self) -> Result<u32> {
109 u32::try_from(number(&fields(&self.sdk)?, 1, None)?).map_err(Into::into)
110 }
111}
112
113#[derive(Clone, Copy, Debug, PartialEq, Eq)]
114pub struct TaskIds {
115 pub sdk: u32,
116 pub host: u32,
117}
118#[derive(Clone, Copy, Debug, PartialEq, Eq)]
119pub enum PersonalPhase {
120 Idle,
121 Inviting,
122 Ringing,
123 Joined,
124 Ending,
125 TerminalPending,
126 Uncertain,
127 Finished,
128}
129#[derive(Clone, Copy, Debug, PartialEq, Eq)]
130pub enum SyncProgress {
131 Waiting,
132 MediaReady,
133 Terminal(RemoteTermination),
134}
135
136#[derive(Clone)]
137struct Member {
138 member: u16,
139 status: u32,
140 openid: Vec<u8>,
141}
142enum ControlOrigin {
143 Outgoing { invite_id: u64 },
144 Incoming,
145}
146struct PersonalControl {
147 identity: VoiceRoom,
148 peer: String,
149 origin: ControlOrigin,
150 peer_joined: Option<bool>,
151 terminal: Option<RemoteTermination>,
152 invitation_pending: bool,
153 pending_recipients: Vec<String>,
154}
155
156#[derive(Clone)]
157struct ActiveRoom {
158 identity: VoiceRoom,
159 peer: String,
160 live_id: u64,
161 cookies: Vec<u8>,
162 strategy: Zeroizing<Vec<u8>>,
163 members: BTreeMap<String, Member>,
164 original_members: BTreeMap<String, Member>,
165 unbound_peer_openid: Option<Vec<u8>>,
166 member_sequence: u32,
167 cap_sequence: u32,
168 engine: Vec<u8>,
169 channel: Vec<u8>,
170 av_sequence: u32,
171 av: AvSnapshot,
172 media_taken: bool,
173}
174
175pub struct NegotiatedMedia {
176 pub relay: SelectedRelay,
177 pub peer_member: u16,
178 pub frames_per_packet: u8,
179}
180
181pub struct NativePersonalSession {
186 app: AppSession,
187 authority: AppAuthority,
188 own_username: String,
189 parameters: VoiceParameters,
190 phase: PersonalPhase,
191 active: Option<ActiveRoom>,
192 control: Option<PersonalControl>,
193 host_tasks: BTreeSet<u32>,
194 sdk_tasks: BTreeSet<u32>,
195}
196impl NativePersonalSession {
197 pub fn authenticate(
200 client: &impl PersonalTransport,
201 state: &mut AccountState,
202 authority: AppAuthority,
203 host_task: u32,
204 device32: [u8; 32],
205 parameters: VoiceParameters,
206 ) -> Result<Self> {
207 ensure!(host_task != 0, "Missing app auth task");
208 parameters.inputs().validate()?;
209 ensure!(
210 number(&fields(¶meters.group)?, 5, None)? == 1,
211 "Selected caller sync requires group mode1"
212 );
213 let pending = PendingAppAuth::new(authority, device32)?;
214 let request = common(state, host_task, &pending.request_body()?)?;
215 let reply = client.call_rpc(state, CallHostRoute::AppAuth, &request)?;
216 let app = pending.complete(&reply, authority)?;
217 Ok(Self {
218 app,
219 authority,
220 own_username: state.own_username.clone(),
221 parameters,
222 phase: PersonalPhase::Idle,
223 active: None,
224 control: None,
225 host_tasks: BTreeSet::from([host_task]),
226 sdk_tasks: BTreeSet::new(),
227 })
228 }
229 pub fn phase(&self) -> PersonalPhase {
230 self.phase
231 }
232 pub fn peer_microphone_muted(&self) -> Option<bool> {
235 if self.phase != PersonalPhase::Joined {
236 return None;
237 }
238 let room = self.active.as_ref()?;
239 let member = room.members.get(&room.peer)?;
240 if member.status != 1 {
241 return None;
242 }
243 match room.av.get(&member.member)?.0 {
244 0 => Some(true),
245 1 => Some(false),
246 _ => None,
247 }
248 }
249 pub fn has_allocated_control(&self) -> bool {
253 self.control.is_some()
254 }
255 pub fn media_ready(&self, state: &AccountState, current: AppAuthority) -> Result<bool> {
256 self.check(state, current)?;
257 if self.phase != PersonalPhase::Joined {
258 return Ok(false);
259 }
260 match self.active.as_ref() {
261 Some(active) => ready(active, &self.own_username),
262 None => Ok(false),
263 }
264 }
265 fn check(&self, state: &AccountState, current: AppAuthority) -> Result<()> {
266 self.app.validate_authority(current)?;
267 ensure!(
268 current == self.authority && state.own_username == self.own_username,
269 "Personal call account changed"
270 );
271 Ok(())
272 }
273 fn reserve(&mut self, tasks: TaskIds) -> Result<()> {
274 ensure!(
275 tasks.host != 0
276 && tasks.sdk != 0
277 && self.host_tasks.len() < 4096
278 && self.sdk_tasks.len() < 4096,
279 "Invalid or exhausted call task IDs"
280 );
281 ensure!(
282 !self.host_tasks.contains(&tasks.host) && !self.sdk_tasks.contains(&tasks.sdk),
283 "Call task already spent"
284 );
285 self.host_tasks.insert(tasks.host);
286 self.sdk_tasks.insert(tasks.sdk);
287 Ok(())
288 }
289 fn host(
290 &mut self,
291 client: &impl PersonalTransport,
292 state: &mut AccountState,
293 route: CallHostRoute,
294 body: &[u8],
295 host_task: u32,
296 ) -> Result<Vec<u8>> {
297 let request = common(state, host_task, body)?;
298 let raw = client.call_rpc(state, route, &request)?;
299 let reply = CommonResponse::decode(&raw)?;
300 ensure!(
301 reply.return_code()? == 0,
302 "Personal call host operation rejected"
303 );
304 reply
305 .body
306 .ok_or_else(|| anyhow::anyhow!("Missing personal call host reply"))
307 }
308 pub fn invite(
309 &mut self,
310 client: &impl PersonalTransport,
311 state: &mut AccountState,
312 current: AppAuthority,
313 peer: &str,
314 invite_id: u64,
315 tasks: TaskIds,
316 ) -> Result<()> {
317 self.check(state, current)?;
318 ensure!(
319 self.phase == PersonalPhase::Idle && peer != self.own_username,
320 "Personal session busy or invalid peer"
321 );
322 self.reserve(tasks)?;
323 self.phase = PersonalPhase::Inviting;
324 let result = self.invite_once(client, state, peer, invite_id, tasks);
325 let understood_media_error = result
326 .as_ref()
327 .is_err_and(|error| error.is::<UnsupportedCallMedia>())
328 && self
329 .control
330 .as_ref()
331 .is_some_and(|control| control.peer_joined.is_some())
332 && matches!(self.phase, PersonalPhase::Ringing | PersonalPhase::Joined);
333 if result.is_err() && !understood_media_error {
334 self.phase = PersonalPhase::Uncertain;
335 }
336 result
337 }
338 fn invite_once(
339 &mut self,
340 client: &impl PersonalTransport,
341 state: &mut AccountState,
342 peer: &str,
343 invite_id: u64,
344 tasks: TaskIds,
345 ) -> Result<()> {
346 let request =
347 personal::invite(&self.parameters.inputs(), peer, invite_id, tasks.sdk, None)?;
348 let body = self.host(
349 client,
350 state,
351 request.operation.into(),
352 request.body(),
353 tasks.host,
354 )?;
355 let wrapper = personal::InviteResponse::decode(&body)?;
356 ensure!(
357 wrapper.custom_auth.as_ref().is_none_or(|v| v.is_empty()),
358 "Additional custom call auth unsupported"
359 );
360 ensure!(
361 wrapper.invite_results.len() <= 1
362 && wrapper.invite_results.iter().all(|(name, _)| name == peer),
363 "Personal invitation result identity mismatch"
364 );
365 ensure!(
366 wrapper.unknown_fields.is_empty(),
367 "Unknown personal allocation fields"
368 );
369 let own_member = u16::try_from(wrapper.own_member)?;
370 self.control = Some(PersonalControl {
371 identity: VoiceRoom {
372 room: wrapper.room,
373 room_key: wrapper.room_key,
374 own_member: wrapper.own_member,
375 },
376 peer: peer.to_owned(),
377 origin: ControlOrigin::Outgoing { invite_id },
378 peer_joined: None,
379 terminal: None,
380 invitation_pending: false,
381 pending_recipients: vec![],
382 });
383 let initial = fields(&wrapper.client_response)?;
387 only(&initial, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10])?;
388 let raw_members = data(&initial, 2)?;
389 let list = fields(raw_members)?;
390 only(&list, &[1, 2, 3])?;
391 p::optional_data(&list, 3)?;
392 let member_sequence = u32::try_from(number(&list, 1, Some(0))?)?;
393 ensure!(
394 member_sequence != 0,
395 "Missing initial current member sequence"
396 );
397 let members = parse_roster_entries(repeated(&list, 2, 16)?, &self.own_username, peer)?;
398 ensure!(
399 members
400 .get(&self.own_username)
401 .is_none_or(|own| own.member == own_member)
402 && members
403 .get(peer)
404 .is_none_or(|peer| peer.member != own_member),
405 "Initial personal member identity mismatch"
406 );
407 let peer_joined = members.get(peer).is_some_and(|peer| peer.status == 1);
408 let pending = members
409 .get(peer)
410 .is_some_and(|peer| matches!(peer.status, 2 | 3));
411 let control = self.control.as_mut().expect("allocated control");
412 control.peer_joined = Some(peer_joined);
413 control.invitation_pending = pending;
414 if pending {
415 control.pending_recipients = vec![peer.to_owned()];
416 }
417 let terminal = termination::classify(
418 termination::Origin::Caller,
419 1,
420 members.get(&self.own_username).map(|own| own.status),
421 members.get(peer).map(|peer| peer.status),
422 pending,
423 )?;
424 if let Some(action @ RemoteTermination::LocalStop(_)) = terminal {
425 control.terminal = Some(action);
426 self.phase = PersonalPhase::Finished;
427 return Ok(());
428 }
429 let live_id = number(&initial, 5, None)?;
430 ensure!(live_id != 0, "Missing initial live identity");
431 self.parameters.group = replace_group(&self.parameters.group, live_id, &wrapper.group_id)?;
432 if let Some(action) = terminal {
433 self.control.as_mut().expect("allocated control").terminal = Some(action);
434 self.phase = PersonalPhase::TerminalPending;
435 return Ok(());
436 }
437 ensure!(
438 members.len() == 2 && members[&self.own_username].status == 1,
439 "Incomplete initial personal roster"
440 );
441 self.phase = if peer_joined {
442 PersonalPhase::Joined
443 } else {
444 PersonalPhase::Ringing
445 };
446 let client_response = personal::JoinClientResponse::decode(&wrapper.client_response)?;
447 let strategy = fields(&client_response.strategy)?;
448 ensure!(
449 number(&strategy, 14, None)? & (1 << 36) == 0,
450 "Alternate caller sync unsupported"
451 );
452 let (cap_sequence, engine, channel) = cap(data(&strategy, 13)?)?;
453 let (av_sequence, av) = av(
454 client_response
455 .av_status
456 .as_deref()
457 .ok_or_else(|| anyhow::anyhow!("Missing initial AV snapshot"))?,
458 own_member,
459 members[peer].member,
460 )?;
461 let active = ActiveRoom {
462 identity: VoiceRoom {
463 room: wrapper.room,
464 room_key: wrapper.room_key,
465 own_member: wrapper.own_member,
466 },
467 peer: peer.to_owned(),
468 live_id: client_response.live_id,
469 cookies: client_response.cookies,
470 strategy: Zeroizing::new(client_response.strategy),
471 original_members: members.clone(),
472 unbound_peer_openid: None,
473 members,
474 member_sequence,
475 cap_sequence,
476 engine,
477 channel,
478 av_sequence,
479 av,
480 media_taken: false,
481 };
482 self.active = Some(active);
483 let _ = ready(
484 self.active.as_ref().expect("accepted room"),
485 &self.own_username,
486 )?;
487 Ok(())
488 }
489 pub fn poll(
493 &mut self,
494 client: &impl PersonalTransport,
495 state: &mut AccountState,
496 current: AppAuthority,
497 tasks: TaskIds,
498 ) -> Result<SyncProgress> {
499 self.check(state, current)?;
500 ensure!(
501 matches!(self.phase, PersonalPhase::Ringing | PersonalPhase::Joined),
502 "Personal caller sync unavailable"
503 );
504 self.reserve(tasks)?;
505 let result = self.poll_once(client, state, tasks);
506 if result.as_ref().is_err_and(|error| {
507 !error.is::<CallRpcNotSubmitted>() && !error.is::<UnsupportedCallMedia>()
508 }) {
509 self.phase = PersonalPhase::Uncertain;
510 }
511 result
512 }
513 fn poll_once(
514 &mut self,
515 client: &impl PersonalTransport,
516 state: &mut AccountState,
517 tasks: TaskIds,
518 ) -> Result<SyncProgress> {
519 let active = self
520 .active
521 .as_ref()
522 .ok_or_else(|| anyhow::anyhow!("Missing personal room"))?;
523 let strategy = fields(&active.strategy)?;
524 let policies = strategy
525 .iter()
526 .filter(|v| v.number == 11)
527 .map(|v| match v.value {
528 Value::Varint(n) => u32::try_from(n).map_err(Into::into),
529 _ => bail!("Invalid call policy wire type"),
530 })
531 .collect::<Result<Vec<u32>>>()?;
532 let timeout = if policies.len() < 83 {
533 6
534 } else if (3..=50).contains(&policies[82]) {
535 policies[82]
536 } else {
537 10
538 };
539 let request = personal::live_sync(active.live_id, &active.cookies, timeout, tasks.sdk)?;
540 let body = self.host(
541 client,
542 state,
543 CallHostRoute::LiveSync,
544 request.body(),
545 tasks.host,
546 )?;
547 let response = personal::LiveSyncResponse::decode(&body)?;
548 ensure!(
549 response.unknown_fields.is_empty(),
550 "Unknown caller sync response field"
551 );
552 let control = self
553 .control
554 .as_ref()
555 .ok_or_else(|| anyhow::anyhow!("Missing personal control"))?;
556 let origin = match control.origin {
557 ControlOrigin::Outgoing { .. } => termination::Origin::Caller,
558 ControlOrigin::Incoming => termination::Origin::Callee,
559 };
560 let local_status = if matches!(control.origin, ControlOrigin::Incoming)
561 && self.phase != PersonalPhase::Joined
562 {
563 3
564 } else {
565 1
566 };
567 let original_invite =
568 matches!(control.origin,ControlOrigin::Outgoing{invite_id}if invite_id!=0);
569 let mut invitation_pending = control.invitation_pending;
570 let mut pending_recipients = control.pending_recipients.clone();
571 let mut terminal = None;
572 let mut next = self.active.as_ref().expect("active room").clone();
573 'notifications: for item in response.items {
574 ensure!(
575 item.kind == 1 && item.unknown_fields.is_empty(),
576 "Unsupported caller sync item"
577 );
578 let message = fields(&item.body)?;
579 only(&message, &[1, 2])?;
580 ensure!(
581 number(&message, 1, None)? == 10002,
582 "Unsupported caller sync command"
583 );
584 let notify = fields(data(&message, 2)?)?;
585 only(¬ify, &[1, 2, 3, 4, 5])?;
586 ensure!(
587 number(¬ify, 3, None)? == next.identity.room
588 && matches_room_key(¬ify, 4, next.identity.room_key)?,
589 "Caller notification room mismatch"
590 );
591 for raw in repeated(¬ify, 5, 64)? {
592 let item = fields(raw)?;
593 only(&item, &[1, 2])?;
594 let kind = number(&item, 1, None)?;
595 let bytes = data(&item, 2)?;
596 match kind {
597 10 => {
598 let update = fields(bytes)?;
599 only(&update, &[1, 2, 3, 4])?;
600 ensure!(
601 matches_room_key(&update, 1, next.identity.room)?
602 && matches_room_key(&update, 2, next.identity.room_key)?,
603 "Caller roster room mismatch"
604 );
605 let sequence = u32::try_from(number(&update, 3, Some(0))?)?;
606 if sequence <= next.member_sequence {
607 continue;
608 }
609 let members = parse_roster_entries(
610 repeated(&update, 4, 16)?,
611 &self.own_username,
612 &next.peer,
613 )?;
614 for (name, member) in &members {
615 if let Some(old) = next.original_members.get(name) {
616 ensure!(
617 old.member == member.member && old.openid == member.openid,
618 "Caller member identity changed"
619 );
620 } else {
621 ensure!(
622 matches!(origin, termination::Origin::Callee)
623 && name == &next.peer
624 && next.unbound_peer_openid.as_deref()
625 == Some(member.openid.as_slice())
626 && u32::from(member.member) != next.identity.own_member,
627 "Unbound caller member identity mismatch"
628 );
629 next.original_members.insert(name.clone(), member.clone());
630 next.unbound_peer_openid = None;
631 }
632 }
633 if members
634 .get(&next.peer)
635 .is_some_and(|member| member.status == 1)
636 {
637 invitation_pending = false;
638 pending_recipients.clear();
639 }
640 let action = termination::classify(
641 origin,
642 local_status,
643 members.get(&self.own_username).map(|m| m.status),
644 members.get(&next.peer).map(|m| m.status),
645 original_invite && invitation_pending && !pending_recipients.is_empty(),
646 )?;
647 if !matches!(terminal, Some(RemoteTermination::LocalStop(_)))
650 && (terminal.is_none()
651 || matches!(action, Some(RemoteTermination::LocalStop(_))))
652 {
653 terminal = action;
654 }
655 next.members = members;
656 next.member_sequence = sequence;
657 if matches!(terminal, Some(RemoteTermination::LocalStop(_))) {
658 break 'notifications;
663 }
664 }
665 11 => {
666 let (sequence, engine, channel) = cap(bytes)?;
667 if sequence <= next.cap_sequence {
668 continue;
669 }
670 ensure!(
671 !next.media_taken || terminal.is_some(),
672 "Post-start call capability reconfiguration unsupported"
673 );
674 next.cap_sequence = sequence;
675 next.engine = engine;
676 next.channel = channel;
677 }
678 12 => {
679 let (sequence, av) = av(
680 bytes,
681 next.identity.own_member as u16,
682 next.original_members
683 .get(&next.peer)
684 .context("Call AV arrived before caller member binding")?
685 .member,
686 )?;
687 if sequence <= next.av_sequence {
688 continue;
689 }
690 ensure!(
691 !next.media_taken
692 || terminal.is_some()
693 || microphone_update(&next, &av, &self.own_username),
694 "Post-start call AV reconfiguration unsupported"
695 );
696 next.av_sequence = sequence;
697 next.av = av;
698 }
699 _ => bail!("Unsupported caller notification kind"),
700 }
701 }
702 }
703 if let Some(action) = terminal {
704 next.cookies = response.cookies.unwrap_or_default();
705 let control = self.control.as_mut().expect("personal control");
706 control.peer_joined = Some(
707 next.members
708 .get(&next.peer)
709 .is_some_and(|member| member.status == 1),
710 );
711 control.invitation_pending = invitation_pending;
712 control.pending_recipients = pending_recipients;
713 control.terminal = Some(action);
714 self.phase = if matches!(action, RemoteTermination::LocalStop(_)) {
715 PersonalPhase::Finished
716 } else {
717 PersonalPhase::TerminalPending
718 };
719 self.active = Some(next);
720 return Ok(SyncProgress::Terminal(action));
721 }
722 let media_result = match if local_status == 3 {
723 Ok(false)
724 } else {
725 ready(&next, &self.own_username)
726 } {
727 Err(error) if !error.is::<UnsupportedCallMedia>() => return Err(error),
728 result => result,
729 };
730 if media_result.is_ok() {
733 next.cookies = response.cookies.unwrap_or_default();
734 }
735 let joined = next
736 .members
737 .get(&next.peer)
738 .is_some_and(|member| member.status == 1);
739 self.control
740 .as_mut()
741 .ok_or_else(|| anyhow::anyhow!("Missing allocated control"))?
742 .peer_joined = Some(joined);
743 let control = self.control.as_mut().expect("personal control");
744 control.invitation_pending = invitation_pending;
745 control.pending_recipients = pending_recipients;
746 self.phase = if (joined || !invitation_pending) && local_status == 1 {
747 PersonalPhase::Joined
748 } else {
749 PersonalPhase::Ringing
750 };
751 self.active = Some(next);
752 Ok(if media_result? {
753 SyncProgress::MediaReady
754 } else {
755 SyncProgress::Waiting
756 })
757 }
758 pub fn take_media(
761 &mut self,
762 state: &AccountState,
763 current: AppAuthority,
764 connection_index: usize,
765 udp_index: usize,
766 ) -> Result<NegotiatedMedia> {
767 self.check(state, current)?;
768 let active = self
769 .active
770 .as_mut()
771 .ok_or_else(|| anyhow::anyhow!("Missing personal room"))?;
772 ensure!(
773 self.phase == PersonalPhase::Joined
774 && !active.media_taken
775 && ready(active, &self.own_username)?,
776 "Personal media not ready or already taken"
777 );
778 let relay = SelectedRelay::from_strategy(
779 active.identity.room,
780 active.identity.room_key,
781 active.identity.own_member as u16,
782 &active.strategy,
783 connection_index,
784 udp_index,
785 )?;
786 active.media_taken = true;
787 Ok(NegotiatedMedia {
788 relay,
789 peer_member: active.members[&active.peer].member,
790 frames_per_packet: (profile_shape(&active.engine, &active.channel)?[1] >> 8) as u8,
791 })
792 }
793 pub fn cancel(
796 &mut self,
797 client: &impl PersonalTransport,
798 state: &mut AccountState,
799 current: AppAuthority,
800 tasks: TaskIds,
801 ) -> Result<()> {
802 self.check(state, current)?;
803 ensure!(
804 self.phase == PersonalPhase::Ringing,
805 "Personal invitation no longer cancellable"
806 );
807 self.reserve(tasks)?;
808 self.phase = PersonalPhase::Ending;
809 let result = (|| {
810 let control = self
811 .control
812 .as_ref()
813 .ok_or_else(|| anyhow::anyhow!("Missing allocated control"))?;
814 let room = control.identity;
815 let ControlOrigin::Outgoing { invite_id } = control.origin else {
816 bail!("Incoming calls cannot use caller Cancel")
817 };
818 ensure!(
819 control.peer_joined == Some(false)
820 && control.invitation_pending
821 && !control.pending_recipients.is_empty(),
822 "Pending invitation provenance missing or already joined"
823 );
824 let request = personal::cancel(
825 &self.parameters.inputs(),
826 room.room,
827 room.room_key,
828 invite_id,
829 &control.pending_recipients,
830 personal::CancelReason::Local,
831 tasks.sdk,
832 )?;
833 let body = self.host(
834 client,
835 state,
836 CallHostRoute::Cancel,
837 request.body(),
838 tasks.host,
839 )?;
840 let outer = fields(&body)?;
841 let cancelled = fields(data(&outer, 1)?)?;
842 ensure!(
843 number(&cancelled, 1, None)? == room.room
844 && number(&cancelled, 2, None)? == room.room_key,
845 "Personal cancellation room mismatch"
846 );
847 Ok(())
848 })();
849 self.phase = if result.is_ok() {
850 PersonalPhase::Finished
851 } else {
852 PersonalPhase::Uncertain
853 };
854 result
855 }
856 pub fn finish_remote(
860 &mut self,
861 client: &impl PersonalTransport,
862 state: &mut AccountState,
863 current: AppAuthority,
864 tasks: TaskIds,
865 talk_success: bool,
866 ) -> Result<()> {
867 self.check(state, current)?;
868 ensure!(
869 self.phase == PersonalPhase::TerminalPending,
870 "No pending remote teardown"
871 );
872 self.reserve(tasks)?;
873 self.phase = PersonalPhase::Ending;
874 let result = (|| {
875 let control = self
876 .control
877 .as_mut()
878 .ok_or_else(|| anyhow::anyhow!("Missing remote termination control"))?;
879 let action = control
880 .terminal
881 .take()
882 .ok_or_else(|| anyhow::anyhow!("Remote termination already spent"))?;
883 let identity = control.identity;
884 match action {
885 RemoteTermination::Exit(reason) => {
886 let request = personal::exit_remote(
887 &self.parameters.inputs(),
888 identity,
889 reason,
890 talk_success,
891 tasks.sdk,
892 )?;
893 let parameters = AppFrameParameters {
894 sdk_version: self.parameters.sdk_version()?,
895 cloud_device_id: &self.parameters.cloud_device_id,
896 host_client_version: state.device.client_version as u32,
897 app_config_version: self.parameters.app_config_version,
898 };
899 let body = app_frame::prepare_transfer(
900 &self.app,
901 current,
902 CallOperation::Exit,
903 request.body.as_deref().expect("prepared remote Exit"),
904 ¶meters,
905 )?;
906 let reply =
907 self.host(client, state, CallHostRoute::Transfer, &body, tasks.host)?;
908 let exit = fields(&app_frame::decode_transfer_reply(
909 &self.app,
910 current,
911 CallOperation::Exit,
912 &reply,
913 )?)?;
914 ensure!(
915 number(&exit, 1, None)? == identity.room
916 && number(&exit, 2, None)? == identity.room_key
917 && number(&exit, 3, None)? == u64::from(identity.own_member),
918 "Remote Exit identity mismatch"
919 );
920 }
921 RemoteTermination::NoResponseCancel => {
922 let ControlOrigin::Outgoing { invite_id } = control.origin else {
923 bail!("Callee cannot cancel an original invitation")
924 };
925 ensure!(
926 control.invitation_pending
927 && !control.pending_recipients.is_empty()
928 && control
929 .pending_recipients
930 .iter()
931 .all(|recipient| recipient == &control.peer),
932 "Missing current pending-recipient provenance"
933 );
934 control.invitation_pending = false;
935 let recipients = std::mem::take(&mut control.pending_recipients);
936 let request = personal::cancel(
937 &self.parameters.inputs(),
938 identity.room,
939 identity.room_key,
940 invite_id,
941 &recipients,
942 personal::CancelReason::NoResponse,
943 tasks.sdk,
944 )?;
945 let body = self.host(
946 client,
947 state,
948 CallHostRoute::Cancel,
949 request.body(),
950 tasks.host,
951 )?;
952 let outer = fields(&body)?;
953 let cancelled = fields(data(&outer, 1)?)?;
954 ensure!(
955 number(&cancelled, 1, None)? == identity.room
956 && number(&cancelled, 2, None)? == identity.room_key,
957 "Remote cancellation room mismatch"
958 );
959 }
960 RemoteTermination::LocalStop(_) => {
961 bail!("Local-only stop must not submit a teardown")
962 }
963 }
964 Ok(())
965 })();
966 self.phase = if result.is_ok() {
967 PersonalPhase::Finished
968 } else {
969 PersonalPhase::Uncertain
970 };
971 result
972 }
973
974 pub fn exit(
978 &mut self,
979 client: &impl PersonalTransport,
980 state: &mut AccountState,
981 current: AppAuthority,
982 tasks: TaskIds,
983 talk_success: bool,
984 ) -> Result<()> {
985 self.check(state, current)?;
986 ensure!(
987 self.phase == PersonalPhase::Joined,
988 "Personal call not joined"
989 );
990 self.reserve(tasks)?;
991 self.phase = PersonalPhase::Ending;
992 let result = (|| {
993 let control = self
994 .control
995 .as_ref()
996 .ok_or_else(|| anyhow::anyhow!("Missing allocated control"))?;
997 ensure!(
998 matches!(control.origin, ControlOrigin::Incoming) || !control.invitation_pending,
999 "Joined control state unknown"
1000 );
1001 let identity = control.identity;
1002 let request = personal::exit(
1003 &self.parameters.inputs(),
1004 identity,
1005 1,
1006 talk_success,
1007 tasks.sdk,
1008 )?;
1009 let parameters = AppFrameParameters {
1010 sdk_version: self.parameters.sdk_version()?,
1011 cloud_device_id: &self.parameters.cloud_device_id,
1012 host_client_version: state.device.client_version as u32,
1013 app_config_version: self.parameters.app_config_version,
1014 };
1015 let body = app_frame::prepare_transfer(
1016 &self.app,
1017 current,
1018 CallOperation::Exit,
1019 request.body.as_deref().expect("prepared Exit body"),
1020 ¶meters,
1021 )?;
1022 let delegate = self.host(client, state, CallHostRoute::Transfer, &body, tasks.host)?;
1023 let exit = fields(&app_frame::decode_transfer_reply(
1024 &self.app,
1025 current,
1026 CallOperation::Exit,
1027 &delegate,
1028 )?)?;
1029 ensure!(
1030 number(&exit, 1, None)? == identity.room
1031 && number(&exit, 2, None)? == identity.room_key
1032 && number(&exit, 3, None)? == u64::from(identity.own_member),
1033 "Personal Exit identity mismatch"
1034 );
1035 Ok(())
1036 })();
1037 self.phase = if result.is_ok() {
1038 PersonalPhase::Finished
1039 } else {
1040 PersonalPhase::Uncertain
1041 };
1042 result
1043 }
1044}
1045
1046fn common(state: &AccountState, host_task: u32, body: &[u8]) -> Result<Vec<u8>> {
1047 CommonRequest {
1048 base_request: Some(wire::base_request(&state.device, state.uin, host_task)?),
1049 body: Some(body.to_vec()),
1050 unknown_fields: vec![],
1051 }
1052 .encode()
1053}
1054fn fields(raw: &[u8]) -> Result<Vec<Field>> {
1055 ensure!(raw.len() <= 64 * 1024, "Personal signaling body too large");
1056 super::signaling::decode_fields(raw)
1057}
1058fn data(values: &[Field], n: u32) -> Result<&[u8]> {
1059 p::optional_data(values, n)?.ok_or_else(|| anyhow::anyhow!("Missing personal bytes field {n}"))
1060}
1061fn number(values: &[Field], n: u32, default: Option<u64>) -> Result<u64> {
1062 match p::optional(values, n)? {
1063 Some(Value::Varint(v)) => Ok(*v),
1064 None => default.ok_or_else(|| anyhow::anyhow!("Missing personal integer {n}")),
1065 _ => bail!("Invalid personal integer {n}"),
1066 }
1067}
1068fn repeated(values: &[Field], n: u32, limit: usize) -> Result<Vec<&[u8]>> {
1069 let mut out = Vec::new();
1070 for f in values.iter().filter(|v| v.number == n) {
1071 ensure!(out.len() < limit, "Too many personal entries");
1072 if let Value::Bytes(raw) = &f.value {
1073 out.push(raw.as_slice());
1074 } else {
1075 bail!("Invalid personal entry wire type");
1076 }
1077 }
1078 Ok(out)
1079}
1080fn only(values: &[Field], allowed: &[u32]) -> Result<()> {
1081 ensure!(
1082 values.iter().all(|f| allowed.contains(&f.number)),
1083 "Unknown personal signaling field"
1084 );
1085 Ok(())
1086}
1087fn matches_room_key(values: &[Field], n: u32, expected: u64) -> Result<bool> {
1088 let value = number(values, n, Some(0))?;
1089 Ok(value == 0 || value == expected)
1090}
1091fn roster(raw: &[u8], own: &str, peer: &str, own_member: u16) -> Result<BTreeMap<String, Member>> {
1092 let values = fields(raw)?;
1093 roster_entries(repeated(&values, 2, 16)?, own, peer, own_member)
1094}
1095fn roster_entries(
1096 entries: Vec<&[u8]>,
1097 own: &str,
1098 peer: &str,
1099 own_member: u16,
1100) -> Result<BTreeMap<String, Member>> {
1101 let members = parse_roster_entries(entries, own, peer)?;
1102 ensure!(
1103 members.len() == 2
1104 && members
1105 .get(own)
1106 .is_some_and(|m| m.member == own_member && m.status == 1)
1107 && members
1108 .get(peer)
1109 .is_some_and(|m| (1..=3).contains(&m.status)),
1110 "Incomplete or unsupported personal roster"
1111 );
1112 Ok(members)
1113}
1114fn parse_roster_entries(
1115 entries: Vec<&[u8]>,
1116 own: &str,
1117 peer: &str,
1118) -> Result<BTreeMap<String, Member>> {
1119 ensure!(entries.len() <= 2, "Unsupported direct roster size");
1120 let mut members = BTreeMap::new();
1121 let mut ids = BTreeSet::new();
1122 for raw in entries {
1123 let values = fields(raw)?;
1124 only(&values, &[1, 2, 3, 4, 5, 6, 7, 8, 9])?;
1125 for tag in [4, 5, 9] {
1126 if let Some(Value::Varint(value)) = p::optional(&values, tag)? {
1127 u32::try_from(*value)?;
1128 } else {
1129 ensure!(
1130 p::optional(&values, tag)?.is_none(),
1131 "Invalid member scalar wire type"
1132 );
1133 }
1134 }
1135 for tag in [6, 8] {
1136 if let Some(value) = p::optional_data(&values, tag)? {
1137 std::str::from_utf8(value)?;
1138 }
1139 }
1140 let name = std::str::from_utf8(data(&values, 7)?)?;
1141 ensure!(
1142 (name == own || name == peer) && !members.contains_key(name),
1143 "Unexpected personal roster identity"
1144 );
1145 let member = u16::try_from(number(&values, 2, None)?)?;
1146 let status = u32::try_from(number(&values, 3, None)?)?;
1147 let openid = data(&values, 1)?.to_vec();
1148 ensure!(
1149 !openid.is_empty() && ids.insert(member),
1150 "Incomplete or duplicate personal member"
1151 );
1152 members.insert(
1153 name.to_owned(),
1154 Member {
1155 member,
1156 status,
1157 openid,
1158 },
1159 );
1160 }
1161 Ok(members)
1162}
1163fn cap(raw: &[u8]) -> Result<(u32, Vec<u8>, Vec<u8>)> {
1164 let values = fields(raw)?;
1165 only(&values, &[1, 2])?;
1166 let sequence = u32::try_from(number(&values, 1, Some(0))?)?;
1167 ensure!(sequence != 0, "Missing current call capability sequence");
1168 let content = fields(data(&values, 2)?)?;
1169 only(&content, &[1, 2])?;
1170 let engine = data(&content, 1)?.to_vec();
1171 let channel = data(&content, 2)?.to_vec();
1172 ensure!(
1173 !engine.is_empty() && !channel.is_empty(),
1174 "Partial call capabilities unsupported"
1175 );
1176 profile_shape(&engine, &channel)?;
1177 Ok((sequence, engine, channel))
1178}
1179fn av(raw: &[u8], own: u16, peer: u16) -> Result<(u32, AvSnapshot)> {
1180 let values = fields(raw)?;
1181 only(&values, &[1, 3, 4])?;
1182 let sequence = u32::try_from(number(&values, 3, Some(0))?)?;
1183 let mut members = BTreeMap::new();
1184 for raw in repeated(&values, 4, 2)? {
1185 let values = fields(raw)?;
1186 only(&values, &[1, 2, 3])?;
1187 let member = u16::try_from(number(&values, 1, None)?)?;
1188 ensure!(
1189 [own, peer].contains(&member) && !members.contains_key(&member),
1190 "Unexpected AV member"
1191 );
1192 members.insert(
1193 member,
1194 (
1195 u32::try_from(number(&values, 2, None)?)?,
1196 p::optional_data(&values, 3)?
1197 .map(|info| {
1198 ensure!(info.len() <= 1024, "Microphone metadata too large");
1199 Ok(info.to_vec())
1200 })
1201 .transpose()?,
1202 ),
1203 );
1204 }
1205 Ok((sequence, members))
1206}
1207fn profile_shape(engine: &[u8], channel: &[u8]) -> Result<[u64; 6]> {
1208 let ef = fields(engine)?;
1209 let cf = fields(channel)?;
1210 let mut seen = BTreeSet::new();
1211 for field in &ef {
1212 ensure!(
1213 (1..=42).contains(&field.number) && field.number != 41 && seen.insert(field.number),
1214 "Unsupported engine capability shape"
1215 );
1216 let Value::Varint(value) = field.value else {
1217 bail!("Invalid engine capability wire type")
1218 };
1219 if [1, 2, 3, 4, 25, 26, 27].contains(&field.number) {
1220 i32::try_from(value as i64)?;
1221 } else {
1222 u32::try_from(value)?;
1223 }
1224 }
1225 only(&cf, &[1, 2])?;
1226 Ok([
1227 number(&ef, 5, Some(0))?,
1228 number(&ef, 6, Some(0))?,
1229 number(&ef, 7, Some(0))?,
1230 number(&ef, 39, Some(0))?,
1231 u64::from(u32::try_from(number(&cf, 1, Some(0))?)?),
1232 u64::from(u32::try_from(number(&cf, 2, Some(0))?)?),
1233 ])
1234}
1235fn profile(engine: &[u8], channel: &[u8]) -> Result<()> {
1236 if !matches!(
1237 profile_shape(engine, channel)?,
1238 [17, 0x106 | 0x206, 0x800000, 0, 2, 3]
1239 ) {
1240 return Err(UnsupportedCallMedia.into());
1241 }
1242 Ok(())
1243}
1244fn microphone_update(room: &ActiveRoom, av: &AvSnapshot, own: &str) -> bool {
1247 let Some(local) = room.members.get(own) else {
1248 return false;
1249 };
1250 let Some(peer) = room.members.get(&room.peer) else {
1251 return false;
1252 };
1253 av.len() == 2
1254 && av
1255 .get(&local.member)
1256 .is_some_and(|(status, info)| *status == 1 && info.is_none())
1257 && av.get(&peer.member).is_some_and(|(status, _)| *status <= 1)
1258}
1259fn ready(room: &ActiveRoom, own: &str) -> Result<bool> {
1260 profile_shape(&room.engine, &room.channel)?;
1263 let own_id = room
1264 .members
1265 .get(own)
1266 .context("Missing current own member")?
1267 .member;
1268 let peer = room
1269 .members
1270 .get(&room.peer)
1271 .context("Missing current peer member")?;
1272 if peer.status != 1
1273 || room.av.len() != 2
1274 || !(room.media_taken && microphone_update(room, &room.av, own)
1275 || [own_id, peer.member].iter().all(|id| {
1276 room.av
1277 .get(id)
1278 .is_some_and(|(status, info)| *status == 1 && info.is_none())
1279 }))
1280 {
1281 return Ok(false);
1282 }
1283 profile(&room.engine, &room.channel)?;
1284 Ok(true)
1285}
1286fn replace_group(raw: &[u8], live_id: u64, group: &str) -> Result<Vec<u8>> {
1287 let values = fields(raw)?;
1288 let mut encoded = Vec::new();
1289 for field in values {
1290 let value = match field.number {
1291 2 => p::uint(2, live_id),
1292 4 => p::bytes(4, group.as_bytes()),
1293 _ => match field.value {
1294 Value::Varint(v) => p::uint(field.number, v),
1295 Value::Bytes(v) => p::bytes(field.number, &v),
1296 Value::Fixed64(_) | Value::Fixed32(_) => bail!("Unsupported group field wire type"),
1297 },
1298 };
1299 encoded.push(value);
1300 }
1301 p::message(&encoded)
1302}
1303
1304#[cfg(test)]
1305mod tests {
1306 use super::*;
1307
1308 pub(super) struct FakeTransport(
1309 pub(super) std::cell::RefCell<std::collections::VecDeque<(CallHostRoute, Vec<u8>)>>,
1310 );
1311 impl PersonalTransport for FakeTransport {
1312 fn call_rpc(
1313 &self,
1314 state: &mut AccountState,
1315 route: CallHostRoute,
1316 request: &[u8],
1317 ) -> Result<Vec<u8>> {
1318 let (expected, reply) = self
1319 .0
1320 .borrow_mut()
1321 .pop_front()
1322 .ok_or_else(|| anyhow::anyhow!("Unexpected fake call RPC"))?;
1323 ensure!(route == expected, "Unexpected fake call route");
1324 let request = CommonRequest::decode(request)?;
1325 ensure!(
1326 request.base_request.is_some() && request.body.is_some(),
1327 "Missing host common wrapper"
1328 );
1329 source_request(route, &request)?;
1330 state.cookie = vec![route.command() as u8];
1331 Ok(reply)
1332 }
1333 }
1334 fn source_request(route: CallHostRoute, request: &CommonRequest) -> Result<()> {
1338 let base = fields(
1339 request
1340 .base_request
1341 .as_deref()
1342 .context("Missing host base")?,
1343 )?;
1344 assert_eq!(data(&base, 1)?, &[0]);
1345 assert_eq!(number(&base, 2, None)?, 1);
1346 assert!(number(&base, 6, None)? > 0);
1347 let body = fields(request.body.as_deref().context("Missing host body")?)?;
1348 match route {
1349 CallHostRoute::AppAuth => {
1350 let device = data(&fields(data(&body, 1)?)?, 1)?.to_vec();
1351 assert_eq!(device.len(), 32);
1352 let raw = request.body.as_deref().unwrap();
1353 assert_eq!(&raw[..4], &[10, 34, 10, 32]);
1354 assert_eq!(raw.len(), 36);
1355 }
1356 CallHostRoute::Invite => {
1357 assert_eq!(data(&body, 1)?, b"peer");
1358 assert_eq!(number(&body, 2, None)?, 2);
1359 assert!(number(&body, 5, None)? > 0);
1360 assert!(p::optional(&body, 6)?.is_none());
1361 assert_eq!(data(&body, 7)?, b"voip-2p");
1362 let client = fields(data(&body, 4)?)?;
1363 assert_eq!(number(&client, 1, None)?, 0x050000cb);
1364 assert_eq!(data(&client, 2)?, b"test");
1365 assert_eq!(number(&client, 3, None)?, 0);
1366 assert_eq!(number(&client, 4, None)?, 1);
1367 let caps = fields(data(&client, 5)?)?;
1368 assert_eq!(number(&fields(data(&caps, 1)?)?, 5, None)?, 0x40000);
1369 assert_eq!(data(&caps, 2)?.len(), 12);
1370 }
1371 CallHostRoute::LiveSync => {
1372 assert_eq!(number(&body, 1, None)?, 99);
1373 data(&body, 2)?;
1374 assert_eq!(data(&body, 4)?, b"voip-2p");
1375 assert_eq!(number(&body, 5, None)?, 6);
1376 }
1377 CallHostRoute::Cancel => {
1378 let inner = fields(data(&body, 1)?)?;
1379 assert_eq!(number(&inner, 3, None)?, 7);
1380 assert_eq!(number(&inner, 4, None)?, 9);
1381 assert_eq!(number(&inner, 5, None)?, number(&body, 2, None)?);
1382 assert!([1, 2].contains(&number(&inner, 6, None)?));
1383 assert_eq!(number(&body, 3, None)?, 7);
1384 assert_eq!(data(&body, 4)?, b"peer");
1385 assert_eq!(data(&body, 5)?, b"voip-2p");
1386 }
1387 CallHostRoute::RingingAck => {
1388 let inner = fields(data(&body, 1)?)?;
1389 assert_eq!(number(&inner, 4, None)?, 1);
1390 assert_eq!(number(&inner, 5, None)?, 7);
1391 assert_eq!(number(&inner, 6, None)?, 9);
1392 assert_eq!(number(&inner, 7, None)?, 0);
1393 assert_eq!(number(&inner, 13, None)?, 1);
1394 let caps = fields(data(&inner, 12)?)?;
1395 assert_eq!(number(&fields(data(&caps, 1)?)?, 5, None)?, 0x40000);
1396 assert_eq!(data(&caps, 2)?.len(), 12);
1397 assert_eq!(data(&body, 3)?, b"voip-2p");
1398 }
1399 CallHostRoute::Transfer => {
1400 assert_eq!(number(&body, 1, None)?, 1901);
1401 let command = number(&body, 2, None)? as u32;
1402 let path: &[u8] = match command {
1403 3361 => b"/cloudim/cloud-bin/cloudvoipmtjoin",
1404 3095 => b"/cloudim/cloud-bin/cloudvoipmtreject",
1405 3380 => b"/cloudim/cloud-bin/cloudvoipmtexit",
1406 _ => bail!("Unexpected synthetic Cloud command"),
1407 };
1408 assert_eq!(data(&body, 3)?, b"wechat");
1409 assert_eq!(data(&body, 4)?, path);
1410 let frame = app_frame::TaskFrame::decode(data(&body, 5)?, command)?;
1411 assert_eq!(frame.integers[&2], frame.integers[&22]);
1412 assert!(frame.integers[&2] > 0);
1413 assert_eq!(frame.strings[&24], b"wechat");
1414 let clear = app_frame::lz4_decode(&frame.payload, 64 * 1024)?;
1415 let cloud = fields(&clear)?;
1416 let app_base = fields(data(&cloud, 1)?)?;
1417 assert_eq!(number(&app_base, 1, None)?, frame.integers[&22]);
1418 assert_eq!(data(&app_base, 2)?, b"Ldeviceid");
1419 let inner = fields(data(&cloud, 2)?)?;
1420 if command == 3361 {
1421 assert_eq!(number(&inner, 5, None)?, 2);
1422 assert_eq!(number(&inner, 6, None)?, 1);
1423 assert_eq!(number(&inner, 8, None)?, 0);
1424 assert_eq!(number(&inner, 11, None)?, 7);
1425 assert_eq!(number(&inner, 12, None)?, 9);
1426 } else {
1427 assert_eq!(number(&inner, 3, None)?, 7);
1428 assert_eq!(number(&inner, 4, None)?, 9);
1429 assert_eq!(number(&inner, 5, None)?, 0);
1430 let reason = number(&inner, 6, None)?;
1431 if command == 3095 {
1432 assert_eq!(reason, 1);
1433 } else {
1434 assert!([1, 5, 6, 7].contains(&reason));
1435 }
1436 }
1437 }
1438 }
1439 Ok(())
1440 }
1441 pub(super) fn host_reply(body: &[u8]) -> Vec<u8> {
1442 p::message(&[p::bytes(1, &[8, 0]), p::bytes(2, body)]).unwrap()
1443 }
1444 pub(super) fn state() -> AccountState {
1445 AccountState {
1446 schema: 1,
1447 device: crate::DeviceProfile {
1448 device_id: vec![b'x'; 16],
1449 device_type: "Linux".to_owned(),
1450 client_version: -227271398,
1451 },
1452 uin: 1,
1453 own_username: "own".to_owned(),
1454 cookie: vec![],
1455 session_key: vec![1; 16],
1456 request_key: vec![],
1457 response_key: vec![],
1458 auto_auth_key: vec![],
1459 key_info: vec![],
1460 database_password: vec![],
1461 auth_snapshot: crate::AuthSnapshot::default(),
1462 long_host: "localhost".to_owned(),
1463 short_host: "localhost".to_owned(),
1464 }
1465 }
1466 pub(super) fn authority() -> AppAuthority {
1467 AppAuthority {
1468 account_generation: 1,
1469 logout_epoch: 0,
1470 credential_digest: [4; 32],
1471 }
1472 }
1473
1474 pub(super) fn capabilities_for_codec(codec: u64) -> (Vec<u8>, Vec<u8>) {
1475 (
1476 p::message(&[p::uint(5, codec), p::uint(6, 0x206), p::uint(7, 0x800000)]).unwrap(),
1477 p::message(&[p::uint(1, 2), p::uint(2, 3)]).unwrap(),
1478 )
1479 }
1480 pub(super) fn parameters() -> VoiceParameters {
1481 let offer =
1482 super::super::voice_offer::VoiceOffer::from_owned_random(0x01000000, [5; 32]).unwrap();
1483 VoiceParameters {
1484 sdk: p::message(&[p::uint(1, 0x050000cb), p::bytes(2, b"test")]).unwrap(),
1485 group: p::message(&[
1486 p::bytes(1, b"wechat"),
1487 p::uint(2, 0),
1488 p::bytes(3, b"voip-2p"),
1489 p::bytes(4, &[]),
1490 p::uint(5, 1),
1491 p::bytes(6, &[]),
1492 p::uint(7, 0),
1493 ])
1494 .unwrap(),
1495 device: vec![],
1496 engine_capability: offer.engine_advertisement().to_vec(),
1497 channel_capability: offer.advertised_channel_identity().to_vec(),
1498 network_type: 0,
1499 sim_type: 0,
1500 video_length: 0,
1501 video_ratio: 0,
1502 nics: vec![],
1503 cloud_device_id: b"Ldeviceid".to_vec(),
1504 app_config_version: 1,
1505 }
1506 }
1507 pub(super) fn member(name: &str, id: u16, status: u32) -> Vec<u8> {
1508 p::message(&[
1509 p::bytes(1, name.as_bytes()),
1510 p::uint(2, u64::from(id)),
1511 p::uint(3, u64::from(status)),
1512 p::bytes(7, name.as_bytes()),
1513 ])
1514 .unwrap()
1515 }
1516 pub(super) fn av_status(sequence: u32, peer_status: u32) -> Vec<u8> {
1517 let own = p::message(&[p::uint(1, 0), p::uint(2, 1)]).unwrap();
1518 let peer = p::message(&[p::uint(1, 1), p::uint(2, u64::from(peer_status))]).unwrap();
1519 p::message(&[
1520 p::uint(3, u64::from(sequence)),
1521 p::bytes(4, &own),
1522 p::bytes(4, &peer),
1523 ])
1524 .unwrap()
1525 }
1526 pub(super) fn auth_reply() -> Vec<u8> {
1527 auth_reply_uin(17)
1528 }
1529 fn auth_reply_uin(uin: u64) -> Vec<u8> {
1530 let auth = p::message(&[p::bytes(9, b"server"), p::uint(11, 1)]).unwrap();
1531 let unify = p::message(&[p::bytes(2, &auth), p::bytes(3, &p::uint(1, uin))]).unwrap();
1532 host_reply(&p::message(&[p::bytes(1, &[8, 0]), p::bytes(2, &unify)]).unwrap())
1533 }
1534 fn invite_reply() -> Vec<u8> {
1535 invite_reply_profile(17, 2)
1536 }
1537 fn invite_reply_profile(codec: u64, peer_status: u32) -> Vec<u8> {
1538 invite_reply_full(codec, peer_status, 0)
1539 }
1540 fn invite_reply_full(codec: u64, peer_status: u32, peer_av: u32) -> Vec<u8> {
1541 let (engine, channel) = capabilities_for_codec(codec);
1542 let engine = if codec == 0 {
1544 p::message(&[p::uint(6, 0x206), p::uint(7, 0x800000)]).unwrap()
1545 } else {
1546 engine
1547 };
1548 let cap = p::message(&[
1549 p::uint(1, 1),
1550 p::bytes(
1551 2,
1552 &p::message(&[p::bytes(1, &engine), p::bytes(2, &channel)]).unwrap(),
1553 ),
1554 ])
1555 .unwrap();
1556 let address = p::message(&[p::bytes(1, b"127.0.0.1"), p::uint(2, 6000)]).unwrap();
1557 let connection = p::message(&[p::uint(1, 1), p::bytes(4, &address)]).unwrap();
1558 let strategy = p::message(&[
1559 p::bytes(10, &[7; 32]),
1560 p::bytes(13, &cap),
1561 p::uint(14, 0),
1562 p::bytes(15, &connection),
1563 ])
1564 .unwrap();
1565 let roster = p::message(&[
1566 p::uint(1, 1),
1567 p::bytes(2, &member("own", 0, 1)),
1568 p::bytes(2, &member("peer", 1, peer_status)),
1569 ])
1570 .unwrap();
1571 let client = p::message(&[
1572 p::bytes(1, &strategy),
1573 p::bytes(2, &roster),
1574 p::bytes(3, &av_status(1, peer_av)),
1575 p::uint(5, 99),
1576 p::bytes(6, b"old"),
1577 ])
1578 .unwrap();
1579 host_reply(
1580 &p::message(&[
1581 p::uint(1, 7),
1582 p::uint(2, 9),
1583 p::uint(3, 0),
1584 p::bytes(4, b"group"),
1585 p::bytes(5, &client),
1586 ])
1587 .unwrap(),
1588 )
1589 }
1590 fn with_result(reply: &[u8], recipient: &str, code: u64) -> Vec<u8> {
1591 let common = fields(reply).unwrap();
1592 let mut body = data(&common, 2).unwrap().to_vec();
1593 body.extend(p::bytes(
1594 8,
1595 &p::message(&[p::bytes(1, recipient.as_bytes()), p::uint(2, code)]).unwrap(),
1596 ));
1597 host_reply(&body)
1598 }
1599 fn initial_roster_reply(sequence: u32, own: Option<u32>, peer: Option<u32>) -> Vec<u8> {
1600 let mut list = vec![p::uint(1, u64::from(sequence))];
1601 if let Some(status) = own {
1602 list.push(p::bytes(2, &member("own", 0, status)));
1603 }
1604 if let Some(status) = peer {
1605 list.push(p::bytes(2, &member("peer", 1, status)));
1606 }
1607 let client = p::message(&[
1609 p::bytes(2, &p::message(&list).unwrap()),
1610 p::uint(5, 99),
1611 p::bytes(6, b"initial"),
1612 ])
1613 .unwrap();
1614 with_result(
1615 &host_reply(
1616 &p::message(&[
1617 p::uint(1, 7),
1618 p::uint(2, 9),
1619 p::uint(3, 0),
1620 p::bytes(4, b"group"),
1621 p::bytes(5, &client),
1622 ])
1623 .unwrap(),
1624 ),
1625 "peer",
1626 23,
1627 )
1628 }
1629 fn capability_sync(engine: &[u8], channel: &[u8]) -> Vec<u8> {
1630 let content = p::message(&[p::bytes(1, engine), p::bytes(2, channel)]).unwrap();
1631 let cap = p::message(&[p::uint(1, 2), p::bytes(2, &content)]).unwrap();
1632 let notify = p::message(&[
1633 p::uint(3, 7),
1634 p::uint(4, 9),
1635 p::bytes(
1636 5,
1637 &p::message(&[p::uint(1, 11), p::bytes(2, &cap)]).unwrap(),
1638 ),
1639 ])
1640 .unwrap();
1641 let message = p::message(&[p::uint(1, 10002), p::bytes(2, ¬ify)]).unwrap();
1642 host_reply(
1643 &p::message(&[
1644 p::bytes(
1645 1,
1646 &p::message(&[p::uint(1, 1), p::bytes(2, &message)]).unwrap(),
1647 ),
1648 p::bytes(2, b"bad-cursor"),
1649 ])
1650 .unwrap(),
1651 )
1652 }
1653 fn microphone_sync(sequence: u32, status: u32, info: Option<&[u8]>) -> Vec<u8> {
1654 let own = p::message(&[p::uint(1, 0), p::uint(2, 1)]).unwrap();
1655 let mut peer = vec![p::uint(1, 1), p::uint(2, u64::from(status))];
1656 if let Some(info) = info {
1657 peer.push(p::bytes(3, info));
1658 }
1659 let av = p::message(&[
1660 p::uint(3, u64::from(sequence)),
1661 p::bytes(4, &own),
1662 p::bytes(4, &p::message(&peer).unwrap()),
1663 ])
1664 .unwrap();
1665 let notify = p::message(&[
1666 p::uint(3, 7),
1667 p::uint(4, 9),
1668 p::bytes(5, &p::message(&[p::uint(1, 12), p::bytes(2, &av)]).unwrap()),
1669 ])
1670 .unwrap();
1671 let message = p::message(&[p::uint(1, 10002), p::bytes(2, ¬ify)]).unwrap();
1672 host_reply(
1673 &p::message(&[
1674 p::bytes(
1675 1,
1676 &p::message(&[p::uint(1, 1), p::bytes(2, &message)]).unwrap(),
1677 ),
1678 p::bytes(2, b"mic-update"),
1679 ])
1680 .unwrap(),
1681 )
1682 }
1683 fn joined_sync(extra_unknown: bool) -> Vec<u8> {
1684 joined_sync_profile(extra_unknown, None)
1685 }
1686 fn joined_sync_profile(extra_unknown: bool, codec: Option<u64>) -> Vec<u8> {
1687 let members = p::message(&[
1688 p::uint(1, 7),
1689 p::uint(2, 9),
1690 p::uint(3, 2),
1691 p::bytes(4, &member("own", 0, 1)),
1692 p::bytes(4, &member("peer", 1, 1)),
1693 ])
1694 .unwrap();
1695 let mut notify = vec![
1696 p::uint(3, 7),
1697 p::uint(4, 9),
1698 p::bytes(
1699 5,
1700 &p::message(&[p::uint(1, 10), p::bytes(2, &members)]).unwrap(),
1701 ),
1702 p::bytes(
1703 5,
1704 &p::message(&[p::uint(1, 12), p::bytes(2, &av_status(2, 1))]).unwrap(),
1705 ),
1706 ];
1707 if let Some(codec) = codec {
1708 let (engine, channel) = capabilities_for_codec(codec);
1709 let content = p::message(&[p::bytes(1, &engine), p::bytes(2, &channel)]).unwrap();
1710 let cap = p::message(&[p::uint(1, 2), p::bytes(2, &content)]).unwrap();
1711 notify.push(p::bytes(
1712 5,
1713 &p::message(&[p::uint(1, 11), p::bytes(2, &cap)]).unwrap(),
1714 ));
1715 }
1716 if extra_unknown {
1717 notify.push(p::bytes(
1718 5,
1719 &p::message(&[p::uint(1, 999), p::bytes(2, &[])]).unwrap(),
1720 ));
1721 }
1722 let message = p::message(&[
1723 p::uint(1, 10002),
1724 p::bytes(2, &p::message(¬ify).unwrap()),
1725 ])
1726 .unwrap();
1727 host_reply(
1728 &p::message(&[
1729 p::bytes(
1730 1,
1731 &p::message(&[p::uint(1, 1), p::bytes(2, &message)]).unwrap(),
1732 ),
1733 p::bytes(2, b"new"),
1734 ])
1735 .unwrap(),
1736 )
1737 }
1738 pub(super) fn exit_reply() -> Vec<u8> {
1739 exit_reply_uin(17)
1740 }
1741 fn exit_reply_uin(uin: u64) -> Vec<u8> {
1742 let exit = p::message(&[p::uint(1, 7), p::uint(2, 9), p::uint(3, 0)]).unwrap();
1743 let cloud =
1744 p::message(&[p::bytes(1, &[8, 0]), p::uint(2, uin), p::bytes(3, &exit)]).unwrap();
1745 let frame = app_frame::TaskFrame {
1746 command: 3380,
1747 integers: BTreeMap::from([
1748 (4, 0),
1749 (7, 0),
1750 (8, 0),
1751 (21, 0),
1752 (22, uin),
1753 (23, 0),
1754 (25, 0),
1755 (26, 0),
1756 ]),
1757 strings: BTreeMap::new(),
1758 payload: cloud,
1759 }
1760 .encode()
1761 .unwrap();
1762 host_reply(&p::message(&[p::uint(1, 0), p::bytes(2, &frame)]).unwrap())
1763 }
1764 #[test]
1765 fn fake_caller_auth_invite_full_sync_media_and_exit() {
1766 let transport = RecordingTransport {
1767 inner: FakeTransport(std::cell::RefCell::new(std::collections::VecDeque::from([
1768 (CallHostRoute::AppAuth, auth_reply_uin(u64::MAX)),
1769 (CallHostRoute::Invite, invite_reply()),
1770 (CallHostRoute::LiveSync, joined_sync(false)),
1771 (
1772 CallHostRoute::LiveSync,
1773 microphone_sync(3, 0, Some(&[8, 2])),
1774 ),
1775 (
1776 CallHostRoute::LiveSync,
1777 microphone_sync(4, 1, Some(&[8, 3])),
1778 ),
1779 (CallHostRoute::Transfer, exit_reply_uin(u64::MAX)),
1780 ]))),
1781 requests: Default::default(),
1782 };
1783 let mut state = state();
1784 let mut session = NativePersonalSession::authenticate(
1785 &transport,
1786 &mut state,
1787 authority(),
1788 1,
1789 [9; 32],
1790 parameters(),
1791 )
1792 .unwrap();
1793 session
1794 .invite(
1795 &transport,
1796 &mut state,
1797 authority(),
1798 "peer",
1799 123,
1800 TaskIds { sdk: 1, host: 2 },
1801 )
1802 .unwrap();
1803 assert_eq!(session.phase(), PersonalPhase::Ringing);
1804 assert_eq!(
1805 session
1806 .poll(
1807 &transport,
1808 &mut state,
1809 authority(),
1810 TaskIds { sdk: 2, host: 3 }
1811 )
1812 .unwrap(),
1813 SyncProgress::MediaReady
1814 );
1815 let media = session.take_media(&state, authority(), 0, 0).unwrap();
1816 assert_eq!(session.peer_microphone_muted(), Some(false));
1817 assert_eq!(media.peer_member, 1);
1818 assert_eq!(media.relay.room_id, 7);
1819 assert!(session.take_media(&state, authority(), 0, 0).is_err());
1820 for (sdk, host, status) in [(3, 4, 0), (4, 5, 1)] {
1821 assert_eq!(
1822 session
1823 .poll(&transport, &mut state, authority(), TaskIds { sdk, host })
1824 .unwrap(),
1825 SyncProgress::MediaReady
1826 );
1827 assert_eq!(session.phase(), PersonalPhase::Joined);
1828 assert_eq!(session.peer_microphone_muted(), Some(status == 0));
1829 let active = session.active.as_ref().unwrap();
1830 assert_eq!(active.av.get(&1).unwrap().0, status);
1831 assert!(active.media_taken);
1832 assert!(session.take_media(&state, authority(), 0, 0).is_err());
1833 }
1834 let active = session.active.as_ref().unwrap();
1835 let mut unsupported = active.av.clone();
1836 unsupported.get_mut(&1).unwrap().0 = 4;
1837 assert!(!microphone_update(
1838 active,
1839 &unsupported,
1840 &state.own_username
1841 ));
1842 session
1843 .exit(
1844 &transport,
1845 &mut state,
1846 authority(),
1847 TaskIds { sdk: 5, host: 6 },
1848 false,
1849 )
1850 .unwrap();
1851 assert_eq!(session.phase(), PersonalPhase::Finished);
1852 assert_eq!(session.peer_microphone_muted(), None);
1853 assert!(transport.inner.0.borrow().is_empty());
1854 let requests = transport.requests.borrow();
1855 let host_tasks = requests
1856 .iter()
1857 .map(|(_, raw)| {
1858 let common = CommonRequest::decode(raw).unwrap();
1859 number(
1860 &fields(common.base_request.as_deref().unwrap()).unwrap(),
1861 6,
1862 None,
1863 )
1864 .unwrap()
1865 })
1866 .collect::<Vec<_>>();
1867 assert_eq!(host_tasks, [1, 2, 3, 4, 5, 6]);
1868 let common = CommonRequest::decode(&requests.last().unwrap().1).unwrap();
1869 let delegate = fields(common.body.as_deref().unwrap()).unwrap();
1870 let frame = app_frame::TaskFrame::decode(data(&delegate, 5).unwrap(), 3380).unwrap();
1871 assert_eq!(frame.integers[&2], u64::MAX);
1872 assert_eq!(frame.integers[&22], u64::MAX);
1873 let common = fields(&session.app.common_session(authority()).unwrap()).unwrap();
1874 assert_eq!(number(&common, 4, None).unwrap(), u64::MAX);
1875 assert_eq!(data(&common, 5).unwrap(), b"server");
1876 assert_eq!(number(&common, 6, None).unwrap(), 1);
1877 assert!(p::optional(&common, 1).unwrap().is_none());
1878 }
1879 #[test]
1880 fn later_unknown_sync_item_prevents_cookie_and_partial_member_install() {
1881 let transport = FakeTransport(std::cell::RefCell::new(std::collections::VecDeque::from([
1882 (CallHostRoute::AppAuth, auth_reply()),
1883 (CallHostRoute::Invite, invite_reply()),
1884 (CallHostRoute::LiveSync, joined_sync(true)),
1885 ])));
1886 let mut state = state();
1887 let mut session = NativePersonalSession::authenticate(
1888 &transport,
1889 &mut state,
1890 authority(),
1891 1,
1892 [9; 32],
1893 parameters(),
1894 )
1895 .unwrap();
1896 session
1897 .invite(
1898 &transport,
1899 &mut state,
1900 authority(),
1901 "peer",
1902 123,
1903 TaskIds { sdk: 1, host: 2 },
1904 )
1905 .unwrap();
1906 assert!(session
1907 .poll(
1908 &transport,
1909 &mut state,
1910 authority(),
1911 TaskIds { sdk: 2, host: 3 }
1912 )
1913 .is_err());
1914 assert_eq!(session.phase(), PersonalPhase::Uncertain);
1915 let active = session.active.as_ref().unwrap();
1916 assert_eq!(active.cookies, b"old");
1917 assert_eq!(active.members["peer"].status, 2);
1918 assert!(session
1919 .poll(
1920 &transport,
1921 &mut state,
1922 authority(),
1923 TaskIds { sdk: 3, host: 4 }
1924 )
1925 .is_err());
1926 }
1927
1928 #[test]
1929 fn only_proven_unsent_poll_retains_known_room_for_exit() {
1930 struct StopTransport<'a> {
1931 inner: &'a FakeTransport,
1932 unsent: bool,
1933 }
1934 impl PersonalTransport for StopTransport<'_> {
1935 fn call_rpc(
1936 &self,
1937 state: &mut AccountState,
1938 route: CallHostRoute,
1939 request: &[u8],
1940 ) -> Result<Vec<u8>> {
1941 if route == CallHostRoute::LiveSync {
1942 if self.unsent {
1943 return Err(CallRpcNotSubmitted.into());
1944 }
1945 bail!("Ambiguous transport failure")
1946 }
1947 self.inner.call_rpc(state, route, request)
1948 }
1949 }
1950 for unsent in [true, false] {
1951 let transport =
1952 FakeTransport(std::cell::RefCell::new(std::collections::VecDeque::from([
1953 (CallHostRoute::AppAuth, auth_reply()),
1954 (CallHostRoute::Invite, invite_reply()),
1955 (CallHostRoute::LiveSync, joined_sync(false)),
1956 (CallHostRoute::Transfer, exit_reply()),
1957 ])));
1958 let mut state = state();
1959 let mut session = NativePersonalSession::authenticate(
1960 &transport,
1961 &mut state,
1962 authority(),
1963 1,
1964 [9; 32],
1965 parameters(),
1966 )
1967 .unwrap();
1968 session
1969 .invite(
1970 &transport,
1971 &mut state,
1972 authority(),
1973 "peer",
1974 123,
1975 TaskIds { sdk: 1, host: 2 },
1976 )
1977 .unwrap();
1978 session
1979 .poll(
1980 &transport,
1981 &mut state,
1982 authority(),
1983 TaskIds { sdk: 2, host: 3 },
1984 )
1985 .unwrap();
1986 let cookie = state.cookie.clone();
1987 let stop = StopTransport {
1988 inner: &transport,
1989 unsent,
1990 };
1991 assert!(session
1992 .poll(&stop, &mut state, authority(), TaskIds { sdk: 3, host: 4 })
1993 .is_err());
1994 assert_eq!(state.cookie, cookie);
1995 if unsent {
1996 assert_eq!(session.phase(), PersonalPhase::Joined);
1997 session
1998 .exit(
1999 &transport,
2000 &mut state,
2001 authority(),
2002 TaskIds { sdk: 4, host: 5 },
2003 false,
2004 )
2005 .unwrap();
2006 assert_eq!(session.phase(), PersonalPhase::Finished);
2007 } else {
2008 assert_eq!(session.phase(), PersonalPhase::Uncertain);
2009 }
2010 }
2011 }
2012
2013 #[test]
2014 fn pending_codec_zero_continues_sync_until_both_av_and_selected_codec_are_ready() {
2015 let transport = FakeTransport(std::cell::RefCell::new(std::collections::VecDeque::from([
2016 (CallHostRoute::AppAuth, auth_reply()),
2017 (
2018 CallHostRoute::Invite,
2019 with_result(&invite_reply_profile(0, 2), "peer", 23),
2020 ),
2021 (
2022 CallHostRoute::LiveSync,
2023 host_reply(&p::bytes(2, b"waiting")),
2024 ),
2025 (
2026 CallHostRoute::LiveSync,
2027 joined_sync_profile(false, Some(17)),
2028 ),
2029 ])));
2030 let mut state = state();
2031 let mut session = NativePersonalSession::authenticate(
2032 &transport,
2033 &mut state,
2034 authority(),
2035 1,
2036 [9; 32],
2037 parameters(),
2038 )
2039 .unwrap();
2040 session
2041 .invite(
2042 &transport,
2043 &mut state,
2044 authority(),
2045 "peer",
2046 123,
2047 TaskIds { sdk: 1, host: 2 },
2048 )
2049 .unwrap();
2050 assert_eq!(session.phase(), PersonalPhase::Ringing);
2051 assert!(!session.media_ready(&state, authority()).unwrap());
2052 assert!(session.take_media(&state, authority(), 0, 0).is_err());
2053 assert!(p::optional(
2054 &fields(&session.active.as_ref().unwrap().engine).unwrap(),
2055 5
2056 )
2057 .unwrap()
2058 .is_none());
2059 assert_eq!(
2060 session
2061 .poll(
2062 &transport,
2063 &mut state,
2064 authority(),
2065 TaskIds { sdk: 2, host: 3 }
2066 )
2067 .unwrap(),
2068 SyncProgress::Waiting
2069 );
2070 assert_eq!(session.active.as_ref().unwrap().cookies, b"waiting");
2071 assert_eq!(
2072 session
2073 .poll(
2074 &transport,
2075 &mut state,
2076 authority(),
2077 TaskIds { sdk: 3, host: 4 }
2078 )
2079 .unwrap(),
2080 SyncProgress::MediaReady
2081 );
2082 assert!(session.media_ready(&state, authority()).unwrap());
2083 assert_eq!(
2084 session
2085 .take_media(&state, authority(), 0, 0)
2086 .unwrap()
2087 .peer_member,
2088 1
2089 );
2090 }
2091 #[test]
2092 fn pending_capability_shapes_are_checked_before_cursor_installation() {
2093 let (engine, channel) = capabilities_for_codec(0);
2094 for (bad_engine, bad_channel) in [
2095 ([engine.clone(), p::uint(99, 0)].concat(), channel.clone()),
2096 ([engine.clone(), p::uint(6, 0)].concat(), channel.clone()),
2097 (p::bytes(6, b"wrong-wire"), channel.clone()),
2098 (
2099 engine.clone(),
2100 p::message(&[p::uint(1, u64::MAX), p::uint(2, 3)]).unwrap(),
2101 ),
2102 ] {
2103 let transport =
2104 FakeTransport(std::cell::RefCell::new(std::collections::VecDeque::from([
2105 (CallHostRoute::AppAuth, auth_reply()),
2106 (CallHostRoute::Invite, invite_reply_profile(0, 2)),
2107 (
2108 CallHostRoute::LiveSync,
2109 capability_sync(&bad_engine, &bad_channel),
2110 ),
2111 ])));
2112 let mut state = state();
2113 let mut session = NativePersonalSession::authenticate(
2114 &transport,
2115 &mut state,
2116 authority(),
2117 1,
2118 [9; 32],
2119 parameters(),
2120 )
2121 .unwrap();
2122 session
2123 .invite(
2124 &transport,
2125 &mut state,
2126 authority(),
2127 "peer",
2128 123,
2129 TaskIds { sdk: 1, host: 2 },
2130 )
2131 .unwrap();
2132 assert!(session
2133 .poll(
2134 &transport,
2135 &mut state,
2136 authority(),
2137 TaskIds { sdk: 2, host: 3 }
2138 )
2139 .is_err());
2140 assert_eq!(session.phase(), PersonalPhase::Uncertain);
2141 let active = session.active.as_ref().unwrap();
2142 assert_eq!(active.cookies, b"old");
2143 assert_eq!(active.cap_sequence, 1);
2144 assert_eq!(active.members["peer"].status, 2);
2145 assert!(session.take_media(&state, authority(), 0, 0).is_err());
2146 }
2147 }
2148 #[test]
2149 fn initial_full_roster_terminates_before_profile_and_recipient_result_is_not_overall_error() {
2150 for (own, peer, action) in [
2151 (
2152 Some(1),
2153 Some(6),
2154 RemoteTermination::Exit(personal::RemoteExitReason::PeerBusy),
2155 ),
2156 (
2157 Some(1),
2158 Some(7),
2159 RemoteTermination::Exit(personal::RemoteExitReason::PeerRejected),
2160 ),
2161 (
2162 Some(1),
2163 None,
2164 RemoteTermination::Exit(personal::RemoteExitReason::LastPeerLeft),
2165 ),
2166 (
2167 None,
2168 Some(1),
2169 RemoteTermination::LocalStop(LocalStopReason::SelfRemoved),
2170 ),
2171 (
2172 Some(2),
2173 Some(1),
2174 RemoteTermination::LocalStop(LocalStopReason::SelfRemoved),
2175 ),
2176 ] {
2177 let local = matches!(action, RemoteTermination::LocalStop(_));
2178 let mut replies = std::collections::VecDeque::from([
2179 (CallHostRoute::AppAuth, auth_reply()),
2180 (CallHostRoute::Invite, initial_roster_reply(1, own, peer)),
2181 ]);
2182 if !local {
2183 replies.push_back((CallHostRoute::Transfer, exit_reply()));
2184 }
2185 let transport = RecordingTransport {
2186 inner: FakeTransport(std::cell::RefCell::new(replies)),
2187 requests: Default::default(),
2188 };
2189 let mut state = state();
2190 let mut session = NativePersonalSession::authenticate(
2191 &transport,
2192 &mut state,
2193 authority(),
2194 1,
2195 [9; 32],
2196 parameters(),
2197 )
2198 .unwrap();
2199 session
2200 .invite(
2201 &transport,
2202 &mut state,
2203 authority(),
2204 "peer",
2205 123,
2206 TaskIds { sdk: 1, host: 2 },
2207 )
2208 .unwrap();
2209 assert_eq!(session.control.as_ref().unwrap().terminal, Some(action));
2210 assert!(session.active.is_none());
2211 assert!(!session.media_ready(&state, authority()).unwrap());
2212 if local {
2213 assert_eq!(session.phase(), PersonalPhase::Finished);
2214 } else {
2215 assert_eq!(session.phase(), PersonalPhase::TerminalPending);
2216 session
2217 .finish_remote(
2218 &transport,
2219 &mut state,
2220 authority(),
2221 TaskIds { sdk: 2, host: 3 },
2222 false,
2223 )
2224 .unwrap();
2225 let expected = match action {
2226 RemoteTermination::Exit(reason) => reason.wire(),
2227 _ => unreachable!(),
2228 };
2229 assert_eq!(
2230 recorded_exit_reason(&transport.requests.borrow().last().unwrap().1),
2231 expected
2232 );
2233 }
2234 assert_eq!(session.phase(), PersonalPhase::Finished);
2235 assert!(transport.inner.0.borrow().is_empty());
2236 }
2237 }
2238 #[test]
2239 fn initial_zero_sequence_or_unknown_membership_is_not_terminal_proof() {
2240 for (sequence, own, peer) in [
2241 (0, None, Some(1)),
2242 (1, Some(1), Some(99)),
2243 (1, Some(1), Some(8)),
2244 ] {
2245 let transport =
2246 FakeTransport(std::cell::RefCell::new(std::collections::VecDeque::from([
2247 (CallHostRoute::AppAuth, auth_reply()),
2248 (
2249 CallHostRoute::Invite,
2250 initial_roster_reply(sequence, own, peer),
2251 ),
2252 ])));
2253 let mut state = state();
2254 let mut session = NativePersonalSession::authenticate(
2255 &transport,
2256 &mut state,
2257 authority(),
2258 1,
2259 [9; 32],
2260 parameters(),
2261 )
2262 .unwrap();
2263 assert!(session
2264 .invite(
2265 &transport,
2266 &mut state,
2267 authority(),
2268 "peer",
2269 123,
2270 TaskIds { sdk: 1, host: 2 }
2271 )
2272 .is_err());
2273 assert_eq!(session.phase(), PersonalPhase::Uncertain);
2274 assert!(session.control.as_ref().unwrap().terminal.is_none());
2275 assert!(session
2276 .finish_remote(
2277 &transport,
2278 &mut state,
2279 authority(),
2280 TaskIds { sdk: 2, host: 3 },
2281 false
2282 )
2283 .is_err());
2284 assert!(transport.0.borrow().is_empty());
2285 }
2286 }
2287 #[test]
2288 fn pending_unselected_control_is_not_media_admission_and_cancels_once() {
2289 let cancel = host_reply(
2290 &p::message(&[p::bytes(
2291 1,
2292 &p::message(&[p::uint(1, 7), p::uint(2, 9)]).unwrap(),
2293 )])
2294 .unwrap(),
2295 );
2296 let transport = FakeTransport(std::cell::RefCell::new(std::collections::VecDeque::from([
2297 (CallHostRoute::AppAuth, auth_reply()),
2298 (CallHostRoute::Invite, invite_reply_profile(0, 2)),
2299 (CallHostRoute::Cancel, cancel),
2300 ])));
2301 let mut state = state();
2302 let mut session = NativePersonalSession::authenticate(
2303 &transport,
2304 &mut state,
2305 authority(),
2306 1,
2307 [9; 32],
2308 parameters(),
2309 )
2310 .unwrap();
2311 session
2312 .invite(
2313 &transport,
2314 &mut state,
2315 authority(),
2316 "peer",
2317 123,
2318 TaskIds { sdk: 1, host: 2 },
2319 )
2320 .unwrap();
2321 assert!(!session.media_ready(&state, authority()).unwrap());
2322 assert!(session.has_allocated_control());
2323 assert_eq!(session.phase(), PersonalPhase::Ringing);
2324 assert!(session.take_media(&state, authority(), 0, 0).is_err());
2325 struct CheckCancel<'a>(&'a FakeTransport);
2326 impl PersonalTransport for CheckCancel<'_> {
2327 fn call_rpc(
2328 &self,
2329 state: &mut AccountState,
2330 route: CallHostRoute,
2331 request: &[u8],
2332 ) -> Result<Vec<u8>> {
2333 assert_eq!(route, CallHostRoute::Cancel);
2334 let common = CommonRequest::decode(request)?;
2335 let outer = fields(common.body.as_deref().unwrap())?;
2336 let inner = fields(data(&outer, 1)?)?;
2337 assert_eq!(number(&inner, 3, None)?, 7);
2338 assert_eq!(number(&inner, 4, None)?, 9);
2339 assert_eq!(number(&inner, 5, None)?, 123);
2340 assert_eq!(data(&outer, 4)?, b"peer");
2341 self.0.call_rpc(state, route, request)
2342 }
2343 }
2344 session
2345 .cancel(
2346 &CheckCancel(&transport),
2347 &mut state,
2348 authority(),
2349 TaskIds { sdk: 2, host: 3 },
2350 )
2351 .unwrap();
2352 assert_eq!(session.phase(), PersonalPhase::Finished);
2353 assert!(transport.0.borrow().is_empty());
2354 assert!(session
2355 .cancel(
2356 &transport,
2357 &mut state,
2358 authority(),
2359 TaskIds { sdk: 3, host: 4 }
2360 )
2361 .is_err());
2362 }
2363 #[test]
2364 fn missing_cleanup_reply_stays_uncertain_and_is_not_retried() {
2365 let transport = FakeTransport(std::cell::RefCell::new(std::collections::VecDeque::from([
2366 (CallHostRoute::AppAuth, auth_reply()),
2367 (CallHostRoute::Invite, invite_reply_profile(0, 2)),
2368 ])));
2369 let mut state = state();
2370 let mut session = NativePersonalSession::authenticate(
2371 &transport,
2372 &mut state,
2373 authority(),
2374 1,
2375 [9; 32],
2376 parameters(),
2377 )
2378 .unwrap();
2379 session
2380 .invite(
2381 &transport,
2382 &mut state,
2383 authority(),
2384 "peer",
2385 123,
2386 TaskIds { sdk: 1, host: 2 },
2387 )
2388 .unwrap();
2389 assert!(session
2390 .cancel(
2391 &transport,
2392 &mut state,
2393 authority(),
2394 TaskIds { sdk: 2, host: 3 }
2395 )
2396 .is_err());
2397 assert_eq!(session.phase(), PersonalPhase::Uncertain);
2398 assert!(session
2399 .cancel(
2400 &transport,
2401 &mut state,
2402 authority(),
2403 TaskIds { sdk: 3, host: 4 }
2404 )
2405 .is_err());
2406 assert!(transport.0.borrow().is_empty());
2407 }
2408 pub(super) fn roster_sync(
2409 sequence: u32,
2410 own: Option<u32>,
2411 peer: Option<u32>,
2412 unknown_after: bool,
2413 ) -> Vec<u8> {
2414 let mut roster = vec![
2415 p::uint(1, 7),
2416 p::uint(2, 9),
2417 p::uint(3, u64::from(sequence)),
2418 ];
2419 if let Some(status) = own {
2420 roster.push(p::bytes(4, &member("own", 0, status)));
2421 }
2422 if let Some(status) = peer {
2423 roster.push(p::bytes(4, &member("peer", 1, status)));
2424 }
2425 let mut notify = vec![
2426 p::uint(3, 7),
2427 p::uint(4, 9),
2428 p::bytes(
2429 5,
2430 &p::message(&[p::uint(1, 10), p::bytes(2, &p::message(&roster).unwrap())]).unwrap(),
2431 ),
2432 ];
2433 if unknown_after {
2434 notify.push(p::bytes(
2435 5,
2436 &p::message(&[p::uint(1, 999), p::bytes(2, &[])]).unwrap(),
2437 ));
2438 }
2439 let message = p::message(&[
2440 p::uint(1, 10002),
2441 p::bytes(2, &p::message(¬ify).unwrap()),
2442 ])
2443 .unwrap();
2444 host_reply(
2445 &p::message(&[
2446 p::bytes(
2447 1,
2448 &p::message(&[p::uint(1, 1), p::bytes(2, &message)]).unwrap(),
2449 ),
2450 p::bytes(2, b"terminal-cursor"),
2451 ])
2452 .unwrap(),
2453 )
2454 }
2455 pub(super) struct RecordingTransport {
2456 pub(super) inner: FakeTransport,
2457 pub(super) requests: std::cell::RefCell<Vec<(CallHostRoute, Vec<u8>)>>,
2458 }
2459 impl PersonalTransport for RecordingTransport {
2460 fn call_rpc(
2461 &self,
2462 state: &mut AccountState,
2463 route: CallHostRoute,
2464 request: &[u8],
2465 ) -> Result<Vec<u8>> {
2466 self.requests.borrow_mut().push((route, request.to_vec()));
2467 self.inner.call_rpc(state, route, request)
2468 }
2469 }
2470 fn caller_with(
2471 transport: &impl PersonalTransport,
2472 state: &mut AccountState,
2473 ) -> NativePersonalSession {
2474 let mut session = NativePersonalSession::authenticate(
2475 transport,
2476 state,
2477 authority(),
2478 1,
2479 [9; 32],
2480 parameters(),
2481 )
2482 .unwrap();
2483 session
2484 .invite(
2485 transport,
2486 state,
2487 authority(),
2488 "peer",
2489 123,
2490 TaskIds { sdk: 1, host: 2 },
2491 )
2492 .unwrap();
2493 session
2494 }
2495 pub(super) fn recorded_exit_reason(request: &[u8]) -> u64 {
2496 let common = CommonRequest::decode(request).unwrap();
2497 let delegate = fields(common.body.as_deref().unwrap()).unwrap();
2498 assert_eq!(number(&delegate, 2, None).unwrap(), 3380);
2499 let frame = app_frame::TaskFrame::decode(data(&delegate, 5).unwrap(), 3380).unwrap();
2500 let cloud = fields(&app_frame::lz4_decode(&frame.payload, 64 * 1024).unwrap()).unwrap();
2501 number(&fields(data(&cloud, 2).unwrap()).unwrap(), 6, None).unwrap()
2502 }
2503 #[test]
2504 fn caller_remote_last_peer_uses_one_exit_with_distinct_reason_and_no_local_cancel() {
2505 for (peer, wire) in [(4, 5), (6, 7), (7, 6)] {
2506 let transport = RecordingTransport {
2507 inner: FakeTransport(std::cell::RefCell::new(std::collections::VecDeque::from([
2508 (CallHostRoute::AppAuth, auth_reply()),
2509 (CallHostRoute::Invite, invite_reply()),
2510 (
2511 CallHostRoute::LiveSync,
2512 roster_sync(2, Some(1), Some(peer), false),
2513 ),
2514 (CallHostRoute::Transfer, exit_reply()),
2515 ]))),
2516 requests: Default::default(),
2517 };
2518 let mut state = state();
2519 let mut session = caller_with(&transport, &mut state);
2520 assert!(matches!(
2521 session
2522 .poll(
2523 &transport,
2524 &mut state,
2525 authority(),
2526 TaskIds { sdk: 2, host: 3 }
2527 )
2528 .unwrap(),
2529 SyncProgress::Terminal(RemoteTermination::Exit(_))
2530 ));
2531 assert_eq!(session.phase(), PersonalPhase::TerminalPending);
2532 assert!(session.take_media(&state, authority(), 0, 0).is_err());
2533 session
2534 .finish_remote(
2535 &transport,
2536 &mut state,
2537 authority(),
2538 TaskIds { sdk: 3, host: 4 },
2539 false,
2540 )
2541 .unwrap();
2542 let requests = transport.requests.borrow();
2543 let last = requests.last().unwrap();
2544 assert_eq!(last.0, CallHostRoute::Transfer);
2545 assert_eq!(recorded_exit_reason(&last.1), wire);
2546 assert_eq!(session.phase(), PersonalPhase::Finished);
2547 assert!(session
2548 .finish_remote(
2549 &transport,
2550 &mut state,
2551 authority(),
2552 TaskIds { sdk: 4, host: 5 },
2553 false
2554 )
2555 .is_err());
2556 assert!(transport.inner.0.borrow().is_empty());
2557 }
2558 }
2559 #[test]
2560 fn no_response_cancel_uses_original_id_and_direct_invite_timeout_wire_reason() {
2561 let cancel = host_reply(&p::bytes(
2562 1,
2563 &p::message(&[p::uint(1, 7), p::uint(2, 9)]).unwrap(),
2564 ));
2565 let transport = RecordingTransport {
2566 inner: FakeTransport(std::cell::RefCell::new(std::collections::VecDeque::from([
2567 (CallHostRoute::AppAuth, auth_reply()),
2568 (CallHostRoute::Invite, invite_reply()),
2569 (
2570 CallHostRoute::LiveSync,
2571 roster_sync(2, Some(1), Some(8), false),
2572 ),
2573 (CallHostRoute::Cancel, cancel),
2574 ]))),
2575 requests: Default::default(),
2576 };
2577 let mut state = state();
2578 let mut session = caller_with(&transport, &mut state);
2579 assert_eq!(
2580 session
2581 .poll(
2582 &transport,
2583 &mut state,
2584 authority(),
2585 TaskIds { sdk: 2, host: 3 }
2586 )
2587 .unwrap(),
2588 SyncProgress::Terminal(RemoteTermination::NoResponseCancel)
2589 );
2590 session
2591 .finish_remote(
2592 &transport,
2593 &mut state,
2594 authority(),
2595 TaskIds { sdk: 3, host: 4 },
2596 false,
2597 )
2598 .unwrap();
2599 let requests = transport.requests.borrow();
2600 let last = requests.last().unwrap();
2601 assert_eq!(last.0, CallHostRoute::Cancel);
2602 let common = CommonRequest::decode(&last.1).unwrap();
2603 let outer = fields(common.body.as_deref().unwrap()).unwrap();
2604 assert_eq!(number(&outer, 2, None).unwrap(), 123);
2605 assert_eq!(repeated(&outer, 4, 16).unwrap(), vec![b"peer".as_slice()]);
2606
2607 let inner = fields(data(&outer, 1).unwrap()).unwrap();
2608 assert_eq!(number(&inner, 5, None).unwrap(), 123);
2609 assert_eq!(number(&inner, 6, None).unwrap(), 2);
2610 }
2611 #[test]
2612 fn previously_joined_peer_permanently_disarms_original_pending_cancel() {
2613 let transport = FakeTransport(std::cell::RefCell::new(std::collections::VecDeque::from([
2614 (CallHostRoute::AppAuth, auth_reply()),
2615 (CallHostRoute::Invite, invite_reply()),
2616 (CallHostRoute::LiveSync, joined_sync(false)),
2617 (
2618 CallHostRoute::LiveSync,
2619 roster_sync(3, Some(1), Some(8), false),
2620 ),
2621 ])));
2622 let mut state = state();
2623 let mut session = caller_with(&transport, &mut state);
2624 assert_eq!(
2625 session
2626 .poll(
2627 &transport,
2628 &mut state,
2629 authority(),
2630 TaskIds { sdk: 2, host: 3 }
2631 )
2632 .unwrap(),
2633 SyncProgress::MediaReady
2634 );
2635 assert!(!session.control.as_ref().unwrap().invitation_pending);
2636 assert!(session
2637 .control
2638 .as_ref()
2639 .unwrap()
2640 .pending_recipients
2641 .is_empty());
2642 assert!(session
2643 .poll(
2644 &transport,
2645 &mut state,
2646 authority(),
2647 TaskIds { sdk: 3, host: 4 }
2648 )
2649 .is_err());
2650 assert_eq!(session.phase(), PersonalPhase::Uncertain);
2651 assert_eq!(session.active.as_ref().unwrap().cookies, b"new");
2652 assert!(session
2653 .finish_remote(
2654 &transport,
2655 &mut state,
2656 authority(),
2657 TaskIds { sdk: 4, host: 5 },
2658 false
2659 )
2660 .is_err());
2661 assert!(transport.0.borrow().is_empty());
2662 }
2663}