|
| 1 | +use bluer::Address; |
| 2 | +use serde::de::{self, Deserializer, Error as DeError, Visitor}; |
| 3 | +use serde::{Deserialize, Serialize}; |
| 4 | +use simplelog::*; |
| 5 | +use std::fmt::{self, Display}; |
| 6 | +use std::fs; |
| 7 | +use std::path::PathBuf; |
| 8 | +use std::str::FromStr; |
| 9 | +use toml_edit::{value, DocumentMut}; |
| 10 | + |
| 11 | +#[derive( |
| 12 | + clap::ValueEnum, Default, Debug, PartialEq, PartialOrd, Clone, Copy, Deserialize, Serialize, |
| 13 | +)] |
| 14 | +pub enum HexdumpLevel { |
| 15 | + #[default] |
| 16 | + Disabled, |
| 17 | + DecryptedInput, |
| 18 | + RawInput, |
| 19 | + DecryptedOutput, |
| 20 | + RawOutput, |
| 21 | + All, |
| 22 | +} |
| 23 | + |
| 24 | +#[derive(Debug, Clone, Serialize)] |
| 25 | +pub struct UsbId { |
| 26 | + pub vid: u16, |
| 27 | + pub pid: u16, |
| 28 | +} |
| 29 | + |
| 30 | +impl std::str::FromStr for UsbId { |
| 31 | + type Err = String; |
| 32 | + |
| 33 | + fn from_str(s: &str) -> Result<Self, Self::Err> { |
| 34 | + let parts: Vec<&str> = s.split(':').collect(); |
| 35 | + if parts.len() != 2 { |
| 36 | + return Err("Expected format VID:PID".to_string()); |
| 37 | + } |
| 38 | + let vid = u16::from_str_radix(parts[0], 16).map_err(|e| e.to_string())?; |
| 39 | + let pid = u16::from_str_radix(parts[1], 16).map_err(|e| e.to_string())?; |
| 40 | + Ok(UsbId { vid, pid }) |
| 41 | + } |
| 42 | +} |
| 43 | + |
| 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 | + |
| 50 | +impl<'de> Deserialize<'de> for UsbId { |
| 51 | + fn deserialize<D>(deserializer: D) -> Result<UsbId, D::Error> |
| 52 | + where |
| 53 | + D: Deserializer<'de>, |
| 54 | + { |
| 55 | + struct UsbIdVisitor; |
| 56 | + |
| 57 | + impl<'de> Visitor<'de> for UsbIdVisitor { |
| 58 | + type Value = UsbId; |
| 59 | + |
| 60 | + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { |
| 61 | + formatter.write_str("a string in the format VID:PID") |
| 62 | + } |
| 63 | + |
| 64 | + fn visit_str<E>(self, value: &str) -> Result<UsbId, E> |
| 65 | + where |
| 66 | + E: de::Error, |
| 67 | + { |
| 68 | + UsbId::from_str(value).map_err(de::Error::custom) |
| 69 | + } |
| 70 | + } |
| 71 | + |
| 72 | + deserializer.deserialize_str(UsbIdVisitor) |
| 73 | + } |
| 74 | +} |
| 75 | + |
| 76 | +pub fn empty_string_as_none<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error> |
| 77 | +where |
| 78 | + T: FromStr, |
| 79 | + T::Err: Display, |
| 80 | + D: Deserializer<'de>, |
| 81 | +{ |
| 82 | + let s: String = Deserialize::deserialize(deserializer)?; |
| 83 | + if s.trim().is_empty() { |
| 84 | + Ok(None) |
| 85 | + } else { |
| 86 | + T::from_str(&s).map(Some).map_err(DeError::custom) |
| 87 | + } |
| 88 | +} |
| 89 | + |
| 90 | +#[derive(Debug, Clone, Deserialize, Serialize)] |
| 91 | +#[serde(default)] |
| 92 | +pub struct AppConfig { |
| 93 | + pub advertise: bool, |
| 94 | + pub debug: bool, |
| 95 | + pub hexdump_level: HexdumpLevel, |
| 96 | + pub disable_console_debug: bool, |
| 97 | + pub legacy: bool, |
| 98 | + #[serde(default, deserialize_with = "empty_string_as_none")] |
| 99 | + pub connect: Option<Address>, |
| 100 | + pub logfile: PathBuf, |
| 101 | + pub stats_interval: u16, |
| 102 | + #[serde(default, deserialize_with = "empty_string_as_none")] |
| 103 | + pub udc: Option<String>, |
| 104 | + pub iface: String, |
| 105 | + pub hostapd_conf: PathBuf, |
| 106 | + #[serde(default, deserialize_with = "empty_string_as_none")] |
| 107 | + pub btalias: Option<String>, |
| 108 | + pub keepalive: bool, |
| 109 | + pub timeout_secs: u16, |
| 110 | + pub bt_timeout_secs: u16, |
| 111 | + pub mitm: bool, |
| 112 | + pub dpi: u16, |
| 113 | + pub remove_tap_restriction: bool, |
| 114 | + pub video_in_motion: bool, |
| 115 | + pub disable_media_sink: bool, |
| 116 | + pub disable_tts_sink: bool, |
| 117 | + pub developer_mode: bool, |
| 118 | + #[serde(default, deserialize_with = "empty_string_as_none")] |
| 119 | + pub wired: Option<UsbId>, |
| 120 | + pub dhu: bool, |
| 121 | + pub ev: bool, |
| 122 | + #[serde(default, deserialize_with = "empty_string_as_none")] |
| 123 | + pub ev_battery_logger: Option<PathBuf>, |
| 124 | + pub ev_battery_capacity: u64, |
| 125 | + pub ev_factor: f32, |
| 126 | +} |
| 127 | + |
| 128 | +impl Default for AppConfig { |
| 129 | + fn default() -> Self { |
| 130 | + Self { |
| 131 | + advertise: false, |
| 132 | + debug: false, |
| 133 | + hexdump_level: HexdumpLevel::Disabled, |
| 134 | + disable_console_debug: false, |
| 135 | + legacy: true, |
| 136 | + connect: None, |
| 137 | + logfile: "/var/log/aa-proxy-rs.log".into(), |
| 138 | + stats_interval: 0, |
| 139 | + udc: None, |
| 140 | + iface: "wlan0".to_string(), |
| 141 | + hostapd_conf: "/var/run/hostapd.conf".into(), |
| 142 | + btalias: None, |
| 143 | + keepalive: false, |
| 144 | + timeout_secs: 10, |
| 145 | + bt_timeout_secs: 120, |
| 146 | + mitm: false, |
| 147 | + dpi: 0, |
| 148 | + remove_tap_restriction: false, |
| 149 | + video_in_motion: false, |
| 150 | + disable_media_sink: false, |
| 151 | + disable_tts_sink: false, |
| 152 | + developer_mode: false, |
| 153 | + wired: None, |
| 154 | + dhu: false, |
| 155 | + ev: false, |
| 156 | + ev_battery_logger: None, |
| 157 | + ev_battery_capacity: 22000, |
| 158 | + ev_factor: 0.075, |
| 159 | + } |
| 160 | + } |
| 161 | +} |
| 162 | + |
| 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 | + }); |
| 182 | + |
| 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 | + } |
| 227 | +} |
0 commit comments