Repository navigation
refactor: untangle the codec's read side - #54
Merged
Merged
Conversation
- A field of each decoder fails in turn: the exact error text, and the packet SnmpDecodePacket returns with it, whose fields read before the failure are set and the later ones are not. The variables are an empty list, not nil, for any input of two octets or more, decoded or not (known bug). The decode golden records outcomes, not error texts or partial packets. - The trap table path tries a user's parameter sets in the order they were added, each on its own copy of the trap: a later set decodes after an earlier one failed, and when none does the error carries the last attempt's.
… own loop - readVersion takes only the message: unmarshalVersionFromHeader took a packet to nil-check it (no caller passes nil) and to preset its variables, which unmarshalHeader now does itself, under the same condition, marked as the known bug it is. UnmarshalTrap and the engine harness no longer allocate a throwaway packet for it. - unmarshalPayload loses the same unreachable nil-packet check. - UnmarshalTrap's table branch is unmarshalTrapByUser: one attempt per parameter set on bytes.Clone of the trap, and an explicit last error instead of one carried by a variable shadowing the named result.
unmarshalVBL's loop body reads one varbind; it is readVarbind now, the read-side counterpart of appendVarbind. The list's own header is checked on the slice, so no reader is shadowed by the varbind's and no tag variable is declared three times. Varbinds decoded before an error stay appended, as before.
Twenty header field reads each spelled seven lines: read, wrap the error as "error parsing <field>", assign when the field has the expected type. A fieldReader keeps the first error, with the same text, and returns false for every read after it, so each field is a three-line read-and-assign and the decoder checks the error once. Tried on unmarshalResponse first (it now takes the PDU type from its caller, and its GetBulk/else branches hold two reads each), then used for the community, the SNMPv1 trap header, the SNMPv3 header, the scoped PDU and the USM parameters. The security model is still taken only when something follows it; the version and the varbind name stay explicit reads, as a check follows each before its value is used.
The table is consulted only for SNMPv3 traps; looking up a user for an SNMPv1 or SNMPv2c trap would panic on its missing security parameters. Passes on the code before the codec reads refactor too.
The field reader kept a pointer to its reader beside its error. Escape analysis tracks a struct variable as one location, so returning the error moved every reader it wrapped to the heap: two more allocations per decoded message, three for SNMPv3. The reader now holds only the error and each read takes the reader; readField is its one read, and readInt, readString and readUint are gone. The version and the varbind name, single reads each followed by its own check (the name once per varbind), call readField directly.
A header field of another type than expected is skipped. For the USM fields, whose parameters start as a copy of the decoder's, the decoder's engine ID, boots, time and user name stay. Passes on the code before the codec reads refactor too.
ilyam8
added this pull request to stack #53
October 13, 2026 09:19
ilyam8
marked this pull request as ready for review
October 13, 2026 09:19
stelfrag
approved these changes
Oct 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 6 (guardrails), codec reads. No behavior change, no exported API change.
", assign when the field has the expected type). A small
fieldReaderkeeps the first error, with the sametext, and makes every later read a no-op, so each field is a three-line read-and-assign and a decoder checks the
error once. Tried on
unmarshalResponsefirst (which now takes the PDU type from its caller, and whose GetBulk/elsebranches hold two reads each), then used for the community, the SNMPv1 trap header, the SNMPv3 header, the scoped
PDU and the USM parameters.
readFieldis its one read;readInt,readStringandreadUintare gone, and theversion and the varbind name, single reads each followed by its own check, call
readFielddirectly.readVarbind, the read-side counterpart ofappendVarbind; no readeror tag variable is shadowed any more.
readVersiontakes only the message. Its predecessor took a packet only to nil-check it (no callerpasses nil) and to preset its variables, which
unmarshalHeadernow does itself under the same condition, marked asthe known bug it is.
unmarshalPayloadloses the same unreachable nil check.UnmarshalTrap's table branch isunmarshalTrapByUser: one attempt per parameter set on a clone ofthe trap, with an explicit last error instead of one carried by a variable shadowing the named result.
Validation
text and the partial packet
SnmpDecodePacketreturns (the decode golden records outcomes only); the trap table'sattempts (a later set decodes after an earlier one failed, the last attempt's error is reported, the caller's trap
is untouched); a table receiver decoding SNMPv1 and SNMPv2c traps; mistyped header fields keeping the value already
there (the USM parameters start as a copy of the decoder's).
SNMPv1/SNMPv2c traps, and mistyped header fields keeping the value already there); 3 cannot change any reachable
outcome.
time +1-3% on larger messages and up to +9% (+28 ns) on the smallest SNMPv3 one, about 2.3 ns per header field read:
a reader that skips after a failure and keeps the error does not fit Go's inlining budget. Accepted for the
readability. A first form of the reader kept the reader beside its error, and escape analysis, which tracks a struct
as one value, moved every decoder's reader to the heap (+2 allocations per message); fixed here.
go test -race, FIPS 140-only, golangci-lint, cross-builds, govulncheck: pass locally.