mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-04-26 15:39:59 -07:00
Transition to async I/O for most things
Mixing async and sync I/O leads to a multitude of complications, and generally speaking it's much more convenient to stick to one paradigm or the other. Since axum (and many other HTTP servers) use async, and since async is a convenient model for performing operations like "handle an MPSC message or file read, whichever happens first", let's commit to an async interface.
This commit is contained in:
145
lib/src/diag.rs
145
lib/src/diag.rs
@@ -1,8 +1,13 @@
|
||||
//! Diag protocol serialization/deserialization
|
||||
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use crc::{Algorithm, Crc};
|
||||
use deku::prelude::*;
|
||||
|
||||
use crate::hdlc::{self, hdlc_decapsulate};
|
||||
use log::{warn, error};
|
||||
use thiserror::Error;
|
||||
|
||||
pub const MESSAGE_TERMINATOR: u8 = 0x7e;
|
||||
pub const MESSAGE_ESCAPE_CHAR: u8 = 0x7d;
|
||||
|
||||
@@ -49,6 +54,28 @@ pub enum DataType {
|
||||
Other(u32),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Error)]
|
||||
pub enum DiagParsingError {
|
||||
#[error("Failed to parse Message: {0}, data: {1:?}")]
|
||||
MessageParsingError(deku::DekuError, Vec<u8>),
|
||||
#[error("HDLC decapsulation of message failed: {0}, data: {1:?}")]
|
||||
HdlcDecapsulationError(hdlc::HdlcError, Vec<u8>),
|
||||
}
|
||||
|
||||
// this is sorta based on the params qcsuper uses, plus what seems to be used in
|
||||
// https://github.com/fgsect/scat/blob/f1538b397721df3ab8ba12acd26716abcf21f78b/util.py#L47
|
||||
pub const CRC_CCITT_ALG: Algorithm<u16> = Algorithm {
|
||||
poly: 0x1021,
|
||||
init: 0xffff,
|
||||
refin: true,
|
||||
refout: true,
|
||||
width: 16,
|
||||
xorout: 0xffff,
|
||||
check: 0x2189,
|
||||
residue: 0x0000,
|
||||
};
|
||||
|
||||
pub const CRC_CCITT: Crc<u16> = Crc::<u16>::new(&CRC_CCITT_ALG);
|
||||
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
|
||||
pub struct MessagesContainer {
|
||||
pub data_type: DataType,
|
||||
@@ -57,6 +84,29 @@ pub struct MessagesContainer {
|
||||
pub messages: Vec<HdlcEncapsulatedMessage>,
|
||||
}
|
||||
|
||||
impl MessagesContainer {
|
||||
pub fn into_messages(self) -> Vec<Result<Message, DiagParsingError>> {
|
||||
let mut result = Vec::new();
|
||||
for msg in self.messages {
|
||||
for sub_msg in msg.data.split_inclusive(|&b| b == MESSAGE_TERMINATOR) {
|
||||
match hdlc_decapsulate(sub_msg, &CRC_CCITT) {
|
||||
Ok(data) => match Message::from_bytes((&data, 0)) {
|
||||
Ok(((leftover_bytes, _), res)) => {
|
||||
if !leftover_bytes.is_empty() {
|
||||
warn!("warning: {} leftover bytes when parsing Message", leftover_bytes.len());
|
||||
}
|
||||
result.push(Ok(res));
|
||||
},
|
||||
Err(e) => result.push(Err(DiagParsingError::MessageParsingError(e, data))),
|
||||
},
|
||||
Err(err) => result.push(Err(DiagParsingError::HdlcDecapsulationError(err, sub_msg.to_vec()))),
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, DekuRead, DekuWrite)]
|
||||
pub struct HdlcEncapsulatedMessage {
|
||||
pub len: u32,
|
||||
@@ -431,4 +481,99 @@ mod test {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
fn make_container(data_type: DataType, message: HdlcEncapsulatedMessage) -> MessagesContainer {
|
||||
MessagesContainer {
|
||||
data_type,
|
||||
num_messages: 1,
|
||||
messages: vec![message],
|
||||
}
|
||||
}
|
||||
|
||||
// this log is based on one captured on a real device -- if it fails to
|
||||
// serialize or deserialize, that's probably a problem with this mock, not
|
||||
// the DiagReader implementation
|
||||
fn get_test_message(payload: &[u8]) -> (HdlcEncapsulatedMessage, Message) {
|
||||
let length_with_payload = 31 + payload.len() as u16;
|
||||
let message = Message::Log {
|
||||
pending_msgs: 0,
|
||||
outer_length: length_with_payload,
|
||||
inner_length: length_with_payload,
|
||||
log_type: 0xb0c0,
|
||||
timestamp: Timestamp { ts: 72659535985485082 },
|
||||
body: LogBody::LteRrcOtaMessage {
|
||||
ext_header_version: 20,
|
||||
packet: LteRrcOtaPacket::V8 {
|
||||
rrc_rel_maj: 14,
|
||||
rrc_rel_min: 48,
|
||||
bearer_id: 0,
|
||||
phy_cell_id: 160,
|
||||
earfcn: 2050,
|
||||
sfn_subfn: 4057,
|
||||
pdu_num: 5,
|
||||
sib_mask: 0,
|
||||
len: payload.len() as u16,
|
||||
packet: payload.to_vec(),
|
||||
},
|
||||
},
|
||||
};
|
||||
let serialized = message.to_bytes().expect("failed to serialize test message");
|
||||
let encapsulated_data = hdlc::hdlc_encapsulate(&serialized, &CRC_CCITT);
|
||||
let encapsulated = HdlcEncapsulatedMessage {
|
||||
len: encapsulated_data.len() as u32,
|
||||
data: encapsulated_data,
|
||||
};
|
||||
(encapsulated, message)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_containers_with_multiple_messages() {
|
||||
let (encapsulated1, message1) = get_test_message(&[1]);
|
||||
let (encapsulated2, message2) = get_test_message(&[2]);
|
||||
let mut container = make_container(DataType::UserSpace, encapsulated1);
|
||||
container.messages.push(encapsulated2);
|
||||
container.num_messages += 1;
|
||||
assert_eq!(container.into_messages(), vec![Ok(message1), Ok(message2)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_containers_with_concatenated_message() {
|
||||
let (mut encapsulated1, message1) = get_test_message(&[1]);
|
||||
let (encapsulated2, message2) = get_test_message(&[2]);
|
||||
encapsulated1.data.extend(encapsulated2.data);
|
||||
encapsulated1.len += encapsulated2.len;
|
||||
let container = make_container(DataType::UserSpace, encapsulated1);
|
||||
assert_eq!(container.into_messages(), vec![Ok(message1), Ok(message2)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handles_parsing_errors() {
|
||||
let (encapsulated1, message1) = get_test_message(&[1]);
|
||||
let bad_message = hdlc::hdlc_encapsulate(&[0x01, 0x02, 0x03, 0x04], &CRC_CCITT);
|
||||
let encapsulated2 = HdlcEncapsulatedMessage {
|
||||
len: bad_message.len() as u32,
|
||||
data: bad_message,
|
||||
};
|
||||
let mut container = make_container(DataType::UserSpace, encapsulated1);
|
||||
container.messages.push(encapsulated2);
|
||||
container.num_messages += 1;
|
||||
let result = container.into_messages();
|
||||
assert_eq!(result[0], Ok(message1));
|
||||
assert!(matches!(result[1], Err(DiagParsingError::MessageParsingError(_, _))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handles_encapsulation_errors() {
|
||||
let (encapsulated1, message1) = get_test_message(&[1]);
|
||||
let bad_encapsulation = HdlcEncapsulatedMessage {
|
||||
len: 4,
|
||||
data: vec![0x01, 0x02, 0x03, 0x04],
|
||||
};
|
||||
let mut container = make_container(DataType::UserSpace, encapsulated1);
|
||||
container.messages.push(bad_encapsulation);
|
||||
container.num_messages += 1;
|
||||
let result = container.into_messages();
|
||||
assert_eq!(result[0], Ok(message1));
|
||||
assert!(matches!(result[1], Err(DiagParsingError::HdlcDecapsulationError(_, _))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
use crate::hdlc::hdlc_encapsulate;
|
||||
use crate::diag::{Message, ResponsePayload, Request, LogConfigRequest, LogConfigResponse, build_log_mask_request, RequestContainer, DataType, MessagesContainer};
|
||||
use crate::diag_reader::{DiagReader, CRC_CCITT};
|
||||
use crate::qmdl::QmdlWriter;
|
||||
use crate::diag::{build_log_mask_request, DataType, DiagParsingError, LogConfigRequest, LogConfigResponse, Message, MessagesContainer, Request, RequestContainer, ResponsePayload, CRC_CCITT};
|
||||
use crate::log_codes;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::io::ErrorKind;
|
||||
use std::os::fd::AsRawFd;
|
||||
use futures_core::TryStream;
|
||||
use thiserror::Error;
|
||||
use log::{info, warn, error};
|
||||
use deku::prelude::*;
|
||||
use tokio::fs::File;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
pub type DiagResult<T> = Result<T, DiagDeviceError>;
|
||||
|
||||
@@ -20,7 +20,7 @@ pub enum DiagDeviceError {
|
||||
#[error("Failed to read diag device: {0}")]
|
||||
DeviceReadFailed(std::io::Error),
|
||||
#[error("Failed to write diag device: {0}")]
|
||||
DeviceWriteFailed(String),
|
||||
DeviceWriteFailed(std::io::Error),
|
||||
#[error("Nonzero status code {0} for diag request: {1:?}")]
|
||||
RequestFailed(u32, Request),
|
||||
#[error("Didn't receive response for request: {0:?}")]
|
||||
@@ -71,43 +71,18 @@ const DIAG_IOCTL_SWITCH_LOGGING: u64 = 7;
|
||||
|
||||
pub struct DiagDevice {
|
||||
file: File,
|
||||
pub qmdl_writer: Option<QmdlWriter<File>>,
|
||||
fully_initialized: bool,
|
||||
read_buf: Vec<u8>,
|
||||
use_mdm: i32,
|
||||
}
|
||||
|
||||
impl DiagReader for DiagDevice {
|
||||
type Err = DiagDeviceError;
|
||||
|
||||
fn get_next_messages_container(&mut self) -> DiagResult<MessagesContainer> {
|
||||
let mut bytes_read = 0;
|
||||
while bytes_read == 0 {
|
||||
bytes_read = self.file.read(&mut self.read_buf)
|
||||
.map_err(DiagDeviceError::DeviceReadFailed)?;
|
||||
}
|
||||
let ((leftover_bytes, _), container) = MessagesContainer::from_bytes((&self.read_buf[0..bytes_read], 0))
|
||||
.map_err(DiagDeviceError::ParseMessagesContainerError)?;
|
||||
if !leftover_bytes.is_empty() {
|
||||
warn!("warning: {} leftover bytes when parsing MessagesContainer", leftover_bytes.len());
|
||||
}
|
||||
|
||||
if let Some(qmdl_writer) = self.qmdl_writer.as_mut() {
|
||||
if self.fully_initialized {
|
||||
qmdl_writer.write_container(&container)
|
||||
.map_err(DiagDeviceError::QmdlFileWriteError)?;
|
||||
}
|
||||
}
|
||||
Ok(container)
|
||||
}
|
||||
}
|
||||
|
||||
impl DiagDevice {
|
||||
pub fn new(qmdl_writer: Option<QmdlWriter<File>>) -> DiagResult<Self> {
|
||||
let diag_file = std::fs::File::options()
|
||||
pub async fn new() -> DiagResult<Self> {
|
||||
let diag_file = File::options()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open("/dev/diag")
|
||||
.await
|
||||
.map_err(DiagDeviceError::OpenDiagDeviceError)?;
|
||||
let fd = diag_file.as_raw_fd();
|
||||
|
||||
@@ -118,12 +93,32 @@ impl DiagDevice {
|
||||
read_buf: vec![0; BUFFER_LEN],
|
||||
file: diag_file,
|
||||
fully_initialized: false,
|
||||
qmdl_writer,
|
||||
use_mdm,
|
||||
})
|
||||
}
|
||||
|
||||
fn write_request(&mut self, req: &Request) -> DiagResult<()> {
|
||||
pub fn as_stream(&mut self) -> impl TryStream<Ok = MessagesContainer, Error = DiagDeviceError> + '_ {
|
||||
futures::stream::try_unfold(self, |dev| async {
|
||||
let container = dev.get_next_messages_container().await?;
|
||||
Ok(Some((container, dev)))
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_next_messages_container(&mut self) -> Result<MessagesContainer, DiagDeviceError> {
|
||||
let mut bytes_read = 0;
|
||||
while bytes_read == 0 {
|
||||
bytes_read = self.file.read(&mut self.read_buf).await
|
||||
.map_err(DiagDeviceError::DeviceReadFailed)?;
|
||||
}
|
||||
let ((leftover_bytes, _), container) = MessagesContainer::from_bytes((&self.read_buf[0..bytes_read], 0))
|
||||
.map_err(DiagDeviceError::ParseMessagesContainerError)?;
|
||||
if !leftover_bytes.is_empty() {
|
||||
warn!("warning: {} leftover bytes when parsing MessagesContainer", leftover_bytes.len());
|
||||
}
|
||||
Ok(container)
|
||||
}
|
||||
|
||||
async fn write_request(&mut self, req: &Request) -> DiagResult<()> {
|
||||
let req_bytes = &req.to_bytes().expect("Failed to serialize Request");
|
||||
let buf = RequestContainer {
|
||||
data_type: DataType::UserSpace,
|
||||
@@ -131,23 +126,35 @@ impl DiagDevice {
|
||||
mdm_field: -1,
|
||||
hdlc_encapsulated_request: hdlc_encapsulate(req_bytes, &CRC_CCITT),
|
||||
}.to_bytes().expect("Failed to serialize RequestContainer");
|
||||
unsafe {
|
||||
let fd = self.file.as_raw_fd();
|
||||
let buf_ptr = buf.as_ptr() as *const libc::c_void;
|
||||
let ret = libc::write(fd, buf_ptr, buf.len());
|
||||
if ret < 0 {
|
||||
let msg = format!("write failed with error code {}", ret);
|
||||
return Err(DiagDeviceError::DeviceWriteFailed(msg));
|
||||
if let Err(err) = self.file.write(&buf).await {
|
||||
// For reasons I don't entirely understand, calls to write(2) on
|
||||
// /dev/diag always return 0 bytes written, though the written
|
||||
// requests end up being interpreted. As such, we're not concerned
|
||||
// about WriteZero errors
|
||||
if err.kind() != ErrorKind::WriteZero {
|
||||
return Err(DiagDeviceError::DeviceWriteFailed(err));
|
||||
}
|
||||
}
|
||||
self.file.flush().await
|
||||
.map_err(DiagDeviceError::DeviceWriteFailed)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn retrieve_id_ranges(&mut self) -> DiagResult<[u32; 16]> {
|
||||
let req = Request::LogConfig(LogConfigRequest::RetrieveIdRanges);
|
||||
self.write_request(&req)?;
|
||||
async fn read_response(&mut self) -> DiagResult<Vec<Result<Message, DiagParsingError>>> {
|
||||
loop {
|
||||
let container = self.get_next_messages_container().await?;
|
||||
if container.data_type != DataType::UserSpace {
|
||||
continue;
|
||||
}
|
||||
return Ok(container.into_messages());
|
||||
}
|
||||
}
|
||||
|
||||
for msg in self.read_response()? {
|
||||
async fn retrieve_id_ranges(&mut self) -> DiagResult<[u32; 16]> {
|
||||
let req = Request::LogConfig(LogConfigRequest::RetrieveIdRanges);
|
||||
self.write_request(&req).await?;
|
||||
|
||||
for msg in self.read_response().await? {
|
||||
match msg {
|
||||
Ok(Message::Log { .. }) => info!("skipping log response..."),
|
||||
Ok(Message::Response { payload, status, .. }) => match payload {
|
||||
@@ -166,11 +173,11 @@ impl DiagDevice {
|
||||
Err(DiagDeviceError::NoResponse(req))
|
||||
}
|
||||
|
||||
fn set_log_mask(&mut self, log_type: u32, log_mask_bitsize: u32) -> DiagResult<()> {
|
||||
async fn set_log_mask(&mut self, log_type: u32, log_mask_bitsize: u32) -> DiagResult<()> {
|
||||
let req = build_log_mask_request(log_type, log_mask_bitsize, &LOG_CODES_FOR_RAW_PACKET_LOGGING);
|
||||
self.write_request(&req)?;
|
||||
self.write_request(&req).await?;
|
||||
|
||||
for msg in self.read_response()? {
|
||||
for msg in self.read_response().await? {
|
||||
match msg {
|
||||
Ok(Message::Log { .. }) => info!("skipping log response..."),
|
||||
Ok(Message::Response { payload, status, .. }) => {
|
||||
@@ -188,13 +195,13 @@ impl DiagDevice {
|
||||
Err(DiagDeviceError::NoResponse(req))
|
||||
}
|
||||
|
||||
pub fn config_logs(&mut self) -> DiagResult<()> {
|
||||
pub async fn config_logs(&mut self) -> DiagResult<()> {
|
||||
info!("retrieving diag logging capabilities...");
|
||||
let log_mask_sizes = self.retrieve_id_ranges()?;
|
||||
let log_mask_sizes = self.retrieve_id_ranges().await?;
|
||||
|
||||
for (log_type, &log_mask_bitsize) in log_mask_sizes.iter().enumerate() {
|
||||
if log_mask_bitsize > 0 {
|
||||
self.set_log_mask(log_type as u32, log_mask_bitsize)?;
|
||||
self.set_log_mask(log_type as u32, log_mask_bitsize).await?;
|
||||
info!("enabled logging for log type {}", log_type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,223 +0,0 @@
|
||||
use crate::diag;
|
||||
use crate::{diag::*, hdlc::hdlc_decapsulate};
|
||||
use crate::hdlc;
|
||||
|
||||
use crc::{Crc, Algorithm};
|
||||
use deku::prelude::*;
|
||||
use log::{info, warn, error};
|
||||
use thiserror::Error;
|
||||
|
||||
// this is sorta based on the params qcsuper uses, plus what seems to be used in
|
||||
// https://github.com/fgsect/scat/blob/f1538b397721df3ab8ba12acd26716abcf21f78b/util.py#L47
|
||||
pub const CRC_CCITT_ALG: Algorithm<u16> = Algorithm {
|
||||
poly: 0x1021,
|
||||
init: 0xffff,
|
||||
refin: true,
|
||||
refout: true,
|
||||
width: 16,
|
||||
xorout: 0xffff,
|
||||
check: 0x2189,
|
||||
residue: 0x0000,
|
||||
};
|
||||
pub const CRC_CCITT: Crc<u16> = Crc::<u16>::new(&CRC_CCITT_ALG);
|
||||
|
||||
#[derive(Debug, PartialEq, Error)]
|
||||
pub enum DiagParsingError {
|
||||
#[error("Failed to parse Message: {0}, data: {1:?}")]
|
||||
MessageParsingError(deku::DekuError, Vec<u8>),
|
||||
#[error("HDLC decapsulation of message failed: {0}, data: {1:?}")]
|
||||
HdlcDecapsulationError(hdlc::HdlcError, Vec<u8>),
|
||||
}
|
||||
|
||||
type MaybeMessage = Result<Message, DiagParsingError>;
|
||||
|
||||
pub trait DiagReader {
|
||||
type Err;
|
||||
|
||||
fn get_next_messages_container(&mut self) -> Result<MessagesContainer, Self::Err>;
|
||||
|
||||
fn read_response(&mut self) -> Result<Vec<MaybeMessage>, Self::Err> {
|
||||
loop {
|
||||
let container = self.get_next_messages_container()?;
|
||||
if container.data_type == DataType::UserSpace {
|
||||
return self.parse_response_container(container);
|
||||
} else {
|
||||
info!("skipping non-userspace message...")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_response_container(&self, container: MessagesContainer) -> Result<Vec<MaybeMessage>, Self::Err> {
|
||||
let mut result = Vec::new();
|
||||
for msg in container.messages {
|
||||
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)) => {
|
||||
if !leftover_bytes.is_empty() {
|
||||
warn!("warning: {} leftover bytes when parsing Message", leftover_bytes.len());
|
||||
}
|
||||
result.push(Ok(res));
|
||||
},
|
||||
Err(e) => {
|
||||
result.push(Err(DiagParsingError::MessageParsingError(e, data)));
|
||||
},
|
||||
},
|
||||
Err(err) => {
|
||||
result.push(Err(DiagParsingError::HdlcDecapsulationError(err, sub_msg.to_vec())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
struct MockReader {
|
||||
containers: Vec<MessagesContainer>,
|
||||
}
|
||||
|
||||
impl DiagReader for MockReader {
|
||||
type Err = ();
|
||||
|
||||
fn get_next_messages_container(&mut self) -> Result<MessagesContainer, Self::Err> {
|
||||
Ok(self.containers.remove(0))
|
||||
}
|
||||
}
|
||||
|
||||
fn make_container(data_type: DataType, message: HdlcEncapsulatedMessage) -> MessagesContainer {
|
||||
MessagesContainer {
|
||||
data_type,
|
||||
num_messages: 1,
|
||||
messages: vec![message],
|
||||
}
|
||||
}
|
||||
|
||||
// this log is based on one captured on a real device -- if it fails to
|
||||
// serialize or deserialize, that's probably a problem with this mock, not
|
||||
// the DiagReader implementation
|
||||
fn get_test_message(payload: &[u8]) -> (HdlcEncapsulatedMessage, Message) {
|
||||
let length_with_payload = 31 + payload.len() as u16;
|
||||
let message = Message::Log {
|
||||
pending_msgs: 0,
|
||||
outer_length: length_with_payload,
|
||||
inner_length: length_with_payload,
|
||||
log_type: 0xb0c0,
|
||||
timestamp: Timestamp { ts: 72659535985485082 },
|
||||
body: LogBody::LteRrcOtaMessage {
|
||||
ext_header_version: 20,
|
||||
packet: LteRrcOtaPacket::V8 {
|
||||
rrc_rel_maj: 14,
|
||||
rrc_rel_min: 48,
|
||||
bearer_id: 0,
|
||||
phy_cell_id: 160,
|
||||
earfcn: 2050,
|
||||
sfn_subfn: 4057,
|
||||
pdu_num: 5,
|
||||
sib_mask: 0,
|
||||
len: payload.len() as u16,
|
||||
packet: payload.to_vec(),
|
||||
},
|
||||
},
|
||||
};
|
||||
let serialized = message.to_bytes().expect("failed to serialize test message");
|
||||
let encapsulated_data = hdlc::hdlc_encapsulate(&serialized, &CRC_CCITT);
|
||||
let encapsulated = HdlcEncapsulatedMessage {
|
||||
len: encapsulated_data.len() as u32,
|
||||
data: encapsulated_data,
|
||||
};
|
||||
(encapsulated, message)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_skipping_nonuser_containers() {
|
||||
let (encapsulated1, message1) = get_test_message(&[1]);
|
||||
let (encapsulated2, _) = get_test_message(&[2]);
|
||||
let (encapsulated3, message3) = get_test_message(&[3]);
|
||||
let mut reader = MockReader {
|
||||
containers: vec![
|
||||
make_container(DataType::UserSpace, encapsulated1),
|
||||
make_container(DataType::Other(0), encapsulated2),
|
||||
make_container(DataType::UserSpace, encapsulated3),
|
||||
],
|
||||
};
|
||||
assert_eq!(reader.read_response(), Ok(vec![Ok(message1)]));
|
||||
assert_eq!(reader.read_response(), Ok(vec![Ok(message3)]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_containers_with_multiple_messages() {
|
||||
let (encapsulated1, message1) = get_test_message(&[1]);
|
||||
let (encapsulated2, message2) = get_test_message(&[2]);
|
||||
let mut container1 = make_container(DataType::UserSpace, encapsulated1);
|
||||
container1.messages.push(encapsulated2);
|
||||
container1.num_messages += 1;
|
||||
let (encapsulated3, message3) = get_test_message(&[3]);
|
||||
let mut reader = MockReader {
|
||||
containers: vec![
|
||||
container1,
|
||||
make_container(DataType::UserSpace, encapsulated3),
|
||||
],
|
||||
};
|
||||
assert_eq!(reader.read_response(), Ok(vec![Ok(message1), Ok(message2)]));
|
||||
assert_eq!(reader.read_response(), Ok(vec![Ok(message3)]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_containers_with_concatenated_message() {
|
||||
let (mut encapsulated1, message1) = get_test_message(&[1]);
|
||||
let (encapsulated2, message2) = get_test_message(&[2]);
|
||||
encapsulated1.data.extend(encapsulated2.data);
|
||||
encapsulated1.len += encapsulated2.len;
|
||||
let (encapsulated3, message3) = get_test_message(&[3]);
|
||||
let mut reader = MockReader {
|
||||
containers: vec![
|
||||
make_container(DataType::UserSpace, encapsulated1),
|
||||
make_container(DataType::UserSpace, encapsulated3),
|
||||
],
|
||||
};
|
||||
assert_eq!(reader.read_response(), Ok(vec![Ok(message1), Ok(message2)]));
|
||||
assert_eq!(reader.read_response(), Ok(vec![Ok(message3)]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handles_parsing_errors() {
|
||||
let (encapsulated1, message1) = get_test_message(&[1]);
|
||||
let bad_message = hdlc::hdlc_encapsulate(&[0x01, 0x02, 0x03, 0x04], &CRC_CCITT);
|
||||
let encapsulated2 = HdlcEncapsulatedMessage {
|
||||
len: bad_message.len() as u32,
|
||||
data: bad_message,
|
||||
};
|
||||
let mut container = make_container(DataType::UserSpace, encapsulated1);
|
||||
container.messages.push(encapsulated2);
|
||||
container.num_messages += 1;
|
||||
let mut reader = MockReader {
|
||||
containers: vec![container],
|
||||
};
|
||||
let result = reader.read_response().unwrap();
|
||||
assert_eq!(result[0], Ok(message1));
|
||||
assert!(matches!(result[1], Err(DiagParsingError::MessageParsingError(_, _))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handles_encapsulation_errors() {
|
||||
let (encapsulated1, message1) = get_test_message(&[1]);
|
||||
let bad_encapsulation = HdlcEncapsulatedMessage {
|
||||
len: 4,
|
||||
data: vec![0x01, 0x02, 0x03, 0x04],
|
||||
};
|
||||
let mut container = make_container(DataType::UserSpace, encapsulated1);
|
||||
container.messages.push(bad_encapsulation);
|
||||
container.num_messages += 1;
|
||||
let mut reader = MockReader {
|
||||
containers: vec![container],
|
||||
};
|
||||
let result = reader.read_response().unwrap();
|
||||
assert_eq!(result[0], Ok(message1));
|
||||
assert!(matches!(result[1], Err(DiagParsingError::HdlcDecapsulationError(_, _))));
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ use thiserror::Error;
|
||||
|
||||
use crate::diag::{MESSAGE_ESCAPE_CHAR, MESSAGE_TERMINATOR, ESCAPED_MESSAGE_ESCAPE_CHAR, ESCAPED_MESSAGE_TERMINATOR};
|
||||
|
||||
#[derive(Debug, Error, PartialEq)]
|
||||
#[derive(Debug, Clone, Error, PartialEq)]
|
||||
pub enum HdlcError {
|
||||
#[error("Invalid checksum (expected {0}, got {1})")]
|
||||
InvalidChecksum(u16, u16),
|
||||
@@ -89,7 +89,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_hdlc_encapsulate() {
|
||||
let crc = Crc::<u16>::new(&crate::diag_reader::CRC_CCITT_ALG);
|
||||
let crc = Crc::<u16>::new(&crate::diag::CRC_CCITT_ALG);
|
||||
let data = vec![0x01, 0x02, 0x03, 0x04];
|
||||
let expected = vec![1, 2, 3, 4, 145, 57, 126];
|
||||
let encapsulated = hdlc_encapsulate(&data, &crc);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
pub mod hdlc;
|
||||
pub mod diag;
|
||||
pub mod diag_device;
|
||||
pub mod diag_reader;
|
||||
pub mod qmdl;
|
||||
pub mod log_codes;
|
||||
pub mod gsmtap;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use crate::gsmtap::GsmtapMessage;
|
||||
use crate::diag::Timestamp;
|
||||
|
||||
use std::io::Write;
|
||||
use tokio::io::AsyncWrite;
|
||||
use std::borrow::Cow;
|
||||
use chrono::prelude::*;
|
||||
use deku::prelude::*;
|
||||
use pcap_file::pcapng::blocks::enhanced_packet::EnhancedPacketBlock;
|
||||
use pcap_file::pcapng::blocks::interface_description::InterfaceDescriptionBlock;
|
||||
use pcap_file::pcapng::PcapNgWriter;
|
||||
use pcap_file::PcapError;
|
||||
use pcap_file_tokio::pcapng::blocks::enhanced_packet::EnhancedPacketBlock;
|
||||
use pcap_file_tokio::pcapng::blocks::interface_description::InterfaceDescriptionBlock;
|
||||
use pcap_file_tokio::pcapng::PcapNgWriter;
|
||||
use pcap_file_tokio::PcapError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@@ -23,7 +23,7 @@ pub enum GsmtapPcapError {
|
||||
Deku(#[from] DekuError),
|
||||
}
|
||||
|
||||
pub struct GsmtapPcapWriter<T> where T: Write {
|
||||
pub struct GsmtapPcapWriter<T> where T: AsyncWrite {
|
||||
writer: PcapNgWriter<T>,
|
||||
ip_id: u16,
|
||||
}
|
||||
@@ -56,23 +56,23 @@ struct UdpHeader {
|
||||
checksum: u16,
|
||||
}
|
||||
|
||||
impl<T> GsmtapPcapWriter<T> where T: Write {
|
||||
pub fn new(writer: T) -> Result<Self, GsmtapPcapError> {
|
||||
let writer = PcapNgWriter::new(writer)?;
|
||||
impl<T> GsmtapPcapWriter<T> where T: AsyncWrite + Unpin + Send {
|
||||
pub async fn new(writer: T) -> Result<Self, GsmtapPcapError> {
|
||||
let writer = PcapNgWriter::new(writer).await?;
|
||||
Ok(GsmtapPcapWriter { writer, ip_id: 0 })
|
||||
}
|
||||
|
||||
pub fn write_iface_header(&mut self) -> Result<(), GsmtapPcapError> {
|
||||
pub async fn write_iface_header(&mut self) -> Result<(), GsmtapPcapError> {
|
||||
let interface = InterfaceDescriptionBlock {
|
||||
linktype: pcap_file::DataLink::IPV4,
|
||||
linktype: pcap_file_tokio::DataLink::IPV4,
|
||||
snaplen: 0xffff,
|
||||
options: vec![],
|
||||
};
|
||||
self.writer.write_pcapng_block(interface)?;
|
||||
self.writer.write_pcapng_block(interface).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_gsmtap_message(&mut self, msg: GsmtapMessage, timestamp: Timestamp) -> Result<(), GsmtapPcapError> {
|
||||
pub async fn write_gsmtap_message(&mut self, msg: GsmtapMessage, timestamp: Timestamp) -> Result<(), GsmtapPcapError> {
|
||||
let duration = timestamp.to_datetime()
|
||||
.signed_duration_since(DateTime::UNIX_EPOCH)
|
||||
.to_std()?;
|
||||
@@ -113,7 +113,7 @@ impl<T> GsmtapPcapWriter<T> where T: Write {
|
||||
data: Cow::Owned(data),
|
||||
options: vec![],
|
||||
};
|
||||
self.writer.write_pcapng_block(packet)?;
|
||||
self.writer.write_pcapng_block(packet).await?;
|
||||
self.ip_id = self.ip_id.wrapping_add(1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -3,19 +3,18 @@
|
||||
//! QmdlReader and QmdlWriter can read and write MessagesContainers to and from
|
||||
//! QMDL files.
|
||||
|
||||
use crate::diag_reader::DiagReader;
|
||||
use crate::diag::{MessagesContainer, MESSAGE_TERMINATOR, HdlcEncapsulatedMessage, DataType};
|
||||
|
||||
use std::io::{Write, BufReader, BufRead, Read};
|
||||
use thiserror::Error;
|
||||
use futures::TryStream;
|
||||
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, AsyncBufReadExt};
|
||||
use log::error;
|
||||
|
||||
pub struct QmdlWriter<T> where T: Write {
|
||||
pub struct QmdlWriter<T> where T: AsyncWrite + Unpin {
|
||||
writer: T,
|
||||
pub total_written: usize,
|
||||
}
|
||||
|
||||
impl<T> QmdlWriter<T> where T: Write {
|
||||
impl<T> QmdlWriter<T> where T: AsyncWrite + Unpin {
|
||||
pub fn new(writer: T) -> Self {
|
||||
QmdlWriter::new_with_existing_size(writer, 0)
|
||||
}
|
||||
@@ -27,30 +26,22 @@ impl<T> QmdlWriter<T> where T: Write {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_container(&mut self, container: &MessagesContainer) -> std::io::Result<()> {
|
||||
pub async fn write_container(&mut self, container: &MessagesContainer) -> std::io::Result<()> {
|
||||
for msg in &container.messages {
|
||||
self.writer.write_all(&msg.data)?;
|
||||
self.writer.write_all(&msg.data).await?;
|
||||
self.total_written += msg.data.len();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum QmdlReaderError {
|
||||
#[error("IO error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
#[error("Reached max_bytes count {0}")]
|
||||
MaxBytesReached(usize),
|
||||
}
|
||||
|
||||
pub struct QmdlReader<T> where T: Read {
|
||||
pub struct QmdlReader<T> where T: AsyncRead {
|
||||
reader: BufReader<T>,
|
||||
bytes_read: usize,
|
||||
max_bytes: Option<usize>,
|
||||
}
|
||||
|
||||
impl<T> QmdlReader<T> where T: Read {
|
||||
impl<T> QmdlReader<T> where T: AsyncRead + Unpin {
|
||||
pub fn new(reader: T, max_bytes: Option<usize>) -> Self {
|
||||
QmdlReader {
|
||||
reader: BufReader::new(reader),
|
||||
@@ -58,23 +49,29 @@ impl<T> QmdlReader<T> where T: Read {
|
||||
max_bytes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DiagReader for QmdlReader<T> where T: Read {
|
||||
type Err = QmdlReaderError;
|
||||
pub fn as_stream(&mut self) -> impl TryStream<Ok = MessagesContainer, Error = std::io::Error> + '_ {
|
||||
futures::stream::try_unfold(self, |reader| async {
|
||||
let maybe_container = reader.get_next_messages_container().await?;
|
||||
match maybe_container {
|
||||
Some(container) => Ok(Some((container, reader))),
|
||||
None => Ok(None)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn get_next_messages_container(&mut self) -> Result<MessagesContainer, Self::Err> {
|
||||
async fn get_next_messages_container(&mut self) -> Result<Option<MessagesContainer>, std::io::Error> {
|
||||
if let Some(max_bytes) = self.max_bytes {
|
||||
if self.bytes_read >= max_bytes {
|
||||
if self.bytes_read > max_bytes {
|
||||
error!("warning: {} bytes read, but max_bytes was {}", self.bytes_read, max_bytes);
|
||||
}
|
||||
return Err(QmdlReaderError::MaxBytesReached(max_bytes));
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
let mut buf = Vec::new();
|
||||
let bytes_read = self.reader.read_until(MESSAGE_TERMINATOR, &mut buf)?;
|
||||
let bytes_read = self.reader.read_until(MESSAGE_TERMINATOR, &mut buf).await?;
|
||||
self.bytes_read += bytes_read;
|
||||
|
||||
// Since QMDL is just a flat list of messages, we can't actually
|
||||
@@ -82,7 +79,7 @@ impl<T> DiagReader for QmdlReader<T> where T: Read {
|
||||
// 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 {
|
||||
Ok(Some(MessagesContainer {
|
||||
data_type: DataType::UserSpace,
|
||||
num_messages: 1,
|
||||
messages: vec![
|
||||
@@ -91,7 +88,7 @@ impl<T> DiagReader for QmdlReader<T> where T: Read {
|
||||
data: buf,
|
||||
},
|
||||
]
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +97,7 @@ mod test {
|
||||
use std::io::Cursor;
|
||||
|
||||
use crate::hdlc::hdlc_encapsulate;
|
||||
use crate::diag_reader::CRC_CCITT;
|
||||
use crate::diag::CRC_CCITT;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -140,8 +137,8 @@ mod test {
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unbounded_qmdl_reader() {
|
||||
#[tokio::test]
|
||||
async fn test_unbounded_qmdl_reader() {
|
||||
let mut buf = Cursor::new(get_test_message_bytes());
|
||||
let mut reader = QmdlReader::new(&mut buf, None);
|
||||
let expected_messages = get_test_messages();
|
||||
@@ -151,12 +148,12 @@ mod test {
|
||||
num_messages: 1,
|
||||
messages: vec![message],
|
||||
};
|
||||
assert_eq!(expected_container, reader.get_next_messages_container().unwrap());
|
||||
assert_eq!(expected_container, reader.get_next_messages_container().await.unwrap().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bounded_qmdl_reader() {
|
||||
#[tokio::test]
|
||||
async fn test_bounded_qmdl_reader() {
|
||||
let mut buf = Cursor::new(get_test_message_bytes());
|
||||
|
||||
// bound the reader to the first two messages
|
||||
@@ -170,30 +167,30 @@ mod test {
|
||||
num_messages: 1,
|
||||
messages: vec![message],
|
||||
};
|
||||
assert_eq!(expected_container, reader.get_next_messages_container().unwrap());
|
||||
assert_eq!(expected_container, reader.get_next_messages_container().await.unwrap().unwrap());
|
||||
}
|
||||
assert!(matches!(reader.get_next_messages_container(), Err(QmdlReaderError::MaxBytesReached(_))));
|
||||
assert!(matches!(reader.get_next_messages_container().await, Ok(None)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_qmdl_writer() {
|
||||
#[tokio::test]
|
||||
async fn test_qmdl_writer() {
|
||||
let mut buf = Vec::new();
|
||||
let mut writer = QmdlWriter::new(&mut buf);
|
||||
let expected_containers = get_test_containers();
|
||||
for container in &expected_containers {
|
||||
writer.write_container(container).unwrap();
|
||||
writer.write_container(container).await.unwrap();
|
||||
}
|
||||
assert_eq!(writer.total_written, buf.len());
|
||||
assert_eq!(buf, get_test_message_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_writing_and_reading() {
|
||||
#[tokio::test]
|
||||
async fn test_writing_and_reading() {
|
||||
let mut buf = Vec::new();
|
||||
let mut writer = QmdlWriter::new(&mut buf);
|
||||
let expected_containers = get_test_containers();
|
||||
for container in &expected_containers {
|
||||
writer.write_container(container).unwrap();
|
||||
writer.write_container(container).await.unwrap();
|
||||
}
|
||||
|
||||
let limit = Some(buf.len());
|
||||
@@ -205,8 +202,8 @@ mod test {
|
||||
num_messages: 1,
|
||||
messages: vec![message],
|
||||
};
|
||||
assert_eq!(expected_container, reader.get_next_messages_container().unwrap());
|
||||
assert_eq!(expected_container, reader.get_next_messages_container().await.unwrap().unwrap());
|
||||
}
|
||||
assert!(matches!(reader.get_next_messages_container(), Err(QmdlReaderError::MaxBytesReached(_))));
|
||||
assert!(matches!(reader.get_next_messages_container().await, Ok(None)));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user