use crate::{
Error, Result,
blocks::{metadata_block::MetadataBlock, text_block::TextBlock},
};
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
#[inline]
pub fn read_u64(bytes: &[u8], offset: usize) -> u64 {
u64::from_le_bytes([
bytes[offset],
bytes[offset + 1],
bytes[offset + 2],
bytes[offset + 3],
bytes[offset + 4],
bytes[offset + 5],
bytes[offset + 6],
bytes[offset + 7],
])
}
#[inline]
pub fn read_u32(bytes: &[u8], offset: usize) -> u32 {
u32::from_le_bytes([
bytes[offset],
bytes[offset + 1],
bytes[offset + 2],
bytes[offset + 3],
])
}
#[inline]
pub fn read_u16(bytes: &[u8], offset: usize) -> u16 {
u16::from_le_bytes([bytes[offset], bytes[offset + 1]])
}
#[inline]
pub fn read_f64(bytes: &[u8], offset: usize) -> f64 {
f64::from_le_bytes([
bytes[offset],
bytes[offset + 1],
bytes[offset + 2],
bytes[offset + 3],
bytes[offset + 4],
bytes[offset + 5],
bytes[offset + 6],
bytes[offset + 7],
])
}
#[inline]
pub fn read_u8(bytes: &[u8], offset: usize) -> u8 {
bytes[offset]
}
#[inline]
pub fn validate_buffer_size(bytes: &[u8], expected: usize) -> Result<()> {
if bytes.len() < expected {
return Err(Error::TooShortBuffer {
actual: bytes.len(),
expected,
file: file!(),
line: line!(),
});
}
Ok(())
}
#[inline]
pub fn validate_block_id(header: &BlockHeader, expected_id: &str) -> Result<()> {
if header.id != expected_id {
return Err(Error::BlockSerializationError(format!(
"Block must have ID '{}', found '{}'",
expected_id, header.id
)));
}
Ok(())
}
#[inline]
pub fn validate_block_length(header: &BlockHeader, expected: u64) -> Result<()> {
if header.length != expected {
return Err(Error::BlockSerializationError(format!(
"Block must have length={}, found {}",
expected, header.length
)));
}
Ok(())
}
#[inline]
pub fn debug_assert_aligned(size: usize) {
debug_assert_eq!(size % 8, 0, "Block size {} is not 8-byte aligned", size);
}
#[inline]
pub const fn padding_to_align_8(size: usize) -> usize {
(8 - (size % 8)) % 8
}
#[inline]
pub fn u64_to_usize(value: u64, context: &str) -> Result<usize> {
usize::try_from(value).map_err(|_| {
Error::BlockSerializationError(format!(
"{} value {} exceeds maximum addressable size on this platform",
context, value
))
})
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BlockHeader {
pub id: String,
pub reserved: u32,
pub length: u64,
pub link_count: u64,
}
impl Default for BlockHeader {
fn default() -> Self {
BlockHeader {
id: String::from("UNSET"),
reserved: 0,
length: 0,
link_count: 0,
}
}
}
impl BlockHeader {
pub fn to_bytes(&self) -> Result<Vec<u8>> {
let mut buffer = Vec::with_capacity(24);
let id_bytes = self.id.as_bytes();
let mut id_field = [0u8; 4];
let id_len = core::cmp::min(id_bytes.len(), 4);
id_field[..id_len].copy_from_slice(&id_bytes[..id_len]);
buffer.extend_from_slice(&id_field);
buffer.extend_from_slice(&self.reserved.to_le_bytes());
buffer.extend_from_slice(&self.length.to_le_bytes());
buffer.extend_from_slice(&self.link_count.to_le_bytes());
debug_assert_eq!(buffer.len(), 24);
Ok(buffer)
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
validate_buffer_size(bytes, 24)?;
let id = match core::str::from_utf8(&bytes[0..4]) {
Ok(s) => String::from(s),
Err(_) => String::from_utf8_lossy(&bytes[0..4]).into_owned(),
};
Ok(Self {
id,
reserved: read_u32(bytes, 4),
length: read_u64(bytes, 8),
link_count: read_u64(bytes, 16),
})
}
}
pub trait BlockParse<'a>: Sized {
const ID: &'static str;
fn parse_header(bytes: &[u8]) -> Result<BlockHeader> {
let header = BlockHeader::from_bytes(&bytes[0..24])?;
if header.id != Self::ID {
return Err(Error::BlockIDError {
actual: header.id.clone(),
expected: Self::ID.to_string(),
});
}
Ok(header)
}
fn from_bytes(bytes: &'a [u8]) -> Result<Self>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DataType {
UnsignedIntegerLE,
UnsignedIntegerBE,
SignedIntegerLE,
SignedIntegerBE,
FloatLE,
FloatBE,
StringLatin1,
StringUtf8,
StringUtf16LE,
StringUtf16BE,
ByteArray,
MimeSample,
MimeStream,
CanOpenDate,
CanOpenTime,
ComplexLE,
ComplexBE,
Unknown(()),
}
impl DataType {
pub fn to_u8(&self) -> u8 {
match self {
DataType::UnsignedIntegerLE => 0,
DataType::UnsignedIntegerBE => 1,
DataType::SignedIntegerLE => 2,
DataType::SignedIntegerBE => 3,
DataType::FloatLE => 4,
DataType::FloatBE => 5,
DataType::StringLatin1 => 6,
DataType::StringUtf8 => 7,
DataType::StringUtf16LE => 8,
DataType::StringUtf16BE => 9,
DataType::ByteArray => 10,
DataType::MimeSample => 11,
DataType::MimeStream => 12,
DataType::CanOpenDate => 13,
DataType::CanOpenTime => 14,
DataType::ComplexLE => 15, DataType::ComplexBE => 16, DataType::Unknown(_) => 0, }
}
pub fn from_u8(value: u8) -> Self {
match value {
0 => DataType::UnsignedIntegerLE,
1 => DataType::UnsignedIntegerBE,
2 => DataType::SignedIntegerLE,
3 => DataType::SignedIntegerBE,
4 => DataType::FloatLE,
5 => DataType::FloatBE,
6 => DataType::StringLatin1,
7 => DataType::StringUtf8,
8 => DataType::StringUtf16LE,
9 => DataType::StringUtf16BE,
10 => DataType::ByteArray,
11 => DataType::MimeSample,
12 => DataType::MimeStream,
13 => DataType::CanOpenDate,
14 => DataType::CanOpenTime,
15 => DataType::ComplexLE,
16 => DataType::ComplexBE,
_ => DataType::Unknown(()),
}
}
pub fn default_bits(&self) -> u32 {
match self {
DataType::UnsignedIntegerLE
| DataType::UnsignedIntegerBE
| DataType::SignedIntegerLE
| DataType::SignedIntegerBE => 32,
DataType::FloatLE | DataType::FloatBE => 32,
DataType::StringLatin1
| DataType::StringUtf8
| DataType::StringUtf16LE
| DataType::StringUtf16BE
| DataType::ByteArray
| DataType::MimeSample
| DataType::MimeStream => 8,
DataType::CanOpenDate | DataType::CanOpenTime => 64,
DataType::ComplexLE | DataType::ComplexBE => 64,
DataType::Unknown(_) => 8,
}
}
}
impl core::fmt::Display for DataType {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
DataType::UnsignedIntegerLE => write!(f, "uint (LE)"),
DataType::UnsignedIntegerBE => write!(f, "uint (BE)"),
DataType::SignedIntegerLE => write!(f, "int (LE)"),
DataType::SignedIntegerBE => write!(f, "int (BE)"),
DataType::FloatLE => write!(f, "float (LE)"),
DataType::FloatBE => write!(f, "float (BE)"),
DataType::StringLatin1 => write!(f, "string (Latin-1)"),
DataType::StringUtf8 => write!(f, "string (UTF-8)"),
DataType::StringUtf16LE => write!(f, "string (UTF-16 LE)"),
DataType::StringUtf16BE => write!(f, "string (UTF-16 BE)"),
DataType::ByteArray => write!(f, "byte array"),
DataType::MimeSample => write!(f, "MIME sample"),
DataType::MimeStream => write!(f, "MIME stream"),
DataType::CanOpenDate => write!(f, "CANopen date"),
DataType::CanOpenTime => write!(f, "CANopen time"),
DataType::ComplexLE => write!(f, "complex (LE)"),
DataType::ComplexBE => write!(f, "complex (BE)"),
DataType::Unknown(_) => write!(f, "unknown"),
}
}
}
pub fn read_string_block(mmap: &[u8], address: u64) -> Result<Option<String>> {
if address == 0 {
return Ok(None);
}
let offset = u64_to_usize(address, "block address")?;
validate_buffer_size(mmap, offset + 24)?;
let header = BlockHeader::from_bytes(&mmap[offset..offset + 24])?;
match header.id.as_str() {
"##TX" => Ok(Some(TextBlock::from_bytes(&mmap[offset..])?.text)),
"##MD" => Ok(Some(MetadataBlock::from_bytes(&mmap[offset..])?.xml)),
_ => Ok(None),
}
}