Skip to main content

syncweb_core/
verify.rs

1use std::path::PathBuf;
2
3use iroh_blobs::Hash;
4
5use crate::{
6    Result, SyncwebError,
7    folder::SyncwebFolder,
8    node::{blob_store::BlobStore, docs_engine::DocsEngine},
9};
10
11/// Details about a blob whose local bytes do not match its expected hash.
12#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
13#[non_exhaustive]
14pub struct CorruptionInfo {
15    pub path: PathBuf,
16    pub expected_hash: Hash,
17    pub actual_hash: Hash,
18}
19
20/// Result of checking local content referenced by a folder.
21#[derive(Clone, Debug, Default, Eq, PartialEq, serde::Serialize)]
22#[non_exhaustive]
23pub struct VerifyResult {
24    pub total: u64,
25    pub verified: u64,
26    pub corrupted: Vec<CorruptionInfo>,
27    pub missing: Vec<PathBuf>,
28}
29
30impl VerifyResult {
31    #[must_use]
32    pub const fn is_valid(&self) -> bool {
33        self.corrupted.is_empty() && self.missing.is_empty()
34    }
35}
36
37/// Re-checks the BLAKE3 content hash of locally stored folder blobs.
38#[derive(Clone)]
39pub struct IntegrityChecker {
40    blob_store: BlobStore,
41    docs_engine: DocsEngine,
42}
43
44impl IntegrityChecker {
45    #[must_use]
46    pub const fn new(blob_store: BlobStore, docs_engine: DocsEngine) -> Self {
47        Self {
48            blob_store,
49            docs_engine,
50        }
51    }
52
53    /// Verify every non-system entry in a folder.
54    ///
55    /// # Errors
56    ///
57    /// Returns an error if folder entries or local blobs cannot be read.
58    pub async fn verify_folder(&self, folder: &SyncwebFolder) -> Result<VerifyResult> {
59        let entries = self.docs_engine.list_latest(folder.doc()).await?;
60        let mut result = VerifyResult::default();
61        for entry in entries {
62            if entry.key().starts_with(b"sys/") {
63                continue;
64            }
65            result.total = result.total.saturating_add(1);
66            let path = String::from_utf8(entry.key().to_vec())
67                .map(PathBuf::from)
68                .map_err(|error| SyncwebError::operation("folder entry path is not UTF-8", error))?;
69            let expected = entry.content_hash();
70            if !self.blob_store.has(expected).await? {
71                result.missing.push(path);
72                continue;
73            }
74            let bytes = self.blob_store.get(expected).await?;
75            let actual = Hash::from_bytes(*blake3::hash(&bytes).as_bytes());
76            if actual == expected {
77                result.verified = result.verified.saturating_add(1);
78            } else {
79                result.corrupted.push(CorruptionInfo {
80                    path,
81                    expected_hash: expected,
82                    actual_hash: actual,
83                });
84            }
85        }
86        Ok(result)
87    }
88}