Skip to content

refactor: simplify the BER writer and the length reader - #55

Merged
ilyam8 merged 4 commits into
codec-readsfrom
ber-writer-reader
Oct 13, 2026
Merged

ilyam8 merged 4 commits into
codec-readsfrom
ber-writer-reader

Conversation

@ilyam8

@ilyam8 ilyam8 commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

Summary

Phase 6 (guardrails), BER writer and reader in internal/ber. No behavior change, no exported API change.

  • OID arcs. AppendOID's nested index loops become a loop over the rest of the OID that skips empty
    sub-identifiers and takes each arc with nextArc, which parses up to the next dot in one pass with the same
    left-to-right errors. strconv.ParseUint over arcs split with strings.Cut was tried first and made AppendOID
    almost three times slower (two scans per arc and its general-purpose parsing); nextArc's doc records why.
  • Lengths in one place. End writes a long-form length with AppendLength instead of its own copy of the length
    octets.
  • Base 128. appendBase128 takes a uint64 and sizes its groups with bits.Len64.
  • Length. Each case returns on its own; the long form checks its octet count before reading them. Same results
    and errors.

Validation

  • New pins, each passing on the code before the refactor: AppendOID's error precedence (out of range before a
    non-digit in one arc and the reverse, arc errors before the count check, the first and second arcs out of range,
    signs, underscores, hex prefixes, spaces, leading zeros), and Length's long form at the size of int (built from
    strconv.IntSize, so the linux/386 job checks the 32-bit edges).
  • Codec, engine, FIPS 140-only and USM goldens unchanged.
  • 21 mutation probes: 19 killed (two by pins added after the first run: the octet after '9', and length octets that
    pass the int range and wrap back); 2 cannot change any output (nextArc leaving the dot to the caller's empty-arc
    skip, and End's short-form bound at exactly 127).
  • A/B (8 interleaved rounds against the previous PR's head): AppendOID -15.5% (32.6 to 27.5 ns), encoding -5% to
    -11%, decoding unchanged, allocations unchanged.
  • go test -race, FIPS 140-only, golangci-lint, cross-builds, govulncheck: pass locally.

- AppendOID reads each arc left to right and fails at its first bad
  octet: a value crossing 2^32-1 before a non-digit is out of range,
  the reverse is invalid; signs, underscores, hex prefixes and spaces
  are invalid, leading zeros are not; an arc error comes before the
  sub-identifier count check, the first and second arc's range before
  their other rules.
- Length's long form at the size of int: the largest length whose
  header still fits, length octets whose header overflows, length
  octets past the int range, length octets cut short, and more length
  octets than an int holds, which wrap silently. The cases are built
  from strconv.IntSize, so they hold on 32-bit platforms too.
- AppendOID splits arcs with strings.Cut and parses each with
  strconv.ParseUint(arc, 10, 32) instead of its own digit loop.
  ParseUint reads left to right and fails at the same octet: a
  non-digit is a syntax error, the digit crossing 2^32-1 a range error,
  mapped to ErrInvalidOID and ErrOIDOutOfRange.
- End writes a long-form length with AppendLength instead of its own
  copy of the length octets.
- appendBase128 takes a uint64 and sizes its groups with bits.Len64.
- Length returns from each case: the short-input and short-form cases
  had nothing to add to the shared tail, and the long form checks its
  octet count before reading them; the errors are the same.
strconv.ParseUint over arcs split with strings.Cut made AppendOID about
three times slower (32 to 90 ns for a 9-arc OID; encoding a 10-varbind
request 545 to 1488 ns): its general-purpose parsing and a second scan
of each arc cost more than the digit loop it replaced. nextArc parses
one arc up to the next dot, with the same left-to-right errors, in a
single pass: 27 ns.
- A colon, the octet after '9', is not a digit of an OID arc.
- Length octets that pass the int range and then wrap back to a small
  value are rejected where they first pass it, not accepted with the
  wrapped length.

Both pass on the code before the BER refactor.
@ilyam8
ilyam8 added this pull request to stack #53 October 13, 2026 09:51
@ilyam8 ilyam8 changed the title ber writer reader refactor: simplify the BER writer and the length reader Oct 13, 2026
@ilyam8
ilyam8 marked this pull request as ready for review October 13, 2026 09:51
@ilyam8
ilyam8 merged commit c6b27bd into master Oct 13, 2026
22 checks passed
@ilyam8
ilyam8 deleted the ber-writer-reader branch October 13, 2026 14:22
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.

2 participants