1use std::collections::BTreeMap;
4use std::future::Future;
5use std::pin::Pin;
6
7use crate::error::{MantarayError, Result};
8use crate::mode::NodeEntry;
9use crate::obfuscation::ObfuscationKey;
10use crate::{PATH_SEPARATOR, PREFIX_MAX_LEN};
11use bytes::Bytes;
12use nectar_primitives::chunk::{Chunk, ChunkAddress, ContentChunk};
13use nectar_primitives::store::{ChunkGet, ChunkPut, MaybeSend};
14
15#[cfg(not(target_arch = "wasm32"))]
19type RecurseFuture<'a> = Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>;
20#[cfg(target_arch = "wasm32")]
21type RecurseFuture<'a> = Pin<Box<dyn Future<Output = Result<()>> + 'a>>;
22
23#[derive(Clone, PartialEq, Eq)]
28pub struct Prefix {
29 len: u8,
30 data: [u8; PREFIX_MAX_LEN],
31}
32
33impl Default for Prefix {
34 #[inline]
35 fn default() -> Self {
36 Self::new()
37 }
38}
39
40impl Prefix {
41 pub const MAX_LEN: usize = PREFIX_MAX_LEN;
43
44 #[inline]
46 pub const fn new() -> Self {
47 Self {
48 len: 0,
49 data: [0u8; PREFIX_MAX_LEN],
50 }
51 }
52
53 #[inline]
55 pub fn from_slice(src: &[u8]) -> Self {
56 debug_assert!(src.len() <= PREFIX_MAX_LEN);
57 let mut data = [0u8; PREFIX_MAX_LEN];
58 data[..src.len()].copy_from_slice(src);
59 Self {
60 len: src.len() as u8,
61 data,
62 }
63 }
64
65 #[inline]
67 pub const fn len(&self) -> usize {
68 self.len as usize
69 }
70
71 #[inline]
73 pub const fn is_empty(&self) -> bool {
74 self.len == 0
75 }
76
77 #[inline]
79 pub const fn padded_bytes(&self) -> &[u8; PREFIX_MAX_LEN] {
80 &self.data
81 }
82}
83
84impl std::ops::Deref for Prefix {
85 type Target = [u8];
86
87 #[inline]
88 fn deref(&self) -> &[u8] {
89 &self.data[..self.len as usize]
90 }
91}
92
93impl std::fmt::Debug for Prefix {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 write!(f, "Prefix({:?})", &**self)
96 }
97}
98
99bitflags::bitflags! {
100 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
102 pub struct NodeType: u8 {
103 const VALUE = 2;
105 const EDGE = 4;
107 const PATH_SEPARATOR = 8;
109 const METADATA = 16;
111 }
112}
113
114#[derive(Debug, Clone, PartialEq, Eq)]
116pub struct Node<E: NodeEntry = ChunkAddress> {
117 pub(crate) node_type: NodeType,
119 pub(crate) obfuscation_key: ObfuscationKey,
121 pub(crate) reference: Option<ChunkAddress>,
123 pub(crate) entry: Option<E>,
125 pub(crate) metadata: BTreeMap<String, String>,
127 pub(crate) forks: BTreeMap<u8, Fork<E>>,
129 pub(crate) loaded: bool,
131}
132
133impl<E: NodeEntry> Default for Node<E> {
134 fn default() -> Self {
135 Self {
136 node_type: NodeType::empty(),
137 obfuscation_key: ObfuscationKey::ZERO,
138 reference: None,
139 entry: None,
140 metadata: BTreeMap::new(),
141 forks: BTreeMap::new(),
142 loaded: false,
143 }
144 }
145}
146
147#[derive(Debug, Clone, PartialEq, Eq)]
149pub struct Fork<E: NodeEntry = ChunkAddress> {
150 pub(crate) prefix: Prefix,
152 pub(crate) node: Node<E>,
154}
155
156impl<E: NodeEntry> Default for Fork<E> {
157 fn default() -> Self {
158 Self {
159 prefix: Prefix::new(),
160 node: Node::default(),
161 }
162 }
163}
164
165impl<E: NodeEntry> Fork<E> {
166 pub fn prefix(&self) -> &[u8] {
168 &self.prefix
169 }
170
171 pub const fn node(&self) -> &Node<E> {
173 &self.node
174 }
175
176 pub const fn node_mut(&mut self) -> &mut Node<E> {
178 &mut self.node
179 }
180}
181
182fn common_prefix_len(a: &[u8], b: &[u8]) -> usize {
184 a.iter().zip(b.iter()).take_while(|(x, y)| x == y).count()
185}
186
187impl<E: NodeEntry> Node<E> {
188 pub fn new_unencrypted() -> Self {
190 Self {
191 obfuscation_key: ObfuscationKey::ZERO,
192 ..Default::default()
193 }
194 }
195
196 pub fn from_reference(reference: ChunkAddress) -> Self {
198 Self {
199 reference: Some(reference),
200 ..Default::default()
201 }
202 }
203
204 pub const fn entry(&self) -> Option<&E> {
206 self.entry.as_ref()
207 }
208
209 pub const fn metadata(&self) -> &BTreeMap<String, String> {
211 &self.metadata
212 }
213
214 pub(crate) const fn metadata_mut(&mut self) -> &mut BTreeMap<String, String> {
216 &mut self.metadata
217 }
218
219 pub const fn reference(&self) -> Option<&ChunkAddress> {
221 self.reference.as_ref()
222 }
223
224 pub const fn forks(&self) -> &BTreeMap<u8, Fork<E>> {
226 &self.forks
227 }
228
229 pub const fn obfuscation_key(&self) -> &ObfuscationKey {
231 &self.obfuscation_key
232 }
233
234 pub const fn is_value(&self) -> bool {
236 self.node_type.contains(NodeType::VALUE)
237 }
238
239 pub(crate) const fn make_value(&mut self) {
241 self.node_type = self.node_type.union(NodeType::VALUE);
242 }
243
244 pub const fn is_edge(&self) -> bool {
246 self.node_type.contains(NodeType::EDGE)
247 }
248
249 pub(crate) const fn make_edge(&mut self) {
251 self.node_type = self.node_type.union(NodeType::EDGE);
252 }
253
254 pub const fn is_with_path_separator(&self) -> bool {
256 self.node_type.contains(NodeType::PATH_SEPARATOR)
257 }
258
259 pub const fn is_with_metadata(&self) -> bool {
261 self.node_type.contains(NodeType::METADATA)
262 }
263
264 pub(crate) const fn make_with_metadata(&mut self) {
266 self.node_type = self.node_type.union(NodeType::METADATA);
267 }
268
269 fn update_is_with_path_separator(&mut self, path: &[u8]) {
270 let sep = PATH_SEPARATOR.as_bytes()[0];
271 if path.iter().skip(1).any(|&b| b == sep) {
272 self.node_type = self.node_type.union(NodeType::PATH_SEPARATOR);
273 } else {
274 self.node_type = self.node_type.difference(NodeType::PATH_SEPARATOR);
275 }
276 }
277
278 pub(crate) const fn mark_dirty(&mut self) {
280 self.reference = None;
281 }
282
283 async fn ensure_loaded<S: ChunkGet<BS>, const BS: usize>(&mut self, store: &S) -> Result<()> {
285 if !self.loaded {
286 self.load(store).await?;
287 }
288 Ok(())
289 }
290
291 pub(crate) async fn load<S: ChunkGet<BS>, const BS: usize>(&mut self, store: &S) -> Result<()> {
293 let address = match self.reference {
294 Some(addr) => addr,
295 None => {
296 self.loaded = true;
297 return Ok(());
298 }
299 };
300
301 let chunk = store
302 .get(&address)
303 .await
304 .map_err(|e| MantarayError::StoreGet {
305 source: std::sync::Arc::new(e),
306 })?;
307 let mut loaded = Self::try_from(chunk.data().as_ref())?;
308 loaded.reference = Some(address);
309 loaded.node_type |= self.node_type;
312 loaded.metadata = core::mem::take(&mut self.metadata);
313 *self = loaded;
314 Ok(())
315 }
316
317 pub(crate) async fn lookup_node<S: ChunkGet<BS>, const BS: usize>(
319 &mut self,
320 path: &[u8],
321 store: &S,
322 ) -> Result<&mut Self> {
323 let mut current = self;
325 let mut rest = path;
326 loop {
327 current.ensure_loaded(store).await?;
328
329 if rest.is_empty() {
330 return Ok(current);
331 }
332
333 let first = rest[0];
334 let reference = current.reference;
335 let fork = current
336 .forks
337 .get_mut(&first)
338 .ok_or(MantarayError::NoForkFound { reference })?;
339
340 let c = common_prefix_len(&fork.prefix, rest);
341 if c != fork.prefix.len() {
342 return Err(MantarayError::NoForkFound { reference });
343 }
344
345 current = &mut fork.node;
346 rest = &rest[c..];
347 }
348 }
349
350 #[cfg(test)]
352 pub(crate) async fn lookup<S: ChunkGet<BS>, const BS: usize>(
353 &mut self,
354 path: &[u8],
355 store: &S,
356 ) -> Result<Option<&E>> {
357 let node = self.lookup_node(path, store).await?;
358 if !node.is_value() && !path.is_empty() {
359 return Err(MantarayError::NoEntryFound {
360 reference: node.reference,
361 });
362 }
363 Ok(node.entry.as_ref())
364 }
365
366 pub(crate) fn add<'a, S: ChunkGet<BS>, const BS: usize>(
371 &'a mut self,
372 path: &'a [u8],
373 entry: Option<E>,
374 metadata: BTreeMap<String, String>,
375 store: &'a S,
376 ) -> RecurseFuture<'a>
377 where
378 E: MaybeSend,
379 {
380 Box::pin(async move {
381 if path.is_empty() {
383 self.entry = entry;
384 self.make_value();
385
386 if !metadata.is_empty() {
387 self.metadata = metadata;
388 self.make_with_metadata();
389 }
390
391 self.mark_dirty();
392 return Ok(());
393 }
394
395 if !self.loaded {
397 self.load(store).await?;
398 self.mark_dirty();
399 }
400
401 if !self.forks.contains_key(&path[0]) {
402 let mut nn = Self {
404 obfuscation_key: self.obfuscation_key,
405 ..Default::default()
406 };
407
408 if path.len() > PREFIX_MAX_LEN {
409 let (prefix, rest) = path.split_at(PREFIX_MAX_LEN);
410 nn.add(rest, entry, metadata, store).await?;
411 nn.update_is_with_path_separator(prefix);
412 self.forks.insert(
413 path[0],
414 Fork {
415 prefix: Prefix::from_slice(prefix),
416 node: nn,
417 },
418 );
419 self.make_edge();
420 return Ok(());
421 }
422
423 nn.entry = entry;
424 if !metadata.is_empty() {
425 nn.metadata = metadata;
426 nn.make_with_metadata();
427 }
428 nn.make_value();
429 nn.update_is_with_path_separator(path);
430
431 self.forks.insert(
432 path[0],
433 Fork {
434 prefix: Prefix::from_slice(path),
435 node: nn,
436 },
437 );
438 self.make_edge();
439 return Ok(());
440 }
441
442 let fork = self.forks.get(&path[0]).expect("checked above");
444 let c = common_prefix_len(&fork.prefix, path);
445 let rest = Prefix::from_slice(&fork.prefix[c..]);
446 let common_prefix = Prefix::from_slice(&fork.prefix[..c]);
447
448 let old_fork = self.forks.remove(&path[0]).expect("checked above");
450
451 let mut nn = if rest.is_empty() {
452 old_fork.node
453 } else {
454 let mut intermediate = Self {
456 obfuscation_key: self.obfuscation_key,
457 ..Default::default()
458 };
459
460 let mut old_fork_node = old_fork.node;
461 old_fork_node.update_is_with_path_separator(&rest);
462 intermediate.forks.insert(
463 rest[0],
464 Fork {
465 prefix: rest,
466 node: old_fork_node,
467 },
468 );
469 intermediate.make_edge();
470
471 if c == path.len() {
472 intermediate.make_value();
473 }
474 intermediate
475 };
476
477 nn.update_is_with_path_separator(path);
478 nn.add(&path[c..], entry, metadata, store).await?;
479
480 self.forks.insert(
481 path[0],
482 Fork {
483 prefix: common_prefix,
484 node: nn,
485 },
486 );
487 self.make_edge();
488
489 Ok(())
490 })
491 }
492
493 pub(crate) fn remove<'a, S: ChunkGet<BS>, const BS: usize>(
497 &'a mut self,
498 path: &'a [u8],
499 store: &'a S,
500 ) -> RecurseFuture<'a>
501 where
502 E: MaybeSend,
503 {
504 Box::pin(async move {
505 if path.is_empty() {
506 return Err(MantarayError::EmptyPath);
507 }
508
509 self.ensure_loaded(store).await?;
510
511 let first = path[0];
512
513 let prefix = match self.forks.get(&first) {
515 Some(f) => f.prefix.clone(),
516 None => {
517 return Err(MantarayError::PathPrefixNotFound {
518 prefix: String::from_utf8_lossy(&[first]).to_string(),
519 });
520 }
521 };
522
523 if !path.starts_with(&prefix) {
524 return Err(MantarayError::PathPrefixNotFound {
525 prefix: String::from_utf8_lossy(path).to_string(),
526 });
527 }
528
529 let rest = &path[prefix.len()..];
530 let result = if rest.is_empty() {
531 self.forks.remove(&first);
532 Ok(())
533 } else {
534 let fork = self.forks.get_mut(&first).expect("checked above");
535 fork.node.remove(rest, store).await
536 };
537
538 self.mark_dirty();
540 result
541 })
542 }
543
544 pub(crate) async fn has_prefix<S: ChunkGet<BS>, const BS: usize>(
546 &mut self,
547 path: &[u8],
548 store: &S,
549 ) -> Result<bool> {
550 let mut current = self;
552 let mut rest = path;
553 loop {
554 if rest.is_empty() {
555 return Ok(true);
556 }
557
558 current.ensure_loaded(store).await?;
559
560 let fork = match current.forks.get_mut(&rest[0]) {
561 Some(f) => f,
562 None => return Ok(false),
563 };
564
565 let c = common_prefix_len(&fork.prefix, rest);
566
567 if c == fork.prefix.len() {
568 current = &mut fork.node;
569 rest = &rest[c..];
570 continue;
571 }
572
573 if fork.prefix.starts_with(rest) {
574 return Ok(true);
575 }
576
577 return Ok(false);
578 }
579 }
580
581 pub(crate) async fn save<S: ChunkPut<BS>, const BS: usize>(&mut self, store: &S) -> Result<()> {
587 if self.reference.is_some() {
588 return Ok(());
589 }
590
591 struct SaveFrame<E: NodeEntry> {
592 node: *mut Node<E>,
594 keys: Vec<u8>,
596 key_idx: usize,
598 }
599
600 let mut stack: Vec<SaveFrame<E>> = vec![SaveFrame {
601 node: self as *mut Self,
602 keys: self.forks.keys().copied().collect(),
603 key_idx: 0,
604 }];
605
606 while let Some(frame) = stack.last_mut() {
607 let node = unsafe { &mut *frame.node };
611
612 if frame.key_idx < frame.keys.len() {
613 let key = frame.keys[frame.key_idx];
614 frame.key_idx += 1;
615 let child = node.forks.get_mut(&key).expect("key from this node");
616 if child.node.reference.is_none() {
617 let child_ptr = &mut child.node as *mut Self;
618 let child_keys = child.node.forks.keys().copied().collect();
619 stack.push(SaveFrame {
620 node: child_ptr,
621 keys: child_keys,
622 key_idx: 0,
623 });
624 }
625 continue;
626 }
627
628 let data = Vec::<u8>::try_from(&*node)?;
630 let chunk = ContentChunk::<BS>::new(Bytes::from(data))?;
631 let address = *chunk.address();
632 store
633 .put(chunk.into())
634 .await
635 .map_err(|e| MantarayError::StorePut {
636 source: std::sync::Arc::new(e),
637 })?;
638 node.reference = Some(address);
639 node.forks.clear();
640 node.loaded = false;
641 stack.pop();
642 }
643
644 Ok(())
645 }
646
647 pub(crate) async fn walk<S: ChunkGet<BS>, const BS: usize, F>(
649 &mut self,
650 store: &S,
651 f: &mut F,
652 ) -> Result<()>
653 where
654 F: FnMut(&[u8], &Self) -> Result<()>,
655 {
656 let mut path_buf = Vec::new();
657 walk_inner(&mut path_buf, self, store, f).await
658 }
659
660 pub(crate) async fn walk_from<S: ChunkGet<BS>, const BS: usize, F>(
662 &mut self,
663 root: &[u8],
664 store: &S,
665 f: &mut F,
666 ) -> Result<()>
667 where
668 F: FnMut(&[u8], &Self) -> Result<()>,
669 {
670 let mut path_buf = root.to_vec();
671 if root.is_empty() {
672 return walk_inner(&mut path_buf, self, store, f).await;
673 }
674
675 let target = self.lookup_node(root, store).await?;
676 walk_inner(&mut path_buf, target, store, f).await
677 }
678}
679
680async fn walk_inner<E: NodeEntry, S: ChunkGet<BS>, const BS: usize, F>(
684 path_buf: &mut Vec<u8>,
685 node: &mut Node<E>,
686 store: &S,
687 f: &mut F,
688) -> Result<()>
689where
690 F: FnMut(&[u8], &Node<E>) -> Result<()>,
691{
692 struct WalkFrame {
693 node: *mut (),
695 path_len_before: usize,
697 keys: Vec<u8>,
699 key_idx: usize,
701 }
702
703 node.ensure_loaded(store).await?;
704 f(path_buf, node)?;
705
706 let mut stack: Vec<WalkFrame> = vec![WalkFrame {
707 node: (node as *mut Node<E>).cast::<()>(),
708 path_len_before: path_buf.len(),
709 keys: node.forks.keys().copied().collect(),
710 key_idx: 0,
711 }];
712
713 while let Some(frame) = stack.last_mut() {
714 if frame.key_idx >= frame.keys.len() {
715 path_buf.truncate(frame.path_len_before);
716 stack.pop();
717 continue;
718 }
719
720 let key = frame.keys[frame.key_idx];
721 frame.key_idx += 1;
722
723 let parent = unsafe { &mut *frame.node.cast::<Node<E>>() };
727 let reference = parent.reference;
728 let fork = parent
729 .forks
730 .get_mut(&key)
731 .ok_or(MantarayError::NoForkFound { reference })?;
732
733 let prev_len = path_buf.len();
734 path_buf.extend_from_slice(&fork.prefix);
735
736 let child = &mut fork.node;
737 child.ensure_loaded(store).await?;
738 f(path_buf, child)?;
739
740 let child_ptr = (child as *mut Node<E>).cast::<()>();
741 let child_keys = child.forks.keys().copied().collect();
742 stack.push(WalkFrame {
743 node: child_ptr,
744 path_len_before: prev_len,
745 keys: child_keys,
746 key_idx: 0,
747 });
748 }
749
750 Ok(())
751}
752
753#[cfg(test)]
754mod tests {
755 use super::*;
756 use nectar_primitives::bmt::DEFAULT_BODY_SIZE;
757 use nectar_primitives::store::{MemoryStore, NullLoader};
758
759 struct TestCase {
760 _name: &'static str,
761 items: Vec<&'static str>,
762 }
763
764 #[derive(Default, Clone)]
765 struct RemoveTestCaseItem {
766 path: String,
767 metadata: BTreeMap<String, String>,
768 }
769
770 #[derive(Clone)]
771 struct RemoveTestCase {
772 _name: &'static str,
773 items: Vec<RemoveTestCaseItem>,
774 remove: Vec<String>,
775 }
776
777 #[derive(Clone)]
778 struct HasPrefixTestCase {
779 _name: &'static str,
780 paths: Vec<String>,
781 test_paths: Vec<String>,
782 should_exist: Vec<bool>,
783 }
784
785 fn test_case_data() -> [TestCase; 6] {
786 [
787 TestCase {
788 _name: "a",
789 items: vec![
790 "aaaaaa", "aaaaab", "abbbb", "abbba", "bbbbba", "bbbaaa", "bbbaab", "aa", "b",
791 ],
792 },
793 TestCase {
794 _name: "simple",
795 items: vec!["/", "index.html", "img/1.png", "img/2.png", "robots.txt"],
796 },
797 TestCase {
798 _name: "nested-value-node-is-recognized",
799 items: vec![
800 "..............................@",
801 "..............................",
802 ],
803 },
804 TestCase {
805 _name: "nested-prefix-is-not-collapsed",
806 items: vec![
807 "index.html",
808 "img/1.png",
809 "img/2/test1.png",
810 "img/2/test2.png",
811 "robots.txt",
812 ],
813 },
814 TestCase {
815 _name: "conflicting-path",
816 items: vec!["app.js.map", "app.js"],
817 },
818 TestCase {
819 _name: "spa-website",
820 items: vec![
821 "css/",
822 "css/app.css",
823 "favicon.ico",
824 "img/",
825 "img/logo.png",
826 "index.html",
827 "js/",
828 "js/chunk-vendors.js.map",
829 "js/chunk-vendors.js",
830 "js/app.js.map",
831 "js/app.js",
832 ],
833 },
834 ]
835 }
836
837 fn remove_test_case_data() -> Vec<RemoveTestCase> {
838 vec![
839 RemoveTestCase {
840 _name: "simple",
841 items: vec![
842 RemoveTestCaseItem {
843 path: "/".to_string(),
844 metadata: serde_json::from_str(r#"{"index-document": "index.html"}"#)
845 .unwrap(),
846 },
847 RemoveTestCaseItem {
848 path: "index.html".to_string(),
849 ..Default::default()
850 },
851 RemoveTestCaseItem {
852 path: "img/1.png".to_string(),
853 ..Default::default()
854 },
855 RemoveTestCaseItem {
856 path: "img/2.png".to_string(),
857 ..Default::default()
858 },
859 RemoveTestCaseItem {
860 path: "robots.txt".to_string(),
861 ..Default::default()
862 },
863 ],
864 remove: vec!["img/2.png".to_string()],
865 },
866 RemoveTestCase {
867 _name: "nested-prefix-is-not-collapsed",
868 items: vec![
869 RemoveTestCaseItem {
870 path: "index.html".to_string(),
871 ..Default::default()
872 },
873 RemoveTestCaseItem {
874 path: "img/1.png".to_string(),
875 ..Default::default()
876 },
877 RemoveTestCaseItem {
878 path: "img/2/test1.png".to_string(),
879 ..Default::default()
880 },
881 RemoveTestCaseItem {
882 path: "img/2/test2.png".to_string(),
883 ..Default::default()
884 },
885 RemoveTestCaseItem {
886 path: "robots.txt".to_string(),
887 ..Default::default()
888 },
889 ],
890 remove: vec!["img/2/test1.png".to_string()],
891 },
892 ]
893 }
894
895 fn has_prefix_test_case_data() -> Vec<HasPrefixTestCase> {
896 vec![
897 HasPrefixTestCase {
898 _name: "simple",
899 paths: vec![
900 "index.html".to_string(),
901 "img/1.png".to_string(),
902 "img/2.png".to_string(),
903 "robots.txt".to_string(),
904 ],
905 test_paths: vec!["img/".to_string(), "images/".to_string()],
906 should_exist: vec![true, false],
907 },
908 HasPrefixTestCase {
909 _name: "nested-single",
910 paths: vec!["some-path/file.ext".to_string()],
911 test_paths: vec![
912 "some-path".to_string(),
913 "some-path/file".to_string(),
914 "some-other-path/".to_string(),
915 ],
916 should_exist: vec![true, true, false],
917 },
918 ]
919 }
920
921 use futures::executor::block_on;
922
923 const NL: NullLoader = NullLoader;
924 const BS: usize = DEFAULT_BODY_SIZE;
925
926 fn make_entry(s: &str) -> ChunkAddress {
928 let bytes = s.as_bytes();
929 let mut buf = [0u8; 32];
930 let start = 32 - bytes.len();
931 buf[start..].copy_from_slice(bytes);
932 ChunkAddress::from(buf)
933 }
934
935 fn node_add(n: &mut Node, path: &[u8], entry: ChunkAddress, meta: BTreeMap<String, String>) {
937 block_on(n.add::<NullLoader, BS>(path, Some(entry), meta, &NL)).unwrap();
938 }
939
940 fn node_lookup<'n>(n: &'n mut Node, path: &[u8]) -> Result<Option<&'n ChunkAddress>> {
942 block_on(n.lookup::<NullLoader, BS>(path, &NL))
943 }
944
945 fn node_lookup_node<'n>(n: &'n mut Node, path: &[u8]) -> Result<&'n mut Node> {
947 block_on(n.lookup_node::<NullLoader, BS>(path, &NL))
948 }
949
950 fn node_remove(n: &mut Node, path: &[u8]) -> Result<()> {
952 block_on(n.remove::<NullLoader, BS>(path, &NL))
953 }
954
955 fn node_has_prefix(n: &mut Node, path: &[u8]) -> Result<bool> {
957 block_on(n.has_prefix::<NullLoader, BS>(path, &NL))
958 }
959
960 fn node_walk<F>(n: &mut Node, f: &mut F) -> Result<()>
962 where
963 F: FnMut(&[u8], &Node) -> Result<()>,
964 {
965 block_on(n.walk::<NullLoader, BS, _>(&NL, f))
966 }
967
968 fn node_walk_node<F>(n: &mut Node, root: &[u8], f: &mut F) -> Result<()>
970 where
971 F: FnMut(&[u8], &Node) -> Result<()>,
972 {
973 block_on(n.walk_from::<NullLoader, BS, _>(root, &NL, f))
974 }
975
976 #[test]
977 fn nil_path() {
978 let mut n = Node::default();
979 assert!(node_lookup(&mut n, b"").is_ok());
980 }
981
982 #[test]
983 fn add_and_lookup() {
984 let mut n = Node::default();
985 let items = &test_case_data()[0].items;
986
987 for (i, c) in items.iter().enumerate() {
988 let e = make_entry(c);
989 node_add(&mut n, c.as_bytes(), e, BTreeMap::new());
990
991 for &d in items.iter().take(i) {
992 let r = node_lookup(&mut n, d.as_bytes()).unwrap();
993 assert_eq!(r, Some(&make_entry(d)));
994 }
995 }
996 }
997
998 fn run_add_and_lookup_node(items: &[&str]) {
999 let mut n = Node::default();
1000
1001 for (i, c) in items.iter().enumerate() {
1002 let e = make_entry(c);
1003 node_add(&mut n, c.as_bytes(), e, BTreeMap::new());
1004
1005 for &d in items.iter().take(i) {
1006 let node = node_lookup_node(&mut n, d.as_bytes()).unwrap();
1007 assert!(node.is_value());
1008 assert_eq!(node.entry(), Some(&make_entry(d)));
1009 }
1010 }
1011 }
1012
1013 #[test]
1014 fn add_and_lookup_node_a() {
1015 run_add_and_lookup_node(&test_case_data()[0].items);
1016 }
1017
1018 #[test]
1019 fn add_and_lookup_node_simple() {
1020 run_add_and_lookup_node(&test_case_data()[1].items);
1021 }
1022
1023 #[test]
1024 fn add_and_lookup_node_nested_value() {
1025 run_add_and_lookup_node(&test_case_data()[2].items);
1026 }
1027
1028 #[test]
1029 fn add_and_lookup_node_nested_prefix() {
1030 run_add_and_lookup_node(&test_case_data()[3].items);
1031 }
1032
1033 #[test]
1034 fn add_and_lookup_node_conflicting_path() {
1035 run_add_and_lookup_node(&test_case_data()[4].items);
1036 }
1037
1038 #[test]
1039 fn add_and_lookup_node_spa_website() {
1040 run_add_and_lookup_node(&test_case_data()[5].items);
1041 }
1042
1043 fn run_add_and_lookup_with_load_save(items: &[&str]) {
1044 let mut n = Node::default();
1045
1046 for c in items {
1047 let e = make_entry(c);
1048 node_add(&mut n, c.as_bytes(), e, BTreeMap::new());
1049 }
1050
1051 let store = MemoryStore::<{ DEFAULT_BODY_SIZE }>::new();
1052 block_on(n.save(&store)).unwrap();
1053
1054 let mut n2: Node = Node::from_reference(n.reference.unwrap());
1055
1056 for &d in items {
1057 let node = block_on(n2.lookup_node(d.as_bytes(), &store)).unwrap();
1058 assert!(node.is_value());
1059 assert_eq!(node.entry(), Some(&make_entry(d)));
1060 }
1061 }
1062
1063 #[test]
1064 fn add_and_lookup_with_load_save_a() {
1065 run_add_and_lookup_with_load_save(&test_case_data()[0].items);
1066 }
1067
1068 #[test]
1069 fn add_and_lookup_with_load_save_simple() {
1070 run_add_and_lookup_with_load_save(&test_case_data()[1].items);
1071 }
1072
1073 #[test]
1074 fn add_and_lookup_with_load_save_nested_value() {
1075 run_add_and_lookup_with_load_save(&test_case_data()[2].items);
1076 }
1077
1078 #[test]
1079 fn add_and_lookup_with_load_save_nested_prefix() {
1080 run_add_and_lookup_with_load_save(&test_case_data()[3].items);
1081 }
1082
1083 #[test]
1084 fn add_and_lookup_with_load_save_conflicting_path() {
1085 run_add_and_lookup_with_load_save(&test_case_data()[4].items);
1086 }
1087
1088 #[test]
1089 fn add_and_lookup_with_load_save_spa_website() {
1090 run_add_and_lookup_with_load_save(&test_case_data()[5].items);
1091 }
1092
1093 fn run_remove(tc: RemoveTestCase) {
1094 let mut n = Node::default();
1095
1096 for (i, c) in tc.items.iter().enumerate() {
1097 let e = make_entry(&c.path);
1098 node_add(&mut n, c.path.as_bytes(), e, c.metadata.clone());
1099
1100 for item in tc.items.iter().take(i) {
1101 let r = node_lookup(&mut n, item.path.as_bytes()).unwrap();
1102 assert_eq!(r, Some(&make_entry(&item.path)));
1103 }
1104 }
1105
1106 for c in &tc.remove {
1107 node_remove(&mut n, c.as_bytes()).unwrap();
1108 assert!(node_lookup(&mut n, c.as_bytes()).is_err());
1109 }
1110 }
1111
1112 #[test]
1113 fn remove_simple() {
1114 run_remove(remove_test_case_data()[0].clone());
1115 }
1116
1117 #[test]
1118 fn remove_nested_prefix() {
1119 run_remove(remove_test_case_data()[1].clone());
1120 }
1121
1122 fn run_has_prefix(tc: HasPrefixTestCase) {
1123 let mut n = Node::default();
1124
1125 for c in &tc.paths {
1126 let e = make_entry(c);
1127 node_add(&mut n, c.as_bytes(), e, BTreeMap::default());
1128 }
1129
1130 for (i, test_prefix) in tc.test_paths.iter().enumerate() {
1131 assert_eq!(
1132 node_has_prefix(&mut n, test_prefix.as_bytes()).unwrap(),
1133 tc.should_exist[i],
1134 );
1135 }
1136 }
1137
1138 #[test]
1139 fn has_prefix_simple() {
1140 run_has_prefix(has_prefix_test_case_data()[0].clone());
1141 }
1142
1143 #[test]
1144 fn has_prefix_nested_single() {
1145 run_has_prefix(has_prefix_test_case_data()[1].clone());
1146 }
1147
1148 fn run_persist_remove(tc: RemoveTestCase) {
1151 let store = MemoryStore::<{ DEFAULT_BODY_SIZE }>::new();
1152
1153 let mut n = Node::default();
1155 for c in &tc.items {
1156 let e = make_entry(&c.path);
1157 block_on(n.add(c.path.as_bytes(), Some(e), c.metadata.clone(), &store)).unwrap();
1158 }
1159 block_on(n.save(&store)).unwrap();
1160 let ref_ = n.reference.unwrap();
1161
1162 let mut nn: Node = Node::from_reference(ref_);
1164 for path in &tc.remove {
1165 block_on(nn.remove(path.as_bytes(), &store)).unwrap();
1166 }
1167 block_on(nn.save(&store)).unwrap();
1168 let ref2 = nn.reference.unwrap();
1169
1170 let mut nnn: Node = Node::from_reference(ref2);
1172 for path in &tc.remove {
1173 let result = block_on(nnn.lookup_node(path.as_bytes(), &store));
1174 assert!(
1175 result.is_err(),
1176 "expected removed path '{path}' to be not found"
1177 );
1178 }
1179 }
1180
1181 #[test]
1182 fn persist_remove_simple() {
1183 run_persist_remove(remove_test_case_data()[0].clone());
1184 }
1185
1186 #[test]
1187 fn persist_remove_nested_prefix() {
1188 run_persist_remove(remove_test_case_data()[1].clone());
1189 }
1190
1191 fn make_entry_bytes(s: &[u8]) -> ChunkAddress {
1192 let mut buf = [0u8; 32];
1193 let start = 32 - s.len();
1194 buf[start..].copy_from_slice(s);
1195 ChunkAddress::from(buf)
1196 }
1197
1198 #[test]
1199 fn walk_visits_all_nodes() {
1200 let mut root = Node::default();
1201
1202 let paths = &["index.html", "img/1.png", "img/2.png", "robots.txt"];
1203 for &p in paths {
1204 let entry = make_entry_bytes(p.as_bytes());
1205 node_add(&mut root, p.as_bytes(), entry, BTreeMap::new());
1206 }
1207
1208 let mut visited: Vec<(Vec<u8>, bool)> = Vec::new();
1209 node_walk(&mut root, &mut |path, node| {
1210 visited.push((path.to_vec(), node.is_value()));
1211 Ok(())
1212 })
1213 .unwrap();
1214
1215 for &p in paths {
1216 assert!(
1217 visited
1218 .iter()
1219 .any(|(vp, is_val)| vp == p.as_bytes() && *is_val),
1220 "path {p} not visited as value"
1221 );
1222 }
1223 }
1224
1225 #[test]
1226 fn walk_node_exact_order() {
1227 let to_add: &[&[u8]] = &[
1228 b"index.html.backup",
1229 b"index.html",
1230 b"img/test/oho.png",
1231 b"img/test/old/test.png.backup",
1232 b"img/test/old/test.png",
1233 b"img/2.png",
1234 b"img/1.png",
1235 b"robots.txt",
1236 ];
1237
1238 let expected: &[&[u8]] = &[
1239 b"",
1240 b"i",
1241 b"img/",
1242 b"img/1.png",
1243 b"img/2.png",
1244 b"img/test/o",
1245 b"img/test/oho.png",
1246 b"img/test/old/test.png",
1247 b"img/test/old/test.png.backup",
1248 b"index.html",
1249 b"index.html.backup",
1250 b"robots.txt",
1251 ];
1252
1253 let mut n = Node::default();
1254 for &path in to_add {
1255 let entry = make_entry_bytes(path);
1256 node_add(&mut n, path, entry, BTreeMap::new());
1257 }
1258
1259 let mut walked: Vec<Vec<u8>> = Vec::new();
1260 node_walk_node(&mut n, b"", &mut |path, _node| {
1261 walked.push(path.to_vec());
1262 Ok(())
1263 })
1264 .unwrap();
1265
1266 assert_eq!(
1267 walked.len(),
1268 expected.len(),
1269 "expected {} nodes, got {}",
1270 expected.len(),
1271 walked.len()
1272 );
1273
1274 for (i, (got, &want)) in walked.iter().zip(expected.iter()).enumerate() {
1275 assert_eq!(
1276 got.as_slice(),
1277 want,
1278 "walk step {i}: expected {:?}, got {:?}",
1279 core::str::from_utf8(want).unwrap_or("<non-utf8>"),
1280 core::str::from_utf8(got).unwrap_or("<non-utf8>"),
1281 );
1282 }
1283 }
1284
1285 #[test]
1286 fn walk_node_from_subtree() {
1287 let to_add: &[&[u8]] = &[b"index.html", b"img/1.png", b"img/2.png", b"robots.txt"];
1288
1289 let mut n = Node::default();
1290 for &path in to_add {
1291 let entry = make_entry_bytes(path);
1292 node_add(&mut n, path, entry, BTreeMap::new());
1293 }
1294
1295 let mut walked: Vec<Vec<u8>> = Vec::new();
1296 node_walk_node(&mut n, b"img/", &mut |path, _node| {
1297 walked.push(path.to_vec());
1298 Ok(())
1299 })
1300 .unwrap();
1301
1302 assert!(walked.iter().any(|p| p == b"img/1.png"));
1303 assert!(walked.iter().any(|p| p == b"img/2.png"));
1304 assert!(!walked.iter().any(|p| p == b"index.html"));
1305 assert!(!walked.iter().any(|p| p == b"robots.txt"));
1306 }
1307
1308 #[test]
1309 fn walk_node_exact_order_with_load_save() {
1310 let to_add: &[&[u8]] = &[
1311 b"index.html.backup",
1312 b"index.html",
1313 b"img/test/oho.png",
1314 b"img/test/old/test.png.backup",
1315 b"img/test/old/test.png",
1316 b"img/2.png",
1317 b"img/1.png",
1318 b"robots.txt",
1319 ];
1320
1321 let expected: &[&[u8]] = &[
1322 b"",
1323 b"i",
1324 b"img/",
1325 b"img/1.png",
1326 b"img/2.png",
1327 b"img/test/o",
1328 b"img/test/oho.png",
1329 b"img/test/old/test.png",
1330 b"img/test/old/test.png.backup",
1331 b"index.html",
1332 b"index.html.backup",
1333 b"robots.txt",
1334 ];
1335
1336 let mut n = Node::default();
1337 for &path in to_add {
1338 let entry = make_entry_bytes(path);
1339 node_add(&mut n, path, entry, BTreeMap::new());
1340 }
1341
1342 let store = MemoryStore::<{ DEFAULT_BODY_SIZE }>::new();
1343 block_on(n.save(&store)).unwrap();
1344
1345 let mut n2: Node = Node::from_reference(n.reference.unwrap());
1346
1347 let mut walked: Vec<Vec<u8>> = Vec::new();
1348 block_on(n2.walk_from(b"", &store, &mut |path: &[u8], _node: &Node| {
1349 walked.push(path.to_vec());
1350 Ok(())
1351 }))
1352 .unwrap();
1353
1354 assert_eq!(
1355 walked.len(),
1356 expected.len(),
1357 "expected {} nodes, got {}",
1358 expected.len(),
1359 walked.len()
1360 );
1361
1362 for (i, (got, &want)) in walked.iter().zip(expected.iter()).enumerate() {
1363 assert_eq!(
1364 got.as_slice(),
1365 want,
1366 "walk step {i}: expected {:?}, got {:?}",
1367 core::str::from_utf8(want).unwrap_or("<non-utf8>"),
1368 core::str::from_utf8(got).unwrap_or("<non-utf8>"),
1369 );
1370 }
1371 }
1372}