wechat_protocol/
logout.rs1use 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 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}