geph5_client/
control_prot.rs

1use std::{
2    convert::Infallible,
3    sync::LazyLock,
4    time::{Duration, SystemTime},
5};
6
7use anyctx::AnyCtx;
8use async_trait::async_trait;
9use chrono::{NaiveDate, NaiveDateTime};
10use geph5_broker_protocol::{
11    puzzle::solve_puzzle, AccountLevel, ExitDescriptor, NetStatus, VoucherInfo,
12};
13
14use nanorpc::{nanorpc_derive, JrpcRequest, JrpcResponse, RpcService, RpcTransport};
15use parking_lot::Mutex;
16use serde::{Deserialize, Serialize};
17use slab::Slab;
18
19use crate::{
20    broker::get_net_status, broker_client, client::CtxField, logging::get_json_logs,
21    stats::stat_get_num, traffcount::TRAFF_COUNT, updates::get_update_manifest, Config,
22};
23
24#[nanorpc_derive]
25#[async_trait]
26pub trait ControlProtocol {
27    async fn conn_info(&self) -> ConnInfo;
28    async fn stat_num(&self, stat: String) -> f64;
29    async fn start_time(&self) -> SystemTime;
30    async fn stop(&self);
31
32    async fn recent_logs(&self) -> Vec<String>;
33
34    // broker-proxying stuff
35
36    async fn check_secret(&self, secret: String) -> Result<bool, String>;
37    async fn user_info(&self, secret: String) -> Result<UserInfo, String>;
38    async fn start_registration(&self) -> Result<usize, String>;
39    async fn delete_account(&self, secret: String) -> Result<(), String>;
40    async fn poll_registration(&self, idx: usize) -> Result<RegistrationProgress, String>;
41    async fn convert_legacy_account(
42        &self,
43        username: String,
44        password: String,
45    ) -> Result<String, String>;
46    async fn stat_history(&self, stat: String) -> Result<Vec<f64>, String>;
47
48    async fn net_status(&self) -> Result<NetStatus, String>;
49    async fn latest_news(&self, lang: String) -> Result<Vec<NewsItem>, String>;
50    async fn price_points(&self) -> Result<Vec<(u32, f64)>, String>;
51    async fn payment_methods(&self) -> Result<Vec<String>, String>;
52    async fn create_payment(
53        &self,
54        secret: String,
55        days: u32,
56        method: String,
57    ) -> Result<String, String>;
58    async fn get_free_voucher(&self, secret: String) -> Result<Option<VoucherInfo>, String>;
59    async fn redeem_voucher(&self, secret: String, code: String) -> Result<i32, String>;
60    async fn export_debug_pack(
61        &self,
62        email: Option<String>,
63        contents: String,
64    ) -> Result<(), String>;
65
66    async fn get_update_manifest(&self) -> Result<(serde_json::Value, String), String>;
67}
68
69#[derive(Serialize, Deserialize, Clone, Debug)]
70#[serde(tag = "state")]
71pub enum ConnInfo {
72    Disconnected,
73    Connecting,
74    Connected(ConnectedInfo),
75}
76
77#[derive(Serialize, Deserialize, Clone, Debug)]
78pub struct ConnectedInfo {
79    pub protocol: String,
80    pub bridge: String,
81
82    pub exit: ExitDescriptor,
83}
84
85#[derive(Serialize, Deserialize, Clone, Debug)]
86pub struct UserInfo {
87    pub user_id: u64,
88    pub level: AccountLevel,
89
90    pub recurring: bool,
91    pub expiry: Option<u64>,
92}
93
94pub struct ControlProtocolImpl {
95    pub ctx: AnyCtx<Config>,
96}
97
98pub static CURRENT_CONN_INFO: CtxField<Mutex<ConnInfo>> = |_| Mutex::new(ConnInfo::Disconnected);
99
100static REGISTRATIONS: LazyLock<Mutex<Slab<RegistrationProgress>>> =
101    LazyLock::new(|| Mutex::new(Slab::new()));
102
103#[derive(Serialize, Deserialize, Clone)]
104pub struct RegistrationProgress {
105    pub progress: f64,
106    pub secret: Option<String>,
107}
108
109#[async_trait]
110impl ControlProtocol for ControlProtocolImpl {
111    async fn conn_info(&self) -> ConnInfo {
112        self.ctx.get(CURRENT_CONN_INFO).lock().clone()
113    }
114
115    async fn stat_num(&self, stat: String) -> f64 {
116        stat_get_num(&self.ctx, &stat)
117    }
118
119    async fn start_time(&self) -> SystemTime {
120        static START_TIME: CtxField<SystemTime> = |_| SystemTime::now();
121        *self.ctx.get(START_TIME)
122    }
123
124    async fn stop(&self) {
125        std::thread::spawn(move || {
126            std::thread::sleep(Duration::from_millis(100));
127            std::process::exit(0);
128        });
129    }
130
131    async fn recent_logs(&self) -> Vec<String> {
132        get_json_logs().split("\n").map(|s| s.to_string()).collect()
133    }
134
135    async fn check_secret(&self, secret: String) -> Result<bool, String> {
136        let res = broker_client(&self.ctx)
137            .map_err(|e| format!("{:?}", e))?
138            .get_user_info_by_cred(geph5_broker_protocol::Credential::Secret(secret))
139            .await
140            .map_err(|e| format!("{:?}", e))?
141            .map_err(|e| format!("{:?}", e))?;
142        Ok(res.is_some())
143    }
144
145    async fn user_info(&self, secret: String) -> Result<UserInfo, String> {
146        let res = broker_client(&self.ctx)
147            .map_err(|e| format!("{:?}", e))?
148            .get_user_info_by_cred(geph5_broker_protocol::Credential::Secret(secret))
149            .await
150            .map_err(|e| format!("{:?}", e))?
151            .map_err(|e| format!("{:?}", e))?
152            .ok_or_else(|| "no such user".to_string())?;
153        Ok(UserInfo {
154            user_id: res.user_id,
155            level: if res.plus_expires_unix.is_some() {
156                AccountLevel::Plus
157            } else {
158                AccountLevel::Free
159            },
160            expiry: res.plus_expires_unix,
161            recurring: res.recurring,
162        })
163    }
164
165    async fn start_registration(&self) -> Result<usize, String> {
166        let (puzzle, difficulty) = broker_client(&self.ctx)
167            .map_err(|e| format!("{:?}", e))?
168            .get_puzzle()
169            .await
170            .map_err(|e| format!("{:?}", e))?;
171        tracing::debug!(puzzle, difficulty, "got puzzle");
172        let idx = REGISTRATIONS.lock().insert(RegistrationProgress {
173            progress: 0.0,
174            secret: None,
175        });
176        let ctx = self.ctx.clone();
177        smolscale::spawn(async move {
178            loop {
179                let fallible = async {
180                    let solution = {
181                        let puzzle = puzzle.clone();
182                        smol::unblock(move || {
183                            solve_puzzle(&puzzle, difficulty, |progress| {
184                                REGISTRATIONS.lock()[idx] = RegistrationProgress {
185                                    progress,
186                                    secret: None,
187                                }
188                            })
189                        })
190                        .await
191                    };
192                    let secret = broker_client(&ctx)?
193                        .register_user_secret(puzzle.clone(), solution)
194                        .await?
195                        .map_err(|e| anyhow::anyhow!(e))?;
196                    REGISTRATIONS.lock()[idx] = RegistrationProgress {
197                        progress: 1.0,
198                        secret: Some(secret.clone()),
199                    };
200                    anyhow::Ok(secret)
201                };
202                if let Err(err) = fallible.await {
203                    tracing::warn!(err = debug(err), "restarting registration")
204                } else {
205                    break;
206                }
207            }
208        })
209        .detach();
210        Ok(idx)
211    }
212
213    async fn delete_account(&self, secret: String) -> Result<(), String> {
214        tracing::debug!("FROM delete_account: secret={secret}");
215        broker_client(&self.ctx)
216            .map_err(|e| format!("{:?}", e))?
217            .delete_account(secret)
218            .await
219            .map_err(|e| format!("BROKER TRANSPORT ERROR: {:?}", e))?
220            .map_err(|e| format!("ERROR FROM BROKER {:?}", e))?;
221        Ok(())
222    }
223
224    async fn poll_registration(&self, idx: usize) -> Result<RegistrationProgress, String> {
225        tracing::debug!(idx, "polling registration");
226        let registers = REGISTRATIONS.lock();
227        registers
228            .get(idx)
229            .cloned()
230            .ok_or_else(|| "no such registration".to_string())
231    }
232
233    async fn convert_legacy_account(
234        &self,
235        username: String,
236        password: String,
237    ) -> Result<String, String> {
238        Ok(broker_client(&self.ctx)
239            .map_err(|e| format!("{:?}", e))?
240            .upgrade_to_secret(geph5_broker_protocol::Credential::LegacyUsernamePassword {
241                username,
242                password,
243            })
244            .await
245            .map_err(|e| format!("{:?}", e))?
246            .map_err(|e| format!("{:?}", e))?)
247    }
248
249    async fn stat_history(&self, stat: String) -> Result<Vec<f64>, String> {
250        if stat != "traffic" {
251            return Err(format!("bad: {stat}"));
252        }
253        Ok(self.ctx.get(TRAFF_COUNT).read().unwrap().speed_history())
254    }
255
256    async fn net_status(&self) -> Result<NetStatus, String> {
257        let resp = get_net_status(&self.ctx)
258            .await
259            .map_err(|e| format!("{:?}", e))?;
260        Ok(resp)
261    }
262
263    async fn latest_news(&self, lang: String) -> Result<Vec<NewsItem>, String> {
264        let (manifest, _) = get_update_manifest().await.map_err(|e| e.to_string())?;
265        let news = manifest["news"]
266            .as_array()
267            .ok_or_else(|| "No news array".to_string())?;
268
269        let mut out = Vec::new();
270
271        for item in news {
272            let important = item["important"].as_bool().unwrap_or_default();
273            let date_str = item["date"]
274                .as_str()
275                .ok_or_else(|| "No or invalid 'date' field in news item".to_string())?;
276
277            let naive_date = NaiveDate::parse_from_str(date_str, "%Y-%m-%d")
278                .map_err(|_| format!("Invalid date format: {}", date_str))?;
279
280            let naive_dt: NaiveDateTime = naive_date
281                .and_hms_opt(0, 0, 0)
282                .ok_or_else(|| "Unable to create NaiveDateTime from date".to_string())?;
283            let date_unix = naive_dt.and_utc().timestamp() as u64;
284
285            let localized = item[&lang]
286                .as_object()
287                .ok_or_else(|| format!("No localized data for language '{}'", lang))?;
288
289            let title = localized["title"]
290                .as_str()
291                .ok_or_else(|| "Missing 'title' in localized news data".to_string())?;
292            let contents = localized["contents"]
293                .as_str()
294                .ok_or_else(|| "Missing 'contents' in localized news data".to_string())?;
295
296            out.push(NewsItem {
297                title: title.to_string(),
298                date_unix,
299                contents: contents.to_string(),
300                important,
301            });
302        }
303
304        Ok(out)
305    }
306
307    async fn get_free_voucher(&self, secret: String) -> Result<Option<VoucherInfo>, String> {
308        let client = broker_client(&self.ctx).map_err(|e| format!("{:?}", e))?;
309        Ok(client
310            .get_free_voucher(secret)
311            .await
312            .map_err(|s| s.to_string())?
313            .map_err(|s| s.to_string())?)
314    }
315
316    async fn redeem_voucher(&self, secret: String, code: String) -> Result<i32, String> {
317        let client = broker_client(&self.ctx).map_err(|e| format!("{:?}", e))?;
318
319        // Call the broker's redeem_voucher method directly with the secret
320        client
321            .redeem_voucher(secret, code)
322            .await
323            .map_err(|s| s.to_string())?
324            .map_err(|s| s.to_string())
325    }
326
327    async fn price_points(&self) -> Result<Vec<(u32, f64)>, String> {
328        let client = broker_client(&self.ctx).map_err(|e| format!("{:?}", e))?;
329        Ok(client
330            .raw_price_points()
331            .await
332            .map_err(|s| s.to_string())?
333            .map_err(|s| s.to_string())?
334            .into_iter()
335            .map(|(a, b)| (a, b as f64 / 100.0))
336            .collect())
337    }
338
339    async fn payment_methods(&self) -> Result<Vec<String>, String> {
340        let client = broker_client(&self.ctx).map_err(|e| format!("{:?}", e))?;
341        Ok(client
342            .payment_methods()
343            .await
344            .map_err(|s| s.to_string())?
345            .map_err(|s| s.to_string())?)
346    }
347
348    async fn create_payment(
349        &self,
350        secret: String,
351        days: u32,
352        method: String,
353    ) -> Result<String, String> {
354        let client = broker_client(&self.ctx).map_err(|e| format!("{:?}", e))?;
355        Ok(client
356            .create_payment(secret, days, method)
357            .await
358            .map_err(|s| s.to_string())?
359            .map_err(|s| s.to_string())?)
360    }
361
362    async fn export_debug_pack(
363        &self,
364        email: Option<String>,
365        contents: String,
366    ) -> Result<(), String> {
367        let client = broker_client(&self.ctx).map_err(|e| format!("{:?}", e))?;
368        client
369            .upload_debug_pack(email, contents)
370            .await
371            .map_err(|s| s.to_string())?
372            .map_err(|s| s.to_string())?;
373        Ok(())
374    }
375
376    async fn get_update_manifest(&self) -> Result<(serde_json::Value, String), String> {
377        get_update_manifest().await.map_err(|e| format!("{:?}", e))
378    }
379}
380
381pub struct DummyControlProtocolTransport(pub ControlService<ControlProtocolImpl>);
382
383#[async_trait]
384impl RpcTransport for DummyControlProtocolTransport {
385    type Error = Infallible;
386
387    async fn call_raw(&self, req: JrpcRequest) -> Result<JrpcResponse, Self::Error> {
388        Ok(self.0.respond_raw(req).await)
389    }
390}
391
392#[derive(Serialize, Deserialize, Clone, Debug)]
393pub struct NewsItem {
394    pub title: String,
395    pub date_unix: u64,
396    pub contents: String,
397    pub important: bool,
398}