Files
曾志威 19a8e03a83
ci / rust (push) Canceled after 0s
ci / web (push) Canceled after 0s
ci / package-preview (push) Canceled after 0s
ci / package-installer (push) Canceled after 0s
ci / linux-agent (push) Canceled after 0s
ci / edge-service (push) Canceled after 0s
ci / coturn-pop (push) Canceled after 0s
ci / package-windows-host (push) Canceled after 0s
Document all-Rust migration and extend native media stack
2026-08-14 14:31:57 +08:00

33 KiB
Raw Permalink Blame History

RemoteDesk 协议设计

1. 目标

协议服务于 Windows 客户端与自研 Linux Agent,不用于 Windows RDP。设计目标:

  • 设备先配对,再创建桌面或终端会话。
  • 信令、输入、媒体、剪贴板和文件相互隔离。
  • 支持断线恢复、能力协商和动态网络质量。
  • 消息可演进,旧版本能明确拒绝不兼容能力。
  • 所有入口有大小、速率、状态和权限校验。

2. 分层

Application
├── Pairing / Device authorization
├── Session lifecycle
├── Input / Clipboard / File / Terminal
└── Quality control

Transport
├── TLS WebSocket              authentication/signaling
├── WebRTC RTP/RTCP            video/audio/feedback
└── WebRTC DataChannel         control/data

本地模式由 Agent 监听 TLS WebSocket;边缘模式由 Agent 主动连接 Rendezvous Gateway。WebRTC 媒体和 DataChannel 始终在会话进程与客户端之间建立,agentd/Gateway 只转发 SDP/ICE 信令。

3. 协议版本

每个 Envelope 包含:

message Envelope {
  uint32 protocol_major = 1;
  uint32 protocol_minor = 2;
  bytes message_id = 3;       // 16-byte UUID
  bytes session_id = 4;       // empty before session creation
  uint64 sequence = 5;
  uint64 sender_mono_us = 6;  // only meaningful within one sender
  MessageType type = 7;
  bytes payload = 8;
}

规则:

  • 主版本不一致直接返回 INCOMPATIBLE_PROTOCOL
  • 次版本通过 capability bitset 协商。
  • 未识别的可选字段忽略。
  • 未满足的 required capability 导致请求失败,不静默降级。
  • monotonic timestamp 不跨机器比较;RTT 使用带 nonce 的 Ping/Pong 测量。
  • message/session ID 使用随机 UUID,不能由递增数据库 ID 替代。

4. WebSocket 帧

  • 一个 binary WebSocket message 对应一个 Protobuf Envelope。
  • 默认最大信令消息 1 MiB;握手、输入和控制使用更小的类型级限制。
  • 压缩默认关闭,避免对包含认证数据的消息产生压缩侧信道。
  • 未认证连接只能发送 ClientHello、配对相关消息和有限 Ping。
  • 认证前执行连接数、消息速率和失败次数限制。

5. 设备配对

5.1 设备身份

  • Agent 安装时生成长期 Ed25519 设备密钥。
  • Windows 客户端首次启动生成长期客户端设备密钥。
  • 私钥从不通过协议传输。
  • 已配对记录绑定客户端公钥、名称、权限和撤销状态。

5.2 首次配对流程

Windows Client                         Linux agentd/local UI
      |                                         |
      |---- TLS ClientHello ------------------->|
      |<--- AgentHello + cert fingerprint ------|
      |---- PairStart(pairing code) ------------>|
      |<--- PairChallenge + nonce ---------------|
      |---- client key + signed transcript ------>|
      |<--- local confirmation pending -----------|
      |       user compares short fingerprint     |
      |<--- PairAccepted + permissions -----------|

配对码必须限时、限次且只能由 Linux 本地用户开启。双方显示由完整握手 transcript 派生的短指纹,由用户在 Linux 本地确认客户端名称和指纹。最终实现应使用经过审查的 PAKE/安全配对库;不得自行组合加密原语。

5.3 后续连接

  • 使用已保存设备密钥进行双向认证。
  • TLS certificate/fingerprint 变化时中止自动连接。
  • 每次连接使用新 nonce,签名覆盖双方 nonce、协议版本和 capability digest。
  • 被撤销客户端在认证阶段拒绝,不能进入会话创建。

6. 权限模型

配对设备按能力授权:

DESKTOP_VIEW
DESKTOP_INPUT
AUDIO_RECEIVE
CLIPBOARD_READ
CLIPBOARD_WRITE
FILE_SEND_TO_HOST
FILE_RECEIVE_FROM_HOST
TERMINAL_OPEN
TERMINAL_RESUME
UNATTENDED_REQUEST

