Skip to content

Commit 2bf455e

Browse files
committed
Add explicit constant-time decoder helper
1 parent 4c334a7 commit 2bf455e

5 files changed

Lines changed: 23 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@
7676
unwrapped `Profile`.
7777
- Added `ct::CtEngine::decoded_len()` so sensitive decode paths can size
7878
caller-owned buffers without switching to the diagnostic decoder.
79+
- Added `Engine::ct_decoder()` for explicit promotion to the matching
80+
constant-time-oriented decoder without type annotations.
7981

8082
## 0.8.0 - 2026-05-16
8183

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,15 +248,18 @@ assert!(MIME.validate(&encoded[..written]));
248248
```
249249

250250
An engine can also be promoted explicitly to an unwrapped profile when a common
251-
configuration path expects profile values:
251+
configuration path expects profile values, or to the matching
252+
constant-time-oriented decoder when sensitive decode policy is required:
252253

253254
```rust
254255
use base64_ng::STANDARD;
255256

256257
let profile = STANDARD.profile();
258+
let ct_decoder = STANDARD.ct_decoder();
257259

258260
assert!(profile.is_padded());
259261
assert!(!profile.is_wrapped());
262+
assert_eq!(ct_decoder.decoded_len(b"aGVsbG8=").unwrap(), 5);
260263
```
261264

262265
When wrapping policy comes from configuration, prefer checked construction:

portability/no_alloc_smoke/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ pub fn custom_profile_surfaces() -> bool {
159159
{
160160
return false;
161161
}
162+
if STANDARD.ct_decoder().decoded_len(b"aGVsbG8=") != Ok(5) {
163+
return false;
164+
}
162165

163166
let encoded = match SMOKE_NO_PAD.encode_buffer::<4>(&[0xff, 0xff, 0xff]) {
164167
Ok(encoded) => encoded,

src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3928,6 +3928,16 @@ where
39283928
Profile::new(*self, None)
39293929
}
39303930

3931+
/// Returns the matching constant-time-oriented decoder for this engine's
3932+
/// alphabet and padding policy.
3933+
///
3934+
/// The returned decoder is still an explicit opt-in to the [`ct`] module's
3935+
/// slower, opaque-error, constant-time-oriented scalar path.
3936+
#[must_use]
3937+
pub const fn ct_decoder(&self) -> ct::CtEngine<A, PAD> {
3938+
ct::CtEngine::new()
3939+
}
3940+
39313941
/// Returns the encoded length for this engine's padding policy.
39323942
pub const fn encoded_len(&self, input_len: usize) -> Result<usize, EncodeError> {
39333943
encoded_len(input_len, PAD)

tests/rfc4648.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,10 @@ fn named_profiles_expose_expected_policies() {
254254
assert_eq!(URL_SAFE_NO_PAD.profile().engine(), URL_SAFE_NO_PAD);
255255
assert!(!URL_SAFE_NO_PAD.profile().is_padded());
256256
assert!(!URL_SAFE_NO_PAD.profile().is_wrapped());
257+
assert_eq!(STANDARD.ct_decoder(), ct::STANDARD);
258+
assert_eq!(STANDARD_NO_PAD.ct_decoder(), ct::STANDARD_NO_PAD);
259+
assert_eq!(URL_SAFE.ct_decoder(), ct::URL_SAFE);
260+
assert_eq!(URL_SAFE_NO_PAD.ct_decoder(), ct::URL_SAFE_NO_PAD);
257261
assert!(standard_profile.is_padded());
258262
assert!(!standard_profile.is_wrapped());
259263
assert!(standard_profile.is_valid());

0 commit comments

Comments
 (0)