Skip to content

feat: add base16.decode_strict for canonical-form hex verification - #90

Merged
nao1215 merged 1 commit into
mainfrom
feat/issue-87-base16-decode-strict
May 10, 2026
Merged

feat: add base16.decode_strict for canonical-form hex verification#90
nao1215 merged 1 commit into
mainfrom
feat/issue-87-base16-decode-strict

Conversation

@nao1215

@nao1215 nao1215 commented May 10, 2026

Copy link
Copy Markdown
Owner

Summary

Adds `base16.decode_strict/1` to close the API symmetry gap with
`base32/rfc4648` and `base64/standard`, both of which already
expose `decode_strict`.

Changes

  • New `pub fn decode_strict(input: String) -> Result(BitArray, CodecError)`
    in `src/yabase/base16.gleam`.
  • Imports `NonCanonical` from `yabase/core/error`.
  • Adds 6 regression tests in `test/base16_test.gleam`.

Design decisions

Verification

  • `just check` passes (799 tests, README snippets verified).

Closes #87

Summary by CodeRabbit

  • New Features

    • Added strict Base16 decoder to enforce canonical uppercase hexadecimal format validation.
  • Tests

    • Added comprehensive test coverage for canonical form validation and error handling.
  • Documentation

    • Updated changelog documenting strict decoding behavior.

Review Change Stack

Mirrors the existing decode_strict pattern on base32/rfc4648 and
base64/standard. Returns Error(NonCanonical) when input decodes
successfully but is not byte-equal to encode/1's output — i.e.
lowercase, mixed case, or any deviation from RFC 4648 §8's
uppercase canonical form.

Useful for HMAC/TOTP/WebAuthn/content-addressable-storage contexts
where the encoded string itself is part of the contract and two
distinct wire forms ("DEADBEEF" vs "deadbeef") for the same bytes
are a replay-attack surface that strict mode is meant to close.

The lenient decode/1 path is unchanged — it still accepts both
cases for the wide-tolerance read side.

Adds 6 regression tests covering uppercase canonical, lowercase
rejection, mixed case rejection, InvalidCharacter pass-through,
InvalidLength pass-through, and the empty-input case.

Closes #87
@coderabbitai

coderabbitai Bot commented May 10, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1c74cb93-161d-45d2-86f1-99a5624b97f5

📥 Commits

Reviewing files that changed from the base of the PR and between 14f98ab and bc1f6fe.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • src/yabase/base16.gleam
  • test/base16_test.gleam

📝 Walkthrough

Walkthrough

Adds a strict decoder base16.decode_strict/1 that enforces RFC 4648 canonical uppercase form by re-encoding decoded bytes and comparing against the original input, returning Error(NonCanonical) for non-canonical hex. Includes comprehensive tests and changelog documentation. Closes #87.

Changes

Base16 Strict Decoder Implementation

Layer / File(s) Summary
Error Type Imports
src/yabase/base16.gleam, test/base16_test.gleam
Import NonCanonical error constructor alongside existing codec error variants.
Strict Decoder Logic
src/yabase/base16.gleam
New decode_strict/1 function decodes leniently via decode/1, re-encodes bytes with uppercase encode/1, and returns Error(NonCanonical) if the re-encoded string differs from the original input. Other errors propagate unchanged.
Test Coverage
test/base16_test.gleam
Six unit tests verify canonical uppercase acceptance, rejection of lowercase and mixed-case with NonCanonical, propagation of InvalidCharacter and InvalidLength errors, and acceptance of empty input.
Changelog
CHANGELOG.md
Document yabase/base16.decode_strict/1 with canonical-form semantics and closure of #87.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

Poem

🐰 A hex decoder hopped with cheer,
Now strict and canonical, crystal clear—
Uppercase only, no mixed case,
Canonical checks keep crooks from the base,
DEADBEEF stays, deadbeef must go!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a new base16.decode_strict function for canonical-form verification.
Linked Issues check ✅ Passed The implementation fully satisfies issue #87: decode_strict returns Error(NonCanonical) for non-canonical input, preserves decode/1's leniency, uses direct byte-equality check (encode(bytes) == input), and includes comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing decode_strict for base16 as specified in #87; no unrelated modifications are present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-87-base16-decode-strict

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@nao1215
nao1215 merged commit e6afd0c into main May 10, 2026
8 checks passed
@nao1215
nao1215 deleted the feat/issue-87-base16-decode-strict branch May 10, 2026 01:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

base16 lacks decode_strict — canonical-form check unavailable for hex digests

1 participant