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