common/mount/
mount_inner.rs

1use std::collections::BTreeMap;
2use std::io::Read;
3use std::path::{Path, PathBuf};
4use std::sync::Arc;
5
6use tokio::sync::Mutex;
7use uuid::Uuid;
8
9use crate::crypto::{PublicKey, Secret, SecretError, SecretKey, SecretShare};
10use crate::linked_data::{BlockEncoded, CodecError, Link};
11use crate::peer::{BlobsStore, BlobsStoreError};
12
13use super::manifest::Manifest;
14use super::node::{Node, NodeError, NodeLink};
15use super::pins::Pins;
16
17pub fn clean_path(path: &Path) -> PathBuf {
18    if !path.is_absolute() {
19        panic!("path is not absolute");
20    }
21    path.iter()
22        .skip(1)
23        .map(|part| part.to_string_lossy().to_string())
24        .collect::<PathBuf>()
25}
26
27#[derive(Clone)]
28pub struct MountInner {
29    // link to the manifest
30    pub link: Link,
31    // the loaded manifest
32    pub manifest: Manifest,
33    // the loaded, decrypted entry node
34    pub entry: Node,
35    // the loaded pins
36    pub pins: Pins,
37    // convenience pointer to the height of the mount
38    pub height: u64,
39}
40
41impl MountInner {
42    pub fn link(&self) -> &Link {
43        &self.link
44    }
45    pub fn entry(&self) -> &Node {
46        &self.entry
47    }
48    pub fn manifest(&self) -> &Manifest {
49        &self.manifest
50    }
51    pub fn pins(&self) -> &Pins {
52        &self.pins
53    }
54    pub fn height(&self) -> u64 {
55        self.height
56    }
57}
58
59#[derive(Clone)]
60pub struct Mount(Arc<Mutex<MountInner>>, BlobsStore);
61
62#[derive(Debug, thiserror::Error)]
63pub enum MountError {
64    #[error("default error: {0}")]
65    Default(#[from] anyhow::Error),
66    #[error("link not found")]
67    LinkNotFound(Link),
68    #[error("path not found: {0}")]
69    PathNotFound(PathBuf),
70    #[error("path is not a node: {0}")]
71    PathNotNode(PathBuf),
72    #[error("path already exists: {0}")]
73    PathAlreadyExists(PathBuf),
74    #[error("blobs store error: {0}")]
75    BlobsStore(#[from] BlobsStoreError),
76    #[error("secret error: {0}")]
77    Secret(#[from] SecretError),
78    #[error("node error: {0}")]
79    Node(#[from] NodeError),
80    #[error("codec error: {0}")]
81    Codec(#[from] CodecError),
82    #[error("share error: {0}")]
83    Share(#[from] crate::crypto::SecretShareError),
84    #[error("peers share was not found. this should be impossible")]
85    ShareNotFound,
86}
87
88impl Mount {
89    pub async fn inner(&self) -> MountInner {
90        self.0.lock().await.clone()
91    }
92
93    pub fn blobs(&self) -> BlobsStore {
94        self.1.clone()
95    }
96
97    pub async fn link(&self) -> Link {
98        let inner = self.0.lock().await;
99        inner.link.clone()
100    }
101
102    /// Save the current mount state to the blobs store
103    pub async fn save(&self, blobs: &BlobsStore) -> Result<(Link, Link, u64), MountError> {
104        // Clone data we need before any async operations
105        let (entry_node, mut pins, previous_link, previous_height, manifest_template) = {
106            let inner = self.0.lock().await;
107            (
108                inner.entry.clone(),
109                inner.pins.clone(),
110                inner.link.clone(),
111                inner.height,
112                inner.manifest.clone(),
113            )
114        };
115
116        // Increment the height of the mount
117        let height = previous_height + 1;
118
119        // Create a new secret for the updated root
120        let secret = Secret::generate();
121
122        // Put the current root node into blobs with the new secret
123        let entry = Self::_put_node_in_blobs(&entry_node, &secret, blobs).await?;
124
125        // Serialize current pins to blobs
126        // put the new root link into the pins, as well as the previous link
127        pins.insert(entry.clone().hash());
128        pins.insert(previous_link.hash());
129        let pins_link = Self::_put_pins_in_blobs(&pins, blobs).await?;
130
131        // Update the bucket's share with the new root link
132        // (add_share creates the Share internally)
133        let mut manifest = manifest_template;
134        let _m = manifest.clone();
135        let shares = _m.shares();
136        manifest.unset_shares();
137        for share in shares.values() {
138            let public_key = share.principal().identity;
139            manifest.add_share(public_key, secret.clone())?;
140        }
141        manifest.set_pins(pins_link.clone());
142        manifest.set_previous(previous_link.clone());
143        manifest.set_entry(entry.clone());
144        manifest.set_height(height);
145
146        // Put the updated manifest into blobs to determine the new link
147        let link = Self::_put_manifest_in_blobs(&manifest, blobs).await?;
148
149        // Update the internal state
150        {
151            let mut inner = self.0.lock().await;
152            inner.manifest = manifest;
153            inner.height = height;
154        }
155
156        Ok((link, previous_link, height))
157    }
158
159    pub async fn init(
160        id: Uuid,
161        name: String,
162        owner: &SecretKey,
163        blobs: &BlobsStore,
164    ) -> Result<Self, MountError> {
165        // create a new root node for the bucket
166        let entry = Node::default();
167        // create a new secret for the owner
168        let secret = Secret::generate();
169        // put the node in the blobs store for the secret
170        let entry_link = Self::_put_node_in_blobs(&entry, &secret, blobs).await?;
171        // share the secret with the owner
172        let share = SecretShare::new(&secret, &owner.public())?;
173        // Initialize pins with root node hash
174        let mut pins = Pins::new();
175        pins.insert(entry_link.hash());
176        // Put the pins in blobs to get a pins link
177        let pins_link = Self::_put_pins_in_blobs(&pins, blobs).await?;
178        // construct the new manifest
179        let manifest = Manifest::new(
180            id,
181            name.clone(),
182            owner.public(),
183            share,
184            entry_link.clone(),
185            pins_link.clone(),
186            0, // initial height is 0
187        );
188        let link = Self::_put_manifest_in_blobs(&manifest, blobs).await?;
189
190        // return the new mount
191        Ok(Mount(
192            Arc::new(Mutex::new(MountInner {
193                link,
194                manifest,
195                entry,
196                pins,
197                height: 0,
198            })),
199            blobs.clone(),
200        ))
201    }
202
203    pub async fn load(
204        link: &Link,
205        secret_key: &SecretKey,
206        blobs: &BlobsStore,
207    ) -> Result<Self, MountError> {
208        let public_key = &secret_key.public();
209        let manifest = Self::_get_manifest_from_blobs(link, blobs).await?;
210
211        let _share = manifest.get_share(public_key);
212
213        let share = match _share {
214            Some(share) => share.share(),
215            None => return Err(MountError::ShareNotFound),
216        };
217
218        let secret = share.recover(secret_key)?;
219
220        let pins = Self::_get_pins_from_blobs(manifest.pins(), blobs).await?;
221        let entry =
222            Self::_get_node_from_blobs(&NodeLink::Dir(manifest.entry().clone(), secret), blobs)
223                .await?;
224
225        // Read height from the manifest
226        let height = manifest.height();
227
228        Ok(Mount(
229            Arc::new(Mutex::new(MountInner {
230                link: link.clone(),
231                manifest,
232                entry,
233                pins,
234                height,
235            })),
236            blobs.clone(),
237        ))
238    }
239
240    pub async fn share(&mut self, peer: PublicKey) -> Result<(), MountError> {
241        let mut inner = self.0.lock().await;
242        inner.manifest.add_share(peer, Secret::default())?;
243        Ok(())
244    }
245
246    pub async fn add<R>(&mut self, path: &Path, data: R) -> Result<(), MountError>
247    where
248        R: Read + Send + Sync + 'static + Unpin,
249    {
250        let secret = Secret::generate();
251
252        let encrypted_reader = secret.encrypt_reader(data)?;
253
254        // TODO (amiller68): this is incredibly dumb
255        use bytes::Bytes;
256        use futures::stream;
257        let encrypted_bytes = {
258            let mut buf = Vec::new();
259            let mut reader = encrypted_reader;
260            reader.read_to_end(&mut buf).map_err(SecretError::Io)?;
261            buf
262        };
263
264        let stream = Box::pin(stream::once(async move {
265            Ok::<_, std::io::Error>(Bytes::from(encrypted_bytes))
266        }));
267
268        let hash = self.1.put_stream(stream).await?;
269
270        let link = Link::new(crate::linked_data::LD_RAW_CODEC, hash);
271
272        let node_link = NodeLink::new_data_from_path(link.clone(), secret, path);
273
274        let root_node = {
275            let inner = self.0.lock().await;
276            inner.entry.clone()
277        };
278
279        let (updated_link, node_hashes) =
280            Self::_set_node_link_at_path(root_node, node_link, path, &self.1).await?;
281
282        // Update entry if needed
283        let new_entry = if let NodeLink::Dir(new_root_link, new_secret) = updated_link {
284            Some(
285                Self::_get_node_from_blobs(
286                    &NodeLink::Dir(new_root_link.clone(), new_secret),
287                    &self.1,
288                )
289                .await?,
290            )
291        } else {
292            None
293        };
294
295        // Update inner state
296        {
297            let mut inner = self.0.lock().await;
298            // Track pins: data blob + all created node hashes
299            inner.pins.insert(hash);
300            inner.pins.extend(node_hashes);
301
302            if let Some(entry) = new_entry {
303                inner.entry = entry;
304            }
305        }
306
307        Ok(())
308    }
309
310    pub async fn rm(&mut self, path: &Path) -> Result<(), MountError> {
311        let path = clean_path(path);
312        let parent_path = path
313            .parent()
314            .ok_or_else(|| MountError::Default(anyhow::anyhow!("Cannot remove root")))?;
315
316        let entry = {
317            let inner = self.0.lock().await;
318            inner.entry.clone()
319        };
320
321        let mut parent_node = if parent_path == Path::new("") {
322            entry.clone()
323        } else {
324            Self::_get_node_at_path(&entry, parent_path, &self.1).await?
325        };
326
327        let file_name = path.file_name().unwrap().to_string_lossy().to_string();
328
329        if parent_node.del(&file_name).is_none() {
330            return Err(MountError::PathNotFound(path.to_path_buf()));
331        }
332
333        if parent_path == Path::new("") {
334            let secret = Secret::generate();
335            let link = Self::_put_node_in_blobs(&parent_node, &secret, &self.1).await?;
336
337            let mut inner = self.0.lock().await;
338            // Track the new root node hash
339            inner.pins.insert(link.hash());
340            inner.entry = parent_node;
341        } else {
342            // Save the modified parent node to blobs
343            let secret = Secret::generate();
344            let parent_link = Self::_put_node_in_blobs(&parent_node, &secret, &self.1).await?;
345            let node_link = NodeLink::new_dir(parent_link.clone(), secret);
346
347            // Convert parent_path back to absolute for _set_node_link_at_path
348            let abs_parent_path = Path::new("/").join(parent_path);
349            let (updated_link, node_hashes) =
350                Self::_set_node_link_at_path(entry, node_link, &abs_parent_path, &self.1).await?;
351
352            let new_entry = if let NodeLink::Dir(new_root_link, new_secret) = updated_link {
353                Some(
354                    Self::_get_node_from_blobs(
355                        &NodeLink::Dir(new_root_link.clone(), new_secret),
356                        &self.1,
357                    )
358                    .await?,
359                )
360            } else {
361                None
362            };
363
364            let mut inner = self.0.lock().await;
365            // Track the parent node hash and all created node hashes
366            inner.pins.insert(parent_link.hash());
367            inner.pins.extend(node_hashes);
368
369            if let Some(new_entry) = new_entry {
370                inner.entry = new_entry;
371            }
372        }
373
374        Ok(())
375    }
376
377    pub async fn mkdir(&mut self, path: &Path) -> Result<(), MountError> {
378        let path = clean_path(path);
379
380        // Check if the path already exists
381        let entry = {
382            let inner = self.0.lock().await;
383            inner.entry.clone()
384        };
385
386        // Try to get the parent path and file name
387        let (parent_path, dir_name) = if let Some(parent) = path.parent() {
388            (
389                parent,
390                path.file_name().unwrap().to_string_lossy().to_string(),
391            )
392        } else {
393            return Err(MountError::Default(anyhow::anyhow!("Cannot mkdir at root")));
394        };
395
396        // Get the parent node (or use root if parent is empty)
397        let parent_node = if parent_path == Path::new("") {
398            entry.clone()
399        } else {
400            // Check if parent path exists, if not it will be created by _set_node_link_at_path
401            match Self::_get_node_at_path(&entry, parent_path, &self.1).await {
402                Ok(node) => node,
403                Err(MountError::PathNotFound(_)) => Node::default(), // Will be created
404                Err(err) => return Err(err),
405            }
406        };
407
408        // Check if a node with this name already exists in the parent
409        if parent_node.get_link(&dir_name).is_some() {
410            return Err(MountError::PathAlreadyExists(Path::new("/").join(&path)));
411        }
412
413        // Create an empty directory node
414        let new_dir_node = Node::default();
415
416        // Generate a secret for the new directory
417        let secret = Secret::generate();
418
419        // Store the node in blobs
420        let dir_link = Self::_put_node_in_blobs(&new_dir_node, &secret, &self.1).await?;
421
422        // Create a NodeLink for the directory
423        let node_link = NodeLink::new_dir(dir_link.clone(), secret);
424
425        // Convert path back to absolute for _set_node_link_at_path
426        let abs_path = Path::new("/").join(&path);
427
428        // Use _set_node_link_at_path to insert the directory into the tree
429        let (updated_link, node_hashes) =
430            Self::_set_node_link_at_path(entry, node_link, &abs_path, &self.1).await?;
431
432        // Update entry if the root was modified
433        let new_entry = if let NodeLink::Dir(new_root_link, new_secret) = updated_link {
434            Some(
435                Self::_get_node_from_blobs(
436                    &NodeLink::Dir(new_root_link.clone(), new_secret),
437                    &self.1,
438                )
439                .await?,
440            )
441        } else {
442            None
443        };
444
445        // Update inner state
446        {
447            let mut inner = self.0.lock().await;
448            // Track the directory node hash and all created node hashes
449            inner.pins.insert(dir_link.hash());
450            inner.pins.extend(node_hashes);
451
452            if let Some(new_entry) = new_entry {
453                inner.entry = new_entry;
454            }
455        }
456
457        Ok(())
458    }
459
460    pub async fn ls(&self, path: &Path) -> Result<BTreeMap<PathBuf, NodeLink>, MountError> {
461        let mut items = BTreeMap::new();
462        let path = clean_path(path);
463
464        let inner = self.0.lock().await;
465        let root_node = inner.entry.clone();
466        drop(inner);
467
468        let node = if path == Path::new("") {
469            root_node
470        } else {
471            match Self::_get_node_at_path(&root_node, &path, &self.1).await {
472                Ok(node) => node,
473                Err(MountError::LinkNotFound(_)) => {
474                    return Err(MountError::PathNotNode(path.to_path_buf()))
475                }
476                Err(err) => return Err(err),
477            }
478        };
479
480        for (name, link) in node.get_links() {
481            let mut full_path = path.clone();
482            full_path.push(name);
483            items.insert(full_path, link.clone());
484        }
485
486        Ok(items)
487    }
488
489    pub async fn ls_deep(&self, path: &Path) -> Result<BTreeMap<PathBuf, NodeLink>, MountError> {
490        let base_path = clean_path(path);
491        self._ls_deep(path, &base_path).await
492    }
493
494    async fn _ls_deep(
495        &self,
496        path: &Path,
497        base_path: &Path,
498    ) -> Result<BTreeMap<PathBuf, NodeLink>, MountError> {
499        let mut all_items = BTreeMap::new();
500
501        // get the initial items at the given path
502        let items = self.ls(path).await?;
503
504        for (item_path, link) in items {
505            // Make path relative to the base_path
506            let relative_path = if base_path == Path::new("") {
507                item_path.clone()
508            } else {
509                item_path
510                    .strip_prefix(base_path)
511                    .unwrap_or(&item_path)
512                    .to_path_buf()
513            };
514            all_items.insert(relative_path.clone(), link.clone());
515
516            if link.is_dir() {
517                // Recurse using the absolute path
518                let abs_item_path = Path::new("/").join(&item_path);
519                let sub_items = Box::pin(self._ls_deep(&abs_item_path, base_path)).await?;
520
521                // Sub items already have correct relative paths from base_path
522                for (sub_path, sub_link) in sub_items {
523                    all_items.insert(sub_path, sub_link);
524                }
525            }
526        }
527
528        Ok(all_items)
529    }
530
531    #[allow(clippy::await_holding_lock)]
532    pub async fn cat(&self, path: &Path) -> Result<Vec<u8>, MountError> {
533        let path = clean_path(path);
534
535        let inner = self.0.lock().await;
536        let root_node = inner.entry.clone();
537        drop(inner);
538
539        let (parent_path, file_name) = if let Some(parent) = path.parent() {
540            (
541                parent,
542                path.file_name().unwrap().to_string_lossy().to_string(),
543            )
544        } else {
545            return Err(MountError::PathNotFound(path.to_path_buf()));
546        };
547
548        let parent_node = if parent_path == Path::new("") {
549            root_node
550        } else {
551            Self::_get_node_at_path(&root_node, parent_path, &self.1).await?
552        };
553
554        let link = parent_node
555            .get_link(&file_name)
556            .ok_or_else(|| MountError::PathNotFound(path.to_path_buf()))?;
557
558        match link {
559            NodeLink::Data(link, secret, _) => {
560                let encrypted_data = self.1.get(&link.hash()).await?;
561                let data = secret.decrypt(&encrypted_data)?;
562                Ok(data)
563            }
564            NodeLink::Dir(_, _) => Err(MountError::PathNotNode(path.to_path_buf())),
565        }
566    }
567
568    /// Get the NodeLink for a file at a given path
569    #[allow(clippy::await_holding_lock)]
570    pub async fn get(&self, path: &Path) -> Result<NodeLink, MountError> {
571        let path = clean_path(path);
572
573        let inner = self.0.lock().await;
574        let root_node = inner.entry.clone();
575        drop(inner);
576
577        let (parent_path, file_name) = if let Some(parent) = path.parent() {
578            (
579                parent,
580                path.file_name().unwrap().to_string_lossy().to_string(),
581            )
582        } else {
583            return Err(MountError::PathNotFound(path.to_path_buf()));
584        };
585
586        let parent_node = if parent_path == Path::new("") {
587            root_node
588        } else {
589            Self::_get_node_at_path(&root_node, parent_path, &self.1).await?
590        };
591
592        parent_node
593            .get_link(&file_name)
594            .cloned()
595            .ok_or_else(|| MountError::PathNotFound(path.to_path_buf()))
596    }
597
598    async fn _get_node_at_path(
599        node: &Node,
600        path: &Path,
601        blobs: &BlobsStore,
602    ) -> Result<Node, MountError> {
603        let mut current_node = node.clone();
604        let mut consumed_path = PathBuf::from("/");
605
606        for part in path.iter() {
607            consumed_path.push(part);
608            let next = part.to_string_lossy().to_string();
609            let next_link = current_node
610                .get_link(&next)
611                .ok_or(MountError::PathNotFound(consumed_path.clone()))?;
612            current_node = Self::_get_node_from_blobs(next_link, blobs).await?
613        }
614        Ok(current_node)
615    }
616
617    pub async fn _set_node_link_at_path(
618        node: Node,
619        node_link: NodeLink,
620        path: &Path,
621        blobs: &BlobsStore,
622    ) -> Result<(NodeLink, Vec<crate::linked_data::Hash>), MountError> {
623        let path = clean_path(path);
624        let mut visited_nodes = Vec::new();
625        let mut name = path.file_name().unwrap().to_string_lossy().to_string();
626        let parent_path = path.parent().unwrap_or(Path::new(""));
627
628        let mut consumed_path = PathBuf::from("/");
629        let mut node = node;
630        visited_nodes.push((consumed_path.clone(), node.clone()));
631
632        for part in parent_path.iter() {
633            let next = part.to_string_lossy().to_string();
634            let next_link = node.get_link(&next);
635            if let Some(next_link) = next_link {
636                consumed_path.push(part);
637                match next_link {
638                    NodeLink::Dir(..) => {
639                        node = Self::_get_node_from_blobs(next_link, blobs).await?
640                    }
641                    NodeLink::Data(..) => {
642                        return Err(MountError::PathNotNode(consumed_path.clone()));
643                    }
644                }
645                visited_nodes.push((consumed_path.clone(), node.clone()));
646            } else {
647                // Create a new directory node
648                node = Node::default();
649                consumed_path.push(part);
650                visited_nodes.push((consumed_path.clone(), node.clone()));
651            }
652        }
653
654        let mut node_link = node_link;
655        let mut created_hashes = Vec::new();
656        for (path, mut node) in visited_nodes.into_iter().rev() {
657            node.insert(name, node_link.clone());
658            let secret = Secret::generate();
659            let link = Self::_put_node_in_blobs(&node, &secret, blobs).await?;
660            created_hashes.push(link.hash());
661            node_link = NodeLink::Dir(link, secret);
662            name = path
663                .file_name()
664                .unwrap_or_default()
665                .to_string_lossy()
666                .to_string();
667        }
668
669        Ok((node_link, created_hashes))
670    }
671
672    async fn _get_manifest_from_blobs(
673        link: &Link,
674        blobs: &BlobsStore,
675    ) -> Result<Manifest, MountError> {
676        tracing::debug!(
677            "_get_bucket_from_blobs: Checking for bucket data at link {:?}",
678            link
679        );
680        let hash = link.hash();
681        tracing::debug!("_get_bucket_from_blobs: Bucket hash: {}", hash);
682
683        match blobs.stat(&hash).await {
684            Ok(true) => {
685                tracing::debug!(
686                    "_get_bucket_from_blobs: Bucket hash {} exists in blobs",
687                    hash
688                );
689            }
690            Ok(false) => {
691                tracing::error!("_get_bucket_from_blobs: Bucket hash {} NOT FOUND in blobs - LinkNotFound error!", hash);
692                return Err(MountError::LinkNotFound(link.clone()));
693            }
694            Err(e) => {
695                tracing::error!(
696                    "_get_bucket_from_blobs: Error checking bucket hash {}: {}",
697                    hash,
698                    e
699                );
700                return Err(e.into());
701            }
702        }
703
704        tracing::debug!("_get_bucket_from_blobs: Reading bucket data from blobs");
705        let data = blobs.get(&hash).await?;
706        tracing::debug!(
707            "_get_bucket_from_blobs: Got {} bytes of bucket data",
708            data.len()
709        );
710
711        let bucket_data = Manifest::decode(&data)?;
712        tracing::debug!(
713            "_get_bucket_from_blobs: Successfully decoded BucketData for bucket '{}'",
714            bucket_data.name()
715        );
716
717        Ok(bucket_data)
718    }
719
720    pub async fn _get_pins_from_blobs(link: &Link, blobs: &BlobsStore) -> Result<Pins, MountError> {
721        tracing::debug!("_get_pins_from_blobs: Checking for pins at link {:?}", link);
722        let hash = link.hash();
723        tracing::debug!("_get_pins_from_blobs: Pins hash: {}", hash);
724
725        match blobs.stat(&hash).await {
726            Ok(true) => {
727                tracing::debug!("_get_pins_from_blobs: Pins hash {} exists in blobs", hash);
728            }
729            Ok(false) => {
730                tracing::error!(
731                    "_get_pins_from_blobs: Pins hash {} NOT FOUND in blobs - LinkNotFound error!",
732                    hash
733                );
734                return Err(MountError::LinkNotFound(link.clone()));
735            }
736            Err(e) => {
737                tracing::error!(
738                    "_get_pins_from_blobs: Error checking pins hash {}: {}",
739                    hash,
740                    e
741                );
742                return Err(e.into());
743            }
744        }
745
746        tracing::debug!("_get_pins_from_blobs: Reading hash list from blobs");
747        // Read hashes from the hash list blob
748        let hashes = blobs.read_hash_list(hash).await?;
749        tracing::debug!(
750            "_get_pins_from_blobs: Successfully read {} hashes from pinset",
751            hashes.len()
752        );
753
754        Ok(Pins::from_vec(hashes))
755    }
756
757    async fn _get_node_from_blobs(
758        node_link: &NodeLink,
759        blobs: &BlobsStore,
760    ) -> Result<Node, MountError> {
761        let link = node_link.link();
762        let secret = node_link.secret();
763        let hash = link.hash();
764
765        tracing::debug!("_get_node_from_blobs: Checking for node at hash {}", hash);
766
767        match blobs.stat(&hash).await {
768            Ok(true) => {
769                tracing::debug!("_get_node_from_blobs: Node hash {} exists in blobs", hash);
770            }
771            Ok(false) => {
772                tracing::error!(
773                    "_get_node_from_blobs: Node hash {} NOT FOUND in blobs - LinkNotFound error!",
774                    hash
775                );
776                return Err(MountError::LinkNotFound(link.clone()));
777            }
778            Err(e) => {
779                tracing::error!(
780                    "_get_node_from_blobs: Error checking node hash {}: {}",
781                    hash,
782                    e
783                );
784                return Err(e.into());
785            }
786        }
787
788        tracing::debug!("_get_node_from_blobs: Reading encrypted node blob");
789        let blob = blobs.get(&hash).await?;
790        tracing::debug!(
791            "_get_node_from_blobs: Got {} bytes of encrypted node data",
792            blob.len()
793        );
794
795        tracing::debug!("_get_node_from_blobs: Decrypting node data");
796        let data = secret.decrypt(&blob)?;
797        tracing::debug!("_get_node_from_blobs: Decrypted {} bytes", data.len());
798
799        let node = Node::decode(&data)?;
800        tracing::debug!("_get_node_from_blobs: Successfully decoded Node");
801
802        Ok(node)
803    }
804
805    // TODO (amiller68): you should inline a Link
806    //  into the node when we store encrypt it,
807    //  so that we have an integrity check
808    async fn _put_node_in_blobs(
809        node: &Node,
810        secret: &Secret,
811        blobs: &BlobsStore,
812    ) -> Result<Link, MountError> {
813        let _data = node.encode()?;
814        let data = secret.encrypt(&_data)?;
815        let hash = blobs.put(data).await?;
816        // NOTE (amiller68): nodes are always stored as raw
817        //  since they are encrypted blobs
818        let link = Link::new(crate::linked_data::LD_RAW_CODEC, hash);
819        Ok(link)
820    }
821
822    pub async fn _put_manifest_in_blobs(
823        bucket_data: &Manifest,
824        blobs: &BlobsStore,
825    ) -> Result<Link, MountError> {
826        let data = bucket_data.encode()?;
827        let hash = blobs.put(data).await?;
828        // NOTE (amiller68): buckets are unencrypted, so they can inherit
829        //  the codec of the bucket itself (which is currently always cbor)
830        let link = Link::new(bucket_data.codec(), hash);
831        Ok(link)
832    }
833
834    pub async fn _put_pins_in_blobs(pins: &Pins, blobs: &BlobsStore) -> Result<Link, MountError> {
835        // Create a hash list blob from the pins (raw bytes: 32 bytes per hash, concatenated)
836        let hash = blobs.create_hash_list(pins.iter().copied()).await?;
837        // Pins are stored as raw blobs containing concatenated hashes
838        // Note: The underlying blob is HashSeq format, but Link doesn't track this
839        let link = Link::new(crate::linked_data::LD_RAW_CODEC, hash);
840        Ok(link)
841    }
842}
843
844#[cfg(test)]
845mod test {
846    use super::*;
847    use std::io::Cursor;
848    use tempfile::TempDir;
849
850    async fn setup_test_env() -> (Mount, BlobsStore, crate::crypto::SecretKey, TempDir) {
851        let temp_dir = TempDir::new().unwrap();
852        let blob_path = temp_dir.path().join("blobs");
853
854        let secret_key = SecretKey::generate();
855        // Generate iroh secret key from random bytes
856        let blobs = BlobsStore::fs(&blob_path).await.unwrap();
857
858        let mount = Mount::init(Uuid::new_v4(), "test".to_string(), &secret_key, &blobs)
859            .await
860            .unwrap();
861
862        (mount, blobs, secret_key, temp_dir)
863    }
864
865    #[tokio::test]
866    async fn test_add_and_cat() {
867        let (mut mount, _, _, _temp) = setup_test_env().await;
868
869        let data = b"Hello, world!";
870        let path = PathBuf::from("/test.txt");
871
872        mount.add(&path, Cursor::new(data.to_vec())).await.unwrap();
873
874        let result = mount.cat(&path).await.unwrap();
875        assert_eq!(result, data);
876    }
877
878    #[tokio::test]
879    async fn test_add_with_metadata() {
880        let (mut mount, _blobs, _, _temp) = setup_test_env().await;
881
882        let data = b"{ \"key\": \"value\" }";
883        let path = PathBuf::from("/data.json");
884
885        mount.add(&path, Cursor::new(data.to_vec())).await.unwrap();
886
887        let items = mount.ls(&PathBuf::from("/")).await.unwrap();
888        assert_eq!(items.len(), 1);
889
890        let (file_path, link) = items.iter().next().unwrap();
891        assert_eq!(file_path, &PathBuf::from("data.json"));
892
893        if let Some(data_info) = link.data() {
894            assert!(data_info.mime().is_some());
895            assert_eq!(data_info.mime().unwrap().as_ref(), "application/json");
896        } else {
897            panic!("Expected data link with metadata");
898        }
899    }
900
901    #[tokio::test]
902    async fn test_ls() {
903        let (mut mount, _blobs, _, _temp) = setup_test_env().await;
904
905        mount
906            .add(&PathBuf::from("/file1.txt"), Cursor::new(b"data1".to_vec()))
907            .await
908            .unwrap();
909        mount
910            .add(&PathBuf::from("/file2.txt"), Cursor::new(b"data2".to_vec()))
911            .await
912            .unwrap();
913        mount
914            .add(
915                &PathBuf::from("/dir/file3.txt"),
916                Cursor::new(b"data3".to_vec()),
917            )
918            .await
919            .unwrap();
920
921        let items = mount.ls(&PathBuf::from("/")).await.unwrap();
922        assert_eq!(items.len(), 3);
923
924        assert!(items.contains_key(&PathBuf::from("file1.txt")));
925        assert!(items.contains_key(&PathBuf::from("file2.txt")));
926        assert!(items.contains_key(&PathBuf::from("dir")));
927
928        let sub_items = mount.ls(&PathBuf::from("/dir")).await.unwrap();
929        assert_eq!(sub_items.len(), 1);
930        assert!(sub_items.contains_key(&PathBuf::from("dir/file3.txt")));
931    }
932
933    #[tokio::test]
934    async fn test_ls_deep() {
935        let (mut mount, _blobs, _, _temp) = setup_test_env().await;
936
937        mount
938            .add(&PathBuf::from("/a.txt"), Cursor::new(b"a".to_vec()))
939            .await
940            .unwrap();
941        mount
942            .add(&PathBuf::from("/dir1/b.txt"), Cursor::new(b"b".to_vec()))
943            .await
944            .unwrap();
945        mount
946            .add(
947                &PathBuf::from("/dir1/dir2/c.txt"),
948                Cursor::new(b"c".to_vec()),
949            )
950            .await
951            .unwrap();
952        mount
953            .add(
954                &PathBuf::from("/dir1/dir2/dir3/d.txt"),
955                Cursor::new(b"d".to_vec()),
956            )
957            .await
958            .unwrap();
959
960        let all_items = mount.ls_deep(&PathBuf::from("/")).await.unwrap();
961
962        assert!(all_items.contains_key(&PathBuf::from("a.txt")));
963        assert!(all_items.contains_key(&PathBuf::from("dir1")));
964        assert!(all_items.contains_key(&PathBuf::from("dir1/b.txt")));
965        assert!(all_items.contains_key(&PathBuf::from("dir1/dir2")));
966        assert!(all_items.contains_key(&PathBuf::from("dir1/dir2/c.txt")));
967        assert!(all_items.contains_key(&PathBuf::from("dir1/dir2/dir3")));
968        assert!(all_items.contains_key(&PathBuf::from("dir1/dir2/dir3/d.txt")));
969    }
970
971    #[tokio::test]
972    async fn test_rm() {
973        let (mut mount, _blobs, _, _temp) = setup_test_env().await;
974
975        mount
976            .add(&PathBuf::from("/file1.txt"), Cursor::new(b"data1".to_vec()))
977            .await
978            .unwrap();
979        mount
980            .add(&PathBuf::from("/file2.txt"), Cursor::new(b"data2".to_vec()))
981            .await
982            .unwrap();
983
984        let items = mount.ls(&PathBuf::from("/")).await.unwrap();
985        assert_eq!(items.len(), 2);
986
987        mount.rm(&PathBuf::from("/file1.txt")).await.unwrap();
988
989        let items = mount.ls(&PathBuf::from("/")).await.unwrap();
990        assert_eq!(items.len(), 1);
991        assert!(items.contains_key(&PathBuf::from("file2.txt")));
992        assert!(!items.contains_key(&PathBuf::from("file1.txt")));
993
994        let result = mount.cat(&PathBuf::from("/file1.txt")).await;
995        assert!(result.is_err());
996    }
997
998    #[tokio::test]
999    async fn test_nested_operations() {
1000        let (mut mount, _blobs, _, _temp) = setup_test_env().await;
1001
1002        let files = vec![
1003            ("/root.txt", b"root" as &[u8]),
1004            ("/docs/readme.md", b"readme" as &[u8]),
1005            ("/docs/guide.pdf", b"guide" as &[u8]),
1006            ("/src/main.rs", b"main" as &[u8]),
1007            ("/src/lib.rs", b"lib" as &[u8]),
1008            ("/src/tests/unit.rs", b"unit" as &[u8]),
1009            ("/src/tests/integration.rs", b"integration" as &[u8]),
1010        ];
1011
1012        for (path, data) in &files {
1013            mount
1014                .add(&PathBuf::from(path), Cursor::new(data.to_vec()))
1015                .await
1016                .unwrap();
1017        }
1018
1019        for (path, expected_data) in &files {
1020            let data = mount.cat(&PathBuf::from(path)).await.unwrap();
1021            assert_eq!(data, expected_data.to_vec());
1022        }
1023
1024        mount
1025            .rm(&PathBuf::from("/src/tests/unit.rs"))
1026            .await
1027            .unwrap();
1028
1029        let result = mount.cat(&PathBuf::from("/src/tests/unit.rs")).await;
1030        assert!(result.is_err());
1031
1032        let data = mount
1033            .cat(&PathBuf::from("/src/tests/integration.rs"))
1034            .await
1035            .unwrap();
1036        assert_eq!(data, b"integration");
1037    }
1038
1039    #[tokio::test]
1040    async fn test_various_file_types() {
1041        let (mut mount, _blobs, _, _temp) = setup_test_env().await;
1042
1043        let test_files = vec![
1044            ("/image.png", "image/png"),
1045            ("/video.mp4", "video/mp4"),
1046            ("/style.css", "text/css"),
1047            ("/script.js", "text/javascript"),
1048            ("/data.json", "application/json"),
1049            ("/archive.zip", "application/zip"),
1050            ("/document.pdf", "application/pdf"),
1051            ("/code.rs", "text/x-rust"),
1052        ];
1053
1054        for (path, expected_mime) in test_files {
1055            mount
1056                .add(&PathBuf::from(path), Cursor::new(b"test".to_vec()))
1057                .await
1058                .unwrap();
1059
1060            let items = mount.ls(&PathBuf::from("/")).await.unwrap();
1061            let link = items.values().find(|l| l.is_data()).unwrap();
1062
1063            if let Some(data_info) = link.data() {
1064                assert!(data_info.mime().is_some());
1065                assert_eq!(data_info.mime().unwrap().as_ref(), expected_mime);
1066            }
1067
1068            mount.rm(&PathBuf::from(path)).await.unwrap();
1069        }
1070    }
1071
1072    #[tokio::test]
1073    async fn test_error_cases() {
1074        let (mount, _blobs, _, _temp) = setup_test_env().await;
1075
1076        let result = mount.cat(&PathBuf::from("/does_not_exist.txt")).await;
1077        assert!(result.is_err());
1078
1079        let result = mount.ls(&PathBuf::from("/does_not_exist")).await;
1080        assert!(result.is_err() || result.unwrap().is_empty());
1081
1082        let (mut mount, _blobs, _, _temp) = setup_test_env().await;
1083        mount
1084            .add(
1085                &PathBuf::from("/dir/file.txt"),
1086                Cursor::new(b"data".to_vec()),
1087            )
1088            .await
1089            .unwrap();
1090
1091        let result = mount.cat(&PathBuf::from("/dir")).await;
1092        assert!(result.is_err());
1093    }
1094
1095    #[tokio::test]
1096    async fn test_save_load() {
1097        let (mount, blobs, secret_key, _temp) = setup_test_env().await;
1098        let (link, _previous_link, height) = mount.save(&blobs).await.unwrap();
1099        assert_eq!(height, 1); // Height should be 1 after first save
1100        let loaded_mount = Mount::load(&link, &secret_key, &blobs).await.unwrap();
1101        assert_eq!(loaded_mount.inner().await.height(), 1);
1102    }
1103
1104    #[tokio::test]
1105    async fn test_mkdir() {
1106        let (mut mount, _blobs, _, _temp) = setup_test_env().await;
1107
1108        // Create a directory
1109        mount.mkdir(&PathBuf::from("/test_dir")).await.unwrap();
1110
1111        // Verify it exists and is a directory
1112        let items = mount.ls(&PathBuf::from("/")).await.unwrap();
1113        assert_eq!(items.len(), 1);
1114        assert!(items.get(&PathBuf::from("test_dir")).unwrap().is_dir());
1115    }
1116
1117    #[tokio::test]
1118    async fn test_mkdir_nested() {
1119        let (mut mount, _blobs, _, _temp) = setup_test_env().await;
1120
1121        // Create nested directories (should create parents automatically)
1122        mount.mkdir(&PathBuf::from("/a/b/c")).await.unwrap();
1123
1124        // Verify the whole path exists
1125        let items_root = mount.ls(&PathBuf::from("/")).await.unwrap();
1126        assert!(items_root.contains_key(&PathBuf::from("a")));
1127
1128        let items_a = mount.ls(&PathBuf::from("/a")).await.unwrap();
1129        assert!(items_a.contains_key(&PathBuf::from("a/b")));
1130
1131        let items_b = mount.ls(&PathBuf::from("/a/b")).await.unwrap();
1132        assert!(items_b.contains_key(&PathBuf::from("a/b/c")));
1133        assert!(items_b.get(&PathBuf::from("a/b/c")).unwrap().is_dir());
1134    }
1135
1136    #[tokio::test]
1137    async fn test_mkdir_already_exists() {
1138        let (mut mount, _blobs, _, _temp) = setup_test_env().await;
1139
1140        // Create directory
1141        mount.mkdir(&PathBuf::from("/test_dir")).await.unwrap();
1142
1143        // Try to create it again - should error
1144        let result = mount.mkdir(&PathBuf::from("/test_dir")).await;
1145        assert!(matches!(result, Err(MountError::PathAlreadyExists(_))));
1146    }
1147
1148    #[tokio::test]
1149    async fn test_mkdir_file_exists() {
1150        let (mut mount, _blobs, _, _temp) = setup_test_env().await;
1151
1152        // Create a file
1153        mount
1154            .add(&PathBuf::from("/test.txt"), Cursor::new(b"data".to_vec()))
1155            .await
1156            .unwrap();
1157
1158        // Try to create directory with same name - should error
1159        let result = mount.mkdir(&PathBuf::from("/test.txt")).await;
1160        assert!(matches!(result, Err(MountError::PathAlreadyExists(_))));
1161    }
1162
1163    #[tokio::test]
1164    async fn test_mkdir_then_add_file() {
1165        let (mut mount, _blobs, _, _temp) = setup_test_env().await;
1166
1167        // Create a directory
1168        mount.mkdir(&PathBuf::from("/docs")).await.unwrap();
1169
1170        // Add a file to the created directory
1171        mount
1172            .add(
1173                &PathBuf::from("/docs/readme.md"),
1174                Cursor::new(b"# README".to_vec()),
1175            )
1176            .await
1177            .unwrap();
1178
1179        // Verify the file exists
1180        let data = mount.cat(&PathBuf::from("/docs/readme.md")).await.unwrap();
1181        assert_eq!(data, b"# README");
1182
1183        // Verify directory structure
1184        let items = mount.ls(&PathBuf::from("/docs")).await.unwrap();
1185        assert_eq!(items.len(), 1);
1186        assert!(items.contains_key(&PathBuf::from("docs/readme.md")));
1187    }
1188
1189    #[tokio::test]
1190    async fn test_mkdir_multiple_siblings() {
1191        let (mut mount, _blobs, _, _temp) = setup_test_env().await;
1192
1193        // Create multiple sibling directories
1194        mount.mkdir(&PathBuf::from("/dir1")).await.unwrap();
1195        mount.mkdir(&PathBuf::from("/dir2")).await.unwrap();
1196        mount.mkdir(&PathBuf::from("/dir3")).await.unwrap();
1197
1198        // Verify all exist
1199        let items = mount.ls(&PathBuf::from("/")).await.unwrap();
1200        assert_eq!(items.len(), 3);
1201        assert!(items.get(&PathBuf::from("dir1")).unwrap().is_dir());
1202        assert!(items.get(&PathBuf::from("dir2")).unwrap().is_dir());
1203        assert!(items.get(&PathBuf::from("dir3")).unwrap().is_dir());
1204    }
1205}