Flattens analysis structure a bit

Instead of mirroring the QMDL container format exactly, let our analysis
files just be flat lists of packet analysis. Also removes the dummy
analyzer and adds version numbers to analysis reports and Analyzers
This commit is contained in:
Will Greenberg
2025-07-14 13:08:22 -07:00
committed by Cooper Quintin
parent da18a1f9da
commit 0915103ede
12 changed files with 78 additions and 129 deletions
+12 -15
View File
@@ -18,7 +18,6 @@ use tokio::sync::mpsc::Receiver;
use tokio::sync::{RwLock, RwLockWriteGuard};
use tokio_util::task::TaskTracker;
use crate::dummy_analyzer::TestAnalyzer;
use crate::qmdl_store::RecordingStore;
use crate::server::ServerState;
@@ -37,13 +36,10 @@ pub struct AnalysisWriter {
impl AnalysisWriter {
pub async fn new(
file: File,
enable_dummy_analyzer: bool,
analyzer_config: &AnalyzerConfig,
) -> Result<Self, std::io::Error> {
let mut harness = Harness::new_with_config(analyzer_config);
if enable_dummy_analyzer {
harness.add_analyzer(Box::new(TestAnalyzer { count: 0 }));
}
let harness = Harness::new_with_config(analyzer_config);
let mut result = Self {
writer: BufWriter::new(file),
@@ -56,16 +52,20 @@ impl AnalysisWriter {
}
// Runs the analysis harness on the given container, serializing the results
// to the analysis file and returning the file's new length.
// to the analysis file, returning the file's new length, and whether any
// warnings were detected
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?;
let mut warning_detected = false;
for row in self.harness.analyze_qmdl_messages(container) {
if !row.is_empty() {
self.write(&row).await?;
}
warning_detected |= row.contains_warnings();
}
Ok((self.bytes_written, row.contains_warnings()))
Ok((self.bytes_written, warning_detected))
}
async fn write<T: Serialize>(&mut self, value: &T) -> Result<(), std::io::Error> {
@@ -134,7 +134,6 @@ async fn finish_running_analysis(analysis_status_lock: Arc<RwLock<AnalysisStatus
async fn perform_analysis(
name: &str,
qmdl_store_lock: Arc<RwLock<RecordingStore>>,
enable_dummy_analyzer: bool,
analyzer_config: &AnalyzerConfig,
) -> Result<(), String> {
info!("Opening QMDL and analysis file for {name}...");
@@ -156,7 +155,7 @@ async fn perform_analysis(
};
let mut analysis_writer =
AnalysisWriter::new(analysis_file, enable_dummy_analyzer, analyzer_config)
AnalysisWriter::new(analysis_file, analyzer_config)
.await
.map_err(|e| format!("{e:?}"))?;
let file_size = qmdl_file
@@ -203,7 +202,6 @@ pub fn run_analysis_thread(
mut analysis_rx: Receiver<AnalysisCtrlMessage>,
qmdl_store_lock: Arc<RwLock<RecordingStore>>,
analysis_status_lock: Arc<RwLock<AnalysisStatus>>,
enable_dummy_analyzer: bool,
analyzer_config: AnalyzerConfig,
) {
task_tracker.spawn(async move {
@@ -216,7 +214,6 @@ pub fn run_analysis_thread(
if let Err(err) = perform_analysis(
&name,
qmdl_store_lock.clone(),
enable_dummy_analyzer,
&analyzer_config,
)
.await
+6 -2
View File
@@ -11,10 +11,12 @@ pub struct Config {
pub port: u16,
pub debug_mode: bool,
pub ui_level: u8,
pub enable_dummy_analyzer: bool,
pub colorblind_mode: bool,
pub key_input_mode: u8,
pub analyzers: AnalyzerConfig,
// deprecated
pub enable_dummy_analyzer: bool,
}
impl Default for Config {
@@ -24,10 +26,12 @@ impl Default for Config {
port: 8080,
debug_mode: false,
ui_level: 1,
enable_dummy_analyzer: false,
colorblind_mode: false,
key_input_mode: 0,
analyzers: AnalyzerConfig::default(),
// deprecated
enable_dummy_analyzer: false,
}
}
}
+2 -3
View File
@@ -37,14 +37,13 @@ pub fn run_diag_read_thread(
ui_update_sender: Sender<display::DisplayState>,
qmdl_store_lock: Arc<RwLock<RecordingStore>>,
analysis_sender: Sender<AnalysisCtrlMessage>,
enable_dummy_analyzer: bool,
analyzer_config: AnalyzerConfig,
) {
task_tracker.spawn(async move {
let (initial_qmdl_file, initial_analysis_file) = qmdl_store_lock.write().await.new_entry().await.expect("failed creating QMDL file entry");
let mut maybe_qmdl_writer: Option<QmdlWriter<File>> = Some(QmdlWriter::new(initial_qmdl_file));
let mut diag_stream = pin!(dev.as_stream().into_stream());
let mut maybe_analysis_writer = Some(AnalysisWriter::new(initial_analysis_file, enable_dummy_analyzer, &analyzer_config).await
let mut maybe_analysis_writer = Some(AnalysisWriter::new(initial_analysis_file, &analyzer_config).await
.expect("failed to create analysis writer"));
loop {
tokio::select! {
@@ -66,7 +65,7 @@ pub fn run_diag_read_thread(
analysis_writer.close().await.expect("failed to close analysis writer");
}
maybe_analysis_writer = Some(AnalysisWriter::new(new_analysis_file, enable_dummy_analyzer, &analyzer_config).await
maybe_analysis_writer = Some(AnalysisWriter::new(new_analysis_file, &analyzer_config).await
.expect("failed to write to analysis file"));
if let Err(e) = ui_update_sender.send(display::DisplayState::Recording).await {
-53
View File
@@ -1,53 +0,0 @@
use std::borrow::Cow;
use rayhunter::telcom_parser::lte_rrc::{PCCH_MessageType, PCCH_MessageType_c1, PagingUE_Identity};
use rayhunter::analysis::analyzer::{Analyzer, Event, EventType, Severity};
use rayhunter::analysis::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 pcch_msg = match ie {
InformationElement::LTE(lte_ie) => match &**lte_ie {
LteInformationElement::PCCH(pcch_msg) => pcch_msg,
_ => return None,
},
_ => 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
}
}
-3
View File
@@ -2,7 +2,6 @@ mod analysis;
mod config;
mod diag;
mod display;
mod dummy_analyzer;
mod error;
mod key_input;
mod pcap;
@@ -231,7 +230,6 @@ async fn run_with_config(
ui_update_tx.clone(),
qmdl_store_lock.clone(),
analysis_tx.clone(),
config.enable_dummy_analyzer,
config.analyzers.clone(),
);
info!("Starting UI");
@@ -256,7 +254,6 @@ async fn run_with_config(
analysis_rx,
qmdl_store_lock.clone(),
analysis_status_lock.clone(),
config.enable_dummy_analyzer,
config.analyzers.clone(),
);
let should_restart_flag = Arc::new(AtomicBool::new(false));