Skip to main content

hashtree_core/hashtree/
stream.rs

1use std::pin::Pin;
2
3use futures::stream::{self, Stream};
4
5use crate::codec::{decode_tree_node, is_tree_node};
6use crate::crypto::{decrypt_chk, EncryptionKey};
7use crate::store::Store;
8use crate::types::{to_hex, Cid, Hash, Link};
9
10use super::{HashTree, HashTreeError};
11
12impl<S: Store> HashTree<S> {
13    /// Read content as a stream of chunks by Cid (handles decryption automatically)
14    ///
15    /// Returns an async stream that yields chunks as they are read.
16    /// Useful for large files or when you want to process data incrementally.
17    pub fn get_stream(
18        &self,
19        cid: &Cid,
20    ) -> Pin<Box<dyn Stream<Item = Result<Vec<u8>, HashTreeError>> + Send + '_>> {
21        let hash = cid.hash;
22        let key = cid.key;
23
24        if let Some(k) = key {
25            // Encrypted stream
26            Box::pin(self.read_file_stream_encrypted(hash, k))
27        } else {
28            // Unencrypted stream
29            self.read_file_stream(hash)
30        }
31    }
32
33    /// Read encrypted file as stream (internal)
34    fn read_file_stream_encrypted(
35        &self,
36        hash: Hash,
37        key: EncryptionKey,
38    ) -> impl Stream<Item = Result<Vec<u8>, HashTreeError>> + Send + '_ {
39        stream::unfold(
40            EncryptedStreamState::Init {
41                hash,
42                key,
43                tree: self,
44            },
45            |state| async move {
46                match state {
47                    EncryptedStreamState::Init { hash, key, tree } => {
48                        let data = match tree.store.get(&hash).await {
49                            Ok(Some(d)) => d,
50                            Ok(None) => return None,
51                            Err(e) => {
52                                return Some((
53                                    Err(HashTreeError::Store(e.to_string())),
54                                    EncryptedStreamState::Done,
55                                ))
56                            }
57                        };
58
59                        // Try to decrypt
60                        let decrypted = match decrypt_chk(&data, &key) {
61                            Ok(d) => d,
62                            Err(e) => {
63                                return Some((
64                                    Err(HashTreeError::Decryption(e.to_string())),
65                                    EncryptedStreamState::Done,
66                                ))
67                            }
68                        };
69
70                        if !is_tree_node(&decrypted) {
71                            // Single blob - yield decrypted data
72                            return Some((Ok(decrypted), EncryptedStreamState::Done));
73                        }
74
75                        // Tree node - parse and traverse
76                        let node = match decode_tree_node(&decrypted) {
77                            Ok(n) => n,
78                            Err(e) => {
79                                return Some((
80                                    Err(HashTreeError::Codec(e)),
81                                    EncryptedStreamState::Done,
82                                ))
83                            }
84                        };
85
86                        let mut stack = node.links.into_iter().rev().collect();
87
88                        tree.process_encrypted_stream_stack(&mut stack).await
89                    }
90                    EncryptedStreamState::Processing { mut stack, tree } => {
91                        tree.process_encrypted_stream_stack(&mut stack).await
92                    }
93                    EncryptedStreamState::Done => None,
94                }
95            },
96        )
97    }
98
99    async fn process_encrypted_stream_stack<'a>(
100        &'a self,
101        stack: &mut Vec<Link>,
102    ) -> Option<(Result<Vec<u8>, HashTreeError>, EncryptedStreamState<'a, S>)> {
103        while let Some(item) = stack.pop() {
104            let data = match self.store.get(&item.hash).await {
105                Ok(Some(d)) => d,
106                Ok(None) => {
107                    return Some((
108                        Err(HashTreeError::MissingChunk(to_hex(&item.hash))),
109                        EncryptedStreamState::Done,
110                    ))
111                }
112                Err(e) => {
113                    return Some((
114                        Err(HashTreeError::Store(e.to_string())),
115                        EncryptedStreamState::Done,
116                    ))
117                }
118            };
119
120            // Decrypt if we have a key
121            let decrypted = if let Some(key) = item.key {
122                match decrypt_chk(&data, &key) {
123                    Ok(d) => d,
124                    Err(e) => {
125                        return Some((
126                            Err(HashTreeError::Decryption(e.to_string())),
127                            EncryptedStreamState::Done,
128                        ))
129                    }
130                }
131            } else {
132                data
133            };
134
135            match Self::decode_linked_file_node(&item, &decrypted) {
136                Ok(Some(node)) => stack.extend(node.links.into_iter().rev()),
137                Ok(None) => {
138                    return Some((
139                        Ok(decrypted),
140                        EncryptedStreamState::Processing {
141                            stack: std::mem::take(stack),
142                            tree: self,
143                        },
144                    ))
145                }
146                Err(e) => return Some((Err(e), EncryptedStreamState::Done)),
147            }
148        }
149        None
150    }
151
152    /// Read a file as stream of chunks
153    /// Returns an async stream that yields chunks as they are read
154    pub fn read_file_stream(
155        &self,
156        hash: Hash,
157    ) -> Pin<Box<dyn Stream<Item = Result<Vec<u8>, HashTreeError>> + Send + '_>> {
158        Box::pin(stream::unfold(
159            ReadStreamState::Init { hash, tree: self },
160            |state| async move {
161                match state {
162                    ReadStreamState::Init { hash, tree } => {
163                        let data = match tree.store.get(&hash).await {
164                            Ok(Some(d)) => d,
165                            Ok(None) => return None,
166                            Err(e) => {
167                                return Some((
168                                    Err(HashTreeError::Store(e.to_string())),
169                                    ReadStreamState::Done,
170                                ))
171                            }
172                        };
173
174                        if !is_tree_node(&data) {
175                            // Single blob - yield it and finish
176                            return Some((Ok(data), ReadStreamState::Done));
177                        }
178
179                        // Tree node - start streaming chunks
180                        let node = match decode_tree_node(&data) {
181                            Ok(n) => n,
182                            Err(e) => {
183                                return Some((Err(HashTreeError::Codec(e)), ReadStreamState::Done))
184                            }
185                        };
186
187                        // Create stack with all links to process
188                        let mut stack = node.links.into_iter().rev().collect();
189
190                        // Process first item
191                        tree.process_stream_stack(&mut stack).await
192                    }
193                    ReadStreamState::Processing { mut stack, tree } => {
194                        tree.process_stream_stack(&mut stack).await
195                    }
196                    ReadStreamState::Done => None,
197                }
198            },
199        ))
200    }
201
202    async fn process_stream_stack<'a>(
203        &'a self,
204        stack: &mut Vec<Link>,
205    ) -> Option<(Result<Vec<u8>, HashTreeError>, ReadStreamState<'a, S>)> {
206        while let Some(item) = stack.pop() {
207            let data = match self.store.get(&item.hash).await {
208                Ok(Some(d)) => d,
209                Ok(None) => {
210                    return Some((
211                        Err(HashTreeError::MissingChunk(to_hex(&item.hash))),
212                        ReadStreamState::Done,
213                    ))
214                }
215                Err(e) => {
216                    return Some((
217                        Err(HashTreeError::Store(e.to_string())),
218                        ReadStreamState::Done,
219                    ))
220                }
221            };
222
223            match Self::decode_linked_file_node(&item, &data) {
224                Ok(Some(node)) => stack.extend(node.links.into_iter().rev()),
225                Ok(None) => {
226                    return Some((
227                        Ok(data),
228                        ReadStreamState::Processing {
229                            stack: std::mem::take(stack),
230                            tree: self,
231                        },
232                    ))
233                }
234                Err(e) => return Some((Err(e), ReadStreamState::Done)),
235            }
236        }
237        None
238    }
239}
240
241// Internal state types for streaming
242
243enum ReadStreamState<'a, S: Store> {
244    Init {
245        hash: Hash,
246        tree: &'a HashTree<S>,
247    },
248    Processing {
249        stack: Vec<Link>,
250        tree: &'a HashTree<S>,
251    },
252    Done,
253}
254
255// Encrypted stream state types
256enum EncryptedStreamState<'a, S: Store> {
257    Init {
258        hash: Hash,
259        key: [u8; 32],
260        tree: &'a HashTree<S>,
261    },
262    Processing {
263        stack: Vec<Link>,
264        tree: &'a HashTree<S>,
265    },
266    Done,
267}