1use 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#[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
59const DAG_PB: u64 = 0x70;
61pub(crate) const SHA2_256: u64 = 0x12;
63
64#[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 version: Version,
73 codec: u64,
75 hash: Multihash<S>,
77}
78
79impl<const S: usize> Cid<S> {
80 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 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 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 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 pub const fn version(&self) -> Version {
129 self.version
130 }
131
132 pub const fn codec(&self) -> u64 {
134 self.codec
135 }
136
137 pub const fn hash(&self) -> &Multihash<S> {
139 &self.hash
140 }
141
142 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 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 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 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 #[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 #[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#[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 assert_eq!(
419 &format!("{:?}", cid),
420 "Cid(bafyreibjo4xmgaevkgud7mbifn3dzp4v4lyaui4yvqp3f2bqwtxcjrdqg4)"
421 );
422 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}