base64-ng 2.0.0

no_std-first Base64 encoding and decoding with strict APIs and a security-heavy release process
Documentation
# 2.0 Operation Contracts

Status: implemented from Commit 8 onward for the `2.0.0` candidate.

This document freezes the error, progress, lifecycle, indexing, destination,
and leakage contracts used by every 2.0 state machine. Commit 8 defined the
model; subsequent commits exposed it with each completed implementation.

## Lifecycle

An incremental transform is in exactly one state: `Active`, `Finishing`,
`Complete`, or `Failed(error)`. Failure is absorbing. Only an explicit `reset`
creates a new message after completion or failure.

| Current state | Call | Result | Next state |
|---|---|---|---|
| Active | update with progress | `NeedInput` | Active |
| Active | update without enough destination space | `OutputFull` | Active |
| Active | finish with pending final output | `OutputFull` | Finishing |
| Active | finish successfully | `Complete` | Complete |
| Active | malformed/truncated input, limit, position overflow, or backend fault | exact error | Failed(error) |
| Finishing | finish with pending final output | `OutputFull` | Finishing |
| Finishing | finish after final output drains | `Complete` | Complete |
| Finishing | update, including empty input | `input-after-finish` | Finishing |
| Finishing | finalization failure | exact error | Failed(error) |
| Complete | finish | `Complete` with zero progress | Complete |
| Complete | update with new input | `input-after-complete` | Complete |
| Failed(error) | update, finish, or another failure | original error with zero new progress | Failed(error) |
| Any | reset | no result | Active at source position zero |

`OutputFull` is a retryable status, not a failure. It reports a non-zero
minimum output capacity for the next retry. `Progress` reports only the exact
input prefix accepted and output prefix initialized by that call. A caller
must not infer progress from buffer capacity, a sink's prior length, or a
failed operation.

Truncation is an input failure discovered by `finish`; it is not `NeedInput`
after the caller has declared end of input. The first `finish` closes input
even when bounded final output still needs retry. Only later `finish` calls may
drain that output; `update` cannot reopen the message. Repeated `finish` is
idempotent only after successful completion. A failed state returns its
original failure until reset so later calls cannot erase the first cause.

## Source Positions

Every diagnostic index is the original absolute byte index in the source,
including bytes discarded by compatibility whitespace removal, line parsing,
or in-place compaction. Chunk boundaries do not restart indexes. A
whitespace-only chunk still advances the original source position.

Each input chunk preflights its complete source span before any byte is
processed. Position accumulation uses `checked_add`. If the end position is
not representable, the operation consumes no byte from that chunk and enters
absorbing `position-overflow`. A successful call advances the absolute source
position by exactly `Progress::input_consumed`; an unconsumed suffix retains
its original index when retried. It never saturates different positions to
`usize::MAX`.

## Error Taxonomy

- `NeedInput`, `OutputFull`, and `Complete` are non-failing statuses.
- Input errors classify attacker-controlled malformed data: invalid bytes,
  invalid padding, noncanonical trailing bits, invalid length, truncation,
  trailing data, and invalid wrapping.
- `resource-limit` means an explicit caller or policy limit was exceeded.
- Backend faults classify internal integrity failures: self-test failure,
  checked-output mismatch, impossible backend state, or failed scalar retry.
- Terminal errors classify input supplied after finalization begins or after
  successful completion.

An attacker-controlled byte is never reported as a backend fault, and a
backend integrity failure is never downgraded to malformed input. Ordinary
`InputError::Display` may include the rejected byte and original source index.
Its `Debug` representation is redacted. Secret APIs never return detailed
input diagnostics and callers must not log ordinary `Display` output for
secret-bearing input.

## Destination Contract Matrix

| API family | Overlap | On returned preflight/input/limit/backend error | Successful or partial commit |
|---|---|---|---|
| One-shot caller slice | Input and output must be disjoint unless the API is explicitly in-place | Entire destination unchanged | Exact initialized prefix returned; bytes outside it unchanged unless an explicit clear policy says otherwise |
| Allocating one-shot | Internal allocation only | No value returned; temporary initialized storage is discarded or wiped according to its type | Returned value contains exactly the completed output |
| `Vec`/`String` append | Source must not alias a destination whose growth may reallocate | Original prefix and entry length restored on every returned crate error and unwind | New suffix committed only after success |
| Formatter or writer | Third-party sink controls overlap and side effects | No rollback promise; already accepted writes remain | Irrevocable committed prefix; progress counts only writes reported successful |
| Incremental transform | Current input and output chunks must be disjoint | Previously reported prefixes remain; failure becomes absorbing | Exact `Progress`; completed output quanta remain committed; `OutputFull` is retryable |
| In-place transform | Exact documented buffer is the only permitted overlap | Full validation, sizing, and backend health checks occur before mutation, so every safe returned error leaves the buffer unchanged | Mutation kernel is infallible after its first write; returned length identifies the initialized prefix |
| Bounded secret frame | Public destination is byte-disjoint from private staging | Preflight leaves both unchanged; invalid input leaves public bytes unchanged and wipes staging; internal integrity fault wipes both complete ranges | Public release occurs only after the final validity and assurance gate |
| Unbounded stream | Source and sink are independent I/O objects | Prior sink writes cannot be rolled back; state absorbs the failure | Prefix committing with bounded internal storage and exact accepted-byte progress |

The in-place contract does not permit a recoverable error after mutation
starts. An implementation that cannot prove an infallible mutation phase must
stage or use a different API contract. Internal impossible states detected
before mutation are backend faults and leave the destination unchanged.

Caller-provided overlapping slices outside an explicitly in-place API are a
contract violation that safe Rust borrowing normally prevents. Unsafe foreign
callers must uphold the same non-overlap rule.

## Allocation And Abort Boundary

Fallible allocation paths use checked lengths and return typed allocation or
resource failures where the platform API permits. Process termination from
the global allocator, `panic=abort`, stack exhaustion, forced process death,
or a third-party callback that aborts are outside catchable rollback claims.
No documentation may describe abort recovery as atomic rollback.

For unwind-capable append helpers, guards restore the entry length before an
unwind escapes. This does not claim that allocator pages, prior reallocations,
foreign sinks, logs, swap, or crash dumps are erased.

## Leakage Taxonomy

The following are public unless a higher-level protocol hides them:

- input length, output capacity, configured limits, codec and wrapping policy;
- call and chunk boundaries, exact progress, and `OutputFull` requirements;
- selected backend, backend quarantine state, assurance class, and protocol
  scope;
- final ordinary success/failure and explicitly declassified output.

Before a secret operation's final release gate, the following are secret:

- validity and the location or value of an invalid byte;
- padding and trailing-bit validity;
- decoded plaintext and secret-bearing intermediate values.

Secret operations therefore use opaque failures, bounded work, private
staging, cleanup, and an explicit declassification boundary. Ordinary APIs do
not become constant-time merely because their `Debug` output is redacted.

## Stable Classification Policy

Public error, status, backend, assurance, atomicity, and protocol enums are
`#[non_exhaustive]`. Downstream matches must include a wildcard. Minor releases
may add variants but may not remove, rename, or repurpose an existing stable
lowercase identifier. Consumers of machine reports must preserve or safely
classify unknown identifiers instead of treating them as success.

The stable identifiers classify behavior; they are not a promise about enum
discriminants, memory layout, human-readable `Display` wording, or a specific
accelerated implementation.