Carry hibernate and resume over the control channel - #261
Open
bkearns wants to merge 4 commits into
Open
Conversation
The command names lived in two places -- ControlCommandType in the mobile core and CoordinatorCommand in the listener -- and had to match exactly or a command silently became 'unknown'. They drifted the first time anyone added one: coordinator_offer went into the listener, a build against a different worktree of the same repo compiled cleanly, and the resulting binary did not know the command. Nothing failed. The app simply waited. ferrosa-control-vocabulary defines the names, the read/write classification, which commands the coordinator answers, and which frame may carry a secret. Both sides compile it, so a name they disagree about cannot be expressed. No dependencies, deliberately: the phone, the desktop app, the listener and the streamer all compile it, and anything it pulls in they all pull in. Names and classifications need nothing. An unknown command is classified as a WRITE. Guessing 'read' for something a build cannot classify would let an unrecognised name past a read-only guard, and the safe direction for an unknown is restrictive. A path dependency, not a pinned rev -- these repos are all ours and a pin would be a second place to bump. The cost is that a consumer must sit beside ferrosa-memory on disk, which is what ferrosa-streamer's CI already arranges and what ferrosa-mobile's CI now does too. 8 vocabulary tests, plus agreement tests on both sides asserting each knows every name the other can send.
The coordinator can hibernate a microVM to disk and wake it again. Nothing could ask it to over WebRTC, because the shared vocabulary had no name for either. Both are added in ferrosa-control-vocabulary, which is the point of that crate existing: a name added once is a name both sides know, and the listener's effect/capability answers follow from it rather than being restated. The classification is the part worth checking rather than the spelling -- vm_list sits beside these in every listing and is a Read, and copying that across would put a command that stops a running machine behind a read-only guard. The VM id arrives inside a frame written by a peer and goes straight into a URL path segment, so it is validated here as well as by the coordinator: a slash re-points the request at a different endpoint and an encoded one walks out of /v1/vms. A whitelist, not an escaper. Worth recording for the next person: the dispatch match is exhaustive and DID catch the two missing arms -- but only with --features webrtc-transport. Without it, control_session is not compiled at all, so a plain `cargo build` reported success while none of this was checked.
There was a control-connect subcommand on an old branch and it took --api-key. That credential no longer exists: control-listen says so in its own help -- "the identity signs every request, so there is no bearer secret sitting on the machine" -- and the controller half had simply not been carried across the change. So it is rebuilt on Credential::device, the same way device-approve and control-listen authenticate, and it sends a real command rather than a line of text. The frame is built in code rather than at a shell prompt because the listener is strict in three ways that are each easy to get wrong by hand and each refuse differently: frame_id must be present and at most 128 bytes, command_id must be a UUID of version SEVEN specifically, and command_type must be a name the shared vocabulary knows. A v4 id is refused with a message about the id, which reads as malformed rather than as the wrong kind of id.
The vocabulary had no way to say it. /v1/launch is HTTP on loopback, reachable by the process beside the coordinator and by nothing else, so a phone could hibernate and resume VMs it had no way to create. vm_launch, not launch: it is one word from agent_launch, which starts a teammate INSIDE a runtime rather than creating one, and the listener would accept either. The payload is forwarded to the coordinator verbatim. It was built on the controller by shared Rust from the same offering this machine published, and rebuilding it here would give the two sides two ideas of what was asked for. The coordinator validates it again regardless; it does not trust the controller's copy either.
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.
Adds
vm_hibernateandvm_resumeto the control channel, so a phone can ask amachine to put a microVM to sleep and wake it again. The coordinator side is in
ferrosa-coordinator; this is the half that carries the request.What is here
The two commands live in
ferrosa-control-vocabulary. That crate exists so aname added once is a name both sides know — it was created after
coordinator_offerwas added to the listener and the streamer built cleanly against a different
worktree, producing a binary that did not know the command. Nothing failed; the app
simply waited.
The classification matters more than the spelling.
vm_listsits beside these inevery listing and is a
Read; copying that across would put a command that stops arunning machine behind a read-only guard. Both are
Write, and there is a testsaying so by name.
The VM id is validated before it goes in a URL. It arrives inside a frame
written by a peer and lands in a path segment: a slash re-points the request at a
different endpoint, and an encoded one walks out of
/v1/vmsentirely. A whitelist,not an escaper.
control-connectis rebuilt without an API key. An older branch had thissubcommand taking
--api-key. That credential no longer exists —control-listensays so in its own help — and the controller half had not been carried across the
change. It now uses
Credential::device, the same asdevice-approveandcontrol-listen.Frames are built in code, not at a shell prompt. The listener requires
command_idto be a UUID of version seven; a v4 is refused with a message aboutthe id, which reads as malformed rather than as the wrong kind of id.
Verification
ferrosa-memory-syncwith--features webrtc-transport.control_sessionis notcompiled at all, and a plain
cargo buildreports success while none of this ischecked. The exhaustive dispatch
matchdid catch the two missing arms — but onlywith the feature on.
What is NOT verified
The command has not been carried over a live WebRTC session end to end. The listener
on the target host answers and both sides exchange candidates through the gateway,
but ICE does not complete from the machine I am testing from — its application
firewall drops inbound UDP to a freshly built binary. The same listener binds
sessions from the phone without trouble, so this is a property of the test client,
not of this change. Stated here rather than left to be discovered.