From ca4f49b15f6a6ab41b8d0d56be6ab6c9224ed85e Mon Sep 17 00:00:00 2001 From: Cooper Quintin Date: Thu, 3 Oct 2024 10:41:59 -0700 Subject: [PATCH] Framebuffer update (#60) * first pass at changing the UI color based on state * adding flag to qmdl metadata for when hueristic is triggered * update style for web page to match UI and have color alert on heuristic trigger * add test analyzer * rename example_analyzer to test_analyzer * refactor ui update to not depend on server * refactor to pass around color instead of display state for framebuffer channel * add debug feature flag for test analyzer * remove warning status from qmdl manifest * dont keep has warning around --- bin/src/daemon.rs | 25 ++++++++++++----- bin/src/diag.rs | 20 +++++++++++--- bin/src/framebuffer.rs | 15 +++++++++++ bin/src/server.rs | 3 ++- bin/static/css/style.css | 5 ++++ lib/Cargo.toml | 3 +++ lib/src/analysis/analyzer.rs | 11 ++++++++ lib/src/analysis/mod.rs | 1 + lib/src/analysis/test_analyzer.rs | 45 +++++++++++++++++++++++++++++++ make.sh | 2 +- 10 files changed, 118 insertions(+), 12 deletions(-) create mode 100644 lib/src/analysis/test_analyzer.rs diff --git a/bin/src/daemon.rs b/bin/src/daemon.rs index bec950b..a98b41c 100644 --- a/bin/src/daemon.rs +++ b/bin/src/daemon.rs @@ -23,7 +23,7 @@ use rayhunter::diag_device::DiagDevice; use axum::routing::{get, post}; use axum::Router; use stats::get_qmdl_manifest; -use tokio::sync::mpsc::{self, Sender}; +use tokio::sync::mpsc::{self, Sender, Receiver}; use tokio::sync::oneshot::error::TryRecvError; use tokio::task::JoinHandle; use tokio_util::task::TaskTracker; @@ -43,11 +43,13 @@ async fn run_server( config: &config::Config, qmdl_store_lock: Arc>, server_shutdown_rx: oneshot::Receiver<()>, + ui_update_tx: Sender, diag_device_sender: Sender ) -> JoinHandle<()> { let state = Arc::new(ServerState { qmdl_store_lock, diag_device_ctrl_sender: diag_device_sender, + ui_update_sender: ui_update_tx, readonly_mode: config.readonly_mode }); @@ -123,13 +125,15 @@ fn run_ctrl_c_thread( }) } -async fn update_ui(task_tracker: &TaskTracker, config: &config::Config, mut ui_shutdown_rx: oneshot::Receiver<()>){ +async fn update_ui(task_tracker: &TaskTracker, config: &config::Config, mut ui_shutdown_rx: oneshot::Receiver<()>, mut ui_update_rx: Receiver){ static IMAGE_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/static/images/"); let display_level = config.ui_level; if display_level == 0 { info!("Invisible mode, not spawning UI."); } + let mut display_color = framebuffer::Color565::Green; + task_tracker.spawn_blocking(move || { let mut fb: Framebuffer = Framebuffer::new(); // this feels wrong, is there a more rusty way to do this? @@ -149,6 +153,14 @@ async fn update_ui(task_tracker: &TaskTracker, config: &config::Config, mut ui_ Err(e) => panic!("error receiving shutdown message: {e}") } + match ui_update_rx.try_recv() { + Ok(state) => { + display_color = Framebuffer::get_color_from_state(state); + }, + Err(tokio::sync::mpsc::error::TryRecvError::Empty) => {}, + Err(e) => panic!("error receiving framebuffer update message: {e}") + } + match display_level { 2 => { fb.draw_gif(img.unwrap()); @@ -164,7 +176,7 @@ async fn update_ui(task_tracker: &TaskTracker, config: &config::Config, mut ui_ fb.draw_line(framebuffer::Color565::Cyan, 25); }, 1 | _ => { - fb.draw_line(framebuffer::Color565::Green, 2); + fb.draw_line(display_color, 2); }, }; sleep(Duration::from_millis(100)); @@ -186,19 +198,20 @@ async fn main() -> Result<(), RayhunterError> { let qmdl_store_lock = Arc::new(RwLock::new(init_qmdl_store(&config).await?)); let (tx, rx) = mpsc::channel::(1); + let (ui_update_tx, ui_update_rx) = mpsc::channel::(1); if !config.readonly_mode { let mut dev = DiagDevice::new().await .map_err(RayhunterError::DiagInitError)?; dev.config_logs().await .map_err(RayhunterError::DiagInitError)?; - run_diag_read_thread(&task_tracker, dev, rx, qmdl_store_lock.clone()); + run_diag_read_thread(&task_tracker, dev, rx, ui_update_tx.clone(), qmdl_store_lock.clone()); } let (ui_shutdown_tx, ui_shutdown_rx) = oneshot::channel(); let (server_shutdown_tx, server_shutdown_rx) = oneshot::channel::<()>(); run_ctrl_c_thread(&task_tracker, tx.clone(), server_shutdown_tx, ui_shutdown_tx, qmdl_store_lock.clone()); - run_server(&task_tracker, &config, qmdl_store_lock.clone(), server_shutdown_rx, tx).await; - update_ui(&task_tracker, &config, ui_shutdown_rx).await; + run_server(&task_tracker, &config, qmdl_store_lock.clone(), server_shutdown_rx, ui_update_tx, tx).await; + update_ui(&task_tracker, &config, ui_shutdown_rx, ui_update_rx).await; task_tracker.close(); task_tracker.wait().await; diff --git a/bin/src/diag.rs b/bin/src/diag.rs index b7bf850..564679a 100644 --- a/bin/src/diag.rs +++ b/bin/src/diag.rs @@ -11,7 +11,7 @@ use rayhunter::diag::{DataType, MessagesContainer}; use rayhunter::diag_device::DiagDevice; use serde::Serialize; use tokio::sync::RwLock; -use tokio::sync::mpsc::Receiver; +use tokio::sync::mpsc::{Receiver, Sender}; use rayhunter::qmdl::QmdlWriter; use log::{debug, error, info}; use tokio::fs::File; @@ -20,6 +20,7 @@ use tokio_util::io::ReaderStream; use tokio_util::task::TaskTracker; use futures::{StreamExt, TryStreamExt}; +use crate::framebuffer; use crate::qmdl_store::RecordingStore; use crate::server::ServerState; @@ -55,12 +56,12 @@ impl AnalysisWriter { // Runs the analysis harness on the given container, serializing the results // to the analysis file and returning the file's new length. - pub async fn analyze(&mut self, container: MessagesContainer) -> Result { + pub async fn analyze(&mut self, container: MessagesContainer) -> Result<(usize, bool), std::io::Error> { let row = self.harness.analyze_qmdl_messages(container); if !row.is_empty() { self.write(&row).await?; } - Ok(self.bytes_written) + Ok((self.bytes_written, ! &row.analysis.is_empty())) } async fn write(&mut self, value: &T) -> Result<(), std::io::Error> { @@ -83,6 +84,7 @@ pub fn run_diag_read_thread( task_tracker: &TaskTracker, mut dev: DiagDevice, mut qmdl_file_rx: Receiver, + ui_update_sender: Sender, qmdl_store_lock: Arc> ) { task_tracker.spawn(async move { @@ -143,8 +145,14 @@ pub fn run_diag_read_thread( } if let Some(analysis_writer) = maybe_analysis_writer.as_mut() { - let analysis_file_len = analysis_writer.analyze(container).await + let analysis_output = analysis_writer.analyze(container).await .expect("failed to analyze container"); + let (analysis_file_len, heuristic_warning) = analysis_output; + if heuristic_warning { + info!("a heuristic triggered on this run!"); + ui_update_sender.send(framebuffer::DisplayState::WarningDetected).await + .expect("couldn't send ui update message: {}"); + } let mut qmdl_store = qmdl_store_lock.write().await; let index = qmdl_store.current_entry.expect("DiagDevice had qmdl_writer, but QmdlStore didn't have current entry???"); qmdl_store.update_entry_analysis_size(index, analysis_file_len as usize).await @@ -172,6 +180,8 @@ pub async fn start_recording(State(state): State>) -> Result<(S let qmdl_writer = QmdlWriter::new(qmdl_file); state.diag_device_ctrl_sender.send(DiagDeviceCtrlMessage::StartRecording((qmdl_writer, analysis_file))).await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't send stop recording message: {}", e)))?; + state.ui_update_sender.send(framebuffer::DisplayState::Recording).await + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't send ui update message: {}", e)))?; Ok((StatusCode::ACCEPTED, "ok".to_string())) } @@ -184,6 +194,8 @@ pub async fn stop_recording(State(state): State>) -> Result<(St .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't close current qmdl entry: {}", e)))?; state.diag_device_ctrl_sender.send(DiagDeviceCtrlMessage::StopRecording).await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't send stop recording message: {}", e)))?; + state.ui_update_sender.send(framebuffer::DisplayState::Paused).await + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't send ui update message: {}", e)))?; Ok((StatusCode::ACCEPTED, "ok".to_string())) } diff --git a/bin/src/framebuffer.rs b/bin/src/framebuffer.rs index 13fd614..d98f39f 100644 --- a/bin/src/framebuffer.rs +++ b/bin/src/framebuffer.rs @@ -11,6 +11,7 @@ struct Dimensions { } #[allow(dead_code)] +#[derive(Copy, Clone)] pub enum Color565 { Red = 0b1111100000000000, Green = 0b0000011111100000, @@ -22,6 +23,12 @@ pub enum Color565 { Pink = 0b1111010010011111, } +pub enum DisplayState { + Recording, + Paused, + WarningDetected, +} + #[derive(Copy, Clone)] pub struct Framebuffer<'a> { dimensions: Dimensions, @@ -36,6 +43,14 @@ impl Framebuffer<'_>{ } } + pub fn get_color_from_state(state: DisplayState) -> Color565 { + match state { + DisplayState::Paused => Color565::White, + DisplayState::Recording => Color565::Green, + DisplayState::WarningDetected => Color565::Red, + } + } + fn write(&mut self, img: DynamicImage) { let mut width = img.width(); let mut height = img.height(); diff --git a/bin/src/server.rs b/bin/src/server.rs index bef6311..028df21 100644 --- a/bin/src/server.rs +++ b/bin/src/server.rs @@ -11,12 +11,13 @@ use tokio::sync::RwLock; use tokio_util::io::ReaderStream; use include_dir::{include_dir, Dir}; -use crate::DiagDeviceCtrlMessage; +use crate::{framebuffer, DiagDeviceCtrlMessage}; use crate::qmdl_store::RecordingStore; pub struct ServerState { pub qmdl_store_lock: Arc>, pub diag_device_ctrl_sender: Sender, + pub ui_update_sender: Sender, pub readonly_mode: bool } diff --git a/bin/static/css/style.css b/bin/static/css/style.css index de89e27..4ef7e54 100644 --- a/bin/static/css/style.css +++ b/bin/static/css/style.css @@ -22,6 +22,11 @@ th[scope='row'] { } tr.current { + background-color: #53fe7b; + font-weight: bold; +} + +tr.warning { background-color: #fe537b; font-weight: bold; } diff --git a/lib/Cargo.toml b/lib/Cargo.toml index fc2c5e5..13ab783 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -24,3 +24,6 @@ tokio = { version = "1.35.1", features = ["full"] } futures-core = "0.3.30" futures = "0.3.30" serde = { version = "1.0.197", features = ["derive"] } + +[features] +debug = [] \ No newline at end of file diff --git a/lib/src/analysis/analyzer.rs b/lib/src/analysis/analyzer.rs index fbd65ef..a4a962e 100644 --- a/lib/src/analysis/analyzer.rs +++ b/lib/src/analysis/analyzer.rs @@ -6,6 +6,12 @@ use crate::{diag::MessagesContainer, gsmtap_parser}; use super::{imsi_provided::ImsiProvidedAnalyzer, information_element::InformationElement, lte_downgrade::LteSib6And7DowngradeAnalyzer, null_cipher::NullCipherAnalyzer}; +#[cfg(feature="debug")] + use log::warn; + +#[cfg(feature="debug")] + use super::test_analyzer::TestAnalyzer; + /// Qualitative measure of how severe a Warning event type is. /// The levels should break down like this: /// * Low: if combined with a large number of other Warnings, user should investigate @@ -102,6 +108,11 @@ impl Harness { harness.add_analyzer(Box::new(LteSib6And7DowngradeAnalyzer{})); harness.add_analyzer(Box::new(ImsiProvidedAnalyzer{})); harness.add_analyzer(Box::new(NullCipherAnalyzer{})); + + #[cfg(feature="debug")] { + warn!("Loading test analyzers!"); + harness.add_analyzer(Box::new(TestAnalyzer{count:0})); + } harness } diff --git a/lib/src/analysis/mod.rs b/lib/src/analysis/mod.rs index aa0b490..a0cdd3f 100644 --- a/lib/src/analysis/mod.rs +++ b/lib/src/analysis/mod.rs @@ -3,3 +3,4 @@ pub mod information_element; pub mod lte_downgrade; pub mod imsi_provided; pub mod null_cipher; +pub mod test_analyzer; diff --git a/lib/src/analysis/test_analyzer.rs b/lib/src/analysis/test_analyzer.rs new file mode 100644 index 0000000..65235af --- /dev/null +++ b/lib/src/analysis/test_analyzer.rs @@ -0,0 +1,45 @@ +use std::borrow::Cow; + +use telcom_parser::lte_rrc::{PCCH_MessageType, PCCH_MessageType_c1, PagingUE_Identity}; + +use super::analyzer::{Analyzer, Event, EventType, Severity}; +use super::information_element::{InformationElement, LteInformationElement}; + +pub struct TestAnalyzer{ + pub count: i32, +} + +impl Analyzer for TestAnalyzer{ + fn get_name(&self) -> Cow { + Cow::from("Example Analyzer") + } + + fn get_description(&self) -> Cow { + Cow::from("Always returns true, if you are seeing this you are either a developer or you are about to have problems.") + } + + fn analyze_information_element(&mut self, ie: &InformationElement) -> Option { + self.count += 1; + if self.count % 100 == 0 { + return Some(Event { + event_type: EventType::Informational , + message: "multiple of 100 events processed".to_string(), + }) + } + let InformationElement::LTE(LteInformationElement::PCCH(pcch_msg)) = ie else { + return None; + }; + let PCCH_MessageType::C1(PCCH_MessageType_c1::Paging(paging)) = &pcch_msg.message else { + return None; + }; + for record in &paging.paging_record_list.as_ref()?.0 { + if let PagingUE_Identity::S_TMSI(_) = record.ue_identity { + return Some(Event { + event_type: EventType::QualitativeWarning { severity: Severity::Low }, + message: "TMSI was provided to cell".to_string(), + }) + } + } + None + } +} diff --git a/make.sh b/make.sh index 96f6fd7..5af2e25 100755 --- a/make.sh +++ b/make.sh @@ -1,4 +1,4 @@ #!/bin/sh -cargo build --release --target="armv7-unknown-linux-gnueabihf" +cargo build --release --target="armv7-unknown-linux-gnueabihf" #--features debug adb push target/armv7-unknown-linux-gnueabihf/release/rayhunter-daemon /data/rayhunter/rayhunter-daemon adb shell '/bin/rootshell -c "/etc/init.d/rayhunter_daemon restart"'