1#![allow(dead_code)]
21
22use std::io::{self, Write};
23
24use sha1::{Digest as Sha1Digest, Sha1};
25use sha1dc::Hasher as Sha1dcHasher;
26use sha2::Sha256;
27
28use crate::error::{Error, Result};
29use crate::objects::{HashAlgo, ObjectId, ObjectKind};
30
31mod parallel;
32
33pub use parallel::{
34 hash_objects_parallel, par_hash_with, parallel_hash_worthwhile, try_par_hash_with,
35 try_par_hash_with_force, ParallelHashError, Parallelism, PAR_HASH_MIN_ITEMS,
36 PAR_HASH_MIN_TOTAL_BYTES,
37};
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
46pub enum Backend {
47 X86ShaNi,
49 Aarch64Sha,
51 Portable,
53}
54
55impl Backend {
56 #[must_use]
58 pub const fn as_str(self) -> &'static str {
59 match self {
60 Self::X86ShaNi => "x86_sha_ni",
61 Self::Aarch64Sha => "aarch64_sha",
62 Self::Portable => "portable",
63 }
64 }
65}
66
67impl std::fmt::Display for Backend {
68 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69 f.write_str(self.as_str())
70 }
71}
72
73#[must_use]
83pub fn backend(algo: HashAlgo) -> Backend {
84 let _ = algo;
85 #[cfg(target_arch = "x86_64")]
86 {
87 if std::arch::is_x86_feature_detected!("sha") {
88 Backend::X86ShaNi
89 } else {
90 Backend::Portable
91 }
92 }
93 #[cfg(target_arch = "aarch64")]
94 {
95 if std::arch::is_aarch64_feature_detected!("sha2") {
96 Backend::Aarch64Sha
97 } else {
98 Backend::Portable
99 }
100 }
101 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
102 {
103 Backend::Portable
104 }
105}
106const OBJECT_HEADER_BUF_LEN: usize = 6 + 1 + 20 + 1;
111
112#[derive(Debug, Clone)]
118pub enum ChecksumHasher {
119 Sha1(Sha1),
121 Sha256(Sha256),
123}
124
125impl ChecksumHasher {
126 #[must_use]
128 pub fn new(algo: HashAlgo) -> Self {
129 algo.checksum_hasher()
130 }
131
132 #[must_use]
134 pub fn algo(self: &ChecksumHasher) -> HashAlgo {
135 match self {
136 Self::Sha1(_) => HashAlgo::Sha1,
137 Self::Sha256(_) => HashAlgo::Sha256,
138 }
139 }
140
141 pub fn update(&mut self, data: &[u8]) {
143 match self {
144 Self::Sha1(h) => h.update(data),
145 Self::Sha256(h) => h.update(data),
146 }
147 }
148
149 #[must_use]
151 pub fn finalize(self) -> Vec<u8> {
152 match self {
153 Self::Sha1(h) => h.finalize().to_vec(),
154 Self::Sha256(h) => h.finalize().to_vec(),
155 }
156 }
157
158 #[must_use]
164 pub fn finalize_into(self, out: &mut [u8]) -> usize {
165 let digest = self.finalize();
166 let len = digest.len();
167 assert!(
168 out.len() >= len,
169 "checksum digest buffer too short: need {len} bytes, have {}",
170 out.len()
171 );
172 out[..len].copy_from_slice(&digest);
173 len
174 }
175}
176
177impl Write for ChecksumHasher {
178 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
179 self.update(buf);
180 Ok(buf.len())
181 }
182
183 fn flush(&mut self) -> io::Result<()> {
184 Ok(())
185 }
186}
187
188#[derive(Debug, Clone)]
190pub enum ObjectHasher {
191 Sha1(Sha1dcHasher),
193 Sha256(Sha256),
195}
196
197impl ObjectHasher {
198 #[must_use]
200 pub fn new(algo: HashAlgo) -> Self {
201 algo.hasher()
202 }
203
204 #[must_use]
206 pub fn algo(self: &ObjectHasher) -> HashAlgo {
207 match self {
208 Self::Sha1(_) => HashAlgo::Sha1,
209 Self::Sha256(_) => HashAlgo::Sha256,
210 }
211 }
212
213 pub fn update(&mut self, data: &[u8]) {
215 match self {
216 Self::Sha1(h) => h.update(data),
217 Self::Sha256(h) => h.update(data),
218 }
219 }
220
221 #[must_use]
225 pub fn for_object(algo: HashAlgo, kind: ObjectKind, len: u64) -> Self {
226 let mut hasher = Self::new(algo);
227 let mut header = [0u8; OBJECT_HEADER_BUF_LEN];
228 let header_len = write_object_header(&mut header, kind, len);
229 hasher.update(&header[..header_len]);
230 hasher
231 }
232
233 pub fn finalize(self) -> Result<ObjectId> {
239 match self {
240 Self::Sha1(h) => match h.finalize() {
241 Ok(digest) => Ok(object_id_from_sha1_digest(&digest)),
242 Err(collision) => Err(collision_to_error(collision)),
243 },
244 Self::Sha256(h) => Ok(ObjectId::from_bytes(h.finalize().as_slice())
245 .unwrap_or_else(|_| unreachable!("SHA-256 digest is 32 bytes"))),
246 }
247 }
248
249 pub fn finalize_into(self, out: &mut [u8]) -> Result<usize> {
256 match self {
257 Self::Sha1(h) => {
258 let oid = match h.finalize() {
259 Ok(digest) => object_id_from_sha1_digest(&digest),
260 Err(collision) => return Err(collision_to_error(collision)),
261 };
262 let bytes = oid.as_bytes();
263 if out.len() < bytes.len() {
264 return Err(Error::InvalidObjectId(format!(
265 "digest buffer too short: need {} bytes, have {}",
266 bytes.len(),
267 out.len()
268 )));
269 }
270 out[..bytes.len()].copy_from_slice(bytes);
271 Ok(bytes.len())
272 }
273 Self::Sha256(h) => {
274 let digest = h.finalize();
275 let len = digest.len();
276 if out.len() < len {
277 return Err(Error::InvalidObjectId(format!(
278 "digest buffer too short: need {len} bytes, have {}",
279 out.len()
280 )));
281 }
282 out[..len].copy_from_slice(&digest);
283 Ok(len)
284 }
285 }
286 }
287}
288
289impl Write for ObjectHasher {
290 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
291 self.update(buf);
292 Ok(buf.len())
293 }
294
295 fn flush(&mut self) -> io::Result<()> {
296 Ok(())
297 }
298}
299
300impl HashAlgo {
301 #[must_use]
303 pub fn checksum_hasher(self) -> ChecksumHasher {
304 match self {
305 Self::Sha1 => ChecksumHasher::Sha1(Sha1::new()),
306 Self::Sha256 => ChecksumHasher::Sha256(Sha256::new()),
307 }
308 }
309
310 #[must_use]
312 pub fn checksum_digest(self, data: &[u8]) -> Vec<u8> {
313 let mut hasher = self.checksum_hasher();
314 hasher.update(data);
315 hasher.finalize()
316 }
317
318 #[must_use]
320 pub fn hasher(self) -> ObjectHasher {
321 match self {
322 Self::Sha1 => ObjectHasher::Sha1(Sha1dcHasher::new()),
323 Self::Sha256 => ObjectHasher::Sha256(Sha256::new()),
324 }
325 }
326
327 pub fn digest(self, data: &[u8]) -> Result<ObjectId> {
333 let mut hasher = self.hasher();
334 hasher.update(data);
335 hasher.finalize()
336 }
337
338 pub fn hash_object(self, kind: ObjectKind, data: &[u8]) -> Result<ObjectId> {
344 hash_object(self, kind, data)
345 }
346}
347
348pub fn hash_object(algo: HashAlgo, kind: ObjectKind, data: &[u8]) -> Result<ObjectId> {
361 let len = u64::try_from(data.len()).unwrap_or(u64::MAX);
362 let mut hasher = ObjectHasher::for_object(algo, kind, len);
363 hasher.update(data);
364 hasher.finalize()
365}
366
367pub struct ObjectPayloadHasher {
371 inner: ObjectHasher,
372}
373
374impl ObjectPayloadHasher {
375 #[must_use]
377 pub fn new(algo: HashAlgo, kind: ObjectKind, len: u64) -> Self {
378 Self {
379 inner: ObjectHasher::for_object(algo, kind, len),
380 }
381 }
382
383 pub fn update(&mut self, chunk: &[u8]) {
385 self.inner.update(chunk);
386 }
387
388 pub fn finish(self) -> Result<ObjectId> {
394 self.inner.finalize()
395 }
396}
397
398fn object_id_from_sha1_digest(digest: &sha1dc::Digest) -> ObjectId {
399 ObjectId::from_bytes(digest.as_ref())
400 .unwrap_or_else(|_| unreachable!("SHA-1 digest is 20 bytes"))
401}
402
403fn collision_to_error(collision: sha1dc::Collision) -> Error {
404 Error::HashCollision {
405 oid: object_id_from_sha1_digest(&collision.digest()),
406 }
407}
408
409pub struct HashingWriter<W: Write> {
414 inner: W,
415 hasher: ChecksumHasher,
416}
417
418impl<W: Write> HashingWriter<W> {
419 #[must_use]
421 pub fn new(algo: HashAlgo, inner: W) -> Self {
422 Self {
423 inner,
424 hasher: algo.checksum_hasher(),
425 }
426 }
427
428 #[must_use]
430 pub fn get_ref(&self) -> &W {
431 &self.inner
432 }
433
434 pub fn get_mut(&mut self) -> &mut W {
436 &mut self.inner
437 }
438
439 pub fn finish(mut self) -> io::Result<(W, ObjectId)> {
445 let digest_bytes = self.hasher.finalize();
446 self.inner.write_all(&digest_bytes)?;
447 let digest = ObjectId::from_bytes(&digest_bytes)
448 .unwrap_or_else(|_| unreachable!("checksum digest length matches object id for algo"));
449 Ok((self.inner, digest))
450 }
451}
452
453impl<W: Write> Write for HashingWriter<W> {
454 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
455 let n = self.inner.write(buf)?;
456 if n > 0 {
457 self.hasher.update(&buf[..n]);
458 }
459 Ok(n)
460 }
461
462 fn flush(&mut self) -> io::Result<()> {
463 self.inner.flush()
464 }
465}
466
467#[derive(Debug, Clone, PartialEq, Eq)]
469pub struct TrailerMismatch {
470 pub expected: Vec<u8>,
472 pub found: Vec<u8>,
474}
475
476impl std::fmt::Display for TrailerMismatch {
477 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
478 write!(
479 f,
480 "trailer checksum mismatch (expected {}, found {})",
481 hex::encode(&self.expected),
482 hex::encode(&self.found)
483 )
484 }
485}
486
487impl std::error::Error for TrailerMismatch {}
488
489pub fn verify_trailer(algo: HashAlgo, bytes: &[u8]) -> std::result::Result<(), TrailerMismatch> {
496 let trailer_len = algo.len();
497 if bytes.len() < trailer_len {
498 return Err(TrailerMismatch {
499 expected: Vec::new(),
500 found: bytes.to_vec(),
501 });
502 }
503 let split = bytes.len() - trailer_len;
504 let (body, trailer) = bytes.split_at(split);
505 let expected = algo.checksum_digest(body);
506 if expected.as_slice() == trailer {
507 Ok(())
508 } else {
509 Err(TrailerMismatch {
510 expected,
511 found: trailer.to_vec(),
512 })
513 }
514}
515
516fn write_object_header(buf: &mut [u8], kind: ObjectKind, len: u64) -> usize {
518 let kind_bytes = kind.as_str().as_bytes();
519 let mut pos = 0;
520 buf[pos..pos + kind_bytes.len()].copy_from_slice(kind_bytes);
521 pos += kind_bytes.len();
522 buf[pos] = b' ';
523 pos += 1;
524 pos += write_decimal(&mut buf[pos..], len);
525 buf[pos] = 0;
526 pos + 1
527}
528
529fn write_decimal(out: &mut [u8], mut n: u64) -> usize {
530 if n == 0 {
531 out[0] = b'0';
532 return 1;
533 }
534 let mut tmp = [0u8; 20];
535 let mut digits = 0usize;
536 while n > 0 {
537 tmp[digits] = b'0' + (n % 10) as u8;
538 digits += 1;
539 n /= 10;
540 }
541 for i in 0..digits {
542 out[i] = tmp[digits - 1 - i];
543 }
544 digits
545}
546
547#[cfg(test)]
548mod tests {
549 use super::*;
550 use std::io::Cursor;
551
552 fn hex_oid(_algo: HashAlgo, hex: &str) -> ObjectId {
553 ObjectId::from_hex(hex).expect("test vector hex")
554 }
555
556 fn assert_digest(algo: HashAlgo, data: &[u8], expected_hex: &str) {
557 let got = algo.digest(data).expect("digest");
558 assert_eq!(got, hex_oid(algo, expected_hex), "digest mismatch");
559 }
560
561 fn assert_checksum_digest(algo: HashAlgo, data: &[u8], expected_hex: &str) {
562 let got = algo.checksum_digest(data);
563 assert_eq!(hex::encode(&got), expected_hex, "checksum digest mismatch");
564 }
565
566 #[test]
567 fn checksum_hasher_nist_vectors_sha1_sha256() {
568 assert_checksum_digest(
569 HashAlgo::Sha1,
570 b"",
571 "da39a3ee5e6b4b0d3255bfef95601890afd80709",
572 );
573 assert_checksum_digest(
574 HashAlgo::Sha1,
575 b"abc",
576 "a9993e364706816aba3e25717850c26c9cd0d89d",
577 );
578 assert_checksum_digest(
579 HashAlgo::Sha256,
580 b"",
581 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
582 );
583 assert_checksum_digest(
584 HashAlgo::Sha256,
585 b"abc",
586 "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
587 );
588 }
589
590 #[test]
591 fn checksum_hasher_matches_object_hasher_digest() {
592 let data: Vec<u8> = (0..512).map(|i| (i % 251) as u8).collect();
593 for algo in [HashAlgo::Sha1, HashAlgo::Sha256] {
594 let mut checksum = algo.checksum_hasher();
595 checksum.update(&data);
596 let checksum_bytes = checksum.finalize();
597 assert_eq!(
598 checksum_bytes,
599 algo.digest(&data).expect("digest").as_bytes()
600 );
601 }
602 }
603
604 #[test]
605 fn hashing_writer_uses_checksum_hasher() {
606 let body = b"streaming checksum body";
607 let mut buf = Vec::new();
608 {
609 let mut w = HashingWriter::new(HashAlgo::Sha256, Cursor::new(&mut buf));
610 w.write_all(body).unwrap();
611 let (_, digest) = w.finish().unwrap();
612 assert_eq!(
613 digest.as_bytes(),
614 HashAlgo::Sha256.checksum_digest(body).as_slice()
615 );
616 }
617 verify_trailer(HashAlgo::Sha256, &buf).expect("trailer valid");
618 }
619
620 #[test]
621 fn sha1_nist_vectors() {
622 assert_digest(
623 HashAlgo::Sha1,
624 b"",
625 "da39a3ee5e6b4b0d3255bfef95601890afd80709",
626 );
627 assert_digest(
628 HashAlgo::Sha1,
629 b"abc",
630 "a9993e364706816aba3e25717850c26c9cd0d89d",
631 );
632 assert_digest(
633 HashAlgo::Sha1,
634 b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
635 "84983e441c3bd26ebaae4aa1f95129e5e54670f1",
636 );
637 let million_a = vec![b'a'; 1_000_000];
638 assert_digest(
639 HashAlgo::Sha1,
640 &million_a,
641 "34aa973cd4c4daa4f61eeb2bdbad27316534016f",
642 );
643 }
644
645 #[test]
646 fn sha256_nist_vectors() {
647 assert_digest(
648 HashAlgo::Sha256,
649 b"",
650 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
651 );
652 assert_digest(
653 HashAlgo::Sha256,
654 b"abc",
655 "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
656 );
657 assert_digest(
658 HashAlgo::Sha256,
659 b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
660 "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1",
661 );
662 let million_a = vec![b'a'; 1_000_000];
663 assert_digest(
664 HashAlgo::Sha256,
665 &million_a,
666 "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0",
667 );
668 }
669
670 #[test]
671 fn incremental_matches_oneshot_random_splits() {
672 let data: Vec<u8> = (0..4096).map(|i| (i % 251) as u8).collect();
673 for algo in [HashAlgo::Sha1, HashAlgo::Sha256] {
674 let oneshot = algo.digest(&data).expect("digest");
675 let mut state = 0xdead_beef_u64;
676 let mut pos = 0usize;
677 let mut hasher = algo.hasher();
678 while pos < data.len() {
679 state = state
680 .wrapping_mul(6364136223846793005)
681 .wrapping_add(1442695040888963407);
682 let remaining = data.len() - pos;
683 let chunk = if remaining == 1 {
684 1
685 } else {
686 (state as usize % remaining).max(1)
687 };
688 hasher.update(&data[pos..pos + chunk]);
689 pos += chunk;
690 }
691 assert_eq!(hasher.finalize().expect("finalize"), oneshot);
692 }
693 }
694
695 #[test]
696 fn object_header_encoding() {
697 let kinds = [
698 (ObjectKind::Blob, "blob"),
699 (ObjectKind::Tree, "tree"),
700 (ObjectKind::Commit, "commit"),
701 (ObjectKind::Tag, "tag"),
702 ];
703 let lens = [0_u64, 1, 4_294_967_297];
704 let cases: Vec<(ObjectKind, u64, String)> = kinds
705 .iter()
706 .flat_map(|&(kind, name)| {
707 lens.iter()
708 .map(move |&len| (kind, len, format!("{name} {len}\0")))
709 })
710 .collect();
711 for (kind, len, expect) in &cases {
712 let mut buf = [0u8; OBJECT_HEADER_BUF_LEN];
713 let n = write_object_header(&mut buf, *kind, *len);
714 assert_eq!(
715 &buf[..n],
716 expect.as_bytes(),
717 "header for {kind:?} len {len}"
718 );
719 }
720 }
721
722 #[test]
723 fn hash_object_all_kinds() {
724 for kind in [
725 ObjectKind::Blob,
726 ObjectKind::Tree,
727 ObjectKind::Commit,
728 ObjectKind::Tag,
729 ] {
730 for payload in [b"" as &[u8], b"x"] {
731 let mut hasher =
732 ObjectHasher::for_object(HashAlgo::Sha1, kind, payload.len() as u64);
733 hasher.update(payload);
734 assert_eq!(
735 hash_object(HashAlgo::Sha1, kind, payload).expect("hash"),
736 hasher.finalize().expect("finalize")
737 );
738 }
739 }
740 }
741
742 struct PartialWrite {
744 max: usize,
745 buf: Vec<u8>,
746 fail: bool,
747 }
748
749 impl Write for PartialWrite {
750 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
751 if self.fail {
752 return Err(io::Error::new(
753 io::ErrorKind::Other,
754 "simulated write failure",
755 ));
756 }
757 let n = buf.len().min(self.max);
758 self.buf.extend_from_slice(&buf[..n]);
759 Ok(n)
760 }
761
762 fn flush(&mut self) -> io::Result<()> {
763 Ok(())
764 }
765 }
766
767 #[test]
768 fn hashing_writer_partial_write_checksum() {
769 let inner = PartialWrite {
770 max: 2,
771 buf: Vec::new(),
772 fail: false,
773 };
774 let mut w = HashingWriter::new(HashAlgo::Sha1, inner);
775 let n = w.write(b"abcdef").expect("write");
776 assert_eq!(n, 2);
777 assert_eq!(w.get_ref().buf, b"ab");
778 let (_, digest) = w.finish().expect("finish");
779 assert_eq!(
780 digest.as_bytes(),
781 HashAlgo::Sha1.checksum_digest(b"ab").as_slice()
782 );
783 assert_ne!(
784 digest.as_bytes(),
785 HashAlgo::Sha1.checksum_digest(b"abcdef").as_slice()
786 );
787 }
788
789 #[test]
790 fn hashing_writer_write_error_does_not_update_checksum() {
791 let inner = PartialWrite {
792 max: 64,
793 buf: Vec::new(),
794 fail: true,
795 };
796 let mut w = HashingWriter::new(HashAlgo::Sha1, inner);
797 assert!(w.write(b"abcdef").is_err());
798 w.get_mut().fail = false;
799 w.write_all(b"ab").expect("write");
800 let (_, digest) = w.finish().expect("finish");
801 assert_eq!(
802 digest.as_bytes(),
803 HashAlgo::Sha1.checksum_digest(b"ab").as_slice()
804 );
805 }
806
807 #[test]
808 fn hashing_writer_trailer_matches_digest() {
809 let body = b"index payload bytes";
810 let mut buf = Vec::new();
811 {
812 let mut w = HashingWriter::new(HashAlgo::Sha1, Cursor::new(&mut buf));
813 w.write_all(body).unwrap();
814 let (_, digest) = w.finish().unwrap();
815 assert_eq!(
816 digest.as_bytes(),
817 HashAlgo::Sha1.checksum_digest(body).as_slice()
818 );
819 assert_eq!(&buf[body.len()..], digest.as_bytes());
820 }
821 verify_trailer(HashAlgo::Sha1, &buf).expect("trailer valid");
822 }
823
824 #[test]
825 fn verify_trailer_mismatch_is_typed() {
826 let body = b"payload";
827 let mut file = body.to_vec();
828 file.extend_from_slice(&[0u8; 20]);
829 let err = verify_trailer(HashAlgo::Sha1, &file).unwrap_err();
830 assert!(matches!(err, TrailerMismatch { .. }));
831 assert_ne!(err.expected, err.found);
832 }
833
834 #[test]
835 fn finalize_into_and_write_trait() {
836 let mut hasher = HashAlgo::Sha256.hasher();
837 hasher.write_all(b"via write trait").unwrap();
838 let mut out = [0u8; 32];
839 let n = hasher.finalize_into(&mut out).unwrap();
840 assert_eq!(n, 32);
841 assert_eq!(
842 HashAlgo::Sha256.digest(b"via write trait").expect("digest"),
843 ObjectId::from_bytes(&out).unwrap()
844 );
845 }
846
847 #[test]
848 fn backend_matches_cpu() {
849 let expected = {
850 #[cfg(target_arch = "x86_64")]
851 {
852 if std::arch::is_x86_feature_detected!("sha") {
853 Backend::X86ShaNi
854 } else {
855 Backend::Portable
856 }
857 }
858 #[cfg(target_arch = "aarch64")]
859 {
860 if std::arch::is_aarch64_feature_detected!("sha2") {
861 Backend::Aarch64Sha
862 } else {
863 Backend::Portable
864 }
865 }
866 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
867 {
868 Backend::Portable
869 }
870 };
871 assert_eq!(super::backend(HashAlgo::Sha1), expected);
872 }
873
874 #[test]
875 fn sha1_object_hasher_matches_sha1_crate_random() {
876 let mut state = 0x9e37_79b9_u64;
877 for len in [0_usize, 1, 63, 64, 65, 127, 128, 129, 512, 4096, 8192] {
878 let mut data = vec![0_u8; len];
879 for b in &mut data {
880 state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
881 *b = (state >> 32) as u8;
882 }
883 let mut plain = Sha1::new();
884 plain.update(&data);
885 let expected = plain.finalize();
886 let mut hasher = ObjectHasher::new(HashAlgo::Sha1);
887 hasher.update(&data);
888 assert_eq!(
889 hasher.finalize().expect("finalize").as_bytes(),
890 expected.as_slice(),
891 "len {len}"
892 );
893 }
894 }
895
896 #[test]
897 fn finalize_detects_shattered_prefix() {
898 let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/data");
899 for name in ["shattered-1-prefix.bin", "shattered-2-prefix.bin"] {
900 let bytes = std::fs::read(dir.join(name)).expect("fixture");
901 let mut hasher = ObjectHasher::new(HashAlgo::Sha1);
902 hasher.update(&bytes);
903 let err = hasher.finalize().expect_err("SHAttered block must collide");
904 assert!(matches!(err, Error::HashCollision { .. }));
905 assert!(matches!(
906 HashAlgo::Sha1.digest(&bytes),
907 Err(Error::HashCollision { .. })
908 ));
909
910 let mut plain = Sha1::new();
913 plain.update(format!("blob {}\0", bytes.len()).as_bytes());
914 plain.update(&bytes);
915 let oid = hash_object(HashAlgo::Sha1, ObjectKind::Blob, &bytes)
916 .expect("blob holding a SHAttered prefix is not a collision");
917 assert_eq!(oid.as_bytes(), plain.finalize().as_slice());
918 }
919 }
920
921 #[test]
922 fn sha256_finalize_never_collides() {
923 let data: Vec<u8> = (0..1024).map(|i| (i % 251) as u8).collect();
924 let mut hasher = ObjectHasher::new(HashAlgo::Sha256);
925 hasher.update(&data);
926 hasher
927 .finalize()
928 .expect("SHA-256 has no collision detection");
929 }
930
931 #[test]
932 fn sha256_backend_matches_sha2_dispatcher() {
933 #[cfg(target_arch = "x86_64")]
934 {
935 let got = super::backend(HashAlgo::Sha256);
936 if std::arch::is_x86_feature_detected!("sha") {
937 assert_eq!(got, Backend::X86ShaNi);
938 } else {
939 assert_eq!(got, Backend::Portable);
940 }
941 }
942 }
943}