From 413e71de0e88dcb2ac9a7aee24bc61eb5c5d7ad1 Mon Sep 17 00:00:00 2001 From: Dorian Peron Date: Mon, 3 Nov 2025 23:25:22 +0100 Subject: [PATCH] checksum: Introduce `SizedAlgoKind` to improve representation and cleanup --- src/uu/cksum/src/cksum.rs | 42 ++--- src/uu/hashsum/src/hashsum.rs | 26 +-- src/uucore/src/lib/features/checksum.rs | 211 ++++++++++++++++++------ 3 files changed, 183 insertions(+), 96 deletions(-) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index cdd9de33f..8aef74ba5 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -13,11 +13,9 @@ use std::io::{BufReader, Read, Write, stdin, stdout}; use std::iter; use std::path::Path; use uucore::checksum::{ - ALGORITHM_OPTIONS_BLAKE2B, ALGORITHM_OPTIONS_BSD, ALGORITHM_OPTIONS_CRC, - ALGORITHM_OPTIONS_CRC32B, ALGORITHM_OPTIONS_SYSV, AlgoKind, ChecksumError, ChecksumOptions, - ChecksumVerbose, HashAlgorithm, LEGACY_ALGORITHMS, SUPPORTED_ALGORITHMS, - calculate_blake2b_length_str, detect_algo, digest_reader, perform_checksum_validation, - sanitize_sha2_sha3_length_str, + AlgoKind, ChecksumError, ChecksumOptions, ChecksumVerbose, HashAlgorithm, SUPPORTED_ALGORITHMS, + SizedAlgoKind, calculate_blake2b_length_str, detect_algo, digest_reader, + perform_checksum_validation, sanitize_sha2_sha3_length_str, }; use uucore::translate; @@ -31,10 +29,9 @@ use uucore::{ }; struct Options { - algo_name: &'static str, + algo_kind: SizedAlgoKind, digest: Box, output_bits: usize, - length: Option, output_format: OutputFormat, line_ending: LineEnding, } @@ -108,16 +105,16 @@ fn print_legacy_checksum( sum: &str, size: usize, ) -> UResult<()> { - debug_assert!(LEGACY_ALGORITHMS.contains(&options.algo_name)); + debug_assert!(options.algo_kind.is_legacy()); // Print the sum - match options.algo_name { - ALGORITHM_OPTIONS_SYSV => print!( + match options.algo_kind { + SizedAlgoKind::Sysv => print!( "{} {}", sum.parse::().unwrap(), size.div_ceil(options.output_bits), ), - ALGORITHM_OPTIONS_BSD => { + SizedAlgoKind::Bsd => { // The BSD checksum output is 5 digit integer let bsd_width = 5; print!( @@ -126,7 +123,7 @@ fn print_legacy_checksum( size.div_ceil(options.output_bits), ); } - ALGORITHM_OPTIONS_CRC | ALGORITHM_OPTIONS_CRC32B => { + SizedAlgoKind::Crc | SizedAlgoKind::Crc32b => { print!("{sum} {size}"); } _ => unreachable!("Not a legacy algorithm"), @@ -143,15 +140,7 @@ fn print_legacy_checksum( fn print_tagged_checksum(options: &Options, filename: &OsStr, sum: &String) -> UResult<()> { // Print algo name and opening parenthesis. - print!( - "{} (", - match (options.algo_name, options.length) { - // Multiply the length by 8, as we want to print the length in bits. - (ALGORITHM_OPTIONS_BLAKE2B, Some(l)) => format!("BLAKE2b-{}", l * 8), - (ALGORITHM_OPTIONS_BLAKE2B, None) => "BLAKE2b".into(), - (name, _) => name.to_ascii_uppercase(), - } - ); + print!("{} (", options.algo_kind.to_tag()); // Print filename let _dropped_result = stdout().write_all(os_str_as_bytes(filename)?); @@ -235,11 +224,11 @@ where match options.output_format { OutputFormat::Raw => { - let bytes = match options.algo_name { - ALGORITHM_OPTIONS_CRC | ALGORITHM_OPTIONS_CRC32B => { + let bytes = match options.algo_kind { + SizedAlgoKind::Crc | SizedAlgoKind::Crc32b => { sum_hex.parse::().unwrap().to_be_bytes().to_vec() } - ALGORITHM_OPTIONS_SYSV | ALGORITHM_OPTIONS_BSD => { + SizedAlgoKind::Sysv | SizedAlgoKind::Bsd => { sum_hex.parse::().unwrap().to_be_bytes().to_vec() } _ => hex::decode(sum_hex).unwrap(), @@ -343,7 +332,7 @@ fn figure_out_output_format( } // Then, if the algo is legacy, takes precedence over the rest - if LEGACY_ALGORITHMS.contains(&algo.name) { + if algo.kind.is_legacy() { return OutputFormat::Legacy; } @@ -465,10 +454,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { ); let opts = Options { - algo_name: algo.name, + algo_kind: algo.kind, digest: (algo.create_fn)(), output_bits: algo.bits, - length, output_format, line_ending, }; diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 4e0206252..dfd3caf7c 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -15,7 +15,6 @@ use std::io::{BufReader, Read, stdin}; use std::iter; use std::num::ParseIntError; use std::path::Path; -use uucore::checksum::ChecksumOptions; use uucore::checksum::ChecksumVerbose; use uucore::checksum::calculate_blake2b_length; use uucore::checksum::detect_algo; @@ -23,6 +22,7 @@ use uucore::checksum::digest_reader; use uucore::checksum::escape_filename; use uucore::checksum::perform_checksum_validation; use uucore::checksum::{AlgoKind, ChecksumError}; +use uucore::checksum::{ChecksumOptions, SizedAlgoKind}; use uucore::error::{UResult, strip_errno}; use uucore::format_usage; use uucore::sum::Digest; @@ -33,7 +33,7 @@ const NAME: &str = "hashsum"; const READ_BUFFER_SIZE: usize = 32 * 1024; struct Options<'a> { - algoname: &'static str, + algo: SizedAlgoKind, digest: Box, binary: bool, binary_name: &'a str, @@ -246,7 +246,7 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> { let algo = detect_algo(algo_kind, length)?; let opts = Options { - algoname: algo.name, + algo: algo.kind, digest: (algo.create_fn)(), output_bits: algo.bits, binary, @@ -549,22 +549,10 @@ where let (escaped_filename, prefix) = escape_filename(filename); if options.tag { - if options.algoname == "blake2b" { - if options.digest.output_bits() == 512 { - println!("BLAKE2b ({escaped_filename}) = {sum}"); - } else { - // special case for BLAKE2b with non-default output length - println!( - "BLAKE2b-{} ({escaped_filename}) = {sum}", - options.digest.output_bits() - ); - } - } else { - println!( - "{prefix}{} ({escaped_filename}) = {sum}", - options.algoname.to_ascii_uppercase() - ); - } + println!( + "{prefix}{} ({escaped_filename}) = {sum}", + options.algo.to_tag() + ); } else if options.nonames { println!("{sum}"); } else if options.zero { diff --git a/src/uucore/src/lib/features/checksum.rs b/src/uucore/src/lib/features/checksum.rs index e71d8135c..3437a0a6e 100644 --- a/src/uucore/src/lib/features/checksum.rs +++ b/src/uucore/src/lib/features/checksum.rs @@ -72,13 +72,6 @@ pub const SUPPORTED_ALGORITHMS: [&str; 17] = [ ALGORITHM_OPTIONS_SHAKE256, ]; -pub const LEGACY_ALGORITHMS: [&str; 4] = [ - ALGORITHM_OPTIONS_SYSV, - ALGORITHM_OPTIONS_BSD, - ALGORITHM_OPTIONS_CRC, - ALGORITHM_OPTIONS_CRC32B, -]; - /// Represents an algorithm kind. In some cases, it is not sufficient by itself /// to know which algorithm to use exactly, because it lacks a digest length, /// which is why [`SizedAlgoKind`] exists. @@ -212,8 +205,127 @@ impl AlgoKind { } } +/// Holds a length for a SHA2 of SHA3 algorithm kind. +#[derive(Debug, Clone, Copy)] +pub enum ShaLength { + Len224, + Len256, + Len384, + Len512, +} + +impl ShaLength { + pub fn as_usize(self) -> usize { + match self { + Self::Len224 => 224, + Self::Len256 => 256, + Self::Len384 => 384, + Self::Len512 => 512, + } + } +} + +impl TryFrom for ShaLength { + type Error = ChecksumError; + + fn try_from(value: usize) -> Result { + use ShaLength::*; + match value { + 224 => Ok(Len224), + 256 => Ok(Len256), + 384 => Ok(Len384), + 512 => Ok(Len512), + _ => Err(ChecksumError::InvalidLengthForSha(value.to_string())), + } + } +} + +/// Represents an actual determined algorithm. +#[derive(Debug, Clone, Copy)] +pub enum SizedAlgoKind { + Sysv, + Bsd, + Crc, + Crc32b, + Md5, + Sm3, + Sha1, + Blake3, + Sha2(ShaLength), + Sha3(ShaLength), + Blake2b(Option), + Shake128(usize), + Shake256(usize), +} + +impl SizedAlgoKind { + pub fn from_unsized(kind: AlgoKind, length: Option) -> UResult { + use AlgoKind as ak; + match (kind, length) { + ( + ak::Sysv + | ak::Bsd + | ak::Crc + | ak::Crc32b + | ak::Md5 + | ak::Sm3 + | ak::Sha1 + | ak::Blake3 + | ak::Sha224 + | ak::Sha256 + | ak::Sha384 + | ak::Sha512, + Some(_), + ) => Err(ChecksumError::LengthOnlyForBlake2bSha2Sha3.into()), + + (ak::Sysv, _) => Ok(Self::Sysv), + (ak::Bsd, _) => Ok(Self::Bsd), + (ak::Crc, _) => Ok(Self::Crc), + (ak::Crc32b, _) => Ok(Self::Crc32b), + (ak::Md5, _) => Ok(Self::Md5), + (ak::Sm3, _) => Ok(Self::Sm3), + (ak::Sha1, _) => Ok(Self::Sha1), + (ak::Blake3, _) => Ok(Self::Blake3), + + (ak::Shake128, Some(l)) => Ok(Self::Shake128(l)), + (ak::Shake256, Some(l)) => Ok(Self::Shake256(l)), + (ak::Sha2, Some(l)) => Ok(Self::Sha2(ShaLength::try_from(l)?)), + (ak::Sha3, Some(l)) => Ok(Self::Sha3(ShaLength::try_from(l)?)), + (ak::Blake2b, Some(l)) => Ok(Self::Blake2b(calculate_blake2b_length(l)?)), + + (ak::Sha224, None) => Ok(Self::Sha2(ShaLength::Len224)), + (ak::Sha256, None) => Ok(Self::Sha2(ShaLength::Len256)), + (ak::Sha384, None) => Ok(Self::Sha2(ShaLength::Len384)), + (ak::Sha512, None) => Ok(Self::Sha2(ShaLength::Len512)), + (_, None) => Err(ChecksumError::LengthRequired(kind.to_uppercase().into()).into()), + } + } + + pub fn to_tag(&self) -> String { + use SizedAlgoKind::*; + match self { + Md5 => "MD5".into(), + Sm3 => "SM3".into(), + Sha1 => "SHA1".into(), + Blake3 => "BLAKE3".into(), + Sha2(len) => format!("SHA{}", len.as_usize()), + Sha3(len) => format!("SHA3-{}", len.as_usize()), + Blake2b(Some(len)) => format!("BLAKE2b-{}", len * 8), + Blake2b(None) => "BLAKE2b".into(), + Shake128(_) => "SHAKE128".into(), + Shake256(_) => "SHAKE256".into(), + Sysv | Bsd | Crc | Crc32b => panic!("Should not be used for tagging"), + } + } + + pub fn is_legacy(&self) -> bool { + use SizedAlgoKind::*; + matches!(self, Sysv | Bsd | Crc | Crc32b) + } +} + pub struct HashAlgorithm { - pub name: &'static str, + pub kind: SizedAlgoKind, pub create_fn: Box Box>, pub bits: usize, } @@ -399,57 +511,53 @@ impl UError for ChecksumError { /// /// Returns a `UResult` with an `HashAlgorithm` or an `Err` if an unsupported /// output size is provided. -pub fn create_sha3(bits: usize) -> UResult { - match bits { - 224 => Ok(HashAlgorithm { - name: "SHA3-224", +pub fn create_sha3(len: ShaLength) -> UResult { + match len { + ShaLength::Len224 => Ok(HashAlgorithm { + kind: SizedAlgoKind::Sha3(ShaLength::Len224), create_fn: Box::new(|| Box::new(Sha3_224::new())), bits: 224, }), - 256 => Ok(HashAlgorithm { - name: "SHA3-256", + ShaLength::Len256 => Ok(HashAlgorithm { + kind: SizedAlgoKind::Sha3(ShaLength::Len256), create_fn: Box::new(|| Box::new(Sha3_256::new())), bits: 256, }), - 384 => Ok(HashAlgorithm { - name: "SHA3-384", + ShaLength::Len384 => Ok(HashAlgorithm { + kind: SizedAlgoKind::Sha3(ShaLength::Len384), create_fn: Box::new(|| Box::new(Sha3_384::new())), bits: 384, }), - 512 => Ok(HashAlgorithm { - name: "SHA3-512", + ShaLength::Len512 => Ok(HashAlgorithm { + kind: SizedAlgoKind::Sha3(ShaLength::Len512), create_fn: Box::new(|| Box::new(Sha3_512::new())), bits: 512, }), - - _ => Err(ChecksumError::InvalidLengthForSha("SHA3".into()).into()), } } -pub fn create_sha2(bits: usize) -> UResult { - match bits { - 224 => Ok(HashAlgorithm { - name: "SHA224", +pub fn create_sha2(len: ShaLength) -> UResult { + match len { + ShaLength::Len224 => Ok(HashAlgorithm { + kind: SizedAlgoKind::Sha2(ShaLength::Len224), create_fn: Box::new(|| Box::new(Sha224::new())), bits: 224, }), - 256 => Ok(HashAlgorithm { - name: "SHA256", + ShaLength::Len256 => Ok(HashAlgorithm { + kind: SizedAlgoKind::Sha2(ShaLength::Len256), create_fn: Box::new(|| Box::new(Sha256::new())), bits: 256, }), - 384 => Ok(HashAlgorithm { - name: "SHA384", + ShaLength::Len384 => Ok(HashAlgorithm { + kind: SizedAlgoKind::Sha2(ShaLength::Len384), create_fn: Box::new(|| Box::new(Sha384::new())), bits: 384, }), - 512 => Ok(HashAlgorithm { - name: "SHA512", + ShaLength::Len512 => Ok(HashAlgorithm { + kind: SizedAlgoKind::Sha2(ShaLength::Len512), create_fn: Box::new(|| Box::new(Sha512::new())), bits: 512, }), - - _ => Err(ChecksumError::InvalidLengthForSha("SHA2".into()).into()), } } @@ -540,63 +648,63 @@ fn print_file_report( pub fn detect_algo(algo: AlgoKind, length: Option) -> UResult { match algo { AlgoKind::Sysv => Ok(HashAlgorithm { - name: ALGORITHM_OPTIONS_SYSV, + kind: SizedAlgoKind::Sysv, create_fn: Box::new(|| Box::new(SysV::new())), bits: 512, }), AlgoKind::Bsd => Ok(HashAlgorithm { - name: ALGORITHM_OPTIONS_BSD, + kind: SizedAlgoKind::Bsd, create_fn: Box::new(|| Box::new(Bsd::new())), bits: 1024, }), AlgoKind::Crc => Ok(HashAlgorithm { - name: ALGORITHM_OPTIONS_CRC, + kind: SizedAlgoKind::Crc, create_fn: Box::new(|| Box::new(Crc::new())), bits: 256, }), AlgoKind::Crc32b => Ok(HashAlgorithm { - name: ALGORITHM_OPTIONS_CRC32B, + kind: SizedAlgoKind::Crc32b, create_fn: Box::new(|| Box::new(CRC32B::new())), bits: 32, }), AlgoKind::Md5 => Ok(HashAlgorithm { - name: ALGORITHM_OPTIONS_MD5, + kind: SizedAlgoKind::Md5, create_fn: Box::new(|| Box::new(Md5::new())), bits: 128, }), AlgoKind::Sha1 => Ok(HashAlgorithm { - name: ALGORITHM_OPTIONS_SHA1, + kind: SizedAlgoKind::Sha1, create_fn: Box::new(|| Box::new(Sha1::new())), bits: 160, }), - AlgoKind::Sha224 => Ok(create_sha2(224)?), - AlgoKind::Sha256 => Ok(create_sha2(256)?), - AlgoKind::Sha384 => Ok(create_sha2(384)?), - AlgoKind::Sha512 => Ok(create_sha2(512)?), + AlgoKind::Sha224 => Ok(create_sha2(ShaLength::Len224)?), + AlgoKind::Sha256 => Ok(create_sha2(ShaLength::Len256)?), + AlgoKind::Sha384 => Ok(create_sha2(ShaLength::Len384)?), + AlgoKind::Sha512 => Ok(create_sha2(ShaLength::Len512)?), AlgoKind::Blake2b => { // Set default length to 512 if None let bits = length.unwrap_or(512); if bits == 512 { Ok(HashAlgorithm { - name: ALGORITHM_OPTIONS_BLAKE2B, + kind: SizedAlgoKind::Blake2b(None), create_fn: Box::new(move || Box::new(Blake2b::new())), bits: 512, }) } else { Ok(HashAlgorithm { - name: ALGORITHM_OPTIONS_BLAKE2B, + kind: SizedAlgoKind::Blake2b(Some(bits)), create_fn: Box::new(move || Box::new(Blake2b::with_output_bytes(bits))), bits, }) } } AlgoKind::Blake3 => Ok(HashAlgorithm { - name: ALGORITHM_OPTIONS_BLAKE3, + kind: SizedAlgoKind::Blake3, create_fn: Box::new(|| Box::new(Blake3::new())), bits: 256, }), AlgoKind::Sm3 => Ok(HashAlgorithm { - name: ALGORITHM_OPTIONS_SM3, + kind: SizedAlgoKind::Sm3, create_fn: Box::new(|| Box::new(Sm3::new())), bits: 512, }), @@ -605,7 +713,7 @@ pub fn detect_algo(algo: AlgoKind, length: Option) -> UResult) -> UResult UResult> { } } -pub fn validate_sha2_sha3_length(algo_name: AlgoKind, length: Option) -> UResult { +pub fn validate_sha2_sha3_length(algo_name: AlgoKind, length: Option) -> UResult { match length { - Some(len @ (224 | 256 | 384 | 512)) => Ok(len), + Some(224) => Ok(ShaLength::Len224), + Some(256) => Ok(ShaLength::Len256), + Some(384) => Ok(ShaLength::Len384), + Some(512) => Ok(ShaLength::Len512), Some(len) => { show_error!("{}", ChecksumError::InvalidLength(len.to_string())); Err(ChecksumError::InvalidLengthForSha(algo_name.to_uppercase().into()).into())