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(), + }, + ] + }) + } +}