1use std::pin::Pin;
2
3use async_recursion::async_recursion;
4use bytes::Bytes;
5use sha2::{Digest, Sha384};
6
7use crate::{
8 consts::{BLOB_AS_BUFFER, LIST_AS_BUFFER},
9 error::BundlrError,
10};
11use futures::{Stream, TryStream, TryStreamExt};
12
13pub enum DeepHashChunk<'a> {
14 Chunk(Bytes),
15 Stream(&'a mut Pin<Box<dyn Stream<Item = anyhow::Result<Bytes>>>>),
16 Chunks(Vec<DeepHashChunk<'a>>),
17}
18
19trait Foo: Stream<Item = anyhow::Result<Bytes>> + TryStream {}
20
21pub async fn deep_hash(chunk: DeepHashChunk<'_>) -> Result<Bytes, BundlrError> {
22 match chunk {
23 DeepHashChunk::Chunk(b) => {
24 let tag = [BLOB_AS_BUFFER, b.len().to_string().as_bytes()].concat();
25 let c = [sha384hash(tag.into()), sha384hash(b)].concat();
26 Ok(Bytes::copy_from_slice(&sha384hash(c.into())))
27 }
28 DeepHashChunk::Stream(s) => {
29 let mut hasher = Sha384::new();
30 let mut length = 0;
31 while let Some(chunk) = s
32 .as_mut()
33 .try_next()
34 .await
35 .map_err(|_| BundlrError::NoBytesLeft)?
36 {
37 length += chunk.len();
38 hasher.update(&chunk);
39 }
40
41 let tag = [BLOB_AS_BUFFER, length.to_string().as_bytes()].concat();
42
43 let tagged_hash = [
44 sha384hash(tag.into()),
45 Bytes::copy_from_slice(&hasher.finalize()),
46 ]
47 .concat();
48
49 Ok(sha384hash(tagged_hash.into()))
50 }
51 DeepHashChunk::Chunks(mut chunks) => {
52 let len = chunks.len() as f64;
54 let tag = [LIST_AS_BUFFER, len.to_string().as_bytes()].concat();
55
56 let acc = sha384hash(tag.into());
57
58 deep_hash_chunks(&mut chunks, acc).await
59 }
60 }
61}
62
63#[async_recursion(?Send)]
64pub async fn deep_hash_chunks(
65 chunks: &mut Vec<DeepHashChunk<'_>>,
66 acc: Bytes,
67) -> Result<Bytes, BundlrError> {
68 if chunks.is_empty() {
69 return Ok(acc);
70 };
71
72 let acc = Bytes::copy_from_slice(&acc);
73
74 let hash_pair = [acc, deep_hash(chunks.remove(0)).await?].concat();
75 let new_acc = sha384hash(hash_pair.into());
76 deep_hash_chunks(chunks, new_acc).await
77}
78
79fn sha384hash(b: Bytes) -> Bytes {
80 let mut hasher = Sha384::new();
81 hasher.update(&b);
82 Bytes::copy_from_slice(&hasher.finalize())
83}