1use crate::codec::DAG_PROTOBUF;
2use crate::error::{Error, Result};
3use crate::version::Version;
4use tiny_multihash::{Multihash, U64};
5
6#[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 version: Version,
17 codec: u64,
19 hash: Multihash<U64>,
21}
22
23impl Cid {
24 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 codec: DAG_PROTOBUF,
33 hash,
34 })
35 }
36
37 pub fn new_v1(codec: u64, hash: Multihash<U64>) -> Self {
39 Self {
40 version: Version::V1,
41 codec,
42 hash,
43 }
44 }
45
46 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 pub fn version(&self) -> Version {
61 self.version
62 }
63
64 pub fn codec(&self) -> u64 {
66 self.codec
67 }
68
69 pub fn hash(&self) -> &Multihash<U64> {
71 &self.hash
72 }
73
74 #[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 #[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 #[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 #[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}