桌面查看不隐含输入权限,终端权限不隐含文件权限。权限变化立即增加 authorization_epoch;旧 epoch 的会话票据失效。

7. 能力协商

客户端和 Agent 交换:

  • Session typesWayland、Xorg、Terminal。
  • Video codecs/profiles/levels。
  • Hardware encode/decode results,不只报告插件名称。
  • Raw-frame memory pathsDMA-BUF/D3D11 surface、GPU copy、CPU upload、software,以及是否经过短帧验证。
  • Agent GPU/DRM identity、DMA-BUF modifier/import 与硬件编码器的同设备匹配结果。
  • Audio codec 和 FEC/DTX。
  • Display count、尺寸、scale 和 capture type。
  • Input typesabsolute、relative、keyboard、touch。
  • Clipboard MIME types 和最大大小。
  • RTX/NACK/FEC/TWCC 支持。
  • Portal permission level 和恢复能力。

能力摘要使用稳定排序后计算 digest,创建会话时将 digest 写入票据,避免协商过程被替换。

8. 会话创建

8.1 请求

CreateSessionRequest 至少包含:

request_id
session_type
target_user
requested_permissions
display_selection
quality_profile
zero_copy_policy: required_end_to_end | compatibility
client_capabilities_digest

required_end_to_end 是 Linux 图形会话默认值。Agent 在捕获/缩放/编码端、native-video helper 在解码/呈现端分别发送带 generation 的 EndpointMemoryPathSessionController 只有在两端均为经过验证的 zero_copy 后才进入 Connected。任一端报告 gpu_copycpu_uploadsoftwareopaque_gpu_path 都使协商失败。compatibility 必须由用户显式选择,不能由远端或自动重连自行降低策略。

EndpointMemoryPath:
endpoint: agent_encode | client_render
generation
status: zero_copy | gpu_copy | cpu_upload | software | opaque_gpu_path
adapter_identity
capture_device_identity
surface_type: dmabuf | d3d11 | cpu | opaque
cpu_map_count
cross_adapter_copy_count
verification_source

切换显示器、GPU、编码器、decoder 或分辨率会递增 generation,并使旧验证立即失效。两端必须重新报告;计数非零或验证信息缺失时不能进入严格 Connected 状态。

agentd 完成设备权限、目标用户、会话存在性和并发限制校验后,签发短期单次使用的 Session Ticket。

8.2 Ticket

Ticket 绑定:

  • client public key hash。
  • session ID/type/target UID。
  • permission bitset 和 authorization epoch。
  • issue/expiry time。
  • 一次性 nonce。
  • capability digest。

Session 进程只接受 agentd 签名且未过期的 Ticket。Ticket 使用一次后进入短期 replay cache。

8.3 生命周期

CREATED -> AUTHORIZING -> NEGOTIATING -> CONNECTED
                                      -> DEGRADED
                                      -> RECONNECTING
                                      -> CLOSING -> CLOSED
                       failures ----------------> FAILED

所有状态变化包含 reason code、可重试标记和用户安全展示文本 ID。不得仅发送任意字符串错误。

8.4 多显示器布局

显示拓扑是独立、带版本的安全边界:

DisplayLayout:
  generation
  displays[]:
    stable_id: 16 bytes
    rect: x, y, width, height
    scale: numerator / denominator
    is_primary

DisplaySelection:
  layout_generation
  mode: single | all | custom
  display_ids[]

坐标使用物理桌面像素,主屏原点固定为 (0, 0);位于主屏左侧或上方的显示器使用负坐标。布局必须恰好有一个主屏,并拒绝空布局、重复 ID、零尺寸、无效缩放、坐标端点溢出和累计像素数溢出。自定义选择拒绝空集合、重复/未知 ID 以及 generation 不匹配。

显示器热插拔、位置、旋转、DPI、分辨率或主屏变化都会发布新 generation。输入、捕获选择、分辨率请求和零拷贝报告必须绑定同一 generation;收到旧 generation 时丢弃请求并重新同步,不能按数组序号猜测显示器身份。

Windows RDP helper 把验证后的本地布局映射为 Display Control Dynamic Monitor Layout。Linux Agent 把同一选择模型映射到 Portal/PipeWire 或 XRandR 捕获源,但不会修改 Linux 实体显示模式。

8.5 Windows Headless Compatibility Contract

