Skip to content

Commit f804f23

Browse files
committed
Expand no-alloc smoke coverage
1 parent 8ca8963 commit f804f23

3 files changed

Lines changed: 111 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
policy inspection in diagnostics and audit logs.
4040
- Added reader-side stream `has_finished_input()` helpers so callers can
4141
distinguish EOF or terminal padding from fully drained buffered output.
42+
- Expanded the no-alloc portability smoke harness to cover custom alphabets,
43+
checked profiles, recoverable length helpers, and stack-buffer state helpers.
4244

4345
## 0.8.0 - 2026-05-16
4446

docs/RELEASE_EVIDENCE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ The release gate runs:
6666
- no-alloc portability smoke crate checks for stack-backed encode/decode,
6767
wrapped output, URL-safe no-padding, and constant-time-oriented decode with
6868
default features disabled, plus validate-only, legacy decode, in-place
69-
encode/decode, and constant-time-oriented in-place decode surfaces; the
69+
encode/decode, constant-time-oriented in-place decode, custom
70+
alphabet/profile, recoverable length, and stack-buffer state surfaces; the
7071
harness also runs host-side unit tests before cross-target compile checks
7172
- Local and CI target-matrix no-alloc portability smoke checks so installed
7273
Linux, FreeBSD, wasm32, ARM, and Cortex-M targets compile the same

portability/no_alloc_smoke/src/lib.rs

Lines changed: 107 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
#![no_std]
22

3-
use base64_ng::{LineEnding, LineWrap, STANDARD, URL_SAFE_NO_PAD, ct};
3+
use base64_ng::{
4+
DecodedBuffer, EncodedBuffer, Engine, LineEnding, LineWrap, Profile, STANDARD,
5+
URL_SAFE_NO_PAD, checked_encoded_len, decoded_capacity, decoded_len, encoded_len, ct,
6+
};
7+
8+
base64_ng::define_alphabet! {
9+
struct SmokeAlphabet = b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
10+
}
11+
12+
const SMOKE_NO_PAD: Engine<SmokeAlphabet, false> = Engine::new();
413

514
pub const CONST_HELLO: [u8; 8] = STANDARD.encode_array(b"hello");
615

@@ -99,11 +108,104 @@ pub fn legacy_stack_decode() -> bool {
99108
decoded.as_bytes() == b"hello"
100109
}
101110

111+
pub fn custom_profile_surfaces() -> bool {
112+
let encoded = match SMOKE_NO_PAD.encode_buffer::<4>(&[0xff, 0xff, 0xff]) {
113+
Ok(encoded) => encoded,
114+
Err(_) => return false,
115+
};
116+
if encoded.as_bytes() != b"9999" {
117+
return false;
118+
}
119+
120+
let decoded = match SMOKE_NO_PAD.decode_buffer::<3>(encoded.as_bytes()) {
121+
Ok(decoded) => decoded,
122+
Err(_) => return false,
123+
};
124+
if decoded.as_bytes() != [0xff, 0xff, 0xff] {
125+
return false;
126+
}
127+
128+
let wrap = match LineWrap::checked_new(4, LineEnding::Lf) {
129+
Some(wrap) => wrap,
130+
None => return false,
131+
};
132+
let profile = match Profile::checked_new(STANDARD, Some(wrap)) {
133+
Some(profile) => profile,
134+
None => return false,
135+
};
136+
137+
if !profile.is_valid() || !profile.is_padded() || profile.engine() != STANDARD {
138+
return false;
139+
}
140+
141+
let wrapped = match profile.encode_buffer::<9>(b"hello") {
142+
Ok(encoded) => encoded,
143+
Err(_) => return false,
144+
};
145+
wrapped.as_bytes() == b"aGVs\nbG8="
146+
}
147+
148+
pub fn length_and_stack_state_surfaces() -> bool {
149+
if checked_encoded_len(5, true) != Some(8) {
150+
return false;
151+
}
152+
if encoded_len(5, true) != Ok(8) {
153+
return false;
154+
}
155+
if decoded_capacity(8) != 6 {
156+
return false;
157+
}
158+
if decoded_len(b"aGVsbG8=", true) != Ok(5) {
159+
return false;
160+
}
161+
162+
let empty_encoded = EncodedBuffer::<8>::new();
163+
if !empty_encoded.is_empty()
164+
|| empty_encoded.is_full()
165+
|| empty_encoded.capacity() != 8
166+
|| empty_encoded.remaining_capacity() != 8
167+
{
168+
return false;
169+
}
170+
171+
let encoded = match STANDARD.encode_buffer::<8>(b"hello") {
172+
Ok(encoded) => encoded,
173+
Err(_) => return false,
174+
};
175+
if encoded.is_empty()
176+
|| !encoded.is_full()
177+
|| encoded.remaining_capacity() != 0
178+
|| encoded.as_str() != "aGVsbG8="
179+
{
180+
return false;
181+
}
182+
183+
let empty_decoded = DecodedBuffer::<5>::new();
184+
if !empty_decoded.is_empty()
185+
|| empty_decoded.is_full()
186+
|| empty_decoded.capacity() != 5
187+
|| empty_decoded.remaining_capacity() != 5
188+
{
189+
return false;
190+
}
191+
192+
let decoded = match STANDARD.decode_buffer::<5>(encoded.as_bytes()) {
193+
Ok(decoded) => decoded,
194+
Err(_) => return false,
195+
};
196+
197+
!decoded.is_empty()
198+
&& decoded.is_full()
199+
&& decoded.remaining_capacity() == 0
200+
&& decoded.as_bytes() == b"hello"
201+
}
202+
102203
#[cfg(test)]
103204
mod tests {
104205
use super::{
105-
CONST_HELLO, ct_stack_decode, in_place_surfaces, legacy_stack_decode, stack_round_trip,
106-
url_safe_round_trip, validate_only_surfaces, wrapped_round_trip,
206+
CONST_HELLO, ct_stack_decode, custom_profile_surfaces, in_place_surfaces,
207+
legacy_stack_decode, length_and_stack_state_surfaces, stack_round_trip, url_safe_round_trip,
208+
validate_only_surfaces, wrapped_round_trip,
107209
};
108210

109211
#[test]
@@ -117,12 +219,14 @@ mod tests {
117219
assert!(url_safe_round_trip(b"\xfb\xff"));
118220
assert!(wrapped_round_trip());
119221
assert!(legacy_stack_decode());
222+
assert!(custom_profile_surfaces());
120223
}
121224

122225
#[test]
123226
fn validation_and_in_place_surfaces_work() {
124227
assert!(validate_only_surfaces());
125228
assert!(in_place_surfaces());
126229
assert!(ct_stack_decode());
230+
assert!(length_and_stack_state_surfaces());
127231
}
128232
}

0 commit comments

Comments
 (0)