Files
RemoteDesk/docs/technology-stack.md
T
曾志威 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

328 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RemoteDesk 技术栈规划
> 当前决策:所有发布运行时使用 Rust;Agent 是服务端;原生客户端使用
> winit、wgpu 和 egui;实时传输使用 str0m WebRTC。Go、Hysteria2、
> Tauri 和 WebView 不属于目标组件。详见
> [ADR 0001](adr/0001-all-rust-runtime.md)。下文保留的旧技术栈内容仅用于
> 迁移对照,后续随代码迁移删除。
## 1. 混合架构
RemoteDesk Windows 客户端采用 Web UI 与 Rust 原生会话进程混合方案。JavaScript 负责管理界面和终端;Linux 桌面 WebRTC、Windows RDP 和 GPU 呈现均由 Rust 原生 helper 承载,原始视频像素不进入 WebView。
| 模块 | 技术栈 | 职责 |
|---|---|---|
| Client Shell | Tauri 2、Rust、Tokio | 应用生命周期、凭据、配对、窗口和 helper 管理 |
| Management UI | React、TypeScript、Vite | 主机、设置、诊断和会话工具栏 |
| Linux Terminal | xterm.js、DataChannel | ANSI/VT 终端和输入 |
| RDP Protocol Probe | Rust helper、IronRDP | 无凭据检查 DNS、TCP、X.224 与 RDP 安全协议协商 |
| Native RDP Viewer | Rust helper、IronRDP、winit、D3D11/DXGI | Windows RDP 原生窗口;优先使用 D3D11 swap chain CPU 上传,失败时回退 software framebuffer |
| Native Linux Video | Rust helper、gstreamer-rs、D3D11 | 默认 WebRTC、硬解和零拷贝呈现 |
| Linux Agent | Rust、Tokio、GStreamer、systemd | Wayland/Xorg/PTY 被控端 |
| 协议 | Protobuf、TLS WebSocket、WebRTC | 配对、会话和媒体数据 |
| Edge | Rust Rendezvous/Allocator、双 POP TURN、L4 Connector | CDN 优质骨干路径 |
| 数据 | SQLite、Windows Credential Manager | 配置和敏感凭据 |
## 2. 进程模型
```text
remotedesk.exe (Tauri/Rust)
├── WebView2 main window
├── WebView2 terminal window
├── Rust session/auth/storage/edge services
├── remotedesk-rdp-session.exe
│ └── IronRDP protocol + TLS certificate probe (no credentials)
├── remotedesk-rdp-viewer.exe
│ └── IronRDP + winit native window + D3D11 CPU upload fallback
└── remotedesk-native-video.exe (default Linux desktop)
└── GStreamer WebRTC + selected D3D11 adapter
```
原生 helper 是独立进程,原因:
- Tauri/WebView2 与 winit/D3D11 不共享事件循环。
- RDP 或 GPU driver failure 不拖垮主界面。
- helper 可以拥有独立 GPU、DPI、Raw Input 和全屏生命周期。
- 主进程只交换状态和命令,不传输视频帧。
## 3. Tauri Client Shell
### 3.1 Rust Core
- Tauri 2 application runtime。
- Tokio 处理信令、配对、Edge 和 helper IPC。
- rusqlite 保存 HostProfile 与非敏感设置。
- Windows Credential Manager 保存密钥、密码和 Token。
- reqwest/rustls 调用边缘控制面。
- tracing 输出结构化日志。
Tauri command 使用最小 capability allowlist。JavaScript 不能直接读取 Credential Manager、任意文件、Agent 长期私钥。
### 3.2 React UI
- React + TypeScript + Vite。
- 状态按 Host/Profile/Session/Diagnostics 拆分。
- 图标使用成熟图标库,不在业务代码中手写 SVG。
- 页面资源全部本地打包,不加载 CDN 脚本或远端网页。
- Release 禁用开发者工具、任意导航和不必要 Web API。
UI 只接收结构化状态,不接收密码、长期私钥或媒体帧。
## 5. Linux Terminal
- xterm.js 负责 ANSI/VT、Unicode、中文宽字符、选择和滚动区。
- PTY 字节通过可靠 DataChannel 直接进入页面。
- 大量输出有有界缓存和背压。
- 多行粘贴前确认。
- Alternate Screen 与普通历史分离。
- JS 不解析或拼接远端 shell 命令,只呈现 PTY 字节。
xterm.js 性能通过高频输出 Spike 验证;若无法满足,再启用 Rust 原生终端 helper,不影响协议。
## 6. Native RDP Helpers
### 6.1 当前实现
- Rust stable。
- `remotedesk-rdp-session.exe`:从有界 stdin JSON 接收目标地址,完成 DNS、TCP、X.224、RDP Negotiation Confirm 和无凭据 TLS 证书交换,不接收账号密码,也不进入 CredSSP/NLA。
- `remotedesk-rdp-viewer.exe`:使用 IronRDP 与 winit 建立单屏或全部本地显示器原生会话;账号和密码只在 helper 的本地遮罩提示中输入。
- viewer 的桌面帧留在原生进程,不进入 React、Tauri IPC 或浏览器。
- 当前 viewer 优先报告 `d3d11_cpu_upload`,并通过独立能力 `d3d11_dirty_rect_upload` 声明局部上传;D3D11/DXGI 创建失败时报告 `software_framebuffer`。单屏支持窗口尺寸驱动的动态分辨率,全部本地显示器使用 Win32 geometry 和 RDPEDISP 多 monitor layout,自定义显示器子集仍切换系统 `mstsc`
### 6.2 目标渲染路径
渲染路径:
```text
IronRDP dirty rect
-> D3D11 upload/merge
-> GPU texture
-> native swap chain
```
IronRDP 关键能力不足时,主程序启动 mstsc 回退;mstsc 不伪装成内嵌原生 helper。
当前已完成 D3D11 device、持久 CPU framebuffer 与完整 GPU frame texture、dirty region 紧凑像素转换、多个待呈现 dirty rect 合并、局部 `UpdateSubresource` 和 swap chain 呈现。为保证 discard swap chain 下未变化区域正确,每次呈现仍从完整 frame texture 复制到 back buffer。因此该路径继续报告 `d3d11_cpu_upload`,不得报告硬件解码或零拷贝。
### 6.3 Helper IPC
主程序与 helper 使用 Windows Named Pipe
- Pipe 使用当前用户 SID 的严格 ACL。
- RDP viewer 启动时命令行只接收随机 pipe name;一次性 32 字节 bootstrap key 只进入目标子进程环境并在读取后清除。
- 控制服务校验 pipe API 返回的客户端 PID、进程创建时间、实际可执行文件路径和 SHA-256 build hash,再完成 HMAC-SHA256 challenge。
- viewer 严格校验版本化配置后返回载荷 SHA-256 与 challenge 绑定的 HMAC ACK;服务端验证版本、摘要和 MAC 后才确认配置已接受。
- 连接、challenge/auth、配置帧和 ACK 共用有界绝对 deadline;非阻塞分段读取支持正常短读,但不会因已连接 helper 停滞而永久占用握手线程。
- viewer 启动后立即加入设置 `KILL_ON_JOB_CLOSE` 的独立 Job Object;控制服务退出或握手失败时由 Job 终止遗留进程。
- 当前 RDP 管道发送证书指纹、opaque 凭据引用和连接参数,密码仍由 viewer 直接从 Credential Manager 读取;Session Ticket 后续复用同一有界协议。
- IPC 只传控制、状态、指标和输入命令,不传视频 frame。
- helper crash 后主程序显示结构化错误并可重启。
## 7. Native Linux Video Helper
这是 Linux 桌面默认客户端媒体进程,负责 WebRTC、DataChannel、Raw Input、硬件解码和呈现。默认选择窗口显示器所属 Adapter,用户也可手动指定。
当前 helper 的 Windows 源码除 D3D11 device 和 H.264 decoder allocation probe 外,还提供有界本地文件解码及原生窗口播放入口。它使用启用 DXVA/hardware transforms 且绑定 D3D11 device manager 的 Media Foundation Source Reader,将 H.264 MP4/M4V/MOV 请求为 NV12;输出必须是同一 device 上的 `IMFDXGIBuffer` texture,系统内存 buffer 会失败。播放器跟踪媒体类型的可见尺寸和变化标志,按 100 ns 时间戳调度 texture subresource,以同 device `ID3D11VideoProcessor` 转换和缩放到 BGRA swap chain,黑边由 render target 清除,过程中不映射 CPU frame。该链路提交真实压缩样本并实际调用窗口呈现,但在 ETW/GPUView 尚未证明 video decode engine 和无隐藏 copy 前仍报告 `hardware_decode_verified=false``zero_copy_verified=false`
Linux 桌面控制 helper 的 Windows 模块还将远端 H.264 RTP 按 RFC 6184 重组为有界 Annex-B AU,覆盖单 NAL、STAP-A 和 FU-A。跨帧序号中断或本地一槽邮箱积压会清除参考链、请求 PLI 并等待新关键帧;重连时销毁旧解码器。AU 优先以 `MFVideoFormat_H264_ES` 输入 D3D11-aware H.264 MFT,只接受同一 device 的 NV12 `IMFDXGIBuffer`,再复用 VideoProcessor/swap-chain 模式呈现到 Linux 桌面窗口。第一帧呈现后,协议 minor 9 在完整 zlib 帧 ACK 后确认 H.264-only;随后每个 sequence 只在 D3D11 呈现成功后 ACK,Agent 才捕获下一帧,不再执行同帧 zlib 编码和传输。初始化、解码、媒体、sender/encoder 或 ACK 失败时恢复 zlib 软件 surface。该远程链路尚未编译和实机验证,不能据此打开 `linux.native_video`
管线:
```text
gstreamer-rs webrtcbin
-> D3D11 hardware decoder on selected adapter
-> GstD3D11Memory
-> d3d11videosink
-> native HWND
```
decoder、sink 和 swap chain 必须位于同一 Adapter;验证到 CPU map 或跨 Adapter copy 时,`required_end_to_end` 协商失败。该 helper 与 RDP helper 共享 WindowHost、GPU selection、IPC 和诊断 crate,但不共享协议 Adapter。
## 8. Linux Agent
```text
agent/
└── crates/
├── agentd
├── desktop-session
├── shell-session
├── agent-core
├── agent-media
├── wayland-backend
├── xorg-backend
└── platform-linux
```
主要技术:Tokio、Axum/rustls、zbus/ashpd、gstreamer-rs、x11rb、rustix/nix、prost、rusqlite 和 tracing。
Wayland 使用 Portal/PipeWire/libeiXorg 默认使用 XComposite/DRI3/XDamage/XRandR/XTestXShm 仅兼容模式;终端使用 PAM/PTY。unsafe FFI 必须隔离在专用 crate。
Linux 视频内存策略:
```text
preferred:
PipeWire memory:DMABuf
-> GPU color convert/scale
-> VA-API or NVENC hardware encoder
-> RTP
explicit compatibility fallback:
PipeWire MemPtr / XShm
-> CPU or GPU upload
-> encoder
-> RTP
```
Agent 媒体核心只传递带所有权和 fence 的 surface handle,不提供接收原始 RGBA `Vec<u8>` 的通用接口。禁止用 `appsink` 拉取原始帧再送入编码器。每种 GStreamer/驱动组合必须通过 tracer、perf 和 GPU 工具证明没有 CPU map/copy 后,才能把会话标记为 `zero_copy`
Agent 默认从 DMA-BUF/DRI3 元数据解析 DRM render node,并在同一设备上选择 VA-API/NVENC video processor 和编码器。服务端手动 GPU 只有在能直接导入该 surface 时可用;跨 GPU 选择不满足严格模式。
## 9. 协议与 Edge
- Protobuf/Buf 定义共享消息契约。
- Rust 端使用 prostWeb UI 使用生成的 TypeScript codec。
- TLS WebSocket 负责配对和信令。
- WebRTC RTP/RTCP 负责媒体和质量反馈。
- IronRDP 原生会话把 RDP `NetworkCharacteristicsResult` 中服务器测得的平均 RTT、基础 RTT 和带宽提升为脱敏诊断事件;不以帧间隔推算网络时延。
- IronRDP 图形事件记录网络 PDU 在本地 active stage 的处理与 framebuffer 像素转换耗时;本地事件保持空值,且该指标不代表服务端编码或 GPU 视频解码。
- DataChannel 负责输入、终端、控制、剪贴板和文件。
- 智能比较 Direct、Single Edge 和 Dual Edge。
- Linux WebRTC 使用双 POP TURNWindows RDP 使用透明 L4 Connector。
TURN credentials、SDP 和 Session Ticket 通过受限 Named Pipe 交给 native-video helper;长期设备私钥、Agent 授权和短期会话材料均不进入 JavaScript。
## 10. GPU 选择
配置模式:
```text
inherit global
default
manual(adapter identity)
```
- RDP helper:默认和手动均可精确控制 DXGI Adapter。
- Linux native default:选择窗口显示器所属 Adapter,使 decoder、sink 和输出保持同 GPU。
- Linux manualnative-video helper 使用指定 LUID;窗口不在该 Adapter 的 output 上时严格模式拒绝跨 GPU copy。
- 指定 GPU 缺失时本次回退默认,不修改用户配置。
- 诊断展示 requested/effective GPU、decoder、driver 和 copy path。
- 零拷贝策略为默认 `required_end_to_end` 或显式 `compatibility`;前者要求 Agent/client 两端同时验证通过。
## 11. 分辨率实现
- Tauri/React:提供自动跟随、原始、固定预设和自定义尺寸菜单,保存主机级策略。
- IronRDP helper:把尺寸请求映射到 Display Control Dynamic Monitor Layout;不支持时仅在确认后重连。
- Linux Agent:使用 VA-API/CUDA 等 GPU video processor/scaler 和 DMA-BUF caps 在编码前缩放,不使用可能落入 CPU raw frame 的通用 `videoscale`,也不改变 Wayland/Xorg 实体显示模式。
- Native renderer:按收到的实际编码尺寸重建呈现资源,保持等比缩放并正确换算黑边输入坐标。
- Protobuf:传递 capability、request/applied、generation 和 source/encoded size,防止乱序 resize 覆盖新状态。
自动跟随窗口必须防抖;固定尺寸默认允许弱网临时下调,也可由用户锁定。所有模式都受双方最大纹理尺寸、编码器像素率和资源预算限制。
## 12. 仓库结构
```text
RemoteDesk/
├── Cargo.toml
├── rust-toolchain.toml
├── client/
│ ├── src-tauri/
│ ├── web/
│ └── helpers/
│ ├── rdp-session/
│ └── native-video/
├── agent/
├── shared/
│ ├── protocol/
│ ├── session/
│ ├── helper-ipc/
│ └── diagnostics/
├── services/
│ ├── rendezvous/
│ └── relay-allocator/
├── protocol/
├── packaging/
└── tests/
```
## 13. 构建
Rust
- Cargo workspace、`Cargo.lock``rust-toolchain.toml`
- rustfmt、Clippy、cargo-deny 和 cargo-audit。
Web
- pnpm lockfile。
- TypeScript strict mode。
- ESLint、Vitest 和 Playwright。
- Vite 构建为本地静态资源。
Windows
- Tauri/WiX MSI。
- 安装主程序、native helpers 和必要 GStreamer runtime。
- WebView2 Evergreen Runtime 作为前置条件。
- 主程序、helper 和安装包全部签名。
Linux
- Cargo + 发行版 GStreamer/PipeWire/PAM/X11/libei。
- DEB/RPM 与 systemd units。
## 14. 测试
| 层级 | 工具 |
|---|---|
| Rust | cargo test、proptest |
| React/TypeScript | Vitest、Testing Library |
| WebRTC 页面 | Playwright + synthetic MediaStream |
| RDP | IronRDP + Windows VM |
| GPU | GPUView/ETW、GStreamer tracer |
| Agent | Cargo integration、真实 Wayland/Xorg |
| Edge | coturn、多 POP/netem |
| 端到端 | pytest + 虚拟化测试机 |
Web 视频测试必须证明 frame 不经过 JS/Canvas:页面不注册逐帧像素处理,Chromium media internals 显示硬解路径,CPU/拷贝指标符合基线。Linux Agent 同时使用 GStreamer tracer、`perf` 和驱动工具验证 DMA-BUF 未被 CPU map;仅看到 Video Encode 引擎占用不能作为零拷贝证据。
## 15. 安全
- WebView 使用严格 CSP:本地资源、必要的 media/connect source,禁止任意导航。
- Tauri capability 按窗口最小授权。
- Release 禁用 devtools 和远端脚本。
- Web 页面不获取长期私钥或 RDP 密码。
- Native helper IPC 使用 Named Pipe ACL、一次性 token 和进程验证。
- TURN/SDP/session material 限时并在窗口关闭后销毁。
- 依赖生成 SBOMnpm 与 Cargo 同时审计。
## 16. 许可证
- Tauri、React、xterm.js、IronRDP 和 Rust crates 进入许可证 allowlist。
- WebView2 Runtime 遵循 Microsoft 分发条款。
- GStreamer 插件逐项审计,不默认捆绑 GPL x264。
- 每个安装包附带第三方许可证和机器可读 SBOM。
## 17. M0 门槛
1. Native-video helper 证明 D3D11 decoder 到 d3d11videosink 无 CPU/cross-GPU copy。
2. Linux 输入、DataChannel 和系统组合键回退正确。
3. xterm.js 通过中文、全屏 TUI 和高频输出测试。
4. Native RDP helper 的 IronRDP/NLA/D3D11 通过。
5. Named Pipe IPC 的 ACL、崩溃和重启通过。
6. 默认显示器 GPU 与手动 GPU 的 native-video 零拷贝路径通过。
7. Direct/Single/Dual Edge 路径通过。
8. Wayland DMA-BUF 和 Xorg DRI3 到硬件编码器的零拷贝路径通过;任一端回退时默认拒绝。
## 18. 明确不做
- 不把视频 frame 通过 Tauri IPC 传给页面。
- 不用 Canvas/WebGL/WebGPU 逐帧绘制 Linux 远程视频。
- 不在 Web UI 中实现 RDP 协议。
- 不从零实现 RDP、WebRTC、TURN、编解码或密码学。
- 不把 mstsc 回退描述为内嵌 RDP。
该方案用 Web 平台降低管理 UI 和终端开发成本,用 Rust 原生 helper 承载所有默认桌面视频、RDP、精确 GPU、Raw Input 和驱动恢复能力。