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 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 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}