Skip to main content

co_primitives/types/
codec.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2// Copyright (C) 2026 1io BRANDGUARDIAN GmbH
3
4use cid::Cid;
5use core::fmt::Debug;
6use serde_repr::{Deserialize_repr, Serialize_repr};
7use std::fmt::Display;
8
9/// Known Muiticodecs
10/// See: https://github.com/multiformats/multicodec/blob/master/table.csv
11#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize_repr, Deserialize_repr)]
12#[non_exhaustive]
13#[repr(u64)]
14pub enum KnownMultiCodec {
15	/// Tag: multihash
16	/// Status: permanent
17	Identity = 0x0,
18
19	/// Tag: multihash
20	/// Status: permanent
21	Sha1 = 0x11,
22	/// Tag: multihash
23	/// Status: permanent
24	Sha2256 = 0x12,
25	/// Tag: multihash
26	/// Status: permanent
27	Sha2512 = 0x13,
28	/// Tag: multihash
29	/// Status: permanent
30	Sha3512 = 0x14,
31	/// Tag: multihash
32	/// Status: permanent
33	Sha3384 = 0x15,
34	/// Tag: multihash
35	/// Status: permanent
36	Sha3256 = 0x16,
37	/// Tag: multihash
38	/// Status: permanent
39	Sha3224 = 0x17,
40	/// Tag: multihash
41	/// Status: draft
42	Shake128 = 0x18,
43	/// Tag: multihash
44	/// Status: draft
45	Shake256 = 0x19,
46	/// keccak has variable output length. The number specifies the core length
47	/// Tag: multihash
48	/// Status: draft
49	Keccak224 = 0x1a,
50	/// Tag: multihash
51	/// Status: draft
52	Keccak256 = 0x1b,
53	/// Tag: multihash
54	/// Status: draft
55	Keccak384 = 0x1c,
56	/// Tag: multihash
57	/// Status: draft
58	Keccak512 = 0x1d,
59	/// BLAKE3 has a default 32 byte output length. The maximum length is (2^64)-1 bytes.
60	/// Tag: multihash
61	/// Status: draft
62	Blake3 = 0x1e,
63	/// aka SHA-384; as specified by FIPS 180-4.
64	/// Tag: multihash
65	/// Status: permanent
66	Sha2384 = 0x20,
67
68	/// raw binary
69	/// Tag: ipld
70	/// Status: permanent
71	Raw = 0x55,
72
73	/// MerkleDAG protobuf
74	/// Tag: ipld
75	/// Status: permanent
76	DagPb = 0x70,
77	/// MerkleDAG cbor
78	/// Tag: ipld
79	/// Status: permanent
80	DagCbor = 0x71,
81
82	/// MerkleDAG json
83	/// Tag: ipld
84	/// Status: permanent
85	DagJson = 0x0129,
86
87	/// Co Encrypted Block wrapped in [`KnownMultiCodec::DagCbor`].
88	CoEncryptedBlock = 0x301000,
89
90	/// [`crate::CoReference`] as [`KnownMultiCodec::DagCbor`].
91	CoReference = 0x301001,
92}
93impl KnownMultiCodec {
94	pub fn multi_codec(&self) -> MultiCodec {
95		MultiCodec::Known(*self)
96	}
97}
98impl TryFrom<u64> for KnownMultiCodec {
99	type Error = u64;
100
101	fn try_from(value: u64) -> Result<Self, Self::Error> {
102		serde_json::from_value(value.into()).map_err(|_| value)
103	}
104}
105impl From<KnownMultiCodec> for u64 {
106	fn from(value: KnownMultiCodec) -> Self {
107		value as u64
108	}
109}
110impl PartialEq<u64> for KnownMultiCodec {
111	fn eq(&self, other: &u64) -> bool {
112		(*self) as u64 == *other
113	}
114}
115
116/// MultiCodec matching utility.
117///
118/// See: https://github.com/multiformats/multicodec/blob/master/table.csv
119#[derive(Copy, Clone)]
120#[non_exhaustive]
121#[repr(u64)]
122pub enum MultiCodec {
123	Known(KnownMultiCodec),
124	Unknown(u64),
125}
126impl Ord for MultiCodec {
127	fn cmp(&self, other: &Self) -> std::cmp::Ordering {
128		self.codec().cmp(&other.codec())
129	}
130}
131impl PartialOrd for MultiCodec {
132	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
133		Some(self.cmp(other))
134	}
135}
136impl Eq for MultiCodec {}
137impl PartialEq for MultiCodec {
138	fn eq(&self, other: &Self) -> bool {
139		self.codec() == other.codec()
140	}
141}
142impl PartialEq<KnownMultiCodec> for MultiCodec {
143	fn eq(&self, other: &KnownMultiCodec) -> bool {
144		self == &other.multi_codec()
145	}
146}
147impl PartialEq<u64> for MultiCodec {
148	fn eq(&self, other: &u64) -> bool {
149		&self.codec() == other
150	}
151}
152impl<'de> serde::Deserialize<'de> for MultiCodec {
153	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
154	where
155		D: serde::Deserializer<'de>,
156	{
157		Ok(MultiCodec::from(u64::deserialize(deserializer)?))
158	}
159}
160impl serde::Serialize for MultiCodec {
161	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
162	where
163		S: serde::Serializer,
164	{
165		serializer.serialize_u64((*self).into())
166	}
167}
168impl MultiCodec {
169	/// Expect cid to be of type codec.
170	pub fn with_codec(codec: impl Into<MultiCodec>, cid: &Cid) -> Result<&Cid, MultiCodecError> {
171		let codec = codec.into();
172		let actual_codec: MultiCodec = cid.codec().into();
173		if actual_codec == codec {
174			Ok(cid)
175		} else {
176			Err(MultiCodecError::new(*cid, codec, actual_codec))
177		}
178	}
179
180	/// Expect cid to be of type codec.
181	pub fn is_any_codec<C: Into<MultiCodec>>(codecs: impl IntoIterator<Item = C>, cid: &Cid) -> Option<&Cid> {
182		let actual_codec: MultiCodec = cid.codec().into();
183		if codecs.into_iter().map(Into::into).any(|c| c == actual_codec) {
184			Some(cid)
185		} else {
186			None
187		}
188	}
189
190	/// Error if not DAG-CBOR or a codec that is represented in DAG-CBOR.
191	pub fn with_cbor(cid: &Cid) -> Result<&Cid, MultiCodecError> {
192		Self::is_any_codec([KnownMultiCodec::DagCbor, KnownMultiCodec::CoReference], cid).ok_or_else(|| {
193			MultiCodecError::new(*cid, MultiCodec::Known(KnownMultiCodec::DagCbor), MultiCodec::from(cid))
194		})
195	}
196
197	/// Is DAG-CBOR or a codec that is represented in DAG-CBOR.
198	pub fn is_cbor(cid: impl Into<MultiCodec>) -> bool {
199		matches!(
200			cid.into(),
201			MultiCodec::Known(KnownMultiCodec::DagCbor) | MultiCodec::Known(KnownMultiCodec::CoReference)
202		)
203	}
204
205	pub fn is(actual: impl Into<MultiCodec>, expect: impl Into<MultiCodec>) -> bool {
206		actual.into() == expect.into()
207	}
208
209	pub fn codec(&self) -> u64 {
210		(*self).into()
211	}
212}
213impl Display for MultiCodec {
214	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215		match self {
216			Self::Known(m) => write!(f, "{:?}", m),
217			Self::Unknown(c) => write!(f, "{:#x}", c),
218		}
219	}
220}
221impl Debug for MultiCodec {
222	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
223		match self {
224			Self::Known(m) => write!(f, "{:?} ({:#x})", m, self.codec()),
225			Self::Unknown(c) => write!(f, "{:#x}", c),
226		}
227	}
228}
229impl From<KnownMultiCodec> for MultiCodec {
230	fn from(value: KnownMultiCodec) -> Self {
231		MultiCodec::Known(value)
232	}
233}
234impl From<MultiCodec> for u64 {
235	fn from(val: MultiCodec) -> Self {
236		match val {
237			MultiCodec::Known(v) => v.into(),
238			MultiCodec::Unknown(v) => v,
239		}
240	}
241}
242impl From<u64> for MultiCodec {
243	fn from(value: u64) -> MultiCodec {
244		match KnownMultiCodec::try_from(value) {
245			Ok(v) => MultiCodec::Known(v),
246			Err(v) => MultiCodec::Unknown(v),
247		}
248	}
249}
250impl From<&Cid> for MultiCodec {
251	fn from(value: &Cid) -> MultiCodec {
252		Self::from(value.codec())
253	}
254}
255impl From<Cid> for MultiCodec {
256	fn from(value: Cid) -> Self {
257		Self::from(value.codec())
258	}
259}
260
261/// Multi Codec Error.
262///
263/// # Note
264/// Inner values are boxed to keep stack small.
265#[derive(Debug, thiserror::Error)]
266#[error("Expected {} codec to be {} got {}", .0.0, .0.1, .0.2)]
267pub struct MultiCodecError(Box<(Cid, MultiCodec, MultiCodec)>);
268impl MultiCodecError {
269	pub fn new(cid: Cid, expected: MultiCodec, actual: MultiCodec) -> Self {
270		Self(Box::new((cid, expected, actual)))
271	}
272}
273
274#[cfg(test)]
275mod tests {
276	use super::MultiCodec;
277	use crate::{BlockSerializer, CoReference, KnownMultiCodec};
278	use serde::{Deserialize, Serialize};
279
280	#[test]
281	fn test_eq() {
282		assert_eq!(MultiCodec::Known(KnownMultiCodec::DagCbor), MultiCodec::Known(KnownMultiCodec::DagCbor));
283		assert_eq!(MultiCodec::Unknown(0x71u64), MultiCodec::Known(KnownMultiCodec::DagCbor));
284		assert_ne!(MultiCodec::Unknown(0x72u64), MultiCodec::Known(KnownMultiCodec::DagCbor));
285	}
286
287	#[test]
288	fn test_from_u64_known() {
289		assert!(matches!(MultiCodec::from(0x71u64), MultiCodec::Known(KnownMultiCodec::DagCbor)));
290	}
291
292	#[test]
293	fn test_from_u64_unknown() {
294		assert!(matches!(MultiCodec::from(0xdeadbeefu64), MultiCodec::Unknown(0xdeadbeefu64)));
295	}
296
297	#[test]
298	fn test_into_u64_known() {
299		assert_eq!(MultiCodec::from(KnownMultiCodec::DagCbor).codec(), 0x71u64);
300	}
301
302	#[test]
303	fn test_into_u64_unknown() {
304		assert_eq!(MultiCodec::from(0xdeadbeefu64).codec(), 0xdeadbeefu64);
305	}
306
307	#[test]
308	fn test_serde() {
309		#[derive(Debug, PartialEq, Serialize, Deserialize)]
310		struct Test {
311			codec: MultiCodec,
312		}
313
314		// serialize
315		assert_eq!(serde_json::to_string(&Test { codec: KnownMultiCodec::DagCbor.into() }).unwrap(), "{\"codec\":113}");
316		assert_eq!(serde_json::to_string(&Test { codec: 0xdeadbeefu64.into() }).unwrap(), "{\"codec\":3735928559}");
317
318		// deserialize
319		assert_eq!(
320			serde_json::from_str::<Test>("{\"codec\":113}").unwrap(),
321			Test { codec: MultiCodec::Known(KnownMultiCodec::DagCbor) }
322		);
323		assert_eq!(
324			serde_json::from_str::<Test>("{\"codec\":3735928559}").unwrap(),
325			Test { codec: MultiCodec::Unknown(0xdeadbeefu64) }
326		);
327	}
328
329	#[test]
330	fn test_cid() {
331		let block = BlockSerializer::new_codec(KnownMultiCodec::CoReference)
332			.serialize(&CoReference::Weak(1))
333			.unwrap();
334		assert_eq!(block.cid().to_string(), "baga2bqabdyqe2tf374ji3ixvay5hqmwyymxxpgjtxmqfijehizup5f5pypp6bda");
335	}
336}