diff --git a/bin/src/check.rs b/bin/src/check.rs index e17a97c..02fa0bd 100644 --- a/bin/src/check.rs +++ b/bin/src/check.rs @@ -1,5 +1,5 @@ use std::{collections::HashMap, future, path::PathBuf, pin::pin}; -use rayhunter::{analysis::analyzer::Harness, diag::DataType, qmdl::QmdlReader}; +use rayhunter::{analysis::analyzer::Harness, diag::DataType, gsmtap_parser, pcap::GsmtapPcapWriter, qmdl::QmdlReader}; use tokio::fs::{metadata, read_dir, File}; use clap::Parser; use futures::TryStreamExt; @@ -12,6 +12,9 @@ struct Args { #[arg(short, long)] qmdl_path: PathBuf, + #[arg(short, long)] + pcapify: bool, + #[arg(long)] show_skipped: bool, @@ -54,6 +57,27 @@ async fn analyze_file(harness: &mut Harness, qmdl_path: &str, show_skipped: bool println!("{}: {} messages analyzed, {} warnings, {} messages skipped", qmdl_path, total_messages, warnings, skipped); } +async fn pcapify(qmdl_path: &PathBuf) { + let qmdl_file = &mut File::open(&qmdl_path).await.expect("failed to open qmdl file"); + let qmdl_file_size = qmdl_file.metadata().await.unwrap().len(); + let mut qmdl_reader = QmdlReader::new(qmdl_file, Some(qmdl_file_size as usize)); + let mut pcap_path = qmdl_path.clone(); + pcap_path.set_extension("pcap"); + let pcap_file = &mut File::create(&pcap_path).await.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().await.expect("failed to get container") { + for maybe_msg in container.into_messages() { + if let Ok(msg) = maybe_msg { + if let Ok(Some((timestamp, parsed))) = gsmtap_parser::parse(msg) { + pcap_writer.write_gsmtap_message(parsed, timestamp).await.expect("failed to write"); + } + } + } + } + println!("wrote pcap to {:?}", &pcap_path); +} + #[tokio::main] async fn main() { env_logger::init(); @@ -75,10 +99,19 @@ async fn main() { let name = entry.file_name(); let name_str = name.to_str().unwrap(); if name_str.ends_with(".qmdl") { - analyze_file(&mut harness, entry.path().to_str().unwrap(), args.show_skipped).await; + let path = entry.path(); + let path_str = path.to_str().unwrap(); + analyze_file(&mut harness, path_str, args.show_skipped).await; + if args.pcapify { + pcapify(&path).await; + } } } } else { - analyze_file(&mut harness, args.qmdl_path.to_str().unwrap(), args.show_skipped).await; + let path = args.qmdl_path.to_str().unwrap(); + analyze_file(&mut harness, path, args.show_skipped).await; + if args.pcapify { + pcapify(&args.qmdl_path).await; + } } } diff --git a/lib/src/diag.rs b/lib/src/diag.rs index 51c7aec..4d88434 100644 --- a/lib/src/diag.rs +++ b/lib/src/diag.rs @@ -183,6 +183,8 @@ pub enum LogBody { // * 0xb0ed: plain EMM NAS message (outgoing) #[deku(id_pat = "0xb0e2 | 0xb0e3 | 0xb0ec | 0xb0ed")] Nas4GMessage { + #[deku(ctx = "log_type")] + direction: Nas4GMessageDirection, ext_header_version: u8, rrc_rel: u8, rrc_version_minor: u8, @@ -211,6 +213,19 @@ pub enum LogBody { } } +#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)] +#[deku(ctx = "log_type: u16", id = "log_type")] +pub enum Nas4GMessageDirection { + // * 0xb0e2: plain ESM NAS message (incoming) + // * 0xb0e3: plain ESM NAS message (outgoing) + // * 0xb0ec: plain EMM NAS message (incoming) + // * 0xb0ed: plain EMM NAS message (outgoing) + #[deku(id_pat = "0xb0e2 | 0xb0ec")] + Inbound, + #[deku(id_pat = "0xb0e3 | 0xb0ed")] + Outbound, +} + #[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)] #[deku(ctx = "ext_header_version: u8", id = "ext_header_version")] pub enum LteRrcOtaPacket { diff --git a/lib/src/qmdl.rs b/lib/src/qmdl.rs index e6863fc..aae1a85 100644 --- a/lib/src/qmdl.rs +++ b/lib/src/qmdl.rs @@ -60,7 +60,7 @@ impl QmdlReader where T: AsyncRead + Unpin { }) } - async fn get_next_messages_container(&mut self) -> Result, std::io::Error> { + pub async fn get_next_messages_container(&mut self) -> Result, std::io::Error> { if let Some(max_bytes) = self.max_bytes { if self.bytes_read >= max_bytes { if self.bytes_read > max_bytes {