1use std::borrow::Cow;
2
3use bc_crypto::hash::sha256;
4use dcbor::prelude::*;
5
6use crate::{Error, Result, digest_provider::DigestProvider, tags};
7
8#[derive(Clone, PartialEq, Eq, Hash)]
55pub struct Digest([u8; Self::DIGEST_SIZE]);
56
57impl Digest {
58 pub const DIGEST_SIZE: usize = 32;
59
60 pub fn from_data(data: [u8; Self::DIGEST_SIZE]) -> Self { Self(data) }
62
63 pub fn from_data_ref(data: impl AsRef<[u8]>) -> Result<Self> {
67 let data = data.as_ref();
68 if data.len() != Self::DIGEST_SIZE {
69 return Err(Error::invalid_size(
70 "digest",
71 Self::DIGEST_SIZE,
72 data.len(),
73 ));
74 }
75 let mut arr = [0u8; Self::DIGEST_SIZE];
76 arr.copy_from_slice(data.as_ref());
77 Ok(Self::from_data(arr))
78 }
79
80 pub fn from_image(image: impl AsRef<[u8]>) -> Self {
84 Self::from_data(sha256(image.as_ref()))
85 }
86
87 pub fn from_image_parts(image_parts: &[&[u8]]) -> Self {
91 let mut buf = Vec::new();
92 for part in image_parts {
93 buf.extend_from_slice(part);
94 }
95 Self::from_image(&buf)
96 }
97
98 pub fn from_digests(digests: &[Digest]) -> Self {
102 let mut buf = Vec::new();
103 for digest in digests {
104 buf.extend_from_slice(digest.data());
105 }
106 Self::from_image(&buf)
107 }
108
109 pub fn data(&self) -> &[u8; Self::DIGEST_SIZE] { self.into() }
111
112 pub fn as_bytes(&self) -> &[u8] { self.as_ref() }
114
115 pub fn validate(&self, image: impl AsRef<[u8]>) -> bool {
120 self == &Self::from_image(image)
121 }
122
123 pub fn from_hex(hex: impl AsRef<str>) -> Self {
128 Self::from_data_ref(hex::decode(hex.as_ref()).unwrap()).unwrap()
129 }
130
131 pub fn hex(&self) -> String { hex::encode(self.0) }
133
134 pub fn short_description(&self) -> String { hex::encode(&self.0[0..4]) }
136
137 pub fn validate_opt(
143 image: impl AsRef<[u8]>,
144 digest: Option<&Digest>,
145 ) -> bool {
146 match digest {
147 Some(digest) => digest.validate(image),
148 None => true,
149 }
150 }
151}
152
153impl<'a> From<&'a Digest> for &'a [u8; Digest::DIGEST_SIZE] {
155 fn from(value: &'a Digest) -> Self { &value.0 }
156}
157
158impl<'a> From<&'a Digest> for &'a [u8] {
160 fn from(value: &'a Digest) -> Self { &value.0 }
161}
162
163impl AsRef<[u8]> for Digest {
165 fn as_ref(&self) -> &[u8] { &self.0 }
166}
167
168impl AsRef<Digest> for Digest {
170 fn as_ref(&self) -> &Digest { self }
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 {
183 fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.0.cmp(&other.0) }
184}
185
186impl DigestProvider for Digest {
189 fn digest(&self) -> Cow<'_, Digest> { Cow::Borrowed(self) }
190}
191
192impl std::fmt::Debug for Digest {
194 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195 write!(f, "Digest({})", self.hex())
196 }
197}
198
199impl std::fmt::Display for Digest {
201 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202 write!(f, "Digest({})", self.hex())
203 }
204}
205
206impl CBORTagged for Digest {
208 fn cbor_tags() -> Vec<Tag> { tags_for_values(&[tags::TAG_DIGEST]) }
209}
210
211impl From<Digest> for CBOR {
213 fn from(value: Digest) -> Self { value.tagged_cbor() }
214}
215
216impl CBORTaggedEncodable for Digest {
218 fn untagged_cbor(&self) -> CBOR { CBOR::to_byte_string(self.0) }
219}
220
221impl TryFrom<CBOR> for Digest {
223 type Error = dcbor::Error;
224
225 fn try_from(cbor: CBOR) -> std::result::Result<Self, Self::Error> {
226 Self::from_tagged_cbor(cbor)
227 }
228}
229
230impl CBORTaggedDecodable for Digest {
232 fn from_untagged_cbor(cbor: CBOR) -> dcbor::Result<Self> {
233 let data = CBOR::try_into_byte_string(cbor)?;
234 Self::from_data_ref(data).map_err(|e| e.into())
235 }
236}
237
238impl From<&Digest> for Digest {
240 fn from(digest: &Digest) -> Self { digest.clone() }
241}
242
243impl From<Digest> for Vec<u8> {
245 fn from(digest: Digest) -> Self { digest.0.to_vec() }
246}
247
248impl From<&Digest> for Vec<u8> {
250 fn from(digest: &Digest) -> Self { digest.0.to_vec() }
251}
252
253#[cfg(test)]
254mod tests {
255 use bc_ur::prelude::*;
256 use hex_literal::hex;
257
258 use super::*;
259
260 #[test]
261 fn test_digest() {
262 let data = "hello world";
263 let digest = Digest::from_image(data.as_bytes());
264 assert_eq!(digest.data().len(), Digest::DIGEST_SIZE);
265 assert_eq!(*digest.data(), sha256(data.as_bytes()));
266 assert_eq!(
267 *digest.data(),
268 hex!(
269 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
270 )
271 );
272 }
273
274 #[test]
275 fn test_digest_from_hex() {
276 let digest = Digest::from_hex(
277 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
278 );
279 assert_eq!(digest.data().len(), Digest::DIGEST_SIZE);
280 assert_eq!(*digest.data(), sha256("hello world".as_bytes()));
281 assert_eq!(
282 *digest.data(),
283 hex!(
284 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
285 )
286 );
287 }
288
289 #[test]
290 fn test_ur() {
291 crate::register_tags();
292 let data = "hello world";
293 let digest = Digest::from_image(data.as_bytes());
294 let ur_string = digest.ur_string();
295 let expected_ur_string = "ur:digest/hdcxrhgtdirhmugtfmayondmgmtstnkipyzssslrwsvlkngulawymhloylpsvowssnwlamnlatrs";
296 assert_eq!(ur_string, expected_ur_string);
297 let digest2 = Digest::from_ur_string(&ur_string).unwrap();
298 assert_eq!(digest, digest2);
299 }
300
301 #[test]
302 fn test_digest_equality() {
303 let digest1 = Digest::from_hex(
304 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
305 );
306 let digest2 = Digest::from_hex(
307 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
308 );
309 assert_eq!(digest1, digest2);
310 }
311
312 #[test]
313 fn test_digest_inequality() {
314 let digest1 = Digest::from_hex(
315 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
316 );
317 let digest2 = Digest::from_hex(
318 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
319 );
320 assert_ne!(digest1, digest2);
321 }
322
323 #[test]
324 #[should_panic]
325 fn test_invalid_hex_string() {
326 let _ = Digest::from_hex("invalid_hex_string");
327 }
328
329 #[test]
330 #[should_panic]
331 fn test_new_from_invalid_ur_string() {
332 let invalid_ur = "ur:not_digest/invalid";
333 let _ = Digest::from_ur_string(invalid_ur).unwrap();
334 }
335}