lfsx_server/storage/
verify.rs1use futures_util::StreamExt;
2use serde::Serialize;
3use sha2::{Digest, Sha256};
4
5use super::LocalStore;
6use crate::error::Error;
7use crate::namespace::Namespace;
8
9#[derive(Debug, Default, Serialize, PartialEq, Eq)]
10pub struct VerifyReport {
11 pub checked: u64,
12 pub bytes: u64,
13 pub corrupt: Vec<String>,
14 pub unreadable: Vec<String>,
15 pub incomplete: bool,
19}
20
21impl LocalStore {
22 pub async fn verify(&self, ns: &Namespace) -> Result<VerifyReport, Error> {
28 let walk = self.objects_of(ns).await;
29 let mut report = VerifyReport {
30 incomplete: !walk.complete,
31 ..VerifyReport::default()
32 };
33
34 for found in walk.objects {
35 report.checked += 1;
36
37 match self.digest_of(ns, &found.oid).await {
38 Ok((digest, read)) => {
39 report.bytes += read;
40
41 if digest != found.oid {
42 report.corrupt.push(found.oid);
43 }
44 }
45 Err(error) => {
49 tracing::warn!(oid = found.oid, %error, "object could not be read");
50 report.unreadable.push(found.oid);
51 }
52 }
53 }
54
55 Ok(report)
56 }
57
58 async fn digest_of(&self, ns: &Namespace, oid: &str) -> Result<(String, u64), Error> {
59 let object = self.open(ns, oid).await?;
60 let size = object.size();
61
62 let mut hasher = Sha256::new();
63 let mut read = 0u64;
64 let mut chunks = object.stream(0, size).await?;
65
66 while let Some(chunk) = chunks.next().await {
67 let chunk = chunk?;
68 read += chunk.len() as u64;
69 hasher.update(&chunk);
70 }
71
72 Ok((hex::encode(hasher.finalize()), read))
73 }
74}
75
76#[cfg(test)]
77mod tests;