feat(vnc-native): in-process RFB 003.008 server (VA-21) #21

Merged
David merged 2 commits from feat/vnc-native-va-21 into main 2026-05-20 11:09:58 +02:00
Owner

Summary

Closes VA-21. Adds USAGE_VNC_NATIVE = 8 tunnel mode: the agent speaks RFB 003.008 in-process, reusing meshagent_kvm for capture and input. No external helper binary - contrasted with VA-19's x11vnc / wayvnc bridge, this PR delivers the single-static-binary VNC story called out in the issue's acceptance criteria.

Scope picked: "Full acceptance" per the in-thread question.

What's in the diff

  • crates/meshagent/src/host/vnc_native/ (new):
    • proto.rs: PixelFormat (BGRX LE default), message codes (server + client), encoding numbers (Raw / CopyRect / Hextile / ZRLE / DesktopSize), security types (None / VeNCrypt), framing helpers.
    • handshake.rs: ProtocolVersion negotiation (rejects pre-003.008), security-type advertisement (VeNCrypt + None), SecurityResult, ClientInit / ServerInit.
    • tls.rs: VeNCrypt sub-handshake with X509None sub-type, ephemeral self-signed cert via rcgen, rustls TlsAcceptor reusing the workspace ring provider.
    • encoding/raw.rs: RFC 6143 §7.7.1.
    • encoding/hextile.rs: RFC 6143 §7.7.4 with histogram-based BG / FG / SubrectsColoured selection and cost-vs-Raw decisioning.
    • encoding/zrle.rs: RFC 6143 §7.7.6. Persistent flate2::Compress state across rectangles; one Sync flush per encode emits the 6-byte sync marker without ending the deflate stream.
    • input_map.rs: keysym -> meshagent_kvm::key_event (via the existing 0x<hex> keysym parser); pointer button-mask -> mouse_button / mouse_move.
    • server.rs: top-level state machine. 64x64 change-detection tile grid feeds the picked encoder (preference: ZRLE > Hextile > Raw, picked from client SetEncodings). DesktopSize pseudo-rect on resolution change. Parameterised over a CaptureSource trait so tests inject a stub frame source.
    • ws_io.rs: adapter from WebSocketStream to AsyncRead + AsyncWrite via tokio::io::duplex and a background pump task. Re-uses host::tunnel::control_echo for the rendezvous rtt probe.
  • crates/meshagent/src/host/tunnel.rs: USAGE_VNC_NATIVE constant, dispatch arm, docstring entry.
  • crates/meshagent/src/host/mod.rs: register the new module.
  • crates/meshagent/Cargo.toml: add flate2.
  • crates/meshagent-kvm: promote RawFrame + PixelLayout to the public surface and add capture_raw_frame() for uncompressed pixel access (the existing JPEG tile-diff stream is unchanged).
  • README.md: new "Native RFB / VNC server" bullet in Runtime dependencies (no system dep; rustls + rcgen come from existing workspace deps).

