base64-ng 0.12.0

no_std-first Base64 encoding and decoding with strict APIs and a security-heavy release process
Documentation
# Public API Audit

This document tracks the `v0.10` audit-preparation milestone. The goal is to
enter the `v1.0` candidate series with a small, explicit, security-reviewed
public surface. The audit favors removing or documenting ambiguous APIs over
adding convenience.

## Audit Rules

- Keep strict, canonical decoding as the default.
- Keep legacy, wrapped, profile-specific, and constant-time-oriented behavior
  explicit in names or modules.
- Do not add broad conversion traits that hide alphabet, padding, wrapping,
  allocation, or secret-handling policy.
- Do not admit optional ecosystem dependencies without a written dependency
  admission record.
- Do not admit active SIMD dispatch through an API audit; SIMD remains governed
  by the dedicated SIMD admission manifest.
- Treat any API that exposes owned bytes or backing arrays as a security
  boundary that must be documented.
- Prefer caller-owned output APIs and recoverable errors for untrusted input.

## Status Legend

- `candidate stable`: the current API shape looks suitable for `v1.0`, pending
  final release-candidate evidence.
- `documented boundary`: the API is acceptable only because its security or
  ownership boundary is explicit in docs and examples.
- `review pending`: the API still needs audit before a stable `v0.10` release;
  stable releases fail the local gate if any row remains in this state.
- `deferred`: the API area is intentionally not admitted in this release
  series.

## Public Surface Under Review

| Area | Status | Notes |
| --- | --- | --- |
| Engine constants and `Engine<A, PAD>` | candidate stable | Strict/default semantics are explicit; audit constructor naming and stream convenience methods before `v1.0`. |
| `Profile<A, PAD>` and named profiles | candidate stable | MIME, PEM, bcrypt-style, `crypt(3)`-style, wrapping, and padding behavior are explicit and covered by policy tests. |
| Length helpers | candidate stable | Public helpers are recoverable and checked; keep examples focused on untrusted-size handling. |
| Slice encode/decode APIs | candidate stable | Caller-owned output, checked lengths, and clear-tail variants are the preferred stable surface. |
| In-place APIs | candidate stable | Encode-to-back and decode-to-front contracts are explicit, checked, and paired with clear-tail variants. |
| Validation-only APIs | candidate stable | Strict, legacy, wrapped, and ct validation APIs are documented as decode-equivalent policy checks. |
| Stack-backed buffers | documented boundary | `EncodedBuffer` and `DecodedBuffer` are retained with explicit visible-length, cleanup, comparison, and exposed-array boundaries. |
| `SecretBuffer` | documented boundary | Redaction, cleanup limits, comparison semantics, and owned escape hatches are explicit adoption boundaries. |
| `ct` module | documented boundary | Keep non-claim wording and opaque error behavior explicit unless verification evidence changes. |
| `stream` module | documented boundary | Fail-closed decode, retry semantics, state helpers, recovery helpers, and framed-reader behavior are explicit. |
| Runtime backend reporting | candidate stable | Scalar-only posture and stable log identifiers are documented and release-gated. |
| Feature flags | deferred | `tokio`, `kani`, `fuzzing`, and `simd` remain inert or reserved unless admitted by their policy docs. |
| Error types | candidate stable | Encode, decode, and alphabet errors are recoverable and diagnostic without committing ct errors to localized detail. |
| Macros and custom alphabets | documented boundary | Compile-time validation and conservative fixed-scan performance/security tradeoffs are explicit. |

## `v1.0` Admission Questions

- Is the API name explicit about strict, legacy, wrapped, profile, or
  constant-time-oriented behavior?
- Can the caller size memory before using the API?
- Does the API have a caller-owned buffer form when it can allocate?
- Does the API expose a clear cleanup boundary for sensitive data?
- Does the API keep malformed input recoverable instead of panicking?
- Does the API commit to behavior that future SIMD backends can reproduce
  exactly?
- Does the API require dependency admission or feature-policy documentation?

## Audit Decisions

### Profiles

`Profile<A, PAD>` and the named `MIME`, `PEM`, `PEM_CRLF`, `BCRYPT`, and
`CRYPT` profiles are candidates for the `v1.0` stable surface.

Decision rationale:

- A profile is an explicit policy bundle: alphabet, padding mode, and optional
  line wrapping remain visible through type parameters, constructor arguments,
  and policy accessors.
- `Profile::checked_new` rejects invalid wrapping policy instead of silently
  accepting an unusable profile.
