iroh_blobs/
hashseq.rs

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