diff --git a/src/uu/cksum/benches/cksum_bench.rs b/src/uu/cksum/benches/cksum_bench.rs index 5c2d3d9c614..81f70bbb3d1 100644 --- a/src/uu/cksum/benches/cksum_bench.rs +++ b/src/uu/cksum/benches/cksum_bench.rs @@ -104,7 +104,7 @@ bench_algorithm!(cksum_sha224, "sha224"); bench_algorithm!(cksum_sha256, "sha256"); bench_algorithm!(cksum_sha384, "sha384"); bench_algorithm!(cksum_sha512, "sha512"); -// broken. benchmarking error messages issues/10002 bench_algorithm!(cksum_blake3, "blake3"); +bench_algorithm!(cksum_blake3, "blake3"); bench_shake_algorithm!(cksum_shake128, "shake128", Shake128); bench_shake_algorithm!(cksum_shake256, "shake256", Shake256); diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 1e638d8bcdb..ce76612ac06 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -61,11 +61,14 @@ fn maybe_sanitize_length( // validation is performed on this length, any value is valid. If the // given length is not a multiple of 8, the last byte of the output // will have its extra bits set to zero. - (Some(AlgoKind::Shake128 | AlgoKind::Shake256), Some(len)) => match len.parse::() { - Ok(0) => Ok(None), - Ok(l) => Ok(Some(l)), - Err(_) => Err(ChecksumError::InvalidLength(len.into()).into()), - }, + // For BLAKE3, the length is interpreted as a byte length. + (Some(AlgoKind::Shake128 | AlgoKind::Shake256 | AlgoKind::Blake3), Some(len)) => { + match len.parse::() { + Ok(0) => Ok(None), + Ok(l) => Ok(Some(l)), + Err(_) => Err(ChecksumError::InvalidLength(len.into()).into()), + } + } // For BLAKE2b, if a length is provided, validate it. (Some(AlgoKind::Blake2b), Some(len)) => calculate_blake2b_length_str(len), diff --git a/src/uucore/src/lib/features/checksum/mod.rs b/src/uucore/src/lib/features/checksum/mod.rs index 1f77b7e635f..6c84cef42ca 100644 --- a/src/uucore/src/lib/features/checksum/mod.rs +++ b/src/uucore/src/lib/features/checksum/mod.rs @@ -115,6 +115,8 @@ impl AlgoKind { ALGORITHM_OPTIONS_SHA384 => Sha384, ALGORITHM_OPTIONS_SHA512 => Sha512, + // Extensions not in GNU as of version 9.10 + ALGORITHM_OPTIONS_BLAKE3 => Blake3, ALGORITHM_OPTIONS_SHAKE128 => Shake128, ALGORITHM_OPTIONS_SHAKE256 => Shake256, _ => return Err(ChecksumError::UnknownAlgorithm(algo.as_ref().to_string()).into()), @@ -245,11 +247,11 @@ pub enum SizedAlgoKind { Md5, Sm3, Sha1, - Blake3, Sha2(ShaLength), Sha3(ShaLength), - // Note: we store Blake2b's length as BYTES. + // Note: we store Blake*'s length as BYTES. Blake2b(Option), + Blake3(Option), // Shake* length are stored in bits. Shake128(Option), Shake256(Option), @@ -267,7 +269,6 @@ impl SizedAlgoKind { | ak::Md5 | ak::Sm3 | ak::Sha1 - | ak::Blake3 | ak::Sha224 | ak::Sha256 | ak::Sha384 @@ -282,8 +283,8 @@ impl SizedAlgoKind { (ak::Md5, _) => Ok(Self::Md5), (ak::Sm3, _) => Ok(Self::Sm3), (ak::Sha1, _) => Ok(Self::Sha1), - (ak::Blake3, _) => Ok(Self::Blake3), + (ak::Blake3, l) => Ok(Self::Blake3(l)), (ak::Shake128, l) => Ok(Self::Shake128(l)), (ak::Shake256, l) => Ok(Self::Shake256(l)), (ak::Sha2, Some(l)) => Ok(Self::Sha2(ShaLength::try_from(l)?)), @@ -310,11 +311,13 @@ impl SizedAlgoKind { Self::Md5 => "MD5".into(), Self::Sm3 => "SM3".into(), Self::Sha1 => "SHA1".into(), - Self::Blake3 => "BLAKE3".into(), Self::Sha2(len) => format!("SHA{}", len.as_usize()), Self::Sha3(len) => format!("SHA3-{}", len.as_usize()), Self::Blake2b(Some(byte_len)) => format!("BLAKE2b-{}", byte_len * 8), Self::Blake2b(None) => "BLAKE2b".into(), + Self::Blake3(byte_len) => { + format!("BLAKE3-{}", byte_len.unwrap_or(Blake3::DEFAULT_BYTE_SIZE)) + } Self::Shake128(opt_bit_len) => format!( "SHAKE128-{}", opt_bit_len.unwrap_or(Shake128::DEFAULT_BIT_SIZE) @@ -339,7 +342,6 @@ impl SizedAlgoKind { Self::Md5 => Box::new(Md5::default()), Self::Sm3 => Box::new(Sm3::default()), Self::Sha1 => Box::new(Sha1::default()), - Self::Blake3 => Box::new(Blake3::default()), Self::Sha2(Len224) => Box::new(Sha224::default()), Self::Sha2(Len256) => Box::new(Sha256::default()), Self::Sha2(Len384) => Box::new(Sha384::default()), @@ -351,6 +353,9 @@ impl SizedAlgoKind { Self::Blake2b(len_opt) => { Box::new(len_opt.map(Blake2b::with_output_bytes).unwrap_or_default()) } + Self::Blake3(len_opt) => { + Box::new(len_opt.map(Blake3::with_output_bytes).unwrap_or_default()) + } Self::Shake128(len_opt) => { Box::new(len_opt.map(Shake128::with_output_bits).unwrap_or_default()) } @@ -369,7 +374,7 @@ impl SizedAlgoKind { Self::Md5 => 128, Self::Sm3 => 512, Self::Sha1 => 160, - Self::Blake3 => 256, + Self::Blake3(len) => len.unwrap_or(Blake3::DEFAULT_BYTE_SIZE) * 8, Self::Sha2(len) => len.as_usize(), Self::Sha3(len) => len.as_usize(), Self::Blake2b(len) => len.unwrap_or(Blake2b::DEFAULT_BYTE_SIZE * 8), diff --git a/src/uucore/src/lib/features/checksum/validate.rs b/src/uucore/src/lib/features/checksum/validate.rs index 17b1c7bf522..5ce3c8c8c89 100644 --- a/src/uucore/src/lib/features/checksum/validate.rs +++ b/src/uucore/src/lib/features/checksum/validate.rs @@ -741,7 +741,7 @@ fn process_algo_based_line( // If the digest bitlen is known, we can check the format of the expected // checksum with it. let digest_char_length_hint = match (algo_kind, algo_byte_len) { - (AlgoKind::Blake2b, Some(byte_len)) => Some(byte_len), + (AlgoKind::Blake2b | AlgoKind::Blake3, Some(byte_len)) => Some(byte_len), (AlgoKind::Shake128 | AlgoKind::Shake256, Some(bit_len)) => Some(bit_len.div_ceil(8)), (AlgoKind::Shake128, None) => Some(sum::Shake128::DEFAULT_BIT_SIZE.div_ceil(8)), (AlgoKind::Shake256, None) => Some(sum::Shake256::DEFAULT_BIT_SIZE.div_ceil(8)), diff --git a/src/uucore/src/lib/features/sum.rs b/src/uucore/src/lib/features/sum.rs index 279643a962b..5b81e41d3d7 100644 --- a/src/uucore/src/lib/features/sum.rs +++ b/src/uucore/src/lib/features/sum.rs @@ -68,7 +68,8 @@ pub trait Digest { } fn result(&mut self) -> DigestOutput { - let mut buf: Vec = vec![0; self.output_bytes()]; + let out_bytes: usize = self.output_bytes(); + let mut buf: Vec = vec![0; out_bytes]; self.hash_finalize(&mut buf); DigestOutput::Vec(buf) } @@ -122,25 +123,48 @@ impl Digest for Blake2b { } } -#[derive(Default)] -pub struct Blake3(blake3::Hasher); +pub struct Blake3 { + digest: blake3::Hasher, + byte_size: usize, +} + +impl Blake3 { + /// Default length for the BLAKE3 digest in bytes. + pub const DEFAULT_BYTE_SIZE: usize = 32; + + pub fn with_output_bytes(output_bytes: usize) -> Self { + Self { + digest: blake3::Hasher::new(), + byte_size: output_bytes, + } + } +} + +impl Default for Blake3 { + fn default() -> Self { + Self { + digest: blake3::Hasher::default(), + byte_size: Self::DEFAULT_BYTE_SIZE, + } + } +} impl Digest for Blake3 { fn hash_update(&mut self, input: &[u8]) { - self.0.update(input); + self.digest.update(input); } fn hash_finalize(&mut self, out: &mut [u8]) { - let hash_result = &self.0.finalize(); - out.copy_from_slice(hash_result.as_bytes()); + let mut hash_result = self.digest.finalize_xof(); + hash_result.fill(out); } fn reset(&mut self) { - *self = Self::default(); + *self = Self::with_output_bytes(self.output_bytes()); } fn output_bits(&self) -> usize { - 256 + self.byte_size * 8 } } diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 67f96e90762..5f40ff1a71b 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -3287,3 +3287,62 @@ fn test_check_shake256_no_length() { .fails() .stderr_only("cksum: 'standard input': no properly formatted checksum lines found\n"); } + +#[rstest] +#[case( + b"foo", + "04e0bb39f30b1a3feb89f536c93be15055482df748674b00d26e5a75777702e9" +)] +fn test_blake3b_no_length(#[case] input: &[u8], #[case] expected: &str) { + new_ucmd!() + .arg("-a") + .arg("blake3") + .pipe_in(input) + .succeeds() + .stdout_only(format!("BLAKE3-32 (-) = {expected}\n")); +} + +#[rstest] +#[case( + b"foo", + "04e0bb39f30b1a3feb89f536c93be15055482df748674b00d26e5a75777702e9", + 0 +)] +#[case( + b"foo", + "04e0bb39f30b1a3feb89f536c93be15055482df748674b00d26e5a75777702e9", + 32 +)] +#[case( + b"foo", + "04e0bb39f30b1a3feb89f536c93be15055482df748674b00d26e5a75777702e9791074b7511b59d31c71c62f5a745689fa6c", + 50 +)] +#[case(b"foo", "04e0bb39f3", 5)] +#[case(b"foo", "04e0", 2)] +#[case(b"foo", "04", 1)] +fn test_blake3b(#[case] input: &[u8], #[case] expected: &str, #[case] length: usize) { + new_ucmd!() + .arg("-a") + .arg("blake3") + .args(&["-l", &length.to_string()]) + .pipe_in(input) + .succeeds() + .stdout_only(format!( + "BLAKE3-{} (-) = {expected}\n", + match length { + 0 => "32".to_string(), + i => i.to_string(), + } + )); + + // with --untagged + new_ucmd!() + .arg("-a") + .arg("blake3") + .arg("--untagged") + .args(&["-l", &length.to_string()]) + .pipe_in(input) + .succeeds() + .stdout_only(format!("{expected} -\n")); +}