- MIME and PEM wrapping policy is strict: non-final encoded lines must match
  the configured width and line ending.
- Bcrypt-style and `crypt(3)`-style profiles expose alphabet and no-padding
  interoperability only; they do not claim password-hash parsing or
  verification.
- Profiles forward to the same scalar engine, validation, in-place, clear-tail,
  stack-buffer, and secret-buffer APIs rather than introducing a separate
  decoding contract.

Stable boundary:

- Keep profile behavior strict and deterministic.
- Do not add permissive profile constructors without a new audit entry.
- Do not broaden bcrypt-style or `crypt(3)`-style profiles into full password
  hash parsers.
- Do not hide profile policy behind broad conversion traits.

### Validation-Only APIs

Strict, legacy, wrapped, and constant-time-oriented validation APIs are
candidates for the `v1.0` stable surface.

Decision rationale:

- Validation-only APIs use the same alphabet, padding, canonical-bit, and line
  wrapping checks as the corresponding decode APIs.
- Boolean helpers are convenience wrappers over `Result`-returning helpers,
  preserving recoverable diagnostics for callers that need them.
- Legacy validation is opt-in and only skips ASCII transport whitespace; it
  keeps alphabet, padding, terminal-data, and canonical-bit checks strict.
- Wrapped validation is stricter than legacy whitespace handling and accepts
  only the configured wrapping policy.
- Constant-time-oriented validation follows the `ct` module's documented
  opaque malformed-input policy.

Stable boundary:

- Keep validation/decode agreement release-tested.
- Keep strict validation canonical by default.
- Keep legacy and wrapped validation explicit in method names.
- Keep ct validation errors opaque unless formal side-channel evidence changes
  the documented contract.

### Stack-Backed Buffers

`EncodedBuffer<CAP>` and `DecodedBuffer<CAP>` are retained as documented
boundaries for `v1.0`.

Decision rationale:

- Stack-backed buffers give no-alloc callers an owned output shape without
  hiding capacity or visible length.
- Accessors expose borrowed bytes or fallible UTF-8 views rather than
  implicitly allocating or assuming decoded text.
- `Debug` is redacted for decoded buffers; encoded buffers remain printable as
  Base64 text through explicit display/text APIs.
- Drop-time cleanup is best-effort and scoped to the buffer's current backing
  array, not historical stack-frame copies.
- `into_exposed_array` is intentionally named as an ownership escape hatch
  where redaction and drop-time cleanup stop applying to the returned array.
- Equality uses the same constant-time-oriented equal-length comparison helper
  used by the redacted owned wrapper.

Stable boundary:

- Keep capacity and visible length explicit.
- Keep ownership escape hatches explicit in names.
- Do not add implicit text conversions for decoded bytes.
- Do not describe drop-time cleanup as formal zeroization.

### Secret Buffer

`SecretBuffer` is retained as a documented security boundary for `v1.0`.

Decision rationale:

- Formatting is redacted by default through `Debug` and `Display`.
- Secret exposure requires explicitly named borrowed or owned escape hatches.
- Drop-time cleanup uses the crate's volatile best-effort wipe helper for
  initialized bytes and vector spare capacity.
- Equality and direct byte/text comparisons use constant-time-oriented
  equal-length comparison semantics.
- Strict standard padded `TryFrom` and `FromStr` implementations are kept only
  for native Rust ergonomics; non-standard profiles remain on explicit
  engine/profile methods.

Stable boundary:

- Keep redaction as the default formatting behavior.
- Keep `expose_secret`, `into_exposed_vec`, and `try_into_exposed_string`
  explicit.
- Do not claim formal zeroization or allocator-wide cleanup.
- Do not add broad conversions that hide profile, alphabet, padding, or
  wrapping policy.

### In-Place APIs

In-place encode and decode APIs are candidates for the `v1.0` stable surface.

Decision rationale:

- In-place encode validates the caller-provided input length and required
  encoded length before writing.
- In-place encode writes from the back of the output region toward the front,
  so unread input bytes are not overwritten.
- In-place decode writes decoded output to the front of the same buffer, which
  is valid because Base64 decoded output is never larger than accepted encoded
  input.
- Legacy and wrapped in-place decode validate and compact input according to
  their explicit policies before decoding.
- Clear-tail variants exist for strict, legacy, wrapped, and
  constant-time-oriented in-place decode when the caller wants best-effort
  cleanup on success and failure.

Stable boundary:

