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