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
79impl fmt::Display for VoidCid {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 fmt::Display::fmt(&self.0, f)
82 }
83}
84
85impl fmt::Debug for VoidCid {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 write!(f, "VoidCid({})", self.0)
88 }
89}
90
91impl std::ops::Deref for VoidCid {
92 type Target = Cid;
93 fn deref(&self) -> &Cid {
94 &self.0
95 }
96}
97
98impl From<Cid> for VoidCid {
99 fn from(cid: Cid) -> Self {
100 Self(cid)
101 }
102}
103
104impl From<VoidCid> for Cid {
105 fn from(vcid: VoidCid) -> Cid {
106 vcid.0
107 }
108}
109
110impl PartialEq<Cid> for VoidCid {
111 fn eq(&self, other: &Cid) -> bool {
112 self.0 == *other
113 }
114}
115
116impl PartialEq<VoidCid> for Cid {
117 fn eq(&self, other: &VoidCid) -> bool {
118 *self == other.0
119 }
120}
121
122pub trait ToVoidCid {
131 fn to_void_cid(&self) -> Result<VoidCid>;
133
134 fn to_cid_string(&self) -> String {
138 self.to_void_cid()
139 .map(|c| c.to_string())
140 .unwrap_or_else(|_| "<invalid-cid>".to_string())
141 }
142}
143
144macro_rules! impl_to_void_cid {
145 ($($ty:ty),+ $(,)?) => {
146 $(
147 impl ToVoidCid for $ty {
148 fn to_void_cid(&self) -> Result<VoidCid> {
149 VoidCid::from_bytes(self.as_bytes())
150 }
151 }
152 )+
153 };
154}
155
156impl_to_void_cid!(
157 void_crypto::CommitCid,
158 void_crypto::MetadataCid,
159 void_crypto::ShardCid,
160 void_crypto::ManifestCid,
161 void_crypto::RepoManifestCid,
162);
163
164pub fn validate(cid: &Cid) -> Result<()> {
170 if cid.version() != cid::Version::V1 {
171 return Err(VoidError::InvalidCid(format!(
172 "unsupported CID version {:?}, void requires CIDv1",
173 cid.version()
174 )));
175 }
176 if cid.codec() != CODEC_RAW {
177 return Err(VoidError::InvalidCid(format!(
178 "unsupported codec 0x{:x}, void requires raw codec (0x55)",
179 cid.codec()
180 )));
181 }
182 if cid.hash().code() != MULTIHASH_SHA2_256 {
183 return Err(VoidError::InvalidCid(format!(
184 "unsupported hash algorithm 0x{:x}, void requires SHA-256 (0x12)",
185 cid.hash().code()
186 )));
187 }
188 Ok(())
189}
190
191pub fn create(data: &[u8]) -> VoidCid {
193 VoidCid::create(data)
194}
195
196pub fn parse(s: &str) -> Result<VoidCid> {
198 VoidCid::parse(s)
199}
200
201pub fn from_bytes(bytes: &[u8]) -> Result<VoidCid> {
203 VoidCid::from_bytes(bytes)
204}
205
206pub fn to_bytes(cid: &Cid) -> Vec<u8> {
208 cid.to_bytes()
209}
210
211pub fn to_string(cid: &Cid) -> String {
213 cid.to_string()
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn create_deterministic() {
222 let cid1 = VoidCid::create(b"hello, void!");
223 let cid2 = VoidCid::create(b"hello, void!");
224 assert_eq!(cid1, cid2);
225 }
226
227 #[test]
228 fn create_different_data() {
229 let cid1 = VoidCid::create(b"hello");
230 let cid2 = VoidCid::create(b"world");
231 assert_ne!(cid1, cid2);
232 }
233
234 #[test]
235 fn roundtrip_string() {
236 let cid = VoidCid::create(b"test data");
237 let s = cid.to_string();
238 let parsed = VoidCid::parse(&s).unwrap();
239 assert_eq!(cid, parsed);
240 }
241
242 #[test]
243 fn roundtrip_bytes() {
244 let cid = VoidCid::create(b"test data");
245 let bytes = cid.to_bytes();
246 let parsed = VoidCid::from_bytes(&bytes).unwrap();
247 assert_eq!(cid, parsed);
248 }
249
250 #[test]
251 fn display_impl() {
252 let cid = VoidCid::create(b"test");
253 let displayed = format!("{cid}");
254 let to_stringed = cid.to_string();
255 assert_eq!(displayed, to_stringed);
256 assert!(!displayed.is_empty());
257 }
258
259 #[test]
260 fn cid_is_v1() {
261 let cid = VoidCid::create(b"test");
262 assert_eq!(cid.version(), cid::Version::V1);
263 }
264
265 #[test]
266 fn parse_invalid_fails() {
267 assert!(VoidCid::parse("not-a-valid-cid").is_err());
268 }
269
270 #[test]
271 fn validate_accepts_void_cid() {
272 let cid = VoidCid::create(b"test data");
273 assert!(cid.validate().is_ok());
274 }
275
276 #[test]
277 fn validate_rejects_cidv0() {
278 let cidv0 = "QmYwAPJzv5CZsnAzt8auVZRn4iiY5J5h6kWEriX4aSx1Dd";
279 let cid = VoidCid::parse(cidv0).unwrap();
280 let result = cid.validate();
281 assert!(result.is_err());
282 assert!(result.unwrap_err().to_string().contains("CIDv1"));
283 }
284
285 #[test]
286 fn validate_rejects_wrong_codec() {
287 let hash = Code::Sha2_256.digest(b"test");
288 let cid = VoidCid::from(Cid::new_v1(0x70, hash)); let result = cid.validate();
290 assert!(result.is_err());
291 assert!(result.unwrap_err().to_string().contains("raw codec"));
292 }
293
294 #[test]
295 fn deref_to_inner_cid() {
296 let vcid = VoidCid::create(b"test");
297 let _inner: &Cid = &vcid; assert_eq!(vcid.codec(), CODEC_RAW);
299 }
300
301 #[test]
302 fn partial_eq_with_raw_cid() {
303 let vcid = VoidCid::create(b"test");
304 let raw: Cid = (*vcid).clone();
305 assert_eq!(vcid, raw);
306 assert_eq!(raw, vcid);
307 }
308}