Windows Headless compatibility 模式允许无硬件 GPU 主机使用可用的 Desktop Duplication 或 IDD 输出、WARP 或 CPU 编码,但必须显式报告 software 和 degraded。能力结果至少包含 capture_backend、adapter_type、surface_type、encoder_backend、hardware_encoder_verified、hardware_pipeline_verified、hardware_path_verified、cpu_map_count、max_width、max_height、max_fps 和 concurrent_sessions。hardware_encoder_verified 只代表真实短样本编码成功;DDA staging/readback 仍有 CPU map,因此该路径的 hardware_pipeline_verifiedhardware_path_verified 必须为 false。若请求硬性要求 4K/120 或硬件编码,而本地只有 software 路径,Agent 返回 HARDWARE_ENCODER_UNAVAILABLE,不得静默降级。

Windows Headless 媒体契约固定为 SDR 8-bitvideo codec 为 H.264/AVC、H.265/HEVC 或 AV1,输入格式为 NV12audio codec 为 Opus。IDD 不提供本项目的 HDR10/10-bit 能力,不得出现 P010、Main10 或 HDR metadata。

Windows Headless 视频使用 Hysteria2 不可靠 datagram,允许丢失。缺少一个分片时丢弃整个帧;过期帧不重传;首帧非 IDR、sequence 中断、discontinuity、解码失败或关键帧损坏时,经独立可靠控制流发送带 session ID 的 request_keyframe。Client 以 250 ms 最小间隔合并请求,Agent 以容量 1 的队列合并请求并通过 CODECAPI_AVEncVideoForceKeyFrame 请求编码器输出 IDR。收到新 codec config 和 IDR 后才能恢复普通帧。

本地 encoded-ring descriptor 只能经 \\.\pipe\RemoteDesk\... 命名空间交付。Pipe DACL 只授权当前用户 SID,拒绝远程客户端并启用首实例保护;双方从进程环境取得同一个随机 32 字节 base64url bootstrapAgent 为每个连接发送 32 字节随机 nonceGo bridge 返回 HMAC-SHA256(key, "RemoteDesk Windows Agent pipe auth v1\\0" || nonce)。bootstrap 不进入命令行或 JSON,双方读取后从各自环境删除;认证和 hello 共用有界握手时限。仅 authenticated: true 的连接可作为生产 descriptor 来源,ring 路径、session ID、generation、owner PID 和 geometry 必须全部校验后才能映射。

RDA1 音频 datagram 使用固定 40 字节 little-endian headermagic/version/reserved/header_bytes、stream ID、generation、sequence、PTS、duration_ms 和 flags,后接一个不超过 4 KiB 的独立 Opus packet。reserved 必须为 0,当前仅定义 discontinuity flagduration 仅允许 10/20/40/60 ms;未知 flags、零 identity、空 payload 和超限 packet 必须在进入 jitter buffer 前拒绝。音频 packet 不依赖 RDV1 frame ID,不因视频分片缺失而等待或丢弃。

输入控制使用独立可靠高优先级流,带 input_seq 和 layout_generation。输入发送不等待视频帧、编码完成、媒体 ACK 或音频播放。文件和剪贴板使用独立可靠低优先级流,不能阻塞输入、音频或视频。

音视频共享 session clock/QPC 映射和 generation,但独立采集、编码、队列和发送。客户端以音频播放时钟同步视频,视频落后时丢弃旧帧,不为追求完整帧率而增加播放延迟。

Hysteria2 音频包使用独立的 RDA1 datagram,不进入视频分片重组器。固定 40 字节头包含 stream、generation、sequence、100ns media PTS、duration10/20/40/60 ms)和 flagsOpus payload 上限为 4 KiB。音频包独立丢弃或播放,视频缺片不能阻塞音频,音频丢包也不能阻塞视频。Windows compatibility runtime 已使用独立 worker 完成 WASAPI loopback、48 kHz 双声道 20 ms Opus、独立 mmap ring、Go RDA1 发送,以及 viewer 有界 jitter/FEC/PLC/WASAPI playback 的源码接线;统一 session-start QPC、长时间漂移校正和真实设备验收仍是发布前条件。

