1use std::fmt;
16
17use cid::Cid;
18use multihash_codetable::{Code, MultihashDigest};
19
20use super::error::{Result, VoidError};
21
22pub const CODEC_RAW: u64 = 0x55;
24
25pub const MULTIHASH_SHA2_256: u64 = 0x12;
27
28#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
40pub struct VoidCid(Cid);
41
42impl VoidCid {
43 pub fn create(data: &[u8]) -> Self {
45 let hash = Code::Sha2_256.digest(data);
46 Self(Cid::new_v1(CODEC_RAW, hash))
47 }
48
49 pub fn parse(s: &str) -> Result<Self> {
51 s.parse::<Cid>()
52 .map(Self)
53 .map_err(|e| VoidError::InvalidCid(e.to_string()))
54 }
55
56 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
58 Cid::try_from(bytes)
59 .map(Self)
60 .map_err(|e| VoidError::InvalidCid(e.to_string()))
61 }
62
63 pub fn to_bytes(&self) -> Vec<u8> {
65 self.0.to_bytes()
66 }
67
68 pub fn validate(&self) -> Result<()> {
70 validate(&self.0)
71 }
72
73 pub fn inner(&self) -> &Cid {
75 &self.0
76 }
77
78 pub fn dht_key(&self) -> &multihash::Multihash<64> {
85 self.0.hash()
86 }
87}
88
89impl fmt::Display for VoidCid {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 fmt::Display::fmt(&self.0, f)
92 }
93}
94
95impl fmt::Debug for VoidCid {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 write!(f, "VoidCid({})", self.0)
98 }
99}
100
101impl std::ops::Deref for VoidCid {
102 type Target = Cid;
103 fn deref(&self) -> &Cid {
104 &self.0
105 }
106}
107
108impl From<Cid> for VoidCid {
109 fn from(cid: Cid) -> Self {
110 Self(cid)
111 }
112}
113
114impl From<VoidCid> for Cid {
115 fn from(vcid: VoidCid) -> Cid {
116 vcid.0
117 }
118}
119
120impl PartialEq<Cid> for VoidCid {
121 fn eq(&self, other: &Cid) -> bool {
122 self.0 == *other
123 }
124}
125
126impl PartialEq<VoidCid> for Cid {
127 fn eq(&self, other: &VoidCid) -> bool {
128 *self == other.0
129 }
130}
131
132pub trait ToVoidCid {
141 fn to_void_cid(&self) -> Result<VoidCid>;
143
144 fn to_cid_string(&self) -> String {
148 self.to_void_cid()
149 .map(|c| c.to_string())
150 .unwrap_or_else(|_| "<invalid-cid>".to_string())
151 }
152}
153
154macro_rules! impl_to_void_cid {
155 ($($ty:ty),+ $(,)?) => {
156 $(
157 impl ToVoidCid for $ty {
158 fn to_void_cid(&self) -> Result<VoidCid> {
159 VoidCid::from_bytes(self.as_bytes())
160 }
161 }
162 )+
163 };
164}
165
166impl_to_void_cid!(
167 void_crypto::CommitCid,
168 void_crypto::MetadataCid,
169 void_crypto::ShardCid,
170 void_crypto::ManifestCid,
171 void_crypto::RepoManifestCid,
172);
173
174pub fn validate(cid: &Cid) -> Result<()> {
180 if cid.version() != cid::Version::V1 {
181 return Err(VoidError::InvalidCid(format!(
182 "unsupported CID version {:?}, void requires CIDv1",
183 cid.version()
184 )));
185 }
186 if cid.codec() != CODEC_RAW {
187 return Err(VoidError::InvalidCid(format!(
188 "unsupported codec 0x{:x}, void requires raw codec (0x55)",
189 cid.codec()
190 )));
191 }
192 if cid.hash().code() != MULTIHASH_SHA2_256 {
193 return Err(VoidError::InvalidCid(format!(
194 "unsupported hash algorithm 0x{:x}, void requires SHA-256 (0x12)",
195 cid.hash().code()
196 )));
197 }
198 Ok(())
199}
200
201pub fn create(data: &[u8]) -> VoidCid {
203 VoidCid::create(data)
204}
205
206pub fn parse(s: &str) -> Result<VoidCid> {
208 VoidCid::parse(s)
209}
210
211pub fn from_bytes(bytes: &[u8]) -> Result<VoidCid> {
213 VoidCid::from_bytes(bytes)
214}
215
216pub fn to_bytes(cid: &Cid) -> Vec<u8> {
218 cid.to_bytes()
219}
220
221pub fn to_string(cid: &Cid) -> String {
223 cid.to_string()
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn create_deterministic() {
232 let cid1 = VoidCid::create(b"hello, void!");
233 let cid2 = VoidCid::create(b"hello, void!");
234 assert_eq!(cid1, cid2);
235 }
236
237 #[test]
238 fn create_different_data() {
239 let cid1 = VoidCid::create(b"hello");
240 let cid2 = VoidCid::create(b"world");
241 assert_ne!(cid1, cid2);
242 }
243
244 #[test]
245 fn roundtrip_string() {
246 let cid = VoidCid::create(b"test data");
247 let s = cid.to_string();
248 let parsed = VoidCid::parse(&s).unwrap();
249 assert_eq!(cid, parsed);
250 }
251
252 #[test]
253 fn roundtrip_bytes() {
254 let cid = VoidCid::create(b"test data");
255 let bytes = cid.to_bytes();
256 let parsed = VoidCid::from_bytes(&bytes).unwrap();
257 assert_eq!(cid, parsed);
258 }
259
260 #[test]
261 fn display_impl() {
262 let cid = VoidCid::create(b"test");
263 let displayed = format!("{cid}");
264 let to_stringed = cid.to_string();
265 assert_eq!(displayed, to_stringed);
266 assert!(!displayed.is_empty());
267 }
268
269 #[test]
270 fn cid_is_v1() {
271 let cid = VoidCid::create(b"test");
272 assert_eq!(cid.version(), cid::Version::V1);
273 }
274
275 #[test]
276 fn parse_invalid_fails() {
277 assert!(VoidCid::parse("not-a-valid-cid").is_err());
278 }
279
280 #[test]
281 fn validate_accepts_void_cid() {
282 let cid = VoidCid::create(b"test data");
283 assert!(cid.validate().is_ok());
284 }
285
286 #[test]
287 fn validate_rejects_cidv0() {
288 let cidv0 = "QmYwAPJzv5CZsnAzt8auVZRn4iiY5J5h6kWEriX4aSx1Dd";
289 let cid = VoidCid::parse(cidv0).unwrap();
290 let result = cid.validate();
291 assert!(result.is_err());
292 assert!(result.unwrap_err().to_string().contains("CIDv1"));
293 }
294
295 #[test]
296 fn validate_rejects_wrong_codec() {
297 let hash = Code::Sha2_256.digest(b"test");
298 let cid = VoidCid::from(Cid::new_v1(0x70, hash)); let result = cid.validate();
300 assert!(result.is_err());
301 assert!(result.unwrap_err().to_string().contains("raw codec"));
302 }
303
304 #[test]
305 fn deref_to_inner_cid() {
306 let vcid = VoidCid::create(b"test");
307 let _inner: &Cid = &vcid; assert_eq!(vcid.codec(), CODEC_RAW);
309 }
310
311 #[test]
312 fn partial_eq_with_raw_cid() {
313 let vcid = VoidCid::create(b"test");
314 let raw: Cid = (*vcid).clone();
315 assert_eq!(vcid, raw);
316 assert_eq!(raw, vcid);
317 }
318}