From 4698278d76b74d50050b62a18b2b2c16614f48f8 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 5 Nov 2025 21:54:08 -0500 Subject: [PATCH] base64ct, base32ct: proptests for decoder equivalence with base32/64 For the base64 case, this MR adds proptests to ensure that base64 and base64ct accept the same inputs when decoding, and produce the same outputs when decoding them. For the base32 case, that property isn't actually true. Instead, the proptest makes sure that whenever base32 and base32ct both accept an input when decoding, they produce the same output. --- base32ct/tests/proptests.rs | 31 +++++++++++++++++++++++++++++++ base64ct/tests/proptests.rs | 21 ++++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/base32ct/tests/proptests.rs b/base32ct/tests/proptests.rs index aa0a36d2d..89f1ebb7f 100644 --- a/base32ct/tests/proptests.rs +++ b/base32ct/tests/proptests.rs @@ -34,4 +34,35 @@ proptest! { let expected = base32::encode(RFC4648_PADDED, &bytes).to_lowercase(); prop_assert_eq!(actual, expected); } + + /// Make sure that, if base32ct and base32 _both_ decode a value + /// when expecting padded inputs, they give the same output. + /// + /// TODO: It might be desirable to ensure that they both decode the + /// _same_ values: that is, that they are equivalently strict about + /// which inputs they accept. But first, we should verify that + /// `base32`'s behavior is actually what we want. + #[test] + fn decode_arbitrary_padded(string in string_regex("[a-z0-9]{0,32}={0,8}").unwrap()) { + let actual = Base32Ct::decode_vec(&string); + let expected = base32::decode(RFC4648_PADDED, &string); + // assert_eq!(actual.ok(), expected); + if let (Ok(a), Some(b)) = (actual, expected) { + assert_eq!(a, b); + } + } + + /// Make sure that, if base32ct and base32 _both_ decode a value + /// when expecting unpadded inputs, they give the same output. + /// + /// TODO: See note above. + #[test] + fn decode_arbitrary_unpadded(string in string_regex("[a-z0-9]{0,32}={0,8}").unwrap()) { + let actual = Base32UnpaddedCt::decode_vec(&string); + let expected = base32::decode(RFC4648_UNPADDED, &string); + // assert_eq!(actual.ok(), expected); + if let (Ok(a), Some(b)) = (actual, expected) { + assert_eq!(a, b); + } + } } diff --git a/base64ct/tests/proptests.rs b/base64ct/tests/proptests.rs index c60cf16d7..6881bcd3b 100644 --- a/base64ct/tests/proptests.rs +++ b/base64ct/tests/proptests.rs @@ -5,7 +5,7 @@ // warning: use of deprecated function `base64::encode`: Use Engine::encode #![allow(deprecated)] -use base64ct::{Base64 as Base64ct, Encoding}; +use base64ct::{Base64 as Base64ct, Base64Unpadded as Base64UnpaddedCt, Encoding}; use proptest::{prelude::*, string::*}; /// Incremental Base64 decoder. @@ -148,4 +148,23 @@ proptest! { prop_assert_eq!(expected, encoder.finish().unwrap()); } + + /// Make sure that base64ct and base64 both decode the same values + /// when expecting padded inputs, and produce the same outputs for those values. + #[test] + fn decode_arbitrary_padded(string in string_regex("[a-zA-Z0-9/+=?]{0,256}").unwrap()) { + let actual = Base64ct::decode_vec(&string); + let expected = base64::decode( &string); + assert_eq!(actual.ok(), expected.ok()); + } + + /// Make sure that base64ct and base64 both decode the same values + /// when expecting unpadded inputs, and produce the same outputs for those values. + #[test] + fn decode_arbitrary_unpadded(string in string_regex("[a-zA-Z0-9/+=?]{0,256}").unwrap()) { + use base64::{engine::general_purpose::STANDARD_NO_PAD, Engine as _}; + let actual = Base64UnpaddedCt::decode_vec(&string); + let expected = STANDARD_NO_PAD.decode(&string); + assert_eq!(actual.ok(), expected.ok()); + } }