microsandbox-control-client 0.7.2

Framed host-control client with explicit legacy JSON compatibility.
Documentation

Rust control client

ControlConnection discovers whether an existing control endpoint supports framed CBOR or legacy JSON. ControlClient is the explicitly framed Client<ControlProtocol> entry point, and JsonControlClient is an explicit legacy unary adapter. Their checked helpers share the same operation records.

ControlConnection -- JSON capabilities --+-- advertised CBOR: fresh stream + hello
                                        |                      |
                                        |               ControlClient
                                        |
                                        +-- legacy: JsonControlClient

Automatic discovery has one deadline across the probe, redial, and hello. It selects CBOR only after a valid affirmative capability response. Malformed replies, arbitrary peer errors, empty EOF, and failed framed setup are errors. There is no error-string downgrade or mutation replay. ControlClient sends its hello directly when callers already know the peer supports framed control.

use microsandbox_control_client::{
    ControlConnection, ControlMessageType, ControlReply, Empty, GetMemoryState, TypedMessage,
};

async fn inspect(path: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {
    let connection = ControlConnection::connect(path).await?;
    let reply = connection.request(TypedMessage::new(ControlMessageType::MemoryState, Empty {})).await?;
    match reply {
        ControlReply::Framed(message) => println!("{}", message.t),
        ControlReply::Json(reply) => println!("{} original JSON bytes", reply.raw().len()),
    }
    println!("{} MiB", connection.request_typed(&GetMemoryState).await?.target_mib);
    connection.close().await;
    Ok(())
}

In framed mode, clones reuse one connection. Automatic JSON mode rediscovers before each fresh exchange when its connector cannot prove runtime identity. An owner implementing VerifiedControlConnector verifies the connected peer and the runtime's OS birth token on every dial, then rechecks the active run before each operation; connect_verified_connector can reuse that owner's JSON selection. A path or PID alone is insufficient. A detected change invalidates the handle before sending instead of silently retargeting the request. The runtime owner, such as an SDK backend, supplies that identity implementation; the protocol package does not claim to verify it itself.

JsonControlClient::new(path) and from_connector(...) are inert constructors for callers explicitly choosing legacy mode. Its request translates only known native requests and returns the actual JsonReply, including ok:false replies. Checked helpers report LegacyRemote with the original reply and unknown batch progress. JSON numbers retain their original tokens; checked memory fields use u64 without a floating-point intermediate. connection.framed() fails locally in JSON mode, and encoded-payload input is rejected there before dialing. Raw streams and exact packets remain available on the explicitly framed client.

connection.capabilities() borrows the validated discovery snapshot without network I/O. GetCapabilities remains available for an explicit fresh observation. closed().await observes shared closure, including an idle framed disconnect; a successful JSON exchange ending in EOF does not close the session.

Enable uds for Unix endpoint connections or named-pipe for Windows endpoint connections. Caller-owned transports and connectors can be used without either native feature. Endpoint paths are accepted verbatim.

use microsandbox_control_client::{
    ControlClient, GetMemoryState, SetMemoryTarget, size::SizeExt,
};

async fn resize(path: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {
    let client = ControlClient::connect(path).await?;
    let before = client.request_typed(&GetMemoryState).await?;
    let accepted = client.request_typed(&SetMemoryTarget::new(2048.mib())).await?;
    println!("{} -> {} MiB", before.target_mib, accepted.target_mib);
    client.close().await;
    Ok(())
}

Memory wire records retain u64 values. The existing size helpers retain their conversion rules; direct SetMemoryTarget { total_mib } construction provides full-width wire input. Target responses report acceptance and current observations, not guest convergence. Ordered secret batches preserve partial completion and the index of the first failed entry.

The client keeps request, stream, request_raw, stream_raw, explicit-ID send/send_raw, exact write_unchecked, owned into_parts, and checked request_typed operations. Generic requests return peer error frames as messages; checked helpers interpret them and retain the original response. No sandbox, execution, filesystem, or convergence service is required to use any of these paths.

use microsandbox_control_client::{ControlClient, EncodedMessage};

async fn inspect(client: &ControlClient, payload: Vec<u8>) -> Result<(), Box<dyn std::error::Error>> {
    let response = client.request(EncodedMessage::new("extension.inspect", payload)).await?;
    println!("{}: {} payload bytes", response.t, response.p.len());
    Ok(())
}

Setup defaults to one ten-second deadline and requests to a thirty-second local wait. Request expiry does not cancel remote work or trigger replay. ClientError retains NotSent versus Unknown delivery. Clones and owned streams share a connection; close closes it for all owners.

Run cargo test -p microsandbox-control-client --all-features --locked. For live tests, set MSB_CONTROL_TEST_SOCKET and MSB_CONTROL_TEST_MODE=cbor or json. The explicitly framed test requires CBOR; run only live_automatic_discovery_and_explicit_json -- --ignored --exact against a JSON-only runtime. Use bounded disposable fixtures (512 MiB and two CPUs), and run live tests serially. The automatic test changes targets and restores them through explicit JSON. Optional MSB_CONTROL_TEST_SECRET names a dummy fixture initially set to before and allowed for example.invalid; the test exercises legacy partial-failure reporting and restores that fixture. Skipped live tests are not runtime or platform validation.