Acceptance checklist

  • RFB ProtocolVersion handshake (vncrypt_tls_handshake_completes_against_rustls_client drives the full path).
  • VeNCrypt-TLS security (rustls client end-to-end with AcceptAll cert verifier; in production the operator's VNC viewer accepts the self-signed cert via its usual trust prompt).
  • Raw + Hextile + ZRLE encodings emit valid frames (unit tests for each; the integration test exercises Raw end-to-end).
  • Keyboard + mouse round-trip (input_map translation tested; X11 / Wayland injection delegates to meshagent_kvm and is already covered there).
  • Bandwidth budget (1920x1080 solid frame + one-tile-row steady-state worst case both under the 2 MiB / frame budget = 30 MiB/s / 15 fps).
  • No regression in meshagent-kvm tile-diff tests.

Test plan

  • cargo test --workspace --lib (32 new vnc_native tests pass; full suite green).
  • cargo clippy --workspace --all-targets -- --deny warnings (clean).
  • cargo fmt --all --check (clean).
  • just pre-commit (full docker check: fmt + clippy + build + tests, all green).
  • Manual: drive an actual TigerVNC client against the agent over a rendezvous tunnel. Deferred until the server side is taught to issue USAGE_VNC_NATIVE = 8.
## Summary Closes VA-21. Adds `USAGE_VNC_NATIVE = 8` tunnel mode: the agent speaks RFB 003.008 in-process, reusing `meshagent_kvm` for capture and input. No external helper binary - contrasted with VA-19's `x11vnc` / `wayvnc` bridge, this PR delivers the single-static-binary VNC story called out in the issue's acceptance criteria. Scope picked: "Full acceptance" per the in-thread question. ## What's in the diff - `crates/meshagent/src/host/vnc_native/` (new): - `proto.rs`: PixelFormat (BGRX LE default), message codes (server + client), encoding numbers (Raw / CopyRect / Hextile / ZRLE / DesktopSize), security types (None / VeNCrypt), framing helpers. - `handshake.rs`: ProtocolVersion negotiation (rejects pre-003.008), security-type advertisement (VeNCrypt + None), SecurityResult, ClientInit / ServerInit. - `tls.rs`: VeNCrypt sub-handshake with `X509None` sub-type, ephemeral self-signed cert via `rcgen`, rustls `TlsAcceptor` reusing the workspace ring provider. - `encoding/raw.rs`: RFC 6143 §7.7.1. - `encoding/hextile.rs`: RFC 6143 §7.7.4 with histogram-based BG / FG / SubrectsColoured selection and cost-vs-Raw decisioning. - `encoding/zrle.rs`: RFC 6143 §7.7.6. Persistent `flate2::Compress` state across rectangles; one Sync flush per encode emits the 6-byte sync marker without ending the deflate stream. - `input_map.rs`: keysym -> `meshagent_kvm::key_event` (via the existing `0x<hex>` keysym parser); pointer button-mask -> `mouse_button` / `mouse_move`. - `server.rs`: top-level state machine. 64x64 change-detection tile grid feeds the picked encoder (preference: ZRLE > Hextile > Raw, picked from client `SetEncodings`). DesktopSize pseudo-rect on resolution change. Parameterised over a `CaptureSource` trait so tests inject a stub frame source. - `ws_io.rs`: adapter from `WebSocketStream` to `AsyncRead + AsyncWrite` via `tokio::io::duplex` and a background pump task. Re-uses `host::tunnel::control_echo` for the rendezvous rtt probe. - `crates/meshagent/src/host/tunnel.rs`: `USAGE_VNC_NATIVE` constant, dispatch arm, docstring entry. - `crates/meshagent/src/host/mod.rs`: register the new module. - `crates/meshagent/Cargo.toml`: add `flate2`. - `crates/meshagent-kvm`: promote `RawFrame` + `PixelLayout` to the public surface and add `capture_raw_frame()` for uncompressed pixel access (the existing JPEG tile-diff stream is unchanged). - `README.md`: new "Native RFB / VNC server" bullet in Runtime dependencies (no system dep; rustls + `rcgen` come from existing workspace deps). ## Acceptance checklist - [x] RFB ProtocolVersion handshake (`vncrypt_tls_handshake_completes_against_rustls_client` drives the full path). - [x] VeNCrypt-TLS security (rustls client end-to-end with `AcceptAll` cert verifier; in production the operator's VNC viewer accepts the self-signed cert via its usual trust prompt). - [x] Raw + Hextile + ZRLE encodings emit valid frames (unit tests for each; the integration test exercises Raw end-to-end). - [x] Keyboard + mouse round-trip (`input_map` translation tested; X11 / Wayland injection delegates to `meshagent_kvm` and is already covered there). - [x] Bandwidth budget (1920x1080 solid frame + one-tile-row steady-state worst case both under the 2 MiB / frame budget = 30 MiB/s / 15 fps). - [x] No regression in `meshagent-kvm` tile-diff tests. ## Test plan - [x] `cargo test --workspace --lib` (32 new vnc_native tests pass; full suite green). - [x] `cargo clippy --workspace --all-targets -- --deny warnings` (clean). - [x] `cargo fmt --all --check` (clean). - [x] `just pre-commit` (full docker check: fmt + clippy + build + tests, all green). - [ ] Manual: drive an actual TigerVNC client against the agent over a rendezvous tunnel. Deferred until the server side is taught to issue `USAGE_VNC_NATIVE = 8`.
feat(vnc-native): in-process RFB 003.008 server (VA-21)
All checks were successful
Check / clippy + fmt + tests (pull_request) Successful in 1m36s
ea1ab278d4
Add `USAGE_VNC_NATIVE = 8` tunnel mode. The agent speaks RFB 003.008 in-process, reusing `meshagent_kvm` for capture (X11 + Wayland-via-grim) and input (XTEST keyboard / mouse, uinput touch). No external helper binary: contrasted with VA-19's `x11vnc` / `wayvnc` bridge, this PR delivers a single-static-binary VNC story.

## Module layout (`crates/meshagent/src/host/vnc_native/`)

`proto.rs` covers the wire types (PixelFormat, message codes, encoding numbers). `handshake.rs` drives ProtocolVersion + Security + ClientInit / ServerInit. `tls.rs` implements the VeNCrypt sub-handshake with TLS upgrade (rustls + `rcgen`-generated self-signed cert per session; X509None sub-type). `encoding/` carries Raw, Hextile, and ZRLE encoders; ZRLE uses a persistent `flate2::Compress` session with explicit Sync-flush per rectangle so the deflate history accumulates across the whole stream. `input_map.rs` maps RFB keysyms onto `meshagent_kvm::key_event` and pointer-button masks onto `mouse_button` / `mouse_move`. `ws_io.rs` adapts the rendezvous `WebSocketStream` into an `AsyncRead + AsyncWrite` byte pipe via a `tokio::io::duplex` plus a background pump task. `server.rs` is the top-level state machine and the per-frame diff loop, parameterised over a `CaptureSource` trait so tests can drive the server with a synthetic frame source.

## `meshagent-kvm` additions

Promoted `RawFrame` + `PixelLayout` to the public surface and added `capture_raw_frame()` so the RFB server can grab uncompressed pixels without going through the JPEG tile path. The existing `capture_changed_tiles` flow is unchanged.

## Tests

32 unit + integration tests covering: PixelFormat round-trip; protocol version negotiation including 003.003 rejection; security-type advertisement; ServerInit byte layout; Raw / Hextile / ZRLE encoder correctness; ZRLE persistent-deflate Sync-flush; ZRLE bandwidth budget for both the solid (no-motion) and one-tile-row (steady-state worst-case) cases; VeNCrypt sub-handshake round-trip and unsupported-subtype rejection; rustls acceptor construction; key + pointer bit-mask translation; an end-to-end handshake-plus-FramebufferUpdate test driving the server through a `tokio::io::duplex` pair; and a TLS-path end-to-end test that completes a real rustls handshake against the self-signed cert and reads SecurityResult + ServerInit through the encrypted stream.

#VA-21 State Done

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Merge remote-tracking branch 'origin/main' into feat/vnc-native-va-21
All checks were successful
Check / clippy + fmt + tests (pull_request) Successful in 1m22s
fd5df55c1c
# Conflicts:
#	README.md
#	crates/meshagent/src/host/tunnel.rs
David merged commit bacea994ad into main 2026-05-20 11:09:58 +02:00
David deleted branch feat/vnc-native-va-21 2026-05-20 11:09:58 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/vervain-agent!21
No description provided.