Skip to main content

wechat_protocol/
logout.rs

1//! Authenticated logout for the supported native profile.
2//!
3//! The current typed wrapper uses CGI282; the historical embedded CGI133 table
4//! does not describe this request. Optional request field2 has no assigned
5//! semantics and is deliberately absent. Acceptance does not establish saved
6//! device revocation or deletion from the phone's device inventory.
7use super::*;
8
9const CGI: u16 = 282;
10const PATH: &str = "/cgi-bin/micromsg-bin/logout";
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct LogoutReply {
14    pub service_code: i32,
15    pub accepted: bool,
16}
17
18fn request(state: &AccountState) -> Result<Vec<u8>> {
19    state.validate()?;
20    Ok(p::bytes(
21        1,
22        &wire::base_request(&state.device, state.uin, 0)?,
23    ))
24}
25
26fn response(bytes: &[u8]) -> Result<LogoutReply> {
27    let fields = p::decode(bytes)?;
28    let base = p::decode(p::data(&fields, 1)?)?;
29    let raw = p::scalar(&base, 1)?;
30    let code = raw as u32 as i32;
31    ensure!(
32        raw <= u32::MAX as u64 || raw == code as i64 as u64,
33        "Invalid logout response status"
34    );
35    Ok(LogoutReply {
36        service_code: code,
37        accepted: code == 0,
38    })
39}
40
41impl NativeClient {
42    /// Submit one authenticated logout without renewal, retry or login fallback.
43    /// An error can follow submission and must be treated as an unknown outcome.
44    pub fn logout(&self, state: &mut AccountState) -> Result<LogoutReply> {
45        let body = request(state)?;
46        response(&self.authenticated_rpc(state, CGI, PATH, &body)?)
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53    fn state() -> AccountState {
54        AccountState {
55            schema: 1,
56            device: DeviceProfile {
57                device_id: b"0123456789abcdef".to_vec(),
58                device_type: "UnifiedPCLinux x86_64".into(),
59                client_version: -227271398,
60            },
61            uin: 42,
62            own_username: "synthetic_user".into(),
63            cookie: vec![],
64            session_key: vec![1; 16],
65            request_key: vec![2; 24],
66            response_key: vec![3; 24],
67            auto_auth_key: vec![4; 16],
68            key_info: vec![],
69            database_password: vec![],
70            auth_snapshot: Default::default(),
71            long_host: "long.weixin.qq.com".into(),
72            short_host: "short.weixin.qq.com".into(),
73        }
74    }
75    #[test]
76    fn current_cgi_and_base_only_presence_match_the_typed_wrapper() {
77        assert_eq!(CGI, 282);
78        assert_eq!(PATH, "/cgi-bin/micromsg-bin/logout");
79        let encoded = request(&state()).unwrap();
80        let outer = p::decode(&encoded).unwrap();
81        assert_eq!(outer.len(), 1);
82        assert!(p::optional(&outer, 2).unwrap().is_none());
83        let base = p::decode(p::data(&outer, 1).unwrap()).unwrap();
84        assert_eq!(p::data(&base, 1).unwrap(), &[0]);
85        assert_eq!(p::scalar(&base, 2).unwrap(), 42);
86        assert_eq!(p::data(&base, 3).unwrap(), b"0123456789abcdef");
87        assert_eq!(p::scalar(&base, 4).unwrap() as u32 as i32, -227271398);
88        assert_eq!(p::data(&base, 5).unwrap(), b"UnifiedPCLinux x86_64");
89        assert_eq!(p::scalar(&base, 6).unwrap(), 0);
90    }
91    #[test]
92    fn response_requires_a_unique_initialized_base_and_retains_service_rejection() {
93        let accepted = response(&p::bytes(1, &p::uint(1, 0))).unwrap();
94        assert!(accepted.accepted);
95        let rejected = response(&p::bytes(1, &p::int32(1, -13))).unwrap();
96        assert_eq!(rejected.service_code, -13);
97        assert!(!rejected.accepted);
98        for invalid in [
99            vec![],
100            p::bytes(1, &[]),
101            p::uint(1, 0),
102            p::bytes(1, &p::uint(1, 1u64 << 32)),
103            [p::bytes(1, &p::uint(1, 0)), p::bytes(1, &p::uint(1, 0))].concat(),
104        ] {
105            assert!(response(&invalid).is_err());
106        }
107    }
108    #[test]
109    fn invalid_account_material_fails_before_any_transport() {
110        let mut invalid = state();
111        invalid.uin = 0;
112        assert!(request(&invalid).is_err());
113    }
114}