Skip to main content

urna_format/layout/
footer.rs

1//! footer (40 bytes). Sits at `file_size - 40` and carries the
2//! `file_hash` (full SHA-256 over the file body) plus the footer's own
3//! size for forward compat.
4
5use crate::error::UrnaError;
6use sha2::{Digest, Sha256};
7
8use super::URNA_FOOTER_SIZE;
9
10// `Pod` + `Zeroable` are derived, not asserted: the derive fails to
11// compile if the struct ever gains padding or a field that is not valid
12// for every bit pattern, which is exactly the invariant the on-disk
13// byte view below relies on. No `unsafe` in this file.
14#[repr(C)]
15#[derive(Clone, Copy, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
16pub struct UrnaFooter {
17    pub footer_size: u64,
18    pub file_hash: [u8; 32],
19}
20
21impl UrnaFooter {
22    pub fn new(file_hash: [u8; 32]) -> Self {
23        Self {
24            footer_size: URNA_FOOTER_SIZE as u64,
25            file_hash,
26        }
27    }
28
29    /// The exact on-disk bytes of this record (little-endian host only,
30    /// which is every supported target; see `layout::tests`).
31    pub fn as_bytes(&self) -> &[u8] {
32        bytemuck::bytes_of(self)
33    }
34
35    /// Mutable view over the on-disk bytes; `from_bytes`-style readers copy
36    /// a slice in here. Sound for any content because every field accepts
37    /// every bit pattern (`Pod`).
38    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
39        bytemuck::bytes_of_mut(self)
40    }
41
42    pub fn compute_file_hash(data_without_footer: &[u8]) -> [u8; 32] {
43        let hash = Sha256::digest(data_without_footer);
44        let mut out = [0u8; 32];
45        out.copy_from_slice(&hash[..]);
46        out
47    }
48
49    pub fn from_bytes(bytes: &[u8]) -> crate::Result<Self> {
50        if bytes.len() < std::mem::size_of::<Self>() {
51            return Err(UrnaError::UnexpectedEof);
52        }
53        let mut footer = UrnaFooter::new([0; 32]);
54        footer.as_bytes_mut().copy_from_slice(bytes);
55        Ok(footer)
56    }
57}