Files
remotedesk/docs/protocol.md
T

36 KiB
Raw 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 产生的消息。

Agent Presence 注册、续期和注销使用域隔离的 AgentPresenceProofV1。Agent 首次启动自动生成并保护 Ed25519 设备身份,不要求用户提供密钥文件;Device ID 从设备公钥稳定派生。签名覆盖 Device ID、公钥、region、gateway、connection ID、register/unregister 动作、注册租约到期时间、一次性 nonce 和签发时间。Edge 在修改路由前验证身份绑定、最长 30 秒时钟偏差和 nonce 未使用,因此共享 Presence token 只能作为 API 访问凭据,不能单独创建、覆盖或删除设备路由。Agent signal poll/ack 继续使用独立的设备签名证明和一次性 nonce。

资源上限为 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 剪贴板尚未接入。

协议 minor 16 在 Ed25519 challenge 签名成功后增加强制 TOTP 步骤。Agent 发送 TotpRequired { digits: 6, period_seconds: 30 },Client 必须在 30 秒内返回 VerifyTotp,随后 Agent 才能消费一次性配对码或返回 Authenticated。Agent 不保存客户端提交的动态码,对失败来源执行限流,并按已验证 Client 公钥拒绝同一时间步重放。未配置 TOTP 的 agentd 拒绝启动;Edge relay 仅透明转发这段固定证书的 TLS/WSS 流量,不参与 TOTP 校验。

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 Android transport

After mandatory TOTP authentication, Windows Agent protocol minor 3 advertises supported_media_transports: ["webrtc", "plain_stream"]. Android requests WebRTC first:

{"kind":"open_desktop","capture_mode":"compatibility","media_transport":"webrtc","frames_per_second":30}

The Agent starts bounded Windows H.264 capture and returns webrtc_media_ready with the desktop dimensions, ICE server list, and negotiation timeout. Android is the sole offerer; the Agent is the answerer. Both sides exchange one JSON object per TCP line:

{"kind":"webrtc_signal","signal_kind":"offer|answer|ice_candidate|ice_end","payload":"..."}

SDP is capped at 128 KiB and ICE candidates at 4 KiB by the shared WebRTC boundary. Both endpoints must signal candidate completion. The Agent returns desktop_opened only after ICE/DTLS connects and both remotedesk-control and remotedesk-pointer DataChannels arrive. H.264 uses WebRTC RTP, RTCP feedback, NACK/RTX and DTLS-SRTP. Reliable ordered keyboard, button, text, keyframe, and close commands use remotedesk-control; pointer moves use an unordered, short-lived channel. Android's native WebRTC stack owns RTP, jitter handling, H.264 decode, and Flutter Texture rendering.

The Windows Agent optionally reads at most eight validated STUN/TURN entries from REMOTEDESK_WINDOWS_ICE_SERVERS. It passes the same session credentials to Android only after TOTP succeeds. With no configured entries, both peers use host candidates for LAN/VPN routing.

If WebRTC negotiation or Windows H.264 capture is unavailable, the Agent returns webrtc_media_unavailable and Android requests media_transport: "plain_stream" on the same authenticated TCP connection. 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 this fallback at 30 FPS, a 16384-pixel dimension, and 256 MiB for raw/compressed frames; Android applies the tighter 33,177,600-pixel and 64 MiB compressed-frame limits before inflating in a worker isolate. WebRTC media and DataChannels are encrypted, but the direct TCP signaling channel has no TLS or host identity. TOTP alone does not prevent a signaling MITM, so this mode remains limited to a trusted LAN, private VPN, or authenticated AnyTLS tunnel.