Refactor and simplify QmdlReader

In the past, QmdlReader was written to share a trait with DiagDevice, so
it had to pretend to be reading MessagesContainers. This needlessly
complicated both its code as well as that of consumers'. Instead,
QmdlReader now returns a stream of diag Messages.

QmdlReader also automatically detects if it's reading a compressed QMDL
stream or not.

Additionally, QmdlReader no longer can be bounded by a filesize limit,
and instead relies on HDLC message framing to detect file truncation.
This works for both compressed and uncompressed QMDL files.
This commit is contained in:
Will Greenberg
2026-05-07 16:46:59 -07:00
parent af4a9aeb95
commit 0cd70ad73c
9 changed files with 368 additions and 270 deletions
+11 -23
View File
@@ -1,15 +1,13 @@
use clap::Parser;
use futures::TryStreamExt;
use log::{debug, error, info, warn};
use pcap_file_tokio::pcapng::{Block, PcapNgReader};
use rayhunter::{
analysis::analyzer::{AnalysisRow, AnalyzerConfig, EventType, Harness},
diag::DataType,
gsmtap_parser,
pcap::GsmtapPcapWriter,
qmdl::QmdlReader,
};
use std::{collections::HashMap, future, path::PathBuf, pin::pin};
use std::{collections::HashMap, path::PathBuf};
use tokio::fs::File;
use walkdir::WalkDir;
@@ -113,22 +111,14 @@ async fn analyze_pcap(pcap_path: &str, show_skipped: bool) {
async fn analyze_qmdl(qmdl_path: &str, show_skipped: bool) {
let mut harness = Harness::new_with_config(&AnalyzerConfig::default());
let qmdl_file = &mut File::open(&qmdl_path).await.expect("failed to open file");
let compressed = qmdl_path.ends_with(".gz");
let qmdl_reader = QmdlReader::new(qmdl_file, compressed, None);
let mut qmdl_stream = pin!(
qmdl_reader
.as_stream()
.try_filter(|container| future::ready(container.data_type == DataType::UserSpace))
);
let mut qmdl_reader = QmdlReader::new(qmdl_file).await.expect("failed to open QmdlReader");
let mut report = Report::new(qmdl_path);
while let Some(container) = qmdl_stream
.try_next()
while let Some(maybe_message) = qmdl_reader
.get_next_message()
.await
.expect("failed getting QMDL container")
.expect("failed to get message")
{
for row in harness.analyze_qmdl_messages(container) {
report.process_row(row);
}
report.process_row(harness.analyze_qmdl_message(maybe_message));
}
report.print_summary(show_skipped);
}
@@ -137,9 +127,7 @@ async fn pcapify(qmdl_path: &PathBuf) {
let qmdl_file = &mut File::open(&qmdl_path)
.await
.expect("failed to open qmdl file");
let compressed = qmdl_path.ends_with(".gz");
let qmdl_file_size = qmdl_file.metadata().await.unwrap().len();
let mut qmdl_reader = QmdlReader::new(qmdl_file, compressed, Some(qmdl_file_size as usize));
let mut qmdl_reader = QmdlReader::new(qmdl_file).await.expect("failed to open QmdlReader");
let mut pcap_path = qmdl_path.clone();
pcap_path.set_extension("pcapng");
let pcap_file = &mut File::create(&pcap_path)
@@ -147,12 +135,12 @@ async fn pcapify(qmdl_path: &PathBuf) {
.expect("failed to open pcap file");
let mut pcap_writer = GsmtapPcapWriter::new(pcap_file).await.unwrap();
pcap_writer.write_iface_header().await.unwrap();
while let Some(container) = qmdl_reader
.get_next_messages_container()
while let Some(maybe_message) = qmdl_reader
.get_next_message()
.await
.expect("failed to get container")
.expect("failed to get message")
{
for msg in container.into_messages().into_iter().flatten() {
if let Ok(msg) = maybe_message {
if let Ok(Some((timestamp, parsed))) = gsmtap_parser::parse(msg) {
pcap_writer
.write_gsmtap_message(parsed, timestamp)