Repository navigation
nvx-rs: encode the NVXS roles' DATA and CREDIT with one function - #747
Merged
Pedro Henrique Penna (ppenna) merged 1 commit intoOct 22, 2026
Merged
Conversation
HostSession::send, GuestSession::send, and GuestSession::credit, in protocol/nvxs, each encoded a record at the next sequence number of its direction in the same steps: take the number after the current one with next_sequence, which fails at the last one, encode the record at the current number, and store the next number only once the record has encoded. The two sends spelled those steps around encode_data, which took the sequence number by value, and credit spelled them around encode_record. A private function of protocol/nvxs/mod.rs, encode_next, now takes those steps for a record of any type: it takes the direction's sequence number by reference, encodes the record at it with the session's identity, and advances it only once the record has encoded. encode_data takes the sequence number by reference too and encodes its DATA record with encode_next, so that each send is one call, and credit encodes its CREDIT with it. So the rule by which a direction numbers its records, which the broker checks, has one definition. Behavior is unchanged: each record carries the same bytes and sequence number as before, the checks run in the same order, with SequenceOverflow before any RecordError, and a failure leaves the output and the sequence number as they were, since encode_record checks a record before it writes it. No public item, message, error, or protocol byte changes, and the guest's ACK keeps its explicit sequence number 0. The session tests of both roles cover the numbering of DATA and CREDIT, the refusal of empty DATA, and refused credits, and the NVXS integration tests, which drive both roles' connections through a broker, and the managed agent's tests exchange records through both roles. A new unit test, a_direction_advances_its_sequence_only_past_the_records_that_it_encodes, covers the last sequence number, which no test can reach through a role, and the hypervisor tests ran the guest agent and the host, built from this change, through OpenVMM's own NVXS broker. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The focused refactor preserves validation order, encoding, and failure behavior, with regression coverage and no unresolved issues.
No open issues 🎉
What changed in this PR
This PR consolidates NVXS record sequencing in nvx-rs, giving host and guest DATA sends and guest CREDIT records one shared encoding path without changing public APIs or wire behavior.
Changes:
- Adds private
encode_nextto advance sequence numbers only after successful encoding. - Routes both roles’ DATA sends and guest CREDIT encoding through the shared helper.
- Adds a regression test for successful advancement, malformed DATA, and sequence overflow.
| File | Description |
|---|---|
nvx-rs/src/protocol/nvxs/tests/record.rs |
Tests sequence advancement and failure behavior; updates module documentation. |
nvx-rs/src/protocol/nvxs/mod.rs |
Introduces encode_next and delegates DATA encoding to it. |
nvx-rs/src/protocol/nvxs/host.rs |
Simplifies host DATA sends using the shared sequencing logic. |
nvx-rs/src/protocol/nvxs/guest.rs |
Shares sequencing logic for guest DATA and CREDIT records. |
Review effort: Balanced
Pedro Henrique Penna (ppenna)
deleted the
nvx-rs-quality/nvxs-next-sequence
branch
October 22, 2026 10:43
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
HostSession::send,GuestSession::send, andGuestSession::credit, the NVXS state machines' encoders of DATA and CREDIT innvx-rs/src/protocol/nvxs, each encoded a record at the next sequence number of its direction in the same steps, such as the host's:That is: take the number after the current one with
next_sequence, which fails at the last one, encode the record at the current number, and store the next number only once the record has encoded. The twosends spelled those steps aroundencode_data, which took the sequence number by value, andcreditspelled them aroundencode_record. So the rule by which a direction numbers its records, which OpenVMM's broker checks, had three definitions. A private function ofprotocol/nvxs/mod.rs,encode_next, now takes those steps for a record of any type, with the direction's sequence number by reference, andencode_dataencodes its DATA record with it.This is the thirty-first
nvx-rs-quality/pull request. Of the earlier thirty, #603 through #705 merged and #742 is open; each had a Copilot overview review that recommended approval with no comment to follow, #636 and #637 also have approvals without comments, and none was closed without merging. Five of them, #678, #683, #685, #689, and #705, listed this change as a follow-up, and none has touchedprotocol/nvxs/{mod,guest,host}.rs.This run surveyed the crate by reading, by the follow-ups of the earlier pull requests, and with a throwaway script, kept outside the repository, that reports every window of six or more whitespace-normalized lines that repeats in
nvx-rs/srcandnvx-rs/tests, ignoring blank lines, comments, and lines of punctuation, exactly and with names and literals abstracted. It skipped every file that an open pull request todevchanged when the run started, among themREADME.md,Cargo.toml,src/lib.rs,protocol/mod.rs,guest/mod.rs,guest/sys/{mod,fd,filesystem,ioctl}.rs,guest/cgroup.rs,guest/mappings.rs,guest/time/checks.rs,guest/managed_agent/seam.rs,host/artifacts.rs,host/filesystem.rs,host/instance.rs,host/process.rs,host/platform,host/python/{mod,fs,windows}.rs,host/store/{mod,files,tests}.rs,host/supervisor/mod.rs,host/sandbox/mount.rs,host/cli/{mod,args}.rs,host/cli/sandbox,testing/fake_openvmm.rs,tests/cli.rs, andtests/fake_openvmm.rs. Among the remaining candidates, this one gives a protocol rule of the shared host and guest code one definition for 84 changed lines, about a third of them a new test. The others repeat test fixtures, or share a few statements behind a helper whose signature and documentation cost about as many lines as it saves; Follow-ups lists them.Where the choice was open, I took the conservative option:
encode_nextandencode_datastay private toprotocol::nvxs, besidenext_sequence, and take the sequence number by reference rather than the session, so that both roles, whose states differ, call them with the field that they keep.GuestSession::acknowledgekeeps encoding its ACK withencode_recordat sequence number 0. A resetting guest's sequence number is always 0, soencode_nextwould number the ACK the same way, but the protocol fixes the ACK at 0, and the explicit number says so.Changes
All changes are in
nvx-rs/src/protocol/nvxs: 57 insertions and 27 deletions in four files.mod.rs:encode_next(record_type, identity, sequence: &mut u64, payload, output), a private function with a doc comment, encodes a record ofrecord_typewithpayloadand the session'sidentityat the number thatsequenceholds, and then advancessequencepast it. A record that would take the direction's last sequence number, or that would be malformed, fails without encoding anything or advancingsequence.encode_datanow takessequence: &mut u64too: it takes at mostOUTER_MAX_PAYLOADbytes from the front ofdata, as before, encodes them in a DATA record withencode_next, and returns how many it took.HostSession::sendinhost.rs(nvx::protocol::nvxs::HostSession::send): one call ofencode_dataafter its state check;GuestSession::sendinguest.rs(nvx::protocol::nvxs::GuestSession::send): the same;GuestSession::creditinguest.rs(nvx::protocol::nvxs::GuestSession::credit):encode_nextwith its CREDIT after its state and credit checks, then the decrease of the outstanding bytes.tests/record.rs: a new unit test,a_direction_advances_its_sequence_only_past_the_records_that_it_encodes, which the module documentation now mentions. It encodes a DATA record at sequence number 7 and checks that the number advances to 8, and that empty DATA, a malformed record, and DATA atu64::MAX, the last sequence number, fail, with aRecordErrorand withSequenceOverflow(Data), without encoding anything or advancing the number.Behavior
Unchanged:
next_sequence, whoseSequenceOverflowcomes before anyRecordErrorofencode_record, as when each caller callednext_sequencebefore it encoded. A failure leavesoutputand the sequence number as they were, sinceencode_recordchecks a record before it writes it and the number advances only after it;creditstill decreases the outstanding bytes only once its record has encoded.sendstill returns the bytes that it took, at mostOUTER_MAX_PAYLOAD. Both functions are private: no public item, message, error, protocol byte, or golden fixture changes. The guest images change only becausemod.rs,guest.rs, andhost.rsare among their sources; no guest program's command line, exit status, or output changes.protocol/nvxs/tests/:the_host_attaches_after_waiting_and_exchanges_dataandthe_host_sends_a_long_message_in_records_of_the_largest_payload, which also checks that empty DATA encodes nothing and takes no sequence number,the_guest_attaches_acknowledges_receives_and_credits, where ACK, CREDIT, and DATA share the guest's numbers, andthe_guest_credits_only_bytes_that_it_received, where refused credits change nothing;tests/nvxs, which driveGuestConnectionandHostConnection, the connections over which the guest's and the host's roles send, through a broker that relays records as OpenVMM's does, with messages that span records; the managed agent's dispatch tests; and the scenarios oftests/cli.rsandtests/fake_openvmm.rs;nvx-rsandaci_edge_sandboxes, all built from TREE, exchanged NVXS records through OpenVMM's own broker intest-aci-edge-sandboxes, themanaged-lifecyclescenario, and the other managed scenarios oftest-microvm.Follow-ups
Not in this pull request, to keep it to one change. I found no suspected bug.
protocol/nvxs/guest.rsandhost.rs:receivestill repeats the same nine lines after its state check in both roles; sharing them needs a trait over each role's parser,accept, andfail.acknowledgecould encode its ACK withencode_next; see Summary.host/python/dumps.rs:write_valuerepeats its bracket, comma, and indentation loop for lists and dicts, and its empty-container arms; one container writer would givejson.dumps's layout one definition.net::is_valid_guest_networkandguest_subnetinhost/network/mod.rsboth parse an IPv4ADDRESS/PREFIXand compute its mask, with prefix limits of 1 to 30 and 1 to 32; the host-loopback check ofopenvmm_argumentsreads only the prefix, which a shared parser must keep.guest/ingress/encode.rs'sListenerTable::addandguest/ingress/diag.rs'snote_wildcard_tcprepeat a bounded find-or-insert merge.guest/time_probe/latency.rsandwarp.rsrepeat the check that fails withunknown TSC rate; pass --tsc-hzand theirout of memoryreport.host/cli/session.rs:features,exec, andacknowledgedeach draw a request ID, set the deadline, and send the request.maps_payloadhelper ofguest/managed_agent/dispatch.rsand thepayloadhelper ofguest/managed_agent/mappings.rs, which are identical; the guest programs'argstest helpers; theshellcommand helpers ofguest/exec/tests.rs,guest/spawn.rs, andguest/process.rs; andtests/nvxs/broker.rs's parse loops.host/python/fs.rs's Windowsstatandlstat,write_privateinhost/instance.rsandhost/store/files.rs, andChecks::load's callers inguest/time.Validation
BASE:
2817f4eb79911ef8380dc2b580b9d5c77e6b5b21,origin/devwhen the run started and still when this pull request was opened.TREE:
0eb2bfbc105c1de3dcb4013b4724fd1384b26e2c, the tree of this commit. Every host applied the patch to BASE withgit apply --indexand checked thatgit write-treeequals TREE.Release on every host:
v0.1.0-dev.e714acbd47bf, whichdownloadchose because BASE and its parent, two[skip ci]baseline updates that change onlydata/, have no release. Its OpenVMM,9f04d05c5441a9b31f7f81effa0ce7c4fc66a0d3, is the pinned revision, as each host checked inbuild/openvmm.provenance.json.nvx-rs:downloadinstalled the release'snvx-rs, which BASE's sources built (source_sha256ca621c9931225e7c7a7fc219e098016e1cd9ca5aeff1e1def9d165a74a0a3d48), so each host removed it, rebuilt it from TREE with the Cargo command with whichnvx.pybuilds it, and recorded its provenance withrecord-nvx-rs-provenance. Each host checked that the record names TREE's sources (source_sha256a0a60f0ee17d00331a6dd3eecd58cab0fe482171b089cddce9216477eb924acd, which this machine also computes for TREE) and the new executable. Each host then built theopenvmm_e2eandmanaged_lifecycletest binaries, after its other Cargo work and before its hypervisor tests, whose logs show no compilation.Guest artifacts were rebuilt from TREE on prometheus30, whose user namespaces let Alpine's apk run its scripts (prometheus32 denies them):
build-guest-binaries --output, nine static executables;test-guest-binaries --binaries, where 34 tests passed;build-initramfswithNVX_GUEST_BINARIES.initramfs.cpio.gz(SHA-2560482761d8ef555f2ec469a27efeaa4d7bc721a1487f8e53104f9c0f0b7a7628a), its packages manifest (2763252b80b0aa36e8c0a59ead9251cc0555563be73d6634fde74af12d3223ae), and its provenance (11c0331b4cc7972bef769d8a1fa3c31878fafb31a81681b81d1b53356cc94485) went through this machine to the other two hosts, where the SHA-256 digests matched.Local checks with Rust 1.95.0. On Windows: rustfmt; Clippy with
-D warningson all targets with all features and on the library without default features;cargo testwith all features and with the default ones; and rustdoc with-D warnings. On WSL Ubuntu, where theguestmodule compiles: Clippy with-D warningswith all features and on the library without default features, and the NVXS unit tests, the new test among them.This machine also ran the steps for which the hosts lack the toolchain or target: on Windows, the MSRV (1.89) checks of
nvx,aci_edge_sandboxes, and the runtime adapter's protocol-only and host-only configurations, since prometheus28 has no 1.89; and, on WSL Ubuntu, the macOS (aarch64-apple-darwin) checks ofnvxandaci_edge_sandboxesand the MSRV check of the runtime adapter's guest-only configuration, which ran as the action defines it, starting with itsrustup target add, which gave the local 1.89 the musl target. I removed that target again afterwards.The change adds, moves, and deletes no file and changes no Markdown, so the CLI tests of validate-nvx and the link check had nothing to cover.
The hosts ran each action's steps with the toolchains and targets that they have, skipping the actions' installation commands, with
RUSTUP_AUTO_INSTALL=0; nothing was installed on them.test-aci-edge-sandboxestest-microvm, default suitenvxtests: 907 in 11 test targets with all features and 424 in 9 with the default ones; artifact bundling (15);check-guest-binaries(508 in 9 test targets); the runtime adapter's protocol-only, host-only, and musl guest-only configurations (13, 14, and 21 tests); 158 and 85aci_edge_sandboxestests over the two feature sets; agent bundling (4). The macOS checks and the guest-only MSRV check ran locallynvxtests: 503 in 11 test targets with all features and 426 in 9 with the default ones; artifact bundling (15); the runtime adapter's protocol-only and host-only configurations (13 and 14 tests); 158 and 85aci_edge_sandboxestests over the two feature sets; agent bundling (4). The MSRV checks ran locallyEvery gate and hypervisor test passed at its first run. The Linux gates ran in the foreground of their SSH session, whose shell ignored no signal (
SigIgn: 0), so the CLI's Ctrl-C tests ran as intended. Each suite'smanaged-lifecyclescenario ran themanaged_lifecycletest with Cargo, which passed. The time-ABI evidence lines counted no boot, capture, or restore over its CPU-time budget on any host, and the exhaustive check reportedok/8/0on each. On every host, oneopenvmm_e2etest printedskipped: the host has no second volumefor a check that needs one, and on the Linux hosts the filesystem caller-ownership scenario reported that OpenVMM cannot assume other host identities, so it checked that guest root and foreign guest callers fail withEPERM; both are properties of the hosts, not of this change.Left to CI:
test-openvmm-unitandtest-openvmm, since OpenVMM is unchanged; the VMM suite boots the Alpine image, which CI rebuilds becausemod.rs,guest.rs, andhost.rsofprotocol/nvxsare among its sources;test-microvmscenarios that run only when named:smp-lapic, andmanaged-exec-configandsandbox-lifecycle, which need artifacts that packages omit;verify-guest-determinism;