1use std::collections::BTreeMap;
4
5use crate::error::{MantarayError, Result};
6use crate::mode::NodeEntry;
7use crate::node::{Fork, Node, NodeType, Prefix};
8use crate::obfuscation::ObfuscationKey;
9
10use alloy_primitives::{U256, hex};
11use nectar_primitives::chunk::ChunkAddress;
12
13enum VersionHash {
15 V01,
16 V02,
17}
18
19impl VersionHash {
20 const SIZE: usize = 31;
22
23 const V01_BYTES: [u8; Self::SIZE] =
24 hex!("025184789d63635766d78c41900196b57d7400875ebe4d9b5d1e76bd9652a9");
25 const V02_BYTES: [u8; Self::SIZE] =
26 hex!("5768b3b6a7db56d21d1abff40d41cebfc83448fed8d7e9b06ec0d3b073f28f");
27
28 const fn as_bytes(&self) -> &[u8; Self::SIZE] {
29 match self {
30 Self::V01 => &Self::V01_BYTES,
31 Self::V02 => &Self::V02_BYTES,
32 }
33 }
34
35 fn from_bytes(bytes: &[u8]) -> Option<Self> {
36 if bytes == Self::V01_BYTES {
37 Some(Self::V01)
38 } else if bytes == Self::V02_BYTES {
39 Some(Self::V02)
40 } else {
41 None
42 }
43 }
44}
45
46struct NodeHeader;
48
49impl NodeHeader {
50 const SIZE: usize = ObfuscationKey::SIZE + VersionHash::SIZE + size_of::<u8>();
51 const VERSION_HASH_OFFSET: usize = ObfuscationKey::SIZE;
52 const REF_SIZE_OFFSET: usize = ObfuscationKey::SIZE + VersionHash::SIZE;
53}
54
55struct ForkHeader;
57
58impl ForkHeader {
59 const PRE_REFERENCE_SIZE: usize = 32;
61 const PREFIX_OFFSET: usize = size_of::<u8>() + size_of::<u8>();
63 const MAX_PREFIX_LEN: usize = Self::PRE_REFERENCE_SIZE - Self::PREFIX_OFFSET;
65 const METADATA_LEN_SIZE: usize = size_of::<u16>();
67}
68
69const _: () = assert!(NodeHeader::SIZE == 64);
71const _: () = assert!(ForkHeader::PRE_REFERENCE_SIZE == 32);
72const _: () = assert!(ForkHeader::MAX_PREFIX_LEN == Prefix::MAX_LEN);
73const _: () = assert!(ObfuscationKey::SIZE == 32);
74
75#[cfg(test)]
76const VERSION_HASH_01_BYTES: [u8; 32] =
77 hex!("025184789d63635766d78c41900196b57d7400875ebe4d9b5d1e76bd9652a9b7");
78#[cfg(test)]
79const VERSION_HASH_02_BYTES: [u8; 32] =
80 hex!("5768b3b6a7db56d21d1abff40d41cebfc83448fed8d7e9b06ec0d3b073f28f7b");
81
82#[cfg(test)]
83const VERSION_STRING_01: &str = "mantaray:0.1";
84#[cfg(test)]
85const VERSION_STRING_02: &str = "mantaray:0.2";
86
87fn xor_in_place(data: &mut [u8], key: &[u8]) {
89 let key_len = key.len();
90 for (i, byte) in data.iter_mut().enumerate() {
91 *byte ^= key[i % key_len];
92 }
93}
94
95impl<E: NodeEntry> TryFrom<&Node<E>> for Vec<u8> {
96 type Error = MantarayError;
97
98 #[inline]
99 fn try_from(node: &Node<E>) -> Result<Self> {
100 encode_node(node)
101 }
102}
103
104fn encode_node<E: NodeEntry>(node: &Node<E>) -> Result<Vec<u8>> {
105 let ref_size = E::SIZE;
106 let estimated = NodeHeader::SIZE
108 + ref_size
109 + 32
110 + node.forks.len() * (ForkHeader::PRE_REFERENCE_SIZE + ref_size);
111 let mut data = Vec::with_capacity(estimated);
112 data.resize(NodeHeader::SIZE, 0);
113
114 let obfuscation_key = node.obfuscation_key.as_bytes();
118
119 data[..ObfuscationKey::SIZE].copy_from_slice(obfuscation_key);
120
121 data[NodeHeader::VERSION_HASH_OFFSET..NodeHeader::VERSION_HASH_OFFSET + VersionHash::SIZE]
122 .copy_from_slice(VersionHash::V02.as_bytes());
123
124 data[NodeHeader::REF_SIZE_OFFSET] = ref_size as u8;
125
126 match &node.entry {
128 Some(e) => e.write_to(&mut data),
129 None => data.resize(data.len() + ref_size, 0),
130 }
131
132 let mut index = U256::ZERO;
134 for &fork_byte in node.forks.keys() {
135 index.set_bit(fork_byte as usize, true);
136 }
137 data.extend_from_slice(&index.to_le_bytes::<32>());
138
139 for fork in node.forks.values() {
141 fork.encode_into(&mut data)?;
142 }
143
144 xor_in_place(&mut data[ObfuscationKey::SIZE..], obfuscation_key);
146
147 Ok(data)
148}
149
150impl<E: NodeEntry> TryFrom<&[u8]> for Node<E> {
151 type Error = MantarayError;
152
153 fn try_from(value: &[u8]) -> Result<Self> {
154 if value.len() < NodeHeader::SIZE {
155 return Err(MantarayError::DataTooShort);
156 }
157
158 let mut data = value.to_vec();
159
160 let key_bytes: [u8; ObfuscationKey::SIZE] = data[..ObfuscationKey::SIZE]
161 .try_into()
162 .map_err(|_| MantarayError::DataTooShort)?;
163 let obfuscation_key = ObfuscationKey::from(key_bytes);
164
165 xor_in_place(
167 &mut data[ObfuscationKey::SIZE..],
168 obfuscation_key.as_bytes(),
169 );
170
171 let version_hash = &data
172 [NodeHeader::VERSION_HASH_OFFSET..NodeHeader::VERSION_HASH_OFFSET + VersionHash::SIZE];
173
174 let mut node = match VersionHash::from_bytes(version_hash) {
175 Some(VersionHash::V01) => decode_v01::<E>(&data)?,
176 Some(VersionHash::V02) => decode_v02::<E>(&data)?,
177 None => return Err(MantarayError::InvalidVersionHash),
178 };
179
180 node.obfuscation_key = obfuscation_key;
181 node.loaded = true;
182 Ok(node)
183 }
184}
185
186fn decode_empty_terminal_node<E: NodeEntry>(data: &[u8]) -> Result<Node<E>> {
215 let bitfield_start = NodeHeader::SIZE;
216 let bitfield_end = bitfield_start + 32;
217 if data.len() < bitfield_end {
218 return Err(MantarayError::DataTooShort);
219 }
220 if data[bitfield_start..bitfield_end].iter().any(|&b| b != 0) {
221 return Err(MantarayError::EntrySizeMismatch {
222 expected: E::SIZE,
223 actual: 0,
224 });
225 }
226 Ok(Node {
227 entry: None,
228 forks: BTreeMap::new(),
229 ..Default::default()
230 })
231}
232
233fn decode_v01<E: NodeEntry>(data: &[u8]) -> Result<Node<E>> {
234 let ref_bytes_size = data[NodeHeader::REF_SIZE_OFFSET] as usize;
235 if ref_bytes_size == 0 {
237 return decode_empty_terminal_node::<E>(data);
238 }
239 if ref_bytes_size != E::SIZE {
240 return Err(MantarayError::EntrySizeMismatch {
241 expected: E::SIZE,
242 actual: ref_bytes_size,
243 });
244 }
245
246 let entry_bytes = &data[NodeHeader::SIZE..NodeHeader::SIZE + ref_bytes_size];
247 let entry = if entry_bytes.iter().all(|&b| b == 0) {
248 None
249 } else {
250 Some(E::try_from_bytes(entry_bytes)?)
251 };
252
253 let mut offset = NodeHeader::SIZE + ref_bytes_size;
254 let index = U256::from_le_slice(&data[offset..offset + 32]);
255 offset += 32;
256
257 let mut forks = BTreeMap::new();
258 for b in 0..=u8::MAX {
259 if index.bit(b as usize) {
260 let end = offset + ForkHeader::PRE_REFERENCE_SIZE + ref_bytes_size;
261 if data.len() < end {
262 return Err(MantarayError::InsufficientForkBytes {
263 expected: end,
264 actual: data.len(),
265 byte_index: b as usize,
266 });
267 }
268
269 let mut fork = Fork::default();
270 fork.decode_v01(&data[offset..end])?;
271 forks.insert(b, fork);
272 offset = end;
273 }
274 }
275
276 Ok(Node {
277 entry,
278 forks,
279 ..Default::default()
280 })
281}
282
283fn decode_v02<E: NodeEntry>(data: &[u8]) -> Result<Node<E>> {
284 let ref_bytes_size = data[NodeHeader::REF_SIZE_OFFSET] as usize;
285 if ref_bytes_size == 0 {
287 return decode_empty_terminal_node::<E>(data);
288 }
289 if ref_bytes_size != E::SIZE {
290 return Err(MantarayError::EntrySizeMismatch {
291 expected: E::SIZE,
292 actual: ref_bytes_size,
293 });
294 }
295
296 let entry_bytes = &data[NodeHeader::SIZE..NodeHeader::SIZE + ref_bytes_size];
297 let entry = if entry_bytes.iter().all(|&b| b == 0) {
298 None
299 } else {
300 Some(E::try_from_bytes(entry_bytes)?)
301 };
302
303 let mut offset = NodeHeader::SIZE + ref_bytes_size;
304 let mut node_type = NodeType::empty();
305
306 if data[offset..offset + 32].iter().any(|&b| b != 0) {
308 node_type |= NodeType::EDGE;
309 }
310
311 let index = U256::from_le_slice(&data[offset..offset + 32]);
312 offset += 32;
313
314 let mut forks = BTreeMap::new();
315 for b in 0..=u8::MAX {
316 if index.bit(b as usize) {
317 let mut fork = Fork::default();
318
319 if data.len() < offset + 1 {
320 return Err(MantarayError::InsufficientForkBytes {
321 expected: offset + 1,
322 actual: data.len(),
323 byte_index: b as usize,
324 });
325 }
326
327 let fork_node_type = NodeType::from_bits_truncate(data[offset]);
328 let mut node_fork_size = ForkHeader::PRE_REFERENCE_SIZE + ref_bytes_size;
329
330 if fork_node_type.contains(NodeType::METADATA) {
331 if data.len()
332 < offset
333 + ForkHeader::PRE_REFERENCE_SIZE
334 + ref_bytes_size
335 + ForkHeader::METADATA_LEN_SIZE
336 {
337 return Err(MantarayError::InsufficientForkBytes {
338 expected: offset
339 + ForkHeader::PRE_REFERENCE_SIZE
340 + ref_bytes_size
341 + ForkHeader::METADATA_LEN_SIZE,
342 actual: data.len(),
343 byte_index: b as usize,
344 });
345 }
346
347 let metadata_bytes_size = u16::from_be_bytes(
348 data[offset + node_fork_size
349 ..offset + node_fork_size + ForkHeader::METADATA_LEN_SIZE]
350 .try_into()
351 .map_err(|_| MantarayError::DataTooShort)?,
352 ) as usize;
353
354 node_fork_size += ForkHeader::METADATA_LEN_SIZE;
355 node_fork_size += metadata_bytes_size;
356
357 if offset + node_fork_size > data.len() {
358 return Err(MantarayError::InsufficientForkBytes {
359 expected: offset + node_fork_size,
360 actual: data.len(),
361 byte_index: b as usize,
362 });
363 }
364
365 fork.decode_v02(
366 &data[offset..offset + node_fork_size],
367 ref_bytes_size,
368 metadata_bytes_size,
369 )?;
370 } else {
371 if data.len() < offset + ForkHeader::PRE_REFERENCE_SIZE + ref_bytes_size {
372 return Err(MantarayError::InsufficientForkBytes {
373 expected: offset + ForkHeader::PRE_REFERENCE_SIZE + ref_bytes_size,
374 actual: data.len(),
375 byte_index: b as usize,
376 });
377 }
378
379 fork.decode_v01(&data[offset..offset + node_fork_size])?;
380 }
381
382 forks.insert(b, fork);
383 offset += node_fork_size;
384 }
385 }
386
387 Ok(Node {
388 node_type,
389 entry,
390 forks,
391 ..Default::default()
392 })
393}
394
395fn parse_fork_header(data: &[u8]) -> Result<(NodeType, Prefix)> {
397 let node_type = NodeType::from_bits_truncate(data[0]);
398 let prefix_length = data[1] as usize;
399 if prefix_length == 0 || prefix_length > Prefix::MAX_LEN {
400 return Err(MantarayError::InvalidPrefixLength {
401 max: Prefix::MAX_LEN,
402 actual: prefix_length,
403 });
404 }
405 let prefix = Prefix::from_slice(
406 &data[ForkHeader::PREFIX_OFFSET..ForkHeader::PREFIX_OFFSET + prefix_length],
407 );
408 Ok((node_type, prefix))
409}
410
411impl<E: NodeEntry> Fork<E> {
412 fn node_from_ref_bytes(ref_data: &[u8]) -> Result<Node<E>> {
414 if ref_data.len() < 32 {
415 return Err(MantarayError::DataTooShort);
416 }
417 let addr_bytes: [u8; 32] = ref_data[..32]
418 .try_into()
419 .map_err(|_| MantarayError::DataTooShort)?;
420 Ok(Node::from_reference(ChunkAddress::from(addr_bytes)))
421 }
422
423 fn encode_into(&self, data: &mut Vec<u8>) -> Result<()> {
425 data.push(self.node.node_type.bits());
426 data.push(self.prefix.len() as u8);
427
428 data.extend_from_slice(self.prefix.padded_bytes());
430
431 if let Some(addr) = &self.node.reference {
433 data.extend_from_slice(addr.as_bytes());
434 let padding = E::SIZE.saturating_sub(32);
436 if padding > 0 {
437 data.resize(data.len() + padding, 0);
438 }
439 }
440
441 if self.node.is_with_metadata() {
442 let mut metadata_json = serde_json::to_string(&self.node.metadata)
443 .map_err(|e| MantarayError::InvalidMetadata {
444 message: e.to_string(),
445 })?
446 .into_bytes();
447
448 let metadata_bytes_size_with_header =
449 metadata_json.len() + ForkHeader::METADATA_LEN_SIZE;
450
451 let padding = if metadata_bytes_size_with_header < ObfuscationKey::SIZE {
452 ObfuscationKey::SIZE - metadata_bytes_size_with_header
453 } else if metadata_bytes_size_with_header > ObfuscationKey::SIZE {
454 let rem = metadata_bytes_size_with_header % ObfuscationKey::SIZE;
455 if rem == 0 {
456 0
457 } else {
458 ObfuscationKey::SIZE - rem
459 }
460 } else {
461 0
462 };
463
464 metadata_json.resize(metadata_json.len() + padding, 0x0a);
465
466 let metadata_size = metadata_json.len();
467 if metadata_size > u16::MAX as usize {
468 return Err(MantarayError::MetadataTooLarge {
469 max: u16::MAX as usize,
470 actual: metadata_size,
471 });
472 }
473
474 data.extend_from_slice(&(metadata_size as u16).to_be_bytes());
475 data.extend_from_slice(&metadata_json);
476 }
477
478 Ok(())
479 }
480
481 pub(crate) fn decode_v01(&mut self, data: &[u8]) -> Result<()> {
483 let (node_type, prefix) = parse_fork_header(data)?;
484
485 self.prefix = prefix;
486 let ref_data = &data[ForkHeader::PRE_REFERENCE_SIZE..];
487 self.node = Self::node_from_ref_bytes(ref_data)?;
488 self.node.node_type = node_type;
489
490 Ok(())
491 }
492
493 pub(crate) fn decode_v02(
495 &mut self,
496 data: &[u8],
497 ref_bytes_size: usize,
498 metadata_bytes_size: usize,
499 ) -> Result<()> {
500 let (node_type, prefix) = parse_fork_header(data)?;
501
502 self.prefix = prefix;
503 let ref_data =
504 &data[ForkHeader::PRE_REFERENCE_SIZE..ForkHeader::PRE_REFERENCE_SIZE + ref_bytes_size];
505 self.node = Self::node_from_ref_bytes(ref_data)?;
506 self.node.node_type = node_type;
507
508 if metadata_bytes_size > 0 {
509 let metadata_start =
510 ForkHeader::PRE_REFERENCE_SIZE + ref_bytes_size + ForkHeader::METADATA_LEN_SIZE;
511 let metadata_bytes = &data[metadata_start..];
512 self.node.metadata = serde_json::from_slice(metadata_bytes).map_err(|e| {
513 MantarayError::InvalidMetadata {
514 message: e.to_string(),
515 }
516 })?;
517 }
518
519 Ok(())
520 }
521}
522
523#[cfg(test)]
524mod tests {
525 use super::*;
526 use alloy_primitives::hex;
527 use alloy_primitives::utils::keccak256;
528
529 const ENCODED_V01: &str = "52fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64950ac787fbce1061870e8d34e0a638bc7e812c7ca4ebd31d626a572ba47b06f6952fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64952fdfc072102654f163f5f0fa0621d729566c74d10037c4d7bbb0407d1e2c64950fcd3072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64952fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64950f89d6640e3044f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64952fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64850ff9f642182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64952fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64b50fc98072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64952fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64a50ff99622182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64952fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64d";
530 const ENCODED_V02: &str = "52fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64905954fb18659339d0b25e0fb9723d3cd5d528fb3c8d495fd157bd7b7a210496952fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64952fdfc072102654f163f5f0fa0621d729566c74d10037c4d7bbb0407d1e2c64940fcd3072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64952fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64952e3872548ec012a6e123b60f9177017fb12e57732621d2c1ada267adbe8cc4350f89d6640e3044f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64952fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64850ff9f642182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64952fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64b50fc98072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64952fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64a50ff99622182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64952fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64d";
531
532 #[derive(Clone, Default)]
533 struct TestEntry {
534 path: String,
535 metadata: BTreeMap<String, String>,
536 }
537
538 fn test_entries() -> [TestEntry; 5] {
539 [
540 TestEntry {
541 path: "/".to_string(),
542 metadata: serde_json::from_str(r#"{"index-document": "aaaaa"}"#).unwrap(),
543 },
544 TestEntry {
545 path: "aaaaa".to_string(),
546 ..Default::default()
547 },
548 TestEntry {
549 path: "cc".to_string(),
550 ..Default::default()
551 },
552 TestEntry {
553 path: "d".to_string(),
554 ..Default::default()
555 },
556 TestEntry {
557 path: "ee".to_string(),
558 ..Default::default()
559 },
560 ]
561 }
562
563 #[test]
564 fn version_hash_01() {
565 assert_eq!(
566 keccak256(VERSION_STRING_01.as_bytes()),
567 VERSION_HASH_01_BYTES,
568 );
569 }
570
571 #[test]
572 fn version_hash_02() {
573 assert_eq!(
574 keccak256(VERSION_STRING_02.as_bytes()),
575 VERSION_HASH_02_BYTES,
576 );
577 }
578
579 #[test]
580 fn decode_v01() {
581 let data = hex::decode(ENCODED_V01).unwrap();
582 let n = Node::<ChunkAddress>::try_from(data.as_slice()).unwrap();
583
584 let mut expect_bytes = hex::decode(&ENCODED_V01[128..192]).unwrap();
585 xor_in_place(&mut expect_bytes, n.obfuscation_key().as_bytes());
586
587 if expect_bytes.iter().all(|&b| b == 0) {
589 assert!(n.entry().is_none());
590 } else {
591 assert_eq!(n.entry().unwrap().as_bytes(), &expect_bytes[..]);
592 }
593 assert_eq!(test_entries().len(), n.forks().len());
594
595 for entry in test_entries() {
596 let key = entry.path.as_bytes()[0];
597 assert!(n.forks().contains_key(&key));
598 assert_eq!(n.forks()[&key].prefix(), entry.path.as_bytes());
599 }
600 }
601
602 #[test]
603 fn decode_v02() {
604 let data = hex::decode(ENCODED_V02).unwrap();
605 let n = Node::<ChunkAddress>::try_from(data.as_slice()).unwrap();
606
607 let mut expect_bytes = hex::decode(&ENCODED_V02[128..192]).unwrap();
608 xor_in_place(&mut expect_bytes, n.obfuscation_key().as_bytes());
609
610 if expect_bytes.iter().all(|&b| b == 0) {
612 assert!(n.entry().is_none());
613 } else {
614 assert_eq!(n.entry().unwrap().as_bytes(), &expect_bytes[..]);
615 }
616 assert_eq!(test_entries().len(), n.forks().len());
617
618 for entry in test_entries() {
619 let key = entry.path.as_bytes()[0];
620 assert!(n.forks().contains_key(&key));
621 assert_eq!(n.forks()[&key].prefix(), entry.path.as_bytes());
622
623 if !entry.metadata.is_empty() {
624 assert_eq!(n.forks()[&key].node().metadata(), &entry.metadata);
625 }
626 }
627 }
628
629 #[test]
630 fn decode_nil_input() {
631 let result = Node::<ChunkAddress>::try_from([].as_slice());
632 assert!(matches!(result, Err(MantarayError::DataTooShort)));
633 }
634
635 #[test]
636 fn decode_too_short_for_header() {
637 let data = vec![0u8; NodeHeader::SIZE - 1];
638 let result = Node::<ChunkAddress>::try_from(data.as_slice());
639 assert!(matches!(result, Err(MantarayError::DataTooShort)));
640 }
641
642 #[test]
643 fn decode_invalid_version_hash() {
644 let data = vec![0u8; NodeHeader::SIZE];
645 let result = Node::<ChunkAddress>::try_from(data.as_slice());
646 assert!(matches!(result, Err(MantarayError::InvalidVersionHash)));
647 }
648
649 #[test]
653 fn decode_valid_manifest_from_go() {
654 let data = hex::decode(
655 "00000000000000000000000000000000000000000000000000000000000000005768b3b6a7db56d21d1abff40d41cebfc83448fed8d7e9b06ec0d3b073f28f200000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000016012f0000000000000000000000000000000000000000000000000000000000e87f95c3d081c4fede769b6c69e27b435e525cbd25c6715c607e7c531e329639005d7b22776562736974652d696e6465782d646f63756d656e74223a2233356561656538316262363338303436393965633637316265323736326465626665346662643330636461646139303232393239646131613965366134366436227d0a"
656 ).unwrap();
657 assert!(Node::<ChunkAddress>::try_from(data.as_slice()).is_ok());
658 }
659
660 #[test]
663 fn decode_invalid_manifest_size_89() {
664 let data = hex::decode(
665 "00000000000000000000000000000000000000000000000000000000000000005768b3b6a7db56d21d1abff40d41cebfc83448fed8d7e9b06ec0d3b073f28f200000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000016012f0000000000000000000000000000000000000000000000000000000000e87f95c3d081c4fede769b6c69e27b435e525cbd25c6715c607e7c531e32963900597b22776562736974652d696e6465782d646f63756d656e74223a2233356561656538316262363338303436393965633637316265323736326465626665346662643330636461646139303232393239646131613965366134366436227d0a"
666 ).unwrap();
667 assert!(Node::<ChunkAddress>::try_from(data.as_slice()).is_err());
668 }
669
670 #[test]
673 fn decode_invalid_manifest_size_95() {
674 let data = hex::decode(
675 "00000000000000000000000000000000000000000000000000000000000000005768b3b6a7db56d21d1abff40d41cebfc83448fed8d7e9b06ec0d3b073f28f200000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000016012f0000000000000000000000000000000000000000000000000000000000e87f95c3d081c4fede769b6c69e27b435e525cbd25c6715c607e7c531e329639005f7b22776562736974652d696e6465782d646f63756d656e74223a2233356561656538316262363338303436393965633637316265323736326465626665346662643330636461646139303232393239646131613965366134366436227d0a"
676 ).unwrap();
677 assert!(Node::<ChunkAddress>::try_from(data.as_slice()).is_err());
678 }
679
680 #[test]
683 fn decode_invalid_manifest_size_96() {
684 let data = hex::decode(
685 "00000000000000000000000000000000000000000000000000000000000000005768b3b6a7db56d21d1abff40d41cebfc83448fed8d7e9b06ec0d3b073f28f200000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000016012f0000000000000000000000000000000000000000000000000000000000e87f95c3d081c4fede769b6c69e27b435e525cbd25c6715c607e7c531e32963900607b22776562736974652d696e6465782d646f63756d656e74223a2233356561656538316262363338303436393965633637316265323736326465626665346662643330636461646139303232393239646131613965366134366436227d0a"
686 ).unwrap();
687 assert!(Node::<ChunkAddress>::try_from(data.as_slice()).is_err());
688 }
689
690 #[test]
696 fn decode_bee_legacy_ref_size_zero_empty_node() {
697 let mut data = vec![0u8; 96];
699 data[ObfuscationKey::SIZE..ObfuscationKey::SIZE + VersionHash::SIZE]
700 .copy_from_slice(VersionHash::V02.as_bytes());
701 let n = Node::<ChunkAddress>::try_from(data.as_slice())
704 .expect("ref_size=0 with empty forks should decode as terminal node");
705 assert!(n.entry().is_none());
706 assert!(n.forks().is_empty());
707 }
708
709 #[test]
714 fn decode_bee_legacy_ref_size_zero_with_forks_is_rejected() {
715 let mut data = vec![0u8; 96];
716 data[ObfuscationKey::SIZE..ObfuscationKey::SIZE + VersionHash::SIZE]
717 .copy_from_slice(VersionHash::V02.as_bytes());
718 data[NodeHeader::SIZE] = 0x01;
720
721 let result = Node::<ChunkAddress>::try_from(data.as_slice());
722 assert!(matches!(
723 result,
724 Err(MantarayError::EntrySizeMismatch {
725 expected: 32,
726 actual: 0
727 })
728 ));
729 }
730
731 #[test]
734 fn decode_bee_legacy_ref_size_zero_v01_empty_node() {
735 let mut data = vec![0u8; 96];
736 data[ObfuscationKey::SIZE..ObfuscationKey::SIZE + VersionHash::SIZE]
737 .copy_from_slice(VersionHash::V01.as_bytes());
738
739 let n = Node::<ChunkAddress>::try_from(data.as_slice())
740 .expect("v0.1 ref_size=0 with empty forks should decode as terminal node");
741 assert!(n.entry().is_none());
742 assert!(n.forks().is_empty());
743 }
744
745 #[test]
750 fn encoder_never_emits_ref_size_zero_for_entryless_node() {
751 let n = Node::<ChunkAddress>::new_unencrypted();
752 let encoded = Vec::<u8>::try_from(&n).unwrap();
753
754 let mut decoded = encoded;
757 let key = decoded[..ObfuscationKey::SIZE].to_vec();
758 xor_in_place(&mut decoded[ObfuscationKey::SIZE..], &key);
759
760 assert_eq!(
761 decoded[NodeHeader::REF_SIZE_OFFSET] as usize,
762 <ChunkAddress as NodeEntry>::SIZE,
763 "encoder must emit ref_size = E::SIZE, not 0; spec requires uniform reference width"
764 );
765 }
766
767 #[test]
769 fn encode_decode_round_trip() {
770 let mut n = Node::<ChunkAddress>::new_unencrypted();
771
772 for entry in test_entries() {
773 let path = entry.path.as_bytes();
774 let e = {
775 let mut buf = [0u8; 32];
776 let len = path.len().min(32);
777 buf[32 - len..].copy_from_slice(&path[..len]);
778 ChunkAddress::from(buf)
779 };
780 n.add::<nectar_primitives::store::NullLoader, { nectar_primitives::bmt::DEFAULT_BODY_SIZE }>(
781 path, Some(e), entry.metadata, &nectar_primitives::store::NullLoader,
782 )
783 .unwrap();
784 }
785
786 for (counter, fork) in n.forks.values_mut().enumerate() {
788 let mut addr = [0u8; 32];
789 addr[31] = counter as u8;
790 fork.node.reference = Some(nectar_primitives::chunk::ChunkAddress::from(addr));
791 }
792
793 let encoded = Vec::<u8>::try_from(&n).unwrap();
794 let n2 = Node::<ChunkAddress>::try_from(encoded.as_slice()).unwrap();
795
796 assert!(n2.entry().is_none());
798 assert_eq!(n.forks().len(), n2.forks().len());
799
800 for entry in test_entries() {
801 let key = entry.path.as_bytes()[0];
802 assert!(n2.forks().contains_key(&key));
803 assert_eq!(n2.forks()[&key].prefix(), entry.path.as_bytes());
804 if !entry.metadata.is_empty() {
805 assert_eq!(n2.forks()[&key].node().metadata(), &entry.metadata);
806 }
807 }
808 }
809}