1use 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#[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
56const DAG_PB: u64 = 0x70;
58const SHA2_256: u64 = 0x12;
60
61#[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 version: Version,
70 codec: u64,
72 hash: Multihash<S>,
74}
75
76impl<const S: usize> Cid<S> {
77 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 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 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 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 pub const fn version(&self) -> Version {
126 self.version
127 }
128
129 pub const fn codec(&self) -> u64 {
131 self.codec
132 }
133
134 pub const fn hash(&self) -> &Multihash<S> {
136 &self.hash
137 }
138
139 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 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 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 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 #[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 #[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#[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 assert_eq!(
416 &format!("{:?}", cid),
417 "Cid(bafyreibjo4xmgaevkgud7mbifn3dzp4v4lyaui4yvqp3f2bqwtxcjrdqg4)"
418 );
419 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}