1use std::sync::atomic::{AtomicU8, Ordering};
9use std::sync::Mutex;
10
11use mail4agent_messenger::{HttpMethod, HttpResponseDescriptor, OutgoingRequest};
12use serde_json::{json, Value};
13
14use super::live::Live;
15use super::matrix::SyncMode;
16use crate::error::Result;
17
18const PATH_SIMPLIFIED: &str = "/_matrix/client/unstable/org.matrix.simplified_msc3575/sync";
19const PATH_MSC4186: &str = "/_matrix/client/unstable/org.matrix.msc4186/sync";
20
21const UNDECIDED: u8 = 0;
22const ON: u8 = 1;
23const OFF: u8 = 2;
24
25pub struct Sliding {
26 state: AtomicU8,
27 forced_v3: AtomicU8,
28 path: Mutex<&'static str>,
29 pos: Mutex<Option<String>>,
30 to_device_since: Mutex<Option<String>>,
31}
32
33impl Default for Sliding {
34 fn default() -> Self {
35 Self::new()
36 }
37}
38
39impl Sliding {
40 pub fn new() -> Self {
41 Self { state: AtomicU8::new(UNDECIDED), forced_v3: AtomicU8::new(0), path: Mutex::new(PATH_SIMPLIFIED), pos: Mutex::new(None), to_device_since: Mutex::new(None) }
42 }
43
44 pub fn set_mode(&self, mode: SyncMode) {
45 self.forced_v3.store((mode == SyncMode::V3) as u8, Ordering::Release);
46 }
47
48 pub fn active(&self) -> bool {
49 self.state.load(Ordering::Acquire) == ON
50 }
51
52 pub fn decide(&self, live: &Live) {
54 if self.forced_v3.load(Ordering::Acquire) == 1 {
55 self.state.store(OFF, Ordering::Release);
56 return;
57 }
58 let exec = live.exec();
59 let offered = exec.get_json("/_matrix/client/versions").ok().filter(|(s, _)| *s == 200).map(|(_, v)| v);
60 let feature = |k: &str| offered.as_ref().and_then(|v| v.get("unstable_features")).and_then(|f| f.get(k)).and_then(Value::as_bool).unwrap_or(false);
61 if feature("org.matrix.simplified_msc3575") {
62 *self.path.lock().unwrap_or_else(|e| e.into_inner()) = PATH_SIMPLIFIED;
63 self.state.store(ON, Ordering::Release);
64 } else if feature("org.matrix.msc4186") {
65 *self.path.lock().unwrap_or_else(|e| e.into_inner()) = PATH_MSC4186;
66 self.state.store(ON, Ordering::Release);
67 } else {
68 self.state.store(OFF, Ordering::Release);
69 }
70 }
71
72 fn body(&self) -> Value {
73 let since = self.to_device_since.lock().unwrap_or_else(|e| e.into_inner()).clone();
74 let mut to_device = json!({ "enabled": true });
75 if let Some(s) = since {
76 to_device["since"] = json!(s);
77 }
78 json!({
79 "conn_id": "m4a",
80 "lists": { "all": { "ranges": [[0, 99]], "timeline_limit": 20, "required_state": [["*", "*"]] } },
81 "extensions": { "to_device": to_device, "e2ee": { "enabled": true }, "account_data": { "enabled": true } }
82 })
83 }
84
85 pub fn sync(&self, live: &Live, request: &OutgoingRequest) -> Result<Option<HttpResponseDescriptor>> {
87 if self.state.load(Ordering::Acquire) != ON {
88 return Ok(None);
89 }
90 let timeout = request.query.iter().find(|(n, _)| n == "timeout").map(|(_, v)| v.clone()).unwrap_or_else(|| "0".into());
91 let since = request.query.iter().find(|(n, _)| n == "since").map(|(_, v)| v.clone());
93 let mut pos = self.pos.lock().unwrap_or_else(|e| e.into_inner()).clone().filter(|p| since.as_deref() == Some(p.as_str()));
94 for attempt in 0..2 {
95 let mut query = vec![("timeout".to_string(), timeout.clone())];
96 if let Some(p) = &pos {
97 query.push(("pos".to_string(), p.clone()));
98 }
99 let sliding_request = OutgoingRequest {
100 id: request.id.clone(),
101 method: HttpMethod::Post,
102 path: self.path.lock().unwrap_or_else(|e| e.into_inner()).to_string(),
103 query,
104 body: Some(self.body()),
105 kind: request.kind,
106 };
107 let r = live.execute(&sliding_request)?;
108 let errcode = serde_json::from_slice::<Value>(&r.body).ok().and_then(|v| v.get("errcode").and_then(Value::as_str).map(str::to_string));
109 match (r.status, errcode.as_deref()) {
110 (200, _) => {
111 let v: Value = serde_json::from_slice(&r.body).unwrap_or(Value::Null);
112 let v3 = to_v3(&v);
113 *self.pos.lock().unwrap_or_else(|e| e.into_inner()) = v.get("pos").and_then(Value::as_str).map(str::to_string);
114 if let Some(n) = v.pointer("/extensions/to_device/next_batch").and_then(Value::as_str) {
115 *self.to_device_since.lock().unwrap_or_else(|e| e.into_inner()) = Some(n.to_string());
116 }
117 return Ok(Some(HttpResponseDescriptor { status: 200, body: serde_json::to_vec(&v3).unwrap_or_default() }));
118 }
119 (400, Some("M_UNKNOWN_POS")) if attempt == 0 => {
120 *self.pos.lock().unwrap_or_else(|e| e.into_inner()) = None;
122 *self.to_device_since.lock().unwrap_or_else(|e| e.into_inner()) = None;
123 pos = None;
124 }
125 (404 | 405 | 501, _) | (_, Some("M_UNRECOGNIZED")) => {
126 self.state.store(OFF, Ordering::Release);
128 return Ok(None);
129 }
130 _ => return Ok(Some(r)),
131 }
132 }
133 Ok(None)
134 }
135}
136
137pub fn to_v3(v: &Value) -> Value {
139 let mut join = serde_json::Map::new();
140 let mut invite = serde_json::Map::new();
141 if let Some(rooms) = v.get("rooms").and_then(Value::as_object) {
142 for (id, room) in rooms {
143 if let Some(stripped) = room.get("invite_state").and_then(Value::as_array) {
144 invite.insert(id.clone(), json!({ "invite_state": { "events": stripped } }));
145 continue;
146 }
147 let timeline = room.get("timeline").cloned().unwrap_or_else(|| json!([]));
148 let mut entry = json!({
149 "timeline": { "events": timeline, "limited": room.get("limited").and_then(Value::as_bool).unwrap_or(false) },
150 "state": { "events": room.get("required_state").cloned().unwrap_or_else(|| json!([])) },
151 });
152 if let Some(p) = room.get("prev_batch") {
153 entry["timeline"]["prev_batch"] = p.clone();
154 }
155 let n = room.get("notification_count").and_then(Value::as_u64);
156 let h = room.get("highlight_count").and_then(Value::as_u64);
157 if n.is_some() || h.is_some() {
158 entry["unread_notifications"] = json!({ "notification_count": n.unwrap_or(0), "highlight_count": h.unwrap_or(0) });
159 }
160 join.insert(id.clone(), entry);
161 }
162 }
163 let ext = v.get("extensions").cloned().unwrap_or(Value::Null);
164 let mut out = json!({
165 "next_batch": v.get("pos").and_then(Value::as_str).unwrap_or(""),
166 "rooms": { "join": join, "invite": invite, "leave": {} },
167 "to_device": { "events": ext.pointer("/to_device/events").cloned().unwrap_or_else(|| json!([])) },
168 "account_data": { "events": ext.pointer("/account_data/global").cloned().unwrap_or_else(|| json!([])) },
169 });
170 if let Some(d) = ext.pointer("/e2ee/device_lists") {
171 out["device_lists"] = d.clone();
172 }
173 if let Some(c) = ext.pointer("/e2ee/device_one_time_keys_count") {
174 out["device_one_time_keys_count"] = c.clone();
175 }
176 if let Some(f) = ext.pointer("/e2ee/device_unused_fallback_key_types") {
177 out["device_unused_fallback_key_types"] = f.clone();
178 }
179 out
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn a_sliding_answer_becomes_a_v3_answer() {
188 let v = json!({
189 "pos": "p7",
190 "rooms": {
191 "!a:s": { "required_state": [{"type":"m.room.create"}], "timeline": [{"type":"m.room.message","event_id":"$1"}], "limited": true, "prev_batch": "pb", "notification_count": 2 },
192 "!i:s": { "invite_state": [{"type":"m.room.member","state_key":"@me:s"}] }
193 },
194 "extensions": {
195 "to_device": { "next_batch": "t1", "events": [{"type":"m.room.encrypted"}] },
196 "e2ee": { "device_lists": {"changed":["@x:s"]}, "device_one_time_keys_count": {"signed_curve25519": 5}, "device_unused_fallback_key_types": [] },
197 "account_data": { "global": [{"type":"m.direct"}] }
198 }
199 });
200 let o = to_v3(&v);
201 assert_eq!(o["next_batch"], "p7");
202 assert_eq!(o["rooms"]["join"]["!a:s"]["timeline"]["events"][0]["event_id"], "$1");
203 assert_eq!(o["rooms"]["join"]["!a:s"]["timeline"]["limited"], true);
204 assert_eq!(o["rooms"]["join"]["!a:s"]["state"]["events"][0]["type"], "m.room.create");
205 assert_eq!(o["rooms"]["join"]["!a:s"]["unread_notifications"]["notification_count"], 2);
206 assert!(o["rooms"]["join"].get("!i:s").is_none());
207 assert_eq!(o["rooms"]["invite"]["!i:s"]["invite_state"]["events"][0]["state_key"], "@me:s");
208 assert_eq!(o["to_device"]["events"][0]["type"], "m.room.encrypted");
209 assert_eq!(o["device_lists"]["changed"][0], "@x:s");
210 assert_eq!(o["device_one_time_keys_count"]["signed_curve25519"], 5);
211 assert_eq!(o["account_data"]["events"][0]["type"], "m.direct");
212 let e = to_v3(&json!({"pos":"p8"}));
213 assert_eq!(e["next_batch"], "p8");
214 assert!(e["rooms"]["join"].as_object().unwrap().is_empty());
215 }
216}