1use core::fmt;
4
5use neco_sha2::Sha256;
6
7const CID_VERSION_V1: u64 = 1;
8const SHA2_256_CODE: u64 = 0x12;
9const SHA2_256_DIGEST_LEN: usize = 32;
10const BASE32_ALPHABET: &[u8; 32] = b"abcdefghijklmnopqrstuvwxyz234567";
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash)]
13pub struct Cid {
14 version: u64,
15 codec: Codec,
16 hash_code: u64,
17 digest: [u8; SHA2_256_DIGEST_LEN],
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21#[repr(u64)]
22pub enum Codec {
23 DagCbor = 0x71,
24 Raw = 0x55,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub enum Base {
29 Base32Lower,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum CidError {
34 InvalidVersion(u64),
35 UnsupportedCodec(u64),
36 UnsupportedHashCode(u64),
37 InvalidDigestLength,
38 InvalidMultibase,
39 UnexpectedEnd,
40 VarintOverflow,
41}
42
43impl fmt::Display for CidError {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 match self {
46 Self::InvalidVersion(version) => write!(f, "invalid CID version: {version}"),
47 Self::UnsupportedCodec(codec) => write!(f, "unsupported codec: {codec}"),
48 Self::UnsupportedHashCode(code) => write!(f, "unsupported hash code: {code}"),
49 Self::InvalidDigestLength => f.write_str("invalid digest length"),
50 Self::InvalidMultibase => f.write_str("invalid multibase"),
51 Self::UnexpectedEnd => f.write_str("unexpected end of input"),
52 Self::VarintOverflow => f.write_str("varint exceeds 64-bit range"),
53 }
54 }
55}
56
57impl std::error::Error for CidError {}
58
59impl Cid {
60 pub fn compute(codec: Codec, data: &[u8]) -> Self {
61 let digest = Sha256::digest(data);
62 let mut digest_bytes = [0u8; SHA2_256_DIGEST_LEN];
63 digest_bytes.copy_from_slice(&digest);
64
65 Self {
66 version: CID_VERSION_V1,
67 codec,
68 hash_code: SHA2_256_CODE,
69 digest: digest_bytes,
70 }
71 }
72
73 pub fn from_bytes(input: &[u8]) -> Result<(Self, usize), CidError> {
74 let (version, mut offset) = decode_varint(input)?;
75 if version != CID_VERSION_V1 {
76 return Err(CidError::InvalidVersion(version));
77 }
78
79 let (codec_raw, consumed) = decode_varint(&input[offset..])?;
80 offset += consumed;
81 let codec = decode_codec(codec_raw)?;
82
83 let (hash_code, consumed) = decode_varint(&input[offset..])?;
84 offset += consumed;
85 if hash_code != SHA2_256_CODE {
86 return Err(CidError::UnsupportedHashCode(hash_code));
87 }
88
89 let (digest_len, consumed) = decode_varint(&input[offset..])?;
90 offset += consumed;
91 if digest_len != SHA2_256_DIGEST_LEN as u64 {
92 return Err(CidError::InvalidDigestLength);
93 }
94
95 let end = offset + SHA2_256_DIGEST_LEN;
96 if input.len() < end {
97 return Err(CidError::UnexpectedEnd);
98 }
99
100 let mut digest = [0u8; SHA2_256_DIGEST_LEN];
101 digest.copy_from_slice(&input[offset..end]);
102
103 Ok((
104 Self {
105 version,
106 codec,
107 hash_code,
108 digest,
109 },
110 end,
111 ))
112 }
113
114 pub fn to_bytes(&self) -> Vec<u8> {
115 let mut out = Vec::with_capacity(4 + SHA2_256_DIGEST_LEN);
116 encode_varint_into(self.version, &mut out);
117 encode_varint_into(self.codec as u64, &mut out);
118 encode_varint_into(self.hash_code, &mut out);
119 encode_varint_into(SHA2_256_DIGEST_LEN as u64, &mut out);
120 out.extend_from_slice(&self.digest);
121 out
122 }
123
124 pub fn to_multibase(&self, base: Base) -> String {
125 match base {
126 Base::Base32Lower => {
127 let binary = self.to_bytes();
128 let mut encoded = String::with_capacity(1 + (binary.len() * 8).div_ceil(5));
129 encoded.push('b');
130 encoded.push_str(&base32lower_encode(&binary));
131 encoded
132 }
133 }
134 }
135
136 pub fn from_multibase(input: &str) -> Result<Self, CidError> {
137 let payload = input.strip_prefix('b').ok_or(CidError::InvalidMultibase)?;
138 let bytes = base32lower_decode(payload)?;
139 let (cid, consumed) = Self::from_bytes(&bytes).map_err(|error| match error {
140 CidError::UnexpectedEnd => CidError::InvalidMultibase,
141 other => other,
142 })?;
143 if consumed != bytes.len() {
144 return Err(CidError::InvalidMultibase);
145 }
146 Ok(cid)
147 }
148
149 pub fn codec(&self) -> Codec {
150 self.codec
151 }
152
153 pub fn digest(&self) -> &[u8; SHA2_256_DIGEST_LEN] {
154 &self.digest
155 }
156}
157
158fn decode_codec(value: u64) -> Result<Codec, CidError> {
159 match value {
160 0x71 => Ok(Codec::DagCbor),
161 0x55 => Ok(Codec::Raw),
162 other => Err(CidError::UnsupportedCodec(other)),
163 }
164}
165
166fn encode_varint_into(mut value: u64, out: &mut Vec<u8>) {
167 loop {
168 let lower = (value & 0x7f) as u8;
169 value >>= 7;
170 if value == 0 {
171 out.push(lower);
172 return;
173 }
174 out.push(lower | 0x80);
175 }
176}
177
178fn decode_varint(input: &[u8]) -> Result<(u64, usize), CidError> {
179 let mut value = 0u64;
180 let mut shift = 0u32;
181
182 for (index, &byte) in input.iter().enumerate() {
183 let chunk = u64::from(byte & 0x7f);
184 value |= chunk << shift;
185 if byte & 0x80 == 0 {
186 return Ok((value, index + 1));
187 }
188 shift += 7;
189 if shift >= 64 {
190 return Err(CidError::VarintOverflow);
191 }
192 }
193
194 Err(CidError::UnexpectedEnd)
195}
196
197pub fn base32lower_encode(input: &[u8]) -> String {
198 if input.is_empty() {
199 return String::new();
200 }
201
202 let mut output = String::with_capacity((input.len() * 8).div_ceil(5));
203 let mut buffer = 0u16;
204 let mut bits = 0u8;
205
206 for &byte in input {
207 buffer = (buffer << 8) | u16::from(byte);
208 bits += 8;
209 while bits >= 5 {
210 bits -= 5;
211 let index = ((buffer >> bits) & 0x1f) as usize;
212 output.push(BASE32_ALPHABET[index] as char);
213 }
214 }
215
216 if bits > 0 {
217 let index = ((buffer << (5 - bits)) & 0x1f) as usize;
218 output.push(BASE32_ALPHABET[index] as char);
219 }
220
221 output
222}
223
224fn base32lower_decode(input: &str) -> Result<Vec<u8>, CidError> {
225 if input.is_empty() {
226 return Err(CidError::InvalidMultibase);
227 }
228
229 let mut output = Vec::with_capacity((input.len() * 5) / 8);
230 let mut buffer = 0u32;
231 let mut bits = 0u8;
232
233 for byte in input.bytes() {
234 let value = decode_base32_char(byte)?;
235 buffer = (buffer << 5) | u32::from(value);
236 bits += 5;
237 while bits >= 8 {
238 bits -= 8;
239 output.push(((buffer >> bits) & 0xff) as u8);
240 }
241 }
242
243 if bits > 0 {
244 let mask = (1u32 << bits) - 1;
245 if buffer & mask != 0 {
246 return Err(CidError::InvalidMultibase);
247 }
248 }
249
250 Ok(output)
251}
252
253fn decode_base32_char(byte: u8) -> Result<u8, CidError> {
254 match byte {
255 b'a'..=b'z' => Ok(byte - b'a'),
256 b'2'..=b'7' => Ok(byte - b'2' + 26),
257 _ => Err(CidError::InvalidMultibase),
258 }
259}