Skip to main content

universal_weave/
versioning.rs

1//! Utilities for versioning serialized binary data.
2
3use rkyv::{rancor::Fallible, ser::Writer};
4
5/// A set of bytes accompanied by file header information.
6///
7/// Buffers deserialized using [`rkyv`] must be [aligned to 16-byte boundaries](https://rkyv.org/format/alignment.html). [`VersionedBytes`] is capable of preserving 16-byte memory alignment if the backing byte buffer is correctly aligned.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
9#[must_use]
10pub struct VersionedBytes<'a> {
11    /// The magic bytes at the start of the file indicating the format used.
12    pub format_identifier: [u8; 24],
13    /// The format version stored within the header.
14    pub version: u64,
15    /// The data following the header.
16    pub data: &'a [u8],
17}
18
19impl<'a> VersionedBytes<'a> {
20    /// Tries to deserialize a [`VersionedBytes`] struct from a byte array.
21    ///
22    /// This can fail in the following cases:
23    /// - The specified `format_identifier` does not match the first 24 bytes of the byte array
24    /// - The byte array is less than 32 bytes long
25    #[allow(clippy::missing_panics_doc, reason = "Should never panic")]
26    #[must_use]
27    #[inline]
28    pub fn try_from_bytes(value: &'a [u8], format_identifier: [u8; 24]) -> Option<Self> {
29        if value.starts_with(&format_identifier) && value.len() >= 32 {
30            let (version_bytes, data) = value[24..].split_at(8);
31
32            Some(Self {
33                format_identifier,
34                version: u64::from_le_bytes(version_bytes.try_into().unwrap()),
35                data,
36            })
37        } else {
38            None
39        }
40    }
41    /// The total length in bytes after serialization.
42    #[must_use]
43    #[inline]
44    pub const fn output_length(&self) -> usize {
45        32_usize.strict_add(self.data.len())
46    }
47    /// Serializes the header into the specified writer.
48    ///
49    /// # Errors
50    ///
51    /// Returns `Err` if writing to the given writer fails.
52    #[inline]
53    pub fn write_header<W>(&self, writer: &mut W) -> Result<(), <W as Fallible>::Error>
54    where
55        W: Writer + Fallible,
56    {
57        writer.write(&self.format_identifier)?;
58        writer.write(&self.version.to_le_bytes())?;
59
60        Ok(())
61    }
62    /// Serializes the header and contents into the specified writer.
63    ///
64    /// # Errors
65    ///
66    /// Returns `Err` if writing to the given writer fails.
67    #[inline]
68    pub fn write<W>(&self, writer: &mut W) -> Result<(), <W as Fallible>::Error>
69    where
70        W: Writer + Fallible,
71    {
72        writer.write(&self.format_identifier)?;
73        writer.write(&self.version.to_le_bytes())?;
74        writer.write(self.data)?;
75
76        Ok(())
77    }
78}