Skip to main content

tempest_core/journal/
header.rs

1use crc64::crc64;
2
3use crate::utils::HexU64;
4
5use super::config::JournalError;
6
7pub const JOURNAL_MAGIC_NUM: &[u8; 8] = b"TMPSJRNL";
8
9pub(super) const JOURNAL_HEADER_SIZE: usize = 24;
10
11#[derive(Debug)]
12pub(super) struct JournalHeader {
13    pub(super) filenum: u64,
14}
15
16impl JournalHeader {
17    pub(super) fn new(filenum: u64) -> Self {
18        Self { filenum }
19    }
20
21    pub(super) fn encode(&self) -> [u8; JOURNAL_HEADER_SIZE] {
22        let mut buf = [0u8; JOURNAL_HEADER_SIZE];
23        buf[0..8].copy_from_slice(JOURNAL_MAGIC_NUM);
24        buf[8..16].copy_from_slice(&self.filenum.to_le_bytes());
25        let checksum = crc64(0, &buf[0..16]);
26        buf[16..24].copy_from_slice(&checksum.to_le_bytes());
27        buf
28    }
29
30    /// A helper function for `decode`, that converts the slice into a fixed length slice.
31    /// The length of `buf` must be equal to `EDIT_PREFIX_SIZE`.
32    ///
33    /// # Panics
34    ///
35    /// Panics if `buf.len() != EDIT_PREFIX_SIZE`.
36    pub(super) fn decode_from_slice(buf: &[u8]) -> Result<Self, JournalError> {
37        assert_eq!(
38            buf.len(),
39            JOURNAL_HEADER_SIZE,
40            "could not decode JournalHeader: invalid slice length"
41        );
42        Self::decode(buf.try_into().unwrap())
43    }
44
45    fn decode(buf: [u8; JOURNAL_HEADER_SIZE]) -> Result<Self, JournalError> {
46        // validate magic num
47        let magic_bytes = &buf[0..8];
48        if magic_bytes != JOURNAL_MAGIC_NUM {
49            return Err(JournalError::InvalidMagic);
50        }
51
52        // validate checksum
53        let stored_checksum = u64::from_le_bytes(buf[16..24].try_into().unwrap());
54        let computed_checksum = crc64(0, &buf[0..16]);
55        if stored_checksum != computed_checksum {
56            return Err(JournalError::Checksum);
57        }
58
59        // retrieve filenum
60        let filenum = u64::from_le_bytes(buf[8..16].try_into().unwrap());
61        Ok(Self { filenum })
62    }
63}
64
65pub(super) const EDIT_PREFIX_SIZE: usize = 12;
66
67#[derive(Debug, Clone, Copy)]
68pub struct EditPrefix {
69    #[debug("{:?}", HexU64(*checksum))]
70    checksum: u64,
71    len: u32,
72}
73
74impl EditPrefix {
75    /// Creates a new `EditPrefix` for `data`, computing the checksum and length for framing it.
76    /// The length of `data` may not be larger than `u32::MAX`.
77    ///
78    /// # Panics
79    ///
80    /// Panics if `data.len() > u32::MAX`.
81    pub fn new(data: &[u8]) -> Self {
82        assert!(
83            data.len() <= u32::MAX as usize,
84            "journal edits may not be larger than u32::MAX bytes"
85        );
86        let checksum = crc64(0, data);
87        let len = data.len() as u32;
88        Self { checksum, len }
89    }
90
91    /// Encode this into bytes.
92    pub fn encode(&self) -> [u8; EDIT_PREFIX_SIZE] {
93        let mut buf = [0u8; EDIT_PREFIX_SIZE];
94        buf[0..8].copy_from_slice(&self.checksum.to_le_bytes());
95        buf[8..12].copy_from_slice(&self.len.to_le_bytes());
96        buf
97    }
98
99    /// A helper function for `decode`, that converts the slice into a fixed length slice.
100    /// The length of `buf` must be equal to `EDIT_PREFIX_SIZE`.
101    ///
102    /// # Panics
103    ///
104    /// Panics if `buf.len() != EDIT_PREFIX_SIZE`.
105    pub fn decode_from_slice(buf: &[u8]) -> Self {
106        assert_eq!(
107            buf.len(),
108            EDIT_PREFIX_SIZE,
109            "could not decode EditPrefix: invalid slice length"
110        );
111        Self::decode(buf.try_into().unwrap())
112    }
113
114    pub fn decode(buf: &[u8; EDIT_PREFIX_SIZE]) -> Self {
115        let checksum = u64::from_le_bytes(buf[0..8].try_into().unwrap());
116        let len = u32::from_le_bytes(buf[8..12].try_into().unwrap());
117        Self { checksum, len }
118    }
119
120    /// Returns the length of data this is framing.
121    pub const fn len(&self) -> u32 {
122        self.len
123    }
124
125    /// Checks if the data is valid, by comparing the checksum result with the stored checksum.
126    /// The length of `data` must be equal to the stored length.
127    ///
128    /// # Panics
129    ///
130    /// Panics if `data.len() != self.len`.
131    pub fn is_valid(&self, data: &[u8]) -> bool {
132        assert_eq!(data.len(), self.len as usize);
133        let computed = crc64(0, data);
134        computed == self.checksum
135    }
136}