1use crate::{
11 Error, Result,
12 blocks::{metadata_block::MetadataBlock, text_block::TextBlock},
13};
14use alloc::format;
15use alloc::string::{String, ToString};
16use alloc::vec::Vec;
17
18#[inline]
27pub fn read_u64(bytes: &[u8], offset: usize) -> u64 {
28 u64::from_le_bytes([
29 bytes[offset],
30 bytes[offset + 1],
31 bytes[offset + 2],
32 bytes[offset + 3],
33 bytes[offset + 4],
34 bytes[offset + 5],
35 bytes[offset + 6],
36 bytes[offset + 7],
37 ])
38}
39
40#[inline]
42pub fn read_u32(bytes: &[u8], offset: usize) -> u32 {
43 u32::from_le_bytes([
44 bytes[offset],
45 bytes[offset + 1],
46 bytes[offset + 2],
47 bytes[offset + 3],
48 ])
49}
50
51#[inline]
53pub fn read_u16(bytes: &[u8], offset: usize) -> u16 {
54 u16::from_le_bytes([bytes[offset], bytes[offset + 1]])
55}
56
57#[inline]
59pub fn read_f64(bytes: &[u8], offset: usize) -> f64 {
60 f64::from_le_bytes([
61 bytes[offset],
62 bytes[offset + 1],
63 bytes[offset + 2],
64 bytes[offset + 3],
65 bytes[offset + 4],
66 bytes[offset + 5],
67 bytes[offset + 6],
68 bytes[offset + 7],
69 ])
70}
71
72#[inline]
74pub fn read_u8(bytes: &[u8], offset: usize) -> u8 {
75 bytes[offset]
76}
77
78#[inline]
86pub fn validate_buffer_size(bytes: &[u8], expected: usize) -> Result<()> {
87 if bytes.len() < expected {
88 return Err(Error::TooShortBuffer {
89 actual: bytes.len(),
90 expected,
91 file: file!(),
92 line: line!(),
93 });
94 }
95 Ok(())
96}
97
98#[inline]
100pub fn validate_block_id(header: &BlockHeader, expected_id: &str) -> Result<()> {
101 if header.id != expected_id {
102 return Err(Error::BlockSerializationError(format!(
103 "Block must have ID '{}', found '{}'",
104 expected_id, header.id
105 )));
106 }
107 Ok(())
108}
109
110#[inline]
112pub fn validate_block_length(header: &BlockHeader, expected: u64) -> Result<()> {
113 if header.length != expected {
114 return Err(Error::BlockSerializationError(format!(
115 "Block must have length={}, found {}",
116 expected, header.length
117 )));
118 }
119 Ok(())
120}
121
122#[inline]
124pub fn debug_assert_aligned(size: usize) {
125 debug_assert_eq!(size % 8, 0, "Block size {} is not 8-byte aligned", size);
126}
127
128#[inline]
130pub const fn padding_to_align_8(size: usize) -> usize {
131 (8 - (size % 8)) % 8
132}
133
134#[inline]
146pub fn u64_to_usize(value: u64, context: &str) -> Result<usize> {
147 usize::try_from(value).map_err(|_| {
148 Error::BlockSerializationError(format!(
149 "{} value {} exceeds maximum addressable size on this platform",
150 context, value
151 ))
152 })
153}
154
155#[derive(Debug, Clone)]
156#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
157pub struct BlockHeader {
158 pub id: String,
160 pub reserved: u32,
162 pub length: u64,
164 pub link_count: u64,
166}
167
168impl Default for BlockHeader {
169 fn default() -> Self {
172 BlockHeader {
173 id: String::from("UNSET"),
174 reserved: 0,
175 length: 0,
176 link_count: 0,
177 }
178 }
179}
180
181impl BlockHeader {
182 pub fn to_bytes(&self) -> Result<Vec<u8>> {
194 let mut buffer = Vec::with_capacity(24);
195
196 let id_bytes = self.id.as_bytes();
198 let mut id_field = [0u8; 4];
199 let id_len = core::cmp::min(id_bytes.len(), 4);
200 id_field[..id_len].copy_from_slice(&id_bytes[..id_len]);
201 buffer.extend_from_slice(&id_field);
202
203 buffer.extend_from_slice(&self.reserved.to_le_bytes());
205
206 buffer.extend_from_slice(&self.length.to_le_bytes());
208
209 buffer.extend_from_slice(&self.link_count.to_le_bytes());
211
212 debug_assert_eq!(buffer.len(), 24);
213 Ok(buffer)
214 }
215
216 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
225 validate_buffer_size(bytes, 24)?;
226
227 let id = match core::str::from_utf8(&bytes[0..4]) {
228 Ok(s) => String::from(s),
229 Err(_) => String::from_utf8_lossy(&bytes[0..4]).into_owned(),
230 };
231
232 Ok(Self {
233 id,
234 reserved: read_u32(bytes, 4),
235 length: read_u64(bytes, 8),
236 link_count: read_u64(bytes, 16),
237 })
238 }
239}
240
241pub trait BlockParse<'a>: Sized {
242 const ID: &'static str;
243
244 fn parse_header(bytes: &[u8]) -> Result<BlockHeader> {
245 let header = BlockHeader::from_bytes(&bytes[0..24])?;
246 if header.id != Self::ID {
247 return Err(Error::BlockIDError {
248 actual: header.id.clone(),
249 expected: Self::ID.to_string(),
250 });
251 }
252 Ok(header)
253 }
254
255 fn from_bytes(bytes: &'a [u8]) -> Result<Self>;
256}
257
258#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
259#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
260pub enum DataType {
261 UnsignedIntegerLE,
262 UnsignedIntegerBE,
263 SignedIntegerLE,
264 SignedIntegerBE,
265 FloatLE,
266 FloatBE,
267 StringLatin1,
268 StringUtf8,
269 StringUtf16LE,
270 StringUtf16BE,
271 ByteArray,
272 MimeSample,
273 MimeStream,
274 CanOpenDate,
275 CanOpenTime,
276 ComplexLE,
277 ComplexBE,
278 Unknown(()),
279}
280
281impl DataType {
282 pub fn to_u8(&self) -> u8 {
292 match self {
293 DataType::UnsignedIntegerLE => 0,
294 DataType::UnsignedIntegerBE => 1,
295 DataType::SignedIntegerLE => 2,
296 DataType::SignedIntegerBE => 3,
297 DataType::FloatLE => 4,
298 DataType::FloatBE => 5,
299 DataType::StringLatin1 => 6,
300 DataType::StringUtf8 => 7,
301 DataType::StringUtf16LE => 8,
302 DataType::StringUtf16BE => 9,
303 DataType::ByteArray => 10,
304 DataType::MimeSample => 11,
305 DataType::MimeStream => 12,
306 DataType::CanOpenDate => 13,
307 DataType::CanOpenTime => 14,
308 DataType::ComplexLE => 15, DataType::ComplexBE => 16, DataType::Unknown(_) => 0, }
312 }
313
314 pub fn from_u8(value: u8) -> Self {
317 match value {
318 0 => DataType::UnsignedIntegerLE,
319 1 => DataType::UnsignedIntegerBE,
320 2 => DataType::SignedIntegerLE,
321 3 => DataType::SignedIntegerBE,
322 4 => DataType::FloatLE,
323 5 => DataType::FloatBE,
324 6 => DataType::StringLatin1,
325 7 => DataType::StringUtf8,
326 8 => DataType::StringUtf16LE,
327 9 => DataType::StringUtf16BE,
328 10 => DataType::ByteArray,
329 11 => DataType::MimeSample,
330 12 => DataType::MimeStream,
331 13 => DataType::CanOpenDate,
332 14 => DataType::CanOpenTime,
333 15 => DataType::ComplexLE,
334 16 => DataType::ComplexBE,
335 _ => DataType::Unknown(()),
336 }
337 }
338
339 pub fn default_bits(&self) -> u32 {
342 match self {
343 DataType::UnsignedIntegerLE
344 | DataType::UnsignedIntegerBE
345 | DataType::SignedIntegerLE
346 | DataType::SignedIntegerBE => 32,
347 DataType::FloatLE | DataType::FloatBE => 32,
348 DataType::StringLatin1
349 | DataType::StringUtf8
350 | DataType::StringUtf16LE
351 | DataType::StringUtf16BE
352 | DataType::ByteArray
353 | DataType::MimeSample
354 | DataType::MimeStream => 8,
355 DataType::CanOpenDate | DataType::CanOpenTime => 64,
356 DataType::ComplexLE | DataType::ComplexBE => 64,
357 DataType::Unknown(_) => 8,
358 }
359 }
360}
361
362impl core::fmt::Display for DataType {
363 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
364 match self {
365 DataType::UnsignedIntegerLE => write!(f, "uint (LE)"),
366 DataType::UnsignedIntegerBE => write!(f, "uint (BE)"),
367 DataType::SignedIntegerLE => write!(f, "int (LE)"),
368 DataType::SignedIntegerBE => write!(f, "int (BE)"),
369 DataType::FloatLE => write!(f, "float (LE)"),
370 DataType::FloatBE => write!(f, "float (BE)"),
371 DataType::StringLatin1 => write!(f, "string (Latin-1)"),
372 DataType::StringUtf8 => write!(f, "string (UTF-8)"),
373 DataType::StringUtf16LE => write!(f, "string (UTF-16 LE)"),
374 DataType::StringUtf16BE => write!(f, "string (UTF-16 BE)"),
375 DataType::ByteArray => write!(f, "byte array"),
376 DataType::MimeSample => write!(f, "MIME sample"),
377 DataType::MimeStream => write!(f, "MIME stream"),
378 DataType::CanOpenDate => write!(f, "CANopen date"),
379 DataType::CanOpenTime => write!(f, "CANopen time"),
380 DataType::ComplexLE => write!(f, "complex (LE)"),
381 DataType::ComplexBE => write!(f, "complex (BE)"),
382 DataType::Unknown(_) => write!(f, "unknown"),
383 }
384 }
385}
386
387pub fn read_string_block(mmap: &[u8], address: u64) -> Result<Option<String>> {
397 if address == 0 {
398 return Ok(None);
399 }
400
401 let offset = u64_to_usize(address, "block address")?;
402 validate_buffer_size(mmap, offset + 24)?;
403 let header = BlockHeader::from_bytes(&mmap[offset..offset + 24])?;
404
405 match header.id.as_str() {
406 "##TX" => Ok(Some(TextBlock::from_bytes(&mmap[offset..])?.text)),
407 "##MD" => Ok(Some(MetadataBlock::from_bytes(&mmap[offset..])?.xml)),
408 _ => Ok(None),
409 }
410}