1use std::borrow::Cow;
2use bc_crypto::hash::sha256;
3use crate::{ digest_provider::DigestProvider, tags };
4use anyhow::{ bail, Result };
5use dcbor::prelude::*;
6
7#[derive(Clone, PartialEq, Eq, Hash)]
50pub struct Digest([u8; Self::DIGEST_SIZE]);
51
52impl Digest {
53 pub const DIGEST_SIZE: usize = 32;
54
55 pub fn from_data(data: [u8; Self::DIGEST_SIZE]) -> Self {
57 Self(data)
58 }
59
60 pub fn from_data_ref(data: impl AsRef<[u8]>) -> Result<Self> {
64 let data = data.as_ref();
65 if data.len() != Self::DIGEST_SIZE {
66 bail!("Invalid digest size");
67 }
68 let mut arr = [0u8; Self::DIGEST_SIZE];
69 arr.copy_from_slice(data.as_ref());
70 Ok(Self::from_data(arr))
71 }
72
73 pub fn from_image(image: impl AsRef<[u8]>) -> Self {
77 Self::from_data(sha256(image.as_ref()))
78 }
79
80 pub fn from_image_parts(image_parts: &[&[u8]]) -> Self {
84 let mut buf = Vec::new();
85 for part in image_parts {
86 buf.extend_from_slice(part);
87 }
88 Self::from_image(&buf)
89 }
90
91 pub fn from_digests(digests: &[Digest]) -> Self {
95 let mut buf = Vec::new();
96 for digest in digests {
97 buf.extend_from_slice(digest.data());
98 }
99 Self::from_image(&buf)
100 }
101
102 pub fn data(&self) -> &[u8; Self::DIGEST_SIZE] {
104 self.into()
105 }
106
107 pub fn validate(&self, image: impl AsRef<[u8]>) -> bool {
112 self == &Self::from_image(image)
113 }
114
115 pub fn from_hex(hex: impl AsRef<str>) -> Self {
120 Self::from_data_ref(hex::decode(hex.as_ref()).unwrap()).unwrap()
121 }
122
123 pub fn hex(&self) -> String {
125 hex::encode(self.0)
126 }
127
128 pub fn short_description(&self) -> String {
130 hex::encode(&self.0[0..4])
131 }
132
133 pub fn validate_opt(image: impl AsRef<[u8]>, digest: Option<&Digest>) -> bool {
138 match digest {
139 Some(digest) => digest.validate(image),
140 None => true,
141 }
142 }
143}
144
145impl<'a> From<&'a Digest> for &'a [u8; Digest::DIGEST_SIZE] {
147 fn from(value: &'a Digest) -> Self {
148 &value.0
149 }
150}
151
152impl<'a> From<&'a Digest> for &'a [u8] {
154 fn from(value: &'a Digest) -> Self {
155 &value.0
156 }
157}
158
159impl AsRef<[u8]> for Digest {
161 fn as_ref(&self) -> &[u8] {
162 &self.0
163 }
164}
165
166impl AsRef<Digest> for Digest {
168 fn as_ref(&self) -> &Digest {
169 self
170 }
171}
172
173impl std::cmp::PartialOrd for Digest {
175 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
176 Some(self.0.cmp(&other.0))
177 }
178}
179
180impl std::cmp::Ord for Digest {
182 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
183 self.0.cmp(&other.0)
184 }
185}
186
187impl DigestProvider for Digest {
189 fn digest(&self) -> Cow<'_, Digest> {
190 Cow::Borrowed(self)
191 }
192}
193
194impl std::fmt::Debug for Digest {
196 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197 write!(f, "Digest({})", self.hex())
198 }
199}
200
201impl std::fmt::Display for Digest {
203 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204 write!(f, "Digest({})", self.hex())
205 }
206}
207
208impl CBORTagged for Digest {
210 fn cbor_tags() -> Vec<Tag> {
211 tags_for_values(&[tags::TAG_DIGEST])
212 }
213}
214
215impl From<Digest> for CBOR {
217 fn from(value: Digest) -> Self {
218 value.tagged_cbor()
219 }
220}
221
222impl CBORTaggedEncodable for Digest {
224 fn untagged_cbor(&self) -> CBOR {
225 CBOR::to_byte_string(self.0)
226 }
227}
228
229impl TryFrom<CBOR> for Digest {
231 type Error = dcbor::Error;
232
233 fn try_from(cbor: CBOR) -> Result<Self, Self::Error> {
234 Self::from_tagged_cbor(cbor)
235 }
236}
237
238impl CBORTaggedDecodable for Digest {
240 fn from_untagged_cbor(cbor: CBOR) -> dcbor::Result<Self> {
241 let data = CBOR::try_into_byte_string(cbor)?;
242 Ok(Self::from_data_ref(data)?)
243 }
244}
245
246impl From<&Digest> for Digest {
248 fn from(digest: &Digest) -> Self {
249 digest.clone()
250 }
251}
252
253impl From<Digest> for Vec<u8> {
255 fn from(digest: Digest) -> Self {
256 digest.0.to_vec()
257 }
258}
259
260impl From<&Digest> for Vec<u8> {
262 fn from(digest: &Digest) -> Self {
263 digest.0.to_vec()
264 }
265}
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270 use bc_ur::prelude::*;
271 use hex_literal::hex;
272
273 #[test]
274 fn test_digest() {
275 let data = "hello world";
276 let digest = Digest::from_image(data.as_bytes());
277 assert_eq!(digest.data().len(), Digest::DIGEST_SIZE);
278 assert_eq!(*digest.data(), sha256(data.as_bytes()));
279 assert_eq!(
280 *digest.data(),
281 hex!("b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9")
282 );
283 }
284
285 #[test]
286 fn test_digest_from_hex() {
287 let digest = Digest::from_hex(
288 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
289 );
290 assert_eq!(digest.data().len(), Digest::DIGEST_SIZE);
291 assert_eq!(*digest.data(), sha256("hello world".as_bytes()));
292 assert_eq!(
293 *digest.data(),
294 hex!("b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9")
295 );
296 }
297
298 #[test]
299 fn test_ur() {
300 crate::register_tags();
301 let data = "hello world";
302 let digest = Digest::from_image(data.as_bytes());
303 let ur_string = digest.ur_string();
304 let expected_ur_string =
305 "ur:digest/hdcxrhgtdirhmugtfmayondmgmtstnkipyzssslrwsvlkngulawymhloylpsvowssnwlamnlatrs";
306 assert_eq!(ur_string, expected_ur_string);
307 let digest2 = Digest::from_ur_string(&ur_string).unwrap();
308 assert_eq!(digest, digest2);
309 }
310
311 #[test]
312 fn test_digest_equality() {
313 let digest1 = Digest::from_hex(
314 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
315 );
316 let digest2 = Digest::from_hex(
317 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
318 );
319 assert_eq!(digest1, digest2);
320 }
321
322 #[test]
323 fn test_digest_inequality() {
324 let digest1 = Digest::from_hex(
325 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
326 );
327 let digest2 = Digest::from_hex(
328 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
329 );
330 assert_ne!(digest1, digest2);
331 }
332
333 #[test]
334 #[should_panic]
335 fn test_invalid_hex_string() {
336 let _ = Digest::from_hex("invalid_hex_string");
337 }
338
339 #[test]
340 #[should_panic]
341 fn test_new_from_invalid_ur_string() {
342 let invalid_ur = "ur:not_digest/invalid";
343 let _ = Digest::from_ur_string(invalid_ur).unwrap();
344 }
345}