9. WebRTC 信令

  • Offer/Answer 和 ICE candidate 放入 Protobuf 信令消息。
  • SDP 大小和 candidate 数量有限制。
  • DTLS fingerprint 必须与 Session Ticket/握手身份绑定。
  • ICE 优先直连,并接受 Allocator 签发的短期 TURN credential。
  • Allocator 返回 DIRECT/SINGLE_EDGE/DUAL_EDGE 候选路径、Client/Agent POP 和路径 generation。
  • 智能优选可以让两个端点使用不同 relay candidate,经供应商骨干连接两个 POP。
  • TURN credential 绑定 POP、session/allocation、有效期和配额,不能替代 Session Ticket。
  • 边缘 Gateway 只路由已签名请求,Agent 仍是最终授权方。
  • 网络接口变化可以触发 ICE restart;失败后重建 PeerConnection。
  • 路径变化使用递增 generation,旧 POP 探测和 credential 结果不得覆盖新选择。

当前 Edge HTTP 信令边界使用域隔离的 EdgeNegotiationEnvelopeV1,只允许已被 Agent 接受且仍有效的 Session Intent 建立 mailbox。send/poll 都由对应 Client 或 Agent Ed25519 key 签名;签名覆盖 request ID、session ID、端点角色、操作、sequence、generation、消息类型、payload、nonce、签发和过期时间。send sequence 从 1 开始并按端点严格递增;共享 generation 从 1 开始,只有 restart 可精确加一;poll 的 sequence 是已消费的 peer cursor。服务端拒绝过期、重放、跨会话、跨角色、乱序、错误 generation 和超限消息,只返回 peer 产生的消息。

资源上限为 SDP 128 KiB、单 candidate 4 KiB、每端点每 generation 128 candidates、每会话 512 消息、单次 poll 64 条且 payload 最多 192 KiB、最多 1024 活跃 mailbox 和总 payload 8 MiB。该 mailbox 只完成控制信令转发;DTLS fingerprint、TURN credential 和最终媒体连接仍必须由端点 WebRTC 层验证。

9.1 Edge Path Messages

边缘路径协商增加:

PathProbeRequest(candidate_pops, token, expires_at)
PathProbeReport(endpoint, pop, udp/tcp/tls metrics, token)
BackboneMetric(pop_a, pop_b, rtt/loss/jitter, provider_signature)
PathSelection(mode, client_pop, agent_pop, transport, generation)
RelayCredential(pop, endpoint, allocation_id, expires_at, quota)
PathSwitchRequest(old_generation, candidates, reason)

端点探测只代表最后一公里;POP 间骨干指标必须来自受信控制面或供应商测量,不能由客户端任意声明。PathSelection 不授予会话权限,必须与有效 Session Ticket 同时使用。

10. DataChannel

Channel ordered retransmit 优先级
control 无限/连接级超时 最高
key_button 有限时间可靠 最高
pointer_motion 0 或极低
terminal 可靠
clipboard 可靠
file 可靠 最低

键盘按键与鼠标按钮不能与高频移动共用不可靠 channel。SCTP buffered amount 达到高水位时,文件和剪贴板生产者暂停。

11. 输入协议

键盘事件:

event_id
hid_usage
pressed
modifiers
layout_id
unicode_text optional

鼠标事件:

event_id
mode absolute|relative
display_id
topology_version
x/y or dx/dy
buttons
wheel_x/wheel_y

绝对坐标使用固定整数范围归一化,避免浮点差异。显示器拓扑版本不匹配时丢弃位置事件并请求最新拓扑。重连、失焦和断开时交换完整按键状态,释放可能卡住的键。

12. 质量控制协议

分辨率控制使用可靠、有序的 Control DataChannel。客户端先读取 DisplayCapabilities

display_ids/topology_version
source_width/source_height
min_width/min_height
max_width/max_height
max_pixels
width_alignment/height_alignment
dynamic_reconfigure
supported_modes: follow_window | source_native | fixed

客户端发送 ResolutionRequestAgent 返回 ResolutionApplied 或结构化错误:

request_id
generation
mode
target_width/target_height optional
adaptive_downscale
topology_version

ResolutionApplied:
request_id/generation
source_width/source_height
encoded_width/encoded_height
reason: requested | network_downscale | capability_clamp | source_changed

宽高在进入媒体管线前校验并按编码器约束对齐,不能由 Agent 静默放大到超出用户目标。旧 generation 的请求和确认均忽略。Linux 的请求只改变编码尺寸,不代表修改 Wayland/Xorg 的实体显示模式。RDP 使用相同的客户端状态模型,但请求由 RDP helper 映射到 Display Control,而不是发给 Linux Agent。

Agent 周期性发送 QualityReport

rtt_ms
loss_fraction
jitter_ms
gcc_target_bps
nack_rate
pli_count
encode_time_ms
send_queue_ms
current_ladder
source_width/source_height
encoded_width/encoded_height
resolution_generation
capture_memory_path
encode_memory_path
client_render_memory_path
zero_copy_verified

