From 678a11dcf299a40df324639bb9920080971ab21e Mon Sep 17 00:00:00 2001 From: Yang Hau Date: Mon, 13 Feb 2023 11:40:16 +0800 Subject: [PATCH] cksum: Implement option -a Implement option -a --algorithm. Move digest to src/uucore/src/lib/features and rename it to hash. fix lint fix Cargo.toml --- Cargo.lock | 27 ++ Cargo.toml | 10 + src/uu/cksum/Cargo.toml | 9 +- src/uu/cksum/src/cksum.rs | 237 +++++++++++------- src/uu/hashsum/Cargo.toml | 21 +- src/uu/hashsum/src/hashsum.rs | 8 +- src/uucore/Cargo.toml | 12 + src/uucore/src/lib/features.rs | 2 + .../src/lib/features/sum.rs} | 232 +++++++++++++++-- src/uucore/src/lib/lib.rs | 2 + tests/by-util/test_cksum.rs | 76 ++++++ .../cksum/bsd_multiple_files.expected | 2 + tests/fixtures/cksum/bsd_single_file.expected | 1 + tests/fixtures/cksum/bsd_stdin.expected | 1 + .../cksum/sysv_multiple_files.expected | 2 + .../fixtures/cksum/sysv_single_file.expected | 1 + tests/fixtures/cksum/sysv_stdin.expected | 1 + 17 files changed, 509 insertions(+), 135 deletions(-) rename src/{uu/hashsum/src/digest.rs => uucore/src/lib/features/sum.rs} (58%) create mode 100644 tests/fixtures/cksum/bsd_multiple_files.expected create mode 100644 tests/fixtures/cksum/bsd_single_file.expected create mode 100644 tests/fixtures/cksum/bsd_stdin.expected create mode 100644 tests/fixtures/cksum/sysv_multiple_files.expected create mode 100644 tests/fixtures/cksum/sysv_single_file.expected create mode 100644 tests/fixtures/cksum/sysv_stdin.expected diff --git a/Cargo.lock b/Cargo.lock index 6d210d303..f08af896a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2053,6 +2053,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "sm3" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f943a7c5e3089f2bd046221d1e9f4fa59396bf0fe966360983649683086215da" +dependencies = [ + "digest", +] + [[package]] name = "smallvec" version = "1.10.0" @@ -2390,7 +2399,14 @@ dependencies = [ name = "uu_cksum" version = "0.0.17" dependencies = [ + "blake2b_simd", + "blake3", "clap", + "hex", + "md-5", + "sha1", + "sha2", + "sm3", "uucore", ] @@ -2606,6 +2622,7 @@ dependencies = [ "sha1", "sha2", "sha3", + "sm3", "uucore", ] @@ -3299,17 +3316,27 @@ dependencies = [ name = "uucore" version = "0.0.17" dependencies = [ + "blake2b_simd", + "blake3", "clap", "data-encoding", "data-encoding-macro", + "digest", "dns-lookup", "dunce", "glob", + "hex", "itertools", "libc", + "md-5", + "memchr", "nix", "once_cell", "os_display", + "sha1", + "sha2", + "sha3", + "sm3", "thiserror", "time", "uucore_procs", diff --git a/Cargo.toml b/Cargo.toml index ebd8685ca..d84e7daf9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -332,6 +332,16 @@ windows-sys = { version="0.42.0", default-features=false } xattr = "0.2.3" zip = { version = "0.6.3", default_features=false, features=["deflate"] } +hex = "0.4.3" +md-5 = "0.10.5" +sha1 = "0.10.1" +sha2 = "0.10.2" +sha3 = "0.10.6" +blake2b_simd = "1.0.1" +blake3 = "1.3.2" +sm3 = "0.4.1" +digest = "0.10.6" + uucore = { version=">=0.0.17", package="uucore", path="src/uucore" } uucore_procs = { version=">=0.0.17", package="uucore_procs", path="src/uucore_procs" } uu_ls = { version=">=0.0.17", path="src/uu/ls" } diff --git a/src/uu/cksum/Cargo.toml b/src/uu/cksum/Cargo.toml index 4719af074..20406a3f0 100644 --- a/src/uu/cksum/Cargo.toml +++ b/src/uu/cksum/Cargo.toml @@ -16,7 +16,14 @@ path = "src/cksum.rs" [dependencies] clap = { workspace=true } -uucore = { workspace=true } +uucore = { version=">=0.0.17", package="uucore", path="../../uucore", features=["sum"] } +hex = { workspace=true } +md-5 = { workspace=true } +sha1 = { workspace=true } +sha2 = { workspace=true } +blake2b_simd = { workspace=true } +blake3 = { workspace=true } +sm3 = { workspace=true } [[bin]] name = "cksum" diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index f567d8bf8..146fe0d56 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -5,109 +5,147 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) fname +// spell-checker:ignore (ToDO) fname, algo use clap::{crate_version, Arg, Command}; +use hex::encode; +use md5::Md5; +use sha1::Sha1; +use sha2::{Sha224, Sha256, Sha384, Sha512}; +use std::ffi::OsStr; use std::fs::File; use std::io::{self, stdin, BufReader, Read}; +use std::iter; use std::path::Path; -use uucore::display::Quotable; -use uucore::error::{FromIo, UResult}; -use uucore::{format_usage, show}; - -// NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8 -const CRC_TABLE_LEN: usize = 256; -const CRC_TABLE: [u32; CRC_TABLE_LEN] = generate_crc_table(); +use uucore::{ + error::{FromIo, UResult}, + format_usage, + sum::{div_ceil, Digest, DigestWriter, BSD, CRC, SYSV}, +}; const USAGE: &str = "{} [OPTIONS] [FILE]..."; const ABOUT: &str = "Print CRC and size for each file"; -const fn generate_crc_table() -> [u32; CRC_TABLE_LEN] { - let mut table = [0; CRC_TABLE_LEN]; +fn detect_algo(program: &str) -> (&'static str, Box, usize) { + match program { + "sysv" => ("SYSV", Box::new(SYSV::new()) as Box, 512), + "bsd" => ("BSD", Box::new(BSD::new()) as Box, 1024), + "crc" => ("CRC", Box::new(CRC::new()) as Box, 256), + "md5" => ("MD5", Box::new(Md5::new()) as Box, 128), + "sha1" => ("SHA1", Box::new(Sha1::new()) as Box, 160), + "sha224" => ("SHA224", Box::new(Sha224::new()) as Box, 224), + "sha256" => ("SHA256", Box::new(Sha256::new()) as Box, 256), + "sha384" => ("SHA384", Box::new(Sha384::new()) as Box, 384), + "sha512" => ("SHA512", Box::new(Sha512::new()) as Box, 512), + "blake2b" => ( + "BLAKE2", + Box::new(blake2b_simd::State::new()) as Box, + 512, + ), + "sm3" => ("SM3", Box::new(sm3::Sm3::new()) as Box, 512), + _ => panic!("unknown algorithm"), + } +} - let mut i = 0; - while i < CRC_TABLE_LEN { - table[i] = crc_entry(i as u8); +struct Options { + algo_name: &'static str, + digest: Box, + output_bits: usize, +} - i += 1; +#[allow(clippy::cognitive_complexity)] +fn cksum<'a, I>(mut options: Options, files: I) -> UResult<()> +where + I: Iterator, +{ + for filename in files { + let filename = Path::new(filename); + let stdin_buf; + let file_buf; + let not_file = filename == OsStr::new("-"); + let mut file = BufReader::new(if not_file { + stdin_buf = stdin(); + Box::new(stdin_buf) as Box + } else if filename.is_dir() { + Box::new(BufReader::new(io::empty())) as Box + } else { + file_buf = + File::open(filename).map_err_context(|| filename.to_str().unwrap().to_string())?; + Box::new(file_buf) as Box + }); + let (sum, sz) = digest_read(&mut options.digest, &mut file, options.output_bits) + .map_err_context(|| "failed to read input".to_string())?; + + // Refer to GNU sum.c implementation. The BSD checksum output is 5 digit integer + // https://github.com/coreutils/coreutils/blob/master/src/sum.c + let bsd_width = 5; + match (options.algo_name, not_file) { + ("SYSV", true) => println!( + "{} {}", + sum.parse::().unwrap(), + div_ceil(sz, options.output_bits) + ), + ("SYSV", false) => println!( + "{} {} {}", + sum.parse::().unwrap(), + div_ceil(sz, options.output_bits), + filename.display() + ), + ("BSD", true) => println!( + "{:0bsd_width$} {:bsd_width$}", + sum.parse::().unwrap(), + div_ceil(sz, options.output_bits) + ), + ("BSD", false) => println!( + "{:0bsd_width$} {:bsd_width$} {}", + sum.parse::().unwrap(), + div_ceil(sz, options.output_bits), + filename.display() + ), + (_, true) => println!("{sum} {sz}"), + (_, false) => println!("{sum} {sz} {}", filename.display()), + } } - table + Ok(()) } -const fn crc_entry(input: u8) -> u32 { - let mut crc = (input as u32) << 24; +fn digest_read( + digest: &mut Box, + reader: &mut BufReader, + output_bits: usize, +) -> io::Result<(String, usize)> { + digest.reset(); - let mut i = 0; - while i < 8 { - let if_condition = crc & 0x8000_0000; - let if_body = (crc << 1) ^ 0x04c1_1db7; - let else_body = crc << 1; + // Read bytes from `reader` and write those bytes to `digest`. + // + // If `binary` is `false` and the operating system is Windows, then + // `DigestWriter` replaces "\r\n" with "\n" before it writes the + // bytes into `digest`. Otherwise, it just inserts the bytes as-is. + // + // In order to support replacing "\r\n", we must call `finalize()` + // in order to support the possibility that the last character read + // from the reader was "\r". (This character gets buffered by + // `DigestWriter` and only written if the following character is + // "\n". But when "\r" is the last character read, we need to force + // it to be written.) + let mut digest_writer = DigestWriter::new(digest, true); + let output_size = std::io::copy(reader, &mut digest_writer)? as usize; + digest_writer.finalize(); - // NOTE: i feel like this is easier to understand than emulating an if statement in bitwise - // ops - let condition_table = [else_body, if_body]; - - crc = condition_table[(if_condition != 0) as usize]; - i += 1; - } - - crc -} - -#[inline] -fn crc_update(crc: u32, input: u8) -> u32 { - (crc << 8) ^ CRC_TABLE[((crc >> 24) as usize ^ input as usize) & 0xFF] -} - -#[inline] -fn crc_final(mut crc: u32, mut length: usize) -> u32 { - while length != 0 { - crc = crc_update(crc, length as u8); - length >>= 8; - } - - !crc -} - -fn init_byte_array() -> Vec { - vec![0; 1024 * 1024] -} - -#[inline] -fn cksum(fname: &str) -> io::Result<(u32, usize)> { - let mut crc = 0u32; - let mut size = 0usize; - - let mut rd: Box = match fname { - "-" => Box::new(stdin()), - _ => { - let p = Path::new(fname); - - // Directories should not give an error, but should be interpreted - // as empty files to match GNU semantics. - if p.is_dir() { - Box::new(BufReader::new(io::empty())) as Box - } else { - Box::new(BufReader::new(File::open(p)?)) as Box - } - } - }; - - let mut bytes = init_byte_array(); - loop { - let num_bytes = rd.read(&mut bytes)?; - if num_bytes == 0 { - return Ok((crc_final(crc, size), size)); - } - for &b in bytes[..num_bytes].iter() { - crc = crc_update(crc, b); - } - size += num_bytes; + if digest.output_bits() > 0 { + Ok((digest.result_str(), output_size)) + } else { + // Assume it's SHAKE. result_str() doesn't work with shake (as of 8/30/2016) + let mut bytes = Vec::new(); + bytes.resize((output_bits + 7) / 8, 0); + digest.hash_finalize(&mut bytes); + Ok((encode(bytes), output_size)) } } mod options { pub static FILE: &str = "file"; + pub static ALGORITHM: &str = "algorithm"; } #[uucore::main] @@ -116,23 +154,23 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args)?; - let files: Vec = match matches.get_many::(options::FILE) { - Some(v) => v.clone().map(|v| v.to_owned()).collect(), - None => vec![], + let algo_name: &str = match matches.get_one::(options::ALGORITHM) { + Some(v) => v, + None => "crc", }; - if files.is_empty() { - let (crc, size) = cksum("-")?; - println!("{crc} {size}"); - return Ok(()); - } + let (name, algo, bits) = detect_algo(algo_name); + let opts = Options { + algo_name: name, + digest: algo, + output_bits: bits, + }; + + match matches.get_many::(options::FILE) { + Some(files) => cksum(opts, files.map(OsStr::new))?, + None => cksum(opts, iter::once(OsStr::new("-")))?, + }; - for fname in &files { - match cksum(fname.as_ref()).map_err_context(|| format!("{}", fname.maybe_quote())) { - Ok((crc, size)) => println!("{crc} {size} {fname}"), - Err(err) => show!(err), - }; - } Ok(()) } @@ -148,4 +186,11 @@ pub fn uu_app() -> Command { .action(clap::ArgAction::Append) .value_hint(clap::ValueHint::FilePath), ) + .arg( + Arg::new(options::ALGORITHM) + .long(options::ALGORITHM) + .short('a') + .help("select the digest type to use. See DIGEST below") + .value_name("ALGORITHM"), + ) } diff --git a/src/uu/hashsum/Cargo.toml b/src/uu/hashsum/Cargo.toml index b037cbcb8..a467dee9f 100644 --- a/src/uu/hashsum/Cargo.toml +++ b/src/uu/hashsum/Cargo.toml @@ -15,18 +15,19 @@ edition = "2021" path = "src/hashsum.rs" [dependencies] -digest = "0.10.6" clap = { workspace=true } -hex = "0.4.3" -memchr = { workspace=true } -md-5 = "0.10.5" -regex = { workspace=true } -sha1 = "0.10.1" -sha2 = "0.10.2" -sha3 = "0.10.6" -blake2b_simd = "1.0.1" -blake3 = "1.3.2" uucore = { workspace=true } +memchr = { workspace=true } +regex = { workspace=true } +hex = { workspace=true } +md-5 = { workspace=true } +sha1 = { workspace=true } +sha2 = { workspace=true } +sha3 = { workspace=true } +blake2b_simd = { workspace=true } +blake3 = { workspace=true } +sm3 = { workspace=true } +digest = { workspace=true } [[bin]] name = "hashsum" diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 3271425bc..a240304be 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -9,11 +9,6 @@ // spell-checker:ignore (ToDO) algo, algoname, regexes, nread, nonames -mod digest; - -use self::digest::Digest; -use self::digest::DigestWriter; - use clap::builder::ValueParser; use clap::crate_version; use clap::ArgAction; @@ -36,6 +31,7 @@ use uucore::crash; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult}; use uucore::show_warning; +use uucore::sum::{Digest, DigestWriter}; const NAME: &str = "hashsum"; @@ -680,7 +676,7 @@ fn digest_reader( // Assume it's SHAKE. result_str() doesn't work with shake (as of 8/30/2016) let mut bytes = Vec::new(); bytes.resize((output_bits + 7) / 8, 0); - digest.result(&mut bytes); + digest.hash_finalize(&mut bytes); Ok(encode(bytes)) } } diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index d4d8b8cf2..f5bec9053 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -36,6 +36,17 @@ libc = { version="0.2.137", optional=true } once_cell = { workspace=true } os_display = "0.1.3" +digest = { workspace=true } +hex = { workspace=true } +memchr = { workspace=true } +md-5 = { workspace=true } +sha1 = { workspace=true } +sha2 = { workspace=true } +sha3 = { workspace=true } +blake2b_simd = { workspace=true } +blake3 = { workspace=true } +sm3 = { workspace=true } + [target.'cfg(unix)'.dependencies] walkdir = { workspace=true, optional=true } nix = { workspace=true, features = ["fs", "uio", "zerocopy"] } @@ -66,3 +77,4 @@ utf8 = [] utmpx = ["time", "time/macros", "libc", "dns-lookup"] wide = [] pipes = [] +sum = [] diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index 2e5aea1e2..f8a8d2d10 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -12,6 +12,8 @@ pub mod lines; pub mod memo; #[cfg(feature = "ringbuffer")] pub mod ringbuffer; +#[cfg(feature = "sum")] +pub mod sum; #[cfg(feature = "memo")] mod tokenize; diff --git a/src/uu/hashsum/src/digest.rs b/src/uucore/src/lib/features/sum.rs similarity index 58% rename from src/uu/hashsum/src/digest.rs rename to src/uucore/src/lib/features/sum.rs index df3bfc166..c7cec6e09 100644 --- a/src/uu/hashsum/src/digest.rs +++ b/src/uucore/src/lib/features/sum.rs @@ -16,8 +16,8 @@ pub trait Digest { fn new() -> Self where Self: Sized; - fn input(&mut self, input: &[u8]); - fn result(&mut self, out: &mut [u8]); + fn hash_update(&mut self, input: &[u8]); + fn hash_finalize(&mut self, out: &mut [u8]); fn reset(&mut self); fn output_bits(&self) -> usize; fn output_bytes(&self) -> usize { @@ -25,7 +25,7 @@ pub trait Digest { } fn result_str(&mut self) -> String { let mut buf: Vec = vec![0; self.output_bytes()]; - self.result(&mut buf); + self.hash_finalize(&mut buf); encode(buf) } } @@ -35,11 +35,11 @@ impl Digest for blake2b_simd::State { Self::new() } - fn input(&mut self, input: &[u8]) { + fn hash_update(&mut self, input: &[u8]) { self.update(input); } - fn result(&mut self, out: &mut [u8]) { + fn hash_finalize(&mut self, out: &mut [u8]) { let hash_result = &self.finalize(); out.copy_from_slice(hash_result.as_bytes()); } @@ -58,11 +58,11 @@ impl Digest for blake3::Hasher { Self::new() } - fn input(&mut self, input: &[u8]) { + fn hash_update(&mut self, input: &[u8]) { self.update(input); } - fn result(&mut self, out: &mut [u8]) { + fn hash_finalize(&mut self, out: &mut [u8]) { let hash_result = &self.finalize(); out.copy_from_slice(hash_result.as_bytes()); } @@ -76,6 +76,194 @@ impl Digest for blake3::Hasher { } } +use sm3::{Digest as SM3_D, Sm3}; + +impl Digest for Sm3 { + fn new() -> Self { + ::new() + } + + fn hash_update(&mut self, input: &[u8]) { + self.update(input); + } + + fn hash_finalize(&mut self, out: &mut [u8]) { + out.copy_from_slice(&self.clone().finalize()); + } + + fn reset(&mut self) { + *self = ::new(); + } + + fn output_bits(&self) -> usize { + 256 + } +} + +// NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8 +const CRC_TABLE_LEN: usize = 256; + +pub struct CRC { + state: u32, + size: usize, + crc_table: [u32; CRC_TABLE_LEN], +} + +impl CRC { + fn generate_crc_table() -> [u32; CRC_TABLE_LEN] { + let mut table = [0; CRC_TABLE_LEN]; + + for (i, elt) in table.iter_mut().enumerate().take(CRC_TABLE_LEN) { + *elt = Self::crc_entry(i as u8); + } + + table + } + fn crc_entry(input: u8) -> u32 { + let mut crc = (input as u32) << 24; + + let mut i = 0; + while i < 8 { + let if_condition = crc & 0x8000_0000; + let if_body = (crc << 1) ^ 0x04c1_1db7; + let else_body = crc << 1; + + // NOTE: i feel like this is easier to understand than emulating an if statement in bitwise + // ops + let condition_table = [else_body, if_body]; + + crc = condition_table[(if_condition != 0) as usize]; + i += 1; + } + + crc + } + + fn update(&mut self, input: u8) { + self.state = (self.state << 8) + ^ self.crc_table[((self.state >> 24) as usize ^ input as usize) & 0xFF]; + } +} + +impl Digest for CRC { + fn new() -> Self { + Self { + state: 0, + size: 0, + crc_table: Self::generate_crc_table(), + } + } + + fn hash_update(&mut self, input: &[u8]) { + for &elt in input.iter() { + self.update(elt); + } + self.size += input.len(); + } + + fn hash_finalize(&mut self, out: &mut [u8]) { + let mut sz = self.size; + while sz != 0 { + self.update(sz as u8); + sz >>= 8; + } + self.state = !self.state; + out.copy_from_slice(&self.state.to_ne_bytes()); + } + + fn result_str(&mut self) -> String { + let mut _out: Vec = vec![0; 4]; + self.hash_finalize(&mut _out); + format!("{}", self.state) + } + + fn reset(&mut self) { + *self = Self::new(); + } + + fn output_bits(&self) -> usize { + 256 + } +} + +// This can be replaced with usize::div_ceil once it is stabilized. +// This implementation approach is optimized for when `b` is a constant, +// particularly a power of two. +pub fn div_ceil(a: usize, b: usize) -> usize { + (a + b - 1) / b +} + +pub struct BSD { + state: u16, +} + +impl Digest for BSD { + fn new() -> Self { + Self { state: 0 } + } + + fn hash_update(&mut self, input: &[u8]) { + for &byte in input.iter() { + self.state = (self.state >> 1) + ((self.state & 1) << 15); + self.state = self.state.wrapping_add(u16::from(byte)); + } + } + + fn hash_finalize(&mut self, out: &mut [u8]) { + out.copy_from_slice(&self.state.to_ne_bytes()); + } + + fn result_str(&mut self) -> String { + let mut _out: Vec = vec![0; 2]; + self.hash_finalize(&mut _out); + format!("{}", self.state) + } + + fn reset(&mut self) { + *self = Self::new(); + } + + fn output_bits(&self) -> usize { + 128 + } +} + +pub struct SYSV { + state: u32, +} + +impl Digest for SYSV { + fn new() -> Self { + Self { state: 0 } + } + + fn hash_update(&mut self, input: &[u8]) { + for &byte in input.iter() { + self.state = self.state.wrapping_add(u32::from(byte)); + } + } + + fn hash_finalize(&mut self, out: &mut [u8]) { + self.state = (self.state & 0xffff) + (self.state >> 16); + self.state = (self.state & 0xffff) + (self.state >> 16); + out.copy_from_slice(&(self.state as u16).to_ne_bytes()); + } + + fn result_str(&mut self) -> String { + let mut _out: Vec = vec![0; 2]; + self.hash_finalize(&mut _out); + format!("{}", self.state) + } + + fn reset(&mut self) { + *self = Self::new(); + } + + fn output_bits(&self) -> usize { + 512 + } +} + // Implements the Digest trait for sha2 / sha3 algorithms with fixed output macro_rules! impl_digest_common { ($type: ty, $size: expr) => { @@ -84,16 +272,16 @@ macro_rules! impl_digest_common { Self::default() } - fn input(&mut self, input: &[u8]) { + fn hash_update(&mut self, input: &[u8]) { digest::Digest::update(self, input); } - fn result(&mut self, out: &mut [u8]) { + fn hash_finalize(&mut self, out: &mut [u8]) { digest::Digest::finalize_into_reset(self, out.into()); } fn reset(&mut self) { - *self = Self::new(); + *self = ::new(); } fn output_bits(&self) -> usize { @@ -111,11 +299,11 @@ macro_rules! impl_digest_shake { Self::default() } - fn input(&mut self, input: &[u8]) { + fn hash_update(&mut self, input: &[u8]) { digest::Update::update(self, input); } - fn result(&mut self, out: &mut [u8]) { + fn hash_finalize(&mut self, out: &mut [u8]) { digest::ExtendableOutputReset::finalize_xof_reset_into(self, out); } @@ -148,7 +336,7 @@ impl_digest_shake!(sha3::Shake256); /// /// This struct wraps a [`Digest`] and provides a [`Write`] /// implementation that passes input bytes directly to the -/// [`Digest::input`]. +/// [`Digest::hash_update`]. /// /// On Windows, if `binary` is `false`, then the [`write`] /// implementation replaces instances of "\r\n" with "\n" before passing @@ -182,7 +370,7 @@ impl<'a> DigestWriter<'a> { pub fn finalize(&mut self) -> bool { if self.was_last_character_carriage_return { - self.digest.input(&[b'\r']); + self.digest.hash_update(&[b'\r']); true } else { false @@ -193,14 +381,14 @@ impl<'a> DigestWriter<'a> { impl<'a> Write for DigestWriter<'a> { #[cfg(not(windows))] fn write(&mut self, buf: &[u8]) -> std::io::Result { - self.digest.input(buf); + self.digest.hash_update(buf); Ok(buf.len()) } #[cfg(windows)] fn write(&mut self, buf: &[u8]) -> std::io::Result { if self.binary { - self.digest.input(buf); + self.digest.hash_update(buf); return Ok(buf.len()); } @@ -213,7 +401,7 @@ impl<'a> Write for DigestWriter<'a> { // call to `write()`. let n = buf.len(); if self.was_last_character_carriage_return && n > 0 && buf[0] != b'\n' { - self.digest.input(&[b'\r']); + self.digest.hash_update(&[b'\r']); } // Next, find all occurrences of "\r\n", inputting the slice @@ -221,7 +409,7 @@ impl<'a> Write for DigestWriter<'a> { // the beginning of this "\r\n". let mut i_prev = 0; for i in memmem::find_iter(buf, b"\r\n") { - self.digest.input(&buf[i_prev..i]); + self.digest.hash_update(&buf[i_prev..i]); i_prev = i + 1; } @@ -233,10 +421,10 @@ impl<'a> Write for DigestWriter<'a> { // blocks of the input. if n > 0 && buf[n - 1] == b'\r' { self.was_last_character_carriage_return = true; - self.digest.input(&buf[i_prev..n - 1]); + self.digest.hash_update(&buf[i_prev..n - 1]); } else { self.was_last_character_carriage_return = false; - self.digest.input(&buf[i_prev..n]); + self.digest.hash_update(&buf[i_prev..n]); } // Even though we dropped a "\r" for each "\r\n" we found, we @@ -272,14 +460,14 @@ mod tests { let mut writer_crlf = DigestWriter::new(&mut digest, false); writer_crlf.write_all(&[b'\r']).unwrap(); writer_crlf.write_all(&[b'\n']).unwrap(); - writer_crlf.finalize(); + writer_crlf.hash_finalize(); let result_crlf = digest.result_str(); // We expect "\r\n" to be replaced with "\n" in text mode on Windows. let mut digest = Box::new(md5::Md5::new()) as Box; let mut writer_lf = DigestWriter::new(&mut digest, false); writer_lf.write_all(&[b'\n']).unwrap(); - writer_lf.finalize(); + writer_lf.hash_finalize(); let result_lf = digest.result_str(); assert_eq!(result_crlf, result_lf); diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 66f8881d6..5cbf58faa 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -46,6 +46,8 @@ pub use crate::features::lines; pub use crate::features::memo; #[cfg(feature = "ringbuffer")] pub use crate::features::ringbuffer; +#[cfg(feature = "sum")] +pub use crate::features::sum; // * (platform-specific) feature-gated modules // ** non-windows (i.e. Unix + Fuchsia) diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 523715126..361a9c472 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -114,3 +114,79 @@ fn test_stdin_larger_than_128_bytes() { assert_eq!(cksum, 945_881_979); assert_eq!(bytes_cnt, 2058); } + +#[test] +fn test_sha1_single_file() { + new_ucmd!() + .arg("-a=sha1") + .arg("lorem_ipsum.txt") + .succeeds() + .stdout_is("ab1dd0bae1d8883a3d18a66de6afbd28252cfbef 772 lorem_ipsum.txt\n"); +} + +#[test] +fn test_sm3_single_file() { + new_ucmd!() + .arg("-a=sm3") + .arg("lorem_ipsum.txt") + .succeeds() + .stdout_is( + "6d296b805d060bfed22808df308dbb9b4317794dd4ed6740a10770a782699bc2 772 lorem_ipsum.txt\n", + ); +} + +#[test] +fn test_bsd_single_file() { + new_ucmd!() + .arg("-a=bsd") + .arg("lorem_ipsum.txt") + .succeeds() + .stdout_only_fixture("bsd_single_file.expected"); +} + +#[test] +fn test_bsd_multiple_files() { + new_ucmd!() + .arg("-a=bsd") + .arg("lorem_ipsum.txt") + .arg("alice_in_wonderland.txt") + .succeeds() + .stdout_only_fixture("bsd_multiple_files.expected"); +} + +#[test] +fn test_bsd_stdin() { + new_ucmd!() + .arg("-a=bsd") + .pipe_in_fixture("lorem_ipsum.txt") + .succeeds() + .stdout_only_fixture("bsd_stdin.expected"); +} + +#[test] +fn test_sysv_single_file() { + new_ucmd!() + .arg("-a=sysv") + .arg("lorem_ipsum.txt") + .succeeds() + .stdout_only_fixture("sysv_single_file.expected"); +} + +#[test] +fn test_sysv_multiple_files() { + new_ucmd!() + .arg("-a=sysv") + .arg("lorem_ipsum.txt") + .arg("alice_in_wonderland.txt") + .succeeds() + .stdout_only_fixture("sysv_multiple_files.expected"); +} + +#[test] +fn test_sysv_stdin() { + new_ucmd!() + .arg("-a=sysv") + .pipe_in_fixture("lorem_ipsum.txt") + .succeeds() + .stdout_only_fixture("sysv_stdin.expected"); +} diff --git a/tests/fixtures/cksum/bsd_multiple_files.expected b/tests/fixtures/cksum/bsd_multiple_files.expected new file mode 100644 index 000000000..941a2a512 --- /dev/null +++ b/tests/fixtures/cksum/bsd_multiple_files.expected @@ -0,0 +1,2 @@ +08109 1 lorem_ipsum.txt +01814 1 alice_in_wonderland.txt diff --git a/tests/fixtures/cksum/bsd_single_file.expected b/tests/fixtures/cksum/bsd_single_file.expected new file mode 100644 index 000000000..293ada3bd --- /dev/null +++ b/tests/fixtures/cksum/bsd_single_file.expected @@ -0,0 +1 @@ +08109 1 lorem_ipsum.txt diff --git a/tests/fixtures/cksum/bsd_stdin.expected b/tests/fixtures/cksum/bsd_stdin.expected new file mode 100644 index 000000000..4843ba082 --- /dev/null +++ b/tests/fixtures/cksum/bsd_stdin.expected @@ -0,0 +1 @@ +08109 1 diff --git a/tests/fixtures/cksum/sysv_multiple_files.expected b/tests/fixtures/cksum/sysv_multiple_files.expected new file mode 100644 index 000000000..83a6d6d83 --- /dev/null +++ b/tests/fixtures/cksum/sysv_multiple_files.expected @@ -0,0 +1,2 @@ +6985 2 lorem_ipsum.txt +27441 1 alice_in_wonderland.txt diff --git a/tests/fixtures/cksum/sysv_single_file.expected b/tests/fixtures/cksum/sysv_single_file.expected new file mode 100644 index 000000000..e0f7252cb --- /dev/null +++ b/tests/fixtures/cksum/sysv_single_file.expected @@ -0,0 +1 @@ +6985 2 lorem_ipsum.txt diff --git a/tests/fixtures/cksum/sysv_stdin.expected b/tests/fixtures/cksum/sysv_stdin.expected new file mode 100644 index 000000000..f0fba8c81 --- /dev/null +++ b/tests/fixtures/cksum/sysv_stdin.expected @@ -0,0 +1 @@ +6985 2