Skip to content

Commit 3f992af

Browse files
committed
Add embedded webserver for config management
1 parent 706d33f commit 3f992af

File tree

3 files changed

+149
-14
lines changed

3 files changed

+149
-14
lines changed

src/config.rs

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
use bluer::Address;
22
use serde::de::{self, Deserializer, Error as DeError, Visitor};
3-
use serde::Deserialize;
3+
use serde::{Deserialize, Serialize};
4+
use simplelog::*;
45
use std::fmt::{self, Display};
6+
use std::fs;
57
use std::path::PathBuf;
68
use std::str::FromStr;
9+
use toml_edit::{value, DocumentMut};
710

8-
#[derive(clap::ValueEnum, Default, Debug, PartialEq, PartialOrd, Clone, Copy, Deserialize)]
11+
#[derive(
12+
clap::ValueEnum, Default, Debug, PartialEq, PartialOrd, Clone, Copy, Deserialize, Serialize,
13+
)]
914
pub enum HexdumpLevel {
1015
#[default]
1116
Disabled,
@@ -16,7 +21,7 @@ pub enum HexdumpLevel {
1621
All,
1722
}
1823

19-
#[derive(Debug, Clone)]
24+
#[derive(Debug, Clone, Serialize)]
2025
pub struct UsbId {
2126
pub vid: u16,
2227
pub pid: u16,
@@ -36,6 +41,12 @@ impl std::str::FromStr for UsbId {
3641
}
3742
}
3843

44+
impl fmt::Display for UsbId {
45+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46+
write!(f, "{:x}:{:x}", self.vid, self.pid)
47+
}
48+
}
49+
3950
impl<'de> Deserialize<'de> for UsbId {
4051
fn deserialize<D>(deserializer: D) -> Result<UsbId, D::Error>
4152
where
@@ -76,7 +87,7 @@ where
7687
}
7788
}
7889

79-
#[derive(Debug, Clone, Deserialize)]
90+
#[derive(Debug, Clone, Deserialize, Serialize)]
8091
#[serde(default)]
8192
pub struct AppConfig {
8293
pub advertise: bool,
@@ -149,12 +160,68 @@ impl Default for AppConfig {
149160
}
150161
}
151162

152-
pub fn load_config(config_file: PathBuf) -> Result<AppConfig, Box<dyn std::error::Error>> {
153-
let file_config: AppConfig = config::Config::builder()
154-
.add_source(config::File::from(config_file).required(false))
155-
.build()?
156-
.try_deserialize()
157-
.unwrap_or_default();
163+
impl AppConfig {
164+
pub fn load(config_file: PathBuf) -> Result<Self, Box<dyn std::error::Error>> {
165+
use ::config::File;
166+
let file_config: AppConfig = ::config::Config::builder()
167+
.add_source(File::from(config_file).required(false))
168+
.build()?
169+
.try_deserialize()
170+
.unwrap_or_default();
171+
172+
Ok(file_config)
173+
}
174+
175+
pub fn save(&self, config_file: PathBuf) {
176+
debug!("Saving config: {:?}", self);
177+
let raw = fs::read_to_string(&config_file).unwrap_or_default();
178+
let mut doc = raw.parse::<DocumentMut>().unwrap_or_else(|_| {
179+
// if the file doesn't exists or there is parse error, create a new one
180+
DocumentMut::new()
181+
});
158182

159-
Ok(file_config)
183+
doc["advertise"] = value(self.advertise);
184+
doc["debug"] = value(self.debug);
185+
doc["hexdump_level"] = value(format!("{:?}", self.hexdump_level));
186+
doc["disable_console_debug"] = value(self.disable_console_debug);
187+
doc["legacy"] = value(self.legacy);
188+
doc["connect"] = match &self.connect {
189+
Some(c) => value(c.to_string()),
190+
None => value(""),
191+
};
192+
doc["logfile"] = value(self.logfile.display().to_string());
193+
doc["stats_interval"] = value(self.stats_interval as i64);
194+
if let Some(udc) = &self.udc {
195+
doc["udc"] = value(udc);
196+
}
197+
doc["iface"] = value(&self.iface);
198+
doc["hostapd_conf"] = value(self.hostapd_conf.display().to_string());
199+
if let Some(alias) = &self.btalias {
200+
doc["btalias"] = value(alias);
201+
}
202+
doc["keepalive"] = value(self.keepalive);
203+
doc["timeout_secs"] = value(self.timeout_secs as i64);
204+
doc["bt_timeout_secs"] = value(self.bt_timeout_secs as i64);
205+
doc["mitm"] = value(self.mitm);
206+
doc["dpi"] = value(self.dpi as i64);
207+
doc["remove_tap_restriction"] = value(self.remove_tap_restriction);
208+
doc["video_in_motion"] = value(self.video_in_motion);
209+
doc["disable_media_sink"] = value(self.disable_media_sink);
210+
doc["disable_tts_sink"] = value(self.disable_tts_sink);
211+
doc["developer_mode"] = value(self.developer_mode);
212+
doc["wired"] = value(
213+
self.wired
214+
.as_ref()
215+
.map_or("".to_string(), |w| w.to_string()),
216+
);
217+
doc["dhu"] = value(self.dhu);
218+
doc["ev"] = value(self.ev);
219+
if let Some(path) = &self.ev_battery_logger {
220+
doc["ev_battery_logger"] = value(path.display().to_string());
221+
}
222+
doc["ev_battery_capacity"] = value(self.ev_battery_capacity as i64);
223+
doc["ev_factor"] = value(self.ev_factor as f64);
224+
225+
let _ = fs::write(config_file, doc.to_string());
226+
}
160227
}

src/main.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ mod io_uring;
66
mod mitm;
77
mod usb_gadget;
88
mod usb_stream;
9+
mod web;
910

1011
use crate::config::AppConfig;
1112
use bluetooth::bluetooth_setup_connection;
@@ -26,6 +27,9 @@ use tokio::runtime::Builder;
2627
use tokio::sync::Notify;
2728
use tokio::time::Instant;
2829

30+
use std::net::SocketAddr;
31+
use std::sync::Mutex;
32+
2933
// module name for logging engine
3034
const NAME: &str = "<i><bright-black> main: </>";
3135

@@ -152,10 +156,29 @@ fn logging_init(debug: bool, disable_console_debug: bool, log_path: &PathBuf) {
152156
}
153157
}
154158

