Skip to main content

harmos_storage/
file_checkpoint.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10/* === Modules === */
11
12/* === Re-Exports === */
13
14/* === Imports === */
15
16// Standard Library Imports
17// Third Party Imports
18use serde::{Deserialize, Serialize};
19use sha2::digest::common::hazmat::{SerializableState, SerializedState};
20use sha2::{Digest, Sha256};
21// Local Imports
22use super::{FileMetadata, FileSession};
23use crate::{Error, MemorySize, Result};
24
25/* === Constants === */
26
27/* === Macros === */
28
29/* === Type Aliases === */
30
31/* === Structs === */
32
33#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(deny_unknown_fields)]
35pub struct FileCheckpoint {
36    pub metadata: FileMetadata,
37    pub session: FileSession,
38    pub length: u64,
39    pub hash_format: u32,
40    pub hash_state: Vec<u8>,
41}
42
43/* === Enums === */
44
45/* === Traits === */
46
47/* === Implementations === */
48
49impl FileCheckpoint {
50    pub(super) fn new(metadata: FileMetadata, session: FileSession, length: u64, hash: &Sha256) -> Self {
51        Self {
52            metadata,
53            session,
54            length,
55            hash_format: 1,
56            hash_state: hash.serialize().to_vec(),
57        }
58    }
59
60    pub fn sha256(&self) -> Result<[u8; 32]> {
61        Ok(self.restore_hash()?.finalize().into())
62    }
63
64    pub(super) fn restore_hash(&self) -> Result<Sha256> {
65        if self.hash_format != 1
66            || self.session.generation == 0
67            || self.metadata.id != self.session.id
68            || self.metadata.expected_length.is_some_and(|length| self.length > length)
69        {
70            return Err(invalid());
71        }
72        // Format 1: 32 State Bytes, Eight LE Block-Count Bytes, Then One
73        // Buffer-Length Byte And Up To 63 Buffered Bytes (An Eager SHA-256 Block).
74        if self.hash_state.len() != 104 {
75            return Err(invalid());
76        }
77        let blocks = u64::from_le_bytes(self.hash_state[32..40].try_into().map_err(|_| invalid())?);
78        let length = blocks.checked_mul(64).and_then(|length| length.checked_add(u64::from(self.hash_state[40])));
79        if length != Some(self.length) {
80            return Err(invalid());
81        }
82        let encoded = <&SerializedState<Sha256>>::try_from(self.hash_state.as_slice()).map_err(|_| invalid())?;
83        Sha256::deserialize(encoded).map_err(|_| invalid())
84    }
85}
86
87impl MemorySize for FileCheckpoint {
88    fn memory_size(&self) -> usize {
89        std::mem::size_of::<Self>()
90            .saturating_add(self.metadata.memory_size().saturating_sub(std::mem::size_of::<FileMetadata>()))
91            .saturating_add(self.session.memory_size().saturating_sub(std::mem::size_of::<FileSession>()))
92            .saturating_add(self.hash_state.capacity())
93    }
94}
95
96/* === Public Functions === */
97
98/* === Private Functions === */
99
100fn invalid() -> Error {
101    Error::InvalidInput {
102        field: "file checkpoint",
103        reason: "invalid metadata, generation or hash state",
104    }
105}
106
107/* === Tests === */