Skip to content

Commit

Permalink
Framebuffer update (#60)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
cooperq authored Oct 3, 2024
1 parent 861aaed commit ca4f49b
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 12 deletions.
25 changes: 19 additions & 6 deletions bin/src/daemon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -43,11 +43,13 @@ async fn run_server(
config: &config::Config,
qmdl_store_lock: Arc<RwLock<RecordingStore>>,
server_shutdown_rx: oneshot::Receiver<()>,
ui_update_tx: Sender<framebuffer::DisplayState>,
diag_device_sender: Sender<DiagDeviceCtrlMessage>
) -> 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
});

Expand Down Expand Up @@ -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<framebuffer::DisplayState>){
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?
Expand All @@ -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());
Expand All @@ -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));
Expand All @@ -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::<DiagDeviceCtrlMessage>(1);
let (ui_update_tx, ui_update_rx) = mpsc::channel::<framebuffer::DisplayState>(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;
Expand Down
20 changes: 16 additions & 4 deletions bin/src/diag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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<usize, std::io::Error> {
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<T: Serialize>(&mut self, value: &T) -> Result<(), std::io::Error> {
Expand All @@ -83,6 +84,7 @@ pub fn run_diag_read_thread(
task_tracker: &TaskTracker,
mut dev: DiagDevice,
mut qmdl_file_rx: Receiver<DiagDeviceCtrlMessage>,
ui_update_sender: Sender<framebuffer::DisplayState>,
qmdl_store_lock: Arc<RwLock<RecordingStore>>
) {
task_tracker.spawn(async move {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -172,6 +180,8 @@ pub async fn start_recording(State(state): State<Arc<ServerState>>) -> 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()))
}

Expand All @@ -184,6 +194,8 @@ pub async fn stop_recording(State(state): State<Arc<ServerState>>) -> 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()))
}

Expand Down
15 changes: 15 additions & 0 deletions bin/src/framebuffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ struct Dimensions {
}

#[allow(dead_code)]
#[derive(Copy, Clone)]
pub enum Color565 {
Red = 0b1111100000000000,
Green = 0b0000011111100000,
Expand All @@ -22,6 +23,12 @@ pub enum Color565 {
Pink = 0b1111010010011111,
}

pub enum DisplayState {
Recording,
Paused,
WarningDetected,
}

#[derive(Copy, Clone)]
pub struct Framebuffer<'a> {
dimensions: Dimensions,
Expand All @@ -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();
Expand Down
3 changes: 2 additions & 1 deletion bin/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<RwLock<RecordingStore>>,
pub diag_device_ctrl_sender: Sender<DiagDeviceCtrlMessage>,
pub ui_update_sender: Sender<framebuffer::DisplayState>,
pub readonly_mode: bool
}

Expand Down
5 changes: 5 additions & 0 deletions bin/static/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ th[scope='row'] {
}

tr.current {
background-color: #53fe7b;
font-weight: bold;
}

tr.warning {
background-color: #fe537b;
font-weight: bold;
}
Expand Down
3 changes: 3 additions & 0 deletions lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
11 changes: 11 additions & 0 deletions lib/src/analysis/analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down
1 change: 1 addition & 0 deletions lib/src/analysis/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ pub mod information_element;
pub mod lte_downgrade;
pub mod imsi_provided;
pub mod null_cipher;
pub mod test_analyzer;
45 changes: 45 additions & 0 deletions lib/src/analysis/test_analyzer.rs
Original file line number Diff line number Diff line change
@@ -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<str> {
Cow::from("Example Analyzer")
}

fn get_description(&self) -> Cow<str> {
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<Event> {
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
}
}
2 changes: 1 addition & 1 deletion make.sh
Original file line number Diff line number Diff line change
@@ -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"'

0 comments on commit ca4f49b

Please sign in to comment.