From 59c0a9d9440a9f8219ba04899e00933758f58bb8 Mon Sep 17 00:00:00 2001 From: Will Greenberg Date: Sat, 23 Dec 2023 17:23:42 -0800 Subject: [PATCH] Use QMDL instead of our custom debug file format This might be a bit premature, but since we don't seem to be experiencing any more parsing errors when reading MessagesContainers, let's switch to outputting a standard QMDL (Qualcomm Mobile Diagnostic Log) file. This file format is more widely used by tools like scat, and the only data we lose out on is the container metadata and non-UserData messages (which we weren't using anyway). --- src/bin/wavehunter.rs | 5 +-- src/bin/wavehunter_reader.rs | 12 +++--- src/debug_file.rs | 44 --------------------- src/diag.rs | 6 +++ src/diag_device.rs | 39 ++++++------------- src/diag_reader.rs | 3 +- src/hdlc.rs | 24 ++++++------ src/lib.rs | 2 +- src/qmdl.rs | 74 ++++++++++++++++++++++++++++++++++++ 9 files changed, 115 insertions(+), 94 deletions(-) delete mode 100644 src/debug_file.rs create mode 100644 src/qmdl.rs diff --git a/src/bin/wavehunter.rs b/src/bin/wavehunter.rs index ec4628c..aa81e1f 100644 --- a/src/bin/wavehunter.rs +++ b/src/bin/wavehunter.rs @@ -8,15 +8,14 @@ use log::debug; fn main() -> DiagResult<()> { env_logger::init(); - let mut dev = DiagDevice::new()?; - dev.enable_debug_mode("/data/wavehunter/wavehunter-debug")?; + let mut dev = DiagDevice::new("./wavehunter.qmdl")?; dev.config_logs()?; println!("The orca is hunting for stingrays..."); let mut gsmtap_parser = GsmtapParser::new(); // We are going to want to add a timestamp to this pcap file eventually - let mut pcap_file = PcapFile::new("/data/wavehunter/wavehunter.pcap").unwrap(); + let mut pcap_file = PcapFile::new("./wavehunter.pcap").unwrap(); pcap_file.write_iface_header().unwrap(); loop { diff --git a/src/bin/wavehunter_reader.rs b/src/bin/wavehunter_reader.rs index db9792c..e28a2a0 100644 --- a/src/bin/wavehunter_reader.rs +++ b/src/bin/wavehunter_reader.rs @@ -1,4 +1,4 @@ -use wavehunter::debug_file::DebugFileReader; +use wavehunter::qmdl::QmdlFileReader; use wavehunter::diag_reader::DiagReader; use wavehunter::diag_device::{DiagResult, DiagDeviceError}; use wavehunter::gsmtap_parser::GsmtapParser; @@ -11,17 +11,17 @@ fn main() -> DiagResult<()> { let args: Vec = std::env::args().collect(); if args.len() != 2 { - error!("Usage: {} /path/to/debug/file", args[0]); + error!("Usage: {} /path/to/qmdl/file", args[0]); std::process::exit(1); } - let mut debug_reader = DebugFileReader::new(&args[1])?; + let mut qmdl_reader = QmdlFileReader::new(&args[1])?; let mut gsmtap_parser = GsmtapParser::new(); let mut pcap_file = PcapFile::new("./wavehunter.pcap").unwrap(); pcap_file.write_iface_header().unwrap(); loop { - match debug_reader.read_response() { + match qmdl_reader.read_response() { Ok(msgs) => { for msg in msgs { debug!("msg: {:?}", msg); @@ -32,10 +32,10 @@ fn main() -> DiagResult<()> { } }, Err(DiagDeviceError::IO(err)) if err.kind() == std::io::ErrorKind::UnexpectedEof => { - println!("Reached end of debug file, exiting..."); + println!("Reached end of QMDL file, exiting..."); std::process::exit(0); }, - Err(err) => panic!("Error reading debug file {}", err), + Err(err) => panic!("Error reading QMDL file {}", err), } } } diff --git a/src/debug_file.rs b/src/debug_file.rs deleted file mode 100644 index 55ad776..0000000 --- a/src/debug_file.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::diag_reader::DiagReader; -use crate::diag_device::DiagResult; -use crate::diag::*; - -use deku::prelude::*; -use std::fs::File; -use std::io::Read; -use log::warn; - -#[derive(Debug, DekuRead, DekuWrite)] -#[deku(endian = "little")] -pub struct DebugFileBlock<'a> { - pub size: u32, - #[deku(count = "size")] - pub data: &'a [u8], -} - -pub struct DebugFileReader { - file: File, -} - -impl DebugFileReader { - pub fn new

(path: P) -> DiagResult where P: AsRef { - let file = std::fs::File::options() - .read(true) - .open(path)?; - Ok(DebugFileReader { file }) - } -} - -impl DiagReader for DebugFileReader { - fn get_next_messages_container(&mut self) -> DiagResult { - let mut bytes_read_buf = [0; 4]; - self.file.read_exact(&mut bytes_read_buf)?; - let bytes_read = u32::from_le_bytes(bytes_read_buf) as usize; - let mut data = vec![0; bytes_read as usize]; - self.file.read_exact(&mut data)?; - let ((leftover_bytes, _), container) = MessagesContainer::from_bytes((&data, 0))?; - if leftover_bytes.len() > 0 { - warn!("warning: {} leftover bytes when parsing MessagesContainer", leftover_bytes.len()); - } - Ok(container) - } -} diff --git a/src/diag.rs b/src/diag.rs index 3f5b2c5..2582094 100644 --- a/src/diag.rs +++ b/src/diag.rs @@ -3,6 +3,12 @@ use chrono::{DateTime, FixedOffset}; use deku::prelude::*; +pub const MESSAGE_TERMINATOR: u8 = 0x7e; +pub const MESSAGE_ESCAPE_CHAR: u8 = 0x7d; + +pub const ESCAPED_MESSAGE_TERMINATOR: u8 = 0x5e; +pub const ESCAPED_MESSAGE_ESCAPE_CHAR: u8 = 0x5d; + #[derive(Debug, Clone, DekuWrite)] pub struct RequestContainer { pub data_type: DataType, diff --git a/src/diag_device.rs b/src/diag_device.rs index 8cd9da3..70fa0af 100644 --- a/src/diag_device.rs +++ b/src/diag_device.rs @@ -1,11 +1,11 @@ use crate::hdlc::{hdlc_encapsulate, HdlcError}; use crate::diag::{Message, ResponsePayload, Request, LogConfigRequest, LogConfigResponse, build_log_mask_request, RequestContainer, DataType, MessagesContainer}; use crate::diag_reader::{DiagReader, CRC_CCITT}; -use crate::debug_file::DebugFileBlock; +use crate::qmdl::QmdlFileWriter; use crate::log_codes; use std::fs::File; -use std::io::{Read, Write}; +use std::io::Read; use std::os::fd::AsRawFd; use thiserror::Error; use log::{info, warn, error}; @@ -59,7 +59,7 @@ const DIAG_IOCTL_SWITCH_LOGGING: u32 = 7; pub struct DiagDevice { file: File, - debug_file: Option, + pub qmdl_file: QmdlFileWriter, read_buf: Vec, use_mdm: i32, } @@ -70,53 +70,36 @@ impl DiagReader for DiagDevice { while bytes_read == 0 { bytes_read = self.file.read(&mut self.read_buf)?; } - if let Some(debug_file) = self.debug_file.as_mut() { - let debug_block = DebugFileBlock { - size: bytes_read as u32, - data: &self.read_buf[0..bytes_read], - }; - let debug_block_bytes = debug_block.to_bytes()?; - debug_file.write_all(&debug_block_bytes)?; - } let ((leftover_bytes, _), container) = MessagesContainer::from_bytes((&self.read_buf[0..bytes_read], 0))?; if leftover_bytes.len() > 0 { warn!("warning: {} leftover bytes when parsing MessagesContainer", leftover_bytes.len()); } + self.qmdl_file.write_container(&container)?; Ok(container) } } impl DiagDevice { - pub fn new() -> DiagResult { - let file = std::fs::File::options() + pub fn new

(qmdl_path: P) -> DiagResult where P: AsRef { + let diag_file = std::fs::File::options() .read(true) .write(true) .open("/dev/diag")?; - let fd = file.as_raw_fd(); + let fd = diag_file.as_raw_fd(); + + let qmdl_file = QmdlFileWriter::new(qmdl_path)?; enable_frame_readwrite(fd, MEMORY_DEVICE_MODE)?; let use_mdm = determine_use_mdm(fd)?; Ok(DiagDevice { read_buf: vec![0; BUFFER_LEN], - file, - debug_file: None, + file: diag_file, + qmdl_file, use_mdm, }) } - // Creates a file at the given path where all binary output from /dev/diag - // will be recorded. - pub fn enable_debug_mode

(&mut self, path: P) -> DiagResult<()> where P: AsRef { - let debug_file = std::fs::File::options() - .create(true) - .write(true) - .open(path)?; - info!("enabling debug mode, writing debug output to {:?}", debug_file); - self.debug_file = Some(debug_file); - Ok(()) - } - pub fn write_request(&mut self, req: &Request) -> DiagResult<()> { let buf = RequestContainer { data_type: DataType::UserSpace, diff --git a/src/diag_reader.rs b/src/diag_reader.rs index b8d2d85..3a1cfaa 100644 --- a/src/diag_reader.rs +++ b/src/diag_reader.rs @@ -1,3 +1,4 @@ +use crate::diag; use crate::{diag::*, hdlc::hdlc_decapsulate}; use crate::diag_device::DiagResult; @@ -36,7 +37,7 @@ pub trait DiagReader { fn parse_response_container(&self, container: MessagesContainer) -> DiagResult> { let mut result = Vec::new(); for msg in container.messages { - for sub_msg in msg.data.split_inclusive(|&b| b == 0x7e) { + for sub_msg in msg.data.split_inclusive(|&b| b == diag::MESSAGE_TERMINATOR) { match hdlc_decapsulate(&sub_msg, &CRC_CCITT) { Ok(data) => match Message::from_bytes((&data, 0)) { Ok(((leftover_bytes, _), res)) => { diff --git a/src/hdlc.rs b/src/hdlc.rs index 7813e5b..9e0ba0f 100644 --- a/src/hdlc.rs +++ b/src/hdlc.rs @@ -7,6 +7,8 @@ use crc::Crc; use bytes::Buf; use thiserror::Error; +use crate::diag::{MESSAGE_ESCAPE_CHAR, MESSAGE_TERMINATOR, ESCAPED_MESSAGE_ESCAPE_CHAR, ESCAPED_MESSAGE_TERMINATOR}; + #[derive(Debug, Error, PartialEq)] pub enum HdlcError { #[error("Invalid checksum (expected {0}, got {1})")] @@ -22,25 +24,25 @@ pub enum HdlcError { } pub fn hdlc_encapsulate(data: &[u8], crc: &Crc) -> Vec { - let mut result: Vec = vec![]; + let mut result: Vec = Vec::with_capacity(data.len()); for &b in data { match b { - 0x7e => result.extend([0x7d, 0x5e]), - 0x7d => result.extend([0x7d, 0x5d]), + MESSAGE_TERMINATOR => result.extend([MESSAGE_ESCAPE_CHAR, ESCAPED_MESSAGE_TERMINATOR]), + MESSAGE_ESCAPE_CHAR => result.extend([MESSAGE_ESCAPE_CHAR, ESCAPED_MESSAGE_ESCAPE_CHAR]), _ => result.push(b), } } for b in crc.checksum(&data).to_le_bytes() { match b { - 0x7e => result.extend([0x7d, 0x5e]), - 0x7d => result.extend([0x7d, 0x5d]), + MESSAGE_TERMINATOR => result.extend([MESSAGE_ESCAPE_CHAR, ESCAPED_MESSAGE_TERMINATOR]), + MESSAGE_ESCAPE_CHAR => result.extend([MESSAGE_ESCAPE_CHAR, ESCAPED_MESSAGE_ESCAPE_CHAR]), _ => result.push(b), } } - result.push(0x7e); + result.push(MESSAGE_TERMINATOR); result } @@ -49,21 +51,21 @@ pub fn hdlc_decapsulate(data: &[u8], crc: &Crc) -> Result, HdlcErro return Err(HdlcError::TooShort); } - if data[data.len() - 1] != 0x7e { + if data[data.len() - 1] != MESSAGE_TERMINATOR { return Err(HdlcError::NoTrailingCharacter(data[data.len() - 1])); } - let mut unescaped = Vec::new(); + let mut unescaped = Vec::with_capacity(data.len()); let mut escaping = false; for &b in &data[..data.len() - 1] { if escaping { match b { - 0x5e => unescaped.push(0x7e), - 0x5d => unescaped.push(0x7d), + ESCAPED_MESSAGE_TERMINATOR => unescaped.push(MESSAGE_TERMINATOR), + ESCAPED_MESSAGE_ESCAPE_CHAR => unescaped.push(MESSAGE_ESCAPE_CHAR), _ => return Err(HdlcError::InvalidEscapeSequence(b)), } escaping = false; - } else if b == 0x7d { + } else if b == MESSAGE_ESCAPE_CHAR { escaping = true } else { unescaped.push(b); diff --git a/src/lib.rs b/src/lib.rs index 3a9f757..61de5e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ pub mod hdlc; pub mod diag; pub mod diag_device; pub mod diag_reader; -pub mod debug_file; +pub mod qmdl; pub mod log_codes; pub mod gsmtap; pub mod gsmtap_parser; diff --git a/src/qmdl.rs b/src/qmdl.rs new file mode 100644 index 0000000..fdc17aa --- /dev/null +++ b/src/qmdl.rs @@ -0,0 +1,74 @@ +//! QMDL files are Qualcomm Mobile Diagnostic Logs. Their format is very simple, +//! just a series of of concatenated HDLC encapsulated diag::Message structs. + +use crate::diag_reader::DiagReader; +use crate::diag_device::DiagResult; +use crate::diag::{MessagesContainer, MESSAGE_TERMINATOR, HdlcEncapsulatedMessage, DataType}; + +use std::fs::File; +use std::io::{Write, BufReader, BufRead}; + +pub struct QmdlFileWriter { + file: File, + pub total_written: usize, +} + +impl QmdlFileWriter { + pub fn new

(path: P) -> DiagResult where P: AsRef { + let file = std::fs::File::options() + .create(true) + .append(true) + .open(path)?; + Ok(QmdlFileWriter { + file, + total_written: 0, + }) + } + + pub fn write_container(&mut self, container: &MessagesContainer) -> DiagResult<()> { + for msg in &container.messages { + self.file.write_all(&msg.data)?; + self.total_written += msg.data.len(); + } + Ok(()) + } +} + +pub struct QmdlFileReader { + file: BufReader, + buf: Vec +} + +impl QmdlFileReader { + pub fn new

(path: P) -> DiagResult where P: AsRef { + let file = std::fs::File::options() + .read(true) + .open(path)?; + Ok(QmdlFileReader { + file: BufReader::new(file), + buf: Vec::new(), + }) + } +} + +impl DiagReader for QmdlFileReader { + fn get_next_messages_container(&mut self) -> DiagResult { + let bytes_read = self.file.read_until(MESSAGE_TERMINATOR, &mut self.buf)?; + + // Since QMDL is just a flat list of messages, we can't actually + // reproduce the container structure they came from in the original + // read. So we'll just pretend that all containers had exactly one + // message. As far as I know, the number of messages per container + // doesn't actually affect anything, so this should be fine. + Ok(MessagesContainer { + data_type: DataType::UserSpace, + num_messages: 1, + messages: vec![ + HdlcEncapsulatedMessage { + len: 1, + data: self.buf[0..bytes_read].to_vec(), + }, + ] + }) + } +}