#[derive(Debug, PartialEq, Clone)]
pub enum LogLevel {
Fatal,
Error,
Warn,
Info,
Debug,
Verbose,
Unknown(u8),
}
#[derive(Debug, PartialEq, Clone)]
pub struct DltMessage {
pub timestamp_us: u64,
pub ecu_id: String,
pub apid: Option<String>,
pub ctid: Option<String>,
pub log_level: Option<LogLevel>,
pub payload_text: String,
pub payload_raw: Vec<u8>,
}
#[derive(Debug, PartialEq)]
pub enum ParseError {
Incomplete(usize),
InvalidMagicNumber,
InvalidHeader,
Unknown,
}
use nom::{
IResult,
bytes::complete::{tag, take},
number::complete::{le_u16, le_u32},
};
fn parse_storage_header(input: &[u8]) -> IResult<&[u8], (u64, String)> {
let (input, _magic) = tag("DLT\x01".as_bytes())(input)?;
let (input, timestamp_sec) = le_u32(input)?;
let (input, timestamp_us) = le_u32(input)?;
let (input, ecu_id_bytes) = take(4usize)(input)?;
let ecu_id = String::from_utf8_lossy(ecu_id_bytes)
.trim_end_matches('\0')
.to_string();
let combined_us = (timestamp_sec as u64) * 1_000_000 + (timestamp_us as u64);
Ok((input, (combined_us, ecu_id)))
}
fn parse_standard_header(input: &[u8]) -> IResult<&[u8], (u8, u8, u16)> {
let (input, htyp) = nom::number::complete::u8(input)?;
let (input, mcnt) = nom::number::complete::u8(input)?;
let (input, len) = le_u16(input)?;
Ok((input, (htyp, mcnt, len)))
}
fn parse_extended_header(input: &[u8]) -> IResult<&[u8], (u8, u8, String, String)> {
let (input, msin) = nom::number::complete::u8(input)?;
let (input, noar) = nom::number::complete::u8(input)?;
let (input, apid_bytes) = take(4usize)(input)?;
let (input, ctid_bytes) = take(4usize)(input)?;
let apid = String::from_utf8_lossy(apid_bytes)
.trim_end_matches('\0')
.to_string();
let ctid = String::from_utf8_lossy(ctid_bytes)
.trim_end_matches('\0')
.to_string();
Ok((input, (msin, noar, apid, ctid)))
}
pub fn parse_dlt_message(input: &[u8]) -> Result<(&[u8], DltMessage), ParseError> {
if input.len() < 4 {
return Err(ParseError::Incomplete(4 - input.len()));
}
let storage_res = parse_storage_header(input);
let (input, (timestamp_us, ecu_id)) = match storage_res {
Ok(res) => res,
Err(nom::Err::Error(_e)) | Err(nom::Err::Failure(_e)) => {
if input.starts_with(b"DLT") {
return Err(ParseError::Incomplete(16));
} else {
return Err(ParseError::InvalidMagicNumber);
}
}
Err(nom::Err::Incomplete(_needed)) => {
return Err(ParseError::Incomplete(1));
}
};
let (mut input, (htyp, _mcnt, len)) = match parse_standard_header(input) {
Ok(res) => res,
Err(nom::Err::Incomplete(_)) => return Err(ParseError::Incomplete(4)),
Err(_) => return Err(ParseError::Incomplete(4)), };
let expected_remaining = (len as usize).saturating_sub(4);
if input.len() < expected_remaining {
return Err(ParseError::Incomplete(expected_remaining - input.len()));
}
let ueh = (htyp & 0x01) != 0;
let mut msg_apid = None;
let mut msg_ctid = None;
let mut msg_log_level = None;
let expected_payload_len = len.saturating_sub(4);
let mut actual_payload_len = expected_payload_len as usize;
if ueh {
if actual_payload_len < 10 {
return Err(ParseError::InvalidHeader);
}
let (new_input, (msin, _noar, apid, ctid)) = match parse_extended_header(input) {
Ok(res) => res,
Err(nom::Err::Incomplete(_)) => return Err(ParseError::Incomplete(10)),
Err(_) => return Err(ParseError::InvalidHeader),
};
input = new_input;
msg_apid = Some(apid);
msg_ctid = Some(ctid);
let msg_type = msin & 0x07; if msg_type == 0 {
let log_lvl = (msin >> 3) & 0x07; match log_lvl {
1 => msg_log_level = Some(LogLevel::Fatal),
2 => msg_log_level = Some(LogLevel::Error),
3 => msg_log_level = Some(LogLevel::Warn),
4 => msg_log_level = Some(LogLevel::Info),
5 => msg_log_level = Some(LogLevel::Debug),
6 => msg_log_level = Some(LogLevel::Verbose),
other => msg_log_level = Some(LogLevel::Unknown(other)),
}
}
actual_payload_len -= 10;
}
if input.len() < actual_payload_len {
return Err(ParseError::Incomplete(actual_payload_len - input.len()));
}
let take_payload: IResult<&[u8], &[u8]> = take(actual_payload_len)(input);
let (new_input, payload_bytes) = match take_payload {
Ok(res) => res,
Err(_) => return Err(ParseError::Incomplete(actual_payload_len)),
};
input = new_input;
let raw_text = String::from_utf8_lossy(payload_bytes);
let payload_text = raw_text
.chars()
.map(|c| {
if c.is_control() && c != '\n' && c != '\t' {
'.'
} else {
c
}
})
.collect::<String>();
Ok((
input,
DltMessage {
timestamp_us,
ecu_id,
apid: msg_apid,
ctid: msg_ctid,
log_level: msg_log_level,
payload_text,
payload_raw: payload_bytes.to_vec(),
},
))
}
#[cfg(test)]
mod tests {
use super::*;
fn build_valid_dlt_message_bytes() -> Vec<u8> {
let mut msg = Vec::new();
msg.extend_from_slice(b"DLT\x01"); msg.extend_from_slice(&1640995200u32.to_le_bytes()); msg.extend_from_slice(&123456u32.to_le_bytes()); msg.extend_from_slice(b"ECU1");
msg.push(0x21); msg.push(0x00);
let payload = b"Hello DLT";
msg.extend_from_slice(&23u16.to_le_bytes());
msg.push(0x20); msg.push(1); msg.extend_from_slice(b"APP1"); msg.extend_from_slice(b"CTX1");
msg.extend_from_slice(payload);
msg
}
#[test]
fn test_parse_valid_dlt_message() {
let data = build_valid_dlt_message_bytes();
let (remaining, msg) = parse_dlt_message(&data).expect("Parsing failed for valid message");
assert_eq!(remaining.len(), 0, "Should consume the entire stream");
assert_eq!(msg.ecu_id, "ECU1");
assert_eq!(msg.apid, Some("APP1".to_string()));
assert_eq!(msg.ctid, Some("CTX1".to_string()));
assert_eq!(msg.log_level, Some(LogLevel::Info));
assert_eq!(msg.payload_text, "Hello DLT");
assert_eq!(msg.payload_raw, b"Hello DLT".to_vec());
}
#[test]
fn test_parse_invalid_magic_number() {
let mut data = build_valid_dlt_message_bytes();
data[0] = b'X';
let err = parse_dlt_message(&data).unwrap_err();
assert_eq!(err, ParseError::InvalidMagicNumber);
}
#[test]
fn test_parse_truncated_message() {
let mut data = build_valid_dlt_message_bytes();
data.truncate(20);
let err = parse_dlt_message(&data).unwrap_err();
match err {
ParseError::Incomplete(_) => {} _ => panic!("Expected ParseError::Incomplete"),
}
}
#[test]
fn test_parse_unknown_log_level() {
let mut data = build_valid_dlt_message_bytes();
data[20] = 0x38;
let (_, msg) = parse_dlt_message(&data).expect("Should still parse");
assert_eq!(msg.log_level, Some(LogLevel::Unknown(7)));
}
}