155-
async fn tokio_main(config: AppConfig, need_restart: Arc<Notify>, tcp_start: Arc<Notify>) {
159+
async fn tokio_main(
160+
config: AppConfig,
161+
need_restart: Arc<Notify>,
162+
tcp_start: Arc<Notify>,
163+
config_file: PathBuf,
164+
) {
156165
let accessory_started = Arc::new(Notify::new());
157166
let accessory_started_cloned = accessory_started.clone();
158167

168+
// preparing AppState and starting webserver
169+
let state = web::AppState {
170+
config: Arc::new(Mutex::new(config.clone())),
171+
config_file: config_file.into(),
172+
};
173+
let app = web::app(state.into());
174+
175+
let addr = SocketAddr::from(([0, 0, 0, 0], 80));
176+
info!("Server running at http://{addr}/");
177+
hyper::Server::bind(&addr)
178+
.serve(app.into_make_service())
179+
.await
180+
.unwrap();
181+
159182
let wifi_conf = {
160183
if !config.wired.is_some() {
161184
Some(init_wifi_config(&config.iface, config.hostapd_conf))
@@ -235,7 +258,7 @@ fn main() {
235258
let args = Args::parse();
236259

237260
// parse config
238-
let config = config::load_config(args.config.clone()).unwrap();
261+
let config = AppConfig::load(args.config.clone()).unwrap();
239262

240263
logging_init(config.debug, config.disable_console_debug, &config.logfile);
241264
info!(
@@ -312,7 +335,9 @@ fn main() {
312335

313336
// build and spawn main tokio runtime
314337
let runtime = Builder::new_multi_thread().enable_all().build().unwrap();
315-
runtime.spawn(async move { tokio_main(config, need_restart, tcp_start).await });
338+
runtime.spawn(
339+
async move { tokio_main(config, need_restart, tcp_start, args.config.clone()).await },
340+
);
316341

317342
// start tokio_uring runtime simultaneously
318343
let _ = tokio_uring::start(io_loop(

src/web.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
use crate::config::AppConfig;
2+
use axum::{
3+
extract::State,
4+
response::{Html, IntoResponse},
5+
routing::get,
6+
Json, Router,
7+
};
8+
use std::path::PathBuf;
9+
use std::sync::{Arc, Mutex};
10+
11+
#[derive(Clone)]
12+
pub struct AppState {
13+
pub config: Arc<Mutex<AppConfig>>,
14+
pub config_file: Arc<PathBuf>,
15+
}
16+
17+
pub fn app(state: Arc<AppState>) -> Router {
18+
Router::new()
19+
.route("/", get(index))
20+
.route("/config", get(get_config).post(set_config))
21+
.with_state(state)
22+
}
23+
24+
async fn index() -> impl IntoResponse {
25+
Html(include_str!("../static/index.html"))
26+
}
27+
28+
async fn get_config(State(state): State<Arc<AppState>>) -> impl IntoResponse {
29+
let cfg = state.config.lock().unwrap().clone();
30+
Json(cfg)
31+
}
32+
33+
async fn set_config(
34+
State(state): State<Arc<AppState>>,
35+
Json(new_cfg): Json<AppConfig>,
36+
) -> impl IntoResponse {
37+
{
38+
let mut cfg = state.config.lock().unwrap();
39+
*cfg = new_cfg.clone();
40+
cfg.save((&state.config_file).to_path_buf());
41+
}
42+
Json(new_cfg)
43+
}

0 commit comments

Comments
 (0)