1use alloy_primitives::Keccak256;
7use bytes::Bytes;
8
9use crate::bmt::DEFAULT_BODY_SIZE;
10use crate::error::Result;
11
12use super::chunk_type::ChunkType;
13use super::content::ContentChunk;
14use super::single_owner::SingleOwnerChunk;
15use super::traits::{Chunk, ChunkAddress};
16use super::type_id::ChunkTypeId;
17
18#[derive(Debug, Clone)]
47pub enum AnyChunk<const BODY_SIZE: usize = DEFAULT_BODY_SIZE> {
48 Content(ContentChunk<BODY_SIZE>),
50 SingleOwner(SingleOwnerChunk<BODY_SIZE>),
52}
53
54impl<const BODY_SIZE: usize> AnyChunk<BODY_SIZE> {
55 pub fn address(&self) -> &ChunkAddress {
57 match self {
58 Self::Content(c) => c.address(),
59 Self::SingleOwner(c) => c.address(),
60 }
61 }
62
63 pub fn data(&self) -> &Bytes {
65 match self {
66 Self::Content(c) => c.data(),
67 Self::SingleOwner(c) => c.data(),
68 }
69 }
70
71 pub const fn type_id(&self) -> ChunkTypeId {
73 match self {
74 Self::Content(_) => ChunkTypeId::CONTENT,
75 Self::SingleOwner(_) => ChunkTypeId::SINGLE_OWNER,
76 }
77 }
78
79 pub fn size(&self) -> usize {
81 match self {
82 Self::Content(c) => c.size(),
83 Self::SingleOwner(c) => c.size(),
84 }
85 }
86
87 pub fn span(&self) -> u64 {
90 match self {
91 Self::Content(c) => super::traits::BmtChunk::span(c),
92 Self::SingleOwner(c) => super::traits::BmtChunk::span(c),
93 }
94 }
95
96 pub fn transformed_address(&self, anchor: &[u8]) -> ChunkAddress {
133 match self {
134 Self::Content(c) => ChunkAddress::from(c.body().transformed_root(anchor)),
136 Self::SingleOwner(c) => {
139 let inner = c.inner_body().transformed_root(anchor);
140 let mut hasher = Keccak256::new();
141 hasher.update(c.address().as_slice());
142 hasher.update(inner.as_slice());
143 ChunkAddress::from(hasher.finalize())
144 }
145 }
146 }
147
148 pub fn verify(&self, expected: &ChunkAddress) -> Result<()> {
150 match self {
151 Self::Content(c) => c.verify(expected),
152 Self::SingleOwner(c) => c.verify(expected),
153 }
154 }
155
156 pub fn into_bytes(self) -> Bytes {
158 match self {
159 Self::Content(c) => c.into(),
160 Self::SingleOwner(c) => c.into(),
161 }
162 }
163
164 pub fn is<T: ChunkType>(&self) -> bool {
166 self.type_id() == T::TYPE_ID
167 }
168
169 pub const fn is_content(&self) -> bool {
171 matches!(self, Self::Content(_))
172 }
173
174 pub const fn is_single_owner(&self) -> bool {
176 matches!(self, Self::SingleOwner(_))
177 }
178
179 pub const fn as_content(&self) -> Option<&ContentChunk<BODY_SIZE>> {
181 match self {
182 Self::Content(c) => Some(c),
183 _ => None,
184 }
185 }
186
187 pub const fn as_single_owner(&self) -> Option<&SingleOwnerChunk<BODY_SIZE>> {
189 match self {
190 Self::SingleOwner(c) => Some(c),
191 _ => None,
192 }
193 }
194
195 pub fn into_content(self) -> Option<ContentChunk<BODY_SIZE>> {
197 match self {
198 Self::Content(c) => Some(c),
199 _ => None,
200 }
201 }
202
203 pub fn into_single_owner(self) -> Option<SingleOwnerChunk<BODY_SIZE>> {
205 match self {
206 Self::SingleOwner(c) => Some(c),
207 _ => None,
208 }
209 }
210
211 pub fn to_typed_bytes(&self) -> Vec<u8> {
242 let tag = self.type_id().as_u8();
243 let wire = self.clone().into_bytes();
246 let mut out = Vec::with_capacity(1 + wire.len());
247 out.push(tag);
248 out.extend_from_slice(&wire);
249 out
250 }
251
252 pub fn from_typed_bytes(address: &ChunkAddress, bytes: &[u8]) -> crate::error::Result<Self> {
272 let (&tag, payload) = bytes.split_first().ok_or_else(|| {
273 super::error::ChunkError::invalid_format("empty typed-chunk encoding: missing type tag")
274 })?;
275
276 let type_id = ChunkTypeId::from(tag);
277 match type_id {
278 ChunkTypeId::CONTENT => {
279 let chunk = ContentChunk::try_from(Bytes::copy_from_slice(payload))?;
280 chunk.verify(address)?;
281 Ok(Self::Content(chunk))
282 }
283 ChunkTypeId::SINGLE_OWNER => {
284 let chunk = SingleOwnerChunk::try_from(Bytes::copy_from_slice(payload))?;
285 chunk.verify(address)?;
286 Ok(Self::SingleOwner(chunk))
287 }
288 other => Err(super::error::ChunkError::unsupported_type(other).into()),
289 }
290 }
291
292 pub fn from_wire_bytes(address: &ChunkAddress, data: Bytes) -> crate::error::Result<Self> {
312 if let Ok(content) = ContentChunk::try_from(data.clone())
313 && content.address() == address
314 {
315 return Ok(Self::Content(content));
316 }
317 if let Ok(soc) = SingleOwnerChunk::try_from(data)
318 && soc.address() == address
319 {
320 return Ok(Self::SingleOwner(soc));
321 }
322 Err(super::error::ChunkError::verification_failed(*address, *address).into())
323 }
324}
325
326impl<const BODY_SIZE: usize> From<ContentChunk<BODY_SIZE>> for AnyChunk<BODY_SIZE> {
327 fn from(chunk: ContentChunk<BODY_SIZE>) -> Self {
328 Self::Content(chunk)
329 }
330}
331
332impl<const BODY_SIZE: usize> From<SingleOwnerChunk<BODY_SIZE>> for AnyChunk<BODY_SIZE> {
333 fn from(chunk: SingleOwnerChunk<BODY_SIZE>) -> Self {
334 Self::SingleOwner(chunk)
335 }
336}
337
338impl<const BODY_SIZE: usize> PartialEq for AnyChunk<BODY_SIZE> {
339 fn eq(&self, other: &Self) -> bool {
340 self.address() == other.address()
341 }
342}
343
344impl<const BODY_SIZE: usize> Eq for AnyChunk<BODY_SIZE> {}
345
346#[cfg(test)]
347mod tests {
348 use super::super::traits::{BmtChunk, Chunk};
349 use super::*;
350
351 type DefaultContentChunk = ContentChunk<DEFAULT_BODY_SIZE>;
352 type DefaultSingleOwnerChunk = SingleOwnerChunk<DEFAULT_BODY_SIZE>;
353 type DefaultAnyChunk = AnyChunk<DEFAULT_BODY_SIZE>;
354
355 #[test]
356 fn test_content_chunk_conversion() {
357 let content = DefaultContentChunk::new(&b"hello world"[..]).unwrap();
358 let address = *content.address();
359
360 let any: DefaultAnyChunk = content.into();
361
362 assert!(any.is_content());
363 assert!(!any.is_single_owner());
364 assert_eq!(any.type_id(), ChunkTypeId::CONTENT);
365 assert_eq!(*any.address(), address);
366 }
367
368 #[test]
369 fn test_as_content() {
370 let content = DefaultContentChunk::new(&b"test data"[..]).unwrap();
371 let expected_addr = *content.address();
372
373 let any: DefaultAnyChunk = content.into();
374 let recovered = any.as_content().unwrap();
375
376 assert_eq!(*recovered.address(), expected_addr);
377 }
378
379 #[test]
380 fn test_into_content() {
381 let content = DefaultContentChunk::new(&b"test data"[..]).unwrap();
382 let expected_addr = *content.address();
383
384 let any: DefaultAnyChunk = content.into();
385 let recovered = any.into_content().unwrap();
386
387 assert_eq!(*recovered.address(), expected_addr);
388 }
389
390 #[test]
391 fn test_is_methods() {
392 let content: DefaultAnyChunk = DefaultContentChunk::new(&b"test"[..]).unwrap().into();
393
394 assert!(content.is::<DefaultContentChunk>());
395 assert!(!content.is::<DefaultSingleOwnerChunk>());
396 }
397
398 #[test]
399 fn test_clone() {
400 let content = DefaultContentChunk::new(&b"test"[..]).unwrap();
401 let any: DefaultAnyChunk = content.into();
402 let cloned = any.clone();
403
404 assert_eq!(any.address(), cloned.address());
405 assert_eq!(any.type_id(), cloned.type_id());
406 }
407
408 fn test_signer() -> alloy_signer_local::PrivateKeySigner {
409 let pk = [0x42u8; 32];
411 alloy_signer_local::PrivateKeySigner::from_slice(&pk).unwrap()
412 }
413
414 fn sample_single_owner() -> DefaultSingleOwnerChunk {
415 let id = alloy_primitives::B256::ZERO;
416 DefaultSingleOwnerChunk::new(id, b"single owner payload".to_vec(), &test_signer()).unwrap()
417 }
418
419 #[test]
420 fn test_typed_content_round_trip() {
421 let content = DefaultContentChunk::new(&b"hello typed world"[..]).unwrap();
422 let address = *content.address();
423 let any: DefaultAnyChunk = content.into();
424
425 let encoded = any.to_typed_bytes();
426 assert_eq!(encoded[0], 0, "CONTENT tag must be 0");
427
428 let decoded = DefaultAnyChunk::from_typed_bytes(&address, &encoded).unwrap();
429 assert!(decoded.is_content());
430 assert_eq!(decoded.type_id(), ChunkTypeId::CONTENT);
431 assert_eq!(decoded.address(), any.address());
432 assert_eq!(decoded.data(), any.data());
433 }
434
435 #[test]
436 fn test_typed_single_owner_round_trip() {
437 let soc = sample_single_owner();
438 let address = *soc.address();
439 let any: DefaultAnyChunk = soc.into();
440
441 let encoded = any.to_typed_bytes();
442 assert_eq!(encoded[0], 1, "SINGLE_OWNER tag must be 1");
443
444 let decoded = DefaultAnyChunk::from_typed_bytes(&address, &encoded).unwrap();
445 assert!(decoded.is_single_owner());
446 assert_eq!(decoded.type_id(), ChunkTypeId::SINGLE_OWNER);
447 assert_eq!(decoded.address(), any.address());
448 assert_eq!(decoded.data(), any.data());
449 }
450
451 #[test]
452 fn test_typed_custom_round_trip() {
453 let type_id = ChunkTypeId::custom(200);
456 let address: ChunkAddress = [0x11u8; 32].into();
457 let data = Bytes::from_static(b"opaque custom chunk bytes");
458
459 let encoded = {
460 let mut out = Vec::with_capacity(1 + data.len());
461 out.push(type_id.as_u8());
462 out.extend_from_slice(&data);
463 out
464 };
465
466 let result = DefaultAnyChunk::from_typed_bytes(&address, &encoded);
467 assert!(
468 result.is_err(),
469 "unrecognised type tags must error now that Custom is removed",
470 );
471 }
472
473 #[test]
474 fn test_typed_dispatch_by_tag_not_trial_parse() {
475 let content = DefaultContentChunk::new(&b"dispatch sanity"[..]).unwrap();
478 let address = *content.address();
479 let any: DefaultAnyChunk = content.into();
480
481 let encoded = any.to_typed_bytes();
482 let decoded = DefaultAnyChunk::from_typed_bytes(&address, &encoded).unwrap();
483
484 assert!(decoded.is_content());
486 assert!(!decoded.is_single_owner());
487 }
488
489 #[test]
490 fn test_typed_decode_empty_input_errors() {
491 let address: ChunkAddress = [0u8; 32].into();
492 let result = DefaultAnyChunk::from_typed_bytes(&address, &[]);
493 assert!(result.is_err(), "empty input must error, not panic");
494 }
495
496 #[test]
497 fn test_typed_decode_address_mismatch_errors() {
498 let content = DefaultContentChunk::new(&b"chunk A"[..]).unwrap();
499 let encoded = DefaultAnyChunk::from(content).to_typed_bytes();
500
501 let wrong: ChunkAddress = [0xFFu8; 32].into();
503 let result = DefaultAnyChunk::from_typed_bytes(&wrong, &encoded);
504 assert!(result.is_err(), "address mismatch must error");
505 }
506
507 #[test]
508 fn test_typed_decode_corrupt_content_payload_errors() {
509 let content = DefaultContentChunk::new(&b"corruptible"[..]).unwrap();
510 let address = *content.address();
511
512 let bad = vec![ChunkTypeId::CONTENT.as_u8(), 0x00];
514 let result = DefaultAnyChunk::from_typed_bytes(&address, &bad);
515 assert!(result.is_err(), "corrupt content payload must error");
516 }
517
518 #[test]
519 fn test_from_wire_bytes_content_round_trip() {
520 let content = DefaultContentChunk::new(&b"hello wire world"[..]).unwrap();
521 let address = *content.address();
522 let any: DefaultAnyChunk = content.into();
523 let wire = any.clone().into_bytes();
525
526 let decoded = DefaultAnyChunk::from_wire_bytes(&address, wire.clone()).unwrap();
527 assert!(decoded.is_content());
528 assert_eq!(decoded.address(), any.address());
529 assert_eq!(decoded.into_bytes(), wire);
530 }
531
532 #[test]
533 fn test_from_wire_bytes_single_owner_round_trip() {
534 let soc = sample_single_owner();
535 let address = *soc.address();
536 let any: DefaultAnyChunk = soc.into();
537 let wire = any.clone().into_bytes();
538
539 let decoded = DefaultAnyChunk::from_wire_bytes(&address, wire.clone()).unwrap();
540 assert!(decoded.is_single_owner());
541 assert_eq!(decoded.address(), any.address());
542 assert_eq!(decoded.into_bytes(), wire);
543 }
544
545 #[test]
546 fn test_from_wire_bytes_address_mismatch_errors() {
547 let content = DefaultContentChunk::new(&b"chunk A"[..]).unwrap();
548 let wire = DefaultAnyChunk::from(content).into_bytes();
549
550 let wrong: ChunkAddress = [0xFFu8; 32].into();
551 let result = DefaultAnyChunk::from_wire_bytes(&wrong, wire);
552 assert!(result.is_err(), "address mismatch must error, not panic");
553 }
554
555 #[test]
556 fn test_from_wire_bytes_unknown_shape_errors() {
557 let opaque = Bytes::from_static(b"opaque custom-looking payload bytes");
561 let addr: ChunkAddress = [0x11u8; 32].into();
562 assert!(DefaultAnyChunk::from_wire_bytes(&addr, opaque).is_err());
563 }
564
565 #[test]
575 fn transformed_address_reproduces_bee_cac_vector() {
576 use alloy_primitives::hex;
577
578 const ANCHOR: &[u8] = b"swarm-test-anchor-deterministic!";
579 const WANT_CHUNK_ADDR: &str =
581 "902406053a7a2f3a17f16097e1d0b4b6a4abeae6b84968f5503ae621f9522e16";
582 const WANT_TRANSFORMED: &str =
584 "9dee91d1ed794460474ffc942996bd713176731db4581a3c6470fe9862905a60";
585
586 let mut payload = vec![0u8; 4096];
587 for (i, b) in payload.iter_mut().enumerate() {
588 *b = (i % 256) as u8;
589 }
590
591 let content = DefaultContentChunk::new(payload).unwrap();
592
593 assert_eq!(
595 hex::encode(content.address().as_slice()),
596 WANT_CHUNK_ADDR,
597 "plain BMT address must match bee's published vector",
598 );
599
600 let any: DefaultAnyChunk = content.into();
601 let tr = any.transformed_address(ANCHOR);
602 assert_eq!(
603 hex::encode(tr.as_slice()),
604 WANT_TRANSFORMED,
605 "CAC transformed address must match bee byte-for-byte",
606 );
607 }
608
609 #[test]
614 fn transformed_address_reproduces_bee_soc_vector() {
615 use alloy_primitives::hex;
616
617 const ANCHOR: &[u8] = &[0x64];
619 const WANT_CHUNK_ADDR: &str =
620 "71d5144d0525b82cd550aa9254245c6195fdac9ccbb625eb45a0bfe244cb131f";
621 const WANT_TRANSFORMED: &str =
622 "521f50f895dc1deea14448c09ba8d9c510c5db09cd84c7ed8413b66f15fbc110";
623 const SOC_WIRE: &str = "82d0ed66f956ed70445d02df922606e02c11a79014cc441f9cc678275d260703\
625 15c7157590f599a81b38834786f79f57a6a6626bbe8bf48fbbd6db3e55ad41c0\
626 277d7d310bbefbb5d81d74605a5950171229ebdd320249ef5f8fd6b8dfe2bc7d\
627 1c1a00000000000000556e73746f707061626c65206461746121204368756e6b\
628 202331";
629
630 let wire = hex::decode(SOC_WIRE.replace([' ', '\n'], "")).unwrap();
631 let soc = DefaultSingleOwnerChunk::try_from(wire.as_slice()).unwrap();
632
633 assert_eq!(
635 hex::encode(soc.address().as_slice()),
636 WANT_CHUNK_ADDR,
637 "SOC address must match bee's oracle",
638 );
639
640 let cac = soc.unwrap_cac();
643 assert_eq!(cac.span(), soc.inner_body().span());
644 assert_eq!(cac.data(), soc.inner_body().data());
645
646 let any: DefaultAnyChunk = soc.into();
647 let tr = any.transformed_address(ANCHOR);
648 assert_eq!(
649 hex::encode(tr.as_slice()),
650 WANT_TRANSFORMED,
651 "SOC transformed address must match bee byte-for-byte",
652 );
653 }
654}