Skip to main content

hashtree_core/
hashtree.rs

1//! HashTree - Unified merkle tree operations
2//!
3//! Single struct for creating, reading, and editing content-addressed merkle trees.
4//! Mirrors the hashtree-ts HashTree class API.
5
6use std::pin::Pin;
7use std::sync::Arc;
8
9use futures::io::AsyncRead;
10use futures::stream::{self, Stream};
11use futures::AsyncReadExt;
12
13use crate::builder::{BuilderError, DEFAULT_CHUNK_SIZE, DEFAULT_MAX_LINKS};
14use crate::codec::{
15    decode_tree_node, encode_and_hash, is_directory_node, is_tree_node, try_decode_tree_node,
16};
17use crate::hash::sha256;
18use crate::reader::{ReaderError, TreeEntry, WalkEntry};
19use crate::store::Store;
20use crate::types::{to_hex, Cid, DirEntry, Hash, Link, LinkType, TreeNode};
21
22use crate::crypto::{decrypt_chk, encrypt_chk, EncryptionKey};
23
24#[path = "hashtree/stream.rs"]
25mod read_stream;
26mod walk;
27
28/// HashTree configuration
29#[derive(Clone)]
30pub struct HashTreeConfig<S: Store> {
31    pub store: Arc<S>,
32    pub chunk_size: usize,
33    pub max_links: usize,
34    /// Whether to encrypt content (default: true when encryption feature enabled)
35    pub encrypted: bool,
36}
37
38impl<S: Store> HashTreeConfig<S> {
39    pub fn new(store: Arc<S>) -> Self {
40        Self {
41            store,
42            chunk_size: DEFAULT_CHUNK_SIZE,
43            max_links: DEFAULT_MAX_LINKS,
44            encrypted: true,
45        }
46    }
47
48    pub fn with_chunk_size(mut self, chunk_size: usize) -> Self {
49        self.chunk_size = chunk_size;
50        self
51    }
52
53    pub fn with_max_links(mut self, max_links: usize) -> Self {
54        self.max_links = max_links;
55        self
56    }
57
58    /// Disable encryption (store content publicly)
59    pub fn public(mut self) -> Self {
60        self.encrypted = false;
61        self
62    }
63}
64
65/// HashTree error type
66#[derive(Debug, thiserror::Error)]
67pub enum HashTreeError {
68    #[error("Store error: {0}")]
69    Store(String),
70    #[error("Codec error: {0}")]
71    Codec(#[from] crate::codec::CodecError),
72    #[error("Missing chunk: {0}")]
73    MissingChunk(String),
74    #[error("Path not found: {0}")]
75    PathNotFound(String),
76    #[error("Entry not found: {0}")]
77    EntryNotFound(String),
78    #[error("Encryption error: {0}")]
79    Encryption(String),
80    #[error("Decryption error: {0}")]
81    Decryption(String),
82    #[error("Content size {actual_size} exceeds max_size {max_size}")]
83    SizeLimitExceeded { max_size: u64, actual_size: u64 },
84}
85
86impl From<BuilderError> for HashTreeError {
87    fn from(e: BuilderError) -> Self {
88        match e {
89            BuilderError::Store(s) => HashTreeError::Store(s),
90            BuilderError::Codec(c) => HashTreeError::Codec(c),
91            BuilderError::Encryption(s) => HashTreeError::Encryption(s),
92        }
93    }
94}
95
96impl From<ReaderError> for HashTreeError {
97    fn from(e: ReaderError) -> Self {
98        match e {
99            ReaderError::Store(s) => HashTreeError::Store(s),
100            ReaderError::Codec(c) => HashTreeError::Codec(c),
101            ReaderError::MissingChunk(s) => HashTreeError::MissingChunk(s),
102            ReaderError::Decryption(s) => HashTreeError::Encryption(s),
103            ReaderError::MissingKey => {
104                HashTreeError::Encryption("missing decryption key".to_string())
105            }
106        }
107    }
108}
109
110/// HashTree - unified create, read, and edit merkle tree operations
111pub struct HashTree<S: Store> {
112    store: Arc<S>,
113    chunk_size: usize,
114    max_links: usize,
115    encrypted: bool,
116}
117
118impl<S: Store> HashTree<S> {
119    fn internal_chunk_start(name: &str) -> Option<usize> {
120        let suffix = name.strip_prefix("_chunk_")?;
121        if suffix.is_empty() || !suffix.bytes().all(|byte| byte.is_ascii_digit()) {
122            return None;
123        }
124        suffix.parse().ok()
125    }
126
127    fn node_uses_directory_fanout(node: &TreeNode) -> bool {
128        !node.links.is_empty()
129            && node.links.iter().all(|link| {
130                let Some(name) = link.name.as_deref() else {
131                    return false;
132                };
133                Self::internal_chunk_start(name).is_some() && link.link_type == LinkType::Dir
134            })
135    }
136
137    fn is_internal_directory_link_with_fanout(link: &Link, uses_fanout: bool) -> bool {
138        if !uses_fanout || link.link_type != LinkType::Dir {
139            return false;
140        }
141
142        let Some(name) = link.name.as_deref() else {
143            return false;
144        };
145        Self::internal_chunk_start(name).is_some()
146    }
147
148    fn is_internal_directory_link(node: &TreeNode, link: &Link) -> bool {
149        Self::is_internal_directory_link_with_fanout(link, Self::node_uses_directory_fanout(node))
150    }
151
152    pub fn new(config: HashTreeConfig<S>) -> Self {
153        Self {
154            store: config.store,
155            chunk_size: config.chunk_size,
156            max_links: config.max_links,
157            encrypted: config.encrypted,
158        }
159    }
160
161    /// Check if encryption is enabled
162    pub fn is_encrypted(&self) -> bool {
163        self.encrypted
164    }
165
166    // ============ UNIFIED API ============
167
168    /// Store content, returns (Cid, size) where Cid is hash + optional key
169    /// Encrypts by default when encryption feature is enabled
170    pub async fn put(&self, data: &[u8]) -> Result<(Cid, u64), HashTreeError> {
171        let size = data.len() as u64;
172
173        // Small data - store as single chunk
174        if data.len() <= self.chunk_size {
175            let (hash, key) = self.put_chunk_internal(data).await?;
176            return Ok((Cid { hash, key }, size));
177        }
178
179        // Large data - chunk it
180        let mut links: Vec<Link> = Vec::new();
181        let mut offset = 0;
182
183        while offset < data.len() {
184            let end = (offset + self.chunk_size).min(data.len());
185            let chunk = &data[offset..end];
186            let chunk_size = chunk.len() as u64;
187            let (hash, key) = self.put_chunk_internal(chunk).await?;
188            links.push(Link {
189                hash,
190                name: None,
191                size: chunk_size,
192                key,
193                link_type: LinkType::Blob, // Leaf chunk (raw blob)
194                meta: None,
195            });
196            offset = end;
197        }
198
199        // Build tree from chunks
200        let (root_hash, root_key) = self.build_tree_internal(links, Some(size)).await?;
201        Ok((
202            Cid {
203                hash: root_hash,
204                key: root_key,
205            },
206            size,
207        ))
208    }
209
210    /// Get content by Cid (handles decryption automatically)
211    ///
212    /// - `max_size`: Optional max plaintext size in bytes. If exceeded, returns
213    ///   `HashTreeError::SizeLimitExceeded`.
214    pub async fn get(
215        &self,
216        cid: &Cid,
217        max_size: Option<u64>,
218    ) -> Result<Option<Vec<u8>>, HashTreeError> {
219        if let Some(key) = cid.key {
220            self.get_encrypted(&cid.hash, &key, max_size).await
221        } else {
222            self.read_file_with_limit(&cid.hash, max_size).await
223        }
224    }
225
226    /// Store content from an async reader (streaming put)
227    ///
228    /// Reads data in chunks and builds a merkle tree incrementally.
229    /// Useful for large files or streaming data sources.
230    /// Returns (Cid, size) where Cid is hash + optional key
231    pub async fn put_stream<R: AsyncRead + Unpin>(
232        &self,
233        mut reader: R,
234    ) -> Result<(Cid, u64), HashTreeError> {
235        let mut buffer = vec![0u8; self.chunk_size];
236        let mut links = Vec::new();
237        let mut total_size: u64 = 0;
238        let mut consistent_key: Option<[u8; 32]> = None;
239
240        loop {
241            let mut chunk = Vec::new();
242            let mut bytes_read = 0;
243
244            // Read until we have a full chunk or EOF
245            while bytes_read < self.chunk_size {
246                let n = reader
247                    .read(&mut buffer[..self.chunk_size - bytes_read])
248                    .await
249                    .map_err(|e| HashTreeError::Store(format!("read error: {}", e)))?;
250                if n == 0 {
251                    break; // EOF
252                }
253                chunk.extend_from_slice(&buffer[..n]);
254                bytes_read += n;
255            }
256
257            if chunk.is_empty() {
258                break; // No more data
259            }
260
261            let chunk_len = chunk.len() as u64;
262            total_size += chunk_len;
263
264            let (hash, key) = self.put_chunk_internal(&chunk).await?;
265
266            // Track consistent key for single-key result
267            if links.is_empty() {
268                consistent_key = key;
269            } else if consistent_key != key {
270                consistent_key = None;
271            }
272
273            links.push(Link {
274                hash,
275                name: None,
276                size: chunk_len,
277                key,
278                link_type: LinkType::Blob, // Leaf chunk (raw blob)
279                meta: None,
280            });
281        }
282
283        if links.is_empty() {
284            // Empty input
285            let (hash, key) = self.put_chunk_internal(&[]).await?;
286            return Ok((Cid { hash, key }, 0));
287        }
288
289        // Build tree from chunks
290        let (root_hash, root_key) = self.build_tree_internal(links, Some(total_size)).await?;
291        Ok((
292            Cid {
293                hash: root_hash,
294                key: root_key,
295            },
296            total_size,
297        ))
298    }
299
300    /// Store a chunk with optional encryption
301    async fn put_chunk_internal(
302        &self,
303        data: &[u8],
304    ) -> Result<(Hash, Option<EncryptionKey>), HashTreeError> {
305        if self.encrypted {
306            let (encrypted, key) =
307                encrypt_chk(data).map_err(|e| HashTreeError::Encryption(e.to_string()))?;
308            let hash = sha256(&encrypted);
309            self.store
310                .put(hash, encrypted)
311                .await
312                .map_err(|e| HashTreeError::Store(e.to_string()))?;
313            Ok((hash, Some(key)))
314        } else {
315            let hash = self.put_blob(data).await?;
316            Ok((hash, None))
317        }
318    }
319
320    /// Build tree and return (hash, optional_key)
321    async fn build_tree_internal(
322        &self,
323        links: Vec<Link>,
324        total_size: Option<u64>,
325    ) -> Result<(Hash, Option<[u8; 32]>), HashTreeError> {
326        // Single link with matching size - return directly
327        if links.len() == 1 {
328            if let Some(ts) = total_size {
329                if links[0].size == ts {
330                    return Ok((links[0].hash, links[0].key));
331                }
332            }
333        }
334
335        if links.len() <= self.max_links {
336            let node = TreeNode {
337                node_type: LinkType::File,
338                links,
339            };
340            let (data, _) = encode_and_hash(&node)?;
341
342            if self.encrypted {
343                let (encrypted, key) =
344                    encrypt_chk(&data).map_err(|e| HashTreeError::Encryption(e.to_string()))?;
345                let hash = sha256(&encrypted);
346                self.store
347                    .put(hash, encrypted)
348                    .await
349                    .map_err(|e| HashTreeError::Store(e.to_string()))?;
350                return Ok((hash, Some(key)));
351            }
352
353            // Unencrypted path
354            let hash = sha256(&data);
355            self.store
356                .put(hash, data)
357                .await
358                .map_err(|e| HashTreeError::Store(e.to_string()))?;
359            return Ok((hash, None));
360        }
361
362        // Too many links - create subtrees
363        let mut sub_links = Vec::new();
364        for batch in links.chunks(self.max_links) {
365            let batch_size: u64 = batch.iter().map(|l| l.size).sum();
366            let (hash, key) =
367                Box::pin(self.build_tree_internal(batch.to_vec(), Some(batch_size))).await?;
368            sub_links.push(Link {
369                hash,
370                name: None,
371                size: batch_size,
372                key,
373                link_type: LinkType::File, // Internal tree node
374                meta: None,
375            });
376        }
377
378        Box::pin(self.build_tree_internal(sub_links, total_size)).await
379    }
380
381    /// Get encrypted content by hash and key
382    async fn get_encrypted(
383        &self,
384        hash: &Hash,
385        key: &EncryptionKey,
386        max_size: Option<u64>,
387    ) -> Result<Option<Vec<u8>>, HashTreeError> {
388        let decrypted = match self.get_encrypted_root(hash, key).await? {
389            Some(data) => data,
390            None => return Ok(None),
391        };
392
393        // Check if it's a tree node
394        if is_tree_node(&decrypted) {
395            let node = decode_tree_node(&decrypted)?;
396            let declared_size: u64 = node.links.iter().map(|l| l.size).sum();
397            Self::ensure_size_limit(max_size, declared_size)?;
398
399            let mut bytes_read = 0u64;
400            let assembled = self
401                .assemble_encrypted_chunks_limited(&node, max_size, &mut bytes_read)
402                .await?;
403            return Ok(Some(assembled));
404        }
405
406        // Single chunk data
407        Self::ensure_size_limit(max_size, decrypted.len() as u64)?;
408        Ok(Some(decrypted))
409    }
410
411    async fn get_encrypted_root(
412        &self,
413        hash: &Hash,
414        key: &EncryptionKey,
415    ) -> Result<Option<Vec<u8>>, HashTreeError> {
416        self.get_cid_root_bytes(&Cid {
417            hash: *hash,
418            key: Some(*key),
419        })
420        .await
421        .map_err(|err| match err {
422            HashTreeError::Decryption(message) => HashTreeError::Encryption(message),
423            other => other,
424        })
425    }
426
427    fn ensure_size_limit(max_size: Option<u64>, actual_size: u64) -> Result<(), HashTreeError> {
428        if let Some(max_size) = max_size {
429            if actual_size > max_size {
430                return Err(HashTreeError::SizeLimitExceeded {
431                    max_size,
432                    actual_size,
433                });
434            }
435        }
436        Ok(())
437    }
438
439    /// Assemble encrypted chunks from tree
440    async fn assemble_encrypted_chunks_limited(
441        &self,
442        node: &TreeNode,
443        max_size: Option<u64>,
444        bytes_read: &mut u64,
445    ) -> Result<Vec<u8>, HashTreeError> {
446        let mut parts: Vec<Vec<u8>> = Vec::new();
447
448        for link in &node.links {
449            let projected = (*bytes_read).saturating_add(link.size);
450            Self::ensure_size_limit(max_size, projected)?;
451
452            let chunk_key = link
453                .key
454                .ok_or_else(|| HashTreeError::Encryption("missing chunk key".to_string()))?;
455
456            let encrypted_child = self
457                .store
458                .get(&link.hash)
459                .await
460                .map_err(|e| HashTreeError::Store(e.to_string()))?
461                .ok_or_else(|| HashTreeError::MissingChunk(to_hex(&link.hash)))?;
462
463            let decrypted = decrypt_chk(&encrypted_child, &chunk_key)
464                .map_err(|e| HashTreeError::Encryption(e.to_string()))?;
465
466            if is_tree_node(&decrypted) {
467                // Intermediate tree node - recurse
468                let child_node = decode_tree_node(&decrypted)?;
469                let child_data = Box::pin(self.assemble_encrypted_chunks_limited(
470                    &child_node,
471                    max_size,
472                    bytes_read,
473                ))
474                .await?;
475                parts.push(child_data);
476            } else {
477                // Leaf data chunk
478                let projected = (*bytes_read).saturating_add(decrypted.len() as u64);
479                Self::ensure_size_limit(max_size, projected)?;
480                *bytes_read = projected;
481                parts.push(decrypted);
482            }
483        }
484
485        let total_len: usize = parts.iter().map(|p| p.len()).sum();
486        let mut result = Vec::with_capacity(total_len);
487        for part in parts {
488            result.extend_from_slice(&part);
489        }
490
491        Ok(result)
492    }
493
494    // ============ LOW-LEVEL CREATE ============
495
496    /// Store a blob directly (small data, no encryption)
497    /// Returns the content hash
498    pub async fn put_blob(&self, data: &[u8]) -> Result<Hash, HashTreeError> {
499        let hash = sha256(data);
500        self.store
501            .put(hash, data.to_vec())
502            .await
503            .map_err(|e| HashTreeError::Store(e.to_string()))?;
504        Ok(hash)
505    }
506
507    /// Store a file, chunking if necessary
508    /// Returns (Cid, size) where Cid is hash + optional key
509    pub async fn put_file(&self, data: &[u8]) -> Result<(Cid, u64), HashTreeError> {
510        let size = data.len() as u64;
511
512        // Small file - store as single chunk
513        if data.len() <= self.chunk_size {
514            let (hash, key) = self.put_chunk_internal(data).await?;
515            return Ok((Cid { hash, key }, size));
516        }
517
518        // Large file - chunk it
519        let mut links: Vec<Link> = Vec::new();
520        let mut offset = 0;
521
522        while offset < data.len() {
523            let end = (offset + self.chunk_size).min(data.len());
524            let chunk = &data[offset..end];
525            let chunk_size = (end - offset) as u64;
526
527            let (hash, key) = self.put_chunk_internal(chunk).await?;
528            links.push(Link {
529                hash,
530                name: None,
531                size: chunk_size,
532                key,
533                link_type: LinkType::Blob, // Leaf chunk
534                meta: None,
535            });
536            offset = end;
537        }
538
539        // Build tree from chunks (uses encryption if enabled)
540        let (root_hash, root_key) = self.build_tree_internal(links, Some(size)).await?;
541        Ok((
542            Cid {
543                hash: root_hash,
544                key: root_key,
545            },
546            size,
547        ))
548    }
549
550    /// Build a directory from entries
551    /// Returns Cid with key if encrypted
552    ///
553    /// Large directories are split into BUD-17 `_chunk_<start>` fanout nodes.
554    pub async fn put_directory(&self, entries: Vec<DirEntry>) -> Result<Cid, HashTreeError> {
555        // Sort entries by name for deterministic hashing
556        let mut sorted = entries;
557        sorted.sort_by(|a, b| a.name.cmp(&b.name));
558
559        let links: Vec<Link> = sorted
560            .into_iter()
561            .map(|e| Link {
562                hash: e.hash,
563                name: Some(e.name),
564                size: e.size,
565                key: e.key,
566                link_type: e.link_type,
567                meta: e.meta,
568            })
569            .collect();
570
571        if links.len() <= self.max_links {
572            return self.put_directory_node(links).await;
573        }
574
575        self.build_directory_by_chunks(links).await
576    }
577
578    async fn put_directory_node(&self, links: Vec<Link>) -> Result<Cid, HashTreeError> {
579        let node = TreeNode {
580            node_type: LinkType::Dir,
581            links,
582        };
583        let (data, plain_hash) = encode_and_hash(&node)?;
584
585        if self.encrypted {
586            let (encrypted, key) =
587                encrypt_chk(&data).map_err(|e| HashTreeError::Encryption(e.to_string()))?;
588            let hash = sha256(&encrypted);
589            self.store
590                .put(hash, encrypted)
591                .await
592                .map_err(|e| HashTreeError::Store(e.to_string()))?;
593            return Ok(Cid {
594                hash,
595                key: Some(key),
596            });
597        }
598
599        self.store
600            .put(plain_hash, data)
601            .await
602            .map_err(|e| HashTreeError::Store(e.to_string()))?;
603        Ok(Cid {
604            hash: plain_hash,
605            key: None,
606        })
607    }
608
609    async fn build_directory_by_chunks(&self, links: Vec<Link>) -> Result<Cid, HashTreeError> {
610        let indexed_links: Vec<(usize, Link)> = links.into_iter().enumerate().collect();
611        self.build_indexed_directory_chunks(indexed_links).await
612    }
613
614    async fn build_indexed_directory_chunks(
615        &self,
616        links: Vec<(usize, Link)>,
617    ) -> Result<Cid, HashTreeError> {
618        let mut sub_trees: Vec<(usize, Link)> = Vec::new();
619
620        for (i, batch) in links.chunks(self.max_links).enumerate() {
621            let start = batch
622                .first()
623                .map(|(start, _)| *start)
624                .unwrap_or(i * self.max_links);
625            let batch_size: u64 = batch.iter().map(|(_, link)| link.size).sum();
626            let child_links = batch.iter().map(|(_, link)| link.clone()).collect();
627            let child_cid = self.put_directory_node(child_links).await?;
628
629            sub_trees.push((
630                start,
631                Link {
632                    hash: child_cid.hash,
633                    name: Some(format!("_chunk_{start}")),
634                    size: batch_size,
635                    key: child_cid.key,
636                    link_type: LinkType::Dir,
637                    meta: None,
638                },
639            ));
640        }
641
642        if sub_trees.len() <= self.max_links {
643            return self
644                .put_directory_node(sub_trees.into_iter().map(|(_, link)| link).collect())
645                .await;
646        }
647
648        Box::pin(self.build_indexed_directory_chunks(sub_trees)).await
649    }
650
651    /// Create a tree node with custom links
652    pub async fn put_tree_node(&self, links: Vec<Link>) -> Result<Hash, HashTreeError> {
653        let node = TreeNode {
654            node_type: LinkType::Dir,
655            links,
656        };
657
658        let (data, hash) = encode_and_hash(&node)?;
659        self.store
660            .put(hash, data)
661            .await
662            .map_err(|e| HashTreeError::Store(e.to_string()))?;
663        Ok(hash)
664    }
665
666    // ============ READ ============
667
668    /// Get raw data by hash
669    pub async fn get_blob(&self, hash: &Hash) -> Result<Option<Vec<u8>>, HashTreeError> {
670        self.store
671            .get(hash)
672            .await
673            .map_err(|e| HashTreeError::Store(e.to_string()))
674    }
675
676    async fn has_stored_chunk(&self, hash: &Hash) -> Result<bool, HashTreeError> {
677        self.store
678            .get(hash)
679            .await
680            .map(|data| data.is_some())
681            .map_err(|e| HashTreeError::Store(e.to_string()))
682    }
683
684    /// Get and decode a tree node (unencrypted)
685    pub async fn get_tree_node(&self, hash: &Hash) -> Result<Option<TreeNode>, HashTreeError> {
686        let data = match self
687            .store
688            .get(hash)
689            .await
690            .map_err(|e| HashTreeError::Store(e.to_string()))?
691        {
692            Some(d) => d,
693            None => return Ok(None),
694        };
695
696        if !is_tree_node(&data) {
697            return Ok(None);
698        }
699
700        let node = decode_tree_node(&data)?;
701        Ok(Some(node))
702    }
703
704    async fn get_cid_root_bytes(&self, cid: &Cid) -> Result<Option<Vec<u8>>, HashTreeError> {
705        let data = match self
706            .store
707            .get(&cid.hash)
708            .await
709            .map_err(|e| HashTreeError::Store(e.to_string()))?
710        {
711            Some(d) => d,
712            None => return Ok(None),
713        };
714
715        let Some(key) = &cid.key else {
716            return Ok(Some(data));
717        };
718
719        let raw_is_tree = is_tree_node(&data);
720        match decrypt_chk(&data, key) {
721            Ok(decrypted) => {
722                if is_tree_node(&decrypted) || !raw_is_tree {
723                    Ok(Some(decrypted))
724                } else {
725                    Ok(Some(data))
726                }
727            }
728            Err(err) => {
729                if raw_is_tree {
730                    Ok(Some(data))
731                } else {
732                    Err(HashTreeError::Decryption(err.to_string()))
733                }
734            }
735        }
736    }
737
738    /// Get and decode a tree node using Cid (with decryption if key present)
739    pub async fn get_node(&self, cid: &Cid) -> Result<Option<TreeNode>, HashTreeError> {
740        let decrypted = match self.get_cid_root_bytes(cid).await? {
741            Some(d) => d,
742            None => return Ok(None),
743        };
744
745        if !is_tree_node(&decrypted) {
746            return Ok(None);
747        }
748
749        let node = decode_tree_node(&decrypted)?;
750        Ok(Some(node))
751    }
752
753    /// Get directory node, handling historical byte-chunked directory data.
754    /// Use this when you know the target is a directory (from parent link_type)
755    pub async fn get_directory_node(&self, cid: &Cid) -> Result<Option<TreeNode>, HashTreeError> {
756        let decrypted = match self.get_cid_root_bytes(cid).await? {
757            Some(d) => d,
758            None => return Ok(None),
759        };
760
761        if !is_tree_node(&decrypted) {
762            return Ok(None);
763        }
764
765        let node = decode_tree_node(&decrypted)?;
766
767        // If this is a file tree (chunked data), reassemble to get actual directory
768        if node.node_type == LinkType::File {
769            let mut bytes_read = 0u64;
770            let assembled = self
771                .assemble_chunks_limited(&node, None, &mut bytes_read)
772                .await?;
773            if is_tree_node(&assembled) {
774                let inner_node = decode_tree_node(&assembled)?;
775                return Ok(Some(inner_node));
776            }
777        }
778
779        Ok(Some(node))
780    }
781
782    /// Check if hash points to a tree node (no decryption)
783    pub async fn is_tree(&self, hash: &Hash) -> Result<bool, HashTreeError> {
784        let data = match self
785            .store
786            .get(hash)
787            .await
788            .map_err(|e| HashTreeError::Store(e.to_string()))?
789        {
790            Some(d) => d,
791            None => return Ok(false),
792        };
793        Ok(is_tree_node(&data))
794    }
795
796    /// Check if Cid points to a directory (with decryption)
797    pub async fn is_dir(&self, cid: &Cid) -> Result<bool, HashTreeError> {
798        Ok(matches!(
799            self.get_directory_node(cid).await?,
800            Some(node) if node.node_type == LinkType::Dir
801        ))
802    }
803
804    /// Check if hash points to a directory (tree with named links, no decryption)
805    pub async fn is_directory(&self, hash: &Hash) -> Result<bool, HashTreeError> {
806        let data = match self
807            .store
808            .get(hash)
809            .await
810            .map_err(|e| HashTreeError::Store(e.to_string()))?
811        {
812            Some(d) => d,
813            None => return Ok(false),
814        };
815        Ok(is_directory_node(&data))
816    }
817
818    /// Read a complete file (reassemble chunks if needed)
819    pub async fn read_file(&self, hash: &Hash) -> Result<Option<Vec<u8>>, HashTreeError> {
820        self.read_file_with_limit(hash, None).await
821    }
822
823    /// Read a complete file with optional size limit.
824    async fn read_file_with_limit(
825        &self,
826        hash: &Hash,
827        max_size: Option<u64>,
828    ) -> Result<Option<Vec<u8>>, HashTreeError> {
829        let data = match self
830            .store
831            .get(hash)
832            .await
833            .map_err(|e| HashTreeError::Store(e.to_string()))?
834        {
835            Some(d) => d,
836            None => return Ok(None),
837        };
838
839        // Check if it's a tree (chunked file) or raw blob
840        if !is_tree_node(&data) {
841            Self::ensure_size_limit(max_size, data.len() as u64)?;
842            return Ok(Some(data));
843        }
844
845        // It's a tree - reassemble chunks
846        let node = decode_tree_node(&data)?;
847        let declared_size: u64 = node.links.iter().map(|l| l.size).sum();
848        Self::ensure_size_limit(max_size, declared_size)?;
849
850        let mut bytes_read = 0u64;
851        let assembled = self
852            .assemble_chunks_limited(&node, max_size, &mut bytes_read)
853            .await?;
854        Ok(Some(assembled))
855    }
856
857    /// Read a byte range from a file (fetches only necessary chunks)
858    ///
859    /// - `start`: Starting byte offset (inclusive)
860    /// - `end`: Ending byte offset (exclusive), or None to read to end
861    ///
862    /// This is more efficient than read_file() for partial reads of large files.
863    pub async fn read_file_range(
864        &self,
865        hash: &Hash,
866        start: u64,
867        end: Option<u64>,
868    ) -> Result<Option<Vec<u8>>, HashTreeError> {
869        let data = match self
870            .store
871            .get(hash)
872            .await
873            .map_err(|e| HashTreeError::Store(e.to_string()))?
874        {
875            Some(d) => d,
876            None => return Ok(None),
877        };
878
879        // Single blob - just slice it
880        if !is_tree_node(&data) {
881            let start_idx = start as usize;
882            let end_idx = end.map(|e| e as usize).unwrap_or(data.len());
883            if start_idx >= data.len() {
884                return Ok(Some(vec![]));
885            }
886            let end_idx = end_idx.min(data.len());
887            return Ok(Some(data[start_idx..end_idx].to_vec()));
888        }
889
890        // It's a chunked file - fetch only needed chunks
891        let node = decode_tree_node(&data)?;
892        let range_data = self.assemble_chunks_range(&node, start, end).await?;
893        Ok(Some(range_data))
894    }
895
896    /// Read a byte range from a file using a Cid (handles decryption if key present)
897    pub async fn read_file_range_cid(
898        &self,
899        cid: &Cid,
900        start: u64,
901        end: Option<u64>,
902    ) -> Result<Option<Vec<u8>>, HashTreeError> {
903        if let Some(key) = cid.key {
904            let data = match self.get_encrypted_root(&cid.hash, &key).await? {
905                Some(d) => d,
906                None => return Ok(None),
907            };
908
909            if is_tree_node(&data) {
910                let node = decode_tree_node(&data)?;
911                let total_size: u64 = node.links.iter().map(|link| link.size).sum();
912                let actual_end = end.unwrap_or(total_size).min(total_size);
913                if start >= actual_end {
914                    return Ok(Some(vec![]));
915                }
916
917                let mut result = Vec::with_capacity((actual_end - start) as usize);
918                self.append_encrypted_range(&node, start, actual_end, 0, &mut result)
919                    .await?;
920                return Ok(Some(result));
921            }
922
923            let start_idx = start as usize;
924            let end_idx = end.map(|e| e as usize).unwrap_or(data.len());
925            if start_idx >= data.len() {
926                return Ok(Some(vec![]));
927            }
928            let end_idx = end_idx.min(data.len());
929            return Ok(Some(data[start_idx..end_idx].to_vec()));
930        }
931
932        self.read_file_range(&cid.hash, start, end).await
933    }
934
935    async fn append_encrypted_range(
936        &self,
937        node: &TreeNode,
938        start: u64,
939        end: u64,
940        base_offset: u64,
941        result: &mut Vec<u8>,
942    ) -> Result<(), HashTreeError> {
943        let mut current_offset = base_offset;
944
945        for link in &node.links {
946            let child_start = current_offset;
947            let child_end = child_start.saturating_add(link.size);
948            current_offset = child_end;
949
950            if child_end <= start {
951                continue;
952            }
953            if child_start >= end {
954                break;
955            }
956
957            let chunk_key = link
958                .key
959                .ok_or_else(|| HashTreeError::Encryption("missing chunk key".to_string()))?;
960
961            let encrypted_child = self
962                .store
963                .get(&link.hash)
964                .await
965                .map_err(|e| HashTreeError::Store(e.to_string()))?
966                .ok_or_else(|| HashTreeError::MissingChunk(to_hex(&link.hash)))?;
967            let decrypted_child = decrypt_chk(&encrypted_child, &chunk_key)
968                .map_err(|e| HashTreeError::Encryption(e.to_string()))?;
969
970            if is_tree_node(&decrypted_child) {
971                let child_node = decode_tree_node(&decrypted_child)?;
972                Box::pin(self.append_encrypted_range(&child_node, start, end, child_start, result))
973                    .await?;
974                continue;
975            }
976
977            let slice_start = if start > child_start {
978                (start - child_start) as usize
979            } else {
980                0
981            };
982            let slice_end = if end < child_end {
983                (end - child_start) as usize
984            } else {
985                decrypted_child.len()
986            };
987            result.extend_from_slice(&decrypted_child[slice_start..slice_end]);
988        }
989
990        Ok(())
991    }
992
993    /// Assemble only the chunks needed for a byte range
994    async fn assemble_chunks_range(
995        &self,
996        node: &TreeNode,
997        start: u64,
998        end: Option<u64>,
999    ) -> Result<Vec<u8>, HashTreeError> {
1000        // First, flatten the tree to get all leaf chunks with their byte offsets
1001        let chunks_info = self.collect_chunk_offsets(node).await?;
1002
1003        if chunks_info.is_empty() {
1004            return Ok(vec![]);
1005        }
1006
1007        // Calculate total size and actual end
1008        let total_size: u64 = chunks_info.iter().map(|(_, _, size)| size).sum();
1009        let actual_end = end.unwrap_or(total_size).min(total_size);
1010
1011        if start >= actual_end {
1012            return Ok(vec![]);
1013        }
1014
1015        // Find chunks that overlap with [start, actual_end)
1016        let mut result = Vec::with_capacity((actual_end - start) as usize);
1017        let mut current_offset = 0u64;
1018
1019        for (chunk_hash, _chunk_offset, chunk_size) in &chunks_info {
1020            let chunk_start = current_offset;
1021            let chunk_end = current_offset + chunk_size;
1022
1023            // Check if this chunk overlaps with our range
1024            if chunk_end > start && chunk_start < actual_end {
1025                // Fetch this chunk
1026                let chunk_data = self
1027                    .store
1028                    .get(chunk_hash)
1029                    .await
1030                    .map_err(|e| HashTreeError::Store(e.to_string()))?
1031                    .ok_or_else(|| HashTreeError::MissingChunk(to_hex(chunk_hash)))?;
1032
1033                // Calculate slice bounds within this chunk
1034                let slice_start = if start > chunk_start {
1035                    (start - chunk_start) as usize
1036                } else {
1037                    0
1038                };
1039                let slice_end = if actual_end < chunk_end {
1040                    (actual_end - chunk_start) as usize
1041                } else {
1042                    chunk_data.len()
1043                };
1044
1045                result.extend_from_slice(&chunk_data[slice_start..slice_end]);
1046            }
1047
1048            current_offset = chunk_end;
1049
1050            // Early exit if we've passed the requested range
1051            if current_offset >= actual_end {
1052                break;
1053            }
1054        }
1055
1056        Ok(result)
1057    }
1058
1059    /// Collect all leaf chunk hashes with their byte offsets
1060    /// Returns Vec<(hash, offset, size)>
1061    async fn collect_chunk_offsets(
1062        &self,
1063        node: &TreeNode,
1064    ) -> Result<Vec<(Hash, u64, u64)>, HashTreeError> {
1065        let mut chunks = Vec::new();
1066        let mut offset = 0u64;
1067        self.collect_chunk_offsets_recursive(node, &mut chunks, &mut offset)
1068            .await?;
1069        Ok(chunks)
1070    }
1071
1072    async fn collect_chunk_offsets_recursive(
1073        &self,
1074        node: &TreeNode,
1075        chunks: &mut Vec<(Hash, u64, u64)>,
1076        offset: &mut u64,
1077    ) -> Result<(), HashTreeError> {
1078        for link in &node.links {
1079            let child_data = self
1080                .store
1081                .get(&link.hash)
1082                .await
1083                .map_err(|e| HashTreeError::Store(e.to_string()))?
1084                .ok_or_else(|| HashTreeError::MissingChunk(to_hex(&link.hash)))?;
1085
1086            if is_tree_node(&child_data) {
1087                // Intermediate node - recurse
1088                let child_node = decode_tree_node(&child_data)?;
1089                Box::pin(self.collect_chunk_offsets_recursive(&child_node, chunks, offset)).await?;
1090            } else {
1091                // Leaf chunk
1092                let size = child_data.len() as u64;
1093                chunks.push((link.hash, *offset, size));
1094                *offset += size;
1095            }
1096        }
1097        Ok(())
1098    }
1099
1100    /// Recursively assemble chunks from tree
1101    async fn assemble_chunks_limited(
1102        &self,
1103        node: &TreeNode,
1104        max_size: Option<u64>,
1105        bytes_read: &mut u64,
1106    ) -> Result<Vec<u8>, HashTreeError> {
1107        let mut parts: Vec<Vec<u8>> = Vec::new();
1108
1109        for link in &node.links {
1110            let projected = (*bytes_read).saturating_add(link.size);
1111            Self::ensure_size_limit(max_size, projected)?;
1112
1113            let child_data = self
1114                .store
1115                .get(&link.hash)
1116                .await
1117                .map_err(|e| HashTreeError::Store(e.to_string()))?
1118                .ok_or_else(|| HashTreeError::MissingChunk(to_hex(&link.hash)))?;
1119
1120            if is_tree_node(&child_data) {
1121                let child_node = decode_tree_node(&child_data)?;
1122                parts.push(
1123                    Box::pin(self.assemble_chunks_limited(&child_node, max_size, bytes_read))
1124                        .await?,
1125                );
1126            } else {
1127                let projected = (*bytes_read).saturating_add(child_data.len() as u64);
1128                Self::ensure_size_limit(max_size, projected)?;
1129                *bytes_read = projected;
1130                parts.push(child_data);
1131            }
1132        }
1133
1134        // Concatenate all parts
1135        let total_length: usize = parts.iter().map(|p| p.len()).sum();
1136        let mut result = Vec::with_capacity(total_length);
1137        for part in parts {
1138            result.extend_from_slice(&part);
1139        }
1140
1141        Ok(result)
1142    }
1143
1144    /// Read file chunks as Vec (non-streaming version)
1145    pub async fn read_file_chunks(&self, hash: &Hash) -> Result<Vec<Vec<u8>>, HashTreeError> {
1146        let data = match self
1147            .store
1148            .get(hash)
1149            .await
1150            .map_err(|e| HashTreeError::Store(e.to_string()))?
1151        {
1152            Some(d) => d,
1153            None => return Ok(vec![]),
1154        };
1155
1156        if !is_tree_node(&data) {
1157            return Ok(vec![data]);
1158        }
1159
1160        let node = decode_tree_node(&data)?;
1161        self.collect_chunks(&node).await
1162    }
1163
1164    async fn collect_chunks(&self, node: &TreeNode) -> Result<Vec<Vec<u8>>, HashTreeError> {
1165        let mut chunks = Vec::new();
1166
1167        for link in &node.links {
1168            let child_data = self
1169                .store
1170                .get(&link.hash)
1171                .await
1172                .map_err(|e| HashTreeError::Store(e.to_string()))?
1173                .ok_or_else(|| HashTreeError::MissingChunk(to_hex(&link.hash)))?;
1174
1175            if is_tree_node(&child_data) {
1176                let child_node = decode_tree_node(&child_data)?;
1177                chunks.extend(Box::pin(self.collect_chunks(&child_node)).await?);
1178            } else {
1179                chunks.push(child_data);
1180            }
1181        }
1182
1183        Ok(chunks)
1184    }
1185
1186    /// List directory entries (Cid-based, supports encrypted directories)
1187    pub async fn list(&self, cid: &Cid) -> Result<Vec<TreeEntry>, HashTreeError> {
1188        let node = match self.get_node(cid).await? {
1189            Some(n) => n,
1190            None => return Ok(vec![]),
1191        };
1192
1193        let mut entries = Vec::new();
1194
1195        for link in &node.links {
1196            // Skip internal chunk nodes - recurse into them
1197            if Self::is_internal_directory_link(&node, link) {
1198                let chunk_cid = Cid {
1199                    hash: link.hash,
1200                    key: link.key,
1201                };
1202                let sub_entries = Box::pin(self.list(&chunk_cid)).await?;
1203                entries.extend(sub_entries);
1204                continue;
1205            }
1206
1207            entries.push(TreeEntry {
1208                name: link.name.clone().unwrap_or_else(|| to_hex(&link.hash)),
1209                hash: link.hash,
1210                size: link.size,
1211                link_type: link.link_type,
1212                key: link.key,
1213                meta: link.meta.clone(),
1214            });
1215        }
1216
1217        Ok(entries)
1218    }
1219
1220    /// List directory entries using Cid (with decryption if key present).
1221    /// Handles both regular and fanout directory data.
1222    pub async fn list_directory(&self, cid: &Cid) -> Result<Vec<TreeEntry>, HashTreeError> {
1223        // Use get_directory_node which handles chunked directory data
1224        let node = match self.get_directory_node(cid).await? {
1225            Some(n) => n,
1226            None => return Ok(vec![]),
1227        };
1228
1229        let mut entries = Vec::new();
1230
1231        for link in &node.links {
1232            // Skip internal fanout nodes.
1233            if Self::is_internal_directory_link(&node, link) {
1234                let sub_cid = Cid {
1235                    hash: link.hash,
1236                    key: link.key,
1237                };
1238                let sub_entries = Box::pin(self.list_directory(&sub_cid)).await?;
1239                entries.extend(sub_entries);
1240                continue;
1241            }
1242
1243            entries.push(TreeEntry {
1244                name: link.name.clone().unwrap_or_else(|| to_hex(&link.hash)),
1245                hash: link.hash,
1246                size: link.size,
1247                link_type: link.link_type,
1248                key: link.key,
1249                meta: link.meta.clone(),
1250            });
1251        }
1252
1253        Ok(entries)
1254    }
1255
1256    /// Resolve a path within a tree (returns Cid with key if encrypted)
1257    pub async fn resolve(&self, cid: &Cid, path: &str) -> Result<Option<Cid>, HashTreeError> {
1258        let parts: Vec<&str> = path.split('/').filter(|p| !p.is_empty()).collect();
1259        if parts.is_empty() {
1260            return Ok(Some(cid.clone()));
1261        }
1262
1263        let mut current_cid = cid.clone();
1264
1265        for part in parts {
1266            // Use get_directory_node which handles chunked directory data
1267            let node = match self.get_directory_node(&current_cid).await? {
1268                Some(n) => n,
1269                None => {
1270                    if !self.has_stored_chunk(&current_cid.hash).await? {
1271                        return Err(HashTreeError::MissingChunk(to_hex(&current_cid.hash)));
1272                    }
1273                    return Ok(None);
1274                }
1275            };
1276
1277            if let Some(link) = self.find_link(&node, part) {
1278                current_cid = Cid {
1279                    hash: link.hash,
1280                    key: link.key,
1281                };
1282            } else {
1283                // Check internal nodes
1284                match self
1285                    .find_link_in_subtrees_cid(&node, part, &current_cid)
1286                    .await?
1287                {
1288                    Some(link) => {
1289                        current_cid = Cid {
1290                            hash: link.hash,
1291                            key: link.key,
1292                        };
1293                    }
1294                    None => return Ok(None),
1295                }
1296            }
1297        }
1298
1299        Ok(Some(current_cid))
1300    }
1301
1302    /// Resolve a path within a tree using Cid (with decryption if key present)
1303    pub async fn resolve_path(&self, cid: &Cid, path: &str) -> Result<Option<Cid>, HashTreeError> {
1304        self.resolve(cid, path).await
1305    }
1306
1307    fn find_link(&self, node: &TreeNode, name: &str) -> Option<Link> {
1308        node.links
1309            .iter()
1310            .find(|l| l.name.as_deref() == Some(name))
1311            .cloned()
1312    }
1313
1314    /// Find a link in subtrees using Cid (with decryption support)
1315    async fn find_link_in_subtrees_cid(
1316        &self,
1317        node: &TreeNode,
1318        name: &str,
1319        _parent_cid: &Cid,
1320    ) -> Result<Option<Link>, HashTreeError> {
1321        for link in &node.links {
1322            if !Self::is_internal_directory_link(node, link) {
1323                continue;
1324            }
1325
1326            // Internal nodes inherit encryption from parent context
1327            let sub_cid = Cid {
1328                hash: link.hash,
1329                key: link.key,
1330            };
1331
1332            let sub_node = match self.get_node(&sub_cid).await? {
1333                Some(n) => n,
1334                None => {
1335                    if !self.has_stored_chunk(&sub_cid.hash).await? {
1336                        return Err(HashTreeError::MissingChunk(to_hex(&sub_cid.hash)));
1337                    }
1338                    continue;
1339                }
1340            };
1341
1342            if let Some(found) = self.find_link(&sub_node, name) {
1343                return Ok(Some(found));
1344            }
1345
1346            if let Some(deep_found) =
1347                Box::pin(self.find_link_in_subtrees_cid(&sub_node, name, &sub_cid)).await?
1348            {
1349                return Ok(Some(deep_found));
1350            }
1351        }
1352
1353        Ok(None)
1354    }
1355
1356    /// Get total size of a tree
1357    pub async fn get_size(&self, hash: &Hash) -> Result<u64, HashTreeError> {
1358        let data = match self
1359            .store
1360            .get(hash)
1361            .await
1362            .map_err(|e| HashTreeError::Store(e.to_string()))?
1363        {
1364            Some(d) => d,
1365            None => return Ok(0),
1366        };
1367
1368        if !is_tree_node(&data) {
1369            return Ok(data.len() as u64);
1370        }
1371
1372        let node = decode_tree_node(&data)?;
1373        // Calculate from children
1374        let mut total = 0u64;
1375        for link in &node.links {
1376            total += link.size;
1377        }
1378        Ok(total)
1379    }
1380
1381    /// Get total size using a Cid (handles decryption if key present)
1382    pub async fn get_size_cid(&self, cid: &Cid) -> Result<u64, HashTreeError> {
1383        if let Some(key) = cid.key {
1384            let data = match self.get_encrypted_root(&cid.hash, &key).await? {
1385                Some(d) => d,
1386                None => return Ok(0),
1387            };
1388            if is_tree_node(&data) {
1389                let node = decode_tree_node(&data)?;
1390                return Ok(node.links.iter().map(|link| link.size).sum());
1391            }
1392            return Ok(data.len() as u64);
1393        }
1394
1395        self.get_size(&cid.hash).await
1396    }
1397
1398    // ============ EDIT ============
1399
1400    /// Add or update an entry in a directory
1401    /// Returns new root Cid (immutable operation)
1402    pub async fn set_entry(
1403        &self,
1404        root: &Cid,
1405        path: &[&str],
1406        name: &str,
1407        entry_cid: &Cid,
1408        size: u64,
1409        link_type: LinkType,
1410    ) -> Result<Cid, HashTreeError> {
1411        self.set_entry_with_meta(root, path, name, entry_cid, size, link_type, None)
1412            .await
1413    }
1414
1415    /// Add or update an entry in a directory with optional link metadata.
1416    /// Returns new root Cid (immutable operation)
1417    pub async fn set_entry_with_meta(
1418        &self,
1419        root: &Cid,
1420        path: &[&str],
1421        name: &str,
1422        entry_cid: &Cid,
1423        size: u64,
1424        link_type: LinkType,
1425        meta: Option<std::collections::HashMap<String, serde_json::Value>>,
1426    ) -> Result<Cid, HashTreeError> {
1427        let dir_cid = self.resolve_path_array(root, path).await?;
1428        let dir_cid = dir_cid.ok_or_else(|| HashTreeError::PathNotFound(path.join("/")))?;
1429
1430        let entries = self.list_directory(&dir_cid).await?;
1431        let mut new_entries: Vec<DirEntry> = entries
1432            .into_iter()
1433            .filter(|e| e.name != name)
1434            .map(|e| DirEntry {
1435                name: e.name,
1436                hash: e.hash,
1437                size: e.size,
1438                key: e.key,
1439                link_type: e.link_type,
1440                meta: e.meta,
1441            })
1442            .collect();
1443
1444        new_entries.push(DirEntry {
1445            name: name.to_string(),
1446            hash: entry_cid.hash,
1447            size,
1448            key: entry_cid.key,
1449            link_type,
1450            meta,
1451        });
1452
1453        let new_dir_cid = self.put_directory(new_entries).await?;
1454        self.rebuild_path(root, path, new_dir_cid).await
1455    }
1456
1457    /// Remove an entry from a directory
1458    /// Returns new root Cid
1459    pub async fn remove_entry(
1460        &self,
1461        root: &Cid,
1462        path: &[&str],
1463        name: &str,
1464    ) -> Result<Cid, HashTreeError> {
1465        let dir_cid = self.resolve_path_array(root, path).await?;
1466        let dir_cid = dir_cid.ok_or_else(|| HashTreeError::PathNotFound(path.join("/")))?;
1467
1468        let entries = self.list_directory(&dir_cid).await?;
1469        let new_entries: Vec<DirEntry> = entries
1470            .into_iter()
1471            .filter(|e| e.name != name)
1472            .map(|e| DirEntry {
1473                name: e.name,
1474                hash: e.hash,
1475                size: e.size,
1476                key: e.key,
1477                link_type: e.link_type,
1478                meta: e.meta,
1479            })
1480            .collect();
1481
1482        let new_dir_cid = self.put_directory(new_entries).await?;
1483        self.rebuild_path(root, path, new_dir_cid).await
1484    }
1485
1486    /// Rename an entry in a directory
1487    /// Returns new root Cid
1488    pub async fn rename_entry(
1489        &self,
1490        root: &Cid,
1491        path: &[&str],
1492        old_name: &str,
1493        new_name: &str,
1494    ) -> Result<Cid, HashTreeError> {
1495        if old_name == new_name {
1496            return Ok(root.clone());
1497        }
1498
1499        let dir_cid = self.resolve_path_array(root, path).await?;
1500        let dir_cid = dir_cid.ok_or_else(|| HashTreeError::PathNotFound(path.join("/")))?;
1501
1502        let entries = self.list_directory(&dir_cid).await?;
1503        let entry = entries
1504            .iter()
1505            .find(|e| e.name == old_name)
1506            .ok_or_else(|| HashTreeError::EntryNotFound(old_name.to_string()))?;
1507
1508        let entry_hash = entry.hash;
1509        let entry_size = entry.size;
1510        let entry_key = entry.key;
1511        let entry_link_type = entry.link_type;
1512        let entry_meta = entry.meta.clone();
1513
1514        let new_entries: Vec<DirEntry> = entries
1515            .into_iter()
1516            .filter(|e| e.name != old_name)
1517            .map(|e| DirEntry {
1518                name: e.name,
1519                hash: e.hash,
1520                size: e.size,
1521                key: e.key,
1522                link_type: e.link_type,
1523                meta: e.meta,
1524            })
1525            .chain(std::iter::once(DirEntry {
1526                name: new_name.to_string(),
1527                hash: entry_hash,
1528                size: entry_size,
1529                key: entry_key,
1530                link_type: entry_link_type,
1531                meta: entry_meta,
1532            }))
1533            .collect();
1534
1535        let new_dir_cid = self.put_directory(new_entries).await?;
1536        self.rebuild_path(root, path, new_dir_cid).await
1537    }
1538
1539    /// Move an entry to a different directory
1540    /// Returns new root Cid
1541    pub async fn move_entry(
1542        &self,
1543        root: &Cid,
1544        source_path: &[&str],
1545        name: &str,
1546        target_path: &[&str],
1547    ) -> Result<Cid, HashTreeError> {
1548        let source_dir_cid = self.resolve_path_array(root, source_path).await?;
1549        let source_dir_cid =
1550            source_dir_cid.ok_or_else(|| HashTreeError::PathNotFound(source_path.join("/")))?;
1551
1552        let source_entries = self.list_directory(&source_dir_cid).await?;
1553        let entry = source_entries
1554            .iter()
1555            .find(|e| e.name == name)
1556            .ok_or_else(|| HashTreeError::EntryNotFound(name.to_string()))?;
1557
1558        let entry_cid = Cid {
1559            hash: entry.hash,
1560            key: entry.key,
1561        };
1562        let entry_size = entry.size;
1563        let entry_link_type = entry.link_type;
1564
1565        // Remove from source
1566        let new_root = self.remove_entry(root, source_path, name).await?;
1567
1568        // Add to target
1569        self.set_entry(
1570            &new_root,
1571            target_path,
1572            name,
1573            &entry_cid,
1574            entry_size,
1575            entry_link_type,
1576        )
1577        .await
1578    }
1579
1580    async fn resolve_path_array(
1581        &self,
1582        root: &Cid,
1583        path: &[&str],
1584    ) -> Result<Option<Cid>, HashTreeError> {
1585        if path.is_empty() {
1586            return Ok(Some(root.clone()));
1587        }
1588        self.resolve_path(root, &path.join("/")).await
1589    }
1590
1591    async fn rebuild_path(
1592        &self,
1593        root: &Cid,
1594        path: &[&str],
1595        new_child: Cid,
1596    ) -> Result<Cid, HashTreeError> {
1597        if path.is_empty() {
1598            return Ok(new_child);
1599        }
1600
1601        let mut child_cid = new_child;
1602        let parts: Vec<&str> = path.to_vec();
1603
1604        for i in (0..parts.len()).rev() {
1605            let child_name = parts[i];
1606            let parent_path = &parts[..i];
1607
1608            let parent_cid = if parent_path.is_empty() {
1609                root.clone()
1610            } else {
1611                self.resolve_path_array(root, parent_path)
1612                    .await?
1613                    .ok_or_else(|| HashTreeError::PathNotFound(parent_path.join("/")))?
1614            };
1615
1616            let parent_entries = self.list_directory(&parent_cid).await?;
1617            let new_parent_entries: Vec<DirEntry> = parent_entries
1618                .into_iter()
1619                .map(|e| {
1620                    if e.name == child_name {
1621                        DirEntry {
1622                            name: e.name,
1623                            hash: child_cid.hash,
1624                            size: 0, // Directories don't have a meaningful size in the link
1625                            key: child_cid.key,
1626                            link_type: e.link_type,
1627                            meta: e.meta,
1628                        }
1629                    } else {
1630                        DirEntry {
1631                            name: e.name,
1632                            hash: e.hash,
1633                            size: e.size,
1634                            key: e.key,
1635                            link_type: e.link_type,
1636                            meta: e.meta,
1637                        }
1638                    }
1639                })
1640                .collect();
1641
1642            child_cid = self.put_directory(new_parent_entries).await?;
1643        }
1644
1645        Ok(child_cid)
1646    }
1647
1648    // ============ UTILITY ============
1649
1650    /// Get the underlying store
1651    pub fn get_store(&self) -> Arc<S> {
1652        self.store.clone()
1653    }
1654
1655    /// Get chunk size configuration
1656    pub fn chunk_size(&self) -> usize {
1657        self.chunk_size
1658    }
1659
1660    /// Get max links configuration
1661    pub fn max_links(&self) -> usize {
1662        self.max_links
1663    }
1664}
1665
1666/// Verify tree integrity - checks that all referenced hashes exist
1667pub async fn verify_tree<S: Store>(
1668    store: Arc<S>,
1669    root_hash: &Hash,
1670) -> Result<crate::reader::VerifyResult, HashTreeError> {
1671    let mut missing = Vec::new();
1672    let mut visited = std::collections::HashSet::new();
1673
1674    verify_recursive(store, root_hash, &mut missing, &mut visited).await?;
1675
1676    Ok(crate::reader::VerifyResult {
1677        valid: missing.is_empty(),
1678        missing,
1679    })
1680}
1681
1682async fn verify_recursive<S: Store>(
1683    store: Arc<S>,
1684    hash: &Hash,
1685    missing: &mut Vec<Hash>,
1686    visited: &mut std::collections::HashSet<String>,
1687) -> Result<(), HashTreeError> {
1688    let hex = to_hex(hash);
1689    if visited.contains(&hex) {
1690        return Ok(());
1691    }
1692    visited.insert(hex);
1693
1694    let data = match store
1695        .get(hash)
1696        .await
1697        .map_err(|e| HashTreeError::Store(e.to_string()))?
1698    {
1699        Some(d) => d,
1700        None => {
1701            missing.push(*hash);
1702            return Ok(());
1703        }
1704    };
1705
1706    if is_tree_node(&data) {
1707        let node = decode_tree_node(&data)?;
1708        for link in &node.links {
1709            Box::pin(verify_recursive(
1710                store.clone(),
1711                &link.hash,
1712                missing,
1713                visited,
1714            ))
1715            .await?;
1716        }
1717    }
1718
1719    Ok(())
1720}
1721
1722#[cfg(test)]
1723mod tests;