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:
Will Greenberg
2024-02-13 20:23:02 -08:00
parent abd3b98cff
commit 775cbcda1e
14 changed files with 550 additions and 629 deletions

View File

@@ -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(_, _))));
}
}