Repository navigation
nvx-rs: encode activation records canonically - #873
Open
Enrique Saurez (esaurez) wants to merge 1 commit into
Open
Enrique Saurez (esaurez) wants to merge 1 commit into
Enrique Saurez (esaurez) wants to merge 1 commit into
Conversation
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The codec preserves validated record invariants, has extensive focused test coverage, and has no identified blocking issues.
No open issues 🎉
What changed in this PR
Adds canonical encoding and strict decoding for nvx::activation, completing the second step of #838. The shared codec prepares activation records for future host–guest transport and configuration identity hashing without changing runtime behavior.
Changes:
- Adds a bounded, versioned encoding with ordered, tagged, length-prefixed fields and strict rejection of non-canonical inputs.
- Adds byte-level, boundary, rejection, and property tests for round trips and decoder robustness.
- Documents the wire format, public field identifiers, and decoding errors.
| File | Description |
|---|---|
nvx-rs/src/activation/tests.rs |
Exposes existing fixtures for reuse by codec tests. |
nvx-rs/src/activation/mod.rs |
Documents the encoding and adds public field identifiers and decoding errors. |
nvx-rs/src/activation/codec/tests.rs |
Tests canonical bytes, validation failures, size limits, and round-trip properties. |
nvx-rs/src/activation/codec.rs |
Implements canonical encoding and strict decoding. |
nvx-rs/README.md |
Describes the codec API and expanded test coverage. |
doc/project-structure.md |
Adds canonical encoding to the activation module’s description. |
Review effort: Balanced
This was referenced Oct 24, 2026
Enrique Saurez (esaurez)
force-pushed
the
esaurez/838-pr2-activation-encoding-20261024
branch
from
October 24, 2026 19:06
a3b2fab to
7aaa36b
Compare
This was referenced Oct 27, 2026
The host writes an activation record, and the guest reads it, so they need one encoding of it, which also gives the configuration identity the bytes that it covers (#838). ActivationRecord::encode writes the canonical encoding, and ActivationRecord::decode reads it back: the record's version, a little-endian u16, then a tagged, length-prefixed field for each of the record's parts, in the increasing order of their tags, each at most once, without padding. Choices are one-byte codes from 1, integers, the store generation among them, are little-endian, and identities, IDs, and addresses are their bytes. The largest record takes 602 of the 1,024 bytes that MAX_RECORD_BYTES allows. The module's documentation lists every field. The decoder refuses bytes past MAX_RECORD_BYTES, bytes that end inside the version or a field, other versions, unknown, repeated, and misordered fields, values of another length or with codes that the version does not define, a mapping count or a store generation of 0, missing fields, a DNS server or a proxy without a network, and every configuration that Configuration::check refuses, in a documented order, naming the Field at fault, so that every record has exactly one encoding. The tests build their bytes from the definition, not with the encoder, and property tests check that generated records round-trip and that the decoder accepts only the canonical encoding of what it returns. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8f7bd07b-b09f-48d5-bc49-ad06f47ef3f9
Enrique Saurez (esaurez)
force-pushed
the
esaurez/838-pr1-activation-record-types-20261024
branch
from
October 27, 2026 01:11
dc4517f to
3032e6d
Compare
Enrique Saurez (esaurez)
force-pushed
the
esaurez/838-pr2-activation-encoding-20261024
branch
from
October 27, 2026 01:11
7aaa36b to
5edab9f
Compare
This branch has not been deployed
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.
Stacked on #871; this PR retargets to dev when the pull requests below it merge. The chain on
devis #866 → #882 → #883 → #876 → #871 → #873 → #874, after #863, #864, #865, #867, #868, #869, #870, #872, #875, and #878, which have merged. It builds on #871's third commit, which makes the store generation a nonzerou64, the publication ID of the template store of #839: the restore field holds it in 8 little-endian bytes and the decoder refuses 0, so the field takes 56 bytes and the largest record 602.Part of #838: the second of its three pull requests. The host writes an activation record and the guest reads it, so they need one encoding of it. This gives an
ActivationRecordone canonical encoding:ActivationRecord::encodewrites it, andActivationRecord::decodereads exactly those bytes back and refuses any others. It changes no behavior: nothing carries records yet.What changed
The encoding (
nvx-rs/src/activation/codec.rs; the module documentation's "Encoding" section defines it field by field). A record is its version, a little-endianu16, then, without padding, in the increasing order of their tags, each at most once, a field for each part that the record holds: its tag, one byte, the length of its value, one byte, and its value.0x01u64, and identity of the template's key0x100x110x200x300x400x410x500x510x520x600x610x620x70Choices are one-byte codes from 1, so that a zero byte names none, integers are little-endian, and identities, IDs, UUIDs, and addresses are their bytes. A part that a record lacks is a missing field, never a flag or an empty value. The largest record takes 602 of the 1,024 bytes that
MAX_RECORD_BYTESallows.The decoder refuses, naming the
Fieldat fault where there is one, and in a documented order: bytes pastMAX_RECORD_BYTES; bytes that end inside the version or a field; another version; an unknown, repeated, or misordered field; a value of another length than its contents take, with a code that the version doesn't define, with a mapping count or a store generation of 0, or with a proxy endpoint that isn't UTF-8; a missing field that every record holds; a DNS server or a proxy without a network; and every configuration thatConfiguration::checkrefuses, among them an export without mappings and mappings without an export. The refusals are new variants ofActivationError, andFieldis public. So every record has exactly one encoding.The encoder never writes what the decoder refuses. A record exists only once
ActivationRecord::neworActivationRecord::decodehas checked it, and its fields are private, soencodereturns bytes rather than aResult. The property tests check that every record that they generate reads back from its encoding.Documentation. The module documentation defines the encoding, and its example round-trips a record. The crate's README and
doc/project-structure.mdmention the encoding.Tests (
nvx-rs/src/activation/codec/tests.rs), whose bytes the tests build field by field from the definition, never with the encoder:u32::MAX; and the order of the refusals;Validation
At
5edab9f, rebased onto #871's head without changes to the contents of its commit, on Windows and on Linux x86_64:cargo fmt --check;cargo clippywith--all-targets --all-features,--lib,--lib --no-default-features, and--lib --no-default-features --features guest,-D warnings;cargo testwith all, default, and no features;cargo docwith-D warnings;cargo +1.89 check;aci_edge_sandboxes' checks;bundledartifacts test, the macOS check of both crates, the guest binaries' lint, tests, build, and check, and the Markdown link check.Before the rebase, on
dev, the same checks passed at7aaa36b.Left to CI: the whole matrix, which runs once this pull request targets
dev.