mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-06-05 04:31:53 -07:00
Merge pull request #351 from untitaker/restart
Config change and restart UI
This commit is contained in:
+4
-4
@@ -1,10 +1,10 @@
|
|||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use rayhunter::analysis::analyzer::AnalyzerConfig;
|
use rayhunter::analysis::analyzer::AnalyzerConfig;
|
||||||
|
|
||||||
use crate::error::RayhunterError;
|
use crate::error::RayhunterError;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub qmdl_store_path: String,
|
pub qmdl_store_path: String,
|
||||||
@@ -32,11 +32,11 @@ impl Default for Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_config<P>(path: P) -> Result<Config, RayhunterError>
|
pub async fn parse_config<P>(path: P) -> Result<Config, RayhunterError>
|
||||||
where
|
where
|
||||||
P: AsRef<std::path::Path>,
|
P: AsRef<std::path::Path>,
|
||||||
{
|
{
|
||||||
if let Ok(config_file) = std::fs::read_to_string(&path) {
|
if let Ok(config_file) = tokio::fs::read_to_string(&path).await {
|
||||||
Ok(toml::from_str(&config_file).map_err(RayhunterError::ConfigFileParsingError)?)
|
Ok(toml::from_str(&config_file).map_err(RayhunterError::ConfigFileParsingError)?)
|
||||||
} else {
|
} else {
|
||||||
Ok(Config::default())
|
Ok(Config::default())
|
||||||
|
|||||||
+88
-42
@@ -10,13 +10,17 @@ mod qmdl_store;
|
|||||||
mod server;
|
mod server;
|
||||||
mod stats;
|
mod stats;
|
||||||
|
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::config::{parse_args, parse_config};
|
use crate::config::{parse_args, parse_config};
|
||||||
use crate::diag::run_diag_read_thread;
|
use crate::diag::run_diag_read_thread;
|
||||||
use crate::error::RayhunterError;
|
use crate::error::RayhunterError;
|
||||||
use crate::pcap::get_pcap;
|
use crate::pcap::get_pcap;
|
||||||
use crate::qmdl_store::RecordingStore;
|
use crate::qmdl_store::RecordingStore;
|
||||||
use crate::server::{get_qmdl, get_zip, serve_static, ServerState};
|
use crate::server::{get_config, get_qmdl, get_zip, serve_static, set_config, ServerState};
|
||||||
use crate::stats::get_system_stats;
|
use crate::stats::{get_qmdl_manifest, get_system_stats};
|
||||||
|
|
||||||
use analysis::{
|
use analysis::{
|
||||||
get_analysis_status, run_analysis_thread, start_analysis, AnalysisCtrlMessage, AnalysisStatus,
|
get_analysis_status, run_analysis_thread, start_analysis, AnalysisCtrlMessage, AnalysisStatus,
|
||||||
@@ -31,10 +35,8 @@ use diag::{
|
|||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use qmdl_store::RecordingStoreError;
|
use qmdl_store::RecordingStoreError;
|
||||||
use rayhunter::diag_device::DiagDevice;
|
use rayhunter::diag_device::DiagDevice;
|
||||||
use stats::get_qmdl_manifest;
|
|
||||||
use std::net::SocketAddr;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
|
use tokio::select;
|
||||||
use tokio::sync::mpsc::{self, Sender};
|
use tokio::sync::mpsc::{self, Sender};
|
||||||
use tokio::sync::{oneshot, RwLock};
|
use tokio::sync::{oneshot, RwLock};
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
@@ -56,6 +58,8 @@ fn get_router() -> AppRouter {
|
|||||||
.route("/api/analysis-report/{name}", get(get_analysis_report))
|
.route("/api/analysis-report/{name}", get(get_analysis_report))
|
||||||
.route("/api/analysis", get(get_analysis_status))
|
.route("/api/analysis", get(get_analysis_status))
|
||||||
.route("/api/analysis/{name}", post(start_analysis))
|
.route("/api/analysis/{name}", post(start_analysis))
|
||||||
|
.route("/api/config", get(get_config))
|
||||||
|
.route("/api/config", post(set_config))
|
||||||
.route("/", get(|| async { Redirect::permanent("/index.html") }))
|
.route("/", get(|| async { Redirect::permanent("/index.html") }))
|
||||||
.route("/{*path}", get(serve_static))
|
.route("/{*path}", get(serve_static))
|
||||||
}
|
}
|
||||||
@@ -65,14 +69,14 @@ fn get_router() -> AppRouter {
|
|||||||
// (i.e. user hit ctrl+c)
|
// (i.e. user hit ctrl+c)
|
||||||
async fn run_server(
|
async fn run_server(
|
||||||
task_tracker: &TaskTracker,
|
task_tracker: &TaskTracker,
|
||||||
config: &config::Config,
|
|
||||||
state: Arc<ServerState>,
|
state: Arc<ServerState>,
|
||||||
server_shutdown_rx: oneshot::Receiver<()>,
|
server_shutdown_rx: oneshot::Receiver<()>,
|
||||||
) -> JoinHandle<()> {
|
) -> JoinHandle<()> {
|
||||||
info!("spinning up server");
|
info!("spinning up server");
|
||||||
let app = get_router().with_state(state);
|
let addr = SocketAddr::from(([0, 0, 0, 0], state.config.port));
|
||||||
let addr = SocketAddr::from(([0, 0, 0, 0], config.port));
|
|
||||||
let listener = TcpListener::bind(&addr).await.unwrap();
|
let listener = TcpListener::bind(&addr).await.unwrap();
|
||||||
|
let app = get_router().with_state(state);
|
||||||
|
|
||||||
task_tracker.spawn(async move {
|
task_tracker.spawn(async move {
|
||||||
info!("The orca is hunting for stingrays...");
|
info!("The orca is hunting for stingrays...");
|
||||||
axum::serve(listener, app)
|
axum::serve(listener, app)
|
||||||
@@ -118,46 +122,61 @@ async fn init_qmdl_store(config: &config::Config) -> Result<RecordingStore, Rayh
|
|||||||
// Start a thread that'll track when user hits ctrl+c. When that happens,
|
// Start a thread that'll track when user hits ctrl+c. When that happens,
|
||||||
// trigger various cleanup tasks, including sending signals to other threads to
|
// trigger various cleanup tasks, including sending signals to other threads to
|
||||||
// shutdown
|
// shutdown
|
||||||
fn run_ctrl_c_thread(
|
fn run_shutdown_thread(
|
||||||
task_tracker: &TaskTracker,
|
task_tracker: &TaskTracker,
|
||||||
diag_device_sender: Sender<DiagDeviceCtrlMessage>,
|
diag_device_sender: Sender<DiagDeviceCtrlMessage>,
|
||||||
|
daemon_restart_rx: oneshot::Receiver<()>,
|
||||||
|
should_restart_flag: Arc<AtomicBool>,
|
||||||
server_shutdown_tx: oneshot::Sender<()>,
|
server_shutdown_tx: oneshot::Sender<()>,
|
||||||
maybe_ui_shutdown_tx: Option<oneshot::Sender<()>>,
|
maybe_ui_shutdown_tx: Option<oneshot::Sender<()>>,
|
||||||
|
maybe_key_input_shutdown_tx: Option<oneshot::Sender<()>>,
|
||||||
qmdl_store_lock: Arc<RwLock<RecordingStore>>,
|
qmdl_store_lock: Arc<RwLock<RecordingStore>>,
|
||||||
analysis_tx: Sender<AnalysisCtrlMessage>,
|
analysis_tx: Sender<AnalysisCtrlMessage>,
|
||||||
) -> JoinHandle<Result<(), RayhunterError>> {
|
) -> JoinHandle<Result<(), RayhunterError>> {
|
||||||
|
info!("create shutdown thread");
|
||||||
|
|
||||||
task_tracker.spawn(async move {
|
task_tracker.spawn(async move {
|
||||||
match tokio::signal::ctrl_c().await {
|
select! {
|
||||||
Ok(()) => {
|
res = tokio::signal::ctrl_c() => {
|
||||||
let mut qmdl_store = qmdl_store_lock.write().await;
|
if let Err(err) = res {
|
||||||
if qmdl_store.current_entry.is_some() {
|
error!("Unable to listen for shutdown signal: {}", err);
|
||||||
info!("Closing current QMDL entry...");
|
|
||||||
qmdl_store.close_current_entry().await?;
|
|
||||||
info!("Done!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
server_shutdown_tx
|
should_restart_flag.store(false, Ordering::Relaxed);
|
||||||
.send(())
|
}
|
||||||
.expect("couldn't send server shutdown signal");
|
res = daemon_restart_rx => {
|
||||||
info!("sending UI shutdown");
|
if let Err(err) = res {
|
||||||
if let Some(ui_shutdown_tx) = maybe_ui_shutdown_tx {
|
error!("Unable to listen for shutdown signal: {}", err);
|
||||||
ui_shutdown_tx
|
|
||||||
.send(())
|
|
||||||
.expect("couldn't send ui shutdown signal");
|
|
||||||
}
|
}
|
||||||
diag_device_sender
|
|
||||||
.send(DiagDeviceCtrlMessage::Exit)
|
should_restart_flag.store(true, Ordering::Relaxed);
|
||||||
.await
|
|
||||||
.expect("couldn't send Exit message to diag thread");
|
|
||||||
analysis_tx
|
|
||||||
.send(AnalysisCtrlMessage::Exit)
|
|
||||||
.await
|
|
||||||
.expect("couldn't send Exit message to analysis thread");
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
error!("Unable to listen for shutdown signal: {}", err);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut qmdl_store = qmdl_store_lock.write().await;
|
||||||
|
if qmdl_store.current_entry.is_some() {
|
||||||
|
info!("Closing current QMDL entry...");
|
||||||
|
qmdl_store.close_current_entry().await?;
|
||||||
|
info!("Done!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server_shutdown_tx
|
||||||
|
.send(())
|
||||||
|
.expect("couldn't send server shutdown signal");
|
||||||
|
if let Some(ui_shutdown_tx) = maybe_ui_shutdown_tx {
|
||||||
|
let _ = ui_shutdown_tx.send(());
|
||||||
|
}
|
||||||
|
if let Some(key_input_shutdown_tx) = maybe_key_input_shutdown_tx {
|
||||||
|
let _ = key_input_shutdown_tx.send(());
|
||||||
|
}
|
||||||
|
diag_device_sender
|
||||||
|
.send(DiagDeviceCtrlMessage::Exit)
|
||||||
|
.await
|
||||||
|
.expect("couldn't send Exit message to diag thread");
|
||||||
|
analysis_tx
|
||||||
|
.send(AnalysisCtrlMessage::Exit)
|
||||||
|
.await
|
||||||
|
.expect("couldn't send Exit message to analysis thread");
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -167,8 +186,19 @@ async fn main() -> Result<(), RayhunterError> {
|
|||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
let args = parse_args();
|
let args = parse_args();
|
||||||
let config = parse_config(&args.config_path)?;
|
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let config = parse_config(&args.config_path).await?;
|
||||||
|
if !run_with_config(&args, config).await? {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_with_config(
|
||||||
|
args: &config::Args,
|
||||||
|
config: config::Config,
|
||||||
|
) -> Result<bool, RayhunterError> {
|
||||||
// TaskTrackers give us an interface to spawn tokio threads, and then
|
// TaskTrackers give us an interface to spawn tokio threads, and then
|
||||||
// eventually await all of them ending
|
// eventually await all of them ending
|
||||||
let task_tracker = TaskTracker::new();
|
let task_tracker = TaskTracker::new();
|
||||||
@@ -181,6 +211,7 @@ async fn main() -> Result<(), RayhunterError> {
|
|||||||
let (ui_update_tx, ui_update_rx) = mpsc::channel::<display::DisplayState>(1);
|
let (ui_update_tx, ui_update_rx) = mpsc::channel::<display::DisplayState>(1);
|
||||||
let (analysis_tx, analysis_rx) = mpsc::channel::<AnalysisCtrlMessage>(5);
|
let (analysis_tx, analysis_rx) = mpsc::channel::<AnalysisCtrlMessage>(5);
|
||||||
let mut maybe_ui_shutdown_tx = None;
|
let mut maybe_ui_shutdown_tx = None;
|
||||||
|
let mut maybe_key_input_shutdown_tx = None;
|
||||||
if !config.debug_mode {
|
if !config.debug_mode {
|
||||||
let (ui_shutdown_tx, ui_shutdown_rx) = oneshot::channel();
|
let (ui_shutdown_tx, ui_shutdown_rx) = oneshot::channel();
|
||||||
maybe_ui_shutdown_tx = Some(ui_shutdown_tx);
|
maybe_ui_shutdown_tx = Some(ui_shutdown_tx);
|
||||||
@@ -206,10 +237,18 @@ async fn main() -> Result<(), RayhunterError> {
|
|||||||
display::update_ui(&task_tracker, &config, ui_shutdown_rx, ui_update_rx);
|
display::update_ui(&task_tracker, &config, ui_shutdown_rx, ui_update_rx);
|
||||||
|
|
||||||
info!("Starting Key Input service");
|
info!("Starting Key Input service");
|
||||||
key_input::run_key_input_thread(&task_tracker, &config, diag_tx.clone());
|
let (key_input_shutdown_tx, key_input_shutdown_rx) = oneshot::channel();
|
||||||
|
maybe_key_input_shutdown_tx = Some(key_input_shutdown_tx);
|
||||||
|
key_input::run_key_input_thread(
|
||||||
|
&task_tracker,
|
||||||
|
&config,
|
||||||
|
diag_tx.clone(),
|
||||||
|
key_input_shutdown_rx,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let (daemon_restart_tx, daemon_restart_rx) = oneshot::channel::<()>();
|
||||||
let (server_shutdown_tx, server_shutdown_rx) = oneshot::channel::<()>();
|
let (server_shutdown_tx, server_shutdown_rx) = oneshot::channel::<()>();
|
||||||
info!("create shutdown thread");
|
|
||||||
let analysis_status_lock = Arc::new(RwLock::new(analysis_status));
|
let analysis_status_lock = Arc::new(RwLock::new(analysis_status));
|
||||||
run_analysis_thread(
|
run_analysis_thread(
|
||||||
&task_tracker,
|
&task_tracker,
|
||||||
@@ -219,29 +258,36 @@ async fn main() -> Result<(), RayhunterError> {
|
|||||||
config.enable_dummy_analyzer,
|
config.enable_dummy_analyzer,
|
||||||
config.analyzers.clone(),
|
config.analyzers.clone(),
|
||||||
);
|
);
|
||||||
run_ctrl_c_thread(
|
let should_restart_flag = Arc::new(AtomicBool::new(false));
|
||||||
|
|
||||||
|
run_shutdown_thread(
|
||||||
&task_tracker,
|
&task_tracker,
|
||||||
diag_tx.clone(),
|
diag_tx.clone(),
|
||||||
|
daemon_restart_rx,
|
||||||
|
should_restart_flag.clone(),
|
||||||
server_shutdown_tx,
|
server_shutdown_tx,
|
||||||
maybe_ui_shutdown_tx,
|
maybe_ui_shutdown_tx,
|
||||||
|
maybe_key_input_shutdown_tx,
|
||||||
qmdl_store_lock.clone(),
|
qmdl_store_lock.clone(),
|
||||||
analysis_tx.clone(),
|
analysis_tx.clone(),
|
||||||
);
|
);
|
||||||
let state = Arc::new(ServerState {
|
let state = Arc::new(ServerState {
|
||||||
|
config_path: args.config_path.clone(),
|
||||||
|
config,
|
||||||
qmdl_store_lock: qmdl_store_lock.clone(),
|
qmdl_store_lock: qmdl_store_lock.clone(),
|
||||||
diag_device_ctrl_sender: diag_tx,
|
diag_device_ctrl_sender: diag_tx,
|
||||||
ui_update_sender: ui_update_tx,
|
ui_update_sender: ui_update_tx,
|
||||||
debug_mode: config.debug_mode,
|
|
||||||
analysis_status_lock,
|
analysis_status_lock,
|
||||||
analysis_sender: analysis_tx,
|
analysis_sender: analysis_tx,
|
||||||
|
daemon_restart_tx: Arc::new(RwLock::new(Some(daemon_restart_tx))),
|
||||||
});
|
});
|
||||||
run_server(&task_tracker, &config, state, server_shutdown_rx).await;
|
run_server(&task_tracker, state, server_shutdown_rx).await;
|
||||||
|
|
||||||
task_tracker.close();
|
task_tracker.close();
|
||||||
task_tracker.wait().await;
|
task_tracker.wait().await;
|
||||||
|
|
||||||
info!("see you space cowboy...");
|
info!("see you space cowboy...");
|
||||||
Ok(())
|
Ok(should_restart_flag.load(Ordering::Relaxed))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
+4
-4
@@ -158,7 +158,7 @@ pub fn run_diag_read_thread(
|
|||||||
pub async fn start_recording(
|
pub async fn start_recording(
|
||||||
State(state): State<Arc<ServerState>>,
|
State(state): State<Arc<ServerState>>,
|
||||||
) -> Result<(StatusCode, String), (StatusCode, String)> {
|
) -> Result<(StatusCode, String), (StatusCode, String)> {
|
||||||
if state.debug_mode {
|
if state.config.debug_mode {
|
||||||
return Err((StatusCode::FORBIDDEN, "server is in debug mode".to_string()));
|
return Err((StatusCode::FORBIDDEN, "server is in debug mode".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +179,7 @@ pub async fn start_recording(
|
|||||||
pub async fn stop_recording(
|
pub async fn stop_recording(
|
||||||
State(state): State<Arc<ServerState>>,
|
State(state): State<Arc<ServerState>>,
|
||||||
) -> Result<(StatusCode, String), (StatusCode, String)> {
|
) -> Result<(StatusCode, String), (StatusCode, String)> {
|
||||||
if state.debug_mode {
|
if state.config.debug_mode {
|
||||||
return Err((StatusCode::FORBIDDEN, "server is in debug mode".to_string()));
|
return Err((StatusCode::FORBIDDEN, "server is in debug mode".to_string()));
|
||||||
}
|
}
|
||||||
state
|
state
|
||||||
@@ -199,7 +199,7 @@ pub async fn delete_recording(
|
|||||||
State(state): State<Arc<ServerState>>,
|
State(state): State<Arc<ServerState>>,
|
||||||
Path(qmdl_name): Path<String>,
|
Path(qmdl_name): Path<String>,
|
||||||
) -> Result<(StatusCode, String), (StatusCode, String)> {
|
) -> Result<(StatusCode, String), (StatusCode, String)> {
|
||||||
if state.debug_mode {
|
if state.config.debug_mode {
|
||||||
return Err((StatusCode::FORBIDDEN, "server is in debug mode".to_string()));
|
return Err((StatusCode::FORBIDDEN, "server is in debug mode".to_string()));
|
||||||
}
|
}
|
||||||
let mut qmdl_store = state.qmdl_store_lock.write().await;
|
let mut qmdl_store = state.qmdl_store_lock.write().await;
|
||||||
@@ -244,7 +244,7 @@ pub async fn delete_recording(
|
|||||||
pub async fn delete_all_recordings(
|
pub async fn delete_all_recordings(
|
||||||
State(state): State<Arc<ServerState>>,
|
State(state): State<Arc<ServerState>>,
|
||||||
) -> Result<(StatusCode, String), (StatusCode, String)> {
|
) -> Result<(StatusCode, String), (StatusCode, String)> {
|
||||||
if state.debug_mode {
|
if state.config.debug_mode {
|
||||||
return Err((StatusCode::FORBIDDEN, "server is in debug mode".to_string()));
|
return Err((StatusCode::FORBIDDEN, "server is in debug mode".to_string()));
|
||||||
}
|
}
|
||||||
state
|
state
|
||||||
|
|||||||
+14
-4
@@ -1,8 +1,9 @@
|
|||||||
use log::error;
|
use log::{error, info};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use tokio::fs::File;
|
use tokio::fs::File;
|
||||||
use tokio::io::AsyncReadExt;
|
use tokio::io::AsyncReadExt;
|
||||||
use tokio::sync::mpsc::Sender;
|
use tokio::sync::mpsc::Sender;
|
||||||
|
use tokio::sync::oneshot;
|
||||||
use tokio_util::task::TaskTracker;
|
use tokio_util::task::TaskTracker;
|
||||||
|
|
||||||
use crate::config;
|
use crate::config;
|
||||||
@@ -20,6 +21,7 @@ pub fn run_key_input_thread(
|
|||||||
task_tracker: &TaskTracker,
|
task_tracker: &TaskTracker,
|
||||||
config: &config::Config,
|
config: &config::Config,
|
||||||
diag_tx: Sender<DiagDeviceCtrlMessage>,
|
diag_tx: Sender<DiagDeviceCtrlMessage>,
|
||||||
|
mut ui_shutdown_rx: oneshot::Receiver<()>,
|
||||||
) {
|
) {
|
||||||
if config.key_input_mode == 0 {
|
if config.key_input_mode == 0 {
|
||||||
return;
|
return;
|
||||||
@@ -40,9 +42,17 @@ pub fn run_key_input_thread(
|
|||||||
let mut last_event_time: Option<Instant> = None;
|
let mut last_event_time: Option<Instant> = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if let Err(e) = file.read_exact(&mut buffer).await {
|
tokio::select! {
|
||||||
error!("failed to read key input: {}", e);
|
_ = &mut ui_shutdown_rx => {
|
||||||
return;
|
info!("received key input shutdown");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result = file.read_exact(&mut buffer) => {
|
||||||
|
if let Err(e) = result {
|
||||||
|
error!("failed to read key input: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let event = parse_event(buffer);
|
let event = parse_event(buffer);
|
||||||
|
|||||||
+64
-10
@@ -8,27 +8,32 @@ use axum::extract::State;
|
|||||||
use axum::http::header::{self, CONTENT_LENGTH, CONTENT_TYPE};
|
use axum::http::header::{self, CONTENT_LENGTH, CONTENT_TYPE};
|
||||||
use axum::http::{HeaderValue, StatusCode};
|
use axum::http::{HeaderValue, StatusCode};
|
||||||
use axum::response::{IntoResponse, Response};
|
use axum::response::{IntoResponse, Response};
|
||||||
|
use axum::Json;
|
||||||
use include_dir::{include_dir, Dir};
|
use include_dir::{include_dir, Dir};
|
||||||
use log::error;
|
use log::error;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use tokio::fs::write;
|
||||||
use tokio::io::{copy, duplex, AsyncReadExt};
|
use tokio::io::{copy, duplex, AsyncReadExt};
|
||||||
use tokio::sync::mpsc::Sender;
|
use tokio::sync::mpsc::Sender;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::{oneshot, RwLock};
|
||||||
use tokio_util::compat::FuturesAsyncWriteCompatExt;
|
use tokio_util::compat::FuturesAsyncWriteCompatExt;
|
||||||
use tokio_util::io::ReaderStream;
|
use tokio_util::io::ReaderStream;
|
||||||
|
|
||||||
use crate::analysis::{AnalysisCtrlMessage, AnalysisStatus};
|
use crate::analysis::{AnalysisCtrlMessage, AnalysisStatus};
|
||||||
|
use crate::config::Config;
|
||||||
use crate::pcap::generate_pcap_data;
|
use crate::pcap::generate_pcap_data;
|
||||||
use crate::qmdl_store::RecordingStore;
|
use crate::qmdl_store::RecordingStore;
|
||||||
use crate::{display, DiagDeviceCtrlMessage};
|
use crate::{display, DiagDeviceCtrlMessage};
|
||||||
|
|
||||||
pub struct ServerState {
|
pub struct ServerState {
|
||||||
|
pub config_path: String,
|
||||||
|
pub config: Config,
|
||||||
pub qmdl_store_lock: Arc<RwLock<RecordingStore>>,
|
pub qmdl_store_lock: Arc<RwLock<RecordingStore>>,
|
||||||
pub diag_device_ctrl_sender: Sender<DiagDeviceCtrlMessage>,
|
pub diag_device_ctrl_sender: Sender<DiagDeviceCtrlMessage>,
|
||||||
pub ui_update_sender: Sender<display::DisplayState>,
|
pub ui_update_sender: Sender<display::DisplayState>,
|
||||||
pub analysis_status_lock: Arc<RwLock<AnalysisStatus>>,
|
pub analysis_status_lock: Arc<RwLock<AnalysisStatus>>,
|
||||||
pub analysis_sender: Sender<AnalysisCtrlMessage>,
|
pub analysis_sender: Sender<AnalysisCtrlMessage>,
|
||||||
pub debug_mode: bool,
|
pub daemon_restart_tx: Arc<RwLock<Option<oneshot::Sender<()>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_qmdl(
|
pub async fn get_qmdl(
|
||||||
@@ -41,12 +46,15 @@ pub async fn get_qmdl(
|
|||||||
StatusCode::NOT_FOUND,
|
StatusCode::NOT_FOUND,
|
||||||
format!("couldn't find qmdl file with name {}", qmdl_idx),
|
format!("couldn't find qmdl file with name {}", qmdl_idx),
|
||||||
))?;
|
))?;
|
||||||
let qmdl_file = qmdl_store.open_entry_qmdl(entry_index).await.map_err(|e| {
|
let qmdl_file = qmdl_store
|
||||||
(
|
.open_entry_qmdl(entry_index)
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
.await
|
||||||
format!("error opening QMDL file: {}", e),
|
.map_err(|err| {
|
||||||
)
|
(
|
||||||
})?;
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
format!("error opening QMDL file: {}", err),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
let limited_qmdl_file = qmdl_file.take(entry.qmdl_size_bytes as u64);
|
let limited_qmdl_file = qmdl_file.take(entry.qmdl_size_bytes as u64);
|
||||||
let qmdl_stream = ReaderStream::new(limited_qmdl_file);
|
let qmdl_stream = ReaderStream::new(limited_qmdl_file);
|
||||||
|
|
||||||
@@ -84,6 +92,51 @@ pub async fn serve_static(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_config(
|
||||||
|
State(state): State<Arc<ServerState>>,
|
||||||
|
) -> Result<Json<Config>, (StatusCode, String)> {
|
||||||
|
Ok(Json(state.config.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_config(
|
||||||
|
State(state): State<Arc<ServerState>>,
|
||||||
|
Json(config): Json<Config>,
|
||||||
|
) -> Result<(StatusCode, String), (StatusCode, String)> {
|
||||||
|
let config_str = toml::to_string_pretty(&config).map_err(|err| {
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
format!("failed to serialize config as TOML: {}", err),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
write(&state.config_path, config_str).await.map_err(|err| {
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
format!("failed to write config file: {}", err),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Trigger daemon restart after writing config
|
||||||
|
let mut restart_tx = state.daemon_restart_tx.write().await;
|
||||||
|
if let Some(sender) = restart_tx.take() {
|
||||||
|
sender.send(()).map_err(|_| {
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
"couldn't send restart signal".to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
Ok((
|
||||||
|
StatusCode::ACCEPTED,
|
||||||
|
"wrote config and triggered restart".to_string(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok((
|
||||||
|
StatusCode::ACCEPTED,
|
||||||
|
"wrote config but restart already triggered".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_zip(
|
pub async fn get_zip(
|
||||||
State(state): State<Arc<ServerState>>,
|
State(state): State<Arc<ServerState>>,
|
||||||
Path(entry_name): Path<String>,
|
Path(entry_name): Path<String>,
|
||||||
@@ -180,7 +233,6 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use async_zip::base::read::mem::ZipFileReader;
|
use async_zip::base::read::mem::ZipFileReader;
|
||||||
use axum::extract::{Path, State};
|
use axum::extract::{Path, State};
|
||||||
use std::io::Cursor;
|
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
async fn create_test_qmdl_store() -> (TempDir, Arc<RwLock<crate::qmdl_store::RecordingStore>>) {
|
async fn create_test_qmdl_store() -> (TempDir, Arc<RwLock<crate::qmdl_store::RecordingStore>>) {
|
||||||
@@ -235,12 +287,14 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Arc::new(ServerState {
|
Arc::new(ServerState {
|
||||||
|
config_path: "/tmp/test_config.toml".to_string(),
|
||||||
|
config: Config::default(),
|
||||||
qmdl_store_lock: store_lock,
|
qmdl_store_lock: store_lock,
|
||||||
diag_device_ctrl_sender: tx,
|
diag_device_ctrl_sender: tx,
|
||||||
ui_update_sender: ui_tx,
|
ui_update_sender: ui_tx,
|
||||||
analysis_status_lock: Arc::new(RwLock::new(analysis_status)),
|
analysis_status_lock: Arc::new(RwLock::new(analysis_status)),
|
||||||
analysis_sender: analysis_tx,
|
analysis_sender: analysis_tx,
|
||||||
debug_mode: true,
|
daemon_restart_tx: Arc::new(RwLock::new(None)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,194 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { get_config, set_config, type Config } from '../utils.svelte';
|
||||||
|
|
||||||
|
let config = $state<Config | null>(null);
|
||||||
|
|
||||||
|
let loading = $state(false);
|
||||||
|
let saving = $state(false);
|
||||||
|
let message = $state("");
|
||||||
|
let messageType = $state<"success" | "error" | null>(null);
|
||||||
|
let showConfig = $state(false);
|
||||||
|
|
||||||
|
async function loadConfig() {
|
||||||
|
try {
|
||||||
|
loading = true;
|
||||||
|
config = await get_config();
|
||||||
|
message = "";
|
||||||
|
messageType = null;
|
||||||
|
} catch (error) {
|
||||||
|
message = `Failed to load config: ${error}`;
|
||||||
|
messageType = "error";
|
||||||
|
} finally {
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveConfig() {
|
||||||
|
if (!config) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
saving = true;
|
||||||
|
await set_config(config);
|
||||||
|
message = "Config saved successfully! Rayhunter is restarting now. Reload the page in a few seconds.";
|
||||||
|
messageType = "success";
|
||||||
|
} catch (error) {
|
||||||
|
message = `Failed to save config: ${error}`;
|
||||||
|
messageType = "error";
|
||||||
|
} finally {
|
||||||
|
saving = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Load config when first shown
|
||||||
|
$effect(() => {
|
||||||
|
if (showConfig && !config) {
|
||||||
|
loadConfig();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="bg-white rounded-lg shadow-md p-6 m-4">
|
||||||
|
<button
|
||||||
|
class="w-full flex justify-between items-center text-xl font-bold mb-4 text-rayhunter-dark-blue hover:text-rayhunter-blue"
|
||||||
|
onclick={() => showConfig = !showConfig}
|
||||||
|
>
|
||||||
|
<span>Configuration</span>
|
||||||
|
<svg class="w-6 h-6 transition-transform {showConfig ? 'rotate-180' : ''}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#if showConfig}
|
||||||
|
{#if loading}
|
||||||
|
<div class="text-center py-4">Loading config...</div>
|
||||||
|
{:else if config}
|
||||||
|
<form class="space-y-4" onsubmit={(e) => { e.preventDefault(); saveConfig(); }}>
|
||||||
|
<div>
|
||||||
|
<label for="ui_level" class="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
Device UI Level
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="ui_level"
|
||||||
|
bind:value={config.ui_level}
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-rayhunter-blue"
|
||||||
|
>
|
||||||
|
<option value={0}>0 - Invisible mode</option>
|
||||||
|
<option value={1}>1 - Subtle mode (colored line)</option>
|
||||||
|
<option value={2}>2 - Demo mode (orca gif)</option>
|
||||||
|
<option value={3}>3 - EFF logo</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="key_input_mode" class="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
Device Input Mode
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="key_input_mode"
|
||||||
|
bind:value={config.key_input_mode}
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-rayhunter-blue"
|
||||||
|
>
|
||||||
|
<option value={0}>0 - Disable button control</option>
|
||||||
|
<option value={1}>1 - Double-tap power button to start/stop recording</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<input
|
||||||
|
id="colorblind_mode"
|
||||||
|
type="checkbox"
|
||||||
|
bind:checked={config.colorblind_mode}
|
||||||
|
class="h-4 w-4 text-rayhunter-blue focus:ring-rayhunter-blue border-gray-300 rounded"
|
||||||
|
/>
|
||||||
|
<label for="colorblind_mode" class="ml-2 block text-sm text-gray-700">
|
||||||
|
Colorblind Mode
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="border-t pt-4 mt-6">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-800 mb-4">Analyzer Heuristic Settings</h3>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<input
|
||||||
|
id="imsi_requested"
|
||||||
|
type="checkbox"
|
||||||
|
bind:checked={config.analyzers.imsi_requested}
|
||||||
|
class="h-4 w-4 text-rayhunter-blue focus:ring-rayhunter-blue border-gray-300 rounded"
|
||||||
|
/>
|
||||||
|
<label for="imsi_requested" class="ml-2 block text-sm text-gray-700">
|
||||||
|
IMSI Requested Heuristic
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center">
|
||||||
|
<input
|
||||||
|
id="connection_redirect_2g_downgrade"
|
||||||
|
type="checkbox"
|
||||||
|
bind:checked={config.analyzers.connection_redirect_2g_downgrade}
|
||||||
|
class="h-4 w-4 text-rayhunter-blue focus:ring-rayhunter-blue border-gray-300 rounded"
|
||||||
|
/>
|
||||||
|
<label for="connection_redirect_2g_downgrade" class="ml-2 block text-sm text-gray-700">
|
||||||
|
Connection Redirect 2G Downgrade Heuristic
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center">
|
||||||
|
<input
|
||||||
|
id="lte_sib6_and_7_downgrade"
|
||||||
|
type="checkbox"
|
||||||
|
bind:checked={config.analyzers.lte_sib6_and_7_downgrade}
|
||||||
|
class="h-4 w-4 text-rayhunter-blue focus:ring-rayhunter-blue border-gray-300 rounded"
|
||||||
|
/>
|
||||||
|
<label for="lte_sib6_and_7_downgrade" class="ml-2 block text-sm text-gray-700">
|
||||||
|
LTE SIB6 and SIB7 Downgrade Heuristic
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center">
|
||||||
|
<input
|
||||||
|
id="null_cipher"
|
||||||
|
type="checkbox"
|
||||||
|
bind:checked={config.analyzers.null_cipher}
|
||||||
|
class="h-4 w-4 text-rayhunter-blue focus:ring-rayhunter-blue border-gray-300 rounded"
|
||||||
|
/>
|
||||||
|
<label for="null_cipher" class="ml-2 block text-sm text-gray-700">
|
||||||
|
Null Cipher Heuristic
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-2 pt-4">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={saving}
|
||||||
|
class="bg-blue-500 hover:bg-blue-700 disabled:opacity-50 text-white font-bold py-2 px-4 rounded-md flex flex-row gap-1 items-center"
|
||||||
|
>
|
||||||
|
{#if saving}
|
||||||
|
<div class="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
|
||||||
|
Saving...
|
||||||
|
{:else}
|
||||||
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||||
|
</svg>
|
||||||
|
Apply and restart
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{#if message}
|
||||||
|
<div class="mt-4 p-3 rounded {messageType === 'error' ? 'bg-red-100 text-red-700' : 'bg-green-100 text-green-700'}">
|
||||||
|
{message}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<div class="text-center py-4 text-red-600">
|
||||||
|
Failed to load configuration. Please try reloading the page.
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
@@ -1,6 +1,20 @@
|
|||||||
import { Manifest } from "./manifest.svelte";
|
import { Manifest } from "./manifest.svelte";
|
||||||
import type { SystemStats } from "./systemStats";
|
import type { SystemStats } from "./systemStats";
|
||||||
|
|
||||||
|
export interface AnalyzerConfig {
|
||||||
|
imsi_requested: boolean;
|
||||||
|
connection_redirect_2g_downgrade: boolean;
|
||||||
|
lte_sib6_and_7_downgrade: boolean;
|
||||||
|
null_cipher: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Config {
|
||||||
|
ui_level: number;
|
||||||
|
colorblind_mode: boolean;
|
||||||
|
key_input_mode: number;
|
||||||
|
analyzers: AnalyzerConfig;
|
||||||
|
}
|
||||||
|
|
||||||
export async function req(method: string, url: string): Promise<string> {
|
export async function req(method: string, url: string): Promise<string> {
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: method,
|
method: method,
|
||||||
@@ -21,3 +35,22 @@ export async function get_manifest(): Promise<Manifest> {
|
|||||||
export async function get_system_stats(): Promise<SystemStats> {
|
export async function get_system_stats(): Promise<SystemStats> {
|
||||||
return JSON.parse(await req('GET', '/api/system-stats'));
|
return JSON.parse(await req('GET', '/api/system-stats'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function get_config(): Promise<Config> {
|
||||||
|
return JSON.parse(await req('GET', '/api/config'));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function set_config(config: Config): Promise<void> {
|
||||||
|
const response = await fetch('/api/config', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(config)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.text();
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
import SystemStatsTable from "$lib/components/SystemStatsTable.svelte";
|
import SystemStatsTable from "$lib/components/SystemStatsTable.svelte";
|
||||||
import DeleteAllButton from "$lib/components/DeleteAllButton.svelte";
|
import DeleteAllButton from "$lib/components/DeleteAllButton.svelte";
|
||||||
import RecordingControls from "$lib/components//RecordingControls.svelte";
|
import RecordingControls from "$lib/components//RecordingControls.svelte";
|
||||||
|
import ConfigForm from "$lib/components/ConfigForm.svelte";
|
||||||
|
|
||||||
let manager: AnalysisManager = new AnalysisManager();
|
let manager: AnalysisManager = new AnalysisManager();
|
||||||
let loaded = $state(false);
|
let loaded = $state(false);
|
||||||
@@ -75,6 +76,7 @@
|
|||||||
<ManifestTable entries={entries} server_is_recording={recording} />
|
<ManifestTable entries={entries} server_is_recording={recording} />
|
||||||
</div>
|
</div>
|
||||||
<DeleteAllButton/>
|
<DeleteAllButton/>
|
||||||
|
<ConfigForm />
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex flex-col justify-center items-center">
|
<div class="flex flex-col justify-center items-center">
|
||||||
<img src="/rayhunter_orca_only.png" class="h-48 animate-spin"/>
|
<img src="/rayhunter_orca_only.png" class="h-48 animate-spin"/>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use super::{
|
|||||||
null_cipher::NullCipherAnalyzer, priority_2g_downgrade::LteSib6And7DowngradeAnalyzer,
|
null_cipher::NullCipherAnalyzer, priority_2g_downgrade::LteSib6And7DowngradeAnalyzer,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct AnalyzerConfig {
|
pub struct AnalyzerConfig {
|
||||||
pub imsi_requested: bool,
|
pub imsi_requested: bool,
|
||||||
|
|||||||
+50
-2
@@ -7,12 +7,14 @@ use crate::log_codes;
|
|||||||
|
|
||||||
use deku::prelude::*;
|
use deku::prelude::*;
|
||||||
use futures::TryStream;
|
use futures::TryStream;
|
||||||
use log::{error, info};
|
use log::{debug, error, info};
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
use std::os::fd::AsRawFd;
|
use std::os::fd::AsRawFd;
|
||||||
|
use std::time::Duration;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::fs::File;
|
use tokio::fs::File;
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
use tokio::time::sleep;
|
||||||
|
|
||||||
pub type DiagResult<T> = Result<T, DiagDeviceError>;
|
pub type DiagResult<T> = Result<T, DiagDeviceError>;
|
||||||
|
|
||||||
@@ -85,6 +87,52 @@ pub struct DiagDevice {
|
|||||||
|
|
||||||
impl DiagDevice {
|
impl DiagDevice {
|
||||||
pub async fn new() -> DiagResult<Self> {
|
pub async fn new() -> DiagResult<Self> {
|
||||||
|
Self::new_with_retries(Duration::from_secs(30)).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn new_with_retries(max_duration: Duration) -> DiagResult<Self> {
|
||||||
|
// For some reason the diag device needs a very long time to become available again with in
|
||||||
|
// the same process, on TP-Link M7350 v3. While process restart would reset it faster.
|
||||||
|
|
||||||
|
let start_time = std::time::Instant::now();
|
||||||
|
let max_delay = Duration::from_secs(5);
|
||||||
|
|
||||||
|
let mut delay = Duration::from_millis(100);
|
||||||
|
let mut num_retries = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match Self::try_new().await {
|
||||||
|
Ok(device) => {
|
||||||
|
info!(
|
||||||
|
"Diag device initialization succeeded after {} retries",
|
||||||
|
num_retries
|
||||||
|
);
|
||||||
|
return Ok(device);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
num_retries += 1;
|
||||||
|
if start_time.elapsed() >= max_duration {
|
||||||
|
error!(
|
||||||
|
"Failed to initialize diag device after {:?}: {}",
|
||||||
|
max_duration, e
|
||||||
|
);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Diag device initialization failed {} times, retrying in {:?}: {}",
|
||||||
|
num_retries, delay, e
|
||||||
|
);
|
||||||
|
sleep(delay).await;
|
||||||
|
|
||||||
|
// Exponential backoff
|
||||||
|
delay = std::cmp::min(delay * 2, max_delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn try_new() -> DiagResult<Self> {
|
||||||
let diag_file = File::options()
|
let diag_file = File::options()
|
||||||
.read(true)
|
.read(true)
|
||||||
.write(true)
|
.write(true)
|
||||||
@@ -123,7 +171,7 @@ impl DiagDevice {
|
|||||||
.map_err(DiagDeviceError::DeviceReadFailed)?;
|
.map_err(DiagDeviceError::DeviceReadFailed)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
info!(
|
debug!(
|
||||||
"Parsing messages container size = {:?} [{:?}]",
|
"Parsing messages container size = {:?} [{:?}]",
|
||||||
bytes_read,
|
bytes_read,
|
||||||
&self.read_buf[0..bytes_read]
|
&self.read_buf[0..bytes_read]
|
||||||
|
|||||||
Reference in New Issue
Block a user