iroh_blobs/
hashseq.rs

1//! Helpers for blobs that contain a sequence of hashes.
2use std::{fmt::Debug, io};
3
4use bytes::Bytes;
5use iroh_io::{AsyncSliceReader, AsyncSliceReaderExt};
6
7use crate::Hash;
8
9/// A sequence of links, backed by a [`Bytes`] object.
10#[derive(Clone, derive_more::Into)]
11pub struct HashSeq(Bytes);
12
13impl Debug for HashSeq {
14    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15        f.debug_list().entries(self.iter()).finish()
16    }
17}
18
19impl<'a> FromIterator<&'a Hash> for HashSeq {
20    fn from_iter<T: IntoIterator<Item = &'a Hash>>(iter: T) -> Self {
21        iter.into_iter().copied().collect()
22    }
23}
24
25impl FromIterator<Hash> for HashSeq {
26    fn from_iter<T: IntoIterator<Item = Hash>>(iter: T) -> Self {
27        let iter = iter.into_iter();
28        let (lower, _upper) = iter.size_hint();
29        let mut bytes = Vec::with_capacity(lower * 32);
30        for hash in iter {
31            bytes.extend_from_slice(hash.as_ref());
32        }
33        Self(bytes.into())
34    }
35}
36
37impl TryFrom<Bytes> for HashSeq {
38    type Error = anyhow::Error;
39
40    fn try_from(bytes: Bytes) -> Result<Self, Self::Error> {
41        Self::new(bytes).ok_or_else(|| anyhow::anyhow!("invalid hash sequence"))
42    }
43}
44
45impl IntoIterator for HashSeq {
46    type Item = Hash;
47    type IntoIter = HashSeqIter;
48
49    fn into_iter(self) -> Self::IntoIter {
50        HashSeqIter(self)
51    }
52}
53
54/// Stream over the hashes in a [`HashSeq`].
55///
56/// todo: make this wrap a reader instead of a [`HashSeq`].
57#[derive(Debug, Clone)]
58pub struct HashSeqStream(HashSeq);
59
60impl HashSeqStream {
61    /// Get the next hash in the sequence.
62    #[allow(clippy::should_implement_trait, clippy::unused_async)]
63    pub async fn next(&mut self) -> io::Result<Option<Hash>> {
64        Ok(self.0.pop_front())
65    }
66
67    /// Skip a number of hashes in the sequence.
68    #[allow(clippy::unused_async)]
69    pub async fn skip(&mut self, n: u64) -> io::Result<()> {
70        let ok = self.0.drop_front(n as usize);
71        if !ok {
72            Err(io::Error::new(
73                io::ErrorKind::UnexpectedEof,
74                "end of sequence",
75            ))
76        } else {
77            Ok(())
78        }
79    }
80}
81
82impl HashSeq {
83    /// Create a new sequence of hashes.
84    pub fn new(bytes: Bytes) -> Option<Self> {
85        if bytes.len() % 32 == 0 {
86            Some(Self(bytes))
87        } else {
88            None
89        }
90    }
91
92    fn drop_front(&mut self, n: usize) -> bool {
93        let start = n * 32;
94        if start > self.0.len() {
95            false
96        } else {
97            self.0 = self.0.slice(start..);
98            true
99        }
100    }
101
102    /// Iterate over the hashes in this sequence.
103    pub fn iter(&self) -> impl Iterator<Item = Hash> + '_ {
104        self.0.chunks_exact(32).map(|chunk| {
105            let hash: [u8; 32] = chunk.try_into().unwrap();
106            hash.into()
107        })
108    }
109
110    /// Get the number of hashes in this sequence.
111    pub fn len(&self) -> usize {
112        self.0.len() / 32
113    }
114
115    /// Check if this sequence is empty.
116    pub fn is_empty(&self) -> bool {
117        self.0.is_empty()
118    }
119
120    /// Get the hash at the given index.
121    pub fn get(&self, index: usize) -> Option<Hash> {
122        if index < self.len() {
123            let hash: [u8; 32] = self.0[index * 32..(index + 1) * 32].try_into().unwrap();
124            Some(hash.into())
125        } else {
126            None
127        }
128    }
129
130    /// Get and remove the first hash in this sequence.
131    pub fn pop_front(&mut self) -> Option<Hash> {
132        if self.is_empty() {
133            None
134        } else {
135            let hash = self.get(0).unwrap();
136            self.0 = self.0.slice(32..);
137            Some(hash)
138        }
139    }
140
141    /// Get the underlying bytes.
142    pub fn into_inner(self) -> Bytes {
143        self.0
144    }
145}
146
147/// Iterator over the hashes in a [`HashSeq`].
148#[derive(Debug, Clone)]
149pub struct HashSeqIter(HashSeq);
150
151impl Iterator for HashSeqIter {
152    type Item = Hash;
153
154    fn next(&mut self) -> Option<Self::Item> {
155        self.0.pop_front()
156    }
157}
158
159/// Parse a sequence of hashes.
160pub async fn parse_hash_seq<'a, R: AsyncSliceReader + 'a>(
161    mut reader: R,
162) -> anyhow::Result<(HashSeqStream, u64)> {
163    let bytes = reader.read_to_end().await?;
164    let hashes = HashSeq::try_from(bytes)?;
165    let num_hashes = hashes.len() as u64;
166    let stream = HashSeqStream(hashes);
167    Ok((stream, num_hashes))
168}