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