ipld_nostd/cid/
cid.rs

1//! This module contains the main CID type.
2//!
3//! If you are an application developer you likely won't use the `Cid` which is
4//! generic over the digest size. Intead you would use the concrete top-level
5//! `Cid` type.
6//!
7//! As a library author that works with CIDs that should support hashes of
8//! anysize, you would import the `Cid` type from this module.
9use {
10	super::{
11		error::{Error, Result},
12		version::Version,
13	},
14	crate::{
15		multibase::{self, encode as base_encode, Base},
16		multihash::Multihash,
17		varint::{self, encode as varint_encode},
18	},
19	alloc::{
20		borrow,
21		string::{String, ToString},
22		vec::Vec,
23	},
24	core::convert::TryFrom,
25	core2::io,
26	scale::{Decode, Encode},
27};
28
29/// Reads 64 bits from a byte array into a u64
30/// Adapted from unsigned-varint's generated read_u64 function at
31/// https://github.com/paritytech/unsigned-varint/blob/master/src/io.rs
32pub(crate) fn varint_read_u64<R: io::Read>(mut r: R) -> Result<u64> {
33	use varint::decode;
34	let mut b = varint_encode::u64_buffer();
35	for i in 0..b.len() {
36		let n = r.read(&mut (b[i..i + 1]))?;
37		if n == 0 {
38			return Err(Error::VarIntDecodeError);
39		} else if decode::is_last(b[i]) {
40			match decode::u64(&b[..=i]) {
41				Ok((value, _)) => return Ok(value),
42				Err(_) => return Err(Error::VarIntDecodeError),
43			}
44		}
45	}
46	Err(Error::VarIntDecodeError)
47}
48
49/// DAG-PB multicodec code
50const DAG_PB: u64 = 0x70;
51/// The SHA_256 multicodec code
52pub(crate) const SHA2_256: u64 = 0x12;
53
54/// Representation of a CID.
55///
56/// The generic is about the allocated size of the multihash.
57#[derive(Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash, Decode, Encode)]
58pub struct Cid<const S: usize> {
59	/// The version of CID.
60	version: Version,
61	/// The codec of CID.
62	codec: u64,
63	/// The multihash of CID.
64	hash: Multihash<S>,
65}
66
67impl<const S: usize> Cid<S> {
68	/// Create a new CIDv0.
69	pub const fn new_v0(hash: Multihash<S>) -> Result<Self> {
70		if hash.code() != SHA2_256 || hash.size() != 32 {
71			return Err(Error::InvalidCidV0Multihash);
72		}
73		Ok(Self {
74			version: Version::V0,
75			codec: DAG_PB,
76			hash,
77		})
78	}
79
80	/// Create a new CIDv1.
81	pub const fn new_v1(codec: u64, hash: Multihash<S>) -> Self {
82		Self {
83			version: Version::V1,
84			codec,
85			hash,
86		}
87	}
88
89	/// Create a new CID.
90	pub const fn new(
91		version: Version,
92		codec: u64,
93		hash: Multihash<S>,
94	) -> Result<Self> {
95		match version {
96			Version::V0 => {
97				if codec != DAG_PB {
98					return Err(Error::InvalidCidV0Codec);
99				}
100				Self::new_v0(hash)
101			}
102			Version::V1 => Ok(Self::new_v1(codec, hash)),
103		}
104	}
105
106	/// Convert a CIDv0 to a CIDv1. Returns unchanged if already a CIDv1.
107	pub fn into_v1(self) -> Result<Self> {
108		match self.version {
109			Version::V0 => {
110				if self.codec != DAG_PB {
111					return Err(Error::InvalidCidV0Codec);
112				}
113				Ok(Self::new_v1(self.codec, self.hash))
114			}
115			Version::V1 => Ok(self),
116		}
117	}
118
119	/// Returns the cid version.
120	pub const fn version(&self) -> Version {
121		self.version
122	}
123
124	/// Returns the cid codec.
125	pub const fn codec(&self) -> u64 {
126		self.codec
127	}
128
129	/// Returns the cid multihash.
130	pub const fn hash(&self) -> &Multihash<S> {
131		&self.hash
132	}
133
134	/// Reads the bytes from a byte stream.
135	pub fn read_bytes<R: io::Read>(mut r: R) -> Result<Self> {
136		let version = varint_read_u64(&mut r)?;
137		let codec = varint_read_u64(&mut r)?;
138
139		// CIDv0 has the fixed `0x12 0x20` prefix
140		if [version, codec] == [0x12, 0x20] {
141			let mut digest = [0u8; 32];
142			r.read_exact(&mut digest)?;
143			let mh =
144				Multihash::wrap(version, &digest).expect("Digest is always 32 bytes.");
145			return Self::new_v0(mh);
146		}
147
148		let version = Version::try_from(version)?;
149		match version {
150			Version::V0 => Err(Error::InvalidExplicitCidV0),
151			Version::V1 => {
152				let mh = Multihash::read(r)?;
153				Self::new(version, codec, mh)
154			}
155		}
156	}
157
158	fn write_bytes_v1<W: io::Write>(&self, mut w: W) -> Result<usize> {
159		let mut version_buf = varint_encode::u64_buffer();
160		let version = varint_encode::u64(self.version.into(), &mut version_buf);
161
162		let mut codec_buf = varint_encode::u64_buffer();
163		let codec = varint_encode::u64(self.codec, &mut codec_buf);
164
165		let mut written = version.len() + codec.len();
166
167		w.write_all(version)?;
168		w.write_all(codec)?;
169		written += self.hash.write(&mut w)?;
170
171		Ok(written)
172	}
173
174	/// Writes the bytes to a byte stream, returns the number of bytes written.
175	pub fn write_bytes<W: io::Write>(&self, w: W) -> Result<usize> {
176		let written = match self.version {
177			Version::V0 => self.hash.write(w)?,
178			Version::V1 => self.write_bytes_v1(w)?,
179		};
180		Ok(written)
181	}
182
183	/// Returns the length in bytes needed to encode this cid into bytes.
184	pub fn encoded_len(&self) -> usize {
185		match self.version {
186			Version::V0 => self.hash.encoded_len(),
187			Version::V1 => {
188				let mut version_buf = varint_encode::u64_buffer();
189				let version = varint_encode::u64(self.version.into(), &mut version_buf);
190
191				let mut codec_buf = varint_encode::u64_buffer();
192				let codec = varint_encode::u64(self.codec, &mut codec_buf);
193
194				version.len() + codec.len() + self.hash.encoded_len()
195			}
196		}
197	}
198
199	/// Returns the encoded bytes of the `Cid`.
200	pub fn to_bytes(&self) -> Vec<u8> {
201		let mut bytes = Vec::new();
202		let written = self.write_bytes(&mut bytes).unwrap();
203		debug_assert_eq!(written, bytes.len());
204		bytes
205	}
206
207	#[allow(clippy::wrong_self_convention)]
208	fn to_string_v0(&self) -> String {
209		Base::Base58Btc.encode(self.hash.to_bytes())
210	}
211
212	#[allow(clippy::wrong_self_convention)]
213	fn to_string_v1(&self) -> String {
214		multibase::encode(Base::Base32Lower, self.to_bytes().as_slice())
215	}
216
217	/// Convert CID into a multibase encoded string
218	///
219	/// # Example
220	///
221	/// ```
222	/// use {
223	/// 	cid::Cid,
224	/// 	multibase::Base,
225	/// 	multihash_codetable::{Code, MultihashDigest},
226	/// };
227	///
228	/// const RAW: u64 = 0x55;
229	///
230	/// let cid = Cid::new_v1(RAW, Code::Sha2_256.digest(b"foo"));
231	/// let encoded = cid.to_string_of_base(Base::Base64).unwrap();
232	/// assert_eq!(encoded, "mAVUSICwmtGto/8aP+ZtFPB0wQTQTQi1wZIO/oPmKXohiZueu");
233	/// ```
234	pub fn to_string_of_base(&self, base: Base) -> Result<String> {
235		match self.version {
236			Version::V0 => {
237				if base == Base::Base58Btc {
238					Ok(self.to_string_v0())
239				} else {
240					Err(Error::InvalidCidV0Base)
241				}
242			}
243			Version::V1 => Ok(base_encode(base, self.to_bytes())),
244		}
245	}
246}
247
248impl<const S: usize> Default for Cid<S> {
249	fn default() -> Self {
250		Self {
251			version: Version::V1,
252			codec: 0,
253			hash: Multihash::<S>::default(),
254		}
255	}
256}
257
258impl<const S: usize> core::fmt::Display for Cid<S> {
259	fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
260		let output = match self.version {
261			Version::V0 => self.to_string_v0(),
262			Version::V1 => self.to_string_v1(),
263		};
264		write!(f, "{}", output)
265	}
266}
267
268impl<const S: usize> core::fmt::Debug for Cid<S> {
269	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
270		if f.alternate() {
271			f.debug_struct("Cid")
272				.field("version", &self.version())
273				.field("codec", &self.codec())
274				.field("hash", self.hash())
275				.finish()
276		} else {
277			let output = match self.version {
278				Version::V0 => self.to_string_v0(),
279				Version::V1 => self.to_string_v1(),
280			};
281			write!(f, "Cid({})", output)
282		}
283	}
284}
285
286impl<const S: usize> core::str::FromStr for Cid<S> {
287	type Err = Error;
288
289	fn from_str(cid_str: &str) -> Result<Self> {
290		Self::try_from(cid_str)
291	}
292}
293
294impl<const S: usize> TryFrom<String> for Cid<S> {
295	type Error = Error;
296
297	fn try_from(cid_str: String) -> Result<Self> {
298		Self::try_from(cid_str.as_str())
299	}
300}
301
302impl<const S: usize> TryFrom<&str> for Cid<S> {
303	type Error = Error;
304
305	fn try_from(cid_str: &str) -> Result<Self> {
306		static IPFS_DELIMETER: &str = "/ipfs/";
307
308		let hash = match cid_str.find(IPFS_DELIMETER) {
309			Some(index) => &cid_str[index + IPFS_DELIMETER.len()..],
310			_ => cid_str,
311		};
312
313		if hash.len() < 2 {
314			return Err(Error::InputTooShort);
315		}
316
317		let decoded = if Version::is_v0_str(hash) {
318			Base::Base58Btc.decode(hash)?
319		} else {
320			let (_, decoded) = multibase::decode(hash)?;
321			decoded
322		};
323
324		Self::try_from(decoded)
325	}
326}
327
328impl<const S: usize> TryFrom<Vec<u8>> for Cid<S> {
329	type Error = Error;
330
331	fn try_from(bytes: Vec<u8>) -> Result<Self> {
332		Self::try_from(bytes.as_slice())
333	}
334}
335
336impl<const S: usize> TryFrom<&[u8]> for Cid<S> {
337	type Error = Error;
338
339	fn try_from(mut bytes: &[u8]) -> Result<Self> {
340		Self::read_bytes(&mut bytes)
341	}
342}
343
344impl<const S: usize> From<&Cid<S>> for Cid<S> {
345	fn from(cid: &Cid<S>) -> Self {
346		*cid
347	}
348}
349
350impl<const S: usize> From<Cid<S>> for Vec<u8> {
351	fn from(cid: Cid<S>) -> Self {
352		cid.to_bytes()
353	}
354}
355
356impl<const S: usize> From<Cid<S>> for String {
357	fn from(cid: Cid<S>) -> Self {
358		cid.to_string()
359	}
360}
361
362impl<'a, const S: usize> From<Cid<S>> for borrow::Cow<'a, Cid<S>> {
363	fn from(from: Cid<S>) -> Self {
364		borrow::Cow::Owned(from)
365	}
366}
367
368impl<'a, const S: usize> From<&'a Cid<S>> for borrow::Cow<'a, Cid<S>> {
369	fn from(from: &'a Cid<S>) -> Self {
370		borrow::Cow::Borrowed(from)
371	}
372}
373
374#[cfg(test)]
375mod tests {
376	use alloc::format;
377
378	#[test]
379	fn test_cid_scale_codec() {
380		use {
381			super::Cid,
382			scale::{Decode, Encode},
383		};
384
385		let cid = Cid::<64>::default();
386		let bytes = cid.encode();
387		let cid2 = Cid::decode(&mut &bytes[..]).unwrap();
388		assert_eq!(cid, cid2);
389	}
390
391	#[test]
392	fn test_debug_instance() {
393		use {super::Cid, alloc::str::FromStr};
394		let cid = Cid::<64>::from_str(
395			"bafyreibjo4xmgaevkgud7mbifn3dzp4v4lyaui4yvqp3f2bqwtxcjrdqg4",
396		)
397		.unwrap();
398		// short debug
399		assert_eq!(
400			&format!("{:?}", cid),
401			"Cid(bafyreibjo4xmgaevkgud7mbifn3dzp4v4lyaui4yvqp3f2bqwtxcjrdqg4)"
402		);
403		// verbose debug
404		let mut txt = format!("{:#?}", cid);
405		txt.retain(|c| !c.is_whitespace());
406		assert_eq!(
407			&txt,
408			"Cid{version:V1,codec:113,hash:Multihash{code:18,size:32,digest:[41,119,\
409			 46,195,0,149,81,168,63,176,40,43,118,60,191,149,226,240,10,35,152,172,\
410			 31,178,232,48,180,238,36,196,112,55,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\
411			 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,],},}"
412		);
413	}
414}