From 44c8ab28af96fa162b94529adab978925513fd5e Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Sat, 26 Sep 2026 18:14:38 -0700 Subject: [PATCH] fix(bls): compare version numbers of any length Numeric segments were parsed into a saturating u64, so any two numbers above u64::MAX compared equal. Compare the digits instead, by length and then digit by digit, as systemd does. --- crates/bls/src/lib.rs | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/crates/bls/src/lib.rs b/crates/bls/src/lib.rs index fec8346..60488f1 100644 --- a/crates/bls/src/lib.rs +++ b/crates/bls/src/lib.rs @@ -340,6 +340,8 @@ fn is_valid_char(c: char) -> bool { } /// Compares numerical prefixes by extracting numbers. +/// Numbers are compared by their digit count first and then digit by digit, +/// which supports numbers of any length. fn compare_numeric>( iter_a: &mut Peekable, iter_b: &mut Peekable, @@ -347,21 +349,24 @@ fn compare_numeric>( let num_a = extract_number(iter_a); let num_b = extract_number(iter_b); - num_a.cmp(&num_b) + num_a + .len() + .cmp(&num_b.len()) + .then_with(|| num_a.cmp(&num_b)) } -/// Extracts a number from the iterator, skipping leading zeros. -fn extract_number>(iter: &mut Peekable) -> u64 { +/// Extracts the digits of a number from the iterator, skipping leading zeros. +fn extract_number>(iter: &mut Peekable) -> String { // Skip leading zeros while let Some(&'0') = iter.peek() { iter.next(); } - let mut num = 0u64; + let mut num = String::new(); while let Some(&c) = iter.peek() { if c.is_ascii_digit() { iter.next(); - num = num.saturating_mul(10).saturating_add(c as u64 - '0' as u64); + num.push(c); } else { break; } @@ -732,6 +737,18 @@ efi /EFI/fedora/shimx64.efi assert_eq!(compare_versions("1.a", "1.0"), Ordering::Less); } + #[test] + fn large_numbers_do_not_saturate() { + assert_eq!( + compare_versions("99999999999999999999", "99999999999999999998"), + Ordering::Greater + ); + assert_eq!( + compare_versions("1.99999999999999999998", "1.99999999999999999999"), + Ordering::Less + ); + } + #[test] fn optional_both_none_equal() { assert_eq!(compare_versions_optional(None, None), Ordering::Equal);