1use {
10 super::{
11 error::{Error, Result},
12 version::Version,
13 },
14 crate::{
15 multibase::{self, encode as base_encode, Base},
16 multihash::Multihash,
17 varint::{self, encode as varint_encode},
18 },
19 alloc::{
20 borrow,
21 string::{String, ToString},
22 vec::Vec,
23 },
24 core::convert::TryFrom,
25 core2::io,
26 scale::{Decode, Encode},
27};
28
29pub(crate) fn varint_read_u64<R: io::Read>(mut r: R) -> Result<u64> {
33 use varint::decode;
34 let mut b = varint_encode::u64_buffer();
35 for i in 0..b.len() {
36 let n = r.read(&mut (b[i..i + 1]))?;
37 if n == 0 {
38 return Err(Error::VarIntDecodeError);
39 } else if decode::is_last(b[i]) {
40 match decode::u64(&b[..=i]) {
41 Ok((value, _)) => return Ok(value),
42 Err(_) => return Err(Error::VarIntDecodeError),
43 }
44 }
45 }
46 Err(Error::VarIntDecodeError)
47}
48
49const DAG_PB: u64 = 0x70;
51pub(crate) const SHA2_256: u64 = 0x12;
53
54#[derive(Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash, Decode, Encode)]
58pub struct Cid<const S: usize> {
59 version: Version,
61 codec: u64,
63 hash: Multihash<S>,
65}
66
67impl<const S: usize> Cid<S> {
68 pub const fn new_v0(hash: Multihash<S>) -> Result<Self> {
70 if hash.code() != SHA2_256 || hash.size() != 32 {
71 return Err(Error::InvalidCidV0Multihash);
72 }
73 Ok(Self {
74 version: Version::V0,
75 codec: DAG_PB,
76 hash,
77 })
78 }
79
80 pub const fn new_v1(codec: u64, hash: Multihash<S>) -> Self {
82 Self {
83 version: Version::V1,
84 codec,
85 hash,
86 }
87 }
88
89 pub const fn new(
91 version: Version,
92 codec: u64,
93 hash: Multihash<S>,
94 ) -> Result<Self> {
95 match version {
96 Version::V0 => {
97 if codec != DAG_PB {
98 return Err(Error::InvalidCidV0Codec);
99 }
100 Self::new_v0(hash)
101 }
102 Version::V1 => Ok(Self::new_v1(codec, hash)),
103 }
104 }
105
106 pub fn into_v1(self) -> Result<Self> {
108 match self.version {
109 Version::V0 => {
110 if self.codec != DAG_PB {
111 return Err(Error::InvalidCidV0Codec);
112 }
113 Ok(Self::new_v1(self.codec, self.hash))
114 }
115 Version::V1 => Ok(self),
116 }
117 }
118
119 pub const fn version(&self) -> Version {
121 self.version
122 }
123
124 pub const fn codec(&self) -> u64 {
126 self.codec
127 }
128
129 pub const fn hash(&self) -> &Multihash<S> {
131 &self.hash
132 }
133
134 pub fn read_bytes<R: io::Read>(mut r: R) -> Result<Self> {
136 let version = varint_read_u64(&mut r)?;
137 let codec = varint_read_u64(&mut r)?;
138
139 if [version, codec] == [0x12, 0x20] {
141 let mut digest = [0u8; 32];
142 r.read_exact(&mut digest)?;
143 let mh =
144 Multihash::wrap(version, &digest).expect("Digest is always 32 bytes.");
145 return Self::new_v0(mh);
146 }
147
148 let version = Version::try_from(version)?;
149 match version {
150 Version::V0 => Err(Error::InvalidExplicitCidV0),
151 Version::V1 => {
152 let mh = Multihash::read(r)?;
153 Self::new(version, codec, mh)
154 }
155 }
156 }
157
158 fn write_bytes_v1<W: io::Write>(&self, mut w: W) -> Result<usize> {
159 let mut version_buf = varint_encode::u64_buffer();
160 let version = varint_encode::u64(self.version.into(), &mut version_buf);
161
162 let mut codec_buf = varint_encode::u64_buffer();
163 let codec = varint_encode::u64(self.codec, &mut codec_buf);
164
165 let mut written = version.len() + codec.len();
166
167 w.write_all(version)?;
168 w.write_all(codec)?;
169 written += self.hash.write(&mut w)?;
170
171 Ok(written)
172 }
173
174 pub fn write_bytes<W: io::Write>(&self, w: W) -> Result<usize> {
176 let written = match self.version {
177 Version::V0 => self.hash.write(w)?,
178 Version::V1 => self.write_bytes_v1(w)?,
179 };
180 Ok(written)
181 }
182
183 pub fn encoded_len(&self) -> usize {
185 match self.version {
186 Version::V0 => self.hash.encoded_len(),
187 Version::V1 => {
188 let mut version_buf = varint_encode::u64_buffer();
189 let version = varint_encode::u64(self.version.into(), &mut version_buf);
190
191 let mut codec_buf = varint_encode::u64_buffer();
192 let codec = varint_encode::u64(self.codec, &mut codec_buf);
193
194 version.len() + codec.len() + self.hash.encoded_len()
195 }
196 }
197 }
198
199 pub fn to_bytes(&self) -> Vec<u8> {
201 let mut bytes = Vec::new();
202 let written = self.write_bytes(&mut bytes).unwrap();
203 debug_assert_eq!(written, bytes.len());
204 bytes
205 }
206
207 #[allow(clippy::wrong_self_convention)]
208 fn to_string_v0(&self) -> String {
209 Base::Base58Btc.encode(self.hash.to_bytes())
210 }
211
212 #[allow(clippy::wrong_self_convention)]
213 fn to_string_v1(&self) -> String {
214 multibase::encode(Base::Base32Lower, self.to_bytes().as_slice())
215 }
216
217 pub fn to_string_of_base(&self, base: Base) -> Result<String> {
235 match self.version {
236 Version::V0 => {
237 if base == Base::Base58Btc {
238 Ok(self.to_string_v0())
239 } else {
240 Err(Error::InvalidCidV0Base)
241 }
242 }
243 Version::V1 => Ok(base_encode(base, self.to_bytes())),
244 }
245 }
246}
247
248impl<const S: usize> Default for Cid<S> {
249 fn default() -> Self {
250 Self {
251 version: Version::V1,
252 codec: 0,
253 hash: Multihash::<S>::default(),
254 }
255 }
256}
257
258impl<const S: usize> core::fmt::Display for Cid<S> {
259 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
260 let output = match self.version {
261 Version::V0 => self.to_string_v0(),
262 Version::V1 => self.to_string_v1(),
263 };
264 write!(f, "{}", output)
265 }
266}
267
268impl<const S: usize> core::fmt::Debug for Cid<S> {
269 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
270 if f.alternate() {
271 f.debug_struct("Cid")
272 .field("version", &self.version())
273 .field("codec", &self.codec())
274 .field("hash", self.hash())
275 .finish()
276 } else {
277 let output = match self.version {
278 Version::V0 => self.to_string_v0(),
279 Version::V1 => self.to_string_v1(),
280 };
281 write!(f, "Cid({})", output)
282 }
283 }
284}
285
286impl<const S: usize> core::str::FromStr for Cid<S> {
287 type Err = Error;
288
289 fn from_str(cid_str: &str) -> Result<Self> {
290 Self::try_from(cid_str)
291 }
292}
293
294impl<const S: usize> TryFrom<String> for Cid<S> {
295 type Error = Error;
296
297 fn try_from(cid_str: String) -> Result<Self> {
298 Self::try_from(cid_str.as_str())
299 }
300}
301
302impl<const S: usize> TryFrom<&str> for Cid<S> {
303 type Error = Error;
304
305 fn try_from(cid_str: &str) -> Result<Self> {
306 static IPFS_DELIMETER: &str = "/ipfs/";
307
308 let hash = match cid_str.find(IPFS_DELIMETER) {
309 Some(index) => &cid_str[index + IPFS_DELIMETER.len()..],
310 _ => cid_str,
311 };
312
313 if hash.len() < 2 {
314 return Err(Error::InputTooShort);
315 }
316
317 let decoded = if Version::is_v0_str(hash) {
318 Base::Base58Btc.decode(hash)?
319 } else {
320 let (_, decoded) = multibase::decode(hash)?;
321 decoded
322 };
323
324 Self::try_from(decoded)
325 }
326}
327
328impl<const S: usize> TryFrom<Vec<u8>> for Cid<S> {
329 type Error = Error;
330
331 fn try_from(bytes: Vec<u8>) -> Result<Self> {
332 Self::try_from(bytes.as_slice())
333 }
334}
335
336impl<const S: usize> TryFrom<&[u8]> for Cid<S> {
337 type Error = Error;
338
339 fn try_from(mut bytes: &[u8]) -> Result<Self> {
340 Self::read_bytes(&mut bytes)
341 }
342}
343
344impl<const S: usize> From<&Cid<S>> for Cid<S> {
345 fn from(cid: &Cid<S>) -> Self {
346 *cid
347 }
348}
349
350impl<const S: usize> From<Cid<S>> for Vec<u8> {
351 fn from(cid: Cid<S>) -> Self {
352 cid.to_bytes()
353 }
354}
355
356impl<const S: usize> From<Cid<S>> for String {
357 fn from(cid: Cid<S>) -> Self {
358 cid.to_string()
359 }
360}
361
362impl<'a, const S: usize> From<Cid<S>> for borrow::Cow<'a, Cid<S>> {
363 fn from(from: Cid<S>) -> Self {
364 borrow::Cow::Owned(from)
365 }
366}
367
368impl<'a, const S: usize> From<&'a Cid<S>> for borrow::Cow<'a, Cid<S>> {
369 fn from(from: &'a Cid<S>) -> Self {
370 borrow::Cow::Borrowed(from)
371 }
372}
373
374#[cfg(test)]
375mod tests {
376 use alloc::format;
377
378 #[test]
379 fn test_cid_scale_codec() {
380 use {
381 super::Cid,
382 scale::{Decode, Encode},
383 };
384
385 let cid = Cid::<64>::default();
386 let bytes = cid.encode();
387 let cid2 = Cid::decode(&mut &bytes[..]).unwrap();
388 assert_eq!(cid, cid2);
389 }
390
391 #[test]
392 fn test_debug_instance() {
393 use {super::Cid, alloc::str::FromStr};
394 let cid = Cid::<64>::from_str(
395 "bafyreibjo4xmgaevkgud7mbifn3dzp4v4lyaui4yvqp3f2bqwtxcjrdqg4",
396 )
397 .unwrap();
398 assert_eq!(
400 &format!("{:?}", cid),
401 "Cid(bafyreibjo4xmgaevkgud7mbifn3dzp4v4lyaui4yvqp3f2bqwtxcjrdqg4)"
402 );
403 let mut txt = format!("{:#?}", cid);
405 txt.retain(|c| !c.is_whitespace());
406 assert_eq!(
407 &txt,
408 "Cid{version:V1,codec:113,hash:Multihash{code:18,size:32,digest:[41,119,\
409 46,195,0,149,81,168,63,176,40,43,118,60,191,149,226,240,10,35,152,172,\
410 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,\
411 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,],},}"
412 );
413 }
414}