客户端返回 decode/render time、dropped frames 和 DataChannel buffer。质量档位切换消息带 generation,旧 generation 的异步结果忽略。媒体自适应细节见 network-adaptation.md

13. 剪贴板

  • Offer/Request/Data 三阶段,不收到 Request 不发送内容。
  • 文本第一版只支持 UTF-8 plain text。
  • MIME type allowlist 和单项大小限制。
  • 内容带 content hash,重复内容不循环同步。
  • 本地/远端 sequence 防止双方互相回写。
  • 权限撤销、锁屏或会话结束立即清理缓冲。

协议 minor 15 将文本剪贴板接入 X11 桌面会话。begin_direct_web_rtcopen_desktop 新增默认关闭的 clipboard_readclipboard_write,分别表示 Client 可读取被控端剪贴板、可写入被控端剪贴板;agentd 必须再次对照配对授权检查方向,并只透明转发结构化消息,root 进程不得读取内容。发送方先用单调非零 sequence、UTF-8 字节数和小写 SHA-256 发送 DesktopClipboardOffer,接收方验证 32 KiB 上限后以同 sequence 请求,发送方才返回无 padding 的 canonical Base64 DesktopClipboardData。接收方将 Data 与当前待处理 Offer 的 sequence、长度和摘要绑定,拒绝无效 UTF-8、NUL、非规范 Base64、CR/CRLF 未规范化或摘要不匹配的内容。双方记录最近应用/发出的摘要,避免系统剪贴板所有权变化造成回写循环;只保留最新待请求内容。minor 14 及更早客户端缺少协商字段时两方向均为 false。当前源码实现 X11 与 Windows CF_UNICODETEXTWayland Portal 剪贴板尚未接入。

14. 文件传输

FileOffer -> Accept(path policy) -> Chunk* -> Complete(hash)
          -> Reject/Cancel
  • 文件名是名称,不允许携带绝对路径或 ..
  • Agent 生成目标临时路径,完成哈希校验后原子改名。
  • Chunk 使用固定最大大小和 offset,支持已确认范围恢复。
  • 总大小、并发数、速率和磁盘剩余空间均有限制。
  • 软链接、特殊文件和设备节点默认拒绝。
  • Critical 网络状态暂停数据块但保留控制消息。

当前文件协议 minor 4 的上传请求包含由 Client 对 target user、相对路径、声明大小和整文件 SHA-256 派生的稳定 transfer ID。Agent 不信任 Client 提供 offset;降权 file helper 只打开目标目录中该 ID 对应的普通 partial 文件,读取实际长度并哈希已有前缀,再通过 FileReady.offset 返回。中断时保留 partial,续传只接收剩余字节;达到声明大小但整文件 SHA-256 不符时删除 partial,成功时才原子替换目标。

协议 minor 5 增加 X11 桌面兼容传输。OpenDesktop 绑定授权普通用户、最大宽高和 1..30 FPS;会话中的 DesktopResize 可把最大宽高重配为 200..8192。Client 合并连续窗口变化后发送最终尺寸,Agent 在当前帧收到 DesktopFrameAck 后才按新上限捕获下一帧,不修改 Xorg 的实体显示模式。每帧先发送 DesktopFrameStart,再发送严格递增的 45 KiB DesktopFrameChunk,最后用 DesktopFrameComplete 携带未压缩 BGRA 的 SHA-256。Client 只有在分块数量、压缩长度、解压后精确字节数和 SHA-256 全部匹配且窗口成功呈现后才发送 DesktopFrameAck;Agent 在收到该 ACK 前不捕获下一帧。DesktopPing/DesktopPong 在当前 WSS 或 Edge relay 会话内测量实际往返时间,不依赖两端时钟。单帧最多 33,177,600 像素、128 MiB 未压缩数据和 64 MiB 压缩数据,解压读取使用精确上限防止压缩炸弹。

桌面输入使用 DesktopInput,包含绝对指针、五键鼠标、水平/垂直滚轮、X11 keysym 按下/释放和 release_all。Agent 根据当前用户 X server 的 keyboard mapping 将 keysym 转为 keycode,客户端不能直接注入平台相关 X keycode。桌面断开、窗口失焦和退出都会释放 Agent 跟踪的按键状态。