- Keep all in-place APIs recoverable through `Result`.
- Keep strict, legacy, wrapped, and ct in-place behavior separated by method or
  module name.
- Keep clear-tail variants explicit rather than making cleanup an implicit
  default for all in-place APIs.
- Do not add unchecked in-place APIs to the public surface.

### Custom Alphabets

`validate_alphabet`, `decode_alphabet_byte`, and `define_alphabet!` are retained
as documented boundaries for `v1.0`.

Decision rationale:

- Custom alphabets must contain exactly 64 unique visible ASCII bytes and must
  not contain the padding byte.
- `define_alphabet!` validates the alphabet at compile time, so invalid
  literals fail the build.
- The generated `decode` method delegates to the same validated table
  semantics as runtime custom alphabet helpers.
- The default `Alphabet::encode` implementation performs a fixed 64-entry scan
  for every emitted byte. This preserves the conservative no secret-indexed
  lookup posture, but it is slower than the arithmetic mappers used by built-in
  alphabets.

Stable boundary:

- Keep compile-time validation in the macro.
- Keep custom-alphabet performance tradeoffs documented.
- Do not add a faster custom-alphabet path unless it has its own audit record.
- Do not accept non-visible ASCII or padding bytes in Base64 alphabets.

### Stream Module

The `stream` module is retained as a documented boundary for `v1.0`.

Decision rationale:

- Streaming remains behind the explicit `stream` feature and depends only on
  `std::io`.
- Writer adapters expose `try_finish` for finalization without consuming the
  adapter, and `finish` for finalization plus wrapped object recovery.
- Writer adapters buffer accepted output and allow failed wrapped writes to be
  retried without re-encoding or re-decoding already accepted input.
- Direct `Write::write` calls follow normal `std::io::Write` partial-progress
  semantics; examples and migration docs recommend `write_all` when the whole
  slice must be consumed.
- Decoder adapters fail closed after malformed Base64 input and expose
  `is_failed` for diagnostics.
- `can_into_inner` and `try_into_inner` provide checked recovery paths that
  refuse to silently discard pending input or buffered output.
- Padded `DecoderReader` stops after terminal padding and leaves adjacent
  framed payload bytes unread in the wrapped reader.
- Debug output redacts wrapped I/O values and pending payload bytes while still
  exposing non-sensitive state useful for diagnostics.
- Internal pending and output queues are wiped on consumption and drop as
  best-effort retention reduction.

Stable boundary:

- Keep `std::io` streaming under the explicit `stream` feature.
- Keep async/Tokio out unless the async admission policy is satisfied.
- Keep decoder failure fail-closed.
- Keep recovery helpers explicit; do not make unchecked `into_inner` the
  recommended safe recovery path.
- Keep reader terminal-padding behavior documented for framed protocols.

### Error Types

`EncodeError`, `DecodeError`, and `AlphabetError` are candidates for the
`v1.0` stable surface.

Decision rationale:

- Public runtime errors are recoverable through `Result` and avoid panic-based
  failure for malformed input, size errors, and invalid policies.
- `EncodeError` separates length overflow, invalid line wrapping, input length,
  and output capacity failures.
- `DecodeError` separates invalid length, invalid bytes, invalid padding,
  invalid line wrapping, output capacity, and deliberately opaque malformed
  input.
- Strict, legacy, wrapped, and in-place decode paths preserve absolute input
  indexes where localized diagnostics are part of the public contract.
- `ct` APIs intentionally report malformed content as `InvalidInput` so the
  constant-time-oriented path does not promise localized error detail.
- `AlphabetError` identifies invalid, padding, and duplicate alphabet bytes
  during custom alphabet validation.

Stable boundary:

- Keep existing variants unless a later release-candidate audit finds a
  correctness reason to change them before `v1.0`.
- Keep localized indexes for non-ct scalar diagnostics.
- Keep ct malformed-content errors opaque.
- Do not add panicking convenience APIs for error cases already represented by
  public error variants.

## Initial `v0.10` Direction

- Keep async/Tokio, serde, bytes, zeroize, subtle, property-test, and
  Criterion-style integrations out unless a concrete admission record is
  written.
- Keep `ct` documented as constant-time-oriented rather than formally
  cryptographic unless verification evidence improves during the `v0.10` to
  `v0.12` candidate series.
- Keep active dispatch scalar-only until SIMD admission evidence is complete.
- Focus implementation work on audit findings, documentation gaps, tests, and
  evidence rather than new feature breadth.