Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Framebuffer update #60

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 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::Color565>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this channel should use descriptive enums of display state, rather than colors directly. something like:

enum DisplayState {
    Recording,
    Paused,
    WarningDetected,
}

and then the framebuffer code assigns those colors as an implementation detail

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::Color565>){
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(color) => {
display_color = color;
},
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 Down Expand Up @@ -195,10 +207,11 @@ async fn main() -> Result<(), RayhunterError> {
run_diag_read_thread(&task_tracker, dev, rx, qmdl_store_lock.clone());
}
let (ui_shutdown_tx, ui_shutdown_rx) = oneshot::channel();
let (ui_update_tx, ui_update_rx) = mpsc::channel::<framebuffer::Color565>(1);
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
10 changes: 10 additions & 0 deletions bin/src/diag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 All @@ -33,6 +34,7 @@ struct AnalysisWriter {
writer: BufWriter<File>,
harness: Harness,
bytes_written: usize,
has_warning: bool,
}

// We write our analysis results to a file immediately to minimize the amount of
Expand All @@ -47,6 +49,7 @@ impl AnalysisWriter {
writer: BufWriter::new(file),
harness: Harness::new_with_all_analyzers(),
bytes_written: 0,
has_warning: false,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm i'm not sure why the writer should keep this state around?

};
let metadata = result.harness.get_metadata();
result.write(&metadata).await?;
Expand All @@ -59,6 +62,7 @@ impl AnalysisWriter {
let row = self.harness.analyze_qmdl_messages(container);
if !row.is_empty() {
self.write(&row).await?;
self.has_warning = ! &row.analysis.is_empty()
}
Ok(self.bytes_written)
}
Expand Down Expand Up @@ -149,6 +153,8 @@ pub fn run_diag_read_thread(
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
.expect("failed to update analysis file size");
qmdl_store.update_entry_has_warning(index, analysis_writer.has_warning).await
.expect("failed to update analysis file has warning");
Comment on lines +156 to +157
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo we shouldn't be storing whether a warning was detected in the QMDL store, but instead should return from analysis_writer.analyze() an indication of whether a warning was detected and then update the UI from here.

changing analyze()'s return type to be Result<(usize, bool), std::io::Error> would be easy enough

}
},
Err(err) => {
Expand All @@ -172,6 +178,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::Color565::Green).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 +192,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::Color565::White).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't send ui update message: {}", e)))?;
Ok((StatusCode::ACCEPTED, "ok".to_string()))
}

Expand Down
1 change: 1 addition & 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 Down
7 changes: 7 additions & 0 deletions bin/src/qmdl_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub struct ManifestEntry {
pub last_message_time: Option<DateTime<Local>>,
pub qmdl_size_bytes: usize,
pub analysis_size_bytes: usize,
pub has_warning: bool,
}

impl ManifestEntry {
Expand All @@ -51,6 +52,7 @@ impl ManifestEntry {
last_message_time: None,
qmdl_size_bytes: 0,
analysis_size_bytes: 0,
has_warning: false,
}
}

Expand Down Expand Up @@ -169,6 +171,11 @@ impl RecordingStore {
self.manifest.entries[entry_index].last_message_time = Some(Local::now());
self.write_manifest().await
}

pub async fn update_entry_has_warning(&mut self, entry_index: usize, has_warning: bool) -> Result<(), RecordingStoreError> {
self.manifest.entries[entry_index].has_warning = has_warning;
self.write_manifest().await
}

// Sets the given entry's analysis file size
pub async fn update_entry_analysis_size(&mut self, entry_index: usize, size_bytes: usize) -> Result<(), RecordingStoreError> {
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::Color565>,
pub readonly_mode: bool
}

Expand Down
9 changes: 7 additions & 2 deletions bin/src/stats.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use std::sync::Arc;

use crate::qmdl_store::ManifestEntry;
use crate::{framebuffer, qmdl_store::ManifestEntry};
use crate::server::ServerState;

use axum::Json;
use axum::extract::State;
use axum::http::StatusCode;
use log::error;
use log::{error, info};
use serde::Serialize;
use tokio::process::Command;

Expand Down Expand Up @@ -121,6 +121,11 @@ pub async fn get_qmdl_manifest(State(state): State<Arc<ServerState>>) -> Result<
let qmdl_store = state.qmdl_store_lock.read().await;
let mut entries = qmdl_store.manifest.entries.clone();
let current_entry = qmdl_store.current_entry.map(|index| entries.remove(index));
if current_entry.clone().unwrap().has_warning {
info!("a heuristic triggered on this run!");
state.ui_update_sender.send(framebuffer::Color565::Red).await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't send ui update message: {}", e)))?;
}
Comment on lines +124 to +128
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as per above, this should happen in the diag thread

Ok(Json(ManifestStats {
entries,
current_entry,
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
1 change: 1 addition & 0 deletions bin/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<th scope="col">Date Started</th>
<th scope="col">Date of Last Message</th>
<th scope="col">Size (bytes)</th>
<th scope="col">Has Warning</th>
<th scope="col">PCAP</th>
<th scope="col">QMDL</th>
</tr>
Expand Down
5 changes: 4 additions & 1 deletion bin/static/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function createEntryRow(entry) {
name.scope = 'row';
name.innerText = entry.name;
row.appendChild(name);
for (const key of ['start_time', 'last_message_time', 'qmdl_size_bytes']) {
for (const key of ['start_time', 'last_message_time', 'qmdl_size_bytes', 'has_warning']) {
const td = document.createElement('td');
td.innerText = entry[key];
row.appendChild(td);
Expand All @@ -50,6 +50,9 @@ function createEntryRow(entry) {
qmdl_link.innerText = 'qmdl';
qmdl_td.appendChild(qmdl_link);
row.appendChild(qmdl_td);
if(entry["has_warning"]){
row.classList.add('warning');
}
return row;
}

Expand Down
3 changes: 2 additions & 1 deletion lib/src/analysis/analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use serde::Serialize;

use crate::{diag::MessagesContainer, gsmtap_parser};

use super::{imsi_provided::ImsiProvidedAnalyzer, information_element::InformationElement, lte_downgrade::LteSib6And7DowngradeAnalyzer, null_cipher::NullCipherAnalyzer};
use super::{imsi_provided::ImsiProvidedAnalyzer, information_element::InformationElement, lte_downgrade::LteSib6And7DowngradeAnalyzer, null_cipher::NullCipherAnalyzer, example_analyzer::ExampleAnalyzer};

/// Qualitative measure of how severe a Warning event type is.
/// The levels should break down like this:
Expand Down Expand Up @@ -102,6 +102,7 @@ impl Harness {
harness.add_analyzer(Box::new(LteSib6And7DowngradeAnalyzer{}));
harness.add_analyzer(Box::new(ImsiProvidedAnalyzer{}));
harness.add_analyzer(Box::new(NullCipherAnalyzer{}));
harness.add_analyzer(Box::new(ExampleAnalyzer{count:0}));
harness
}

Expand Down
45 changes: 45 additions & 0 deletions lib/src/analysis/example_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 ExampleAnalyzer{
pub count: i32,
}
Comment on lines +8 to +10
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo this kinda thing should be defined in a test file. also this is pedantic i guess but i'd describe it less as an ExampleAnalyzer and more like a DebugAnalyzer


impl Analyzer for ExampleAnalyzer{
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.")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lmao

}

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
}
}
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 example_analyzer;
Loading