iroh_bytes/
hashseq.rs

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