use alloc::vec::Vec;
use crate::error::Error;
#[derive(Debug)]
pub struct RtMessage<'a> {
tags: Vec<u32>,
values: Vec<&'a [u8]>,
}
impl<'a> RtMessage<'a> {
pub fn parse(data: &'a [u8]) -> Result<Self, Error> {
if data.len() < 4 {
return Err(Error::MessageTooShort);
}
let num_tags = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize;
if num_tags == 0 {
return Err(Error::NoTags);
}
let header_len = 8 * num_tags;
if data.len() < header_len {
return Err(Error::MessageTooShort);
}
let mut offsets = Vec::with_capacity(num_tags);
offsets.push(0usize);
for i in 0..num_tags.saturating_sub(1) {
let idx = 4 + i * 4;
offsets.push(u32::from_le_bytes([
data[idx],
data[idx + 1],
data[idx + 2],
data[idx + 3],
]) as usize);
}
let mut tags = Vec::with_capacity(num_tags);
let tags_start = 4 + num_tags.saturating_sub(1) * 4;
for i in 0..num_tags {
let idx = tags_start + i * 4;
tags.push(u32::from_le_bytes([
data[idx],
data[idx + 1],
data[idx + 2],
data[idx + 3],
]));
}
let values_start = 8 * num_tags;
if data.len() < values_start {
return Err(Error::MessageTooShort);
}
let values_len = data.len() - values_start;
let values_data = &data[values_start..];
for i in 0..num_tags {
let start = offsets[i];
let end = if i + 1 < num_tags {
offsets[i + 1]
} else {
values_len
};
if start > end || end > values_len || start % 4 != 0 || end % 4 != 0 {
return Err(Error::InvalidOffsets);
}
}
let mut values = Vec::with_capacity(num_tags);
for i in 0..num_tags {
let start = offsets[i];
let end = if i + 1 < num_tags {
offsets[i + 1]
} else {
values_len
};
values.push(&values_data[start..end]);
}
Ok(Self { tags, values })
}
#[must_use]
pub fn get(&self, tag: u32) -> Option<&'a [u8]> {
self.tags
.iter()
.position(|&t| t == tag)
.map(|i| self.values[i])
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used)]
use super::*;
use crate::tags::{TAG_NONC, TAG_PAD};
#[test]
fn parses_two_tag_message() {
let mut msg_data = Vec::new();
msg_data.extend_from_slice(&2u32.to_le_bytes());
msg_data.extend_from_slice(&4u32.to_le_bytes());
msg_data.extend_from_slice(&TAG_NONC.to_le_bytes());
msg_data.extend_from_slice(&TAG_PAD.to_le_bytes());
msg_data.extend_from_slice(&[0x11, 0x22, 0x33, 0x44]);
msg_data.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
let parsed = RtMessage::parse(&msg_data).expect("should parse message");
assert_eq!(parsed.get(TAG_NONC), Some(&[0x11, 0x22, 0x33, 0x44][..]));
assert_eq!(parsed.get(TAG_PAD), Some(&[0x00, 0x00, 0x00, 0x00][..]));
}
#[test]
fn rejects_short_message() {
assert!(RtMessage::parse(&[0x01, 0x00]).is_err());
}
#[test]
fn rejects_zero_tags() {
assert!(RtMessage::parse(&0u32.to_le_bytes()).is_err());
}
}