协议 minor 6 为 X11 兼容链路增加短时会话续接。首次 OpenDesktop 不携带令牌,用户会话进程生成 32 字节随机 URL-safe Base64 令牌,并在 DesktopOpened 返回;agentd 从已完成 Ed25519 认证的 ClientGrant 注入客户端指纹,远程请求不能自行指定该绑定。WSS 或 Edge relay 异常断开时立即释放 XTEST 输入状态,丢弃未完成帧和 ACK,但保留 X11 连接、递增帧序列和自适应 pacer 15 秒。Client 按 1、2、4 秒有界重连,重新执行证书固定和 Client 身份认证,Edge 路径重新申请一次性 relay 票据,再以同一用户和令牌调用 OpenDesktop。只有令牌、客户端指纹、目标用户和有效期全部匹配才恢复;成功后令牌立即旋转,旧令牌不可重放。Windows Client 还把最新令牌保存到当前用户 Credential Manager,并按 Agent TLS 证书指纹和 Linux 用户隔离;helper 进程异常退出后,在 15 秒租约内以相同身份重新启动可读取令牌继续会话。正常关闭、服务端关闭或恢复拒绝会删除该记录,令牌不进入 argv、浏览器状态或普通文件。恢复从新的完整帧边界开始,不重放断线期间的输入、帧分块或 ACK。命令解析、状态校验、采集或输入注入失败也统一释放 XTEST 输入,但不建立恢复租约;正常 Close 同样不创建租约。

协议 minor 7 在 DesktopFrameMetadata 增加实际 compression_level。X11 兼容链路从 zlib level 1 启动:连续两个慢呈现 ACK 且最近编码耗时不超过当前帧预算三分之一时,逐级提高压缩至最多 6;连续两帧编码耗时超过帧预算一半时逐级降低;连续 60 个在帧预算内完成的 ACK 后逐级回到 level 1。等级和帧率控制器都使用真实编码计时与呈现 ACK,状态随 15 秒恢复租约保留。每帧诊断同时报告等级、压缩后/未压缩字节比例和编码耗时,客户端仍按元数据长度上限、精确解压长度与 SHA-256 完整性验证后才呈现。

协议 minor 8 为桌面打开命令增加签名 Intent 的 edge_session_id 和 Agentd 到用户会话的显式 webrtc_h264 协商位,并增加 DesktopH264Start/Chunk/Complete/Unavailable 内部事件。Agentd 只有在 session ID、过期时间、目标用户和已认证 Client 指纹全部匹配时才单次消费 WebRTC sender,直连请求不能取走 Edge sender。每个 Annex-B access unit 独立声明尺寸、时长、关键帧、编码耗时、编码器和分块数;Agentd 按 16 MiB AU 上限、45 KiB 分块、严格顺序、canonical base64、精确长度及 SHA-256 验证后才发送。

协议 minor 9 增加显式 DesktopVideoModeDesktopVideoModeChangedDesktopH264FrameAck。Client 只有在实际呈现第一张原生 H.264 帧后才请求 webrtc_h264 模式;用户会话会等待当前 zlib 帧的呈现 ACK,在完整帧边界确认切换。确认后每次捕获只发送 H.264,不再编码或传输完整 zlib 帧,并等待对应 sequence 的 H.264 呈现 ACK 后才捕获下一帧。Agentd 只向 Client 转发 H.264 Start 元数据用于 sequence 绑定、采集和编码遥测,Chunk/Complete 仍仅在 root/user IPC 内使用。RTP 序号中断会立即产生恢复事件并切回 zlib,同时继续监听关键帧以便重新进入原生模式;客户端解码失败、媒体邮箱积压、轨道结束、sender/encoder 失败或三秒内未收到 H.264 ACK 时也显式回退。服务端确认回退后从下一张完整帧恢复兼容传输。zlib ACK 有五秒绝对上限,错误类型或旧 sequence 的 ACK 不推进采集节奏。

协议 minor 10 为 X11 桌面输入增加 pointer_delta。Windows helper 只有在用户启用相对鼠标并成功抓取当前窗口光标后才读取 Raw Input;同一事件循环批次的位移会合并,保留小数余量,并将每轴单消息限制为 -4096..4096。零位移、越界值和未知字段由 Agent 拒绝。X11 用户会话使用 XTEST root=None 的相对移动语义,不把相对量误当成缩放后的绝对坐标。Ctrl+Alt+Home 在本地切换捕获;失焦、重连、全屏切换和退出都会释放远端按键/按钮,失焦、重连和退出还会解除光标抓取并丢弃未发送位移。

