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}