1use crate::ClientConfig;
2use anyhow::{ensure, Result};
3use async_trait::async_trait;
4use futures::{stream::FuturesUnordered, StreamExt};
5use hashtree_core::{Hash, Store, StoreError};
6use hashtree_fs::FsBlobStore;
7use sha2::{Digest, Sha256};
8use std::{path::Path, time::Duration};
9
10const MAX_BLOCK: usize = 4 * 1024 * 1024;
11
12pub struct BlobStore {
13 cache: FsBlobStore,
14 config: ClientConfig,
15 pub(crate) http: reqwest::Client,
16}
17
18fn verify(hash: &Hash, bytes: &[u8]) -> Result<()> {
19 ensure!(
20 bytes.len() <= MAX_BLOCK && Sha256::digest(bytes).as_slice() == hash,
21 "invalid Hashtree block hash or size"
22 );
23 Ok(())
24}
25
26impl BlobStore {
27 pub fn new(cache: &Path, config: ClientConfig) -> Result<Self> {
28 Ok(Self {
29 cache: FsBlobStore::new(cache)?,
30 http: reqwest::Client::builder()
31 .timeout(config.request_timeout)
32 .redirect(reqwest::redirect::Policy::none())
33 .build()?,
34 config,
35 })
36 }
37
38 async fn fetch(&self, server: &str, hash: &Hash) -> Result<Option<Vec<u8>>> {
39 let response = self
40 .http
41 .get(format!(
42 "{}/{}",
43 server.trim_end_matches('/'),
44 hex::encode(hash)
45 ))
46 .send()
47 .await?;
48 if response.status() == reqwest::StatusCode::NOT_FOUND {
49 return Ok(None);
50 }
51 let response = response.error_for_status()?;
52 ensure!(
53 response
54 .content_length()
55 .is_none_or(|n| n <= MAX_BLOCK as u64),
56 "oversized Hashtree block"
57 );
58 let mut body = response.bytes_stream();
59 let mut bytes = Vec::new();
60 while let Some(part) = body.next().await {
61 let part = part?;
62 ensure!(
63 bytes.len().saturating_add(part.len()) <= MAX_BLOCK,
64 "oversized Hashtree block"
65 );
66 bytes.extend_from_slice(&part);
67 }
68 verify(hash, &bytes)?;
69 Ok(Some(bytes))
70 }
71
72 async fn retrieve(&self, hash: &Hash) -> Result<Option<Vec<u8>>> {
73 if let Some(bytes) = self.cache.get(hash).await? {
74 verify(hash, &bytes)?;
75 return Ok(Some(bytes));
76 }
77 let mut servers: Vec<&str> = self.config.daemon_url.as_deref().into_iter().collect();
78 if !self.config.local_only {
79 servers.extend(self.config.read_servers.iter().map(String::as_str));
80 }
81 ensure!(
82 !servers.is_empty(),
83 "no Hashtree content sources configured"
84 );
85 let mut pending = FuturesUnordered::new();
86 for (i, server) in servers.into_iter().enumerate() {
87 pending.push(async move {
88 tokio::time::sleep(Duration::from_millis((i as u64).min(8) * 150)).await;
91 self.fetch(server, hash).await
92 });
93 }
94 let mut error = None;
95 while let Some(result) = pending.next().await {
96 match result {
97 Ok(Some(bytes)) => {
98 self.cache.put(*hash, bytes.clone()).await?;
99 return Ok(Some(bytes));
100 }
101 Ok(None) => {}
102 Err(e) => error = Some(e),
103 }
104 }
105 if let Some(e) = error {
106 return Err(e);
107 }
108 Ok(None)
109 }
110}
111
112#[async_trait]
113impl Store for BlobStore {
114 async fn get(&self, hash: &Hash) -> Result<Option<Vec<u8>>, StoreError> {
115 self.retrieve(hash)
116 .await
117 .map_err(|e| StoreError::Other(e.to_string()))
118 }
119 async fn put(&self, hash: Hash, bytes: Vec<u8>) -> Result<bool, StoreError> {
120 verify(&hash, &bytes).map_err(|e| StoreError::Other(e.to_string()))?;
121 self.cache.put(hash, bytes).await
122 }
123 async fn has(&self, hash: &Hash) -> Result<bool, StoreError> {
124 Ok(self.get(hash).await?.is_some())
125 }
126 async fn delete(&self, hash: &Hash) -> Result<bool, StoreError> {
127 self.cache.delete(hash).await
128 }
129}