协议 minor 11 为 DesktopFrameEncoding 增加 webrtc_h264,用于没有 zlib 原始帧回退的 Wayland 严格媒体会话。Agent 只有在认证后的 Portal 返回单个有效 PipeWire stream、DMABUF 到 VA surface/H.264 管线产生首个 IDR、EIS Sender 完成 seat/device/keymap 协商且 Agentd 已绑定对应 WebRTC sender 后才发送 DesktopOpened。每个 H.264 sequence 由实际呈现后的 DesktopH264FrameAck 推进;DesktopResize 在上一帧 ACK 后先停止旧管线,再重建管线并等待新 IDR。绝对输入必须匹配 Portal stream 与 EIS region 的 mapping_id;相对指针、五键、滚轮和键盘通过同一 EIS emulation 生命周期提交。严格管线不允许退回 CPU BGRA,协商或客户端硬解失败会结束该 Wayland 会话。

协议 minor 12 为 webrtc_h264 会话启用与 X11 相同的轮换恢复令牌语义。意外断线立即释放全部 EIS 输入并销毁旧编码器,但用户会话进程最多保留 Portal、PipeWire remote 和 EIS 连接 15 秒;租约同时绑定 Agentd 注入的已认证 Client 指纹和目标 Linux 用户。Client 仅对 minor 12 及以上持久化该令牌。恢复连接必须先建立并授权新的 WebRTC sender,提交相同绑定的令牌后 Agent 才消费租约、重建严格编码器、等待新 IDR 并以连续递增的 sequence 返回 DesktopOpened(resumed=true)。令牌在每次恢复时轮换;错误令牌、不同 Client/用户、显式关闭、ACK 超时、输入/编码错误或租约到期都会拒绝恢复并显式关闭 Portal。

协议 minor 13 增加认证 WSS 内的直连 WebRTC 信令。Client 先发送绑定目标用户、尺寸和帧率的 begin_direct_web_rtc,Agent 完成现有桌面权限校验后返回 ready;后续 direct_web_rtc_signal 只接受严格有界的 offer、answer、ICE candidate 和 ice_end。双方只有在 PeerConnection connected、本地 ice_end 已发送且远端 ice_end 已消费后才离开信令阶段,从而保证后续 OpenDesktop 不会与残留 ICE 消息混用。OpenDesktop 的用户、尺寸和帧率必须与授权阶段完全一致,Client 指纹仍由 Agent 注入。直连仅使用 host candidates,不依赖 Edge/TURN;失败时 Client 发送 abort_direct_web_rtcAgent 返回一次 direct_web_rtc_unavailable 后才允许 OpenDesktop(webrtc_h264=false) 进入 X11 zlib。Wayland 不接受该软件回退。成功时 Agent 直接把本次 PeerConnection 的 H.264 sender 交给用户会话代理,并在桌面结束或任一代理错误后显式关闭 ICE/DTLS/SRTP。

协议 minor 14 为 begin_direct_web_rtcopen_desktop 增加默认关闭的 opus_audio。Client 只有在对端 minor 不低于 14 且本次 WebRTC 媒体会话存在时才发送 trueminor 13 及更早 JSON 缺少该字段时反序列化为 false。直连授权把音频选择与 Client 指纹、用户、尺寸和帧率一同绑定,失败回退时视频和音频都必须关闭;Edge 路径也只在请求显式开启且取到绑定当前 session/user/Client 的媒体 sender 后向用户会话注入 true。未协商音频时用户会话不启动系统输出捕获,agentd 不向 Client 转发任何 Opus packet/unavailable 事件。协商后 Linux 用户会话仅捕获 Pulse/PipeWire 的默认输出 monitor(或经严格名称校验的管理员覆盖),编码为 48 kHz 双声道、20 ms、96 kbps Opus;每包限制 4 KiB,序号、时长、编码耗时和 canonical Base64 在 root/user IPC 边界重新校验。音频 sender 或 Windows 本地解码/播放失败只关闭音频,不中断 H.264 或 zlib 视频。

X11 用户会话把请求的 1..30 FPS 视为上限,并使用“帧写入 IPC 完成到客户端成功呈现 ACK”的真实反馈调整捕获节奏。连续两帧 ACK 超过当前帧预算两倍或 250 ms 时将目标速率降为当前值的约 75%,最低 1 FPS;连续 30 帧在预算内时每次恢复 1 FPS,最高不超过请求值。档位变化从新的完整周期开始,输入、Ping/Pong 和关闭消息不受帧计时器限制;丢失、错误序列或未呈现的帧不会被当作快速样本。

