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