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