Skip to main content

co_primitives/types/
block.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2// Copyright (C) 2026 1io BRANDGUARDIAN GmbH
3
4use crate::{MultiCodec, StoreParams};
5use cid::Cid;
6use multihash_codetable::{Code, MultihashDigest};
7use std::hash::{Hash, Hasher};
8
9/// Block
10#[derive(Clone)]
11pub struct Block {
12	cid: Cid,
13	data: Vec<u8>,
14}
15impl Block {
16	/// Creates a new block. Returns an error if the hash doesn't match
17	/// the data.
18	pub fn new(cid: Cid, data: Vec<u8>) -> Result<Self, BlockError> {
19		verify_cid::<multihash_codetable::Code, 64>(&cid, &data)?;
20		Ok(Self::new_unchecked(cid, data))
21	}
22
23	/// Creates a new block without verifying the cid.
24	pub fn new_unchecked(cid: Cid, data: Vec<u8>) -> Self {
25		Self { cid, data }
26	}
27
28	/// Create a new block by calculating the [`Cid`] from data using the default hasher.
29	/// Note: The default hasher may changes without notice.
30	pub fn new_data(codec: impl Into<u64>, data: Vec<u8>) -> Self {
31		Self::new_unchecked(Self::cid_data(codec, &data), data)
32	}
33
34	/// Create a new block by calculating the [`Cid`] from data.
35	pub fn new_data_digest(digest: impl MultihashDigest<64>, codec: impl Into<u64>, data: Vec<u8>) -> Self {
36		Self::new_unchecked(Self::cid_data_digest(digest, codec, &data), data)
37	}
38
39	pub fn cid_data(codec: impl Into<u64>, data: &[u8]) -> Cid {
40		Self::cid_data_digest(Code::Blake3_256, codec, data)
41	}
42
43	pub fn cid_data_digest(digest: impl MultihashDigest<64>, codec: impl Into<u64>, data: &[u8]) -> Cid {
44		Cid::new_v1(codec.into(), digest.digest(data))
45	}
46
47	/// Returns the cid.
48	pub fn cid(&self) -> &Cid {
49		&self.cid
50	}
51
52	/// Returns the payload.
53	pub fn data(&self) -> &[u8] {
54		&self.data
55	}
56
57	/// Returns the inner cid and data.
58	pub fn into_inner(self) -> (Cid, Vec<u8>) {
59		(self.cid, self.data)
60	}
61
62	/// Block with verified hash.
63	pub fn with_verify(self) -> Result<Block, BlockError> {
64		verify_cid::<multihash_codetable::Code, 64>(&self.cid, &self.data)?;
65		Ok(self)
66	}
67
68	/// Block with specific store params.
69	pub fn with_store_params<P: StoreParams>(self) -> Result<Block, BlockError> {
70		self.with_block_max_size(P::MAX_BLOCK_SIZE)
71	}
72
73	/// Block with specific store max size.
74	pub fn with_block_max_size(self, max_block_size: usize) -> Result<Block, BlockError> {
75		if self.data.len() > max_block_size {
76			return Err(BlockError::SizeOverflow(self.data.len(), max_block_size));
77		}
78		Ok(self)
79	}
80}
81impl PartialEq for Block {
82	fn eq(&self, other: &Self) -> bool {
83		self.cid == other.cid
84	}
85}
86impl Eq for Block {}
87impl PartialOrd for Block {
88	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
89		Some(self.cmp(other))
90	}
91}
92impl Ord for Block {
93	fn cmp(&self, other: &Self) -> std::cmp::Ordering {
94		self.cid.cmp(&other.cid)
95	}
96}
97impl Hash for Block {
98	fn hash<H: Hasher>(&self, state: &mut H) {
99		Hash::hash(&self, state);
100	}
101}
102impl std::fmt::Debug for Block {
103	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104		let hex = self.data.iter().map(|c| format!("{:02X}", c)).collect::<String>();
105		let codec = MultiCodec::from(&self.cid);
106		f.debug_struct("Block")
107			.field("cid", &self.cid)
108			.field("codec", &codec)
109			.field("size", &self.data.len())
110			.field("data", &hex)
111			.finish()
112	}
113}
114
115#[derive(Debug, thiserror::Error)]
116pub enum BlockError {
117	#[error("Unsupported codec {0:?}.")]
118	UnsupportedCodec(u64),
119
120	#[error("Unsupported multihash {0:?}.")]
121	UnsupportedMultihash(u64),
122
123	#[error("Hash of data does not match the CID.")]
124	InvalidMultihash(Vec<u8>, Cid),
125	// #[error("Serialize failed: {0:?}: {1}")]
126	// Serialize(MultiCodec, #[source] anyhow::Error),
127	#[error("Max block size overflow ({0} > {1})")]
128	SizeOverflow(usize, usize),
129}
130
131fn verify_cid<M: MultihashDigest<S>, const S: usize>(cid: &Cid, payload: &[u8]) -> Result<(), BlockError> {
132	let mh = M::try_from(cid.hash().code())
133		.map_err(|_| BlockError::UnsupportedMultihash(cid.hash().code()))?
134		.digest(payload);
135	if mh.digest() != cid.hash().digest() {
136		return Err(BlockError::InvalidMultihash(mh.to_bytes(), *cid));
137	}
138	Ok(())
139}