base64-ng 0.9.0

no_std-first Base64 encoding and decoding with strict APIs and a security-heavy release process
Documentation
# Migrating from the `base64` Crate

This guide targets projects using `base64` `0.22.x`.

`base64-ng` is intentionally stricter and smaller. It does not try to mirror
every compatibility setting from `base64`; it provides a strict RFC 4648 scalar
core, caller-owned buffers, optional allocation helpers, and release evidence.

## Dependency

Before:

```toml
[dependencies]
base64 = "0.22"
```

After:

```toml
[dependencies]
base64-ng = "0.9.0"
```

For embedded or freestanding use:

```toml
[dependencies]
base64-ng = { version = "0.9.0", default-features = false }
```

## Engine Mapping

| `base64` engine | `base64-ng` engine |
| --- | --- |
| `base64::engine::general_purpose::STANDARD` | `base64_ng::STANDARD` |
| `base64::engine::general_purpose::STANDARD_NO_PAD` | `base64_ng::STANDARD_NO_PAD` |
| `base64::engine::general_purpose::URL_SAFE` | `base64_ng::URL_SAFE` |
| `base64::engine::general_purpose::URL_SAFE_NO_PAD` | `base64_ng::URL_SAFE_NO_PAD` |

## Encoding

`base64`:

```rust
use base64::{Engine as _, engine::general_purpose::STANDARD};

let encoded = STANDARD.encode(b"hello");
assert_eq!(encoded, "aGVsbG8=");
```

`base64-ng` with allocation:

```rust
use base64_ng::STANDARD;

let encoded = STANDARD.encode_string(b"hello").unwrap();
assert_eq!(encoded, "aGVsbG8=");
```

`base64-ng` with caller-owned output:

```rust
use base64_ng::{STANDARD, checked_encoded_len};

let input = b"hello";
let mut output = vec![0u8; checked_encoded_len(input.len(), true).unwrap()];
let written = STANDARD.encode_slice(input, &mut output).unwrap();
output.truncate(written);

assert_eq!(output, b"aGVsbG8=");
```

## Decoding

`base64`:

```rust
use base64::{Engine as _, engine::general_purpose::STANDARD};

let decoded = STANDARD.decode("aGVsbG8=").unwrap();
assert_eq!(decoded, b"hello");
```

`base64-ng` with allocation:

```rust
use base64_ng::STANDARD;

let decoded = STANDARD.decode_vec(b"aGVsbG8=").unwrap();
assert_eq!(decoded, b"hello");
```

`base64-ng` with caller-owned output:

```rust
use base64_ng::{STANDARD, decoded_capacity};

let input = b"aGVsbG8=";
let mut output = vec![0u8; decoded_capacity(input.len())];
let written = STANDARD.decode_slice(input, &mut output).unwrap();
output.truncate(written);

assert_eq!(output, b"hello");
```

## Strictness Differences

`base64-ng` rejects ambiguous input by default:

- whitespace is not ignored
- mixed standard and URL-safe alphabets are rejected
- padding in the payload body is rejected
- trailing bytes after terminal padding are rejected
- non-canonical trailing bits are rejected
- padded engines require canonical padding

If the old project depends on line-wrapped or spaced Base64, use the explicit
legacy whitespace APIs:

```rust
use base64_ng::STANDARD;

let decoded = STANDARD.decode_vec_legacy(b" aG\r\nVs\tbG8= ").unwrap();
assert_eq!(decoded, b"hello");
```

The legacy profile only ignores ASCII space, tab, carriage return, and line
feed. It still rejects mixed alphabets, malformed padding, trailing payload
after padding, and non-canonical trailing bits. If the old project accepts
broader non-canonical input, normalize or reject that input before calling
`base64-ng`.

## Length And Memory Handling

`base64-ng` exposes recoverable length helpers:

```rust
use base64_ng::{
    LineEnding, LineWrap, checked_encoded_len, checked_wrapped_encoded_len, decoded_capacity,
};

assert_eq!(checked_encoded_len(5, true), Some(8));
assert_eq!(
    checked_wrapped_encoded_len(5, true, LineWrap::new(4, LineEnding::Lf)),
    Some(9)
);
assert_eq!(decoded_capacity(8), 6);
```

Use `checked_encoded_len` for untrusted length metadata before allocating.
Use `decode_slice` or `decode_in_place` when a caller-owned memory limit is
required.

## Streaming

Enable the `stream` feature for `std::io` wrappers:

```toml
[dependencies]
base64-ng = { version = "0.9.0", features = ["stream"] }
```

```rust
use std::io::Write;
use base64_ng::{STANDARD, stream::Encoder};

let mut encoder = Encoder::new(Vec::new(), STANDARD);
assert_eq!(encoder.engine(), STANDARD);
assert!(encoder.is_padded());

encoder.write_all(b"he").unwrap();
assert!(encoder.has_pending_input());

encoder.write_all(b"llo").unwrap();
assert!(encoder.has_pending_input());

encoder.try_finish().unwrap();
assert!(encoder.is_finalized());

let encoded = encoder.finish().unwrap();

assert_eq!(encoded, b"aGVsbG8=");
```

Writer adapters expose `try_finish()` when a caller wants to finalize pending
Base64 input and flush the wrapped writer without immediately consuming the
adapter. After successful finalization, later non-empty writes return
`InvalidInput`. Writer adapters buffer encoded or decoded output internally
before draining it into the wrapped writer, so failed wrapped writes can be
retried by calling `flush()` or `try_finish()` again without re-encoding or
re-decoding accepted input. Direct `write()` calls may report partial progress;
use `write_all()` when the whole input slice must be consumed. Stream adapters
also expose non-sensitive state helpers such as
`engine()`, `is_padded()`, `pending_len()`, `has_pending_input()`,
`pending_input_needed_len()`, `buffered_output_len()`,
`buffered_output_capacity()`, `buffered_output_remaining_capacity()`, and
`has_finished_input()`, and decoder-side `has_terminal_padding()` for framed
protocols and audit logging. Use `can_into_inner()` and `try_into_inner()` when
recovering the wrapped reader or writer should be refused if it would discard
pending input or buffered output. Decoder writer and reader adapters also
expose `is_failed()` and fail closed after malformed Base64 input; unchecked
`into_inner()` remains available for explicit recovery of the wrapped object
after a decode error.

The `tokio` feature is reserved for future async wrappers. It is currently
inert and dependency-free; use the explicit `stream` feature for `std::io`
wrappers.

## Security Notes

The scalar encode/decode core has no external crate dependencies and remains
safe Rust. The only scalar-side unsafe code is the audited volatile wipe helpers
used by clear-tail and secret-buffer cleanup APIs; architecture-specific unsafe
code remains limited to the dedicated SIMD boundary.
Release gates include tests, clippy, docs, dependency policy, RustSec audit,
license review, SBOM generation, reproducible package/build checks, and Miri
when installed.

`base64-ng` currently hardens obvious timing pitfalls in scalar encode/decode,
but it does not claim a formally verified cryptographic constant-time API.