tiny_cid/
cid.rs

1use crate::codec::DAG_PROTOBUF;
2use crate::error::{Error, Result};
3use crate::version::Version;
4use tiny_multihash::{Multihash, U64};
5
6/// Representation of a CID.
7///
8/// Usually you would use `Cid` instead, unless you have a custom Multihash code table
9#[derive(Copy, Clone, Debug, PartialEq, Eq)]
10#[cfg_attr(feature = "scale-codec", derive(parity_scale_codec::Decode))]
11#[cfg_attr(feature = "scale-codec", derive(parity_scale_codec::Encode))]
12#[cfg_attr(feature = "serde-codec", derive(serde::Deserialize))]
13#[cfg_attr(feature = "serde-codec", derive(serde::Serialize))]
14pub struct Cid {
15    /// The version of CID.
16    version: Version,
17    /// The codec of CID.
18    codec: u64,
19    /// The multihash of CID.
20    hash: Multihash<U64>,
21}
22
23impl Cid {
24    /// Create a new CIDv0.
25    pub fn new_v0(hash: Multihash<U64>) -> Result<Self> {
26        if hash.code() != 0x12 && hash.size() != 0x20 {
27            return Err(Error::InvalidCidV0Multihash);
28        }
29        Ok(Self {
30            version: Version::V0,
31            // Convert the code of `DagProtobuf` into the given code table
32            codec: DAG_PROTOBUF,
33            hash,
34        })
35    }
36
37    /// Create a new CIDv1.
38    pub fn new_v1(codec: u64, hash: Multihash<U64>) -> Self {
39        Self {
40            version: Version::V1,
41            codec,
42            hash,
43        }
44    }
45
46    /// Create a new CID.
47    pub fn new(version: Version, codec: u64, hash: Multihash<U64>) -> Result<Self> {
48        match version {
49            Version::V0 => {
50                if codec != DAG_PROTOBUF {
51                    return Err(Error::InvalidCidV0Codec);
52                }
53                Self::new_v0(hash)
54            }
55            Version::V1 => Ok(Self::new_v1(codec, hash)),
56        }
57    }
58
59    /// Returns the cid version.
60    pub fn version(&self) -> Version {
61        self.version
62    }
63
64    /// Returns the cid codec.
65    pub fn codec(&self) -> u64 {
66        self.codec
67    }
68
69    /// Returns the cid multihash.
70    pub fn hash(&self) -> &Multihash<U64> {
71        &self.hash
72    }
73
74    /// Reads the bytes from a byte stream.
75    #[cfg(feature = "std")]
76    pub fn read_bytes<R: std::io::Read>(mut r: R) -> Result<Self> {
77        use core::convert::TryFrom;
78        use unsigned_varint::io::read_u64;
79        let version = read_u64(&mut r)?;
80        let codec = read_u64(&mut r)?;
81        if [version, codec] == [0x12, 0x20] {
82            let mut digest = [0u8; 32];
83            r.read_exact(&mut digest)?;
84            let mh = Multihash::wrap(version, &digest).unwrap();
85            Self::new_v0(mh)
86        } else {
87            let version = Version::try_from(version)?;
88            let mh = Multihash::read(r)?;
89            Self::new(version, codec, mh)
90        }
91    }
92
93    #[cfg(feature = "std")]
94    fn write_bytes_v1<W: std::io::Write>(&self, mut w: W) -> Result<()> {
95        use unsigned_varint::encode as varint_encode;
96
97        let mut version_buf = varint_encode::u64_buffer();
98        let version = varint_encode::u64(self.version.into(), &mut version_buf);
99
100        let mut codec_buf = varint_encode::u64_buffer();
101        let codec = varint_encode::u64(self.codec, &mut codec_buf);
102
103        w.write_all(version)?;
104        w.write_all(codec)?;
105        self.hash.write(&mut w)?;
106        Ok(())
107    }
108
109    /// Writes the bytes to a byte stream.
110    #[cfg(feature = "std")]
111    pub fn write_bytes<W: std::io::Write>(&self, w: W) -> Result<()> {
112        match self.version {
113            Version::V0 => self.hash.write(w)?,
114            Version::V1 => self.write_bytes_v1(w)?,
115        }
116        Ok(())
117    }
118
119    /// Returns the encoded bytes of the `Cid`.
120    #[cfg(feature = "std")]
121    pub fn to_bytes(&self) -> Vec<u8> {
122        let mut bytes = vec![];
123        self.write_bytes(&mut bytes).unwrap();
124        bytes
125    }
126
127    #[cfg(feature = "std")]
128    fn to_string_v0(&self) -> String {
129        multibase::Base::Base58Btc.encode(self.hash.to_bytes())
130    }
131
132    #[cfg(feature = "std")]
133    fn to_string_v1(&self) -> String {
134        multibase::encode(multibase::Base::Base32Lower, self.to_bytes().as_slice())
135    }
136
137    /// Convert CID into a multibase encoded string
138    ///
139    /// # Example
140    ///
141    /// ```
142    /// use tiny_cid::{RAW, Cid};
143    /// use multibase::Base;
144    /// use tiny_multihash::{Code, MultihashCode};
145    ///
146    /// let mh = Code::Sha2_256.digest(b"foo");
147    /// let cid = Cid::new_v1(RAW, mh);
148    /// let encoded = cid.to_string_of_base(Base::Base64).unwrap();
149    /// assert_eq!(encoded, "mAVUSICwmtGto/8aP+ZtFPB0wQTQTQi1wZIO/oPmKXohiZueu");
150    /// ```
151    #[cfg(feature = "std")]
152    pub fn to_string_of_base(&self, base: multibase::Base) -> Result<String> {
153        match self.version {
154            Version::V0 => {
155                if base == multibase::Base::Base58Btc {
156                    Ok(self.to_string_v0())
157                } else {
158                    Err(Error::InvalidCidV0Base)
159                }
160            }
161            Version::V1 => Ok(multibase::encode(base, self.to_bytes())),
162        }
163    }
164}
165
166impl Default for Cid {
167    fn default() -> Self {
168        Self {
169            version: Version::V1,
170            codec: 0,
171            hash: Multihash::default(),
172        }
173    }
174}
175
176#[cfg(feature = "std")]
177#[allow(clippy::derive_hash_xor_eq)]
178impl core::hash::Hash for Cid {
179    fn hash<T: core::hash::Hasher>(&self, state: &mut T) {
180        self.to_bytes().hash(state);
181    }
182}
183
184#[cfg(feature = "std")]
185impl core::fmt::Display for Cid {
186    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
187        let output = match self.version {
188            Version::V0 => self.to_string_v0(),
189            Version::V1 => self.to_string_v1(),
190        };
191        write!(f, "{}", output)
192    }
193}
194
195#[cfg(feature = "std")]
196impl core::str::FromStr for Cid {
197    type Err = Error;
198
199    fn from_str(cid_str: &str) -> Result<Self> {
200        use core::convert::TryFrom;
201        Self::try_from(cid_str)
202    }
203}
204
205#[cfg(feature = "std")]
206impl core::convert::TryFrom<String> for Cid {
207    type Error = Error;
208
209    fn try_from(cid_str: String) -> Result<Self> {
210        Self::try_from(cid_str.as_str())
211    }
212}
213
214#[cfg(feature = "std")]
215impl core::convert::TryFrom<&str> for Cid {
216    type Error = Error;
217
218    fn try_from(cid_str: &str) -> Result<Self> {
219        static IPFS_DELIMETER: &str = "/ipfs/";
220
221        let hash = match cid_str.find(IPFS_DELIMETER) {
222            Some(index) => &cid_str[index + IPFS_DELIMETER.len()..],
223            _ => cid_str,
224        };
225
226        if hash.len() < 2 {
227            return Err(Error::InputTooShort);
228        }
229
230        let decoded = if Version::is_v0_str(hash) {
231            multibase::Base::Base58Btc.decode(hash)?
232        } else {
233            let (_, decoded) = multibase::decode(hash)?;
234            decoded
235        };
236
237        Self::try_from(decoded)
238    }
239}
240
241#[cfg(feature = "std")]
242impl core::convert::TryFrom<Vec<u8>> for Cid {
243    type Error = Error;
244
245    fn try_from(bytes: Vec<u8>) -> Result<Self> {
246        Self::try_from(bytes.as_slice())
247    }
248}
249
250#[cfg(feature = "std")]
251impl core::convert::TryFrom<&[u8]> for Cid {
252    type Error = Error;
253
254    fn try_from(mut bytes: &[u8]) -> Result<Self> {
255        Self::read_bytes(&mut bytes)
256    }
257}
258
259impl From<&Cid> for Cid {
260    fn from(cid: &Cid) -> Self {
261        *cid
262    }
263}
264
265#[cfg(feature = "std")]
266impl From<Cid> for Vec<u8> {
267    fn from(cid: Cid) -> Self {
268        cid.to_bytes()
269    }
270}
271
272#[cfg(feature = "std")]
273impl From<Cid> for String {
274    fn from(cid: Cid) -> Self {
275        cid.to_string()
276    }
277}
278
279#[cfg(feature = "std")]
280impl<'a> From<Cid> for std::borrow::Cow<'a, Cid> {
281    fn from(from: Cid) -> Self {
282        std::borrow::Cow::Owned(from)
283    }
284}
285
286#[cfg(feature = "std")]
287impl<'a> From<&'a Cid> for std::borrow::Cow<'a, Cid> {
288    fn from(from: &'a Cid) -> Self {
289        std::borrow::Cow::Borrowed(from)
290    }
291}
292
293#[cfg(test)]
294mod tests {
295    #[test]
296    #[cfg(feature = "scale-codec")]
297    fn test_cid_scale_codec() {
298        use super::Cid;
299        use parity_scale_codec::{Decode, Encode};
300
301        let cid = Cid::default();
302        let bytes = cid.encode();
303        let cid2 = Cid::decode(&mut &bytes[..]).unwrap();
304        assert_eq!(cid, cid2);
305    }
306}