diff --git a/rust/src/zstd_cli.rs b/rust/src/zstd_cli.rs index 8504051ac..41115cfcf 100644 --- a/rust/src/zstd_cli.rs +++ b/rust/src/zstd_cli.rs @@ -516,6 +516,14 @@ fn parse_i32(value: &str, name: &str) -> Result { .map_err(|_| format!("invalid {name}: {value:?}")) } +fn parse_compression_level(value: &str) -> Result { + /* The C CLI reads short-form levels as U32 before storing them in its + * signed level field. Preserve that conversion for values such as + * 4294967295, which represents -1 on the supported two's-complement + * targets and is a valid fast-compression level. */ + Ok(parse_u32(value, "compression level")? as i32) +} + fn parse_worker_count(value: &str) -> Result { let workers = parse_i32(value, "thread count")?; if workers < 0 { @@ -803,14 +811,14 @@ fn parse_long_option( } "--fast" => { let mut level = match attached { - Some(value) => parse_i32(value, "fast level")?, + Some(value) => parse_u32(value, "fast level")?, None => 1, }; - if level <= 0 { + if level == 0 { return Err("fast level must be positive".to_owned()); } - level = level.min(MAX_FAST_ACCELERATION); - cli.level = -level; + level = level.min(MAX_FAST_ACCELERATION as u32); + cli.level = -(level as i32); Ok(None) } "--long" => { @@ -939,7 +947,7 @@ fn parse_short_options( while digits_end < bytes.len() && bytes[digits_end].is_ascii_digit() { digits_end += 1; } - cli.level = parse_i32(&value[offset..digits_end], "compression level")?; + cli.level = parse_compression_level(&value[offset..digits_end])?; offset = digits_end; continue; } @@ -1598,6 +1606,17 @@ mod tests { assert_eq!(cli.display_level, 1); } + #[test] + fn fast_levels_preserve_the_unsigned_32_bit_conversion() { + let cli = parse(&["zstd", "--fast", "-4294967295", "input"]); + + assert_eq!(cli.level, -1); + + let cli = parse(&["zstd", "--fast=4294967295", "input"]); + + assert_eq!(cli.level, -MAX_FAST_ACCELERATION); + } + #[test] fn short_option_equals_form_is_accepted() { let cli = parse(&["zstd", "-T=2", "-M=64M", "-B=1M", "input"]);