客户端主动退出时先发送 release_allClose。用户会话进程完成 XTEST 按键/按钮释放后返回 DesktopClosed { reason: "client_closed" }system Agent 最多等待 2 秒并转发该终止事件;控制端在收到关闭握手或超时后关闭 WebSocket。窗口仅保留 Ctrl+Alt+F 全屏切换,其他键盘事件直接进入远端输入链路。

下载请求携带本地隐藏 partial 的实际长度。Agent 按远端文件真实大小拒绝越界 offset,返回同一 offset、完整大小和 SHA-256,并只发送剩余区间。Client 在追加前重新读取并哈希已有前缀,完成后校验前缀与新增数据组成的整文件哈希;不匹配时删除 partial,匹配后才原子改名。partial 路径不允许替代已存在的最终目标。

15. 终端协议

终端消息包括:

  • PTY output bytes。
  • Client input bytes。
  • Resize(cols, rows, pixel width/height optional)。
  • Signal(INT/TERM/HUP allowlist)。
  • Exit(code/signal/core flag)。
  • Resume(last received sequence)。

协议传输字节而非命令字符串,Agent 不拼接 shell 命令。输出环形缓冲区有固定上限,超过后返回 OUTPUT_GAP,客户端明确提示丢失历史输出。

16. 重连与幂等

  • Create/Close/Transfer 等请求携带 request IDAgent 维护短期幂等缓存。
  • 会话租约由 heartbeat 延长,断线后进入有限 grace period。
  • 重连请求携带 session ID、resume token 和各 channel 最后确认 sequence。
  • 输入事件默认不跨断线重放;终端/文件按各自序号恢复。
  • 恢复成功后请求视频 IDR 并重新同步显示器、权限和输入状态。

17. 错误模型

错误分为:

AUTH_*          配对、证书、票据、权限
SESSION_*       类型、状态、并发、用户会话
CAPABILITY_*    编码、Portal、显示器、输入
MEDIA_*         捕获、编码、WebRTC、解码
TERMINAL_*      PAM、PTY、Shell、退出
FILE_*          路径、空间、哈希、权限
NETWORK_*       超时、重连、协商
INTERNAL_*      非预期内部错误

错误包含稳定 code、retryable、retry delay 和 correlation ID。内部路径、堆栈和密钥信息不发送给远端。

18. 资源限制

实现前锁定默认上限:

  • 未认证连接数和每 IP 配对尝试。
  • 每设备并发桌面/终端会话。
  • Protobuf message、SDP、candidate 和字符串长度。
  • 分辨率宽高、总像素、像素率和运行时重配频率。
  • 剪贴板、文件、终端输出缓存和日志大小。
  • DataChannel buffered amount。
  • 会话创建、Portal 授权、协商和重连超时。

上限由安全配置决定,远端请求只能降低,不能提高系统硬限制。

19. 测试

  • Protobuf golden vectors 与 round-trip。
  • 主/次版本兼容和 unknown fields。
  • 状态机非法转换的 property tests。
  • 重复 request、过期 Ticket 和 replay。
  • 截断、超长、乱序和未知消息 fuzz。
  • 输入断线后的卡键恢复。
  • 文件路径穿越、磁盘不足和 hash mismatch。
  • DataChannel 拥塞时输入优先级。
  • 协议日志脱敏测试。

Windows Agent software fallback

Windows Agent advertises dxgi-desktop-duplication-strict as its preferred capture backend. A controller may explicitly opt in to the lower-performance fallback by sending one JSON line:

{"kind":"open_desktop","allow_software_fallback":true,"frames_per_second":10}

The fallback is never selected implicitly. RDWF v1 uses a 24-byte little-endian header. RDWF v2 uses a 40-byte header followed immediately by the zlib payload:

Offset Size Value
0 4 ASCII RDWF
4 1 Protocol version (2; readers also accept 1)
5 1 Codec (1 = BGRA8 + zlib)
6 2 Reserved (0)
8 4 Width
12 4 Height
16 4 Uncompressed BGRA byte length
20 4 Compressed payload byte length
24 8 GDI capture latency in microseconds (v2)
32 8 zlib encode latency in microseconds (v2)

BGRA rows are top-down and tightly packed at four bytes per pixel. The Agent caps the fallback at 15 FPS, a 16384-pixel dimension, and 256 MiB for both raw and compressed frames.

The Windows Agent viewer validates the header and lengths before allocating, inflates the zlib payload, converts BGRA8 to the local softbuffer surface, and reports decode/presentation timing in the session diagnostics endpoint. The control service starts it through POST /api/v1/windows-agent/desktop/launch and exposes diagnostics at GET /api/v1/windows-agent/session/{session_id}.