wechat_protocol/calls/client/
termination.rs1use super::personal::RemoteExitReason;
4use anyhow::{bail, ensure, Result};
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum LocalStopReason {
8 SelfRemoved,
9 HandledElsewhere { sdk_end: u32 },
10 PeerLeftBeforeJoin,
11}
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum RemoteTermination {
14 LocalStop(LocalStopReason),
15 Exit(RemoteExitReason),
16 NoResponseCancel,
17}
18#[derive(Clone, Copy)]
19pub(super) enum Origin {
20 Caller,
21 Callee,
22}
23
24pub(super) fn classify(
27 origin: Origin,
28 local_status: u32,
29 own: Option<u32>,
30 peer: Option<u32>,
31 pending_invitation: bool,
32) -> Result<Option<RemoteTermination>> {
33 ensure!(
34 own.is_none_or(|status| (1..=10).contains(&status))
35 && peer.is_none_or(|status| (1..=10).contains(&status)),
36 "Unsupported call member status"
37 );
38 if local_status == 1 && own != Some(1) {
39 return Ok(Some(RemoteTermination::LocalStop(
40 LocalStopReason::SelfRemoved,
41 )));
42 }
43 match origin {
44 Origin::Caller => ensure!(local_status == 1, "Unsupported caller local state"),
45 Origin::Callee => {
46 ensure!(
47 (1..=3).contains(&local_status),
48 "Unsupported callee local state"
49 );
50 if local_status != 1 {
51 if let Some(status @ (1 | 4 | 6 | 7 | 8)) = own {
52 let sdk_end = match status {
53 1 => 13,
54 6 => 15,
55 7 => 14,
56 _ => 8,
57 };
58 return Ok(Some(RemoteTermination::LocalStop(
59 LocalStopReason::HandledElsewhere { sdk_end },
60 )));
61 }
62 ensure!(
63 matches!(own, Some(2 | 3)),
64 "Unsupported pending callee membership"
65 );
66 }
67 }
68 }
69 if matches!(peer, Some(1..=3)) {
70 return Ok(None);
71 }
72 if matches!(origin, Origin::Callee) && local_status != 1 {
73 return Ok(Some(RemoteTermination::LocalStop(
74 LocalStopReason::PeerLeftBeforeJoin,
75 )));
76 }
77 let reason = match (origin, peer) {
78 (Origin::Caller, Some(6)) => RemoteExitReason::PeerBusy,
79 (Origin::Caller, Some(7)) => RemoteExitReason::PeerRejected,
80 (Origin::Caller, Some(8)) => {
81 ensure!(
82 pending_invitation,
83 "Missing pending invitation for no-response cancellation"
84 );
85 return Ok(Some(RemoteTermination::NoResponseCancel));
86 }
87 (_, None | Some(4..=10)) => RemoteExitReason::LastPeerLeft,
88 _ => bail!("Unsupported remote member transition"),
89 };
90 Ok(Some(RemoteTermination::Exit(reason)))
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96 #[test]
97 fn pending_callee_handles_elsewhere_only_for_source_predicate() {
98 for local in [2, 3] {
99 for (own, end) in [(1, 13), (4, 8), (6, 15), (7, 14), (8, 8)] {
100 assert_eq!(
101 classify(Origin::Callee, local, Some(own), Some(1), false).unwrap(),
102 Some(RemoteTermination::LocalStop(
103 LocalStopReason::HandledElsewhere { sdk_end: end }
104 ))
105 );
106 }
107 }
108 assert!(classify(Origin::Callee, 3, Some(5), Some(1), false).is_err());
109 assert_eq!(
110 classify(Origin::Callee, 3, Some(3), Some(1), false).unwrap(),
111 None
112 );
113 }
114 #[test]
115 fn unsupported_status_or_origin_does_not_become_terminal_proof() {
116 assert!(classify(Origin::Caller, 1, Some(1), Some(11), true).is_err());
117 assert!(classify(Origin::Callee, 3, None, Some(1), false).is_err());
118 assert!(classify(Origin::Caller, 3, Some(3), Some(4), true).is_err());
119 }
120}