Skip to main content

onelf_format/
footer.rs

1//! Footer structure and serialization
2//!
3//! The footer is located at the end of every ONELF package and contains:
4//! - Magic bytes for identification
5//! - Format version
6//! - Offsets to manifest, payload, and optional dictionary
7//! - Checksums for integrity verification
8//!
9//! # Structure
10//!
11//! The footer is exactly 76 bytes and is organized as follows:
12//!
13//! ```text
14//! Offset  Size    Field
15//! ------  -------  -------------------
16//! 0      8        Magic: "ONELF\0\x01\x00"
17//! 8      2        Format version (u16)
18//! 10     2        Flags (u16)
19//! 12     8        Manifest offset (u64)
20//! 20     8        Manifest compressed size (u64)
21//! 28     8        Manifest original size (u64)
22//! 36     8        Payload offset (u64)
23//! 44     8        Payload total size (u64)
24//! 52     8        Dictionary offset (u64)
25//! 60     4        Dictionary size (u32)
26//! 64     4        Manifest checksum (xxh32)
27//! 68     8        End magic: "FLENONE\x00"
28//!//!
29//! # Example
30//!
31//! no_run
32//! use onelf_format::Footer;
33//!
34//! let footer = Footer {
35//!     format_version: 1,
36//!     // ... other fields
37//! };
38//!
39
40use std::io::{self, Read, Write};
41
42pub const FOOTER_SIZE: usize = 76;
43pub const MAGIC: [u8; 8] = *b"ONELF\x00\x01\x00";
44pub const END_MAGIC: [u8; 8] = *b"FLENONE\x00";
45
46bitflags! {
47    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
48    pub struct Flags: u16 {
49        const HAS_DICT       = 1 << 0;
50        const MEMFD_HINT     = 1 << 1;
51        const SHARUN_COMPAT  = 1 << 2;
52        /// Payload blocks are stored raw (no zstd). `compressed_size`
53        /// equals `original_size` for every block; the runtime reads
54        /// payload bytes directly without decompression.
55        const STORED         = 1 << 3;
56    }
57}
58
59#[derive(Debug, Clone)]
60pub struct Footer {
61    /// Format version number (currently 1).
62    pub format_version: u16,
63    /// Feature flags describing optional sections and capabilities.
64    pub flags: Flags,
65    /// Byte offset where the compressed manifest begins.
66    pub manifest_offset: u64,
67    /// Size of the manifest after compression.
68    pub manifest_compressed: u64,
69    /// Size of the manifest before compression.
70    pub manifest_original: u64,
71    /// Byte offset where the payload section begins.
72    pub payload_offset: u64,
73    /// Total size of the payload section in bytes.
74    pub payload_size: u64,
75    /// Byte offset of the zstd dictionary, or 0 if absent.
76    pub dict_offset: u64,
77    /// Size of the zstd dictionary in bytes, or 0 if absent.
78    pub dict_size: u32,
79    /// xxHash32 checksum of the compressed manifest for integrity verification.
80    pub manifest_checksum: [u8; 4],
81}
82
83impl Footer {
84    /// Whether the payload is stored raw (no zstd). When true the runtime
85    /// must skip decompression and use payload bytes directly.
86    pub fn is_stored(&self) -> bool {
87        self.flags.contains(Flags::STORED)
88    }
89
90    pub fn write_to<W: Write>(&self, w: &mut W) -> io::Result<()> {
91        w.write_all(&MAGIC)?; // 8
92        w.write_all(&self.format_version.to_le_bytes())?; // 2
93        w.write_all(&self.flags.bits().to_le_bytes())?; // 2
94        w.write_all(&self.manifest_offset.to_le_bytes())?; // 8
95        w.write_all(&self.manifest_compressed.to_le_bytes())?; // 8
96        w.write_all(&self.manifest_original.to_le_bytes())?; // 8
97        w.write_all(&self.payload_offset.to_le_bytes())?; // 8
98        w.write_all(&self.payload_size.to_le_bytes())?; // 8
99        w.write_all(&self.dict_offset.to_le_bytes())?; // 8
100        w.write_all(&self.dict_size.to_le_bytes())?; // 4
101        w.write_all(&self.manifest_checksum)?; // 4
102        w.write_all(&END_MAGIC)?; // 8
103        Ok(()) // = 76
104    }
105
106    pub fn read_from<R: Read>(r: &mut R) -> io::Result<Self> {
107        let mut buf = [0u8; FOOTER_SIZE];
108        r.read_exact(&mut buf)?;
109        Self::from_bytes(&buf)
110    }
111
112    pub fn from_bytes(buf: &[u8; FOOTER_SIZE]) -> io::Result<Self> {
113        if &buf[0..8] != &MAGIC {
114            return Err(io::Error::new(
115                io::ErrorKind::InvalidData,
116                "invalid onelf magic",
117            ));
118        }
119        if &buf[68..76] != &END_MAGIC {
120            return Err(io::Error::new(
121                io::ErrorKind::InvalidData,
122                "invalid onelf end magic",
123            ));
124        }
125
126        let format_version = u16::from_le_bytes(buf[8..10].try_into().unwrap());
127        if format_version != 1 {
128            return Err(io::Error::new(
129                io::ErrorKind::InvalidData,
130                format!("unsupported format version: {}", format_version),
131            ));
132        }
133
134        let flags_raw = u16::from_le_bytes(buf[10..12].try_into().unwrap());
135        let flags = Flags::from_bits_truncate(flags_raw);
136
137        Ok(Footer {
138            format_version,
139            flags,
140            manifest_offset: u64::from_le_bytes(buf[12..20].try_into().unwrap()),
141            manifest_compressed: u64::from_le_bytes(buf[20..28].try_into().unwrap()),
142            manifest_original: u64::from_le_bytes(buf[28..36].try_into().unwrap()),
143            payload_offset: u64::from_le_bytes(buf[36..44].try_into().unwrap()),
144            payload_size: u64::from_le_bytes(buf[44..52].try_into().unwrap()),
145            dict_offset: u64::from_le_bytes(buf[52..60].try_into().unwrap()),
146            dict_size: u32::from_le_bytes(buf[60..64].try_into().unwrap()),
147            manifest_checksum: buf[64..68].try_into().unwrap(),
148        })
149    }
150}