1use 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#[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 version: Version,
54 codec: Codec,
56 hash: Multihash<S>,
58}
59
60impl<const S: usize> Cid<S> {
61 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 pub fn new_v1(codec: Codec, hash: Multihash<S>) -> Self {
71 Self { codec, version: Version::V1, hash }
72 }
73
74 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 pub fn version(&self) -> Version { self.version }
93
94 pub fn codec(&self) -> u64 { self.codec }
96
97 pub fn hash(&self) -> &Multihash<S> { &self.hash }
99
100 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 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 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 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 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 assert_eq!(
379 &format!("{:?}", cid),
380 "Cid(bafyreibjo4xmgaevkgud7mbifn3dzp4v4lyaui4yvqp3f2bqwtxcjrdqg4)"
381 );
382 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}