From a8a3b6c66b7494870b55e7ba6e2faa3e770e7584 Mon Sep 17 00:00:00 2001 From: mofeng-git Date: Wed, 31 Dec 2025 18:59:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20RustDesk=20?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE=E6=94=AF=E6=8C=81=E5=92=8C=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 RustDesk 模块,支持与 RustDesk 客户端连接 - 实现会合服务器协议和 P2P 连接 - 支持 NaCl 加密和密钥交换 - 添加视频帧和 HID 事件适配器 - 添加 Protobuf 协议定义 (message.proto, rendezvous.proto) - 新增完整项目文档 - 各功能模块文档 (video, hid, msd, otg, webrtc 等) - hwcodec 和 RustDesk 协议技术报告 - 系统架构和技术栈文档 - 更新 Web 前端 RustDesk 配置界面和 API --- Cargo.toml | 8 + build.rs | 18 + docs/README.md | 130 ++ docs/modules/atx.md | 484 ++++++ docs/modules/audio.md | 463 ++++++ docs/modules/auth.md | 340 ++++ docs/modules/config.md | 297 ++++ docs/modules/events.md | 353 +++++ docs/modules/hid.md | 850 ++++++++++ docs/modules/msd.md | 617 ++++++++ docs/modules/otg.md | 667 ++++++++ docs/modules/rustdesk.md | 776 +++++++++ docs/modules/video.md | 895 +++++++++++ docs/modules/web.md | 428 +++++ docs/modules/webrtc.md | 731 +++++++++ docs/report/hwcodec/00-architecture.md | 550 +++++++ docs/report/hwcodec/01-api-reference.md | 445 ++++++ .../hwcodec/02-hardware-acceleration.md | 615 ++++++++ docs/report/hwcodec/03-build-integration.md | 539 +++++++ docs/report/rustdesk/00-overview.md | 69 + docs/report/rustdesk/01-architecture.md | 218 +++ .../report/rustdesk/02-rendezvous-protocol.md | 438 ++++++ docs/report/rustdesk/03-relay-protocol.md | 318 ++++ docs/report/rustdesk/04-p2p-connection.md | 424 +++++ docs/report/rustdesk/05-message-format.md | 574 +++++++ docs/report/rustdesk/06-encryption.md | 342 ++++ docs/report/rustdesk/07-nat-traversal.md | 410 +++++ docs/report/rustdesk/08-onekvm-comparison.md | 401 +++++ docs/system-architecture.md | 876 +++++++++++ docs/tech-stack.md | 1007 ++++++++++++ protos/message.proto | 875 +++++++++++ protos/rendezvous.proto | 197 +++ src/config/schema.rs | 5 + src/lib.rs | 1 + src/main.rs | 56 + src/rustdesk/bytes_codec.rs | 253 +++ src/rustdesk/config.rs | 210 +++ src/rustdesk/connection.rs | 1396 +++++++++++++++++ src/rustdesk/crypto.rs | 467 ++++++ src/rustdesk/frame_adapters.rs | 315 ++++ src/rustdesk/hid_adapter.rs | 385 +++++ src/rustdesk/mod.rs | 587 +++++++ src/rustdesk/protocol.rs | 169 ++ src/rustdesk/rendezvous.rs | 828 ++++++++++ src/state.rs | 5 + src/video/stream_manager.rs | 72 + src/web/handlers/config/apply.rs | 59 + src/web/handlers/config/mod.rs | 7 + src/web/handlers/config/rustdesk.rs | 142 ++ src/web/handlers/config/types.rs | 58 + src/web/routes.rs | 7 + src/webrtc/webrtc_streamer.rs | 20 + web/src/api/config.ts | 75 + web/src/api/index.ts | 5 + web/src/i18n/en-US.ts | 36 + web/src/i18n/zh-CN.ts | 36 + web/src/views/SettingsView.vue | 281 ++++ 57 files changed, 20830 insertions(+) create mode 100644 docs/README.md create mode 100644 docs/modules/atx.md create mode 100644 docs/modules/audio.md create mode 100644 docs/modules/auth.md create mode 100644 docs/modules/config.md create mode 100644 docs/modules/events.md create mode 100644 docs/modules/hid.md create mode 100644 docs/modules/msd.md create mode 100644 docs/modules/otg.md create mode 100644 docs/modules/rustdesk.md create mode 100644 docs/modules/video.md create mode 100644 docs/modules/web.md create mode 100644 docs/modules/webrtc.md create mode 100644 docs/report/hwcodec/00-architecture.md create mode 100644 docs/report/hwcodec/01-api-reference.md create mode 100644 docs/report/hwcodec/02-hardware-acceleration.md create mode 100644 docs/report/hwcodec/03-build-integration.md create mode 100644 docs/report/rustdesk/00-overview.md create mode 100644 docs/report/rustdesk/01-architecture.md create mode 100644 docs/report/rustdesk/02-rendezvous-protocol.md create mode 100644 docs/report/rustdesk/03-relay-protocol.md create mode 100644 docs/report/rustdesk/04-p2p-connection.md create mode 100644 docs/report/rustdesk/05-message-format.md create mode 100644 docs/report/rustdesk/06-encryption.md create mode 100644 docs/report/rustdesk/07-nat-traversal.md create mode 100644 docs/report/rustdesk/08-onekvm-comparison.md create mode 100644 docs/system-architecture.md create mode 100644 docs/tech-stack.md create mode 100644 protos/message.proto create mode 100644 protos/rendezvous.proto create mode 100644 src/rustdesk/bytes_codec.rs create mode 100644 src/rustdesk/config.rs create mode 100644 src/rustdesk/connection.rs create mode 100644 src/rustdesk/crypto.rs create mode 100644 src/rustdesk/frame_adapters.rs create mode 100644 src/rustdesk/hid_adapter.rs create mode 100644 src/rustdesk/mod.rs create mode 100644 src/rustdesk/protocol.rs create mode 100644 src/rustdesk/rendezvous.rs create mode 100644 src/web/handlers/config/rustdesk.rs diff --git a/Cargo.toml b/Cargo.toml index 2096a50e..0e035439 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -113,6 +113,11 @@ gpio-cdev = "0.6" # H264 hardware/software encoding (hwcodec from rustdesk) hwcodec = { path = "libs/hwcodec" } +# RustDesk protocol support +prost = "0.13" +sodiumoxide = "0.2" +sha2 = "0.10" + # High-performance pixel format conversion (libyuv) libyuv = { path = "res/vcpkg/libyuv" } @@ -123,6 +128,9 @@ typeshare = "1.0" tokio-test = "0.4" tempfile = "3" +[build-dependencies] +prost-build = "0.13" + [profile.release] opt-level = 3 lto = true diff --git a/build.rs b/build.rs index 82ee06d3..d99dca3c 100644 --- a/build.rs +++ b/build.rs @@ -14,8 +14,26 @@ fn main() { println!("cargo:rustc-env=BUILD_DATE={}", build_date); + // Compile protobuf files for RustDesk protocol + compile_protos(); + // Rerun if the script itself changes println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=protos/rendezvous.proto"); + println!("cargo:rerun-if-changed=protos/message.proto"); +} + +/// Compile protobuf files using prost-build +fn compile_protos() { + let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()); + + prost_build::Config::new() + .out_dir(&out_dir) + .compile_protos( + &["protos/rendezvous.proto", "protos/message.proto"], + &["protos/"], + ) + .expect("Failed to compile protobuf files"); } /// Convert days since Unix epoch to year-month-day diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..24545f82 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,130 @@ +# One-KVM 技术文档 + +本目录包含 One-KVM 项目的完整技术文档。 + +## 文档结构 + +``` +docs/ +├── README.md # 本文件 - 文档索引 +├── system-architecture.md # 系统架构文档 +├── tech-stack.md # 技术栈文档 +└── modules/ # 模块文档 + ├── video.md # 视频模块 + ├── hid.md # HID 模块 + ├── otg.md # OTG 模块 + ├── msd.md # MSD 模块 + ├── atx.md # ATX 模块 + ├── audio.md # 音频模块 + ├── webrtc.md # WebRTC 模块 + ├── rustdesk.md # RustDesk 模块 + ├── auth.md # 认证模块 + ├── config.md # 配置模块 + ├── events.md # 事件模块 + └── web.md # Web 模块 +``` + +## 快速导航 + +### 核心文档 + +| 文档 | 描述 | +|------|------| +| [系统架构](./system-architecture.md) | 整体架构设计、数据流、模块依赖 | +| [技术栈](./tech-stack.md) | 使用的技术、库和开发规范 | + +### 功能模块 + +| 模块 | 描述 | 关键文件 | +|------|------|---------| +| [Video](./modules/video.md) | 视频采集和编码 | `src/video/` | +| [HID](./modules/hid.md) | 键盘鼠标控制 | `src/hid/` | +| [OTG](./modules/otg.md) | USB Gadget 管理 | `src/otg/` | +| [MSD](./modules/msd.md) | 虚拟存储设备 | `src/msd/` | +| [ATX](./modules/atx.md) | 电源控制 | `src/atx/` | +| [Audio](./modules/audio.md) | 音频采集编码 | `src/audio/` | +| [WebRTC](./modules/webrtc.md) | WebRTC 流媒体 | `src/webrtc/` | +| [RustDesk](./modules/rustdesk.md) | RustDesk 协议集成 | `src/rustdesk/` | + +### 基础设施 + +| 模块 | 描述 | 关键文件 | +|------|------|---------| +| [Auth](./modules/auth.md) | 认证和会话 | `src/auth/` | +| [Config](./modules/config.md) | 配置管理 | `src/config/` | +| [Events](./modules/events.md) | 事件系统 | `src/events/` | +| [Web](./modules/web.md) | HTTP API | `src/web/` | + +## 架构概览 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ One-KVM System │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ Web Frontend (Vue3) │ │ +│ └──────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ Axum Web Server │ │ +│ └──────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ AppState │ │ +│ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │ +│ │ │ Video │ │ HID │ │ MSD │ │ ATX │ │ │ +│ │ │ Module │ │ Module │ │ Module │ │ Module │ │ │ +│ │ └────────┘ └────────┘ └────────┘ └────────┘ │ │ +│ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │ +│ │ │ Audio │ │ WebRTC │ │RustDesk│ │ Events │ │ │ +│ │ │ Module │ │ Module │ │ Module │ │ Bus │ │ │ +│ │ └────────┘ └────────┘ └────────┘ └────────┘ │ │ +│ └──────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ Hardware Layer │ │ +│ │ V4L2 │ USB OTG │ GPIO │ ALSA │ Network │ │ +│ └──────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## 关键特性 + +- **单一二进制**: Web UI + 后端一体化部署 +- **双流模式**: WebRTC (H264/H265/VP8/VP9) + MJPEG +- **USB OTG**: 虚拟键鼠、虚拟存储 +- **硬件加速**: VAAPI/RKMPP/V4L2 M2M +- **RustDesk**: 跨平台远程访问 +- **无配置文件**: SQLite 配置存储 + +## 目标平台 + +| 平台 | 架构 | 用途 | +|------|------|------| +| aarch64-unknown-linux-gnu | ARM64 | 主要目标 | +| armv7-unknown-linux-gnueabihf | ARMv7 | 备选 | +| x86_64-unknown-linux-gnu | x86-64 | 开发/测试 | + +## 快速开始 + +```bash +# 构建前端 +cd web && npm install && npm run build && cd .. + +# 构建后端 +cargo build --release + +# 运行 +./target/release/one-kvm --enable-https +``` + +## 相关链接 + +- [项目仓库](https://github.com/mofeng-git/One-KVM) +- [开发计划](./DEVELOPMENT_PLAN.md) +- [项目目标](./PROJECT_GOALS.md) diff --git a/docs/modules/atx.md b/docs/modules/atx.md new file mode 100644 index 00000000..316d4a34 --- /dev/null +++ b/docs/modules/atx.md @@ -0,0 +1,484 @@ +# ATX 模块文档 + +## 1. 模块概述 + +ATX 模块提供电源控制功能,通过 GPIO 或 USB 继电器控制目标计算机的电源和重置按钮。 + +### 1.1 主要功能 + +- 电源按钮控制 +- 重置按钮控制 +- 电源 LED 状态监视 +- Wake-on-LAN 支持 +- 多后端支持 (GPIO/USB 继电器) + +### 1.2 文件结构 + +``` +src/atx/ +├── mod.rs # 模块导出 +├── controller.rs # AtxController (11KB) +├── executor.rs # 动作执行器 (10KB) +├── types.rs # 类型定义 (7KB) +├── led.rs # LED 监视 (5KB) +└── wol.rs # Wake-on-LAN (5KB) +``` + +--- + +## 2. 架构设计 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ ATX Architecture │ +└─────────────────────────────────────────────────────────────────────────────┘ + + Web API + │ + ▼ + ┌─────────────────┐ + │ AtxController │ + │ (controller.rs) │ + └────────┬────────┘ + │ + ┌─────────────┼─────────────┐ + │ │ │ + ▼ ▼ ▼ +┌────────┐ ┌────────┐ ┌────────┐ +│ Power │ │ Reset │ │ LED │ +│Executor│ │Executor│ │Monitor │ +└───┬────┘ └───┬────┘ └───┬────┘ + │ │ │ + ▼ ▼ ▼ +┌────────┐ ┌────────┐ ┌────────┐ +│ GPIO │ │ GPIO │ │ GPIO │ +│ or USB │ │ or USB │ │ Input │ +│ Relay │ │ Relay │ │ │ +└───┬────┘ └───┬────┘ └───┬────┘ + │ │ │ + └───────────┼─────────────┘ + │ + ▼ + ┌───────────────┐ + │ Target PC │ + │ (ATX Header) │ + └───────────────┘ +``` + +--- + +## 3. 核心组件 + +### 3.1 AtxController (controller.rs) + +```rust +pub struct AtxController { + /// 电源按钮配置 + power: Arc, + + /// 重置按钮配置 + reset: Arc, + + /// LED 监视器 + led_monitor: Arc>>, + + /// WoL 控制器 + wol: Arc>>, + + /// 当前状态 + state: Arc>, + + /// 事件总线 + events: Arc, +} + +impl AtxController { + /// 创建控制器 + pub fn new(config: &AtxConfig, events: Arc) -> Result; + + /// 短按电源按钮 (开机/正常关机) + pub async fn power_short_press(&self) -> Result<()>; + + /// 长按电源按钮 (强制关机) + pub async fn power_long_press(&self) -> Result<()>; + + /// 按重置按钮 + pub async fn reset_press(&self) -> Result<()>; + + /// 获取电源状态 + pub fn power_state(&self) -> PowerState; + + /// 发送 WoL 魔术包 + pub async fn wake_on_lan(&self, mac: &str) -> Result<()>; + + /// 获取状态 + pub fn state(&self) -> AtxState; + + /// 重新加载配置 + pub async fn reload(&self, config: &AtxConfig) -> Result<()>; +} + +pub struct AtxState { + /// 是否可用 + pub available: bool, + + /// 电源是否开启 + pub power_on: bool, + + /// 最后操作时间 + pub last_action: Option>, + + /// 错误信息 + pub error: Option, +} + +pub enum PowerState { + On, + Off, + Unknown, +} +``` + +### 3.2 AtxButton (executor.rs) + +```rust +pub struct AtxButton { + /// 按钮名称 + name: String, + + /// 驱动类型 + driver: AtxDriverType, + + /// GPIO 句柄 + gpio: Option, + + /// USB 继电器句柄 + relay: Option, + + /// 配置 + config: AtxKeyConfig, +} + +impl AtxButton { + /// 创建按钮 + pub fn new(name: &str, config: &AtxKeyConfig) -> Result; + + /// 短按 (100ms) + pub async fn short_press(&self) -> Result<()>; + + /// 长按 (3000ms) + pub async fn long_press(&self) -> Result<()>; + + /// 自定义按压时间 + pub async fn press(&self, duration: Duration) -> Result<()>; + + /// 设置输出状态 + fn set_output(&self, high: bool) -> Result<()>; +} + +pub enum AtxDriverType { + /// GPIO 直连 + Gpio, + + /// USB 继电器 + UsbRelay, + + /// 禁用 + None, +} +``` + +### 3.3 LedMonitor (led.rs) + +```rust +pub struct LedMonitor { + /// GPIO 引脚 + pin: u32, + + /// GPIO 句柄 + line: LineHandle, + + /// 当前状态 + state: Arc, + + /// 监视任务 + monitor_task: Option>, +} + +impl LedMonitor { + /// 创建监视器 + pub fn new(config: &AtxLedConfig) -> Result; + + /// 启动监视 + pub fn start(&mut self, events: Arc) -> Result<()>; + + /// 停止监视 + pub fn stop(&mut self); + + /// 获取当前状态 + pub fn state(&self) -> bool; +} +``` + +### 3.4 WolController (wol.rs) + +```rust +pub struct WolController { + /// 网络接口 + interface: String, + + /// 广播地址 + broadcast_addr: SocketAddr, +} + +impl WolController { + /// 创建控制器 + pub fn new(interface: Option<&str>) -> Result; + + /// 发送 WoL 魔术包 + pub async fn wake(&self, mac: &str) -> Result<()>; + + /// 构建魔术包 + fn build_magic_packet(mac: &[u8; 6]) -> [u8; 102]; + + /// 解析 MAC 地址 + fn parse_mac(mac: &str) -> Result<[u8; 6]>; +} +``` + +--- + +## 4. 配置 + +```rust +#[derive(Serialize, Deserialize)] +#[typeshare] +pub struct AtxConfig { + /// 是否启用 + pub enabled: bool, + + /// 电源按钮配置 + pub power: AtxKeyConfig, + + /// 重置按钮配置 + pub reset: AtxKeyConfig, + + /// LED 监视配置 + pub led: AtxLedConfig, + + /// WoL 配置 + pub wol: WolConfig, +} + +#[derive(Serialize, Deserialize)] +#[typeshare] +pub struct AtxKeyConfig { + /// 驱动类型 + pub driver: AtxDriverType, + + /// GPIO 芯片 (如 /dev/gpiochip0) + pub gpio_chip: Option, + + /// GPIO 引脚号 + pub gpio_pin: Option, + + /// USB 继电器设备 + pub relay_device: Option, + + /// 继电器通道 + pub relay_channel: Option, + + /// 激活电平 + pub active_level: ActiveLevel, +} + +#[derive(Serialize, Deserialize)] +#[typeshare] +pub struct AtxLedConfig { + /// 是否启用 + pub enabled: bool, + + /// GPIO 芯片 + pub gpio_chip: Option, + + /// GPIO 引脚号 + pub gpio_pin: Option, + + /// 激活电平 + pub active_level: ActiveLevel, +} + +pub enum ActiveLevel { + High, + Low, +} + +impl Default for AtxConfig { + fn default() -> Self { + Self { + enabled: false, + power: AtxKeyConfig::default(), + reset: AtxKeyConfig::default(), + led: AtxLedConfig::default(), + wol: WolConfig::default(), + } + } +} +``` + +--- + +## 5. API 端点 + +| 端点 | 方法 | 描述 | +|------|------|------| +| `/api/atx/status` | GET | 获取 ATX 状态 | +| `/api/atx/power/short` | POST | 短按电源 | +| `/api/atx/power/long` | POST | 长按电源 | +| `/api/atx/reset` | POST | 按重置 | +| `/api/atx/wol` | POST | 发送 WoL | + +### 响应格式 + +```json +// GET /api/atx/status +{ + "available": true, + "power_on": true, + "last_action": "2024-01-15T10:30:00Z", + "error": null +} + +// POST /api/atx/wol +// Request: { "mac": "00:11:22:33:44:55" } +{ + "success": true +} +``` + +--- + +## 6. 硬件连接 + +### 6.1 GPIO 直连 + +``` +One-KVM Device Target PC +┌─────────────┐ ┌─────────────┐ +│ GPIO Pin │───────────────│ Power SW │ +│ (Output) │ │ │ +└─────────────┘ └─────────────┘ + +接线说明: +- GPIO 引脚连接到 ATX 电源按钮 +- 使用光耦或继电器隔离 (推荐) +- 注意电平匹配 +``` + +### 6.2 USB 继电器 + +``` +One-KVM Device USB Relay Target PC +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ USB │───────────────│ Relay │──────────│ Power SW │ +│ │ │ │ │ │ +└─────────────┘ └─────────────┘ └─────────────┘ + +优点: +- 完全隔离 +- 无需担心电平问题 +- 更安全 +``` + +--- + +## 7. 事件 + +```rust +pub enum SystemEvent { + AtxStateChanged { + power_on: bool, + last_action: Option, + error: Option, + }, + + AtxActionPerformed { + action: String, // "power_short" | "power_long" | "reset" | "wol" + success: bool, + }, +} +``` + +--- + +## 8. 错误处理 + +```rust +#[derive(Debug, thiserror::Error)] +pub enum AtxError { + #[error("ATX not available")] + NotAvailable, + + #[error("GPIO error: {0}")] + GpioError(String), + + #[error("Relay error: {0}")] + RelayError(String), + + #[error("WoL error: {0}")] + WolError(String), + + #[error("Invalid MAC address: {0}")] + InvalidMac(String), + + #[error("Operation in progress")] + Busy, +} +``` + +--- + +## 9. 使用示例 + +```rust +let atx = AtxController::new(&config, events)?; + +// 开机 +atx.power_short_press().await?; + +// 检查状态 +tokio::time::sleep(Duration::from_secs(5)).await; +if atx.power_state() == PowerState::On { + println!("PC is now on"); +} + +// 强制关机 +atx.power_long_press().await?; + +// 重置 +atx.reset_press().await?; + +// Wake-on-LAN +atx.wake_on_lan("00:11:22:33:44:55").await?; +``` + +--- + +## 10. 常见问题 + +### Q: GPIO 无法控制? + +1. 检查引脚配置 +2. 检查权限 (`/dev/gpiochip*`) +3. 检查接线 + +### Q: LED 状态不正确? + +1. 检查 active_level 配置 +2. 检查 GPIO 输入模式 + +### Q: WoL 不工作? + +1. 检查目标 PC BIOS 设置 +2. 检查网卡支持 +3. 检查网络广播 diff --git a/docs/modules/audio.md b/docs/modules/audio.md new file mode 100644 index 00000000..670f4469 --- /dev/null +++ b/docs/modules/audio.md @@ -0,0 +1,463 @@ +# Audio 模块文档 + +## 1. 模块概述 + +Audio 模块负责音频采集和编码,支持 ALSA 采集和 Opus 编码。 + +### 1.1 主要功能 + +- ALSA 音频采集 +- Opus 编码 +- 多质量配置 +- WebSocket/WebRTC 传输 + +### 1.2 文件结构 + +``` +src/audio/ +├── mod.rs # 模块导出 +├── controller.rs # AudioController (15KB) +├── capture.rs # ALSA 采集 (12KB) +├── encoder.rs # Opus 编码 (8KB) +├── shared_pipeline.rs # 共享管道 (15KB) +├── monitor.rs # 健康监视 (11KB) +└── device.rs # 设备枚举 (8KB) +``` + +--- + +## 2. 架构设计 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Audio Architecture │ +└─────────────────────────────────────────────────────────────────────────────┘ + +ALSA Device (hw:0,0) + │ + │ PCM 48kHz/16bit/Stereo + ▼ +┌─────────────────┐ +│ AudioCapturer │ +│ (capture.rs) │ +└────────┬────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ SharedAudioPipeline │ +│ ┌─────────────────────────────────┐ │ +│ │ Opus Encoder │ │ +│ │ 48kHz → 24-96 kbps │ │ +│ └─────────────────────────────────┘ │ +└────────────────┬────────────────────────┘ + │ + ┌─────────┴─────────┐ + │ │ + ▼ ▼ +┌─────────────┐ ┌─────────────┐ +│ WebSocket │ │ WebRTC │ +│ Stream │ │ Audio Track │ +└─────────────┘ └─────────────┘ +``` + +--- + +## 3. 核心组件 + +### 3.1 AudioController (controller.rs) + +```rust +pub struct AudioController { + /// 采集器 + capturer: Arc>>, + + /// 共享管道 + pipeline: Arc, + + /// 配置 + config: Arc>, + + /// 状态 + state: Arc>, + + /// 事件总线 + events: Arc, +} + +impl AudioController { + /// 创建控制器 + pub fn new(config: &AudioConfig, events: Arc) -> Result; + + /// 启动音频 + pub async fn start(&self) -> Result<()>; + + /// 停止音频 + pub async fn stop(&self) -> Result<()>; + + /// 订阅音频帧 + pub fn subscribe(&self) -> broadcast::Receiver; + + /// 获取状态 + pub fn status(&self) -> AudioStatus; + + /// 设置质量 + pub fn set_quality(&self, quality: AudioQuality) -> Result<()>; + + /// 列出设备 + pub fn list_devices(&self) -> Vec; + + /// 重新加载配置 + pub async fn reload(&self, config: &AudioConfig) -> Result<()>; +} + +pub struct AudioStatus { + pub enabled: bool, + pub streaming: bool, + pub device: Option, + pub sample_rate: u32, + pub channels: u16, + pub bitrate: u32, + pub error: Option, +} +``` + +### 3.2 AudioCapturer (capture.rs) + +```rust +pub struct AudioCapturer { + /// PCM 句柄 + pcm: PCM, + + /// 设备名 + device: String, + + /// 采样率 + sample_rate: u32, + + /// 通道数 + channels: u16, + + /// 帧大小 + frame_size: usize, + + /// 运行状态 + running: AtomicBool, +} + +impl AudioCapturer { + /// 打开设备 + pub fn open(device: &str, config: &CaptureConfig) -> Result; + + /// 读取音频帧 + pub fn read_frame(&self) -> Result>; + + /// 启动采集 + pub fn start(&self) -> Result<()>; + + /// 停止采集 + pub fn stop(&self); + + /// 是否运行中 + pub fn is_running(&self) -> bool; +} + +pub struct CaptureConfig { + pub sample_rate: u32, // 48000 + pub channels: u16, // 2 + pub frame_size: usize, // 960 (20ms) + pub buffer_size: usize, // 4800 +} +``` + +### 3.3 OpusEncoder (encoder.rs) + +```rust +pub struct OpusEncoder { + /// Opus 编码器 + encoder: audiopus::Encoder, + + /// 采样率 + sample_rate: u32, + + /// 通道数 + channels: u16, + + /// 帧大小 + frame_size: usize, + + /// 码率 + bitrate: u32, +} + +impl OpusEncoder { + /// 创建编码器 + pub fn new(quality: AudioQuality) -> Result; + + /// 编码 PCM 数据 + pub fn encode(&mut self, pcm: &[i16]) -> Result>; + + /// 设置码率 + pub fn set_bitrate(&mut self, bitrate: u32) -> Result<()>; + + /// 获取码率 + pub fn bitrate(&self) -> u32; + + /// 重置编码器 + pub fn reset(&mut self) -> Result<()>; +} +``` + +### 3.4 SharedAudioPipeline (shared_pipeline.rs) + +```rust +pub struct SharedAudioPipeline { + /// 采集器 + capturer: Arc>>, + + /// 编码器 + encoder: Arc>, + + /// 广播通道 + tx: broadcast::Sender, + + /// 采集任务 + capture_task: Arc>>>, + + /// 配置 + config: Arc>, +} + +impl SharedAudioPipeline { + /// 创建管道 + pub fn new(config: &AudioConfig) -> Result; + + /// 启动管道 + pub async fn start(&self) -> Result<()>; + + /// 停止管道 + pub async fn stop(&self) -> Result<()>; + + /// 订阅音频帧 + pub fn subscribe(&self) -> broadcast::Receiver; + + /// 获取统计 + pub fn stats(&self) -> PipelineStats; +} + +pub struct AudioFrame { + /// Opus 数据 + pub data: Bytes, + + /// 时间戳 + pub timestamp: u64, + + /// 帧序号 + pub sequence: u64, +} +``` + +--- + +## 4. 音频质量 + +```rust +pub enum AudioQuality { + /// 24 kbps - 最低带宽 + VeryLow, + + /// 48 kbps - 低带宽 + Low, + + /// 64 kbps - 中等 + Medium, + + /// 96 kbps - 高质量 + High, +} + +impl AudioQuality { + pub fn bitrate(&self) -> u32 { + match self { + Self::VeryLow => 24000, + Self::Low => 48000, + Self::Medium => 64000, + Self::High => 96000, + } + } +} +``` + +--- + +## 5. 配置 + +```rust +#[derive(Serialize, Deserialize)] +#[typeshare] +pub struct AudioConfig { + /// 是否启用 + pub enabled: bool, + + /// 设备名 + pub device: Option, + + /// 音频质量 + pub quality: AudioQuality, + + /// 自动启动 + pub auto_start: bool, +} + +impl Default for AudioConfig { + fn default() -> Self { + Self { + enabled: true, + device: None, // 使用默认设备 + quality: AudioQuality::Medium, + auto_start: false, + } + } +} +``` + +--- + +## 6. API 端点 + +| 端点 | 方法 | 描述 | +|------|------|------| +| `/api/audio/status` | GET | 获取音频状态 | +| `/api/audio/start` | POST | 启动音频 | +| `/api/audio/stop` | POST | 停止音频 | +| `/api/audio/devices` | GET | 列出设备 | +| `/api/audio/quality` | GET | 获取质量 | +| `/api/audio/quality` | POST | 设置质量 | +| `/api/ws/audio` | WS | 音频流 | + +### 响应格式 + +```json +// GET /api/audio/status +{ + "enabled": true, + "streaming": true, + "device": "hw:0,0", + "sample_rate": 48000, + "channels": 2, + "bitrate": 64000, + "error": null +} + +// GET /api/audio/devices +{ + "devices": [ + { + "name": "hw:0,0", + "description": "USB Audio Device", + "is_default": true + } + ] +} +``` + +--- + +## 7. WebSocket 音频流 + +```javascript +// 连接 WebSocket +const ws = new WebSocket('/api/ws/audio'); +ws.binaryType = 'arraybuffer'; + +// 初始化 Opus 解码器 +const decoder = new OpusDecoder(); + +// 接收音频帧 +ws.onmessage = (event) => { + const frame = new Uint8Array(event.data); + const pcm = decoder.decode(frame); + audioContext.play(pcm); +}; +``` + +--- + +## 8. 事件 + +```rust +pub enum SystemEvent { + AudioStateChanged { + enabled: bool, + streaming: bool, + device: Option, + error: Option, + }, +} +``` + +--- + +## 9. 错误处理 + +```rust +#[derive(Debug, thiserror::Error)] +pub enum AudioError { + #[error("Device not found: {0}")] + DeviceNotFound(String), + + #[error("Device busy: {0}")] + DeviceBusy(String), + + #[error("ALSA error: {0}")] + AlsaError(String), + + #[error("Encoder error: {0}")] + EncoderError(String), + + #[error("Not streaming")] + NotStreaming, +} +``` + +--- + +## 10. 使用示例 + +```rust +let controller = AudioController::new(&config, events)?; + +// 启动音频 +controller.start().await?; + +// 订阅音频帧 +let mut rx = controller.subscribe(); +while let Ok(frame) = rx.recv().await { + // 处理 Opus 数据 + send_to_client(frame.data); +} + +// 停止 +controller.stop().await?; +``` + +--- + +## 11. 常见问题 + +### Q: 找不到音频设备? + +1. 检查 ALSA 配置 +2. 运行 `arecord -l` +3. 检查权限 + +### Q: 音频延迟高? + +1. 减小帧大小 +2. 降低质量 +3. 检查网络 + +### Q: 音频断断续续? + +1. 增大缓冲区 +2. 检查 CPU 负载 +3. 使用更低质量 diff --git a/docs/modules/auth.md b/docs/modules/auth.md new file mode 100644 index 00000000..a1e4a4b6 --- /dev/null +++ b/docs/modules/auth.md @@ -0,0 +1,340 @@ +# Auth 模块文档 + +## 1. 模块概述 + +Auth 模块提供用户认证和会话管理功能。 + +### 1.1 主要功能 + +- 用户管理 +- 密码哈希 (Argon2) +- 会话管理 +- 认证中间件 +- 权限控制 + +### 1.2 文件结构 + +``` +src/auth/ +├── mod.rs # 模块导出 +├── user.rs # 用户管理 (5KB) +├── session.rs # 会话管理 (4KB) +├── password.rs # 密码哈希 (1KB) +└── middleware.rs # 中间件 (4KB) +``` + +--- + +## 2. 核心组件 + +### 2.1 UserStore (user.rs) + +```rust +pub struct UserStore { + db: Pool, +} + +impl UserStore { + /// 创建存储 + pub async fn new(db: Pool) -> Result; + + /// 创建用户 + pub async fn create_user(&self, user: &CreateUser) -> Result; + + /// 获取用户 + pub async fn get_user(&self, id: &str) -> Result>; + + /// 按用户名获取 + pub async fn get_by_username(&self, username: &str) -> Result>; + + /// 更新用户 + pub async fn update_user(&self, id: &str, update: &UpdateUser) -> Result<()>; + + /// 删除用户 + pub async fn delete_user(&self, id: &str) -> Result<()>; + + /// 列出用户 + pub async fn list_users(&self) -> Result>; + + /// 验证密码 + pub async fn verify_password(&self, username: &str, password: &str) -> Result>; + + /// 更新密码 + pub async fn update_password(&self, id: &str, new_password: &str) -> Result<()>; + + /// 检查是否需要初始化 + pub async fn needs_setup(&self) -> Result; +} + +pub struct User { + pub id: String, + pub username: String, + pub role: UserRole, + pub created_at: DateTime, +} + +pub enum UserRole { + Admin, + User, +} + +pub struct CreateUser { + pub username: String, + pub password: String, + pub role: UserRole, +} +``` + +### 2.2 SessionStore (session.rs) + +```rust +pub struct SessionStore { + /// 会话映射 + sessions: RwLock>, + + /// 会话超时 + timeout: Duration, +} + +impl SessionStore { + /// 创建存储 + pub fn new(timeout: Duration) -> Self; + + /// 创建会话 + pub fn create_session(&self, user: &User) -> String; + + /// 获取会话 + pub fn get_session(&self, token: &str) -> Option; + + /// 删除会话 + pub fn delete_session(&self, token: &str); + + /// 清理过期会话 + pub fn cleanup_expired(&self); + + /// 刷新会话 + pub fn refresh_session(&self, token: &str) -> bool; +} + +pub struct Session { + pub token: String, + pub user_id: String, + pub username: String, + pub role: UserRole, + pub created_at: Instant, + pub last_active: Instant, +} +``` + +### 2.3 密码哈希 (password.rs) + +```rust +/// 哈希密码 +pub fn hash_password(password: &str) -> Result { + let salt = SaltString::generate(&mut OsRng); + let argon2 = Argon2::default(); + let hash = argon2 + .hash_password(password.as_bytes(), &salt)? + .to_string(); + Ok(hash) +} + +/// 验证密码 +pub fn verify_password(password: &str, hash: &str) -> Result { + let parsed_hash = PasswordHash::new(hash)?; + Ok(Argon2::default() + .verify_password(password.as_bytes(), &parsed_hash) + .is_ok()) +} +``` + +### 2.4 认证中间件 (middleware.rs) + +```rust +pub async fn auth_middleware( + State(state): State>, + cookies: Cookies, + request: Request, + next: Next, +) -> Response { + // 获取 session token + let token = cookies + .get("session_id") + .map(|c| c.value().to_string()); + + // 验证会话 + let session = token + .and_then(|t| state.sessions.get_session(&t)); + + if let Some(session) = session { + // 将用户信息注入请求 + let mut request = request; + request.extensions_mut().insert(session); + next.run(request).await + } else { + StatusCode::UNAUTHORIZED.into_response() + } +} + +pub async fn admin_middleware( + session: Extension, + request: Request, + next: Next, +) -> Response { + if session.role == UserRole::Admin { + next.run(request).await + } else { + StatusCode::FORBIDDEN.into_response() + } +} +``` + +--- + +## 3. API 端点 + +| 端点 | 方法 | 权限 | 描述 | +|------|------|------|------| +| `/api/auth/login` | POST | Public | 登录 | +| `/api/auth/logout` | POST | User | 登出 | +| `/api/auth/check` | GET | User | 检查认证 | +| `/api/auth/password` | POST | User | 修改密码 | +| `/api/users` | GET | Admin | 列出用户 | +| `/api/users` | POST | Admin | 创建用户 | +| `/api/users/:id` | DELETE | Admin | 删除用户 | +| `/api/setup/init` | POST | Public | 初始化设置 | + +### 请求/响应格式 + +```json +// POST /api/auth/login +// Request: +{ + "username": "admin", + "password": "password123" +} + +// Response: +{ + "user": { + "id": "uuid", + "username": "admin", + "role": "admin" + } +} + +// GET /api/auth/check +{ + "authenticated": true, + "user": { + "id": "uuid", + "username": "admin", + "role": "admin" + } +} +``` + +--- + +## 4. 配置 + +```rust +#[derive(Serialize, Deserialize)] +#[typeshare] +pub struct AuthConfig { + /// 会话超时 (秒) + pub session_timeout_secs: u64, + + /// 是否启用认证 + pub enabled: bool, +} + +impl Default for AuthConfig { + fn default() -> Self { + Self { + session_timeout_secs: 86400, // 24 小时 + enabled: true, + } + } +} +``` + +--- + +## 5. 安全特性 + +### 5.1 密码存储 + +- Argon2id 哈希 +- 随机盐值 +- 不可逆 + +### 5.2 会话安全 + +- 随机 token (UUID v4) +- HTTPOnly Cookie +- 会话超时 +- 自动清理 + +### 5.3 权限控制 + +- 两级权限: Admin / User +- 中间件检查 +- 敏感操作需 Admin + +--- + +## 6. 使用示例 + +```rust +// 创建用户 +let user = users.create_user(&CreateUser { + username: "admin".to_string(), + password: "password123".to_string(), + role: UserRole::Admin, +}).await?; + +// 验证密码 +if let Some(user) = users.verify_password("admin", "password123").await? { + // 创建会话 + let token = sessions.create_session(&user); + + // 设置 Cookie + cookies.add(Cookie::build("session_id", token) + .http_only(true) + .path("/") + .finish()); +} + +// 获取会话 +if let Some(session) = sessions.get_session(&token) { + println!("User: {}", session.username); +} +``` + +--- + +## 7. 错误处理 + +```rust +#[derive(Debug, thiserror::Error)] +pub enum AuthError { + #[error("Invalid credentials")] + InvalidCredentials, + + #[error("User not found")] + UserNotFound, + + #[error("User already exists")] + UserExists, + + #[error("Session expired")] + SessionExpired, + + #[error("Permission denied")] + PermissionDenied, + + #[error("Setup required")] + SetupRequired, +} +``` diff --git a/docs/modules/config.md b/docs/modules/config.md new file mode 100644 index 00000000..957be171 --- /dev/null +++ b/docs/modules/config.md @@ -0,0 +1,297 @@ +# Config 模块文档 + +## 1. 模块概述 + +Config 模块提供配置管理功能,所有配置存储在 SQLite 数据库中。 + +### 1.1 主要功能 + +- SQLite 配置存储 +- 类型安全的配置结构 +- 热重载支持 +- TypeScript 类型生成 + +### 1.2 文件结构 + +``` +src/config/ +├── mod.rs # 模块导出 +├── schema.rs # 配置结构定义 (12KB) +└── store.rs # SQLite 存储 (8KB) +``` + +--- + +## 2. 核心组件 + +### 2.1 ConfigStore (store.rs) + +```rust +pub struct ConfigStore { + db: Pool, +} + +impl ConfigStore { + /// 创建存储 + pub async fn new(db_path: &Path) -> Result; + + /// 获取完整配置 + pub async fn get_config(&self) -> Result; + + /// 更新配置 + pub async fn update_config(&self, config: &AppConfig) -> Result<()>; + + /// 获取单个配置项 + pub async fn get(&self, key: &str) -> Result>; + + /// 设置单个配置项 + pub async fn set(&self, key: &str, value: &T) -> Result<()>; + + /// 删除配置项 + pub async fn delete(&self, key: &str) -> Result<()>; + + /// 重置为默认 + pub async fn reset_to_default(&self) -> Result<()>; +} +``` + +### 2.2 AppConfig (schema.rs) + +```rust +#[derive(Serialize, Deserialize, Default)] +#[typeshare] +pub struct AppConfig { + /// 视频配置 + pub video: VideoConfig, + + /// 流配置 + pub stream: StreamConfig, + + /// HID 配置 + pub hid: HidConfig, + + /// MSD 配置 + pub msd: MsdConfig, + + /// ATX 配置 + pub atx: AtxConfig, + + /// 音频配置 + pub audio: AudioConfig, + + /// 认证配置 + pub auth: AuthConfig, + + /// Web 配置 + pub web: WebConfig, + + /// RustDesk 配置 + pub rustdesk: RustDeskConfig, + + /// 扩展配置 + pub extensions: ExtensionsConfig, +} +``` + +### 2.3 各模块配置 + +```rust +#[derive(Serialize, Deserialize)] +#[typeshare] +pub struct VideoConfig { + pub device: Option, + pub format: Option, + pub width: u32, + pub height: u32, + pub fps: u32, + pub quality: u32, +} + +#[derive(Serialize, Deserialize)] +#[typeshare] +pub struct StreamConfig { + pub mode: StreamMode, + pub bitrate_kbps: u32, + pub gop_size: u32, + pub encoder: EncoderType, + pub stun_server: Option, + pub turn_server: Option, + pub turn_username: Option, + pub turn_password: Option, +} + +#[derive(Serialize, Deserialize)] +#[typeshare] +pub struct HidConfig { + pub backend: HidBackendType, + pub ch9329_device: Option, + pub ch9329_baud_rate: Option, + pub default_mouse_mode: MouseMode, +} + +// ... 其他配置结构 +``` + +--- + +## 3. TypeScript 类型生成 + +使用 `#[typeshare]` 属性自动生成 TypeScript 类型: + +```rust +#[derive(Serialize, Deserialize)] +#[typeshare] +pub struct VideoConfig { + pub device: Option, + pub width: u32, + pub height: u32, +} +``` + +生成的 TypeScript: + +```typescript +export interface VideoConfig { + device?: string; + width: number; + height: number; +} +``` + +生成命令: + +```bash +./scripts/generate-types.sh +# 或 +typeshare src --lang=typescript --output-file=web/src/types/generated.ts +``` + +--- + +## 4. API 端点 + +| 端点 | 方法 | 权限 | 描述 | +|------|------|------|------| +| `/api/config` | GET | Admin | 获取完整配置 | +| `/api/config` | PATCH | Admin | 更新配置 | +| `/api/config/video` | GET | Admin | 获取视频配置 | +| `/api/config/video` | PATCH | Admin | 更新视频配置 | +| `/api/config/stream` | GET | Admin | 获取流配置 | +| `/api/config/stream` | PATCH | Admin | 更新流配置 | +| `/api/config/hid` | GET | Admin | 获取 HID 配置 | +| `/api/config/hid` | PATCH | Admin | 更新 HID 配置 | +| `/api/config/reset` | POST | Admin | 重置为默认 | + +### 响应格式 + +```json +// GET /api/config/video +{ + "device": "/dev/video0", + "format": "MJPEG", + "width": 1920, + "height": 1080, + "fps": 30, + "quality": 80 +} + +// PATCH /api/config/video +// Request: +{ + "width": 1280, + "height": 720 +} + +// Response: 更新后的完整配置 +``` + +--- + +## 5. 配置热重载 + +配置更改后自动重载相关组件: + +```rust +// 更新配置 +config_store.update_config(&new_config).await?; + +// 发布配置变更事件 +events.publish(SystemEvent::ConfigChanged { + section: "video".to_string(), +}); + +// 各组件监听事件并重载 +// VideoStreamManager::on_config_changed() +// HidController::reload() +// etc. +``` + +--- + +## 6. 数据库结构 + +```sql +CREATE TABLE IF NOT EXISTS config ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + updated_at TEXT NOT NULL +); +``` + +配置以 JSON 格式存储: + +``` +key: "app_config" +value: { "video": {...}, "hid": {...}, ... } +``` + +--- + +## 7. 使用示例 + +```rust +// 获取配置 +let config = config_store.get_config().await?; +println!("Video device: {:?}", config.video.device); + +// 更新配置 +let mut config = config_store.get_config().await?; +config.video.width = 1280; +config.video.height = 720; +config_store.update_config(&config).await?; + +// 获取单个配置项 +let video: Option = config_store.get("video").await?; + +// 设置单个配置项 +config_store.set("video", &video_config).await?; +``` + +--- + +## 8. 默认配置 + +```rust +impl Default for AppConfig { + fn default() -> Self { + Self { + video: VideoConfig { + device: None, + format: None, + width: 1920, + height: 1080, + fps: 30, + quality: 80, + }, + stream: StreamConfig { + mode: StreamMode::Mjpeg, + bitrate_kbps: 2000, + gop_size: 60, + encoder: EncoderType::H264, + ..Default::default() + }, + // ... + } + } +} +``` diff --git a/docs/modules/events.md b/docs/modules/events.md new file mode 100644 index 00000000..8aa28d0d --- /dev/null +++ b/docs/modules/events.md @@ -0,0 +1,353 @@ +# Events 模块文档 + +## 1. 模块概述 + +Events 模块提供事件总线功能,用于模块间通信和状态广播。 + +### 1.1 主要功能 + +- 事件发布/订阅 +- 多订阅者广播 +- WebSocket 事件推送 +- 状态变更通知 + +### 1.2 文件结构 + +``` +src/events/ +└── mod.rs # EventBus 实现 +``` + +--- + +## 2. 架构设计 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Event System │ +└─────────────────────────────────────────────────────────────────────────────┘ + +┌────────────────┐ ┌────────────────┐ ┌────────────────┐ +│ Video │ │ HID │ │ Audio │ +│ Module │ │ Module │ │ Module │ +└───────┬────────┘ └───────┬────────┘ └───────┬────────┘ + │ │ │ + │ publish() │ publish() │ publish() + └──────────────────┼──────────────────┘ + │ + ▼ + ┌─────────────────────┐ + │ EventBus │ + │ (broadcast channel) │ + └──────────┬──────────┘ + │ + ┌─────────────────┼─────────────────┐ + │ │ │ + │ subscribe() │ subscribe() │ + ▼ ▼ ▼ +┌────────────────┐ ┌────────────────┐ ┌────────────────┐ +│ WebSocket │ │ DeviceInfo │ │ Internal │ +│ Handler │ │ Broadcaster │ │ Tasks │ +└────────────────┘ └────────────────┘ └────────────────┘ +``` + +--- + +## 3. 核心组件 + +### 3.1 EventBus + +```rust +pub struct EventBus { + /// 广播发送器 + tx: broadcast::Sender, +} + +impl EventBus { + /// 创建事件总线 + pub fn new() -> Self { + let (tx, _) = broadcast::channel(1024); + Self { tx } + } + + /// 发布事件 + pub fn publish(&self, event: SystemEvent) { + let _ = self.tx.send(event); + } + + /// 订阅事件 + pub fn subscribe(&self) -> broadcast::Receiver { + self.tx.subscribe() + } +} +``` + +### 3.2 SystemEvent + +```rust +#[derive(Clone, Debug, Serialize)] +pub enum SystemEvent { + // 视频事件 + StreamStateChanged { + state: String, + device: Option, + resolution: Option, + fps: Option, + }, + + VideoDeviceChanged { + added: Vec, + removed: Vec, + }, + + // HID 事件 + HidStateChanged { + backend: String, + initialized: bool, + keyboard_connected: bool, + mouse_connected: bool, + mouse_mode: String, + error: Option, + }, + + // MSD 事件 + MsdStateChanged { + mode: String, + connected: bool, + image: Option, + error: Option, + }, + + MsdDownloadProgress { + download_id: String, + downloaded: u64, + total: u64, + speed: u64, + }, + + // ATX 事件 + AtxStateChanged { + power_on: bool, + last_action: Option, + error: Option, + }, + + // 音频事件 + AudioStateChanged { + enabled: bool, + streaming: bool, + device: Option, + error: Option, + }, + + // 配置事件 + ConfigChanged { + section: String, + }, + + // 设备信息汇总 + DeviceInfo { + video: VideoInfo, + hid: HidInfo, + msd: MsdInfo, + atx: AtxInfo, + audio: AudioInfo, + }, + + // 系统错误 + SystemError { + module: String, + severity: String, + message: String, + }, + + // RustDesk 事件 + RustDeskStatusChanged { + status: String, + device_id: Option, + error: Option, + }, + + RustDeskConnectionOpened { + connection_id: String, + peer_id: String, + }, + + RustDeskConnectionClosed { + connection_id: String, + peer_id: String, + reason: String, + }, +} +``` + +--- + +## 4. 设备信息广播器 + +在 `main.rs` 中启动的后台任务: + +```rust +pub fn spawn_device_info_broadcaster( + state: Arc, + events: Arc, +) -> JoinHandle<()> { + tokio::spawn(async move { + let mut rx = events.subscribe(); + let mut debounce = tokio::time::interval(Duration::from_millis(100)); + let mut pending = false; + + loop { + tokio::select! { + // 收到事件 + result = rx.recv() => { + if result.is_ok() { + pending = true; + } + } + + // 防抖定时器 + _ = debounce.tick() => { + if pending { + pending = false; + // 收集设备信息 + let device_info = state.get_device_info().await; + // 广播 + events.publish(SystemEvent::DeviceInfo(device_info)); + } + } + } + } + }) +} +``` + +--- + +## 5. WebSocket 事件推送 + +```rust +pub async fn ws_handler( + ws: WebSocketUpgrade, + State(state): State>, +) -> impl IntoResponse { + ws.on_upgrade(|socket| handle_ws(socket, state)) +} + +async fn handle_ws(mut socket: WebSocket, state: Arc) { + let mut rx = state.events.subscribe(); + + loop { + tokio::select! { + // 发送事件给客户端 + result = rx.recv() => { + if let Ok(event) = result { + let json = serde_json::to_string(&event).unwrap(); + if socket.send(Message::Text(json)).await.is_err() { + break; + } + } + } + + // 接收客户端消息 + msg = socket.recv() => { + match msg { + Some(Ok(Message::Close(_))) | None => break, + _ => {} + } + } + } + } +} +``` + +--- + +## 6. 使用示例 + +### 6.1 发布事件 + +```rust +// 视频模块发布状态变更 +events.publish(SystemEvent::StreamStateChanged { + state: "streaming".to_string(), + device: Some("/dev/video0".to_string()), + resolution: Some(Resolution { width: 1920, height: 1080 }), + fps: Some(30.0), +}); + +// HID 模块发布状态变更 +events.publish(SystemEvent::HidStateChanged { + backend: "otg".to_string(), + initialized: true, + keyboard_connected: true, + mouse_connected: true, + mouse_mode: "absolute".to_string(), + error: None, +}); +``` + +### 6.2 订阅事件 + +```rust +let mut rx = events.subscribe(); + +loop { + match rx.recv().await { + Ok(SystemEvent::StreamStateChanged { state, .. }) => { + println!("Stream state: {}", state); + } + Ok(SystemEvent::HidStateChanged { backend, .. }) => { + println!("HID backend: {}", backend); + } + Err(_) => break, + } +} +``` + +--- + +## 7. 前端事件处理 + +```typescript +// 连接 WebSocket +const ws = new WebSocket('/api/ws'); + +ws.onmessage = (event) => { + const data = JSON.parse(event.data); + + switch (data.type) { + case 'StreamStateChanged': + updateStreamStatus(data); + break; + case 'HidStateChanged': + updateHidStatus(data); + break; + case 'MsdStateChanged': + updateMsdStatus(data); + break; + case 'DeviceInfo': + updateAllDevices(data); + break; + } +}; +``` + +--- + +## 8. 最佳实践 + +### 8.1 事件粒度 + +- 使用细粒度事件便于精确更新 +- DeviceInfo 用于初始化和定期同步 + +### 8.2 防抖 + +- 使用 100ms 防抖避免事件风暴 +- 合并多个快速变更 + +### 8.3 错误处理 + +- 发布失败静默忽略 (fire-and-forget) +- 订阅者断开自动清理 diff --git a/docs/modules/hid.md b/docs/modules/hid.md new file mode 100644 index 00000000..646f33d3 --- /dev/null +++ b/docs/modules/hid.md @@ -0,0 +1,850 @@ +# HID 模块文档 + +## 1. 模块概述 + +HID (Human Interface Device) 模块负责将键盘和鼠标事件转发到目标计算机,是 One-KVM 实现远程控制的核心模块。 + +### 1.1 主要功能 + +- 键盘事件处理 (按键、修饰键) +- 鼠标事件处理 (移动、点击、滚轮) +- 支持绝对和相对鼠标模式 +- 多后端支持 (OTG、CH9329) +- WebSocket 和 DataChannel 输入 + +### 1.2 文件结构 + +``` +src/hid/ +├── mod.rs # HidController (16KB) +├── backend.rs # 后端抽象 +├── otg.rs # OTG 后端 (33KB) +├── ch9329.rs # CH9329 串口后端 (46KB) +├── keymap.rs # 按键映射 (14KB) +├── types.rs # 类型定义 +├── monitor.rs # 健康监视 (14KB) +├── datachannel.rs # DataChannel 适配 (8KB) +└── websocket.rs # WebSocket 适配 (6KB) +``` + +--- + +## 2. 架构设计 + +### 2.1 整体架构 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ HID Architecture │ +└─────────────────────────────────────────────────────────────────────────────┘ + + Browser Input Events + │ + ┌─────────┴─────────┐ + │ │ + ▼ ▼ +┌─────────────────┐ ┌─────────────────┐ +│ WebSocket │ │ DataChannel │ +│ Handler │ │ Handler │ +│ (websocket.rs) │ │(datachannel.rs) │ +└────────┬────────┘ └────────┬────────┘ + │ │ + └──────────┬─────────┘ + │ + ▼ + ┌─────────────────────┐ + │ HidController │ + │ (mod.rs) │ + │ - send_keyboard() │ + │ - send_mouse() │ + │ - select_backend() │ + └──────────┬──────────┘ + │ + ┌──────────┼──────────┐ + │ │ │ + ▼ ▼ ▼ +┌─────────────┐ ┌──────────┐ ┌──────────┐ +│ OTG Backend│ │ CH9329 │ │ None │ +│ (otg.rs) │ │ Backend │ │ (dummy) │ +└──────┬──────┘ └────┬─────┘ └──────────┘ + │ │ + ▼ ▼ +┌─────────────┐ ┌──────────┐ +│ /dev/hidg* │ │ Serial │ +│ USB Gadget │ │ Port │ +└─────────────┘ └──────────┘ + │ │ + └──────┬──────┘ + │ + ▼ + ┌─────────────┐ + │ Target PC │ + └─────────────┘ +``` + +### 2.2 后端选择 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Backend Selection │ +└─────────────────────────────────────────────────────────────────────────────┘ + +HidBackendType::Otg + │ + ├── 检查 OtgService 是否可用 + │ + ├── 请求 HID 函数 (3个设备) + │ ├── /dev/hidg0 (键盘) + │ ├── /dev/hidg1 (相对鼠标) + │ └── /dev/hidg2 (绝对鼠标) + │ + └── 创建 OtgHidBackend + +HidBackendType::Ch9329 { port, baud_rate } + │ + ├── 打开串口设备 + │ + ├── 初始化 CH9329 芯片 + │ + └── 创建 Ch9329HidBackend + +HidBackendType::None + │ + └── 创建空后端 (丢弃所有事件) +``` + +--- + +## 3. 核心组件 + +### 3.1 HidController (mod.rs) + +HID 控制器主类,统一管理所有 HID 操作。 + +```rust +pub struct HidController { + /// 当前后端 + backend: Arc>>, + + /// 后端类型 + backend_type: Arc>, + + /// OTG 服务引用 + otg_service: Arc, + + /// 健康监视器 + monitor: Arc, + + /// 配置 + config: Arc>, + + /// 事件总线 + events: Arc, + + /// 鼠标模式 + mouse_mode: Arc>, +} + +impl HidController { + /// 初始化控制器 + pub async fn init( + otg_service: Arc, + config: &HidConfig, + events: Arc, + ) -> Result>; + + /// 发送键盘事件 + pub async fn send_keyboard(&self, event: &KeyboardEvent) -> Result<()>; + + /// 发送鼠标事件 + pub async fn send_mouse(&self, event: &MouseEvent) -> Result<()>; + + /// 设置鼠标模式 + pub fn set_mouse_mode(&self, mode: MouseMode); + + /// 获取鼠标模式 + pub fn get_mouse_mode(&self) -> MouseMode; + + /// 重新加载配置 + pub async fn reload(&self, config: &HidConfig) -> Result<()>; + + /// 重置 HID 状态 + pub async fn reset(&self) -> Result<()>; + + /// 获取状态信息 + pub fn info(&self) -> HidInfo; +} + +pub struct HidInfo { + pub backend: String, + pub initialized: bool, + pub keyboard_connected: bool, + pub mouse_connected: bool, + pub mouse_mode: MouseMode, + pub error: Option, +} +``` + +### 3.2 HidBackend Trait (backend.rs) + +```rust +#[async_trait] +pub trait HidBackend: Send + Sync { + /// 发送键盘事件 + async fn send_keyboard(&self, event: &KeyboardEvent) -> Result<()>; + + /// 发送鼠标事件 + async fn send_mouse(&self, event: &MouseEvent, mode: MouseMode) -> Result<()>; + + /// 重置状态 + async fn reset(&self) -> Result<()>; + + /// 获取后端信息 + fn info(&self) -> HidBackendInfo; + + /// 检查连接状态 + fn is_connected(&self) -> bool; +} + +pub struct HidBackendInfo { + pub name: String, + pub backend_type: HidBackendType, + pub keyboard_connected: bool, + pub mouse_connected: bool, +} + +#[derive(Clone, Debug)] +pub enum HidBackendType { + /// USB OTG gadget 模式 + Otg, + + /// CH9329 串口 HID 控制器 + Ch9329 { + port: String, + baud_rate: u32, + }, + + /// 禁用 HID + None, +} +``` + +### 3.3 OTG 后端 (otg.rs) + +通过 Linux USB OTG gadget 模拟 HID 设备。 + +```rust +pub struct OtgHidBackend { + /// HID 设备路径 + paths: HidDevicePaths, + + /// 键盘设备文件 + keyboard_fd: RwLock>, + + /// 相对鼠标设备文件 + mouse_rel_fd: RwLock>, + + /// 绝对鼠标设备文件 + mouse_abs_fd: RwLock>, + + /// 当前键盘状态 + keyboard_state: Mutex, + + /// OTG 服务引用 + otg_service: Arc, +} + +impl OtgHidBackend { + /// 创建 OTG 后端 + pub async fn new(otg_service: Arc) -> Result; + + /// 打开 HID 设备 + async fn open_devices(&self) -> Result<()>; + + /// 关闭 HID 设备 + async fn close_devices(&self); + + /// 写入键盘报告 + fn write_keyboard_report(&self, report: &KeyboardReport) -> Result<()>; + + /// 写入鼠标报告 + fn write_mouse_report(&self, report: &[u8], absolute: bool) -> Result<()>; +} + +pub struct HidDevicePaths { + pub keyboard: PathBuf, // /dev/hidg0 + pub mouse_relative: PathBuf, // /dev/hidg1 + pub mouse_absolute: PathBuf, // /dev/hidg2 +} +``` + +#### HID 报告格式 + +```rust +/// 键盘报告 (8 字节) +#[repr(C, packed)] +pub struct KeyboardReport { + pub modifiers: u8, // Ctrl, Shift, Alt, GUI + pub reserved: u8, // 保留 + pub keys: [u8; 6], // 最多 6 个按键 scancode +} + +/// 相对鼠标报告 (4 字节) +#[repr(C, packed)] +pub struct MouseRelativeReport { + pub buttons: u8, // 按钮状态 + pub x: i8, // X 移动 (-127 ~ 127) + pub y: i8, // Y 移动 (-127 ~ 127) + pub wheel: i8, // 滚轮 (-127 ~ 127) +} + +/// 绝对鼠标报告 (6 字节) +#[repr(C, packed)] +pub struct MouseAbsoluteReport { + pub buttons: u8, // 按钮状态 + pub x: u16, // X 坐标 (0 ~ 32767) + pub y: u16, // Y 坐标 (0 ~ 32767) + pub wheel: i8, // 滚轮 (-127 ~ 127) +} +``` + +### 3.4 CH9329 后端 (ch9329.rs) + +通过 CH9329 芯片(串口转 HID)实现 HID 功能。 + +```rust +pub struct Ch9329HidBackend { + /// 串口设备 + port: Mutex>, + + /// 设备路径 + device_path: String, + + /// 波特率 + baud_rate: u32, + + /// 当前键盘状态 + keyboard_state: Mutex, + + /// 连接状态 + connected: AtomicBool, +} + +impl Ch9329HidBackend { + /// 创建 CH9329 后端 + pub fn new(device: &str, baud_rate: u32) -> Result; + + /// 发送命令 + fn send_command(&self, cmd: &[u8]) -> Result>; + + /// 发送键盘数据包 + fn send_keyboard_packet(&self, report: &KeyboardReport) -> Result<()>; + + /// 发送鼠标数据包 + fn send_mouse_packet(&self, report: &[u8], absolute: bool) -> Result<()>; +} +``` + +#### CH9329 协议 + +``` +帧格式: +┌──────┬──────┬──────┬──────────┬──────────┬──────┐ +│ HEAD │ ADDR │ CMD │ LEN │ DATA │ SUM │ +│ 0x57 │ 0xAB │ 0xXX │ data_len │ payload │ csum │ +└──────┴──────┴──────┴──────────┴──────────┴──────┘ + +命令码: +0x02 - 发送键盘数据 +0x04 - 发送绝对鼠标数据 +0x05 - 发送相对鼠标数据 +0x0E - 获取芯片信息 +``` + +--- + +## 4. 事件类型 + +### 4.1 键盘事件 (types.rs) + +```rust +pub struct KeyboardEvent { + /// 按下的键列表 + pub keys: Vec, + + /// 修饰键状态 + pub modifiers: KeyboardModifiers, +} + +#[derive(Default)] +pub struct KeyboardModifiers { + pub left_ctrl: bool, + pub left_shift: bool, + pub left_alt: bool, + pub left_gui: bool, + pub right_ctrl: bool, + pub right_shift: bool, + pub right_alt: bool, + pub right_gui: bool, +} + +impl KeyboardModifiers { + /// 转换为 USB HID 修饰符字节 + pub fn to_byte(&self) -> u8 { + let mut byte = 0u8; + if self.left_ctrl { byte |= 0x01; } + if self.left_shift { byte |= 0x02; } + if self.left_alt { byte |= 0x04; } + if self.left_gui { byte |= 0x08; } + if self.right_ctrl { byte |= 0x10; } + if self.right_shift { byte |= 0x20; } + if self.right_alt { byte |= 0x40; } + if self.right_gui { byte |= 0x80; } + byte + } +} +``` + +### 4.2 鼠标事件 (types.rs) + +```rust +pub struct MouseEvent { + /// 按钮 + pub button: Option, + + /// 事件类型 + pub event_type: MouseEventType, + + /// 相对移动 X + pub dx: i16, + + /// 相对移动 Y + pub dy: i16, + + /// 绝对位置 X (0-32767) + pub x: u32, + + /// 绝对位置 Y (0-32767) + pub y: u32, + + /// 滚轮移动 + pub wheel: i8, +} + +pub enum MouseButton { + Left, + Right, + Middle, + Button4, + Button5, +} + +pub enum MouseEventType { + Press, + Release, + Move, + Wheel, +} + +pub enum MouseMode { + /// 相对模式 (用于普通操作) + Relative, + + /// 绝对模式 (用于 BIOS/精确定位) + Absolute, +} +``` + +--- + +## 5. 按键映射 + +### 5.1 KeyCode 枚举 (keymap.rs) + +```rust +pub enum KeyCode { + // 字母键 + KeyA, KeyB, KeyC, /* ... */ KeyZ, + + // 数字键 + Digit1, Digit2, /* ... */ Digit0, + + // 功能键 + F1, F2, /* ... */ F12, + + // 控制键 + Escape, Tab, CapsLock, Space, Enter, Backspace, + Insert, Delete, Home, End, PageUp, PageDown, + + // 方向键 + ArrowUp, ArrowDown, ArrowLeft, ArrowRight, + + // 修饰键 + ShiftLeft, ShiftRight, + ControlLeft, ControlRight, + AltLeft, AltRight, + MetaLeft, MetaRight, + + // 小键盘 + Numpad0, Numpad1, /* ... */ Numpad9, + NumpadAdd, NumpadSubtract, NumpadMultiply, NumpadDivide, + NumpadEnter, NumpadDecimal, NumLock, + + // 其他 + PrintScreen, ScrollLock, Pause, + /* ... */ +} + +impl KeyCode { + /// 转换为 USB HID scancode + pub fn to_scancode(&self) -> u8; + + /// 从 JavaScript keyCode 转换 + pub fn from_js_code(code: &str) -> Option; + + /// 是否为修饰键 + pub fn is_modifier(&self) -> bool; +} +``` + +### 5.2 JavaScript 键码映射 + +```javascript +// 前端发送的格式 +{ + "type": "keyboard", + "keys": ["KeyA", "KeyB"], + "modifiers": { + "ctrl": false, + "shift": true, + "alt": false, + "meta": false + } +} +``` + +--- + +## 6. 输入处理器 + +### 6.1 WebSocket Handler (websocket.rs) + +```rust +pub struct WsHidHandler { + hid: Arc, +} + +impl WsHidHandler { + pub fn new(hid: Arc) -> Self; + + /// 处理 WebSocket 消息 + pub async fn handle_message(&self, msg: &str) -> Result<()> { + let event: HidMessage = serde_json::from_str(msg)?; + + match event { + HidMessage::Keyboard(kb) => { + self.hid.send_keyboard(&kb).await?; + } + HidMessage::Mouse(mouse) => { + self.hid.send_mouse(&mouse).await?; + } + HidMessage::SetMouseMode(mode) => { + self.hid.set_mouse_mode(mode); + } + } + + Ok(()) + } +} + +#[derive(Deserialize)] +#[serde(tag = "type")] +pub enum HidMessage { + #[serde(rename = "keyboard")] + Keyboard(KeyboardEvent), + + #[serde(rename = "mouse")] + Mouse(MouseEvent), + + #[serde(rename = "mouse_mode")] + SetMouseMode(MouseMode), +} +``` + +### 6.2 DataChannel Handler (datachannel.rs) + +用于 WebRTC 模式下的 HID 事件处理。 + +```rust +pub struct HidDataChannelHandler { + hid: Arc, +} + +impl HidDataChannelHandler { + pub fn new(hid: Arc) -> Self; + + /// 处理 DataChannel 消息 + pub async fn handle_message(&self, data: &[u8]) -> Result<()>; + + /// 创建 DataChannel 配置 + pub fn datachannel_config() -> RTCDataChannelInit; +} +``` + +--- + +## 7. 健康监视 + +### 7.1 HidHealthMonitor (monitor.rs) + +```rust +pub struct HidHealthMonitor { + /// 错误计数 + error_count: AtomicU32, + + /// 连续错误计数 + consecutive_errors: AtomicU32, + + /// 最后错误时间 + last_error: RwLock>, + + /// 最后错误消息 + last_error_msg: RwLock>, + + /// 重试配置 + config: MonitorConfig, +} + +impl HidHealthMonitor { + /// 记录错误 + pub fn record_error(&self, error: &str); + + /// 记录成功 + pub fn record_success(&self); + + /// 是否应该重试 + pub fn should_retry(&self) -> bool; + + /// 是否需要重新初始化 + pub fn needs_reinit(&self) -> bool; + + /// 获取健康状态 + pub fn health_status(&self) -> HealthStatus; +} + +pub enum HealthStatus { + Healthy, + Degraded { error_rate: f32 }, + Unhealthy { consecutive_errors: u32 }, +} +``` + +--- + +## 8. 配置 + +### 8.1 HID 配置结构 + +```rust +#[derive(Serialize, Deserialize)] +#[typeshare] +pub struct HidConfig { + /// 后端类型 + pub backend: HidBackendType, + + /// CH9329 设备路径 (如果使用 CH9329) + pub ch9329_device: Option, + + /// CH9329 波特率 + pub ch9329_baud_rate: Option, + + /// 默认鼠标模式 + pub default_mouse_mode: MouseMode, + + /// 鼠标灵敏度 (1-10) + pub mouse_sensitivity: u8, + + /// 启用滚轮 + pub enable_wheel: bool, +} + +impl Default for HidConfig { + fn default() -> Self { + Self { + backend: HidBackendType::Otg, + ch9329_device: None, + ch9329_baud_rate: Some(9600), + default_mouse_mode: MouseMode::Absolute, + mouse_sensitivity: 5, + enable_wheel: true, + } + } +} +``` + +--- + +## 9. API 端点 + +| 端点 | 方法 | 描述 | +|------|------|------| +| `/api/hid/status` | GET | 获取 HID 状态 | +| `/api/hid/reset` | POST | 重置 HID 状态 | +| `/api/hid/keyboard` | POST | 发送键盘事件 | +| `/api/hid/mouse` | POST | 发送鼠标事件 | +| `/api/hid/mouse/mode` | GET | 获取鼠标模式 | +| `/api/hid/mouse/mode` | POST | 设置鼠标模式 | + +### 响应格式 + +```json +// GET /api/hid/status +{ + "backend": "otg", + "initialized": true, + "keyboard_connected": true, + "mouse_connected": true, + "mouse_mode": "absolute", + "error": null +} +``` + +--- + +## 10. 事件 + +```rust +pub enum SystemEvent { + HidStateChanged { + backend: String, + initialized: bool, + keyboard_connected: bool, + mouse_connected: bool, + mouse_mode: String, + error: Option, + }, +} +``` + +--- + +## 11. 错误处理 + +```rust +#[derive(Debug, thiserror::Error)] +pub enum HidError { + #[error("Backend not initialized")] + NotInitialized, + + #[error("Device not found: {0}")] + DeviceNotFound(String), + + #[error("Device busy: {0}")] + DeviceBusy(String), + + #[error("Write error: {0}")] + WriteError(String), + + #[error("Serial port error: {0}")] + SerialError(String), + + #[error("Invalid key code: {0}")] + InvalidKeyCode(String), + + #[error("OTG service error: {0}")] + OtgError(String), +} +``` + +--- + +## 12. 使用示例 + +### 12.1 初始化 HID 控制器 + +```rust +let otg_service = Arc::new(OtgService::new()?); +let events = Arc::new(EventBus::new()); + +let hid = HidController::init( + otg_service, + &HidConfig::default(), + events, +).await?; +``` + +### 12.2 发送键盘事件 + +```rust +// 按下 Ctrl+C +hid.send_keyboard(&KeyboardEvent { + keys: vec![KeyCode::KeyC], + modifiers: KeyboardModifiers { + left_ctrl: true, + ..Default::default() + }, +}).await?; + +// 释放所有键 +hid.send_keyboard(&KeyboardEvent { + keys: vec![], + modifiers: KeyboardModifiers::default(), +}).await?; +``` + +### 12.3 发送鼠标事件 + +```rust +// 移动鼠标到绝对位置 +hid.send_mouse(&MouseEvent { + button: None, + event_type: MouseEventType::Move, + dx: 0, + dy: 0, + x: 16384, // 屏幕中心 + y: 16384, + wheel: 0, +}).await?; + +// 点击左键 +hid.send_mouse(&MouseEvent { + button: Some(MouseButton::Left), + event_type: MouseEventType::Press, + ..Default::default() +}).await?; + +hid.send_mouse(&MouseEvent { + button: Some(MouseButton::Left), + event_type: MouseEventType::Release, + ..Default::default() +}).await?; +``` + +--- + +## 13. 常见问题 + +### Q: OTG 模式下键盘/鼠标不工作? + +1. 检查 `/dev/hidg*` 设备是否存在 +2. 检查 USB gadget 是否正确配置 +3. 检查目标 PC 是否识别 USB 设备 +4. 查看 `dmesg` 日志 + +### Q: CH9329 无法初始化? + +1. 检查串口设备路径 +2. 检查波特率设置 +3. 使用 `minicom` 测试串口连接 + +### Q: 鼠标定位不准确? + +1. 使用绝对鼠标模式 +2. 校准屏幕分辨率 +3. 检查缩放设置 + +### Q: 按键有延迟? + +1. 检查网络延迟 +2. 使用 WebRTC 模式 +3. 减少中间代理 diff --git a/docs/modules/msd.md b/docs/modules/msd.md new file mode 100644 index 00000000..bacec741 --- /dev/null +++ b/docs/modules/msd.md @@ -0,0 +1,617 @@ +# MSD 模块文档 + +## 1. 模块概述 + +MSD (Mass Storage Device) 模块提供虚拟存储设备功能,允许将 ISO/IMG 镜像作为 USB 存储设备挂载到目标计算机。 + +### 1.1 主要功能 + +- ISO/IMG 镜像挂载 +- 镜像下载管理 +- Ventoy 多 ISO 启动盘 +- 热插拔支持 +- 下载进度追踪 + +### 1.2 文件结构 + +``` +src/msd/ +├── mod.rs # 模块导出 +├── controller.rs # MsdController (20KB) +├── image.rs # 镜像管理 (21KB) +├── ventoy_drive.rs # Ventoy 驱动 (24KB) +├── monitor.rs # 健康监视 (9KB) +└── types.rs # 类型定义 (6KB) +``` + +--- + +## 2. 架构设计 + +### 2.1 整体架构 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ MSD Architecture │ +└─────────────────────────────────────────────────────────────────────────────┘ + + Web API + │ + ▼ + ┌─────────────────┐ + │ MsdController │ + │ (controller.rs) │ + └────────┬────────┘ + │ + ┌─────────────┼─────────────┐ + │ │ │ + ▼ ▼ ▼ +┌─────────────┐ ┌───────────┐ ┌───────────┐ +│ Image │ │ Ventoy │ │ OTG │ +│ Manager │ │ Drive │ │ Service │ +│ (image.rs) │ │(ventoy.rs)│ │ │ +└──────┬──────┘ └─────┬─────┘ └─────┬─────┘ + │ │ │ + ▼ ▼ ▼ +┌─────────────┐ ┌───────────┐ ┌───────────┐ +│ /data/ │ │ exFAT │ │ MSD │ +│ images/ │ │ Drive │ │ Function │ +└─────────────┘ └───────────┘ └───────────┘ + │ + ▼ + ┌───────────────┐ + │ Target PC │ + │ (USB Drive) │ + └───────────────┘ +``` + +### 2.2 MSD 模式 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ MSD Modes │ +└─────────────────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Image Mode │ +│ ┌───────────┐ │ +│ │ ISO/IMG │ ──► MSD LUN ──► Target PC sees single drive │ +│ │ File │ │ +│ └───────────┘ │ +│ 特点: │ +│ - 单个镜像文件 │ +│ - 直接挂载 │ +│ - 适合系统安装 │ +└─────────────────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Ventoy Mode │ +│ ┌───────────┐ │ +│ │ ISO 1 │ │ +│ ├───────────┤ ┌───────────┐ │ +│ │ ISO 2 │ ──► │ Ventoy │ ──► Target PC sees bootable drive │ +│ ├───────────┤ │ Drive │ with ISO selection menu │ +│ │ ISO 3 │ └───────────┘ │ +│ └───────────┘ │ +│ 特点: │ +│ - 多个 ISO 文件 │ +│ - exFAT 文件系统 │ +│ - 启动菜单选择 │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 3. 核心组件 + +### 3.1 MsdController (controller.rs) + +MSD 控制器主类。 + +```rust +pub struct MsdController { + /// 当前状态 + state: Arc>, + + /// 镜像管理器 + image_manager: Arc, + + /// Ventoy 驱动器 + ventoy_drive: Arc>>, + + /// OTG 服务 + otg_service: Arc, + + /// MSD 函数句柄 + msd_function: Arc>>, + + /// 事件总线 + events: Arc, + + /// 数据目录 + data_dir: PathBuf, +} + +impl MsdController { + /// 创建控制器 + pub async fn new( + otg_service: Arc, + data_dir: PathBuf, + events: Arc, + ) -> Result>; + + /// 获取状态 + pub fn state(&self) -> MsdState; + + /// 连接 MSD + pub async fn connect(&self) -> Result<()>; + + /// 断开 MSD + pub async fn disconnect(&self) -> Result<()>; + + /// 切换到镜像模式 + pub async fn set_image(&self, image_id: &str) -> Result<()>; + + /// 切换到 Ventoy 模式 + pub async fn set_ventoy(&self) -> Result<()>; + + /// 清除当前挂载 + pub async fn clear(&self) -> Result<()>; + + /// 列出镜像 + pub fn list_images(&self) -> Vec; + + /// 上传镜像 + pub async fn upload_image(&self, name: &str, data: Bytes) -> Result; + + /// 从 URL 下载镜像 + pub async fn download_image(&self, url: &str) -> Result; + + /// 删除镜像 + pub async fn delete_image(&self, image_id: &str) -> Result<()>; + + /// 获取下载进度 + pub fn get_download_progress(&self, download_id: &str) -> Option; +} + +pub struct MsdState { + /// 是否可用 + pub available: bool, + + /// 当前模式 + pub mode: MsdMode, + + /// 是否已连接 + pub connected: bool, + + /// 当前镜像信息 + pub current_image: Option, + + /// 驱动器信息 + pub drive_info: Option, + + /// 错误信息 + pub error: Option, +} + +pub enum MsdMode { + /// 未激活 + None, + + /// 单镜像模式 + Image, + + /// Ventoy 模式 + Drive, +} +``` + +### 3.2 ImageManager (image.rs) + +镜像文件管理器。 + +```rust +pub struct ImageManager { + /// 镜像目录 + images_dir: PathBuf, + + /// 镜像列表缓存 + images: RwLock>, + + /// 下载任务 + downloads: RwLock>, + + /// HTTP 客户端 + http_client: reqwest::Client, +} + +impl ImageManager { + /// 创建管理器 + pub fn new(images_dir: PathBuf) -> Result; + + /// 扫描镜像目录 + pub fn scan_images(&self) -> Result>; + + /// 获取镜像信息 + pub fn get_image(&self, id: &str) -> Option; + + /// 添加镜像 + pub async fn add_image(&self, name: &str, data: Bytes) -> Result; + + /// 删除镜像 + pub fn delete_image(&self, id: &str) -> Result<()>; + + /// 开始下载 + pub async fn start_download(&self, url: &str) -> Result; + + /// 取消下载 + pub fn cancel_download(&self, download_id: &str) -> Result<()>; + + /// 获取下载进度 + pub fn get_download_progress(&self, download_id: &str) -> Option; + + /// 验证镜像文件 + fn validate_image(path: &Path) -> Result; +} + +pub struct ImageInfo { + /// 唯一 ID + pub id: String, + + /// 文件名 + pub name: String, + + /// 文件大小 + pub size: u64, + + /// 格式 + pub format: ImageFormat, + + /// 创建时间 + pub created_at: DateTime, + + /// 下载状态 + pub download_status: Option, +} + +pub enum ImageFormat { + /// ISO 光盘镜像 + Iso, + + /// 原始磁盘镜像 + Img, + + /// 未知格式 + Unknown, +} + +pub struct DownloadProgress { + /// 已下载字节 + pub downloaded: u64, + + /// 总字节数 + pub total: u64, + + /// 下载速度 (bytes/sec) + pub speed: u64, + + /// 预计剩余时间 + pub eta_secs: u64, + + /// 状态 + pub status: DownloadStatus, +} + +pub enum DownloadStatus { + Pending, + Downloading, + Completed, + Failed(String), + Cancelled, +} +``` + +### 3.3 VentoyDrive (ventoy_drive.rs) + +Ventoy 可启动驱动器管理。 + +```rust +pub struct VentoyDrive { + /// 驱动器路径 + drive_path: PathBuf, + + /// 镜像路径 + images: Vec, + + /// 容量 + capacity: u64, + + /// 已用空间 + used: u64, +} + +impl VentoyDrive { + /// 创建 Ventoy 驱动器 + pub fn create(drive_path: PathBuf, capacity: u64) -> Result; + + /// 添加 ISO + pub fn add_iso(&mut self, iso_path: &Path) -> Result<()>; + + /// 移除 ISO + pub fn remove_iso(&mut self, name: &str) -> Result<()>; + + /// 列出 ISO + pub fn list_isos(&self) -> Vec; + + /// 获取驱动器信息 + pub fn info(&self) -> DriveInfo; + + /// 获取驱动器路径 + pub fn path(&self) -> &Path; +} + +pub struct DriveInfo { + /// 容量 + pub capacity: u64, + + /// 已用空间 + pub used: u64, + + /// 可用空间 + pub available: u64, + + /// ISO 列表 + pub isos: Vec, +} +``` + +--- + +## 4. 类型定义 + +### 4.1 MSD 配置 + +```rust +#[derive(Serialize, Deserialize)] +#[typeshare] +pub struct MsdConfig { + /// 是否启用 MSD + pub enabled: bool, + + /// 镜像目录 + pub images_dir: Option, + + /// 默认模式 + pub default_mode: MsdMode, + + /// Ventoy 容量 (MB) + pub ventoy_capacity_mb: u32, +} + +impl Default for MsdConfig { + fn default() -> Self { + Self { + enabled: true, + images_dir: None, + default_mode: MsdMode::None, + ventoy_capacity_mb: 4096, // 4GB + } + } +} +``` + +--- + +## 5. API 端点 + +| 端点 | 方法 | 描述 | +|------|------|------| +| `/api/msd/status` | GET | 获取 MSD 状态 | +| `/api/msd/connect` | POST | 连接 MSD | +| `/api/msd/disconnect` | POST | 断开 MSD | +| `/api/msd/images` | GET | 列出镜像 | +| `/api/msd/images` | POST | 上传镜像 | +| `/api/msd/images/:id` | DELETE | 删除镜像 | +| `/api/msd/images/download` | POST | 从 URL 下载 | +| `/api/msd/images/download/:id` | GET | 获取下载进度 | +| `/api/msd/images/download/:id` | DELETE | 取消下载 | +| `/api/msd/set-image` | POST | 设置当前镜像 | +| `/api/msd/set-ventoy` | POST | 设置 Ventoy 模式 | +| `/api/msd/clear` | POST | 清除挂载 | + +### 响应格式 + +```json +// GET /api/msd/status +{ + "available": true, + "mode": "image", + "connected": true, + "current_image": { + "id": "abc123", + "name": "ubuntu-22.04.iso", + "size": 4700000000, + "format": "iso" + }, + "drive_info": null, + "error": null +} + +// GET /api/msd/images +{ + "images": [ + { + "id": "abc123", + "name": "ubuntu-22.04.iso", + "size": 4700000000, + "format": "iso", + "created_at": "2024-01-15T10:30:00Z" + } + ] +} + +// POST /api/msd/images/download +// Request: { "url": "https://example.com/image.iso" } +// Response: { "download_id": "xyz789" } + +// GET /api/msd/images/download/xyz789 +{ + "downloaded": 1234567890, + "total": 4700000000, + "speed": 12345678, + "eta_secs": 280, + "status": "downloading" +} +``` + +--- + +## 6. 事件 + +```rust +pub enum SystemEvent { + MsdStateChanged { + mode: MsdMode, + connected: bool, + image: Option, + error: Option, + }, + + MsdDownloadProgress { + download_id: String, + progress: DownloadProgress, + }, + + MsdDownloadComplete { + download_id: String, + image_id: String, + success: bool, + error: Option, + }, +} +``` + +--- + +## 7. 错误处理 + +```rust +#[derive(Debug, thiserror::Error)] +pub enum MsdError { + #[error("MSD not available")] + NotAvailable, + + #[error("Already connected")] + AlreadyConnected, + + #[error("Not connected")] + NotConnected, + + #[error("Image not found: {0}")] + ImageNotFound(String), + + #[error("Invalid image format: {0}")] + InvalidFormat(String), + + #[error("Download failed: {0}")] + DownloadFailed(String), + + #[error("Storage full")] + StorageFull, + + #[error("OTG error: {0}")] + OtgError(String), + + #[error("IO error: {0}")] + IoError(#[from] std::io::Error), +} +``` + +--- + +## 8. 使用示例 + +### 8.1 挂载 ISO 镜像 + +```rust +let msd = MsdController::new(otg_service, data_dir, events).await?; + +// 列出镜像 +let images = msd.list_images(); +println!("Available images: {:?}", images); + +// 设置镜像 +msd.set_image("abc123").await?; + +// 连接到目标 PC +msd.connect().await?; + +// 目标 PC 现在可以看到 USB 驱动器... + +// 断开连接 +msd.disconnect().await?; +``` + +### 8.2 从 URL 下载 + +```rust +// 开始下载 +let download_id = msd.download_image("https://example.com/ubuntu.iso").await?; + +// 监控进度 +loop { + if let Some(progress) = msd.get_download_progress(&download_id) { + println!("Progress: {}%", progress.downloaded * 100 / progress.total); + + if matches!(progress.status, DownloadStatus::Completed) { + break; + } + } + tokio::time::sleep(Duration::from_secs(1)).await; +} +``` + +### 8.3 使用 Ventoy 模式 + +```rust +// 切换到 Ventoy 模式 +msd.set_ventoy().await?; + +// 获取驱动器信息 +let state = msd.state(); +if let Some(drive_info) = state.drive_info { + println!("Capacity: {} MB", drive_info.capacity / 1024 / 1024); + println!("ISOs: {:?}", drive_info.isos); +} + +// 连接 +msd.connect().await?; +``` + +--- + +## 9. 常见问题 + +### Q: 镜像无法挂载? + +1. 检查镜像文件完整性 +2. 确认文件格式正确 +3. 检查存储空间 + +### Q: 目标 PC 不识别? + +1. 检查 USB 连接 +2. 尝试重新连接 +3. 查看目标 PC 的设备管理器 + +### Q: 下载速度慢? + +1. 检查网络连接 +2. 使用更近的镜像源 +3. 检查磁盘 I/O + +### Q: Ventoy 启动失败? + +1. 检查目标 PC BIOS 设置 +2. 尝试不同的启动模式 +3. 确认 ISO 文件支持 Ventoy diff --git a/docs/modules/otg.md b/docs/modules/otg.md new file mode 100644 index 00000000..e5e1a2a3 --- /dev/null +++ b/docs/modules/otg.md @@ -0,0 +1,667 @@ +# OTG 模块文档 + +## 1. 模块概述 + +OTG (On-The-Go) 模块负责管理 Linux USB Gadget,为 HID 和 MSD 功能提供统一的 USB 设备管理。 + +### 1.1 主要功能 + +- USB Gadget 生命周期管理 +- HID 函数配置 (键盘、鼠标) +- MSD 函数配置 (虚拟存储) +- ConfigFS 操作 +- UDC 绑定/解绑 + +### 1.2 文件结构 + +``` +src/otg/ +├── mod.rs # 模块导出 +├── service.rs # OtgService (17KB) +├── manager.rs # OtgGadgetManager (12KB) +├── hid.rs # HID Function (7KB) +├── msd.rs # MSD Function (14KB) +├── configfs.rs # ConfigFS 操作 (4KB) +├── endpoint.rs # 端点分配 (2KB) +└── report_desc.rs # HID 报告描述符 (6KB) +``` + +--- + +## 2. 架构设计 + +### 2.1 设计目标 + +解决 HID 和 MSD 共享同一个 USB Gadget 的所有权问题: + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ OTG Ownership Model │ +└─────────────────────────────────────────────────────────────────────────────┘ + + ┌─────────────────┐ + │ OtgService │ ◄── 唯一所有者 + │ (service.rs) │ + └────────┬────────┘ + │ + ┌─────────────┼─────────────┐ + │ │ │ + ▼ ▼ ▼ + enable_hid() enable_msd() 状态查询 + │ │ + └──────┬──────┘ + │ + ▼ + ┌─────────────────┐ + │OtgGadgetManager │ + │ (manager.rs) │ + └────────┬────────┘ + │ + ┌─────────────┼─────────────┐ + │ │ │ + ▼ ▼ ▼ +┌───────┐ ┌───────┐ ┌───────┐ +│ HID │ │ MSD │ │ UDC │ +│ Func │ │ Func │ │ Bind │ +└───────┘ └───────┘ └───────┘ +``` + +### 2.2 ConfigFS 结构 + +``` +/sys/kernel/config/usb_gadget/one-kvm/ +├── idVendor # 0x05ac (Apple) +├── idProduct # 0x0001 +├── bcdDevice # 0x0100 +├── bcdUSB # 0x0200 +├── bMaxPacketSize0 # 64 +│ +├── strings/ +│ └── 0x409/ # English +│ ├── manufacturer # "One-KVM" +│ ├── product # "KVM Device" +│ └── serialnumber # UUID +│ +├── configs/ +│ └── c.1/ +│ ├── MaxPower # 500 +│ ├── strings/ +│ │ └── 0x409/ +│ │ └── configuration # "Config 1" +│ └── (function symlinks) +│ +├── functions/ +│ ├── hid.usb0/ # 键盘 +│ │ ├── protocol # 1 (keyboard) +│ │ ├── subclass # 1 (boot) +│ │ ├── report_length # 8 +│ │ └── report_desc # (binary) +│ │ +│ ├── hid.usb1/ # 相对鼠标 +│ │ ├── protocol # 2 (mouse) +│ │ ├── subclass # 1 (boot) +│ │ ├── report_length # 4 +│ │ └── report_desc # (binary) +│ │ +│ ├── hid.usb2/ # 绝对鼠标 +│ │ ├── protocol # 2 (mouse) +│ │ ├── subclass # 0 (none) +│ │ ├── report_length # 6 +│ │ └── report_desc # (binary) +│ │ +│ └── mass_storage.usb0/ # 虚拟存储 +│ ├── stall # 1 +│ └── lun.0/ +│ ├── cdrom # 1 (ISO mode) +│ ├── ro # 1 (read-only) +│ ├── removable # 1 +│ ├── nofua # 1 +│ └── file # /path/to/image.iso +│ +└── UDC # UDC 设备名 +``` + +--- + +## 3. 核心组件 + +### 3.1 OtgService (service.rs) + +OTG 服务主类,提供统一的 USB Gadget 管理接口。 + +```rust +pub struct OtgService { + /// Gadget 管理器 + manager: Arc>, + + /// 当前状态 + state: Arc>, + + /// HID 函数句柄 + hid_function: Arc>>, + + /// MSD 函数句柄 + msd_function: Arc>>, + + /// 请求计数器 (lock-free) + pending_requests: AtomicU8, +} + +impl OtgService { + /// 创建服务 + pub fn new() -> Result; + + /// 启用 HID 功能 + pub async fn enable_hid(&self) -> Result; + + /// 禁用 HID 功能 + pub async fn disable_hid(&self) -> Result<()>; + + /// 启用 MSD 功能 + pub async fn enable_msd(&self) -> Result; + + /// 禁用 MSD 功能 + pub async fn disable_msd(&self) -> Result<()>; + + /// 获取状态 + pub fn state(&self) -> OtgServiceState; + + /// 检查 HID 是否启用 + pub fn is_hid_enabled(&self) -> bool; + + /// 检查 MSD 是否启用 + pub fn is_msd_enabled(&self) -> bool; +} + +pub struct OtgServiceState { + /// Gadget 是否激活 + pub gadget_active: bool, + + /// HID 是否启用 + pub hid_enabled: bool, + + /// MSD 是否启用 + pub msd_enabled: bool, + + /// HID 设备路径 + pub hid_paths: Option, + + /// 错误信息 + pub error: Option, +} + +pub struct HidDevicePaths { + pub keyboard: PathBuf, // /dev/hidg0 + pub mouse_relative: PathBuf, // /dev/hidg1 + pub mouse_absolute: PathBuf, // /dev/hidg2 +} +``` + +### 3.2 OtgGadgetManager (manager.rs) + +Gadget 生命周期管理器。 + +```rust +pub struct OtgGadgetManager { + /// Gadget 路径 + gadget_path: PathBuf, + + /// UDC 设备名 + udc_name: Option, + + /// 是否已创建 + created: bool, + + /// 是否已绑定 + bound: bool, + + /// 端点分配器 + endpoint_allocator: EndpointAllocator, +} + +impl OtgGadgetManager { + /// 创建管理器 + pub fn new() -> Result; + + /// 创建 Gadget + pub fn create_gadget(&mut self, config: &GadgetConfig) -> Result<()>; + + /// 销毁 Gadget + pub fn destroy_gadget(&mut self) -> Result<()>; + + /// 绑定 UDC + pub fn bind_udc(&mut self) -> Result<()>; + + /// 解绑 UDC + pub fn unbind_udc(&mut self) -> Result<()>; + + /// 添加函数 + pub fn add_function(&mut self, func: &dyn GadgetFunction) -> Result<()>; + + /// 移除函数 + pub fn remove_function(&mut self, func: &dyn GadgetFunction) -> Result<()>; + + /// 链接函数到配置 + pub fn link_function(&self, func: &dyn GadgetFunction) -> Result<()>; + + /// 取消链接函数 + pub fn unlink_function(&self, func: &dyn GadgetFunction) -> Result<()>; + + /// 检测可用 UDC + fn detect_udc() -> Result; +} + +pub struct GadgetConfig { + pub name: String, // "one-kvm" + pub vendor_id: u16, // 0x05ac + pub product_id: u16, // 0x0001 + pub manufacturer: String, // "One-KVM" + pub product: String, // "KVM Device" + pub serial: String, // UUID +} +``` + +### 3.3 HID Function (hid.rs) + +```rust +pub struct HidFunction { + /// 键盘函数 + keyboard: HidFunctionConfig, + + /// 相对鼠标函数 + mouse_relative: HidFunctionConfig, + + /// 绝对鼠标函数 + mouse_absolute: HidFunctionConfig, +} + +pub struct HidFunctionConfig { + /// 函数名 + pub name: String, // "hid.usb0" + + /// 协议 + pub protocol: u8, // 1=keyboard, 2=mouse + + /// 子类 + pub subclass: u8, // 1=boot, 0=none + + /// 报告长度 + pub report_length: u8, + + /// 报告描述符 + pub report_desc: Vec, +} + +impl HidFunction { + /// 创建 HID 函数 + pub fn new() -> Self; + + /// 获取键盘报告描述符 + pub fn keyboard_report_desc() -> Vec; + + /// 获取相对鼠标报告描述符 + pub fn mouse_relative_report_desc() -> Vec; + + /// 获取绝对鼠标报告描述符 + pub fn mouse_absolute_report_desc() -> Vec; +} + +impl GadgetFunction for HidFunction { + fn name(&self) -> &str; + fn function_type(&self) -> &str; // "hid" + fn configure(&self, path: &Path) -> Result<()>; +} +``` + +### 3.4 MSD Function (msd.rs) + +```rust +pub struct MsdFunction { + /// 函数名 + name: String, + + /// LUN 配置 + luns: Vec, +} + +pub struct MsdLun { + /// LUN 编号 + pub lun_id: u8, + + /// 镜像文件路径 + pub file: Option, + + /// 是否 CD-ROM 模式 + pub cdrom: bool, + + /// 是否只读 + pub readonly: bool, + + /// 是否可移除 + pub removable: bool, +} + +impl MsdFunction { + /// 创建 MSD 函数 + pub fn new() -> Self; + + /// 设置镜像文件 + pub fn set_image(&mut self, path: &Path, cdrom: bool) -> Result<()>; + + /// 清除镜像 + pub fn clear_image(&mut self) -> Result<()>; + + /// 弹出介质 + pub fn eject(&mut self) -> Result<()>; +} + +impl GadgetFunction for MsdFunction { + fn name(&self) -> &str; + fn function_type(&self) -> &str; // "mass_storage" + fn configure(&self, path: &Path) -> Result<()>; +} +``` + +### 3.5 ConfigFS 操作 (configfs.rs) + +```rust +pub struct ConfigFs; + +impl ConfigFs { + /// ConfigFS 根路径 + const ROOT: &'static str = "/sys/kernel/config/usb_gadget"; + + /// 创建目录 + pub fn mkdir(path: &Path) -> Result<()>; + + /// 删除目录 + pub fn rmdir(path: &Path) -> Result<()>; + + /// 写入文件 + pub fn write_file(path: &Path, content: &str) -> Result<()>; + + /// 写入二进制文件 + pub fn write_binary(path: &Path, data: &[u8]) -> Result<()>; + + /// 读取文件 + pub fn read_file(path: &Path) -> Result; + + /// 创建符号链接 + pub fn symlink(target: &Path, link: &Path) -> Result<()>; + + /// 删除符号链接 + pub fn unlink(path: &Path) -> Result<()>; + + /// 列出目录 + pub fn list_dir(path: &Path) -> Result>; +} +``` + +### 3.6 端点分配 (endpoint.rs) + +```rust +pub struct EndpointAllocator { + /// 已使用的端点 + used_endpoints: HashSet, + + /// 最大端点数 + max_endpoints: u8, +} + +impl EndpointAllocator { + /// 创建分配器 + pub fn new(max_endpoints: u8) -> Self; + + /// 分配端点 + pub fn allocate(&mut self, count: u8) -> Result>; + + /// 释放端点 + pub fn release(&mut self, endpoints: &[u8]); + + /// 检查可用端点数 + pub fn available(&self) -> u8; +} +``` + +### 3.7 报告描述符 (report_desc.rs) + +```rust +pub struct ReportDescriptor; + +impl ReportDescriptor { + /// 标准键盘报告描述符 + pub fn keyboard() -> Vec { + vec![ + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x06, // Usage (Keyboard) + 0xA1, 0x01, // Collection (Application) + 0x05, 0x07, // Usage Page (Key Codes) + 0x19, 0xE0, // Usage Minimum (224) + 0x29, 0xE7, // Usage Maximum (231) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x08, // Report Count (8) + 0x81, 0x02, // Input (Data, Variable, Absolute) + 0x95, 0x01, // Report Count (1) + 0x75, 0x08, // Report Size (8) + 0x81, 0x01, // Input (Constant) + 0x95, 0x06, // Report Count (6) + 0x75, 0x08, // Report Size (8) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x65, // Logical Maximum (101) + 0x05, 0x07, // Usage Page (Key Codes) + 0x19, 0x00, // Usage Minimum (0) + 0x29, 0x65, // Usage Maximum (101) + 0x81, 0x00, // Input (Data, Array) + 0xC0, // End Collection + ] + } + + /// 相对鼠标报告描述符 + pub fn mouse_relative() -> Vec; + + /// 绝对鼠标报告描述符 + pub fn mouse_absolute() -> Vec; +} +``` + +--- + +## 4. 生命周期管理 + +### 4.1 初始化流程 + +``` +OtgService::new() + │ + ├── 检测 UDC 设备 + │ └── 读取 /sys/class/udc/ + │ + ├── 创建 OtgGadgetManager + │ + └── 初始化状态 + +enable_hid() + │ + ├── 检查 Gadget 是否存在 + │ └── 如不存在,创建 Gadget + │ + ├── 创建 HID 函数 + │ ├── hid.usb0 (键盘) + │ ├── hid.usb1 (相对鼠标) + │ └── hid.usb2 (绝对鼠标) + │ + ├── 配置函数 + │ └── 写入报告描述符 + │ + ├── 链接函数到配置 + │ + ├── 绑定 UDC (如未绑定) + │ + └── 等待设备节点出现 + └── /dev/hidg0, hidg1, hidg2 +``` + +### 4.2 清理流程 + +``` +disable_hid() + │ + ├── 检查是否有其他函数使用 + │ + ├── 如果只有 HID,解绑 UDC + │ + ├── 取消链接 HID 函数 + │ + └── 删除 HID 函数目录 + +disable_msd() + │ + ├── 同上... + │ + └── 如果没有任何函数,销毁 Gadget +``` + +--- + +## 5. 配置 + +### 5.1 OTG 配置 + +```rust +#[derive(Serialize, Deserialize)] +#[typeshare] +pub struct OtgConfig { + /// 是否启用 OTG + pub enabled: bool, + + /// 厂商 ID + pub vendor_id: u16, + + /// 产品 ID + pub product_id: u16, + + /// 厂商名称 + pub manufacturer: String, + + /// 产品名称 + pub product: String, +} + +impl Default for OtgConfig { + fn default() -> Self { + Self { + enabled: true, + vendor_id: 0x05ac, // Apple + product_id: 0x0001, + manufacturer: "One-KVM".to_string(), + product: "KVM Device".to_string(), + } + } +} +``` + +--- + +## 6. 错误处理 + +```rust +#[derive(Debug, thiserror::Error)] +pub enum OtgError { + #[error("No UDC device found")] + NoUdcDevice, + + #[error("Gadget already exists")] + GadgetExists, + + #[error("Gadget not found")] + GadgetNotFound, + + #[error("Function already exists: {0}")] + FunctionExists(String), + + #[error("UDC busy")] + UdcBusy, + + #[error("ConfigFS error: {0}")] + ConfigFsError(String), + + #[error("Permission denied: {0}")] + PermissionDenied(String), + + #[error("Device node not found: {0}")] + DeviceNodeNotFound(String), +} +``` + +--- + +## 7. 使用示例 + +### 7.1 启用 HID + +```rust +let otg = OtgService::new()?; + +// 启用 HID +let paths = otg.enable_hid().await?; +println!("Keyboard: {:?}", paths.keyboard); +println!("Mouse relative: {:?}", paths.mouse_relative); +println!("Mouse absolute: {:?}", paths.mouse_absolute); + +// 使用设备... + +// 禁用 HID +otg.disable_hid().await?; +``` + +### 7.2 启用 MSD + +```rust +let otg = OtgService::new()?; + +// 启用 MSD +let mut msd = otg.enable_msd().await?; + +// 挂载 ISO +msd.set_image(Path::new("/data/ubuntu.iso"), true)?; + +// 弹出 +msd.eject()?; + +// 禁用 MSD +otg.disable_msd().await?; +``` + +--- + +## 8. 常见问题 + +### Q: 找不到 UDC 设备? + +1. 检查内核是否支持 USB Gadget +2. 加载必要的内核模块: + ```bash + modprobe libcomposite + modprobe usb_f_hid + modprobe usb_f_mass_storage + ``` +3. 检查 `/sys/class/udc/` 目录 + +### Q: 权限错误? + +1. 以 root 运行 +2. 或配置 udev 规则 + +### Q: 设备节点不出现? + +1. 检查 UDC 是否正确绑定 +2. 查看 `dmesg` 日志 +3. 检查 ConfigFS 配置 + +### Q: 目标 PC 不识别? + +1. 检查 USB 线缆 +2. 检查报告描述符 +3. 使用 `lsusb` 确认设备 diff --git a/docs/modules/rustdesk.md b/docs/modules/rustdesk.md new file mode 100644 index 00000000..8cf18509 --- /dev/null +++ b/docs/modules/rustdesk.md @@ -0,0 +1,776 @@ +# RustDesk 模块文档 + +## 1. 模块概述 + +RustDesk 模块实现 RustDesk 协议集成,允许使用标准 RustDesk 客户端访问 One-KVM 设备。 + +### 1.1 主要功能 + +- RustDesk 协议实现 +- 渲染服务器 (hbbs) 通信 +- 中继服务器 (hbbr) 通信 +- 视频/音频/HID 转换 +- 端到端加密 + +### 1.2 文件结构 + +``` +src/rustdesk/ +├── mod.rs # RustDeskService (21KB) +├── connection.rs # 连接管理 (49KB) +├── rendezvous.rs # 渲染服务器 (32KB) +├── crypto.rs # NaCl 加密 (16KB) +├── config.rs # 配置 (7KB) +├── hid_adapter.rs # HID 适配 (14KB) +├── frame_adapters.rs # 帧转换 (9KB) +├── protocol.rs # 协议包装 (6KB) +└── bytes_codec.rs # 帧编码 (8KB) +``` + +--- + +## 2. 架构设计 + +### 2.1 RustDesk 网络架构 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ RustDesk Network Architecture │ +└─────────────────────────────────────────────────────────────────────────────┘ + +┌─────────────┐ ┌─────────────┐ +│ RustDesk │ │ One-KVM │ +│ Client │ │ Device │ +└──────┬──────┘ └──────┬──────┘ + │ │ + │ 1. 查询设备地址 │ + │─────────────────────►┌─────────────┐◄──────────────│ + │ │ hbbs │ │ + │ │ (Rendezvous)│ │ + │◄─────────────────────└─────────────┘ │ + │ 2. 返回地址 │ + │ │ + │ 3a. 直接连接 (如果可达) │ + │────────────────────────────────────────────────────│ + │ │ + │ 3b. 中继连接 (如果 NAT) │ + │─────────────────────►┌─────────────┐◄──────────────│ + │ │ hbbr │ │ + │ │ (Relay) │ │ + │◄─────────────────────└─────────────┘───────────────│ + │ │ + │ 4. 建立加密通道 │ + │◄───────────────────────────────────────────────────│ + │ │ + │ 5. 传输视频/音频/HID │ + │◄───────────────────────────────────────────────────│ +``` + +### 2.2 模块内部架构 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ RustDesk Module Architecture │ +└─────────────────────────────────────────────────────────────────────────────┘ + + ┌─────────────────┐ + │ RustDeskService │ + │ (mod.rs) │ + └────────┬────────┘ + │ + ┌───────────────────┼───────────────────┐ + │ │ │ + ▼ ▼ ▼ +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Rendezvous │ │ Connection │ │ Crypto │ +│ (rendezvous) │ │ (connection) │ │ (crypto) │ +└────────┬────────┘ └────────┬────────┘ └─────────────────┘ + │ │ + │ │ + ▼ ▼ +┌─────────────────┐ ┌─────────────────────────────────────┐ +│ hbbs Server │ │ Adapters │ +│ Connection │ │ ┌──────────┐ ┌──────────────────┐ │ +└─────────────────┘ │ │ HID │ │ Frame │ │ + │ │ Adapter │ │ Adapters │ │ + │ └──────────┘ └──────────────────┘ │ + └─────────────────────────────────────┘ + │ + ┌─────────────────┼─────────────────┐ + │ │ │ + ▼ ▼ ▼ + ┌───────────┐ ┌───────────┐ ┌───────────┐ + │ HID │ │ Video │ │ Audio │ + │ Controller│ │ Pipeline │ │ Pipeline │ + └───────────┘ └───────────┘ └───────────┘ +``` + +--- + +## 3. 核心组件 + +### 3.1 RustDeskService (mod.rs) + +RustDesk 服务主类。 + +```rust +pub struct RustDeskService { + /// 服务配置 + config: Arc>, + + /// 渲染连接 + rendezvous: Arc>>, + + /// 客户端连接 + connections: Arc>>>, + + /// 加密密钥 + keys: Arc, + + /// 视频管道 + video_pipeline: Arc, + + /// 音频管道 + audio_pipeline: Arc, + + /// HID 控制器 + hid: Arc, + + /// 服务状态 + status: Arc>, + + /// 事件总线 + events: Arc, +} + +impl RustDeskService { + /// 创建服务 + pub async fn new( + config: RustDeskConfig, + video_pipeline: Arc, + audio_pipeline: Arc, + hid: Arc, + events: Arc, + ) -> Result>; + + /// 启动服务 + pub async fn start(&self) -> Result<()>; + + /// 停止服务 + pub async fn stop(&self) -> Result<()>; + + /// 获取设备 ID + pub fn device_id(&self) -> String; + + /// 获取状态 + pub fn status(&self) -> ServiceStatus; + + /// 更新配置 + pub async fn update_config(&self, config: RustDeskConfig) -> Result<()>; + + /// 获取连接列表 + pub fn connections(&self) -> Vec; + + /// 断开连接 + pub async fn disconnect(&self, connection_id: &str) -> Result<()>; +} + +pub enum ServiceStatus { + Stopped, + Starting, + Running, + Error(String), +} + +pub struct ConnectionInfo { + pub id: String, + pub peer_id: String, + pub connected_at: DateTime, + pub ip: String, +} +``` + +### 3.2 RendezvousConnection (rendezvous.rs) + +渲染服务器连接管理。 + +```rust +pub struct RendezvousConnection { + /// 服务器地址 + server_addr: SocketAddr, + + /// TCP 连接 + stream: TcpStream, + + /// 设备 ID + device_id: String, + + /// 公钥 + public_key: [u8; 32], + + /// 注册状态 + registered: AtomicBool, + + /// 心跳任务 + heartbeat_task: Option>, +} + +impl RendezvousConnection { + /// 连接到渲染服务器 + pub async fn connect( + server: &str, + device_id: &str, + keys: &RustDeskKeys, + ) -> Result; + + /// 注册设备 + pub async fn register(&self) -> Result<()>; + + /// 发送心跳 + async fn heartbeat(&self) -> Result<()>; + + /// 接收消息 + pub async fn recv_message(&mut self) -> Result; + + /// 处理穿孔请求 + pub async fn handle_punch_request(&self, peer_id: &str) -> Result; +} + +pub enum RendezvousMessage { + RegisterOk, + PunchRequest { peer_id: String, socket_addr: SocketAddr }, + Heartbeat, + Error(String), +} +``` + +### 3.3 ClientConnection (connection.rs) + +客户端连接处理。 + +```rust +pub struct ClientConnection { + /// 连接 ID + id: String, + + /// 对端 ID + peer_id: String, + + /// 加密通道 + channel: EncryptedChannel, + + /// 帧适配器 + frame_adapter: FrameAdapter, + + /// HID 适配器 + hid_adapter: HidAdapter, + + /// 状态 + state: Arc>, +} + +impl ClientConnection { + /// 创建连接 + pub async fn new( + stream: TcpStream, + keys: &RustDeskKeys, + peer_public_key: &[u8], + ) -> Result; + + /// 处理连接 + pub async fn handle( + &self, + video_rx: broadcast::Receiver, + audio_rx: broadcast::Receiver, + hid: Arc, + ) -> Result<()>; + + /// 发送视频帧 + async fn send_video_frame(&self, frame: &EncodedFrame) -> Result<()>; + + /// 发送音频帧 + async fn send_audio_frame(&self, frame: &AudioFrame) -> Result<()>; + + /// 处理输入事件 + async fn handle_input(&self, msg: &InputMessage) -> Result<()>; + + /// 关闭连接 + pub async fn close(&self) -> Result<()>; +} + +pub enum ConnectionState { + Handshaking, + Authenticating, + Connected, + Closing, + Closed, +} +``` + +### 3.4 RustDeskKeys (crypto.rs) + +加密密钥管理。 + +```rust +pub struct RustDeskKeys { + /// 设备 ID + pub device_id: String, + + /// Curve25519 公钥 + pub public_key: [u8; 32], + + /// Curve25519 私钥 + secret_key: [u8; 32], + + /// Ed25519 签名公钥 + pub sign_public_key: [u8; 32], + + /// Ed25519 签名私钥 + sign_secret_key: [u8; 64], +} + +impl RustDeskKeys { + /// 生成新密钥 + pub fn generate() -> Self; + + /// 从配置加载 + pub fn from_config(config: &KeyConfig) -> Result; + + /// 保存到配置 + pub fn to_config(&self) -> KeyConfig; + + /// 计算共享密钥 + pub fn shared_secret(&self, peer_public_key: &[u8; 32]) -> [u8; 32]; + + /// 签名消息 + pub fn sign(&self, message: &[u8]) -> [u8; 64]; + + /// 验证签名 + pub fn verify(public_key: &[u8; 32], message: &[u8], signature: &[u8; 64]) -> bool; +} + +pub struct EncryptedChannel { + /// 发送密钥 + send_key: [u8; 32], + + /// 接收密钥 + recv_key: [u8; 32], + + /// 发送 nonce + send_nonce: AtomicU64, + + /// 接收 nonce + recv_nonce: AtomicU64, +} + +impl EncryptedChannel { + /// 加密消息 + pub fn encrypt(&self, plaintext: &[u8]) -> Vec; + + /// 解密消息 + pub fn decrypt(&self, ciphertext: &[u8]) -> Result>; +} +``` + +### 3.5 HidAdapter (hid_adapter.rs) + +RustDesk HID 事件转换。 + +```rust +pub struct HidAdapter { + hid: Arc, +} + +impl HidAdapter { + /// 创建适配器 + pub fn new(hid: Arc) -> Self; + + /// 处理键盘事件 + pub async fn handle_keyboard(&self, event: &RdKeyboardEvent) -> Result<()>; + + /// 处理鼠标事件 + pub async fn handle_mouse(&self, event: &RdMouseEvent) -> Result<()>; + + /// 转换键码 + fn convert_keycode(rd_key: u32) -> Option; + + /// 转换鼠标按钮 + fn convert_button(rd_button: u32) -> Option; +} + +/// RustDesk 键盘事件 +pub struct RdKeyboardEvent { + pub keycode: u32, + pub down: bool, + pub modifiers: u32, +} + +/// RustDesk 鼠标事件 +pub struct RdMouseEvent { + pub x: i32, + pub y: i32, + pub mask: u32, +} +``` + +### 3.6 FrameAdapter (frame_adapters.rs) + +帧格式转换。 + +```rust +pub struct FrameAdapter; + +impl FrameAdapter { + /// 转换视频帧到 RustDesk 格式 + pub fn to_rd_video_frame(frame: &EncodedFrame) -> RdVideoFrame; + + /// 转换音频帧到 RustDesk 格式 + pub fn to_rd_audio_frame(frame: &AudioFrame) -> RdAudioFrame; +} + +/// RustDesk 视频帧 +pub struct RdVideoFrame { + pub data: Vec, + pub key_frame: bool, + pub pts: i64, + pub format: RdVideoFormat, +} + +pub enum RdVideoFormat { + H264, + H265, + VP8, + VP9, +} + +/// RustDesk 音频帧 +pub struct RdAudioFrame { + pub data: Vec, + pub timestamp: u64, +} +``` + +### 3.7 协议消息 (protocol.rs) + +Protobuf 消息包装。 + +```rust +/// 使用 prost 生成的 protobuf 消息 +pub mod proto { + include!(concat!(env!("OUT_DIR"), "/rendezvous.rs")); + include!(concat!(env!("OUT_DIR"), "/message.rs")); +} + +pub struct MessageCodec; + +impl MessageCodec { + /// 编码消息 + pub fn encode(msg: &M) -> Vec; + + /// 解码消息 + pub fn decode(data: &[u8]) -> Result; +} +``` + +### 3.8 帧编码 (bytes_codec.rs) + +变长帧协议。 + +```rust +pub struct BytesCodec { + state: DecodeState, + buffer: BytesMut, +} + +impl BytesCodec { + /// 编码帧 + pub fn encode_frame(data: &[u8]) -> Vec { + let mut buf = Vec::with_capacity(4 + data.len()); + buf.extend_from_slice(&(data.len() as u32).to_be_bytes()); + buf.extend_from_slice(data); + buf + } + + /// 解码帧 + pub fn decode_frame(&mut self, src: &mut BytesMut) -> Result>; +} + +enum DecodeState { + Length, + Data(usize), +} +``` + +--- + +## 4. 协议详解 + +### 4.1 Protobuf 定义 + +```protobuf +// protos/rendezvous.proto +message RegisterPeer { + string id = 1; + bytes public_key = 2; +} + +message RegisterPeerResponse { + bool ok = 1; + string error = 2; +} + +message PunchHoleRequest { + string id = 1; + string nat_type = 2; +} + +// protos/message.proto +message VideoFrame { + bytes data = 1; + bool key = 2; + int64 pts = 3; + VideoCodec codec = 4; +} + +message AudioFrame { + bytes data = 1; + int64 timestamp = 2; +} + +message KeyboardEvent { + uint32 keycode = 1; + bool down = 2; + uint32 modifiers = 3; +} + +message MouseEvent { + int32 x = 1; + int32 y = 2; + uint32 mask = 3; +} +``` + +### 4.2 连接握手 + +``` +1. TCP 连接 + Client ────► Device + +2. 公钥交换 + Client ◄───► Device + +3. DH 密钥协商 + shared_secret = X25519(my_private, peer_public) + +4. 密钥派生 + send_key = HKDF(shared_secret, "send") + recv_key = HKDF(shared_secret, "recv") + +5. 认证 (可选) + Client ────► Device: encrypted(password) + Client ◄──── Device: encrypted(ok/fail) + +6. 开始传输 +``` + +--- + +## 5. 配置 + +```rust +#[derive(Serialize, Deserialize)] +#[typeshare] +pub struct RustDeskConfig { + /// 是否启用 + pub enabled: bool, + + /// 渲染服务器地址 + pub rendezvous_server: String, + + /// 中继服务器地址 + pub relay_server: Option, + + /// 设备 ID (自动生成) + pub device_id: Option, + + /// 访问密码 + pub password: Option, + + /// 允许的客户端 ID + pub allowed_clients: Vec, +} + +impl Default for RustDeskConfig { + fn default() -> Self { + Self { + enabled: false, + rendezvous_server: "rs-ny.rustdesk.com:21116".to_string(), + relay_server: None, + device_id: None, + password: None, + allowed_clients: vec![], + } + } +} +``` + +--- + +## 6. API 端点 + +| 端点 | 方法 | 描述 | +|------|------|------| +| `/api/rustdesk/status` | GET | 获取服务状态 | +| `/api/rustdesk/start` | POST | 启动服务 | +| `/api/rustdesk/stop` | POST | 停止服务 | +| `/api/rustdesk/config` | GET | 获取配置 | +| `/api/rustdesk/config` | PATCH | 更新配置 | +| `/api/rustdesk/device-id` | GET | 获取设备 ID | +| `/api/rustdesk/connections` | GET | 获取连接列表 | +| `/api/rustdesk/connections/:id` | DELETE | 断开连接 | + +### 响应格式 + +```json +// GET /api/rustdesk/status +{ + "status": "running", + "device_id": "123456789", + "rendezvous_connected": true, + "active_connections": 1 +} + +// GET /api/rustdesk/connections +{ + "connections": [ + { + "id": "conn-abc", + "peer_id": "987654321", + "connected_at": "2024-01-15T10:30:00Z", + "ip": "192.168.1.100" + } + ] +} +``` + +--- + +## 7. 事件 + +```rust +pub enum SystemEvent { + RustDeskStatusChanged { + status: String, + device_id: Option, + error: Option, + }, + + RustDeskConnectionOpened { + connection_id: String, + peer_id: String, + }, + + RustDeskConnectionClosed { + connection_id: String, + peer_id: String, + reason: String, + }, +} +``` + +--- + +## 8. 错误处理 + +```rust +#[derive(Debug, thiserror::Error)] +pub enum RustDeskError { + #[error("Service not running")] + NotRunning, + + #[error("Already running")] + AlreadyRunning, + + #[error("Rendezvous connection failed: {0}")] + RendezvousFailed(String), + + #[error("Authentication failed")] + AuthFailed, + + #[error("Connection refused")] + ConnectionRefused, + + #[error("Encryption error: {0}")] + EncryptionError(String), + + #[error("Protocol error: {0}")] + ProtocolError(String), + + #[error("Timeout")] + Timeout, +} +``` + +--- + +## 9. 使用示例 + +### 9.1 启动服务 + +```rust +let config = RustDeskConfig { + enabled: true, + rendezvous_server: "rs-ny.rustdesk.com:21116".to_string(), + password: Some("mypassword".to_string()), + ..Default::default() +}; + +let service = RustDeskService::new( + config, + video_pipeline, + audio_pipeline, + hid, + events, +).await?; + +service.start().await?; + +println!("Device ID: {}", service.device_id()); +``` + +### 9.2 客户端连接 + +``` +1. 打开 RustDesk 客户端 +2. 输入设备 ID +3. 输入密码 (如果设置) +4. 连接成功后即可控制 +``` + +--- + +## 10. 常见问题 + +### Q: 无法连接到渲染服务器? + +1. 检查网络连接 +2. 检查服务器地址 +3. 检查防火墙 + +### Q: 客户端连接失败? + +1. 检查设备 ID +2. 检查密码 +3. 检查 NAT 穿透 + +### Q: 视频延迟高? + +1. 使用更近的中继服务器 +2. 检查网络带宽 +3. 降低视频质量 + +### Q: 如何自建服务器? + +参考 RustDesk Server 部署文档: +- hbbs: 渲染服务器 +- hbbr: 中继服务器 diff --git a/docs/modules/video.md b/docs/modules/video.md new file mode 100644 index 00000000..b1ff1cf4 --- /dev/null +++ b/docs/modules/video.md @@ -0,0 +1,895 @@ +# Video 模块文档 + +## 1. 模块概述 + +Video 模块负责视频采集、编码和流传输,是 One-KVM 的核心功能模块。 + +### 1.1 主要功能 + +- V4L2 视频设备采集 +- 多格式像素转换 +- 硬件/软件视频编码 +- MJPEG 和 WebRTC 流传输 +- 帧去重和质量控制 + +### 1.2 文件结构 + +``` +src/video/ +├── mod.rs # 模块导出 +├── capture.rs # V4L2 视频采集 (22KB) +├── streamer.rs # 视频流服务 (34KB) +├── stream_manager.rs # 流管理器 (24KB) +├── shared_video_pipeline.rs # 共享视频管道 (35KB) +├── h264_pipeline.rs # H264 编码管道 (22KB) +├── format.rs # 像素格式定义 (9KB) +├── frame.rs # 视频帧结构 (6KB) +├── convert.rs # 格式转换 (21KB) +└── encoder/ # 编码器 + ├── mod.rs + ├── traits.rs # Encoder trait + ├── h264.rs # H264 编码 + ├── h265.rs # H265 编码 + ├── vp8.rs # VP8 编码 + ├── vp9.rs # VP9 编码 + ├── jpeg.rs # JPEG 编码 + └── registry.rs # 编码器注册表 +``` + +--- + +## 2. 架构设计 + +### 2.1 数据流 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Video Data Flow │ +└─────────────────────────────────────────────────────────────────────────────┘ + +V4L2 Device (/dev/video0) + │ + │ Raw frames (MJPEG/YUYV/NV12) + ▼ +┌───────────────────┐ +│ VideoCapturer │ ◄─── capture.rs +│ - open_device() │ +│ - read_frame() │ +│ - set_format() │ +└─────────┬─────────┘ + │ VideoFrame + ▼ +┌───────────────────┐ +│ Streamer │ ◄─── streamer.rs +│ - start() │ +│ - stop() │ +│ - get_info() │ +└─────────┬─────────┘ + │ + ┌─────┴─────┐ + │ │ + ▼ ▼ +┌────────┐ ┌────────────────────────────┐ +│ MJPEG │ │ SharedVideoPipeline │ +│ Mode │ │ - Decode (MJPEG→YUV) │ +│ │ │ - Convert (YUV→target) │ +│ │ │ - Encode (H264/H265/VP8) │ +└────────┘ └─────────────┬──────────────┘ + │ │ + ▼ ▼ +┌────────┐ ┌────────┐ +│ HTTP │ │ WebRTC │ +│ Stream │ │ RTP │ +└────────┘ └────────┘ +``` + +### 2.2 组件关系 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Component Relationships │ +└─────────────────────────────────────────────────────────────────────────────┘ + +VideoStreamManager (stream_manager.rs) + │ + ├──► Streamer (MJPEG mode) + │ └──► VideoCapturer + │ + └──► WebRtcStreamer (WebRTC mode) + └──► SharedVideoPipeline + ├──► VideoCapturer + ├──► MjpegDecoder + ├──► YuvConverter + └──► Encoders[] + ├── H264Encoder + ├── H265Encoder + ├── VP8Encoder + └── VP9Encoder +``` + +--- + +## 3. 核心组件 + +### 3.1 VideoCapturer (capture.rs) + +V4L2 视频采集器,负责从摄像头/采集卡读取视频帧。 + +#### 主要接口 + +```rust +pub struct VideoCapturer { + device: Device, + stream: Option>, + config: CaptureConfig, + format: PixelFormat, + resolution: Resolution, +} + +impl VideoCapturer { + /// 打开视频设备 + pub fn open(device_path: &str) -> Result; + + /// 设置视频格式 + pub fn set_format(&mut self, config: &CaptureConfig) -> Result<()>; + + /// 开始采集 + pub fn start(&mut self) -> Result<()>; + + /// 停止采集 + pub fn stop(&mut self) -> Result<()>; + + /// 读取一帧 + pub fn read_frame(&mut self) -> Result; + + /// 列出设备支持的格式 + pub fn list_formats(&self) -> Vec; + + /// 列出支持的分辨率 + pub fn list_resolutions(&self, format: PixelFormat) -> Vec; +} +``` + +#### 采集配置 + +```rust +pub struct CaptureConfig { + pub device: String, // /dev/video0 + pub width: u32, // 1920 + pub height: u32, // 1080 + pub fps: u32, // 30 + pub format: Option, // 优先格式 + pub buffer_count: u32, // 4 +} +``` + +#### 使用示例 + +```rust +// 打开设备 +let mut capturer = VideoCapturer::open("/dev/video0")?; + +// 设置格式 +capturer.set_format(&CaptureConfig { + device: "/dev/video0".to_string(), + width: 1920, + height: 1080, + fps: 30, + format: Some(PixelFormat::Mjpeg), + buffer_count: 4, +})?; + +// 开始采集 +capturer.start()?; + +// 读取帧 +loop { + let frame = capturer.read_frame()?; + process_frame(frame); +} +``` + +### 3.2 VideoFrame (frame.rs) + +视频帧数据结构,支持零拷贝和帧去重。 + +```rust +pub struct VideoFrame { + /// 帧数据 (引用计数) + data: Arc, + + /// xxHash64 缓存 (用于去重) + hash: Arc>, + + /// 分辨率 + resolution: Resolution, + + /// 像素格式 + format: PixelFormat, + + /// 行步长 + stride: u32, + + /// 是否关键帧 + key_frame: bool, + + /// 帧序号 + sequence: u64, + + /// 采集时间戳 + capture_ts: Instant, + + /// 是否有信号 + online: bool, +} + +impl VideoFrame { + /// 创建新帧 + pub fn new(data: Bytes, resolution: Resolution, format: PixelFormat) -> Self; + + /// 获取帧数据 + pub fn data(&self) -> &[u8]; + + /// 计算帧哈希 (懒加载) + pub fn hash(&self) -> u64; + + /// 检查帧是否相同 (用于去重) + pub fn is_same_as(&self, other: &Self) -> bool; + + /// 克隆帧 (零拷贝) + pub fn clone_ref(&self) -> Self; +} +``` + +### 3.3 PixelFormat (format.rs) + +支持的像素格式定义。 + +```rust +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum PixelFormat { + // 压缩格式 + Mjpeg, // Motion JPEG (优先级: 100) + Jpeg, // Static JPEG (优先级: 99) + + // YUV 4:2:2 打包格式 + Yuyv, // YUYV/YUY2 (优先级: 80) + Yvyu, // YVYU (优先级: 64) + Uyvy, // UYVY (优先级: 65) + + // YUV 半平面格式 + Nv12, // NV12 (优先级: 75) + Nv16, // NV16 (优先级: 60) + Nv24, // NV24 (优先级: 55) + + // YUV 平面格式 + Yuv420, // I420/YU12 (优先级: 70) + Yvu420, // YV12 (优先级: 63) + + // RGB 格式 + Rgb565, // RGB565 (优先级: 40) + Rgb24, // RGB24 (优先级: 50) + Bgr24, // BGR24 (优先级: 49) + + // 灰度 + Grey, // 8-bit grayscale (优先级: 10) +} + +impl PixelFormat { + /// 获取格式优先级 (越高越好) + pub fn priority(&self) -> u32; + + /// 计算帧大小 + pub fn frame_size(&self, width: u32, height: u32) -> usize; + + /// 转换为 V4L2 FourCC + pub fn to_fourcc(&self) -> u32; + + /// 从 V4L2 FourCC 转换 + pub fn from_fourcc(fourcc: u32) -> Option; + + /// 是否压缩格式 + pub fn is_compressed(&self) -> bool; +} +``` + +### 3.4 SharedVideoPipeline (shared_video_pipeline.rs) + +多会话共享的视频编码管道。 + +```rust +pub struct SharedVideoPipeline { + /// 视频采集器 + capturer: Arc>, + + /// MJPEG 解码器 + decoder: MjpegDecoder, + + /// YUV 转换器 + converter: YuvConverter, + + /// 编码器实例 + encoders: HashMap>, + + /// 活跃会话 + sessions: Arc>>, + + /// 配置 + config: PipelineConfig, +} + +impl SharedVideoPipeline { + /// 创建管道 + pub async fn new(config: PipelineConfig) -> Result; + + /// 启动管道 + pub async fn start(&self) -> Result<()>; + + /// 停止管道 + pub async fn stop(&self) -> Result<()>; + + /// 添加会话订阅 + pub fn subscribe(&self, codec: VideoCodec) -> Receiver; + + /// 移除会话订阅 + pub fn unsubscribe(&self, session_id: &str); + + /// 编码单帧 (多编码器) + async fn encode_frame(&self, frame: VideoFrame) -> Result<()>; +} +``` + +#### 编码流程 + +``` +Input: VideoFrame (MJPEG) + │ + ▼ +┌───────────────────┐ +│ MJPEG Decode │ turbojpeg / VAAPI +│ MJPEG → YUV420 │ +└─────────┬─────────┘ + │ + ▼ +┌───────────────────┐ +│ YUV Convert │ libyuv (SIMD) +│ YUV420 → target │ +└─────────┬─────────┘ + │ + ┌─────┴─────┬─────────┬─────────┐ + │ │ │ │ + ▼ ▼ ▼ ▼ +┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ +│ H264 │ │ H265 │ │ VP8 │ │ VP9 │ +│Encoder│ │Encoder│ │Encoder│ │Encoder│ +└───┬───┘ └───┬───┘ └───┬───┘ └───┬───┘ + │ │ │ │ + └──────────┴──────────┴──────────┘ + │ + ▼ + EncodedFrame[] + (distribute to sessions) +``` + +### 3.5 Streamer (streamer.rs) + +高层视频流服务,管理采集和分发。 + +```rust +pub struct Streamer { + /// 采集器 + capturer: Option>>, + + /// 采集任务句柄 + capture_task: Option>, + + /// 帧广播通道 + frame_tx: broadcast::Sender, + + /// 状态 + state: Arc>, + + /// 配置 + config: StreamerConfig, + + /// 事件总线 + events: Arc, +} + +impl Streamer { + /// 创建流服务 + pub fn new(events: Arc) -> Self; + + /// 启动流 + pub async fn start(&self, config: StreamerConfig) -> Result<()>; + + /// 停止流 + pub async fn stop(&self) -> Result<()>; + + /// 订阅帧 + pub fn subscribe(&self) -> broadcast::Receiver; + + /// 获取状态 + pub fn state(&self) -> StreamerState; + + /// 获取信息 + pub fn get_info(&self) -> StreamerInfo; + + /// 应用配置 + pub async fn apply_config(&self, config: StreamerConfig) -> Result<()>; +} + +pub struct StreamerState { + pub status: StreamStatus, + pub device: Option, + pub resolution: Option, + pub format: Option, + pub fps: f32, + pub frame_count: u64, + pub error: Option, +} + +pub enum StreamStatus { + Idle, + Starting, + Streaming, + Stopping, + Error, +} +``` + +### 3.6 VideoStreamManager (stream_manager.rs) + +统一管理 MJPEG 和 WebRTC 流模式。 + +```rust +pub struct VideoStreamManager { + /// MJPEG 流服务 + mjpeg_streamer: Arc, + + /// WebRTC 流服务 + webrtc_streamer: Arc>>, + + /// 当前模式 + mode: Arc>, + + /// 配置存储 + config_store: ConfigStore, + + /// 事件总线 + events: Arc, +} + +impl VideoStreamManager { + /// 创建管理器 + pub fn new(config_store: ConfigStore, events: Arc) -> Self; + + /// 启动流 + pub async fn start(&self) -> Result<()>; + + /// 停止流 + pub async fn stop(&self) -> Result<()>; + + /// 切换模式 + pub async fn set_mode(&self, mode: StreamMode) -> Result<()>; + + /// 获取当前模式 + pub fn get_mode(&self) -> StreamMode; + + /// 获取设备列表 + pub fn list_devices(&self) -> Vec; + + /// 获取统计信息 + pub fn get_stats(&self) -> StreamStats; + + /// 获取 MJPEG 订阅 + pub fn subscribe_mjpeg(&self) -> broadcast::Receiver; + + /// 创建 WebRTC 会话 + pub async fn create_webrtc_session(&self, params: SessionParams) -> Result; +} + +pub enum StreamMode { + Mjpeg, + Webrtc, +} +``` + +--- + +## 4. 编码器系统 + +### 4.1 Encoder Trait (encoder/traits.rs) + +```rust +pub trait Encoder: Send + Sync { + /// 编码一帧 + fn encode(&mut self, frame: &VideoFrame) -> Result; + + /// 获取编码器类型 + fn codec(&self) -> VideoCodec; + + /// 获取当前码率 + fn bitrate(&self) -> u32; + + /// 设置码率 + fn set_bitrate(&mut self, bitrate: u32) -> Result<()>; + + /// 获取 GOP 大小 + fn gop_size(&self) -> u32; + + /// 强制关键帧 + fn force_keyframe(&mut self); + + /// 重置编码器 + fn reset(&mut self) -> Result<()>; + + /// 获取编码器信息 + fn info(&self) -> EncoderInfo; +} + +pub struct EncodedFrame { + pub data: Bytes, + pub codec: VideoCodec, + pub key_frame: bool, + pub pts: u64, + pub dts: u64, +} + +pub enum VideoCodec { + H264, + H265, + VP8, + VP9, +} +``` + +### 4.2 编码器优先级 + +``` +H264 编码器选择顺序: +1. VAAPI (Intel/AMD GPU) +2. RKMPP (Rockchip) +3. V4L2 M2M +4. x264 (Software) + +H265 编码器选择顺序: +1. VAAPI +2. RKMPP +(无软件后备) + +VP8/VP9 编码器: +1. VAAPI only +``` + +### 4.3 EncoderRegistry (encoder/registry.rs) + +```rust +pub struct EncoderRegistry { + /// 已注册的编码器工厂 + factories: HashMap>, +} + +impl EncoderRegistry { + /// 创建注册表 + pub fn new() -> Self; + + /// 注册编码器工厂 + pub fn register(&mut self, codec: VideoCodec, factory: EncoderFactory); + + /// 创建最佳编码器 + pub fn create_encoder(&self, codec: VideoCodec, config: EncoderConfig) -> Result>; + + /// 列出可用编码器 + pub fn list_available(&self, codec: VideoCodec) -> Vec; + + /// 探测硬件能力 + pub fn probe_hardware() -> HardwareCapabilities; +} + +pub struct EncoderFactory { + pub name: String, + pub priority: u32, + pub create: Box Result>>, + pub probe: Box bool>, +} +``` + +--- + +## 5. 格式转换 + +### 5.1 MjpegDecoder (convert.rs) + +```rust +pub struct MjpegDecoder { + /// turbojpeg 解压缩器 + decompressor: Decompressor, + + /// 输出缓冲区 + output_buffer: Vec, +} + +impl MjpegDecoder { + /// 创建解码器 + pub fn new() -> Result; + + /// 解码 MJPEG 到 YUV420 + pub fn decode(&mut self, jpeg_data: &[u8]) -> Result; + + /// 获取图像信息 + pub fn get_info(jpeg_data: &[u8]) -> Result; +} +``` + +### 5.2 YuvConverter (convert.rs) + +使用 libyuv 进行高性能格式转换。 + +```rust +pub struct YuvConverter; + +impl YuvConverter { + /// YUYV → YUV420 + pub fn yuyv_to_yuv420(src: &[u8], dst: &mut [u8], width: u32, height: u32); + + /// NV12 → YUV420 + pub fn nv12_to_yuv420(src: &[u8], dst: &mut [u8], width: u32, height: u32); + + /// RGB24 → YUV420 + pub fn rgb24_to_yuv420(src: &[u8], dst: &mut [u8], width: u32, height: u32); + + /// YUV420 → NV12 + pub fn yuv420_to_nv12(src: &[u8], dst: &mut [u8], width: u32, height: u32); + + /// 缩放 YUV420 + pub fn scale_yuv420( + src: &[u8], src_width: u32, src_height: u32, + dst: &mut [u8], dst_width: u32, dst_height: u32, + filter: ScaleFilter, + ); +} + +pub enum ScaleFilter { + None, // 最近邻 + Linear, // 双线性 + Bilinear, // 双线性 (同 Linear) + Box, // 盒式滤波 +} +``` + +--- + +## 6. 配置说明 + +### 6.1 视频配置 + +```rust +#[derive(Serialize, Deserialize)] +#[typeshare] +pub struct VideoConfig { + /// 设备路径 (/dev/video0) + pub device: Option, + + /// 像素格式 (MJPEG/YUYV/NV12) + pub format: Option, + + /// 宽度 + pub width: u32, + + /// 高度 + pub height: u32, + + /// 帧率 + pub fps: u32, + + /// JPEG 质量 (1-100) + pub quality: u32, +} + +impl Default for VideoConfig { + fn default() -> Self { + Self { + device: None, + format: None, + width: 1920, + height: 1080, + fps: 30, + quality: 80, + } + } +} +``` + +### 6.2 流配置 + +```rust +#[derive(Serialize, Deserialize)] +#[typeshare] +pub struct StreamConfig { + /// 流模式 + pub mode: StreamMode, + + /// 码率 (kbps) + pub bitrate_kbps: u32, + + /// GOP 大小 + pub gop_size: u32, + + /// 编码器类型 + pub encoder: EncoderType, + + /// STUN 服务器 + pub stun_server: Option, + + /// TURN 服务器 + pub turn_server: Option, + + /// TURN 用户名 + pub turn_username: Option, + + /// TURN 密码 + pub turn_password: Option, +} +``` + +--- + +## 7. API 端点 + +### 7.1 流控制 + +| 端点 | 方法 | 描述 | +|------|------|------| +| `/api/stream/status` | GET | 获取流状态 | +| `/api/stream/start` | POST | 启动流 | +| `/api/stream/stop` | POST | 停止流 | +| `/api/stream/mode` | GET | 获取流模式 | +| `/api/stream/mode` | POST | 设置流模式 | +| `/api/stream/mjpeg` | GET | MJPEG 流 | +| `/api/stream/snapshot` | GET | 获取快照 | + +### 7.2 设备管理 + +| 端点 | 方法 | 描述 | +|------|------|------| +| `/api/devices/video` | GET | 列出视频设备 | +| `/api/devices/video/:id/formats` | GET | 列出设备格式 | +| `/api/devices/video/:id/resolutions` | GET | 列出分辨率 | + +### 7.3 响应格式 + +```json +// GET /api/stream/status +{ + "status": "streaming", + "device": "/dev/video0", + "resolution": { "width": 1920, "height": 1080 }, + "format": "MJPEG", + "fps": 30.0, + "frame_count": 12345, + "mode": "mjpeg" +} + +// GET /api/devices/video +{ + "devices": [ + { + "path": "/dev/video0", + "name": "USB Capture", + "driver": "uvcvideo", + "bus": "usb-0000:00:14.0-1" + } + ] +} +``` + +--- + +## 8. 事件 + +视频模块发布的事件: + +```rust +pub enum SystemEvent { + /// 流状态变化 + StreamStateChanged { + state: String, // "idle" | "starting" | "streaming" | "stopping" | "error" + device: Option, + resolution: Option, + fps: Option, + }, + + /// 设备变化 + VideoDeviceChanged { + added: Vec, + removed: Vec, + }, + + /// 编码器变化 + EncoderChanged { + codec: String, + hardware: bool, + }, +} +``` + +--- + +## 9. 错误处理 + +```rust +#[derive(Debug, thiserror::Error)] +pub enum VideoError { + #[error("Device not found: {0}")] + DeviceNotFound(String), + + #[error("Device busy: {0}")] + DeviceBusy(String), + + #[error("Format not supported: {0:?}")] + FormatNotSupported(PixelFormat), + + #[error("Resolution not supported: {0}x{1}")] + ResolutionNotSupported(u32, u32), + + #[error("Capture error: {0}")] + CaptureError(String), + + #[error("Encoder error: {0}")] + EncoderError(String), + + #[error("No signal")] + NoSignal, + + #[error("Device lost")] + DeviceLost, +} +``` + +--- + +## 10. 性能优化 + +### 10.1 零拷贝 + +- `Arc` 共享帧数据 +- 引用计数避免复制 + +### 10.2 帧去重 + +- xxHash64 快速哈希 +- 相同帧跳过编码 + +### 10.3 硬件加速 + +- VAAPI 优先 +- 自动后备软件编码 + +### 10.4 内存池 + +- 预分配帧缓冲区 +- 复用编码器缓冲区 + +--- + +## 11. 常见问题 + +### Q: 如何添加新的视频格式? + +1. 在 `format.rs` 添加枚举值 +2. 实现 `to_fourcc()` 和 `from_fourcc()` +3. 在 `convert.rs` 添加转换函数 + +### Q: 如何添加新的编码器? + +1. 实现 `Encoder` trait +2. 创建 `EncoderFactory` +3. 在 `EncoderRegistry` 注册 + +### Q: 帧率不稳定怎么办? + +1. 检查 USB 带宽 +2. 降低分辨率 +3. 使用 MJPEG 格式 +4. 启用硬件编码 diff --git a/docs/modules/web.md b/docs/modules/web.md new file mode 100644 index 00000000..f45ed87f --- /dev/null +++ b/docs/modules/web.md @@ -0,0 +1,428 @@ +# Web 模块文档 + +## 1. 模块概述 + +Web 模块提供 HTTP API 和静态文件服务。 + +### 1.1 主要功能 + +- REST API +- WebSocket +- 静态文件服务 +- 认证中间件 +- CORS 支持 + +### 1.2 文件结构 + +``` +src/web/ +├── mod.rs # 模块导出 +├── routes.rs # 路由定义 (9KB) +├── ws.rs # WebSocket (8KB) +├── audio_ws.rs # 音频 WebSocket (8KB) +├── static_files.rs # 静态文件 (6KB) +└── handlers/ # API 处理器 + ├── mod.rs + └── config/ + ├── mod.rs + ├── apply.rs + ├── types.rs + └── rustdesk.rs +``` + +--- + +## 2. 路由结构 + +### 2.1 公共路由 (无认证) + +| 路由 | 方法 | 描述 | +|------|------|------| +| `/health` | GET | 健康检查 | +| `/auth/login` | POST | 登录 | +| `/setup` | GET | 获取设置状态 | +| `/setup/init` | POST | 初始化设置 | + +### 2.2 用户路由 (需认证) + +| 路由 | 方法 | 描述 | +|------|------|------| +| `/info` | GET | 系统信息 | +| `/devices` | GET | 设备列表 | +| `/stream/*` | * | 流控制 | +| `/webrtc/*` | * | WebRTC 信令 | +| `/hid/*` | * | HID 控制 | +| `/audio/*` | * | 音频控制 | +| `/ws` | WS | 事件 WebSocket | +| `/ws/audio` | WS | 音频 WebSocket | + +### 2.3 管理员路由 (需 Admin) + +| 路由 | 方法 | 描述 | +|------|------|------| +| `/config/*` | * | 配置管理 | +| `/msd/*` | * | MSD 操作 | +| `/atx/*` | * | ATX 控制 | +| `/extensions/*` | * | 扩展管理 | +| `/rustdesk/*` | * | RustDesk | +| `/users/*` | * | 用户管理 | + +--- + +## 3. 路由定义 + +```rust +pub fn create_router(state: Arc) -> Router { + Router::new() + // 公共路由 + .route("/health", get(handlers::health)) + .route("/auth/login", post(handlers::login)) + .route("/setup", get(handlers::setup_status)) + .route("/setup/init", post(handlers::setup_init)) + + // 用户路由 + .nest("/api", user_routes()) + + // 管理员路由 + .nest("/api/admin", admin_routes()) + + // 静态文件 + .fallback(static_files::serve) + + // 中间件 + .layer(CorsLayer::permissive()) + .layer(CompressionLayer::new()) + .layer(TraceLayer::new_for_http()) + + // 状态 + .with_state(state) +} + +fn user_routes() -> Router { + Router::new() + .route("/info", get(handlers::system_info)) + .route("/devices", get(handlers::list_devices)) + + // 流控制 + .route("/stream/status", get(handlers::stream_status)) + .route("/stream/start", post(handlers::stream_start)) + .route("/stream/stop", post(handlers::stream_stop)) + .route("/stream/mjpeg", get(handlers::mjpeg_stream)) + + // WebRTC + .route("/webrtc/session", post(handlers::webrtc_create_session)) + .route("/webrtc/offer", post(handlers::webrtc_offer)) + .route("/webrtc/ice", post(handlers::webrtc_ice)) + .route("/webrtc/close", post(handlers::webrtc_close)) + + // HID + .route("/hid/status", get(handlers::hid_status)) + .route("/hid/reset", post(handlers::hid_reset)) + + // WebSocket + .route("/ws", get(handlers::ws_handler)) + .route("/ws/audio", get(handlers::audio_ws_handler)) + + // 认证中间件 + .layer(middleware::from_fn(auth_middleware)) +} + +fn admin_routes() -> Router { + Router::new() + // 配置 + .route("/config", get(handlers::config::get_config)) + .route("/config", patch(handlers::config::update_config)) + + // MSD + .route("/msd/status", get(handlers::msd_status)) + .route("/msd/connect", post(handlers::msd_connect)) + + // ATX + .route("/atx/status", get(handlers::atx_status)) + .route("/atx/power/short", post(handlers::atx_power_short)) + + // 认证中间件 + .layer(middleware::from_fn(auth_middleware)) + .layer(middleware::from_fn(admin_middleware)) +} +``` + +--- + +## 4. 静态文件服务 + +```rust +#[derive(RustEmbed)] +#[folder = "web/dist"] +#[include = "*.html"] +#[include = "*.js"] +#[include = "*.css"] +#[include = "assets/*"] +struct Assets; + +pub async fn serve(uri: Uri) -> impl IntoResponse { + let path = uri.path().trim_start_matches('/'); + + // 尝试获取文件 + if let Some(content) = Assets::get(path) { + let mime = mime_guess::from_path(path) + .first_or_octet_stream(); + + return ( + [(header::CONTENT_TYPE, mime.as_ref())], + content.data.into_owned(), + ).into_response(); + } + + // SPA 回退到 index.html + if let Some(content) = Assets::get("index.html") { + return ( + [(header::CONTENT_TYPE, "text/html")], + content.data.into_owned(), + ).into_response(); + } + + StatusCode::NOT_FOUND.into_response() +} +``` + +--- + +## 5. WebSocket 处理 + +### 5.1 事件 WebSocket (ws.rs) + +```rust +pub async fn ws_handler( + ws: WebSocketUpgrade, + State(state): State>, +) -> impl IntoResponse { + ws.on_upgrade(|socket| handle_ws(socket, state)) +} + +async fn handle_ws(mut socket: WebSocket, state: Arc) { + // 发送初始设备信息 + let device_info = state.get_device_info().await; + let json = serde_json::to_string(&device_info).unwrap(); + let _ = socket.send(Message::Text(json)).await; + + // 订阅事件 + let mut rx = state.events.subscribe(); + + loop { + tokio::select! { + // 发送事件 + result = rx.recv() => { + if let Ok(event) = result { + let json = serde_json::to_string(&event).unwrap(); + if socket.send(Message::Text(json)).await.is_err() { + break; + } + } + } + + // 接收消息 (心跳/关闭) + msg = socket.recv() => { + match msg { + Some(Ok(Message::Ping(data))) => { + let _ = socket.send(Message::Pong(data)).await; + } + Some(Ok(Message::Close(_))) | None => break, + _ => {} + } + } + } + } +} +``` + +### 5.2 音频 WebSocket (audio_ws.rs) + +```rust +pub async fn audio_ws_handler( + ws: WebSocketUpgrade, + State(state): State>, +) -> impl IntoResponse { + ws.on_upgrade(|socket| handle_audio_ws(socket, state)) +} + +async fn handle_audio_ws(mut socket: WebSocket, state: Arc) { + // 订阅音频帧 + let mut rx = state.audio.subscribe(); + + loop { + tokio::select! { + // 发送音频帧 + result = rx.recv() => { + if let Ok(frame) = result { + if socket.send(Message::Binary(frame.data.to_vec())).await.is_err() { + break; + } + } + } + + // 处理关闭 + msg = socket.recv() => { + match msg { + Some(Ok(Message::Close(_))) | None => break, + _ => {} + } + } + } + } +} +``` + +--- + +## 6. MJPEG 流 + +```rust +pub async fn mjpeg_stream( + State(state): State>, +) -> impl IntoResponse { + let boundary = "frame"; + + // 订阅视频帧 + let rx = state.stream_manager.subscribe_mjpeg(); + + // 创建流 + let stream = async_stream::stream! { + let mut rx = rx; + while let Ok(frame) = rx.recv().await { + let header = format!( + "--{}\r\nContent-Type: image/jpeg\r\nContent-Length: {}\r\n\r\n", + boundary, + frame.data.len() + ); + yield Ok::<_, std::io::Error>(Bytes::from(header)); + yield Ok(frame.data.clone()); + yield Ok(Bytes::from("\r\n")); + } + }; + + ( + [( + header::CONTENT_TYPE, + format!("multipart/x-mixed-replace; boundary={}", boundary), + )], + Body::from_stream(stream), + ) +} +``` + +--- + +## 7. 错误处理 + +```rust +impl IntoResponse for AppError { + fn into_response(self) -> Response { + let (status, message) = match self { + AppError::AuthError => (StatusCode::UNAUTHORIZED, "Authentication failed"), + AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized"), + AppError::Forbidden => (StatusCode::FORBIDDEN, "Forbidden"), + AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg.as_str()), + AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg.as_str()), + AppError::Internal(err) => { + tracing::error!("Internal error: {:?}", err); + (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error") + } + // ... + }; + + (status, Json(json!({ "error": message }))).into_response() + } +} +``` + +--- + +## 8. 请求提取器 + +```rust +// 从 Cookie 获取会话 +pub struct AuthUser(pub Session); + +#[async_trait] +impl FromRequestParts for AuthUser +where + S: Send + Sync, +{ + type Rejection = AppError; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let cookies = Cookies::from_request_parts(parts, state).await?; + let token = cookies + .get("session_id") + .map(|c| c.value().to_string()) + .ok_or(AppError::Unauthorized)?; + + let state = parts.extensions.get::>().unwrap(); + let session = state.sessions + .get_session(&token) + .ok_or(AppError::Unauthorized)?; + + Ok(AuthUser(session)) + } +} +``` + +--- + +## 9. 中间件 + +### 9.1 认证中间件 + +```rust +pub async fn auth_middleware( + State(state): State>, + cookies: Cookies, + mut request: Request, + next: Next, +) -> Response { + let token = cookies + .get("session_id") + .map(|c| c.value().to_string()); + + if let Some(session) = token.and_then(|t| state.sessions.get_session(&t)) { + request.extensions_mut().insert(session); + next.run(request).await + } else { + StatusCode::UNAUTHORIZED.into_response() + } +} +``` + +### 9.2 Admin 中间件 + +```rust +pub async fn admin_middleware( + Extension(session): Extension, + request: Request, + next: Next, +) -> Response { + if session.role == UserRole::Admin { + next.run(request).await + } else { + StatusCode::FORBIDDEN.into_response() + } +} +``` + +--- + +## 10. HTTPS 支持 + +```rust +// 使用 axum-server 提供 TLS +let tls_config = RustlsConfig::from_pem_file(cert_path, key_path).await?; + +axum_server::bind_rustls(addr, tls_config) + .serve(app.into_make_service()) + .await?; + +// 或自动生成自签名证书 +let (cert, key) = generate_self_signed_cert()?; +let tls_config = RustlsConfig::from_pem(cert, key).await?; +``` diff --git a/docs/modules/webrtc.md b/docs/modules/webrtc.md new file mode 100644 index 00000000..6c47e383 --- /dev/null +++ b/docs/modules/webrtc.md @@ -0,0 +1,731 @@ +# WebRTC 模块文档 + +## 1. 模块概述 + +WebRTC 模块提供低延迟的实时音视频流传输,支持多种视频编码格式和 DataChannel HID 控制。 + +### 1.1 主要功能 + +- WebRTC 会话管理 +- 多编码器支持 (H264/H265/VP8/VP9) +- 音频轨道 (Opus) +- DataChannel HID +- ICE/STUN/TURN 支持 + +### 1.2 文件结构 + +``` +src/webrtc/ +├── mod.rs # 模块导出 +├── webrtc_streamer.rs # 统一管理器 (34KB) +├── universal_session.rs # 会话管理 (32KB) +├── video_track.rs # 视频轨道 (19KB) +├── rtp.rs # RTP 打包 (24KB) +├── h265_payloader.rs # H265 RTP (15KB) +├── peer.rs # PeerConnection (17KB) +├── config.rs # 配置 (3KB) +├── signaling.rs # 信令 (5KB) +└── track.rs # 轨道基类 (11KB) +``` + +--- + +## 2. 架构设计 + +### 2.1 整体架构 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ WebRTC Architecture │ +└─────────────────────────────────────────────────────────────────────────────┘ + + Browser + │ + │ HTTP Signaling + ▼ + ┌─────────────────┐ + │ WebRtcStreamer │ + │(webrtc_streamer)│ + └────────┬────────┘ + │ + ┌─────────────┼─────────────┐ + │ │ │ + ▼ ▼ ▼ +┌────────┐ ┌────────┐ ┌────────┐ +│Session │ │Session │ │Session │ +│ 1 │ │ 2 │ │ N │ +└───┬────┘ └───┬────┘ └───┬────┘ + │ │ │ + ├───────────┼─────────────┤ + │ │ │ + ▼ ▼ ▼ +┌─────────────────────────────────────┐ +│ SharedVideoPipeline │ +│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ +│ │H264 │ │H265 │ │VP8 │ │VP9 │ │ +│ └─────┘ └─────┘ └─────┘ └─────┘ │ +└─────────────────────────────────────┘ + │ + ▼ + ┌────────────────┐ + │ VideoCapturer │ + └────────────────┘ +``` + +### 2.2 会话生命周期 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Session Lifecycle │ +└─────────────────────────────────────────────────────────────────────────────┘ + +1. 创建会话 + POST /webrtc/session + │ + ▼ + ┌─────────────────┐ + │ Create Session │ + │ Generate ID │ + └────────┬────────┘ + │ + ▼ + { session_id: "..." } + +2. 发送 Offer + POST /webrtc/offer + { session_id, codec, offer_sdp } + │ + ▼ + ┌─────────────────┐ + │ Process Offer │ + │ Create Answer │ + │ Setup Tracks │ + └────────┬────────┘ + │ + ▼ + { answer_sdp, ice_candidates } + +3. ICE 候选 + POST /webrtc/ice + { session_id, candidate } + │ + ▼ + ┌─────────────────┐ + │ Add ICE │ + │ Candidate │ + └─────────────────┘ + +4. 连接建立 + ┌─────────────────┐ + │ DTLS Handshake │ + │ SRTP Setup │ + │ DataChannel │ + └────────┬────────┘ + │ + ▼ + 开始传输视频/音频 + +5. 关闭会话 + POST /webrtc/close + { session_id } + │ + ▼ + ┌─────────────────┐ + │ Cleanup │ + │ Release │ + └─────────────────┘ +``` + +--- + +## 3. 核心组件 + +### 3.1 WebRtcStreamer (webrtc_streamer.rs) + +WebRTC 服务主类。 + +```rust +pub struct WebRtcStreamer { + /// 会话映射 + sessions: Arc>>>, + + /// 共享视频管道 + video_pipeline: Arc, + + /// 共享音频管道 + audio_pipeline: Arc, + + /// HID 控制器 + hid: Arc, + + /// 配置 + config: WebRtcConfig, + + /// 事件总线 + events: Arc, +} + +impl WebRtcStreamer { + /// 创建流服务 + pub async fn new( + video_pipeline: Arc, + audio_pipeline: Arc, + hid: Arc, + config: WebRtcConfig, + events: Arc, + ) -> Result; + + /// 创建会话 + pub async fn create_session(&self) -> Result; + + /// 处理 Offer + pub async fn process_offer( + &self, + session_id: &str, + offer: &str, + codec: VideoCodec, + ) -> Result; + + /// 添加 ICE 候选 + pub async fn add_ice_candidate( + &self, + session_id: &str, + candidate: &str, + ) -> Result<()>; + + /// 关闭会话 + pub async fn close_session(&self, session_id: &str) -> Result<()>; + + /// 获取会话列表 + pub fn list_sessions(&self) -> Vec; + + /// 获取统计信息 + pub fn get_stats(&self) -> WebRtcStats; +} + +pub struct OfferResponse { + pub answer_sdp: String, + pub ice_candidates: Vec, +} + +pub struct WebRtcStats { + pub active_sessions: usize, + pub total_bytes_sent: u64, + pub avg_bitrate: u32, +} +``` + +### 3.2 UniversalSession (universal_session.rs) + +单个 WebRTC 会话。 + +```rust +pub struct UniversalSession { + /// 会话 ID + id: String, + + /// PeerConnection + peer: Arc, + + /// 视频轨道 + video_track: Arc, + + /// 音频轨道 + audio_track: Option>, + + /// HID DataChannel + hid_channel: Arc>>>, + + /// HID 处理器 + hid_handler: Arc, + + /// 状态 + state: Arc>, + + /// 编码器类型 + codec: VideoCodec, +} + +impl UniversalSession { + /// 创建会话 + pub async fn new( + id: String, + config: &WebRtcConfig, + video_pipeline: Arc, + audio_pipeline: Arc, + hid_handler: Arc, + codec: VideoCodec, + ) -> Result; + + /// 处理 Offer SDP + pub async fn handle_offer(&self, offer_sdp: &str) -> Result; + + /// 添加 ICE 候选 + pub async fn add_ice_candidate(&self, candidate: &str) -> Result<()>; + + /// 获取 ICE 候选 + pub fn get_ice_candidates(&self) -> Vec; + + /// 关闭会话 + pub async fn close(&self) -> Result<()>; + + /// 获取状态 + pub fn state(&self) -> SessionState; + + /// 获取统计 + pub fn stats(&self) -> SessionStats; +} + +pub enum SessionState { + New, + Connecting, + Connected, + Disconnected, + Failed, + Closed, +} + +pub struct SessionStats { + pub bytes_sent: u64, + pub packets_sent: u64, + pub bitrate: u32, + pub frame_rate: f32, + pub round_trip_time: Duration, +} +``` + +### 3.3 VideoTrack (video_track.rs) + +视频轨道封装。 + +```rust +pub struct UniversalVideoTrack { + /// 轨道 ID + id: String, + + /// 编码类型 + codec: VideoCodec, + + /// RTP 发送器 + rtp_sender: Arc, + + /// 帧计数 + frame_count: AtomicU64, + + /// 统计 + stats: Arc>, +} + +impl UniversalVideoTrack { + /// 创建轨道 + pub fn new(id: &str, codec: VideoCodec) -> Result; + + /// 发送编码帧 + pub async fn send_frame(&self, frame: &EncodedFrame) -> Result<()>; + + /// 获取 RTP 参数 + pub fn rtp_params(&self) -> RtpParameters; + + /// 获取统计 + pub fn stats(&self) -> TrackStats; +} + +pub struct TrackStats { + pub frames_sent: u64, + pub bytes_sent: u64, + pub packets_sent: u64, + pub packet_loss: f32, +} +``` + +### 3.4 RTP 打包 (rtp.rs) + +RTP 协议实现。 + +```rust +pub struct RtpPacketizer { + /// SSRC + ssrc: u32, + + /// 序列号 + sequence: u16, + + /// 时间戳 + timestamp: u32, + + /// 负载类型 + payload_type: u8, + + /// 时钟频率 + clock_rate: u32, +} + +impl RtpPacketizer { + /// 创建打包器 + pub fn new(codec: VideoCodec) -> Self; + + /// 打包 H264 帧 + pub fn packetize_h264(&mut self, frame: &[u8], keyframe: bool) -> Vec>; + + /// 打包 VP8 帧 + pub fn packetize_vp8(&mut self, frame: &[u8], keyframe: bool) -> Vec>; + + /// 打包 VP9 帧 + pub fn packetize_vp9(&mut self, frame: &[u8], keyframe: bool) -> Vec>; + + /// 打包 Opus 帧 + pub fn packetize_opus(&mut self, frame: &[u8]) -> Vec; +} + +/// H264 NAL 单元分片 +pub struct H264Fragmenter; + +impl H264Fragmenter { + /// 分片大于 MTU 的 NAL + pub fn fragment(nal: &[u8], mtu: usize) -> Vec>; + + /// 创建 STAP-A 聚合 + pub fn aggregate(nals: &[&[u8]]) -> Vec; +} +``` + +### 3.5 H265 打包器 (h265_payloader.rs) + +H265/HEVC RTP 打包。 + +```rust +pub struct H265Payloader { + /// MTU 大小 + mtu: usize, +} + +impl H265Payloader { + /// 创建打包器 + pub fn new(mtu: usize) -> Self; + + /// 打包 H265 帧 + pub fn packetize(&self, frame: &[u8]) -> Vec>; + + /// 分析 NAL 单元类型 + fn get_nal_type(nal: &[u8]) -> u8; + + /// 是否需要分片 + fn needs_fragmentation(&self, nal: &[u8]) -> bool; +} +``` + +--- + +## 4. 信令协议 + +### 4.1 创建会话 + +``` +POST /api/webrtc/session +Content-Type: application/json + +{} + +Response: +{ + "session_id": "abc123-def456" +} +``` + +### 4.2 发送 Offer + +``` +POST /api/webrtc/offer +Content-Type: application/json + +{ + "session_id": "abc123-def456", + "video_codec": "h264", + "enable_audio": true, + "offer_sdp": "v=0\r\no=- ..." +} + +Response: +{ + "answer_sdp": "v=0\r\no=- ...", + "ice_candidates": [ + "candidate:1 1 UDP ...", + "candidate:2 1 TCP ..." + ] +} +``` + +### 4.3 ICE 候选 + +``` +POST /api/webrtc/ice +Content-Type: application/json + +{ + "session_id": "abc123-def456", + "candidate": "candidate:1 1 UDP ..." +} + +Response: +{ + "success": true +} +``` + +### 4.4 关闭会话 + +``` +POST /api/webrtc/close +Content-Type: application/json + +{ + "session_id": "abc123-def456" +} + +Response: +{ + "success": true +} +``` + +--- + +## 5. 配置 + +```rust +#[derive(Serialize, Deserialize)] +#[typeshare] +pub struct WebRtcConfig { + /// STUN 服务器 + pub stun_servers: Vec, + + /// TURN 服务器 + pub turn_servers: Vec, + + /// 默认编码器 + pub default_codec: VideoCodec, + + /// 码率 (kbps) + pub bitrate_kbps: u32, + + /// GOP 大小 + pub gop_size: u32, + + /// 启用音频 + pub enable_audio: bool, + + /// 启用 DataChannel HID + pub enable_datachannel_hid: bool, +} + +pub struct TurnServer { + pub url: String, + pub username: String, + pub password: String, +} + +impl Default for WebRtcConfig { + fn default() -> Self { + Self { + stun_servers: vec!["stun:stun.l.google.com:19302".to_string()], + turn_servers: vec![], + default_codec: VideoCodec::H264, + bitrate_kbps: 2000, + gop_size: 60, + enable_audio: true, + enable_datachannel_hid: true, + } + } +} +``` + +--- + +## 6. DataChannel HID + +### 6.1 消息格式 + +```javascript +// 键盘事件 +{ + "type": "keyboard", + "keys": ["KeyA", "KeyB"], + "modifiers": { + "ctrl": false, + "shift": true, + "alt": false, + "meta": false + } +} + +// 鼠标事件 +{ + "type": "mouse", + "x": 16384, + "y": 16384, + "button": "left", + "event": "press" +} + +// 鼠标模式 +{ + "type": "mouse_mode", + "mode": "absolute" +} +``` + +### 6.2 处理流程 + +``` +DataChannel Message + │ + ▼ +┌─────────────────┐ +│Parse JSON Event │ +└────────┬────────┘ + │ + ▼ +┌─────────────────┐ +│HidDataChannel │ +│ Handler │ +└────────┬────────┘ + │ + ▼ +┌─────────────────┐ +│ HidController │ +└────────┬────────┘ + │ + ▼ + USB/Serial +``` + +--- + +## 7. 支持的编码器 + +| 编码器 | RTP 负载类型 | 时钟频率 | 硬件加速 | +|--------|-------------|---------|---------| +| H264 | 96 (动态) | 90000 | VAAPI/RKMPP/V4L2 | +| H265 | 97 (动态) | 90000 | VAAPI | +| VP8 | 98 (动态) | 90000 | VAAPI | +| VP9 | 99 (动态) | 90000 | VAAPI | +| Opus | 111 (动态) | 48000 | 无 (软件) | + +--- + +## 8. 错误处理 + +```rust +#[derive(Debug, thiserror::Error)] +pub enum WebRtcError { + #[error("Session not found: {0}")] + SessionNotFound(String), + + #[error("Session already exists")] + SessionExists, + + #[error("Invalid SDP: {0}")] + InvalidSdp(String), + + #[error("Codec not supported: {0}")] + CodecNotSupported(String), + + #[error("ICE failed")] + IceFailed, + + #[error("DTLS failed")] + DtlsFailed, + + #[error("Track error: {0}")] + TrackError(String), + + #[error("Connection closed")] + ConnectionClosed, +} +``` + +--- + +## 9. 使用示例 + +### 9.1 创建会话 + +```rust +let streamer = WebRtcStreamer::new( + video_pipeline, + audio_pipeline, + hid, + WebRtcConfig::default(), + events, +).await?; + +// 创建会话 +let session_id = streamer.create_session().await?; + +// 处理 Offer +let response = streamer.process_offer( + &session_id, + &offer_sdp, + VideoCodec::H264, +).await?; + +println!("Answer: {}", response.answer_sdp); +``` + +### 9.2 前端连接 + +```javascript +// 创建 PeerConnection +const pc = new RTCPeerConnection({ + iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] +}); + +// 创建 DataChannel +const hidChannel = pc.createDataChannel('hid'); + +// 创建 Offer +const offer = await pc.createOffer(); +await pc.setLocalDescription(offer); + +// 发送到服务器 +const response = await fetch('/api/webrtc/offer', { + method: 'POST', + body: JSON.stringify({ + session_id, + video_codec: 'h264', + offer_sdp: offer.sdp + }) +}); + +const { answer_sdp, ice_candidates } = await response.json(); + +// 设置 Answer +await pc.setRemoteDescription({ type: 'answer', sdp: answer_sdp }); + +// 添加 ICE 候选 +for (const candidate of ice_candidates) { + await pc.addIceCandidate({ candidate }); +} +``` + +--- + +## 10. 常见问题 + +### Q: 连接超时? + +1. 检查 STUN/TURN 配置 +2. 检查防火墙设置 +3. 尝试使用 TURN 中继 + +### Q: 视频卡顿? + +1. 降低分辨率/码率 +2. 检查网络带宽 +3. 使用硬件编码 + +### Q: 音频不同步? + +1. 检查时间戳同步 +2. 调整缓冲区大小 +3. 使用 NTP 同步 diff --git a/docs/report/hwcodec/00-architecture.md b/docs/report/hwcodec/00-architecture.md new file mode 100644 index 00000000..b9a9421e --- /dev/null +++ b/docs/report/hwcodec/00-architecture.md @@ -0,0 +1,550 @@ +# hwcodec 技术架构报告 + +## 1. 项目概述 + +hwcodec 是一个基于 FFmpeg 的硬件视频编解码库,来源于 RustDesk 项目并针对 One-KVM 进行了定制优化。该库提供跨平台的 GPU 加速视频编解码能力,支持多个 GPU 厂商和多种编码标准。 + +### 1.1 项目位置 + +``` +libs/hwcodec/ +├── src/ # Rust 源代码 +├── cpp/ # C++ 源代码 +├── externals/ # 外部依赖 (SDK) +├── dev/ # 开发工具 +└── examples/ # 示例程序 +``` + +### 1.2 核心特性 + +- **多编解码格式支持**: H.264, H.265 (HEVC), VP8, VP9, AV1, MJPEG +- **硬件加速**: NVENC/NVDEC, AMF, Intel QSV/MFX, VAAPI, RKMPP, V4L2 M2M, VideoToolbox +- **跨平台**: Windows, Linux, macOS, Android, iOS +- **低延迟优化**: 专为实时流媒体场景设计 +- **Rust/C++ 混合架构**: Rust 提供安全的上层 API,C++ 实现底层编解码逻辑 + +## 2. 架构设计 + +### 2.1 整体架构图 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Rust API Layer │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ +│ │ ffmpeg_ram │ │ vram │ │ mux │ │ +│ │ module │ │ module │ │ module │ │ +│ └──────┬──────┘ └──────┬──────┘ └──────────┬──────────┘ │ +├─────────┼────────────────┼───────────────────┼──────────────┤ +│ │ │ │ │ +│ │ FFI Bindings (bindgen) │ │ +│ ▼ ▼ ▼ │ +├─────────────────────────────────────────────────────────────┤ +│ C++ Core Layer │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ +│ │ ffmpeg_ram │ │ ffmpeg_vram │ │ mux.cpp │ │ +│ │ encode/ │ │ encode/ │ │ │ │ +│ │ decode │ │ decode │ │ │ │ +│ └──────┬──────┘ └──────┬──────┘ └──────────┬──────────┘ │ +├─────────┼────────────────┼───────────────────┼──────────────┤ +│ │ │ │ │ +│ └────────────────┴───────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ FFmpeg Libraries │ │ +│ │ libavcodec │ libavutil │ libavformat │ libswscale │ │ +│ └──────────────────────────────────────────────────────┘ │ +│ │ │ +├──────────────────────────┼──────────────────────────────────┤ +│ Hardware Acceleration Backends │ +│ ┌────────┐ ┌─────┐ ┌─────┐ ┌───────┐ ┌───────┐ ┌───────┐ │ +│ │ NVENC │ │ AMF │ │ MFX │ │ VAAPI │ │ RKMPP │ │V4L2M2M│ │ +│ └────────┘ └─────┘ └─────┘ └───────┘ └───────┘ └───────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 2.2 模块职责 + +| 模块 | 职责 | 关键文件 | +|------|------|----------| +| `ffmpeg_ram` | 基于 RAM 的软件/硬件编解码 | `src/ffmpeg_ram/` | +| `vram` | GPU 显存直接编解码 (Windows) | `src/vram/` | +| `mux` | 视频混流 (MP4/MKV) | `src/mux.rs` | +| `common` | 公共定义和 GPU 检测 | `src/common.rs` | +| `ffmpeg` | FFmpeg 日志和初始化 | `src/ffmpeg.rs` | + +## 3. 模块详细分析 + +### 3.1 库入口 (lib.rs) + +```rust +// libs/hwcodec/src/lib.rs +pub mod common; +pub mod ffmpeg; +pub mod ffmpeg_ram; +pub mod mux; +#[cfg(all(windows, feature = "vram"))] +pub mod vram; +#[cfg(target_os = "android")] +pub mod android; +``` + +**功能**: +- 导出所有子模块 +- 提供 C 日志回调函数 `hwcodec_log` +- 条件编译: `vram` 模块仅在 Windows + vram feature 启用时编译 + +### 3.2 公共模块 (common.rs) + +**核心类型**: + +```rust +pub enum Driver { + NV, // NVIDIA + AMF, // AMD + MFX, // Intel + FFMPEG, // 软件编码 +} +``` + +**GPU 检测函数**: + +| 平台 | 检测函数 | 检测方式 | +|------|----------|----------| +| Linux | `linux_support_nv()` | 加载 CUDA/NVENC 动态库 | +| Linux | `linux_support_amd()` | 检查 `libamfrt64.so.1` | +| Linux | `linux_support_intel()` | 检查 `libvpl.so`/`libmfx.so` | +| Linux | `linux_support_rkmpp()` | 检查 `/dev/mpp_service` | +| Linux | `linux_support_v4l2m2m()` | 检查 `/dev/video*` 设备 | +| macOS | `get_video_toolbox_codec_support()` | 调用 VideoToolbox API | +| Windows | 通过 VRAM 模块检测 | 查询 D3D11 设备 | + +### 3.3 FFmpeg RAM 编码模块 + +#### 3.3.1 Rust 层 (src/ffmpeg_ram/) + +**CodecInfo 结构体**: + +```rust +pub struct CodecInfo { + pub name: String, // 编码器名称如 "h264_nvenc" + pub mc_name: Option, // MediaCodec 名称 (Android) + pub format: DataFormat, // H264/H265/VP8/VP9/AV1/MJPEG + pub priority: i32, // 优先级 (Best=0, Good=1, Normal=2, Soft=3, Bad=4) + pub hwdevice: AVHWDeviceType, // 硬件设备类型 +} +``` + +**EncodeContext 结构体**: + +```rust +pub struct EncodeContext { + pub name: String, // 编码器名称 + pub width: i32, // 视频宽度 + pub height: i32, // 视频高度 + pub pixfmt: AVPixelFormat, // 像素格式 (NV12/YUV420P) + pub align: i32, // 内存对齐 + pub fps: i32, // 帧率 + pub gop: i32, // GOP 大小 + pub rc: RateControl, // 码率控制模式 + pub quality: Quality, // 质量级别 + pub kbs: i32, // 目标码率 (kbps) + pub q: i32, // 量化参数 + pub thread_count: i32, // 线程数 +} +``` + +**Encoder 类**: + +```rust +pub struct Encoder { + codec: *mut c_void, // C++ 编码器指针 + frames: *mut Vec, // 编码输出帧 + pub ctx: EncodeContext, + pub linesize: Vec, // 行大小 + pub offset: Vec, // 平面偏移 + pub length: i32, // 总数据长度 +} +``` + +**核心方法**: + +| 方法 | 功能 | +|------|------| +| `Encoder::new()` | 创建编码器实例 | +| `Encoder::encode()` | 编码一帧 YUV 数据 | +| `Encoder::set_bitrate()` | 动态调整码率 | +| `Encoder::request_keyframe()` | 请求下一帧为关键帧 | +| `Encoder::available_encoders()` | 检测系统可用编码器 | + +#### 3.3.2 C++ 层 (cpp/ffmpeg_ram/) + +**FFmpegRamEncoder 类** (ffmpeg_ram_encode.cpp:97-420): + +```cpp +class FFmpegRamEncoder { + AVCodecContext *c_ = NULL; // FFmpeg 编码上下文 + AVFrame *frame_ = NULL; // 输入帧 + AVPacket *pkt_ = NULL; // 编码输出包 + AVBufferRef *hw_device_ctx_; // 硬件设备上下文 + AVFrame *hw_frame_ = NULL; // 硬件帧 + bool force_keyframe_ = false; // 强制关键帧标志 + + // 主要方法 + bool init(int *linesize, int *offset, int *length); + int encode(const uint8_t *data, int length, const void *obj, uint64_t ms); + int do_encode(AVFrame *frame, const void *obj, int64_t ms); + int set_hwframe_ctx(); // 设置硬件帧上下文 +}; +``` + +**编码流程**: + +``` +输入 YUV 数据 + │ + ▼ +fill_frame() - 填充 AVFrame 数据指针 + │ + ├──▶ (软件编码) 直接使用 frame_ + │ + └──▶ (硬件编码) av_hwframe_transfer_data() 传输到 GPU + │ + ▼ + 使用 hw_frame_ + │ + ▼ + avcodec_send_frame() - 发送帧到编码器 + │ + ▼ + avcodec_receive_packet() - 获取编码数据 + │ + ▼ + callback() - 回调输出 +``` + +### 3.4 FFmpeg RAM 解码模块 + +**Decoder 类**: + +```rust +pub struct Decoder { + codec: *mut c_void, + frames: *mut Vec, + pub ctx: DecodeContext, +} + +pub struct DecodeFrame { + pub pixfmt: AVPixelFormat, + pub width: i32, + pub height: i32, + pub data: Vec>, // Y, U, V 平面数据 + pub linesize: Vec, + pub key: bool, +} +``` + +**C++ 实现** (ffmpeg_ram_decode.cpp): + +```cpp +class FFmpegRamDecoder { + AVCodecContext *c_ = NULL; + AVBufferRef *hw_device_ctx_ = NULL; + AVFrame *sw_frame_ = NULL; // 软件帧 (用于硬件→软件转换) + AVFrame *frame_ = NULL; // 解码输出帧 + AVPacket *pkt_ = NULL; + bool hwaccel_ = true; + + int do_decode(const void *obj); +}; +``` + +**解码流程**: + +``` +输入编码数据 + │ + ▼ +avcodec_send_packet() - 发送数据到解码器 + │ + ▼ +avcodec_receive_frame() - 获取解码帧 + │ + ├──▶ (软件解码) 直接使用 frame_ + │ + └──▶ (硬件解码) av_hwframe_transfer_data() + │ + ▼ + sw_frame_ (GPU → CPU) + │ + ▼ + callback() - 回调输出 +``` + +## 4. 硬件加速支持 + +### 4.1 支持的硬件加速后端 + +| 后端 | 厂商 | 平台 | 编码器名称 | +|------|------|------|-----------| +| NVENC | NVIDIA | Windows/Linux | h264_nvenc, hevc_nvenc | +| AMF | AMD | Windows/Linux | h264_amf, hevc_amf | +| QSV | Intel | Windows | h264_qsv, hevc_qsv | +| VAAPI | 通用 | Linux | h264_vaapi, hevc_vaapi, vp8_vaapi, vp9_vaapi | +| RKMPP | Rockchip | Linux | h264_rkmpp, hevc_rkmpp | +| V4L2 M2M | ARM SoC | Linux | h264_v4l2m2m, hevc_v4l2m2m | +| VideoToolbox | Apple | macOS/iOS | hevc_videotoolbox | +| MediaCodec | Google | Android | h264_mediacodec, hevc_mediacodec | + +### 4.2 硬件检测逻辑 (Linux) + +```cpp +// libs/hwcodec/cpp/common/platform/linux/linux.cpp + +// NVIDIA 检测 - 加载 CUDA 和 NVENC 动态库 +int linux_support_nv() { + CudaFunctions *cuda_dl = NULL; + NvencFunctions *nvenc_dl = NULL; + CuvidFunctions *cvdl = NULL; + load_driver(&cuda_dl, &nvenc_dl, &cvdl); + // 成功加载则返回 0 +} + +// AMD 检测 - 检查 AMF 运行时库 +int linux_support_amd() { + void *handle = dlopen("libamfrt64.so.1", RTLD_LAZY); + // 成功加载则返回 0 +} + +// Intel 检测 - 检查 VPL/MFX 库 +int linux_support_intel() { + const char *libs[] = {"libvpl.so", "libmfx.so", ...}; + // 任一成功加载则返回 0 +} + +// Rockchip MPP 检测 - 检查设备节点 +int linux_support_rkmpp() { + if (access("/dev/mpp_service", F_OK) == 0) return 0; + if (access("/dev/rga", F_OK) == 0) return 0; + return -1; +} + +// V4L2 M2M 检测 - 检查视频设备 +int linux_support_v4l2m2m() { + const char *devices[] = {"/dev/video10", "/dev/video11", ...}; + // 任一设备可打开则返回 0 +} +``` + +### 4.3 编码器优先级系统 + +```rust +pub enum Priority { + Best = 0, // 最高优先级 (硬件加速) + Good = 1, // 良好 (VAAPI, 部分硬件) + Normal = 2, // 普通 + Soft = 3, // 软件编码 + Bad = 4, // 最低优先级 +} +``` + +**优先级分配**: + +| 编码器 | 优先级 | +|--------|--------| +| h264_nvenc, hevc_nvenc | Best (0) | +| h264_amf, hevc_amf | Best (0) | +| h264_qsv, hevc_qsv | Best (0) | +| h264_rkmpp, hevc_rkmpp | Best (0) | +| h264_vaapi, hevc_vaapi | Good (1) | +| h264_v4l2m2m, hevc_v4l2m2m | Good (1) | +| h264 (x264), hevc (x265) | Soft (3) | + +### 4.4 低延迟优化配置 + +```cpp +// libs/hwcodec/cpp/common/util.cpp + +bool set_lantency_free(void *priv_data, const std::string &name) { + // NVENC: 禁用延迟缓冲 + if (name.find("nvenc") != std::string::npos) { + av_opt_set(priv_data, "delay", "0", 0); + } + // AMF: 设置查询超时 + if (name.find("amf") != std::string::npos) { + av_opt_set(priv_data, "query_timeout", "1000", 0); + } + // QSV/VAAPI: 设置异步深度为 1 + if (name.find("qsv") != std::string::npos || + name.find("vaapi") != std::string::npos) { + av_opt_set(priv_data, "async_depth", "1", 0); + } + // VideoToolbox: 实时模式 + if (name.find("videotoolbox") != std::string::npos) { + av_opt_set_int(priv_data, "realtime", 1, 0); + av_opt_set_int(priv_data, "prio_speed", 1, 0); + } + // libvpx: 实时模式 + if (name.find("libvpx") != std::string::npos) { + av_opt_set(priv_data, "deadline", "realtime", 0); + av_opt_set_int(priv_data, "cpu-used", 6, 0); + av_opt_set_int(priv_data, "lag-in-frames", 0, 0); + } + return true; +} +``` + +## 5. 混流模块 (Mux) + +### 5.1 功能概述 + +混流模块提供将编码后的视频流写入容器格式 (MP4/MKV) 的功能。 + +### 5.2 Rust API + +```rust +// libs/hwcodec/src/mux.rs + +pub struct MuxContext { + pub filename: String, // 输出文件名 + pub width: usize, // 视频宽度 + pub height: usize, // 视频高度 + pub is265: bool, // 是否为 H.265 + pub framerate: usize, // 帧率 +} + +pub struct Muxer { + inner: *mut c_void, // C++ Muxer 指针 + pub ctx: MuxContext, + start: Instant, // 开始时间 +} + +impl Muxer { + pub fn new(ctx: MuxContext) -> Result; + pub fn write_video(&mut self, data: &[u8], key: bool) -> Result<(), i32>; + pub fn write_tail(&mut self) -> Result<(), i32>; +} +``` + +### 5.3 C++ 实现 + +```cpp +// libs/hwcodec/cpp/mux/mux.cpp + +class Muxer { + OutputStream video_st; // 视频流 + AVFormatContext *oc = NULL; // 格式上下文 + int framerate; + int64_t start_ms; // 起始时间戳 + int64_t last_pts; // 上一帧 PTS + int got_first; // 是否收到第一帧 + + bool init(const char *filename, int width, int height, + int is265, int framerate); + int write_video_frame(const uint8_t *data, int len, + int64_t pts_ms, int key); +}; +``` + +**写入流程**: + +``` +write_video_frame() + │ + ├── 检查是否为关键帧 (第一帧必须是关键帧) + │ + ├── 计算 PTS (相对于 start_ms) + │ + ├── 填充 AVPacket + │ + ├── av_packet_rescale_ts() (ms → stream timebase) + │ + └── av_write_frame() → 写入文件 +``` + +## 6. 构建系统 + +### 6.1 Cargo.toml 配置 + +```toml +[package] +name = "hwcodec" +version = "0.7.1" + +[features] +default = [] +vram = [] # GPU VRAM 直接编解码 (Windows only) + +[dependencies] +log = "0.4" +serde_derive = "1.0" +serde = "1.0" +serde_json = "1.0" + +[build-dependencies] +cc = "1.0" # C++ 编译 +bindgen = "0.59" # FFI 绑定生成 +``` + +### 6.2 构建流程 (build.rs) + +``` +build.rs + │ + ├── build_common() + │ ├── 生成 common_ffi.rs (bindgen) + │ ├── 编译平台相关 C++ 代码 + │ └── 链接系统库 (d3d11, dxgi, stdc++) + │ + ├── ffmpeg::build_ffmpeg() + │ ├── 生成 ffmpeg_ffi.rs + │ ├── 链接 FFmpeg 库 (VCPKG 或 pkg-config) + │ ├── build_ffmpeg_ram() + │ │ └── 编译 ffmpeg_ram_encode.cpp, ffmpeg_ram_decode.cpp + │ ├── build_ffmpeg_vram() [vram feature] + │ │ └── 编译 ffmpeg_vram_encode.cpp, ffmpeg_vram_decode.cpp + │ └── build_mux() + │ └── 编译 mux.cpp + │ + └── sdk::build_sdk() [Windows + vram feature] + ├── build_nv() - NVIDIA SDK + ├── build_amf() - AMD AMF + └── build_mfx() - Intel MFX +``` + +### 6.3 FFmpeg 链接方式 + +| 方式 | 平台 | 条件 | +|------|------|------| +| VCPKG 静态链接 | 跨平台 | 设置 `VCPKG_ROOT` 环境变量 | +| pkg-config 动态链接 | Linux | 默认方式 | + +## 7. 外部依赖 + +### 7.1 SDK 版本 + +| SDK | 版本 | 用途 | +|-----|------|------| +| nv-codec-headers | n12.1.14.0 | NVIDIA 编码头文件 | +| Video_Codec_SDK | 12.1.14 | NVIDIA 编解码 SDK | +| AMF | v1.4.35 | AMD Advanced Media Framework | +| MediaSDK | 22.5.4 | Intel Media SDK | + +### 7.2 FFmpeg 依赖库 + +``` +libavcodec - 编解码核心 +libavutil - 工具函数 +libavformat - 容器格式 +libswscale - 图像缩放转换 +``` + +## 8. 总结 + +hwcodec 库通过 Rust/C++ 混合架构,在保证内存安全的同时实现了高性能的视频编解码。其核心设计特点包括: + +1. **统一的编解码器 API**: 无论使用硬件还是软件编解码,上层 API 保持一致 +2. **自动硬件检测**: 运行时自动检测并选择最优的硬件加速后端 +3. **优先级系统**: 基于质量和性能为不同编码器分配优先级 +4. **低延迟优化**: 针对实时流媒体场景进行了专门优化 +5. **跨平台支持**: 覆盖主流操作系统和 GPU 厂商 diff --git a/docs/report/hwcodec/01-api-reference.md b/docs/report/hwcodec/01-api-reference.md new file mode 100644 index 00000000..bd1f50b4 --- /dev/null +++ b/docs/report/hwcodec/01-api-reference.md @@ -0,0 +1,445 @@ +# hwcodec 编解码器 API 详解 + +## 1. 编码器 API + +### 1.1 编码器初始化 + +#### EncodeContext 参数 + +```rust +pub struct EncodeContext { + pub name: String, // 编码器名称 + pub mc_name: Option, // MediaCodec 名称 (Android) + pub width: i32, // 视频宽度 (必须为偶数) + pub height: i32, // 视频高度 (必须为偶数) + pub pixfmt: AVPixelFormat, // 像素格式 + pub align: i32, // 内存对齐 (通常为 0 或 32) + pub fps: i32, // 帧率 + pub gop: i32, // GOP 大小 (关键帧间隔) + pub rc: RateControl, // 码率控制模式 + pub quality: Quality, // 编码质量 + pub kbs: i32, // 目标码率 (kbps) + pub q: i32, // 量化参数 (CQ 模式) + pub thread_count: i32, // 编码线程数 +} +``` + +#### 参数说明 + +| 参数 | 类型 | 说明 | 推荐值 | +|------|------|------|--------| +| `name` | String | FFmpeg 编码器名称 | 见下表 | +| `width` | i32 | 视频宽度 | 1920 | +| `height` | i32 | 视频高度 | 1080 | +| `pixfmt` | AVPixelFormat | 像素格式 | NV12 / YUV420P | +| `align` | i32 | 内存对齐 | 0 (自动) | +| `fps` | i32 | 帧率 | 30 | +| `gop` | i32 | GOP 大小 | 30 (1秒) | +| `rc` | RateControl | 码率控制 | CBR / VBR | +| `quality` | Quality | 质量级别 | Medium | +| `kbs` | i32 | 码率 (kbps) | 2000-8000 | +| `thread_count` | i32 | 线程数 | 4 | + +#### 编码器名称对照表 + +| 名称 | 格式 | 加速 | 平台 | +|------|------|------|------| +| `h264_nvenc` | H.264 | NVIDIA GPU | Windows/Linux | +| `hevc_nvenc` | H.265 | NVIDIA GPU | Windows/Linux | +| `h264_amf` | H.264 | AMD GPU | Windows/Linux | +| `hevc_amf` | H.265 | AMD GPU | Windows/Linux | +| `h264_qsv` | H.264 | Intel QSV | Windows | +| `hevc_qsv` | H.265 | Intel QSV | Windows | +| `h264_vaapi` | H.264 | VAAPI | Linux | +| `hevc_vaapi` | H.265 | VAAPI | Linux | +| `vp8_vaapi` | VP8 | VAAPI | Linux | +| `vp9_vaapi` | VP9 | VAAPI | Linux | +| `h264_rkmpp` | H.264 | Rockchip MPP | Linux | +| `hevc_rkmpp` | H.265 | Rockchip MPP | Linux | +| `h264_v4l2m2m` | H.264 | V4L2 M2M | Linux | +| `hevc_v4l2m2m` | H.265 | V4L2 M2M | Linux | +| `hevc_videotoolbox` | H.265 | VideoToolbox | macOS | +| `h264` | H.264 | 软件 (x264) | 全平台 | +| `hevc` | H.265 | 软件 (x265) | 全平台 | +| `libvpx` | VP8 | 软件 | 全平台 | +| `libvpx-vp9` | VP9 | 软件 | 全平台 | +| `mjpeg` | MJPEG | 软件 | 全平台 | + +### 1.2 创建编码器 + +```rust +use hwcodec::ffmpeg_ram::encode::{Encoder, EncodeContext}; +use hwcodec::ffmpeg::{AVPixelFormat}; +use hwcodec::common::{RateControl, Quality}; + +let ctx = EncodeContext { + name: "h264_vaapi".to_string(), + mc_name: None, + width: 1920, + height: 1080, + pixfmt: AVPixelFormat::AV_PIX_FMT_NV12, + align: 0, + fps: 30, + gop: 30, + rc: RateControl::RC_CBR, + quality: Quality::Quality_Medium, + kbs: 4000, + q: 0, + thread_count: 4, +}; + +let encoder = Encoder::new(ctx)?; +println!("Linesize: {:?}", encoder.linesize); +println!("Offset: {:?}", encoder.offset); +println!("Buffer length: {}", encoder.length); +``` + +### 1.3 编码帧 + +```rust +// 准备 YUV 数据 +let yuv_data: Vec = prepare_yuv_frame(); + +// 编码 +let pts_ms: i64 = 0; // 时间戳 (毫秒) +match encoder.encode(&yuv_data, pts_ms) { + Ok(frames) => { + for frame in frames.iter() { + println!("Encoded: {} bytes, pts={}, key={}", + frame.data.len(), frame.pts, frame.key); + // 发送 frame.data + } + } + Err(code) => { + eprintln!("Encode error: {}", code); + } +} +``` + +### 1.4 动态调整码率 + +```rust +// 动态调整到 6000 kbps +encoder.set_bitrate(6000)?; +``` + +### 1.5 请求关键帧 + +```rust +// 下一帧强制编码为 IDR 帧 +encoder.request_keyframe(); +``` + +### 1.6 检测可用编码器 + +```rust +use hwcodec::ffmpeg_ram::encode::{Encoder, EncodeContext}; +use hwcodec::ffmpeg_ram::CodecInfo; + +let ctx = EncodeContext { + name: String::new(), + mc_name: None, + width: 1920, + height: 1080, + pixfmt: AVPixelFormat::AV_PIX_FMT_NV12, + align: 0, + fps: 30, + gop: 30, + rc: RateControl::RC_DEFAULT, + quality: Quality::Quality_Default, + kbs: 4000, + q: 0, + thread_count: 4, +}; + +let available_encoders = Encoder::available_encoders(ctx, None); +for encoder in available_encoders { + println!("Available: {} (format: {:?}, priority: {})", + encoder.name, encoder.format, encoder.priority); +} +``` + +## 2. 解码器 API + +### 2.1 解码器初始化 + +#### DecodeContext 参数 + +```rust +pub struct DecodeContext { + pub name: String, // 解码器名称 + pub device_type: AVHWDeviceType, // 硬件设备类型 + pub thread_count: i32, // 解码线程数 +} +``` + +#### 硬件设备类型 + +| AVHWDeviceType | 说明 | +|----------------|------| +| `AV_HWDEVICE_TYPE_NONE` | 软件解码 | +| `AV_HWDEVICE_TYPE_CUDA` | NVIDIA CUDA | +| `AV_HWDEVICE_TYPE_VAAPI` | Linux VAAPI | +| `AV_HWDEVICE_TYPE_D3D11VA` | Windows D3D11 | +| `AV_HWDEVICE_TYPE_VIDEOTOOLBOX` | macOS VideoToolbox | +| `AV_HWDEVICE_TYPE_MEDIACODEC` | Android MediaCodec | + +### 2.2 创建解码器 + +```rust +use hwcodec::ffmpeg_ram::decode::{Decoder, DecodeContext}; +use hwcodec::ffmpeg::AVHWDeviceType; + +let ctx = DecodeContext { + name: "h264".to_string(), + device_type: AVHWDeviceType::AV_HWDEVICE_TYPE_VAAPI, + thread_count: 4, +}; + +let decoder = Decoder::new(ctx)?; +``` + +### 2.3 解码帧 + +```rust +// 输入编码数据 +let encoded_packet: Vec = receive_encoded_data(); + +match decoder.decode(&encoded_packet) { + Ok(frames) => { + for frame in frames.iter() { + println!("Decoded: {}x{}, format={:?}, key={}", + frame.width, frame.height, frame.pixfmt, frame.key); + + // 访问 YUV 数据 + let y_plane = &frame.data[0]; + let u_plane = &frame.data[1]; + let v_plane = &frame.data[2]; // 仅 YUV420P + } + } + Err(code) => { + eprintln!("Decode error: {}", code); + } +} +``` + +### 2.4 DecodeFrame 结构体 + +```rust +pub struct DecodeFrame { + pub pixfmt: AVPixelFormat, // 输出像素格式 + pub width: i32, // 帧宽度 + pub height: i32, // 帧高度 + pub data: Vec>, // 平面数据 [Y, U, V] 或 [Y, UV] + pub linesize: Vec, // 每个平面的行字节数 + pub key: bool, // 是否为关键帧 +} +``` + +#### 像素格式与平面布局 + +| 像素格式 | 平面数 | data[0] | data[1] | data[2] | +|----------|--------|---------|---------|---------| +| `YUV420P` | 3 | Y | U | V | +| `YUVJ420P` | 3 | Y | U | V | +| `YUV422P` | 3 | Y | U | V | +| `NV12` | 2 | Y | UV (交错) | - | +| `NV21` | 2 | Y | VU (交错) | - | + +### 2.5 检测可用解码器 + +```rust +use hwcodec::ffmpeg_ram::decode::Decoder; + +let available_decoders = Decoder::available_decoders(); +for decoder in available_decoders { + println!("Available: {} (format: {:?}, hwdevice: {:?})", + decoder.name, decoder.format, decoder.hwdevice); +} +``` + +## 3. 码率控制模式 + +### 3.1 RateControl 枚举 + +```rust +pub enum RateControl { + RC_DEFAULT, // 使用编码器默认 + RC_CBR, // 恒定码率 + RC_VBR, // 可变码率 + RC_CQ, // 恒定质量 (需设置 q 参数) +} +``` + +### 3.2 模式说明 + +| 模式 | 说明 | 适用场景 | +|------|------|----------| +| `RC_CBR` | 码率恒定,质量随场景变化 | 网络带宽受限 | +| `RC_VBR` | 质量优先,码率波动 | 本地存储 | +| `RC_CQ` | 恒定质量,码率波动大 | 质量敏感场景 | + +### 3.3 各编码器支持情况 + +| 编码器 | CBR | VBR | CQ | +|--------|-----|-----|-----| +| nvenc | ✓ | ✓ | ✓ | +| amf | ✓ | ✓ (低延迟) | ✗ | +| qsv | ✓ | ✓ | ✗ | +| vaapi | ✓ | ✓ | ✗ | +| mediacodec | ✓ | ✓ | ✓ | + +## 4. 质量等级 + +### 4.1 Quality 枚举 + +```rust +pub enum Quality { + Quality_Default, // 使用编码器默认 + Quality_High, // 高质量 (慢速) + Quality_Medium, // 中等质量 (平衡) + Quality_Low, // 低质量 (快速) +} +``` + +### 4.2 编码器预设映射 + +| 质量 | nvenc | amf | qsv | +|------|-------|-----|-----| +| High | - | quality | veryslow | +| Medium | p4 | balanced | medium | +| Low | p1 | speed | veryfast | + +## 5. 混流器 API + +### 5.1 创建混流器 + +```rust +use hwcodec::mux::{Muxer, MuxContext}; + +let ctx = MuxContext { + filename: "/tmp/output.mp4".to_string(), + width: 1920, + height: 1080, + is265: false, // H.264 + framerate: 30, +}; + +let muxer = Muxer::new(ctx)?; +``` + +### 5.2 写入视频帧 + +```rust +// 编码后的帧数据 +let encoded_data: Vec = encoder.encode(...)?; +let is_keyframe = true; + +muxer.write_video(&encoded_data, is_keyframe)?; +``` + +### 5.3 完成写入 + +```rust +// 写入文件尾 +muxer.write_tail()?; +// muxer 被 drop 时自动释放资源 +``` + +## 6. 错误处理 + +### 6.1 错误码 + +| 错误码 | 常量 | 说明 | +|--------|------|------| +| 0 | `HWCODEC_SUCCESS` | 成功 | +| -1 | `HWCODEC_ERR_COMMON` | 通用错误 | +| -2 | `HWCODEC_ERR_HEVC_COULD_NOT_FIND_POC` | HEVC 解码参考帧丢失 | + +### 6.2 常见错误处理 + +```rust +match encoder.encode(&yuv_data, pts) { + Ok(frames) => { + // 处理编码帧 + } + Err(-1) => { + eprintln!("编码失败,可能是输入数据格式错误"); + } + Err(code) => { + eprintln!("未知错误: {}", code); + } +} +``` + +## 7. 最佳实践 + +### 7.1 编码器选择策略 + +```rust +fn select_best_encoder( + width: i32, + height: i32, + format: DataFormat +) -> Option { + let ctx = EncodeContext { + width, + height, + pixfmt: AVPixelFormat::AV_PIX_FMT_NV12, + // ... 其他参数 + }; + + let encoders = Encoder::available_encoders(ctx, None); + + // 按优先级排序,选择最佳 + encoders.into_iter() + .filter(|e| e.format == format) + .min_by_key(|e| e.priority) + .map(|e| e.name) +} +``` + +### 7.2 帧内存布局 + +```rust +// 获取 NV12 帧布局信息 +let (linesize, offset, length) = ffmpeg_linesize_offset_length( + AVPixelFormat::AV_PIX_FMT_NV12, + 1920, + 1080, + 0, // align +)?; + +// 分配缓冲区 +let mut buffer = vec![0u8; length as usize]; + +// 填充 Y 平面: buffer[0..offset[0]] +// 填充 UV 平面: buffer[offset[0]..length] +``` + +### 7.3 关键帧控制 + +```rust +let mut frame_count = 0; + +loop { + // 每 30 帧强制一个关键帧 + if frame_count % 30 == 0 { + encoder.request_keyframe(); + } + + encoder.encode(&yuv_data, pts)?; + frame_count += 1; +} +``` + +### 7.4 线程安全 + +```rust +// Decoder 实现了 Send + Sync +unsafe impl Send for Decoder {} +unsafe impl Sync for Decoder {} + +// 可以安全地在多线程间传递 +let decoder = Arc::new(Mutex::new(Decoder::new(ctx)?)); +``` diff --git a/docs/report/hwcodec/02-hardware-acceleration.md b/docs/report/hwcodec/02-hardware-acceleration.md new file mode 100644 index 00000000..e71efa80 --- /dev/null +++ b/docs/report/hwcodec/02-hardware-acceleration.md @@ -0,0 +1,615 @@ +# hwcodec 硬件加速详解 + +## 1. 硬件加速架构 + +### 1.1 整体流程 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 应用层 (Rust) │ +│ ┌─────────────────────────────────────────────────────────┐│ +│ │ Encoder::available_encoders() → 自动检测可用硬件编码器 ││ +│ └─────────────────────────────────────────────────────────┘│ +└────────────────────────────┬────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 硬件检测层 (C++) │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐│ +│ │linux_ │ │linux_ │ │linux_ │ │linux_support_ ││ +│ │support_nv│ │support_ │ │support_ │ │rkmpp/v4l2m2m ││ +│ └────┬─────┘ │amd │ │intel │ └─────────┬────────┘│ +│ │ └────┬─────┘ └────┬─────┘ │ │ +└───────┼────────────┼────────────┼─────────────────┼─────────┘ + │ │ │ │ + ▼ ▼ ▼ ▼ +┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────────────┐ +│ CUDA/ │ │ AMF │ │ VPL/MFX │ │ 设备节点检测 │ +│ NVENC │ │ Runtime │ │ Library │ │ /dev/mpp_service │ +│ 动态库 │ │ 动态库 │ │ 动态库 │ │ /dev/video* │ +└───────────┘ └───────────┘ └───────────┘ └───────────────────┘ +``` + +### 1.2 编码器测试验证 + +每个检测到的硬件编码器都会进行实际编码测试: + +```rust +// libs/hwcodec/src/ffmpeg_ram/encode.rs:358-450 + +// 生成测试用 YUV 数据 +let yuv = Encoder::dummy_yuv(ctx.clone())?; + +// 尝试创建编码器并编码测试帧 +match Encoder::new(c) { + Ok(mut encoder) => { + let start = std::time::Instant::now(); + match encoder.encode(&yuv, 0) { + Ok(frames) => { + let elapsed = start.elapsed().as_millis(); + // 验证: 必须产生 1 帧且为关键帧,且在 1 秒内完成 + if frames.len() == 1 && frames[0].key == 1 + && elapsed < TEST_TIMEOUT_MS { + res.push(codec); + } + } + Err(_) => { /* 编码失败,跳过 */ } + } + } + Err(_) => { /* 创建失败,跳过 */ } +} +``` + +## 2. NVIDIA NVENC/NVDEC + +### 2.1 检测机制 (Linux) + +```cpp +// libs/hwcodec/cpp/common/platform/linux/linux.cpp:57-73 + +int linux_support_nv() { + CudaFunctions *cuda_dl = NULL; + NvencFunctions *nvenc_dl = NULL; + CuvidFunctions *cvdl = NULL; + + // 加载 CUDA 动态库 + if (cuda_load_functions(&cuda_dl, NULL) < 0) + throw "cuda_load_functions failed"; + + // 加载 NVENC 动态库 + if (nvenc_load_functions(&nvenc_dl, NULL) < 0) + throw "nvenc_load_functions failed"; + + // 加载 CUVID (解码) 动态库 + if (cuvid_load_functions(&cvdl, NULL) < 0) + throw "cuvid_load_functions failed"; + + // 全部成功则支持 NVIDIA 硬件加速 + return 0; +} +``` + +### 2.2 编码配置 + +```cpp +// libs/hwcodec/cpp/common/util.cpp + +// NVENC 低延迟配置 +if (name.find("nvenc") != std::string::npos) { + // 禁用编码延迟 + av_opt_set(priv_data, "delay", "0", 0); +} + +// GPU 选择 +if (name.find("nvenc") != std::string::npos) { + av_opt_set_int(priv_data, "gpu", gpu_index, 0); +} + +// 质量预设 +switch (quality) { + case Quality_Medium: + av_opt_set(priv_data, "preset", "p4", 0); + break; + case Quality_Low: + av_opt_set(priv_data, "preset", "p1", 0); + break; +} + +// 码率控制 +av_opt_set(priv_data, "rc", "cbr", 0); // 或 "vbr" +``` + +### 2.3 环境变量 + +| 变量 | 说明 | +|------|------| +| `RUSTDESK_HWCODEC_NVENC_GPU` | 指定使用的 GPU 索引 (-1 = 自动) | + +### 2.4 依赖库 + +- `libcuda.so` - CUDA 运行时 +- `libnvidia-encode.so` - NVENC 编码器 +- `libnvcuvid.so` - NVDEC 解码器 + +## 3. AMD AMF + +### 3.1 检测机制 (Linux) + +```cpp +// libs/hwcodec/cpp/common/platform/linux/linux.cpp:75-91 + +int linux_support_amd() { +#if defined(__x86_64__) || defined(__aarch64__) + #define AMF_DLL_NAMEA "libamfrt64.so.1" +#else + #define AMF_DLL_NAMEA "libamfrt32.so.1" +#endif + + void *handle = dlopen(AMF_DLL_NAMEA, RTLD_LAZY); + if (!handle) { + return -1; // AMF 不可用 + } + dlclose(handle); + return 0; // AMF 可用 +} +``` + +### 3.2 编码配置 + +```cpp +// libs/hwcodec/cpp/common/util.cpp + +// AMF 低延迟配置 +if (name.find("amf") != std::string::npos) { + av_opt_set(priv_data, "query_timeout", "1000", 0); +} + +// 质量预设 +switch (quality) { + case Quality_High: + av_opt_set(priv_data, "quality", "quality", 0); + break; + case Quality_Medium: + av_opt_set(priv_data, "quality", "balanced", 0); + break; + case Quality_Low: + av_opt_set(priv_data, "quality", "speed", 0); + break; +} + +// 码率控制 +av_opt_set(priv_data, "rc", "cbr", 0); // 恒定码率 +av_opt_set(priv_data, "rc", "vbr_latency", 0); // 低延迟 VBR +``` + +### 3.3 依赖库 + +- `libamfrt64.so.1` (64位) 或 `libamfrt32.so.1` (32位) + +### 3.4 外部 SDK + +``` +externals/AMF_v1.4.35/ +├── amf/ +│ ├── public/common/ # 公共代码 +│ │ ├── AMFFactory.cpp +│ │ ├── Thread.cpp +│ │ └── TraceAdapter.cpp +│ └── public/include/ # 头文件 +│ ├── components/ # 组件定义 +│ └── core/ # 核心定义 +``` + +## 4. Intel QSV/MFX + +### 4.1 检测机制 (Linux) + +```cpp +// libs/hwcodec/cpp/common/platform/linux/linux.cpp:93-107 + +int linux_support_intel() { + const char *libs[] = { + "libvpl.so", // oneVPL (新版) + "libmfx.so", // Media SDK + "libmfx-gen.so.1.2", // 新驱动 + "libmfxhw64.so.1" // 旧版驱动 + }; + + for (size_t i = 0; i < sizeof(libs) / sizeof(libs[0]); i++) { + void *handle = dlopen(libs[i], RTLD_LAZY); + if (handle) { + dlclose(handle); + return 0; // 找到可用库 + } + } + return -1; // Intel MFX 不可用 +} +``` + +### 4.2 编码配置 + +```cpp +// libs/hwcodec/cpp/common/util.cpp + +// QSV 低延迟配置 +if (name.find("qsv") != std::string::npos) { + av_opt_set(priv_data, "async_depth", "1", 0); +} + +// QSV 特殊码率配置 +if (name.find("qsv") != std::string::npos) { + c->rc_max_rate = c->bit_rate; + c->bit_rate--; // 实现 CBR 效果 +} + +// 质量预设 +switch (quality) { + case Quality_High: + av_opt_set(priv_data, "preset", "veryslow", 0); + break; + case Quality_Medium: + av_opt_set(priv_data, "preset", "medium", 0); + break; + case Quality_Low: + av_opt_set(priv_data, "preset", "veryfast", 0); + break; +} + +// 严格标准兼容性 (用于某些特殊设置) +c->strict_std_compliance = FF_COMPLIANCE_UNOFFICIAL; +``` + +### 4.3 限制 + +- QSV 不支持 `YUV420P` 像素格式,必须使用 `NV12` +- 仅在 Windows 平台完全支持 + +### 4.4 外部 SDK + +``` +externals/MediaSDK_22.5.4/ +├── api/ +│ ├── include/ # MFX 头文件 +│ ├── mfx_dispatch/ # MFX 调度器 +│ └── mediasdk_structures/ # 数据结构 +└── samples/sample_common/ # 示例代码 +``` + +## 5. VAAPI (Linux) + +### 5.1 工作原理 + +VAAPI (Video Acceleration API) 是 Linux 上的通用硬件视频加速接口: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Application │ +├─────────────────────────────────────────────────────────────┤ +│ FFmpeg libavcodec │ +├─────────────────────────────────────────────────────────────┤ +│ VAAPI (libva) │ +├──────────────┬──────────────┬──────────────┬────────────────┤ +│ Intel i965 │ Intel iHD │ AMD radeonsi │ NVIDIA VDPAU │ +│ (Gen8-) │ (Gen9+) │ │ (via wrapper) │ +├──────────────┴──────────────┴──────────────┴────────────────┤ +│ Kernel DRM Driver │ +├──────────────┬──────────────┬──────────────┬────────────────┤ +│ i915 │ amdgpu │ nvidia │ ... │ +└──────────────┴──────────────┴──────────────┴────────────────┘ +``` + +### 5.2 编码配置 + +```cpp +// libs/hwcodec/cpp/common/util.cpp + +// VAAPI 低延迟配置 +if (name.find("vaapi") != std::string::npos) { + av_opt_set(priv_data, "async_depth", "1", 0); +} +``` + +### 5.3 硬件上下文初始化 + +```cpp +// libs/hwcodec/cpp/ffmpeg_ram/ffmpeg_ram_encode.cpp + +// 检测 VAAPI 编码器 +if (name_.find("vaapi") != std::string::npos) { + hw_device_type_ = AV_HWDEVICE_TYPE_VAAPI; + hw_pixfmt_ = AV_PIX_FMT_VAAPI; +} + +// 创建硬件设备上下文 +ret = av_hwdevice_ctx_create(&hw_device_ctx_, hw_device_type_, + NULL, // 使用默认设备 + NULL, 0); + +// 设置硬件帧上下文 +set_hwframe_ctx(); + +// 分配硬件帧 +hw_frame_ = av_frame_alloc(); +av_hwframe_get_buffer(c_->hw_frames_ctx, hw_frame_, 0); +``` + +### 5.4 编码流程 + +``` +输入 YUV (CPU 内存) + │ + ▼ +av_hwframe_transfer_data(hw_frame_, frame_, 0) // CPU → GPU + │ + ▼ +avcodec_send_frame(c_, hw_frame_) // 发送 GPU 帧 + │ + ▼ +avcodec_receive_packet(c_, pkt_) // 获取编码数据 + │ + ▼ +编码数据 (CPU 内存) +``` + +### 5.5 依赖库 + +- `libva.so` - VAAPI 核心库 +- `libva-drm.so` - DRM 后端 +- `libva-x11.so` - X11 后端 (可选) + +## 6. Rockchip MPP + +### 6.1 检测机制 + +```cpp +// libs/hwcodec/cpp/common/platform/linux/linux.cpp:122-137 + +int linux_support_rkmpp() { + // 检测 MPP 服务设备 + if (access("/dev/mpp_service", F_OK) == 0) { + return 0; // MPP 可用 + } + // 备用: 检测 RGA 设备 + if (access("/dev/rga", F_OK) == 0) { + return 0; // MPP 可能可用 + } + return -1; // MPP 不可用 +} +``` + +### 6.2 支持的编码器 + +| 编码器 | 优先级 | 说明 | +|--------|--------|------| +| `h264_rkmpp` | Best (0) | H.264 硬件编码 | +| `hevc_rkmpp` | Best (0) | H.265 硬件编码 | + +### 6.3 适用设备 + +- Rockchip RK3328 (Onecloud, Chainedbox) +- Rockchip RK3399/RK3588 系列 +- 其他 Rockchip SoC + +## 7. V4L2 M2M + +### 7.1 检测机制 + +```cpp +// libs/hwcodec/cpp/common/platform/linux/linux.cpp:139-163 + +int linux_support_v4l2m2m() { + const char *m2m_devices[] = { + "/dev/video10", // 常见 M2M 编码设备 + "/dev/video11", // 常见 M2M 解码设备 + "/dev/video0", // 某些 SoC 使用 + }; + + for (size_t i = 0; i < sizeof(m2m_devices) / sizeof(m2m_devices[0]); i++) { + if (access(m2m_devices[i], F_OK) == 0) { + int fd = open(m2m_devices[i], O_RDWR | O_NONBLOCK); + if (fd >= 0) { + close(fd); + return 0; // V4L2 M2M 可用 + } + } + } + return -1; +} +``` + +### 7.2 支持的编码器 + +| 编码器 | 优先级 | 说明 | +|--------|--------|------| +| `h264_v4l2m2m` | Good (1) | H.264 V4L2 编码 | +| `hevc_v4l2m2m` | Good (1) | H.265 V4L2 编码 | + +### 7.3 适用设备 + +- 通用 ARM SoC (Allwinner, Amlogic 等) +- 支持 V4L2 M2M API 的设备 + +## 8. Apple VideoToolbox + +### 8.1 检测机制 (macOS) + +```rust +// libs/hwcodec/src/common.rs:57-87 + +#[cfg(target_os = "macos")] +pub(crate) fn get_video_toolbox_codec_support() -> (bool, bool, bool, bool) { + extern "C" { + fn checkVideoToolboxSupport( + h264_encode: *mut i32, + h265_encode: *mut i32, + h264_decode: *mut i32, + h265_decode: *mut i32, + ) -> c_void; + } + + let mut h264_encode = 0; + let mut h265_encode = 0; + let mut h264_decode = 0; + let mut h265_decode = 0; + + unsafe { + checkVideoToolboxSupport(&mut h264_encode, &mut h265_encode, + &mut h264_decode, &mut h265_decode); + } + + (h264_encode == 1, h265_encode == 1, + h264_decode == 1, h265_decode == 1) +} +``` + +### 8.2 编码配置 + +```cpp +// libs/hwcodec/cpp/common/util.cpp + +// VideoToolbox 低延迟配置 +if (name.find("videotoolbox") != std::string::npos) { + av_opt_set_int(priv_data, "realtime", 1, 0); + av_opt_set_int(priv_data, "prio_speed", 1, 0); +} + +// 强制硬件编码 +if (name.find("videotoolbox") != std::string::npos) { + av_opt_set_int(priv_data, "allow_sw", 0, 0); +} +``` + +### 8.3 限制 + +- H.264 编码不稳定,已禁用 +- 仅支持 H.265 编码 +- 完全支持 H.264/H.265 解码 + +### 8.4 依赖框架 + +``` +CoreFoundation +CoreVideo +CoreMedia +VideoToolbox +AVFoundation +``` + +## 9. 硬件加速优先级 + +### 9.1 优先级定义 + +```rust +pub enum Priority { + Best = 0, // 专用硬件编码器 + Good = 1, // 通用硬件加速 + Normal = 2, // 基本硬件支持 + Soft = 3, // 软件编码 + Bad = 4, // 最低优先级 +} +``` + +### 9.2 各编码器优先级 + +| 优先级 | 编码器 | +|--------|--------| +| Best (0) | nvenc, amf, qsv, rkmpp | +| Good (1) | vaapi, v4l2m2m | +| Soft (3) | x264, x265, libvpx | + +### 9.3 选择策略 + +```rust +// libs/hwcodec/src/ffmpeg_ram/mod.rs:49-117 + +pub fn prioritized(coders: Vec) -> CodecInfos { + // 对于每种格式,选择优先级最高的编码器 + for coder in coders { + match coder.format { + DataFormat::H264 => { + if h264.is_none() || h264.priority > coder.priority { + h264 = Some(coder); + } + } + // ... 其他格式类似 + } + } +} +``` + +## 10. 故障排除 + +### 10.1 NVIDIA + +```bash +# 检查 NVIDIA 驱动 +nvidia-smi + +# 检查 NVENC 支持 +ls /dev/nvidia* + +# 检查 CUDA 库 +ldconfig -p | grep cuda +ldconfig -p | grep nvidia-encode +``` + +### 10.2 AMD + +```bash +# 检查 AMD 驱动 +lspci | grep AMD + +# 检查 AMF 库 +ldconfig -p | grep amf +``` + +### 10.3 Intel + +```bash +# 检查 Intel 驱动 +vainfo + +# 检查 MFX 库 +ldconfig -p | grep mfx +ldconfig -p | grep vpl +``` + +### 10.4 VAAPI + +```bash +# 安装 vainfo +sudo apt install vainfo + +# 检查 VAAPI 支持 +vainfo + +# 输出示例: +# libva info: VA-API version 1.14.0 +# libva info: Trying to open /usr/lib/x86_64-linux-gnu/dri/iHD_drv_video.so +# vainfo: Driver version: Intel iHD driver for Intel(R) Gen Graphics +# vainfo: Supported profile and entrypoints +# VAProfileH264Main : VAEntrypointVLD +# VAProfileH264Main : VAEntrypointEncSlice +# ... +``` + +### 10.5 Rockchip MPP + +```bash +# 检查 MPP 设备 +ls -la /dev/mpp_service +ls -la /dev/rga + +# 检查 MPP 库 +ldconfig -p | grep rockchip_mpp +``` + +### 10.6 V4L2 M2M + +```bash +# 列出 V4L2 设备 +v4l2-ctl --list-devices + +# 检查设备能力 +v4l2-ctl -d /dev/video10 --all +``` diff --git a/docs/report/hwcodec/03-build-integration.md b/docs/report/hwcodec/03-build-integration.md new file mode 100644 index 00000000..9b89803f --- /dev/null +++ b/docs/report/hwcodec/03-build-integration.md @@ -0,0 +1,539 @@ +# hwcodec 构建系统与集成指南 + +## 1. 项目结构 + +``` +libs/hwcodec/ +├── Cargo.toml # 包配置 +├── Cargo.lock # 依赖锁定 +├── build.rs # 构建脚本 +├── src/ # Rust 源码 +│ ├── lib.rs # 库入口 +│ ├── common.rs # 公共定义 +│ ├── ffmpeg.rs # FFmpeg 集成 +│ ├── mux.rs # 混流器 +│ ├── android.rs # Android 支持 +│ ├── ffmpeg_ram/ # RAM 编解码 +│ │ ├── mod.rs +│ │ ├── encode.rs +│ │ └── decode.rs +│ ├── vram/ # GPU 编解码 (Windows) +│ │ ├── mod.rs +│ │ ├── encode.rs +│ │ ├── decode.rs +│ │ └── ... +│ └── res/ # 测试资源 +│ ├── 720p.h264 +│ └── 720p.h265 +├── cpp/ # C++ 源码 +│ ├── common/ # 公共代码 +│ ├── ffmpeg_ram/ # FFmpeg RAM 实现 +│ ├── ffmpeg_vram/ # FFmpeg VRAM 实现 +│ ├── nv/ # NVIDIA 实现 +│ ├── amf/ # AMD 实现 +│ ├── mfx/ # Intel 实现 +│ ├── mux/ # 混流实现 +│ └── yuv/ # YUV 处理 +├── externals/ # 外部 SDK (Git 子模块) +│ ├── nv-codec-headers_n12.1.14.0/ +│ ├── Video_Codec_SDK_12.1.14/ +│ ├── AMF_v1.4.35/ +│ └── MediaSDK_22.5.4/ +├── dev/ # 开发工具 +│ ├── capture/ # 捕获工具 +│ ├── render/ # 渲染工具 +│ └── tool/ # 通用工具 +└── examples/ # 示例程序 +``` + +## 2. Cargo 配置 + +### 2.1 Cargo.toml + +```toml +[package] +name = "hwcodec" +version = "0.7.1" +edition = "2021" + +[features] +default = [] +vram = [] # GPU VRAM 直接编解码 (仅 Windows) + +[dependencies] +log = "0.4" # 日志 +serde_derive = "1.0" # 序列化派生宏 +serde = "1.0" # 序列化 +serde_json = "1.0" # JSON 序列化 + +[build-dependencies] +cc = "1.0" # C++ 编译 +bindgen = "0.59" # FFI 绑定生成 + +[dev-dependencies] +env_logger = "0.10" # 日志输出 +rand = "0.8" # 随机数 +``` + +### 2.2 Feature 说明 + +| Feature | 说明 | 平台 | +|---------|------|------| +| `default` | 基础功能 | 全平台 | +| `vram` | GPU VRAM 直接编解码 | 仅 Windows | + +### 2.3 使用方式 + +```toml +# 基础使用 +[dependencies] +hwcodec = { path = "libs/hwcodec" } + +# 启用 VRAM 功能 (Windows) +[dependencies] +hwcodec = { path = "libs/hwcodec", features = ["vram"] } +``` + +## 3. 构建脚本详解 (build.rs) + +### 3.1 主入口 + +```rust +fn main() { + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let mut builder = Build::new(); + + // 1. 构建公共模块 + build_common(&mut builder); + + // 2. 构建 FFmpeg 相关模块 + ffmpeg::build_ffmpeg(&mut builder); + + // 3. 构建 SDK 模块 (Windows + vram feature) + #[cfg(all(windows, feature = "vram"))] + sdk::build_sdk(&mut builder); + + // 4. 编译生成静态库 + builder.static_crt(true).compile("hwcodec"); +} +``` + +### 3.2 公共模块构建 + +```rust +fn build_common(builder: &mut Build) { + let common_dir = manifest_dir.join("cpp").join("common"); + + // 生成 FFI 绑定 + bindgen::builder() + .header(common_dir.join("common.h")) + .header(common_dir.join("callback.h")) + .rustified_enum("*") + .generate() + .write_to_file(OUT_DIR.join("common_ffi.rs")); + + // 平台相关代码 + #[cfg(windows)] + builder.file(common_dir.join("platform/win/win.cpp")); + + #[cfg(target_os = "linux")] + builder.file(common_dir.join("platform/linux/linux.cpp")); + + #[cfg(target_os = "macos")] + builder.file(common_dir.join("platform/mac/mac.mm")); + + // 工具代码 + builder.files([ + common_dir.join("log.cpp"), + common_dir.join("util.cpp"), + ]); +} +``` + +### 3.3 FFmpeg 模块构建 + +```rust +mod ffmpeg { + pub fn build_ffmpeg(builder: &mut Build) { + // 生成 FFmpeg FFI 绑定 + ffmpeg_ffi(); + + // 链接 FFmpeg 库 + if let Ok(vcpkg_root) = std::env::var("VCPKG_ROOT") { + link_vcpkg(builder, vcpkg_root.into()); + } else { + link_system_ffmpeg(builder); // pkg-config + } + + // 链接系统库 + link_os(); + + // 构建子模块 + build_ffmpeg_ram(builder); + #[cfg(feature = "vram")] + build_ffmpeg_vram(builder); + build_mux(builder); + } +} +``` + +### 3.4 FFmpeg 链接方式 + +#### VCPKG (跨平台静态链接) + +```rust +fn link_vcpkg(builder: &mut Build, path: PathBuf) -> PathBuf { + // 目标平台识别 + let target = match (target_os, target_arch) { + ("windows", "x86_64") => "x64-windows-static", + ("macos", "x86_64") => "x64-osx", + ("macos", "aarch64") => "arm64-osx", + ("linux", arch) => format!("{}-linux", arch), + _ => panic!("unsupported platform"), + }; + + let lib_path = path.join("installed").join(target).join("lib"); + + // 链接 FFmpeg 静态库 + println!("cargo:rustc-link-search=native={}", lib_path); + ["avcodec", "avutil", "avformat"].iter() + .for_each(|lib| println!("cargo:rustc-link-lib=static={}", lib)); +} +``` + +#### pkg-config (Linux 动态链接) + +```rust +fn link_system_ffmpeg(builder: &mut Build) { + let libs = ["libavcodec", "libavutil", "libavformat", "libswscale"]; + + for lib in &libs { + // 获取编译标志 + let cflags = Command::new("pkg-config") + .args(["--cflags", lib]) + .output()?; + + // 获取链接标志 + let libs = Command::new("pkg-config") + .args(["--libs", lib]) + .output()?; + + // 解析并应用 + for flag in libs.split_whitespace() { + if flag.starts_with("-L") { + println!("cargo:rustc-link-search=native={}", &flag[2..]); + } else if flag.starts_with("-l") { + println!("cargo:rustc-link-lib={}", &flag[2..]); + } + } + } +} +``` + +### 3.5 系统库链接 + +```rust +fn link_os() { + let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); + + let libs: Vec<&str> = match target_os.as_str() { + "windows" => vec!["User32", "bcrypt", "ole32", "advapi32"], + "linux" => vec!["drm", "X11", "stdc++", "z"], + "macos" | "ios" => vec!["c++", "m"], + "android" => vec!["z", "m", "android", "atomic", "mediandk"], + _ => panic!("unsupported os"), + }; + + for lib in libs { + println!("cargo:rustc-link-lib={}", lib); + } + + // macOS 框架 + if target_os == "macos" || target_os == "ios" { + for framework in ["CoreFoundation", "CoreVideo", "CoreMedia", + "VideoToolbox", "AVFoundation"] { + println!("cargo:rustc-link-lib=framework={}", framework); + } + } +} +``` + +### 3.6 SDK 模块构建 (Windows) + +```rust +#[cfg(all(windows, feature = "vram"))] +mod sdk { + pub fn build_sdk(builder: &mut Build) { + build_amf(builder); // AMD AMF + build_nv(builder); // NVIDIA + build_mfx(builder); // Intel MFX + } + + fn build_nv(builder: &mut Build) { + let sdk_path = externals_dir.join("Video_Codec_SDK_12.1.14"); + + // 包含 SDK 头文件 + builder.includes([ + sdk_path.join("Interface"), + sdk_path.join("Samples/Utils"), + sdk_path.join("Samples/NvCodec"), + ]); + + // 编译 SDK 源文件 + builder.file(sdk_path.join("Samples/NvCodec/NvEncoder/NvEncoder.cpp")); + builder.file(sdk_path.join("Samples/NvCodec/NvEncoder/NvEncoderD3D11.cpp")); + builder.file(sdk_path.join("Samples/NvCodec/NvDecoder/NvDecoder.cpp")); + + // 编译封装代码 + builder.files([ + nv_dir.join("nv_encode.cpp"), + nv_dir.join("nv_decode.cpp"), + ]); + } +} +``` + +## 4. FFI 绑定生成 + +### 4.1 bindgen 配置 + +```rust +bindgen::builder() + .header("path/to/header.h") + .rustified_enum("*") // 生成 Rust 枚举 + .parse_callbacks(Box::new(Callbacks)) // 自定义回调 + .generate() + .write_to_file(OUT_DIR.join("ffi.rs")); +``` + +### 4.2 自定义派生 + +```rust +#[derive(Debug)] +struct CommonCallbacks; + +impl bindgen::callbacks::ParseCallbacks for CommonCallbacks { + fn add_derives(&self, name: &str) -> Vec { + // 为特定类型添加序列化支持 + match name { + "DataFormat" | "SurfaceFormat" | "API" => { + vec!["Serialize".to_string(), "Deserialize".to_string()] + } + _ => vec![], + } + } +} +``` + +### 4.3 生成的文件 + +| 文件 | 来源 | 内容 | +|------|------|------| +| `common_ffi.rs` | `common.h`, `callback.h` | 枚举、常量、回调类型 | +| `ffmpeg_ffi.rs` | `ffmpeg_ffi.h` | FFmpeg 日志级别、函数 | +| `ffmpeg_ram_ffi.rs` | `ffmpeg_ram_ffi.h` | 编解码器函数 | +| `mux_ffi.rs` | `mux_ffi.h` | 混流器函数 | + +## 5. 外部依赖管理 + +### 5.1 Git 子模块 + +```bash +# 初始化子模块 +git submodule update --init --recursive + +# 更新子模块 +git submodule update --remote externals +``` + +### 5.2 子模块配置 (.gitmodules) + +``` +[submodule "externals"] + path = libs/hwcodec/externals + url = https://github.com/rustdesk-org/externals.git +``` + +### 5.3 依赖版本 + +| 依赖 | 版本 | 用途 | +|------|------|------| +| nv-codec-headers | n12.1.14.0 | NVIDIA FFmpeg 编码头 | +| Video_Codec_SDK | 12.1.14 | NVIDIA 编解码 SDK | +| AMF | v1.4.35 | AMD Advanced Media Framework | +| MediaSDK | 22.5.4 | Intel Media SDK | + +## 6. 平台构建指南 + +### 6.1 Linux 构建 + +```bash +# 安装 FFmpeg 开发库 +sudo apt install libavcodec-dev libavformat-dev libavutil-dev libswscale-dev + +# 安装其他依赖 +sudo apt install libdrm-dev libx11-dev pkg-config + +# 构建 +cargo build --release -p hwcodec +``` + +### 6.2 Windows 构建 (VCPKG) + +```powershell +# 安装 VCPKG +git clone https://github.com/microsoft/vcpkg +cd vcpkg +./bootstrap-vcpkg.bat + +# 安装 FFmpeg +./vcpkg install ffmpeg:x64-windows-static + +# 设置环境变量 +$env:VCPKG_ROOT = "C:\path\to\vcpkg" + +# 构建 +cargo build --release -p hwcodec --features vram +``` + +### 6.3 macOS 构建 + +```bash +# 安装 FFmpeg (Homebrew) +brew install ffmpeg pkg-config + +# 或使用 VCPKG +export VCPKG_ROOT=/path/to/vcpkg +vcpkg install ffmpeg:arm64-osx # Apple Silicon +vcpkg install ffmpeg:x64-osx # Intel + +# 构建 +cargo build --release -p hwcodec +``` + +### 6.4 交叉编译 + +```bash +# 安装 cross +cargo install cross --git https://github.com/cross-rs/cross + +# ARM64 Linux +cross build --release -p hwcodec --target aarch64-unknown-linux-gnu + +# ARMv7 Linux +cross build --release -p hwcodec --target armv7-unknown-linux-gnueabihf +``` + +## 7. 集成到 One-KVM + +### 7.1 依赖配置 + +```toml +# Cargo.toml +[dependencies] +hwcodec = { path = "libs/hwcodec" } +``` + +### 7.2 使用示例 + +```rust +use hwcodec::ffmpeg_ram::encode::{Encoder, EncodeContext}; +use hwcodec::ffmpeg_ram::decode::{Decoder, DecodeContext}; +use hwcodec::ffmpeg::AVPixelFormat; + +// 检测可用编码器 +let encoders = Encoder::available_encoders(ctx, None); + +// 创建编码器 +let encoder = Encoder::new(EncodeContext { + name: "h264_vaapi".to_string(), + width: 1920, + height: 1080, + pixfmt: AVPixelFormat::AV_PIX_FMT_NV12, + fps: 30, + gop: 30, + kbs: 4000, + // ... +})?; + +// 编码 +let frames = encoder.encode(&yuv_data, pts_ms)?; +``` + +### 7.3 日志集成 + +```rust +// hwcodec 使用 log crate,与 One-KVM 日志系统兼容 +use log::{debug, info, warn, error}; + +// C++ 层日志通过回调传递 +#[no_mangle] +pub extern "C" fn hwcodec_log(level: i32, message: *const c_char) { + match level { + 0 => error!("{}", message), + 1 => warn!("{}", message), + 2 => info!("{}", message), + 3 => debug!("{}", message), + 4 => trace!("{}", message), + _ => {} + } +} +``` + +## 8. 故障排除 + +### 8.1 编译错误 + +**FFmpeg 未找到**: +``` +error: pkg-config failed for libavcodec +``` +解决: 安装 FFmpeg 开发库 +```bash +sudo apt install libavcodec-dev libavformat-dev libavutil-dev libswscale-dev +``` + +**bindgen 错误**: +``` +error: failed to run custom build command for `hwcodec` +``` +解决: 安装 clang +```bash +sudo apt install clang libclang-dev +``` + +### 8.2 链接错误 + +**符号未定义**: +``` +undefined reference to `av_log_set_level' +``` +解决: 检查 FFmpeg 库链接顺序,确保 pkg-config 正确配置 + +**动态库未找到**: +``` +error while loading shared libraries: libavcodec.so.59 +``` +解决: +```bash +sudo ldconfig +# 或设置 LD_LIBRARY_PATH +export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH +``` + +### 8.3 运行时错误 + +**硬件编码器不可用**: +``` +Encoder h264_vaapi test failed +``` +检查: +1. 驱动是否正确安装: `vainfo` +2. 权限是否足够: `ls -la /dev/dri/` +3. 用户是否在 video 组: `groups` + +**解码失败**: +``` +avcodec_receive_frame failed, ret = -11 +``` +解决: 这通常表示需要更多输入数据 (EAGAIN),是正常行为 diff --git a/docs/report/rustdesk/00-overview.md b/docs/report/rustdesk/00-overview.md new file mode 100644 index 00000000..2282aa5b --- /dev/null +++ b/docs/report/rustdesk/00-overview.md @@ -0,0 +1,69 @@ +# RustDesk 通信协议技术报告 + +## 概述 + +本报告详细分析 RustDesk 远程桌面软件的客户端与服务器之间的通信协议,包括 Rendezvous 服务器(hbbs)、Relay 服务器(hbbr)以及客户端之间的 P2P 连接机制。 + +## 文档结构 + +| 文档 | 内容 | +|------|------| +| [01-architecture.md](01-architecture.md) | 整体架构设计 | +| [02-rendezvous-protocol.md](02-rendezvous-protocol.md) | Rendezvous 服务器协议 | +| [03-relay-protocol.md](03-relay-protocol.md) | Relay 服务器协议 | +| [04-p2p-connection.md](04-p2p-connection.md) | P2P 连接流程 | +| [05-message-format.md](05-message-format.md) | 消息格式定义 | +| [06-encryption.md](06-encryption.md) | 加密机制 | +| [07-nat-traversal.md](07-nat-traversal.md) | NAT 穿透技术 | +| [08-onekvm-comparison.md](08-onekvm-comparison.md) | **One-KVM 实现对比分析** | + +## 核心组件 + +### 1. Rendezvous Server (hbbs) +- **功能**: ID 注册、Peer 发现、NAT 类型检测、连接协调 +- **端口**: 21116 (TCP/UDP), 21115 (NAT 测试), 21118 (WebSocket) +- **源文件**: `rustdesk-server/src/rendezvous_server.rs` + +### 2. Relay Server (hbbr) +- **功能**: 当 P2P 连接失败时提供数据中转 +- **端口**: 21117 (TCP), 21119 (WebSocket) +- **源文件**: `rustdesk-server/src/relay_server.rs` + +### 3. 客户端 (RustDesk) +- **功能**: 远程桌面控制、文件传输、屏幕共享 +- **核心模块**: + - `rendezvous_mediator.rs` - 与 Rendezvous 服务器通信 + - `client.rs` - 客户端连接逻辑 + - `server/connection.rs` - 被控端连接处理 + +## 协议栈 + +``` +┌─────────────────────────────────────────┐ +│ Application Layer │ +│ (Video/Audio/Keyboard/Mouse/File) │ +├─────────────────────────────────────────┤ +│ Message Layer │ +│ (Protobuf Messages) │ +├─────────────────────────────────────────┤ +│ Security Layer │ +│ (Sodium: X25519 + ChaCha20) │ +├─────────────────────────────────────────┤ +│ Transport Layer │ +│ (TCP/UDP/WebSocket/KCP) │ +└─────────────────────────────────────────┘ +``` + +## 关键技术特点 + +1. **混合连接模式**: 优先尝试 P2P 直连,失败后自动切换到 Relay 中转 +2. **多协议支持**: TCP、UDP、WebSocket、KCP +3. **端到端加密**: 使用 libsodium 实现的 X25519 密钥交换和 ChaCha20-Poly1305 对称加密 +4. **NAT 穿透**: 支持 UDP 打洞和 TCP 打洞技术 +5. **服务器签名**: 可选的服务器公钥签名验证,防止中间人攻击 + +## 版本信息 + +- 分析基于 RustDesk 最新版本源码 +- Protocol Buffer 版本: proto3 +- 加密库: libsodium (sodiumoxide) diff --git a/docs/report/rustdesk/01-architecture.md b/docs/report/rustdesk/01-architecture.md new file mode 100644 index 00000000..4569117c --- /dev/null +++ b/docs/report/rustdesk/01-architecture.md @@ -0,0 +1,218 @@ +# RustDesk 架构设计 + +## 系统架构图 + +``` + ┌──────────────────────┐ + │ Rendezvous Server │ + │ (hbbs) │ + │ Port: 21116 │ + └──────────┬───────────┘ + │ + ┌──────────────────────────┼──────────────────────────┐ + │ │ │ + ▼ ▼ ▼ + ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ + │ Client A │ │ Client B │ │ Client C │ + │ (控制端) │ │ (被控端) │ │ (被控端) │ + └───────┬───────┘ └───────┬───────┘ └───────────────┘ + │ │ + │ P2P Connection │ + │◄────────────────────────►│ + │ │ + │ (如果 P2P 失败) │ + │ │ │ + │ ▼ │ + │ ┌───────────────┐ │ + └─►│ Relay Server │◄──────┘ + │ (hbbr) │ + │ Port: 21117 │ + └───────────────┘ +``` + +## 服务器组件详解 + +### Rendezvous Server (hbbs) + +**监听端口:** +| 端口 | 协议 | 用途 | +|------|------|------| +| 21116 | TCP | 主要通信端口,处理 punch hole 请求 | +| 21116 | UDP | Peer 注册、NAT 类型检测 | +| 21115 | TCP | NAT 测试专用端口 | +| 21118 | WebSocket | Web 客户端支持 | + +**核心数据结构:** + +```rust +// rustdesk-server/src/rendezvous_server.rs:64-83 +pub struct RendezvousServer { + tcp_punch: Arc>>, // TCP punch hole 连接 + pm: PeerMap, // Peer 映射表 + tx: Sender, // 消息发送通道 + relay_servers: Arc, // 可用 Relay 服务器列表 + relay_servers0: Arc, // 原始 Relay 服务器列表 + rendezvous_servers: Arc>, // Rendezvous 服务器列表 + inner: Arc, // 内部配置 +} + +struct Inner { + serial: i32, // 配置序列号 + version: String, // 软件版本 + software_url: String, // 软件更新 URL + mask: Option, // LAN 掩码 + local_ip: String, // 本地 IP + sk: Option, // 服务器签名密钥 +} +``` + +**Peer 数据结构:** + +```rust +// rustdesk-server/src/peer.rs:32-42 +pub struct Peer { + pub socket_addr: SocketAddr, // 最后注册的地址 + pub last_reg_time: Instant, // 最后注册时间 + pub guid: Vec, // 数据库 GUID + pub uuid: Bytes, // 设备 UUID + pub pk: Bytes, // 公钥 + pub info: PeerInfo, // Peer 信息 + pub reg_pk: (u32, Instant), // 注册频率限制 +} +``` + +### Relay Server (hbbr) + +**监听端口:** +| 端口 | 协议 | 用途 | +|------|------|------| +| 21117 | TCP | 主要中转端口 | +| 21119 | WebSocket | Web 客户端支持 | + +**核心特性:** + +```rust +// rustdesk-server/src/relay_server.rs:40-44 +static DOWNGRADE_THRESHOLD_100: AtomicUsize = AtomicUsize::new(66); // 降级阈值 +static DOWNGRADE_START_CHECK: AtomicUsize = AtomicUsize::new(1_800_000); // 检测开始时间(ms) +static LIMIT_SPEED: AtomicUsize = AtomicUsize::new(32 * 1024 * 1024); // 限速(bit/s) +static TOTAL_BANDWIDTH: AtomicUsize = AtomicUsize::new(1024 * 1024 * 1024);// 总带宽 +static SINGLE_BANDWIDTH: AtomicUsize = AtomicUsize::new(128 * 1024 * 1024);// 单连接带宽 +``` + +## 客户端架构 + +### 核心模块 + +``` +rustdesk/src/ +├── rendezvous_mediator.rs # Rendezvous 服务器通信 +├── client.rs # 控制端核心逻辑 +├── server/ +│ ├── mod.rs # 被控端服务 +│ ├── connection.rs # 连接处理 +│ ├── video_service.rs # 视频服务 +│ ├── audio_service.rs # 音频服务 +│ └── input_service.rs # 输入服务 +├── common.rs # 通用函数(加密、解密) +└── platform/ # 平台特定代码 +``` + +### RendezvousMediator + +```rust +// rustdesk/src/rendezvous_mediator.rs:44-50 +pub struct RendezvousMediator { + addr: TargetAddr<'static>, // 服务器地址 + host: String, // 服务器主机名 + host_prefix: String, // 主机前缀 + keep_alive: i32, // 保活间隔 +} +``` + +**两种连接模式:** + +1. **UDP 模式** (默认): + - 用于 Peer 注册和心跳 + - 更低延迟 + - 可能被某些防火墙阻止 + +2. **TCP 模式**: + - 用于代理环境 + - WebSocket 模式 + - 更可靠 + +## 连接流程概述 + +### 被控端启动流程 + +``` +1. 生成设备 ID 和密钥对 +2. 连接 Rendezvous Server +3. 发送 RegisterPeer 消息 +4. 如果需要,发送 RegisterPk 注册公钥 +5. 定期发送心跳保持在线状态 +6. 等待 PunchHole 或 RequestRelay 请求 +``` + +### 控制端连接流程 + +``` +1. 输入目标设备 ID +2. 连接 Rendezvous Server +3. 发送 PunchHoleRequest 消息 +4. 根据响应决定连接方式: + a. 直连 (P2P): 使用 PunchHole 信息尝试打洞 + b. 局域网: 使用 LocalAddr 信息直连 + c. 中转: 通过 Relay Server 连接 +5. 建立安全加密通道 +6. 发送 LoginRequest 进行身份验证 +7. 开始远程控制会话 +``` + +## 数据流 + +### 视频流 + +``` +被控端 控制端 + │ │ + │ VideoFrame (H264/VP9/...) │ + ├─────────────────────────────────►│ + │ │ + │ 加密 → 传输 → 解密 → 解码 → 显示 │ +``` + +### 输入流 + +``` +控制端 被控端 + │ │ + │ MouseEvent/KeyEvent │ + ├─────────────────────────────────►│ + │ │ + │ 加密 → 传输 → 解密 → 模拟输入 │ +``` + +## 高可用设计 + +### 多服务器支持 + +- 客户端可配置多个 Rendezvous Server +- 自动选择延迟最低的服务器 +- 连接失败时自动切换备用服务器 + +### Relay Server 选择 + +- 支持配置多个 Relay Server +- 轮询算法分配负载 +- 定期检查 Relay Server 可用性 + +### 重连机制 + +```rust +// 连接超时和重试参数 +const REG_INTERVAL: i64 = 12_000; // 注册间隔 12 秒 +const REG_TIMEOUT: i32 = 30_000; // 注册超时 30 秒 +const CONNECT_TIMEOUT: u64 = 18_000; // 连接超时 18 秒 +``` diff --git a/docs/report/rustdesk/02-rendezvous-protocol.md b/docs/report/rustdesk/02-rendezvous-protocol.md new file mode 100644 index 00000000..b07266bb --- /dev/null +++ b/docs/report/rustdesk/02-rendezvous-protocol.md @@ -0,0 +1,438 @@ +# Rendezvous 服务器协议 + +## 概述 + +Rendezvous Server(hbbs)是 RustDesk 的核心协调服务器,负责: +- Peer ID 注册和发现 +- 公钥存储和分发 +- NAT 类型检测 +- P2P 连接协调(打洞辅助) +- Relay Server 分配 + +## 协议消息定义 + +所有消息使用 Protocol Buffers 定义在 `protos/rendezvous.proto`: + +```protobuf +message RendezvousMessage { + oneof union { + RegisterPeer register_peer = 6; + RegisterPeerResponse register_peer_response = 7; + PunchHoleRequest punch_hole_request = 8; + PunchHole punch_hole = 9; + PunchHoleSent punch_hole_sent = 10; + PunchHoleResponse punch_hole_response = 11; + FetchLocalAddr fetch_local_addr = 12; + LocalAddr local_addr = 13; + ConfigUpdate configure_update = 14; + RegisterPk register_pk = 15; + RegisterPkResponse register_pk_response = 16; + SoftwareUpdate software_update = 17; + RequestRelay request_relay = 18; + RelayResponse relay_response = 19; + TestNatRequest test_nat_request = 20; + TestNatResponse test_nat_response = 21; + PeerDiscovery peer_discovery = 22; + OnlineRequest online_request = 23; + OnlineResponse online_response = 24; + KeyExchange key_exchange = 25; + HealthCheck hc = 26; + } +} +``` + +## 核心流程 + +### 1. Peer 注册流程 + +**客户端 → 服务器:RegisterPeer** + +```protobuf +message RegisterPeer { + string id = 1; // Peer ID (如 "123456789") + int32 serial = 2; // 配置序列号 +} +``` + +**服务器处理逻辑:** + +```rust +// rustdesk-server/src/rendezvous_server.rs:318-333 +Some(rendezvous_message::Union::RegisterPeer(rp)) => { + if !rp.id.is_empty() { + log::trace!("New peer registered: {:?} {:?}", &rp.id, &addr); + self.update_addr(rp.id, addr, socket).await?; + // 如果服务器配置更新,发送 ConfigUpdate + if self.inner.serial > rp.serial { + let mut msg_out = RendezvousMessage::new(); + msg_out.set_configure_update(ConfigUpdate { + serial: self.inner.serial, + rendezvous_servers: (*self.rendezvous_servers).clone(), + ..Default::default() + }); + socket.send(&msg_out, addr).await?; + } + } +} +``` + +**服务器 → 客户端:RegisterPeerResponse** + +```protobuf +message RegisterPeerResponse { + bool request_pk = 2; // 是否需要注册公钥 +} +``` + +### 2. 公钥注册流程 + +当服务器检测到 Peer 的公钥为空或 IP 变化时,会请求注册公钥。 + +**客户端 → 服务器:RegisterPk** + +```protobuf +message RegisterPk { + string id = 1; // Peer ID + bytes uuid = 2; // 设备 UUID + bytes pk = 3; // Ed25519 公钥 + string old_id = 4; // 旧 ID(如果更换) +} +``` + +**服务器处理逻辑:** + +```rust +// rustdesk-server/src/rendezvous_server.rs:334-418 +Some(rendezvous_message::Union::RegisterPk(rk)) => { + // 验证 UUID 和公钥 + if rk.uuid.is_empty() || rk.pk.is_empty() { + return Ok(()); + } + let id = rk.id; + let ip = addr.ip().to_string(); + + // ID 长度检查 + if id.len() < 6 { + return send_rk_res(socket, addr, UUID_MISMATCH).await; + } + + // IP 封锁检查 + if !self.check_ip_blocker(&ip, &id).await { + return send_rk_res(socket, addr, TOO_FREQUENT).await; + } + + // UUID 匹配验证 + let peer = self.pm.get_or(&id).await; + // ... UUID 验证逻辑 ... + + // 更新数据库 + if changed { + self.pm.update_pk(id, peer, addr, rk.uuid, rk.pk, ip).await; + } + + // 发送成功响应 + msg_out.set_register_pk_response(RegisterPkResponse { + result: register_pk_response::Result::OK.into(), + ..Default::default() + }); +} +``` + +**服务器 → 客户端:RegisterPkResponse** + +```protobuf +message RegisterPkResponse { + enum Result { + OK = 0; + UUID_MISMATCH = 2; + ID_EXISTS = 3; + TOO_FREQUENT = 4; + INVALID_ID_FORMAT = 5; + NOT_SUPPORT = 6; + SERVER_ERROR = 7; + } + Result result = 1; + int32 keep_alive = 2; // 心跳间隔 +} +``` + +### 3. Punch Hole 请求流程 + +当控制端要连接被控端时,首先发送 PunchHoleRequest。 + +**控制端 → 服务器:PunchHoleRequest** + +```protobuf +message PunchHoleRequest { + string id = 1; // 目标 Peer ID + NatType nat_type = 2; // 请求方的 NAT 类型 + string licence_key = 3; // 许可证密钥 + ConnType conn_type = 4; // 连接类型 + string token = 5; // 认证令牌 + string version = 6; // 客户端版本 +} + +enum NatType { + UNKNOWN_NAT = 0; + ASYMMETRIC = 1; + SYMMETRIC = 2; +} + +enum ConnType { + DEFAULT_CONN = 0; + FILE_TRANSFER = 1; + PORT_FORWARD = 2; + RDP = 3; + VIEW_CAMERA = 4; +} +``` + +**服务器处理逻辑:** + +```rust +// rustdesk-server/src/rendezvous_server.rs:674-765 +async fn handle_punch_hole_request(...) { + // 1. 验证许可证密钥 + if !key.is_empty() && ph.licence_key != key { + return Ok((PunchHoleResponse { failure: LICENSE_MISMATCH }, None)); + } + + // 2. 查找目标 Peer + if let Some(peer) = self.pm.get(&id).await { + let (elapsed, peer_addr) = peer.read().await; + + // 3. 检查在线状态 + if elapsed >= REG_TIMEOUT { + return Ok((PunchHoleResponse { failure: OFFLINE }, None)); + } + + // 4. 判断是否同一局域网 + let same_intranet = (peer_is_lan && is_lan) || + (peer_addr.ip() == addr.ip()); + + if same_intranet { + // 请求获取本地地址 + msg_out.set_fetch_local_addr(FetchLocalAddr { + socket_addr: AddrMangle::encode(addr).into(), + relay_server, + }); + } else { + // 发送 Punch Hole 请求给被控端 + msg_out.set_punch_hole(PunchHole { + socket_addr: AddrMangle::encode(addr).into(), + nat_type: ph.nat_type, + relay_server, + }); + } + return Ok((msg_out, Some(peer_addr))); + } + + // Peer 不存在 + Ok((PunchHoleResponse { failure: ID_NOT_EXIST }, None)) +} +``` + +**服务器 → 被控端:PunchHole 或 FetchLocalAddr** + +```protobuf +message PunchHole { + bytes socket_addr = 1; // 控制端地址(编码) + string relay_server = 2; // Relay 服务器地址 + NatType nat_type = 3; // 控制端 NAT 类型 +} + +message FetchLocalAddr { + bytes socket_addr = 1; // 控制端地址(编码) + string relay_server = 2; // Relay 服务器地址 +} +``` + +### 4. 被控端响应流程 + +**被控端 → 服务器:PunchHoleSent 或 LocalAddr** + +```protobuf +message PunchHoleSent { + bytes socket_addr = 1; // 控制端地址 + string id = 2; // 被控端 ID + string relay_server = 3; // Relay 服务器 + NatType nat_type = 4; // 被控端 NAT 类型 + string version = 5; // 客户端版本 +} + +message LocalAddr { + bytes socket_addr = 1; // 控制端地址 + bytes local_addr = 2; // 被控端本地地址 + string relay_server = 3; // Relay 服务器 + string id = 4; // 被控端 ID + string version = 5; // 客户端版本 +} +``` + +**服务器 → 控制端:PunchHoleResponse** + +```protobuf +message PunchHoleResponse { + bytes socket_addr = 1; // 被控端地址 + bytes pk = 2; // 被控端公钥(已签名) + enum Failure { + ID_NOT_EXIST = 0; + OFFLINE = 2; + LICENSE_MISMATCH = 3; + LICENSE_OVERUSE = 4; + } + Failure failure = 3; + string relay_server = 4; + oneof union { + NatType nat_type = 5; + bool is_local = 6; // 是否为局域网连接 + } + string other_failure = 7; + int32 feedback = 8; +} +``` + +### 5. Relay 请求流程 + +当 P2P 连接失败或 NAT 类型不支持打洞时,使用 Relay。 + +**客户端 → 服务器:RequestRelay** + +```protobuf +message RequestRelay { + string id = 1; // 目标 Peer ID + string uuid = 2; // 连接 UUID(用于配对) + bytes socket_addr = 3; // 本端地址 + string relay_server = 4; // 指定的 Relay 服务器 + bool secure = 5; // 是否使用加密 + string licence_key = 6; // 许可证密钥 + ConnType conn_type = 7; // 连接类型 + string token = 8; // 认证令牌 +} +``` + +**服务器 → 客户端:RelayResponse** + +```protobuf +message RelayResponse { + bytes socket_addr = 1; // 对端地址 + string uuid = 2; // 连接 UUID + string relay_server = 3; // Relay 服务器地址 + oneof union { + string id = 4; // 对端 ID + bytes pk = 5; // 对端公钥 + } + string refuse_reason = 6; // 拒绝原因 + string version = 7; // 版本 + int32 feedback = 9; +} +``` + +## NAT 类型检测 + +**客户端 → 服务器:TestNatRequest** + +```protobuf +message TestNatRequest { + int32 serial = 1; // 配置序列号 +} +``` + +**服务器 → 客户端:TestNatResponse** + +```protobuf +message TestNatResponse { + int32 port = 1; // 观测到的源端口 + ConfigUpdate cu = 2; // 配置更新 +} +``` + +NAT 检测原理: +1. 客户端同时向主端口(21116)和 NAT 测试端口(21115)发送请求 +2. 比较两次响应中观测到的源端口 +3. 如果端口一致,则为 ASYMMETRIC NAT(适合打洞) +4. 如果端口不一致,则为 SYMMETRIC NAT(需要 Relay) + +## 地址编码 + +RustDesk 使用 `AddrMangle` 对 SocketAddr 进行编码: + +```rust +// 编码示例 +// IPv4: 4 bytes IP + 2 bytes port = 6 bytes +// IPv6: 16 bytes IP + 2 bytes port = 18 bytes +pub fn encode(addr: SocketAddr) -> Vec; +pub fn decode(bytes: &[u8]) -> SocketAddr; +``` + +## 安全机制 + +### 服务器签名 + +当服务器配置了私钥时,会对 Peer 的公钥进行签名: + +```rust +// rustdesk-server/src/rendezvous_server.rs:1160-1182 +async fn get_pk(&mut self, version: &str, id: String) -> Bytes { + if version.is_empty() || self.inner.sk.is_none() { + Bytes::new() + } else { + match self.pm.get(&id).await { + Some(peer) => { + let pk = peer.read().await.pk.clone(); + // 使用服务器私钥签名 IdPk + sign::sign( + &IdPk { id, pk, ..Default::default() } + .write_to_bytes() + .unwrap_or_default(), + self.inner.sk.as_ref().unwrap(), + ).into() + } + _ => Bytes::new(), + } + } +} +``` + +### IP 封锁 + +服务器实现了 IP 封锁机制防止滥用: + +```rust +// rustdesk-server/src/rendezvous_server.rs:866-894 +async fn check_ip_blocker(&self, ip: &str, id: &str) -> bool { + let mut lock = IP_BLOCKER.lock().await; + if let Some(old) = lock.get_mut(ip) { + // 每秒请求超过 30 次则封锁 + if counter.0 > 30 { + return false; + } + // 每天超过 300 个不同 ID 则封锁 + if counter.0.len() > 300 { + return !is_new; + } + } + true +} +``` + +## 时序图 + +### 完整连接建立流程 + +``` +控制端 Rendezvous Server 被控端 + │ │ │ + │ PunchHoleRequest │ │ + ├──────────────────────►│ │ + │ │ PunchHole │ + │ ├──────────────────────►│ + │ │ │ + │ │ PunchHoleSent │ + │ │◄──────────────────────┤ + │ PunchHoleResponse │ │ + │◄──────────────────────┤ │ + │ │ │ + │ ─────────── P2P Connection ──────────────────│ + │◄─────────────────────────────────────────────►│ +``` diff --git a/docs/report/rustdesk/03-relay-protocol.md b/docs/report/rustdesk/03-relay-protocol.md new file mode 100644 index 00000000..c672f42d --- /dev/null +++ b/docs/report/rustdesk/03-relay-protocol.md @@ -0,0 +1,318 @@ +# Relay 服务器协议 + +## 概述 + +Relay Server(hbbr)是 RustDesk 的数据中转服务器,当 P2P 连接无法建立时(如双方都在 Symmetric NAT 后面),所有通信数据通过 Relay Server 转发。 + +## 服务器架构 + +### 监听端口 + +| 端口 | 协议 | 用途 | +|------|------|------| +| 21117 | TCP | 主要中转端口 | +| 21119 | WebSocket | Web 客户端支持 | + +### 核心配置 + +```rust +// rustdesk-server/src/relay_server.rs:40-46 +static DOWNGRADE_THRESHOLD_100: AtomicUsize = AtomicUsize::new(66); // 0.66 +static DOWNGRADE_START_CHECK: AtomicUsize = AtomicUsize::new(1_800_000); // 30分钟 (ms) +static LIMIT_SPEED: AtomicUsize = AtomicUsize::new(32 * 1024 * 1024); // 32 Mb/s +static TOTAL_BANDWIDTH: AtomicUsize = AtomicUsize::new(1024 * 1024 * 1024);// 1024 Mb/s +static SINGLE_BANDWIDTH: AtomicUsize = AtomicUsize::new(128 * 1024 * 1024);// 128 Mb/s +const BLACKLIST_FILE: &str = "blacklist.txt"; +const BLOCKLIST_FILE: &str = "blocklist.txt"; +``` + +## 连接配对机制 + +### 配对原理 + +Relay Server 使用 UUID 来配对两个客户端的连接: + +1. 第一个客户端连接并发送 `RequestRelay` 消息(包含 UUID) +2. 服务器将该连接存储在等待队列中 +3. 第二个客户端使用相同的 UUID 连接 +4. 服务器将两个连接配对,开始转发数据 + +### 配对流程 + +```rust +// rustdesk-server/src/relay_server.rs:425-462 +async fn make_pair_(stream: impl StreamTrait, addr: SocketAddr, key: &str, limiter: Limiter) { + let mut stream = stream; + if let Ok(Some(Ok(bytes))) = timeout(30_000, stream.recv()).await { + if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(&bytes) { + if let Some(rendezvous_message::Union::RequestRelay(rf)) = msg_in.union { + // 验证许可证密钥 + if !key.is_empty() && rf.licence_key != key { + log::warn!("Relay authentication failed from {}", addr); + return; + } + + if !rf.uuid.is_empty() { + // 尝试查找配对 + let mut peer = PEERS.lock().await.remove(&rf.uuid); + if let Some(peer) = peer.as_mut() { + // 找到配对,开始中转 + log::info!("Relay request {} got paired", rf.uuid); + relay(addr, &mut stream, peer, limiter).await; + } else { + // 没找到,存储等待配对 + log::info!("New relay request {} from {}", rf.uuid, addr); + PEERS.lock().await.insert(rf.uuid.clone(), Box::new(stream)); + sleep(30.).await; // 等待 30 秒 + PEERS.lock().await.remove(&rf.uuid); // 超时移除 + } + } + } + } + } +} +``` + +## 数据转发 + +### 转发逻辑 + +```rust +// rustdesk-server/src/relay_server.rs:464-566 +async fn relay( + addr: SocketAddr, + stream: &mut impl StreamTrait, + peer: &mut Box, + total_limiter: Limiter, +) -> ResultType<()> { + let limiter = ::new(SINGLE_BANDWIDTH.load(Ordering::SeqCst) as f64); + let blacklist_limiter = ::new(LIMIT_SPEED.load(Ordering::SeqCst) as _); + + loop { + tokio::select! { + // 从 peer 接收数据,发送给 stream + res = peer.recv() => { + if let Some(Ok(bytes)) = res { + // 带宽限制 + if blacked || downgrade { + blacklist_limiter.consume(bytes.len() * 8).await; + } else { + limiter.consume(bytes.len() * 8).await; + } + total_limiter.consume(bytes.len() * 8).await; + stream.send_raw(bytes.into()).await?; + } else { + break; + } + }, + // 从 stream 接收数据,发送给 peer + res = stream.recv() => { + if let Some(Ok(bytes)) = res { + // 带宽限制 + limiter.consume(bytes.len() * 8).await; + total_limiter.consume(bytes.len() * 8).await; + peer.send_raw(bytes.into()).await?; + } else { + break; + } + }, + _ = timer.tick() => { + // 超时检测 + if last_recv_time.elapsed().as_secs() > 30 { + bail!("Timeout"); + } + } + } + + // 降级检测 + if elapsed > DOWNGRADE_START_CHECK && total > elapsed * downgrade_threshold { + downgrade = true; + log::info!("Downgrade {}, exceed threshold", id); + } + } + Ok(()) +} +``` + +### 原始模式 + +当两端都支持原始模式时,跳过 protobuf 解析以提高性能: + +```rust +// rustdesk-server/src/relay_server.rs:440-444 +if !stream.is_ws() && !peer.is_ws() { + peer.set_raw(); + stream.set_raw(); + log::info!("Both are raw"); +} +``` + +## 带宽控制 + +### 多级限速 + +1. **总带宽限制**:整个服务器的总带宽 +2. **单连接限制**:每个中转连接的带宽 +3. **黑名单限速**:对黑名单 IP 的特殊限制 + +### 降级机制 + +当连接持续占用高带宽时,会触发降级: + +```rust +// 条件: +// 1. 连接时间 > DOWNGRADE_START_CHECK (30分钟) +// 2. 平均带宽 > SINGLE_BANDWIDTH * 0.66 +// 降级后使用 LIMIT_SPEED (32 Mb/s) 限速 +if elapsed > DOWNGRADE_START_CHECK.load(Ordering::SeqCst) + && !downgrade + && total > elapsed * downgrade_threshold +{ + downgrade = true; +} +``` + +## 安全控制 + +### 黑名单 + +用于限速特定 IP: + +``` +# blacklist.txt +192.168.1.100 +10.0.0.50 +``` + +### 封锁名单 + +用于完全拒绝特定 IP: + +``` +# blocklist.txt +1.2.3.4 +5.6.7.8 +``` + +### 运行时管理命令 + +通过本地 TCP 连接(仅限 localhost)发送命令: + +```rust +// rustdesk-server/src/relay_server.rs:152-324 +match fds.next() { + Some("h") => // 帮助 + Some("blacklist-add" | "ba") => // 添加黑名单 + Some("blacklist-remove" | "br") => // 移除黑名单 + Some("blacklist" | "b") => // 查看黑名单 + Some("blocklist-add" | "Ba") => // 添加封锁名单 + Some("blocklist-remove" | "Br") => // 移除封锁名单 + Some("blocklist" | "B") => // 查看封锁名单 + Some("downgrade-threshold" | "dt") => // 设置降级阈值 + Some("downgrade-start-check" | "t") => // 设置降级检测时间 + Some("limit-speed" | "ls") => // 设置限速 + Some("total-bandwidth" | "tb") => // 设置总带宽 + Some("single-bandwidth" | "sb") => // 设置单连接带宽 + Some("usage" | "u") => // 查看使用统计 +} +``` + +## 协议消息 + +### RequestRelay + +用于建立中转连接的请求消息: + +```protobuf +message RequestRelay { + string id = 1; // 目标 Peer ID + string uuid = 2; // 连接 UUID(配对用) + bytes socket_addr = 3; // 本端地址 + string relay_server = 4; // Relay 服务器 + bool secure = 5; // 是否加密 + string licence_key = 6; // 许可证密钥 + ConnType conn_type = 7; // 连接类型 + string token = 8; // 认证令牌 +} +``` + +## 时序图 + +### 中转连接建立 + +``` +客户端 A Relay Server 客户端 B + │ │ │ + │ RequestRelay(uuid) │ │ + ├─────────────────────────►│ │ + │ │ │ + │ │ (存储等待配对) │ + │ │ │ + │ │ RequestRelay(uuid) │ + │ │◄───────────────────────────┤ + │ │ │ + │ │ (配对成功) │ + │ │ │ + │ ◄────────── 数据转发 ─────────────────────────────────►│ + │ │ │ +``` + +### 数据转发 + +``` +客户端 A Relay Server 客户端 B + │ │ │ + │ ────[数据]───────► │ │ + │ │ ────[数据]───────► │ + │ │ │ + │ │ ◄───[数据]──────── │ + │ ◄───[数据]──────── │ │ + │ │ │ +``` + +## 性能优化 + +### 零拷贝 + +使用 `Bytes` 类型减少内存拷贝: + +```rust +async fn send_raw(&mut self, bytes: Bytes) -> ResultType<()>; +``` + +### WebSocket 支持 + +支持 WebSocket 协议以穿越防火墙: + +```rust +#[async_trait] +impl StreamTrait for tokio_tungstenite::WebSocketStream { + async fn recv(&mut self) -> Option> { + if let Some(msg) = self.next().await { + match msg { + Ok(tungstenite::Message::Binary(bytes)) => { + Some(Ok(bytes[..].into())) + } + // ... + } + } + } +} +``` + +## 监控指标 + +服务器跟踪以下指标: + +| 指标 | 说明 | +|------|------| +| elapsed | 连接持续时间 (ms) | +| total | 总传输数据量 (bit) | +| highest | 最高瞬时速率 (kb/s) | +| speed | 当前速率 (kb/s) | + +通过 `usage` 命令查看: + +``` +192.168.1.100:12345: 3600s 1024.00MB 50000kb/s 45000kb/s 42000kb/s +``` diff --git a/docs/report/rustdesk/04-p2p-connection.md b/docs/report/rustdesk/04-p2p-connection.md new file mode 100644 index 00000000..c41e8fa0 --- /dev/null +++ b/docs/report/rustdesk/04-p2p-connection.md @@ -0,0 +1,424 @@ +# P2P 连接流程 + +## 概述 + +RustDesk 优先尝试建立 P2P 直连,只有在直连失败时才使用 Relay 中转。P2P 连接支持多种方式: +- TCP 打洞 +- UDP 打洞(KCP) +- 局域网直连 +- IPv6 直连 + +## 连接决策流程 + +``` + 开始连接 + │ + ▼ + ┌──────────────┐ + │ 是否强制 Relay?│ + └──────┬───────┘ + 是 │ 否 + ┌─────────┴─────────┐ + ▼ ▼ + 使用 Relay 检查 NAT 类型 + │ + ┌──────────────┴──────────────┐ + │ │ + ▼ ▼ + 双方都是对称 NAT? 有一方是可穿透 NAT + │ │ + 是 │ │ + ▼ ▼ + 使用 Relay 尝试 P2P 连接 + │ + ┌─────────────┴─────────────┐ + │ │ + ▼ ▼ + 同一局域网? 不同网络 + │ │ + 是 │ │ + ▼ ▼ + 局域网直连 尝试打洞 + │ + ┌──────────────┴──────────────┐ + │ │ + ▼ ▼ + TCP 打洞成功? UDP 打洞成功? + │ │ + 是 │ 否 是 │ 否 + ▼ │ ▼ │ + TCP P2P 连接 └───────────► KCP P2P 连接 │ + ▼ + 使用 Relay +``` + +## 客户端连接入口 + +```rust +// rustdesk/src/client.rs:188-230 +impl Client { + pub async fn start( + peer: &str, + key: &str, + token: &str, + conn_type: ConnType, + interface: impl Interface, + ) -> ResultType<...> { + // 检查是否为 IP 直连 + if hbb_common::is_ip_str(peer) { + return connect_tcp_local(check_port(peer, RELAY_PORT + 1), None, CONNECT_TIMEOUT).await; + } + + // 检查是否为域名:端口格式 + if hbb_common::is_domain_port_str(peer) { + return connect_tcp_local(peer, None, CONNECT_TIMEOUT).await; + } + + // 通过 Rendezvous Server 连接 + let (rendezvous_server, servers, _) = crate::get_rendezvous_server(1_000).await; + Self::_start_inner(peer, key, token, conn_type, interface, rendezvous_server, servers).await + } +} +``` + +## 被控端处理连接请求 + +### 处理 PunchHole 消息 + +```rust +// rustdesk/src/rendezvous_mediator.rs:554-619 +async fn handle_punch_hole(&self, ph: PunchHole, server: ServerPtr) -> ResultType<()> { + let peer_addr = AddrMangle::decode(&ph.socket_addr); + let relay_server = self.get_relay_server(ph.relay_server); + + // 判断是否需要 Relay + if ph.nat_type.enum_value() == Ok(NatType::SYMMETRIC) + || Config::get_nat_type() == NatType::SYMMETRIC as i32 + || relay + { + // 使用 Relay + let uuid = Uuid::new_v4().to_string(); + return self.create_relay(ph.socket_addr, relay_server, uuid, server, true, true).await; + } + + // 尝试 UDP 打洞 + if ph.udp_port > 0 { + peer_addr.set_port(ph.udp_port as u16); + self.punch_udp_hole(peer_addr, server, msg_punch).await?; + return Ok(()); + } + + // 尝试 TCP 打洞 + log::debug!("Punch tcp hole to {:?}", peer_addr); + let socket = { + let socket = connect_tcp(&*self.host, CONNECT_TIMEOUT).await?; + let local_addr = socket.local_addr(); + // 关键步骤:尝试连接对方,让 NAT 建立映射 + allow_err!(socket_client::connect_tcp_local(peer_addr, Some(local_addr), 30).await); + socket + }; + + // 发送 PunchHoleSent 告知 Rendezvous Server + let mut msg_out = Message::new(); + msg_out.set_punch_hole_sent(PunchHoleSent { + socket_addr: ph.socket_addr, + id: Config::get_id(), + relay_server, + nat_type: nat_type.into(), + version: crate::VERSION.to_owned(), + }); + socket.send_raw(msg_out.write_to_bytes()?).await?; + + // 接受控制端连接 + crate::accept_connection(server.clone(), socket, peer_addr, true).await; + Ok(()) +} +``` + +### 处理 FetchLocalAddr(局域网连接) + +```rust +// rustdesk/src/rendezvous_mediator.rs:481-552 +async fn handle_intranet(&self, fla: FetchLocalAddr, server: ServerPtr) -> ResultType<()> { + let peer_addr = AddrMangle::decode(&fla.socket_addr); + let relay_server = self.get_relay_server(fla.relay_server.clone()); + + // 尝试局域网直连 + if is_ipv4(&self.addr) && !relay && !config::is_disable_tcp_listen() { + if let Err(err) = self.handle_intranet_(fla.clone(), server.clone(), relay_server.clone()).await { + log::debug!("Failed to handle intranet: {:?}, will try relay", err); + } else { + return Ok(()); + } + } + + // 局域网直连失败,使用 Relay + let uuid = Uuid::new_v4().to_string(); + self.create_relay(fla.socket_addr, relay_server, uuid, server, true, true).await +} + +async fn handle_intranet_(&self, fla: FetchLocalAddr, server: ServerPtr, relay_server: String) -> ResultType<()> { + let peer_addr = AddrMangle::decode(&fla.socket_addr); + let mut socket = connect_tcp(&*self.host, CONNECT_TIMEOUT).await?; + let local_addr = socket.local_addr(); + + // 发送本地地址给 Rendezvous Server + let mut msg_out = Message::new(); + msg_out.set_local_addr(LocalAddr { + id: Config::get_id(), + socket_addr: AddrMangle::encode(peer_addr).into(), + local_addr: AddrMangle::encode(local_addr).into(), + relay_server, + version: crate::VERSION.to_owned(), + }); + socket.send_raw(msg_out.write_to_bytes()?).await?; + + // 接受连接 + crate::accept_connection(server.clone(), socket, peer_addr, true).await; + Ok(()) +} +``` + +## UDP 打洞 (KCP) + +### 打洞原理 + +UDP 打洞利用 NAT 的端口映射特性: + +1. A 向 Rendezvous Server 注册,NAT 创建映射 `A_internal:port1 → A_external:port2` +2. B 同样注册,创建映射 `B_internal:port3 → B_external:port4` +3. A 向 B 的外部地址发送 UDP 包,A 的 NAT 创建到 B 的映射 +4. B 向 A 的外部地址发送 UDP 包,B 的 NAT 创建到 A 的映射 +5. 如果 NAT 不是 Symmetric 类型,双方的包可以到达对方 + +```rust +// rustdesk/src/rendezvous_mediator.rs:621-642 +async fn punch_udp_hole( + &self, + peer_addr: SocketAddr, + server: ServerPtr, + msg_punch: PunchHoleSent, +) -> ResultType<()> { + let mut msg_out = Message::new(); + msg_out.set_punch_hole_sent(msg_punch); + let (socket, addr) = new_direct_udp_for(&self.host).await?; + let data = msg_out.write_to_bytes()?; + + // 发送到 Rendezvous Server + socket.send_to(&data, addr).await?; + + // 多次尝试发送以增加成功率 + let socket_cloned = socket.clone(); + tokio::spawn(async move { + for _ in 0..2 { + let tm = (hbb_common::time_based_rand() % 20 + 10) as f32 / 1000.; + hbb_common::sleep(tm).await; + socket.send_to(&data, addr).await.ok(); + } + }); + + // 等待对方连接 + udp_nat_listen(socket_cloned.clone(), peer_addr, peer_addr, server).await?; + Ok(()) +} +``` + +### KCP 协议 + +RustDesk 在 UDP 上使用 KCP 协议提供可靠传输: + +```rust +// rustdesk/src/rendezvous_mediator.rs:824-851 +async fn udp_nat_listen( + socket: Arc, + peer_addr: SocketAddr, + peer_addr_v4: SocketAddr, + server: ServerPtr, +) -> ResultType<()> { + socket.connect(peer_addr).await?; + + // 执行 UDP 打洞 + let res = crate::punch_udp(socket.clone(), true).await?; + + // 建立 KCP 流 + let stream = crate::kcp_stream::KcpStream::accept( + socket, + Duration::from_millis(CONNECT_TIMEOUT as _), + res, + ).await?; + + // 创建连接 + crate::server::create_tcp_connection(server, stream.1, peer_addr_v4, true).await?; + Ok(()) +} +``` + +## TCP 打洞 + +### 原理 + +TCP 打洞比 UDP 更难,因为 TCP 需要三次握手。基本思路: + +1. A 和 B 都尝试同时向对方发起连接 +2. 第一个 SYN 包会被对方的 NAT 丢弃(因为没有映射) +3. 但这个 SYN 包会在 A 的 NAT 上创建映射 +4. 当 B 的 SYN 包到达 A 的 NAT 时,由于已有映射,会被转发给 A +5. 连接建立 + +### 实现 + +```rust +// rustdesk/src/rendezvous_mediator.rs:604-617 +log::debug!("Punch tcp hole to {:?}", peer_addr); +let mut socket = { + let socket = connect_tcp(&*self.host, CONNECT_TIMEOUT).await?; + let local_addr = socket.local_addr(); + // 关键:使用相同的本地地址尝试连接对方 + // 这会在 NAT 上创建映射,使对方的连接请求能够到达 + allow_err!(socket_client::connect_tcp_local(peer_addr, Some(local_addr), 30).await); + socket +}; +``` + +## Relay 连接 + +当 P2P 失败时,使用 Relay: + +```rust +// rustdesk/src/rendezvous_mediator.rs:434-479 +async fn create_relay( + &self, + socket_addr: Vec, + relay_server: String, + uuid: String, + server: ServerPtr, + secure: bool, + initiate: bool, +) -> ResultType<()> { + let peer_addr = AddrMangle::decode(&socket_addr); + log::info!( + "create_relay requested from {:?}, relay_server: {}, uuid: {}, secure: {}", + peer_addr, relay_server, uuid, secure, + ); + + // 连接 Rendezvous Server 发送 RelayResponse + let mut socket = connect_tcp(&*self.host, CONNECT_TIMEOUT).await?; + let mut msg_out = Message::new(); + let mut rr = RelayResponse { + socket_addr: socket_addr.into(), + version: crate::VERSION.to_owned(), + ..Default::default() + }; + if initiate { + rr.uuid = uuid.clone(); + rr.relay_server = relay_server.clone(); + rr.set_id(Config::get_id()); + } + msg_out.set_relay_response(rr); + socket.send(&msg_out).await?; + + // 连接 Relay Server + crate::create_relay_connection( + server, + relay_server, + uuid, + peer_addr, + secure, + is_ipv4(&self.addr), + ).await; + Ok(()) +} +``` + +## IPv6 支持 + +RustDesk 优先尝试 IPv6 连接: + +```rust +// rustdesk/src/rendezvous_mediator.rs:808-822 +async fn start_ipv6( + peer_addr_v6: SocketAddr, + peer_addr_v4: SocketAddr, + server: ServerPtr, +) -> bytes::Bytes { + crate::test_ipv6().await; + if let Some((socket, local_addr_v6)) = crate::get_ipv6_socket().await { + let server = server.clone(); + tokio::spawn(async move { + allow_err!(udp_nat_listen(socket.clone(), peer_addr_v6, peer_addr_v4, server).await); + }); + return local_addr_v6; + } + Default::default() +} +``` + +## 连接状态机 + +``` + ┌─────────────────────────────────────────┐ + │ │ + ▼ │ + ┌───────────┐ ┌────┴────┐ + │ 等待连接 │──────PunchHoleRequest──────►│正在连接 │ + └───────────┘ └────┬────┘ + │ + ┌──────────────────────────────┼──────────────────────────────┐ + │ │ │ + ▼ ▼ ▼ + ┌────────────┐ ┌─────────────┐ ┌─────────────┐ + │ P2P TCP │ │ P2P UDP/KCP │ │ Relay │ + │ 连接中 │ │ 连接中 │ │ 连接中 │ + └─────┬──────┘ └──────┬──────┘ └──────┬──────┘ + │ │ │ + 成功 │ 失败 成功 │ 失败 成功 │ 失败 + │ │ │ │ │ │ + ▼ │ ▼ │ ▼ │ + ┌──────────┐│ ┌──────────┐│ ┌──────────┐│ + │已连接 ││ │已连接 ││ │已连接 ││ + │(直连) ││ │(UDP) ││ │(中转) ││ + └──────────┘│ └──────────┘│ └──────────┘│ + │ │ │ + └──────────────►尝试 Relay◄───┘ │ + │ │ + └────────────────────────────────────────┘ +``` + +## 直接连接模式 + +用户可以配置允许直接 TCP 连接(不经过 Rendezvous Server): + +```rust +// rustdesk/src/rendezvous_mediator.rs:727-792 +async fn direct_server(server: ServerPtr) { + let mut listener = None; + let mut port = get_direct_port(); // 默认 21118 + + loop { + let disabled = !option2bool(OPTION_DIRECT_SERVER, &Config::get_option(OPTION_DIRECT_SERVER)); + + if !disabled && listener.is_none() { + match hbb_common::tcp::listen_any(port as _).await { + Ok(l) => { + listener = Some(l); + log::info!("Direct server listening on: {:?}", l.local_addr()); + } + Err(err) => { + log::error!("Failed to start direct server: {}", err); + } + } + } + + if let Some(l) = listener.as_mut() { + if let Ok(Ok((stream, addr))) = hbb_common::timeout(1000, l.accept()).await { + stream.set_nodelay(true).ok(); + log::info!("direct access from {}", addr); + let server = server.clone(); + tokio::spawn(async move { + crate::server::create_tcp_connection(server, stream, addr, false).await + }); + } + } + } +} +``` diff --git a/docs/report/rustdesk/05-message-format.md b/docs/report/rustdesk/05-message-format.md new file mode 100644 index 00000000..12028efd --- /dev/null +++ b/docs/report/rustdesk/05-message-format.md @@ -0,0 +1,574 @@ +# 消息格式定义 + +## 概述 + +RustDesk 使用 Protocol Buffers (protobuf) 定义所有网络消息格式。主要有两个 proto 文件: + +- `rendezvous.proto` - Rendezvous/Relay 服务器通信消息 +- `message.proto` - 客户端之间通信消息 + +## Rendezvous 消息 (rendezvous.proto) + +### 顶层消息 + +```protobuf +message RendezvousMessage { + oneof union { + RegisterPeer register_peer = 6; + RegisterPeerResponse register_peer_response = 7; + PunchHoleRequest punch_hole_request = 8; + PunchHole punch_hole = 9; + PunchHoleSent punch_hole_sent = 10; + PunchHoleResponse punch_hole_response = 11; + FetchLocalAddr fetch_local_addr = 12; + LocalAddr local_addr = 13; + ConfigUpdate configure_update = 14; + RegisterPk register_pk = 15; + RegisterPkResponse register_pk_response = 16; + SoftwareUpdate software_update = 17; + RequestRelay request_relay = 18; + RelayResponse relay_response = 19; + TestNatRequest test_nat_request = 20; + TestNatResponse test_nat_response = 21; + PeerDiscovery peer_discovery = 22; + OnlineRequest online_request = 23; + OnlineResponse online_response = 24; + KeyExchange key_exchange = 25; + HealthCheck hc = 26; + } +} +``` + +### 注册相关 + +```protobuf +// Peer 注册 +message RegisterPeer { + string id = 1; // Peer ID + int32 serial = 2; // 配置序列号 +} + +message RegisterPeerResponse { + bool request_pk = 2; // 是否需要注册公钥 +} + +// 公钥注册 +message RegisterPk { + string id = 1; // Peer ID + bytes uuid = 2; // 设备 UUID + bytes pk = 3; // Ed25519 公钥 + string old_id = 4; // 旧 ID +} + +message RegisterPkResponse { + enum Result { + OK = 0; + UUID_MISMATCH = 2; + ID_EXISTS = 3; + TOO_FREQUENT = 4; + INVALID_ID_FORMAT = 5; + NOT_SUPPORT = 6; + SERVER_ERROR = 7; + } + Result result = 1; + int32 keep_alive = 2; +} +``` + +### 连接协调相关 + +```protobuf +// 连接类型 +enum ConnType { + DEFAULT_CONN = 0; + FILE_TRANSFER = 1; + PORT_FORWARD = 2; + RDP = 3; + VIEW_CAMERA = 4; +} + +// NAT 类型 +enum NatType { + UNKNOWN_NAT = 0; + ASYMMETRIC = 1; // 可打洞 + SYMMETRIC = 2; // 需要中转 +} + +// Punch Hole 请求 +message PunchHoleRequest { + string id = 1; // 目标 Peer ID + NatType nat_type = 2; + string licence_key = 3; + ConnType conn_type = 4; + string token = 5; + string version = 6; +} + +// Punch Hole 响应 +message PunchHoleResponse { + bytes socket_addr = 1; // 目标地址 + bytes pk = 2; // 公钥(已签名) + enum Failure { + ID_NOT_EXIST = 0; + OFFLINE = 2; + LICENSE_MISMATCH = 3; + LICENSE_OVERUSE = 4; + } + Failure failure = 3; + string relay_server = 4; + oneof union { + NatType nat_type = 5; + bool is_local = 6; + } + string other_failure = 7; + int32 feedback = 8; +} + +// 服务器转发给被控端 +message PunchHole { + bytes socket_addr = 1; // 控制端地址 + string relay_server = 2; + NatType nat_type = 3; +} + +// 被控端发送给服务器 +message PunchHoleSent { + bytes socket_addr = 1; + string id = 2; + string relay_server = 3; + NatType nat_type = 4; + string version = 5; +} +``` + +### Relay 相关 + +```protobuf +// Relay 请求 +message RequestRelay { + string id = 1; + string uuid = 2; // 配对 UUID + bytes socket_addr = 3; + string relay_server = 4; + bool secure = 5; + string licence_key = 6; + ConnType conn_type = 7; + string token = 8; +} + +// Relay 响应 +message RelayResponse { + bytes socket_addr = 1; + string uuid = 2; + string relay_server = 3; + oneof union { + string id = 4; + bytes pk = 5; + } + string refuse_reason = 6; + string version = 7; + int32 feedback = 9; +} +``` + +## 会话消息 (message.proto) + +### 顶层消息 + +```protobuf +message Message { + oneof union { + SignedId signed_id = 3; + PublicKey public_key = 4; + TestDelay test_delay = 5; + VideoFrame video_frame = 6; + LoginRequest login_request = 7; + LoginResponse login_response = 8; + Hash hash = 9; + MouseEvent mouse_event = 10; + AudioFrame audio_frame = 11; + CursorData cursor_data = 12; + CursorPosition cursor_position = 13; + uint64 cursor_id = 14; + KeyEvent key_event = 15; + Clipboard clipboard = 16; + FileAction file_action = 17; + FileResponse file_response = 18; + Misc misc = 19; + Cliprdr cliprdr = 20; + MessageBox message_box = 21; + SwitchSidesResponse switch_sides_response = 22; + VoiceCallRequest voice_call_request = 23; + VoiceCallResponse voice_call_response = 24; + PeerInfo peer_info = 25; + PointerDeviceEvent pointer_device_event = 26; + Auth2FA auth_2fa = 27; + MultiClipboards multi_clipboards = 28; + } +} +``` + +### 认证相关 + +```protobuf +// ID 和公钥 +message IdPk { + string id = 1; + bytes pk = 2; +} + +// 密钥交换 +message PublicKey { + bytes asymmetric_value = 1; // X25519 公钥 + bytes symmetric_value = 2; // 加密的对称密钥 +} + +// 签名的 ID +message SignedId { + bytes id = 1; // 签名的 IdPk +} + +// 密码哈希挑战 +message Hash { + string salt = 1; + string challenge = 2; +} + +// 登录请求 +message LoginRequest { + string username = 1; + bytes password = 2; // 加密的密码 + string my_id = 4; + string my_name = 5; + OptionMessage option = 6; + oneof union { + FileTransfer file_transfer = 7; + PortForward port_forward = 8; + ViewCamera view_camera = 15; + } + bool video_ack_required = 9; + uint64 session_id = 10; + string version = 11; + OSLogin os_login = 12; + string my_platform = 13; + bytes hwid = 14; +} + +// 登录响应 +message LoginResponse { + oneof union { + string error = 1; + PeerInfo peer_info = 2; + } + bool enable_trusted_devices = 3; +} + +// 2FA 认证 +message Auth2FA { + string code = 1; + bytes hwid = 2; +} +``` + +### 视频相关 + +```protobuf +// 编码后的视频帧 +message EncodedVideoFrame { + bytes data = 1; + bool key = 2; // 是否关键帧 + int64 pts = 3; // 时间戳 +} + +message EncodedVideoFrames { + repeated EncodedVideoFrame frames = 1; +} + +// 视频帧 +message VideoFrame { + oneof union { + EncodedVideoFrames vp9s = 6; + RGB rgb = 7; + YUV yuv = 8; + EncodedVideoFrames h264s = 10; + EncodedVideoFrames h265s = 11; + EncodedVideoFrames vp8s = 12; + EncodedVideoFrames av1s = 13; + } + int32 display = 14; // 显示器索引 +} + +// 显示信息 +message DisplayInfo { + sint32 x = 1; + sint32 y = 2; + int32 width = 3; + int32 height = 4; + string name = 5; + bool online = 6; + bool cursor_embedded = 7; + Resolution original_resolution = 8; + double scale = 9; +} +``` + +### 输入相关 + +```protobuf +// 鼠标事件 +message MouseEvent { + int32 mask = 1; // 按钮掩码 + sint32 x = 2; + sint32 y = 3; + repeated ControlKey modifiers = 4; +} + +// 键盘事件 +message KeyEvent { + bool down = 1; // 按下/释放 + bool press = 2; // 单击 + oneof union { + ControlKey control_key = 3; + uint32 chr = 4; // 字符码 + uint32 unicode = 5; // Unicode + string seq = 6; // 字符序列 + uint32 win2win_hotkey = 7; + } + repeated ControlKey modifiers = 8; + KeyboardMode mode = 9; +} + +// 键盘模式 +enum KeyboardMode { + Legacy = 0; + Map = 1; + Translate = 2; + Auto = 3; +} + +// 控制键枚举(部分) +enum ControlKey { + Unknown = 0; + Alt = 1; + Backspace = 2; + CapsLock = 3; + Control = 4; + Delete = 5; + // ... 更多按键 + CtrlAltDel = 100; + LockScreen = 101; +} +``` + +### 音频相关 + +```protobuf +// 音频格式 +message AudioFormat { + uint32 sample_rate = 1; + uint32 channels = 2; +} + +// 音频帧 +message AudioFrame { + bytes data = 1; // Opus 编码数据 +} +``` + +### 剪贴板相关 + +```protobuf +// 剪贴板格式 +enum ClipboardFormat { + Text = 0; + Rtf = 1; + Html = 2; + ImageRgba = 21; + ImagePng = 22; + ImageSvg = 23; + Special = 31; +} + +// 剪贴板内容 +message Clipboard { + bool compress = 1; + bytes content = 2; + int32 width = 3; + int32 height = 4; + ClipboardFormat format = 5; + string special_name = 6; +} + +message MultiClipboards { + repeated Clipboard clipboards = 1; +} +``` + +### 文件传输相关 + +```protobuf +// 文件操作 +message FileAction { + oneof union { + ReadDir read_dir = 1; + FileTransferSendRequest send = 2; + FileTransferReceiveRequest receive = 3; + FileDirCreate create = 4; + FileRemoveDir remove_dir = 5; + FileRemoveFile remove_file = 6; + ReadAllFiles all_files = 7; + FileTransferCancel cancel = 8; + FileTransferSendConfirmRequest send_confirm = 9; + FileRename rename = 10; + ReadEmptyDirs read_empty_dirs = 11; + } +} + +// 文件响应 +message FileResponse { + oneof union { + FileDirectory dir = 1; + FileTransferBlock block = 2; + FileTransferError error = 3; + FileTransferDone done = 4; + FileTransferDigest digest = 5; + ReadEmptyDirsResponse empty_dirs = 6; + } +} + +// 文件传输块 +message FileTransferBlock { + int32 id = 1; + sint32 file_num = 2; + bytes data = 3; + bool compressed = 4; + uint32 blk_id = 5; +} + +// 文件条目 +message FileEntry { + FileType entry_type = 1; + string name = 2; + bool is_hidden = 3; + uint64 size = 4; + uint64 modified_time = 5; +} +``` + +### 杂项消息 + +```protobuf +message Misc { + oneof union { + ChatMessage chat_message = 4; + SwitchDisplay switch_display = 5; + PermissionInfo permission_info = 6; + OptionMessage option = 7; + AudioFormat audio_format = 8; + string close_reason = 9; + bool refresh_video = 10; + bool video_received = 12; + BackNotification back_notification = 13; + bool restart_remote_device = 14; + // ... 更多选项 + } +} + +// Peer 信息 +message PeerInfo { + string username = 1; + string hostname = 2; + string platform = 3; + repeated DisplayInfo displays = 4; + int32 current_display = 5; + bool sas_enabled = 6; + string version = 7; + Features features = 9; + SupportedEncoding encoding = 10; + SupportedResolutions resolutions = 11; + string platform_additions = 12; + WindowsSessions windows_sessions = 13; +} + +// 选项消息 +message OptionMessage { + enum BoolOption { + NotSet = 0; + No = 1; + Yes = 2; + } + ImageQuality image_quality = 1; + BoolOption lock_after_session_end = 2; + BoolOption show_remote_cursor = 3; + BoolOption privacy_mode = 4; + BoolOption block_input = 5; + int32 custom_image_quality = 6; + BoolOption disable_audio = 7; + BoolOption disable_clipboard = 8; + BoolOption enable_file_transfer = 9; + SupportedDecoding supported_decoding = 10; + int32 custom_fps = 11; + // ... 更多选项 +} +``` + +## 消息编码 + +### 长度前缀 + +TCP 传输时使用长度前缀编码: + +```rust +// hbb_common/src/bytes_codec.rs +pub struct BytesCodec { + state: DecodeState, + raw: bool, +} + +impl Decoder for BytesCodec { + type Item = BytesMut; + type Error = std::io::Error; + + fn decode(&mut self, buf: &mut BytesMut) -> Result, Self::Error> { + if self.raw { + // 原始模式:直接返回数据 + if buf.is_empty() { + Ok(None) + } else { + Ok(Some(buf.split())) + } + } else { + // 标准模式:4 字节长度前缀 + 数据 + match self.state { + DecodeState::Head => { + if buf.len() < 4 { + return Ok(None); + } + let len = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]) as usize; + buf.advance(4); + self.state = DecodeState::Data(len); + self.decode(buf) + } + DecodeState::Data(len) => { + if buf.len() < len { + return Ok(None); + } + let data = buf.split_to(len); + self.state = DecodeState::Head; + Ok(Some(data)) + } + } + } + } +} +``` + +### 加密模式 + +当启用加密时,消息结构为: + +``` +┌─────────────┬─────────────┬─────────────────────────┐ +│ Length(4) │ Nonce(8) │ Encrypted Data(N) │ +└─────────────┴─────────────┴─────────────────────────┘ +``` diff --git a/docs/report/rustdesk/06-encryption.md b/docs/report/rustdesk/06-encryption.md new file mode 100644 index 00000000..c2215c42 --- /dev/null +++ b/docs/report/rustdesk/06-encryption.md @@ -0,0 +1,342 @@ +# 加密机制 + +## 概述 + +RustDesk 使用 libsodium (sodiumoxide) 库实现端到端加密,主要包含: + +- **Ed25519**: 用于身份签名和验证 +- **X25519**: 用于密钥交换 +- **ChaCha20-Poly1305**: 用于对称加密 + +## 密钥类型 + +### 1. 身份密钥对 (Ed25519) + +用于 Peer 身份认证和签名: + +```rust +// 生成密钥对 +use sodiumoxide::crypto::sign; +let (pk, sk) = sign::gen_keypair(); +// pk: sign::PublicKey (32 bytes) +// sk: sign::SecretKey (64 bytes) +``` + +### 2. 服务器签名密钥 + +Rendezvous Server 可以配置签名密钥,用于签名 Peer 公钥: + +```rust +// rustdesk-server/src/rendezvous_server.rs:1185-1210 +fn get_server_sk(key: &str) -> (String, Option) { + let mut out_sk = None; + let mut key = key.to_owned(); + + // 如果是 base64 编码的私钥 + if let Ok(sk) = base64::decode(&key) { + if sk.len() == sign::SECRETKEYBYTES { + log::info!("The key is a crypto private key"); + key = base64::encode(&sk[(sign::SECRETKEYBYTES / 2)..]); // 公钥部分 + let mut tmp = [0u8; sign::SECRETKEYBYTES]; + tmp[..].copy_from_slice(&sk); + out_sk = Some(sign::SecretKey(tmp)); + } + } + + // 如果是占位符,生成新密钥对 + if key.is_empty() || key == "-" || key == "_" { + let (pk, sk) = crate::common::gen_sk(0); + out_sk = sk; + if !key.is_empty() { + key = pk; + } + } + + if !key.is_empty() { + log::info!("Key: {}", key); + } + (key, out_sk) +} +``` + +### 3. 会话密钥 (X25519 + ChaCha20) + +用于客户端之间的加密通信: + +```rust +// hbb_common/src/tcp.rs:27-28 +#[derive(Clone)] +pub struct Encrypt(pub Key, pub u64, pub u64); +// Key: secretbox::Key (32 bytes) +// u64: 发送计数器 +// u64: 接收计数器 +``` + +## 密钥交换流程 + +### 1. 身份验证 + +客户端首先交换签名的身份: + +```protobuf +message IdPk { + string id = 1; // Peer ID + bytes pk = 2; // Ed25519 公钥 +} + +message SignedId { + bytes id = 1; // 签名的 IdPk (by server or self) +} +``` + +### 2. X25519 密钥交换 + +使用 X25519 ECDH 生成共享密钥: + +```rust +// 生成临时密钥对 +use sodiumoxide::crypto::box_; +let (our_pk, our_sk) = box_::gen_keypair(); + +// 计算共享密钥 +let shared_secret = box_::curve25519xsalsa20poly1305::scalarmult(&our_sk, &their_pk); + +// 派生对称密钥 +let symmetric_key = secretbox::Key::from_slice(&shared_secret[..32]).unwrap(); +``` + +### 3. 对称密钥消息 + +```protobuf +message PublicKey { + bytes asymmetric_value = 1; // X25519 公钥 + bytes symmetric_value = 2; // 加密的对称密钥(用于额外安全) +} +``` + +## 会话加密 + +### 加密实现 + +```rust +// hbb_common/src/tcp.rs +impl Encrypt { + pub fn new(key: Key) -> Self { + Self(key, 0, 0) // 初始化计数器为 0 + } + + // 加密 + pub fn enc(&mut self, data: &[u8]) -> Vec { + self.1 += 1; // 递增发送计数器 + let nonce = self.get_nonce(self.1); + let encrypted = secretbox::seal(data, &nonce, &self.0); + + // 格式: nonce (8 bytes) + encrypted data + let mut result = Vec::with_capacity(8 + encrypted.len()); + result.extend_from_slice(&self.1.to_le_bytes()); + result.extend_from_slice(&encrypted); + result + } + + // 解密 + pub fn dec(&mut self, data: &mut BytesMut) -> io::Result<()> { + if data.len() < 8 + secretbox::MACBYTES { + return Err(io::Error::new(io::ErrorKind::InvalidData, "too short")); + } + + // 提取 nonce + let counter = u64::from_le_bytes(data[..8].try_into().unwrap()); + + // 防重放攻击检查 + if counter <= self.2 { + return Err(io::Error::new(io::ErrorKind::InvalidData, "replay attack")); + } + self.2 = counter; + + let nonce = self.get_nonce(counter); + let plaintext = secretbox::open(&data[8..], &nonce, &self.0) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "decrypt failed"))?; + + data.clear(); + data.extend_from_slice(&plaintext); + Ok(()) + } + + fn get_nonce(&self, counter: u64) -> Nonce { + let mut nonce = [0u8; 24]; + nonce[..8].copy_from_slice(&counter.to_le_bytes()); + Nonce(nonce) + } +} +``` + +### 消息格式 + +加密后的消息结构: + +``` +┌──────────────────┬─────────────────────────────────────────┐ +│ Counter (8B) │ Encrypted Data + MAC (N+16 bytes) │ +└──────────────────┴─────────────────────────────────────────┘ +``` + +## 密码验证 + +### 挑战-响应机制 + +被控端生成随机盐和挑战,控制端计算哈希响应: + +```protobuf +message Hash { + string salt = 1; // 随机盐 + string challenge = 2; // 随机挑战 +} +``` + +### 密码处理 + +```rust +// 客户端计算密码哈希 +fn get_password_hash(password: &str, salt: &str) -> Vec { + let mut hasher = Sha256::new(); + hasher.update(password.as_bytes()); + hasher.update(salt.as_bytes()); + hasher.finalize().to_vec() +} + +// 发送加密的密码(使用对称密钥加密) +fn encrypt_password(password_hash: &[u8], symmetric_key: &Key) -> Vec { + secretbox::seal(password_hash, &nonce, symmetric_key) +} +``` + +## 服务器公钥验证 + +### 签名验证 + +如果 Rendezvous Server 配置了密钥,会签名 Peer 公钥: + +```rust +// 服务器签名 IdPk +let signed_id_pk = sign::sign( + &IdPk { id, pk, ..Default::default() } + .write_to_bytes()?, + &server_sk, +); + +// 客户端验证 +fn verify_server_signature(signed_pk: &[u8], server_pk: &sign::PublicKey) -> Option { + if let Ok(verified) = sign::verify(signed_pk, server_pk) { + return IdPk::parse_from_bytes(&verified).ok(); + } + None +} +``` + +### 客户端获取服务器公钥 + +```rust +pub async fn get_rs_pk(id: &str) -> ResultType<(String, sign::PublicKey)> { + // 从配置或 Rendezvous Server 获取公钥 + let key = Config::get_option("key"); + if !key.is_empty() { + if let Ok(pk) = base64::decode(&key) { + if pk.len() == sign::PUBLICKEYBYTES { + return Ok((key, sign::PublicKey::from_slice(&pk).unwrap())); + } + } + } + // ... 从服务器获取 +} +``` + +## TCP 连接加密 + +### 安全 TCP 握手 + +```rust +// rustdesk/src/common.rs +pub async fn secure_tcp(conn: &mut Stream, key: &str) -> ResultType<()> { + // 1. 生成临时 X25519 密钥对 + let (our_pk, our_sk) = box_::gen_keypair(); + + // 2. 发送我们的公钥 + let mut msg = Message::new(); + msg.set_public_key(PublicKey { + asymmetric_value: our_pk.0.to_vec().into(), + ..Default::default() + }); + conn.send(&msg).await?; + + // 3. 接收对方公钥 + let msg = conn.next_timeout(CONNECT_TIMEOUT).await? + .ok_or_else(|| anyhow!("timeout"))?; + let their_pk = msg.get_public_key(); + + // 4. 计算共享密钥 + let shared = box_::curve25519xsalsa20poly1305::scalarmult( + &our_sk, + &box_::PublicKey::from_slice(&their_pk.asymmetric_value)?, + ); + + // 5. 设置加密 + conn.set_key(secretbox::Key::from_slice(&shared[..32]).unwrap()); + Ok(()) +} +``` + +## 安全特性 + +### 1. 前向保密 + +每个会话使用临时密钥对,即使长期密钥泄露,历史会话仍然安全。 + +### 2. 重放攻击防护 + +使用递增计数器作为 nonce 的一部分,拒绝旧的或重复的消息。 + +### 3. 中间人攻击防护 + +- 服务器签名 Peer 公钥 +- 可配置服务器公钥验证 + +### 4. 密码暴力破解防护 + +- 使用盐和多次哈希 +- 服务器端限流 + +## 加密算法参数 + +| 算法 | 密钥大小 | Nonce 大小 | MAC 大小 | +|------|----------|------------|----------| +| Ed25519 | 64 bytes (private), 32 bytes (public) | N/A | 64 bytes | +| X25519 | 32 bytes | N/A | N/A | +| ChaCha20-Poly1305 | 32 bytes | 24 bytes | 16 bytes | + +## 密钥生命周期 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 长期密钥 (Ed25519) │ +│ ┌─────────────────┐ │ +│ │ 设备首次启动时生成 │ │ +│ │ 存储在配置文件中 │ │ +│ └─────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 会话密钥 (X25519) │ +│ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ 每次连接时生成 │───►│ 用于密钥协商 │ │ +│ │ 临时密钥对 │ │ 派生对称密钥 │ │ +│ └─────────────────┘ └─────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ 对称密钥 (ChaCha20-Poly1305) │ │ +│ │ 用于会话中的所有消息加密 │ │ +│ │ 会话结束时销毁 │ │ +│ └─────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` diff --git a/docs/report/rustdesk/07-nat-traversal.md b/docs/report/rustdesk/07-nat-traversal.md new file mode 100644 index 00000000..cfc75688 --- /dev/null +++ b/docs/report/rustdesk/07-nat-traversal.md @@ -0,0 +1,410 @@ +# NAT 穿透技术 + +## 概述 + +RustDesk 实现了多种 NAT 穿透技术,以在不同网络环境下建立 P2P 连接: + +- NAT 类型检测 +- UDP 打洞 +- TCP 打洞 +- Relay 中转(作为后备) + +## NAT 类型 + +### 分类 + +```protobuf +enum NatType { + UNKNOWN_NAT = 0; // 未知 + ASYMMETRIC = 1; // 非对称 NAT (Cone NAT) - 可打洞 + SYMMETRIC = 2; // 对称 NAT - 通常需要 Relay +} +``` + +### NAT 类型说明 + +| 类型 | 描述 | 可打洞 | +|------|------|--------| +| Full Cone | 外部端口固定,任何外部主机可访问 | ✅ 最容易 | +| Restricted Cone | 外部端口固定,仅允许曾发送过数据的 IP | ✅ 容易 | +| Port Restricted Cone | 外部端口固定,仅允许曾发送过数据的 IP:Port | ✅ 可能 | +| Symmetric | 每个目标地址使用不同外部端口 | ❌ 困难 | + +## NAT 类型检测 + +### 检测原理 + +RustDesk 使用双端口检测法: + +1. 客户端向 Rendezvous Server 的主端口 (21116) 发送 TestNatRequest +2. 同时向 NAT 测试端口 (21115) 发送 TestNatRequest +3. 比较两次响应中观测到的源端口 + +``` +客户端 Rendezvous Server + │ │ + │ TestNatRequest ────────►│ Port 21116 + │ │ + │ TestNatRequest ────────►│ Port 21115 + │ │ + │◄──────── TestNatResponse │ (包含观测到的源端口) + │ │ + │ │ + │ 比较两次源端口 │ + │ 相同 → ASYMMETRIC │ + │ 不同 → SYMMETRIC │ +``` + +### 实现代码 + +**客户端发送检测请求:** + +```rust +// rustdesk/src/lib.rs +pub fn test_nat_type() { + tokio::spawn(async move { + let rendezvous_server = Config::get_rendezvous_servers().first().cloned(); + if let Some(host) = rendezvous_server { + // 连接主端口 + let host = check_port(&host, RENDEZVOUS_PORT); + + // 连接 NAT 测试端口 + let host2 = crate::increase_port(&host, -1); + + // 发送测试请求 + let mut msg = RendezvousMessage::new(); + msg.set_test_nat_request(TestNatRequest { + serial: Config::get_serial(), + }); + + // 收集两次响应的端口 + let port1 = send_and_get_port(&host, &msg).await; + let port2 = send_and_get_port(&host2, &msg).await; + + // 判断 NAT 类型 + let nat_type = if port1 == port2 { + NatType::ASYMMETRIC // 可打洞 + } else { + NatType::SYMMETRIC // 需要 Relay + }; + + Config::set_nat_type(nat_type as i32); + } + }); +} +``` + +**服务器响应:** + +```rust +// rustdesk-server/src/rendezvous_server.rs:1080-1087 +Some(rendezvous_message::Union::TestNatRequest(_)) => { + let mut msg_out = RendezvousMessage::new(); + msg_out.set_test_nat_response(TestNatResponse { + port: addr.port() as _, // 返回观测到的源端口 + ..Default::default() + }); + stream.send(&msg_out).await.ok(); +} +``` + +## UDP 打洞 + +### 原理 + +UDP 打洞利用 NAT 的端口映射机制: + +``` + A (内网) B (内网) + │ │ + │ ──► NAT_A ──► Internet ──► NAT_B ──► (丢弃) │ + │ │ + │ │ + │ (NAT_A 创建了映射 A:port → A_ext:port_a) │ + │ │ + │ │ + │ (丢弃) ◄── NAT_A ◄── Internet ◄── NAT_B ◄── │ + │ │ + │ │ + │ (NAT_B 创建了映射 B:port → B_ext:port_b) │ + │ │ + │ ──► NAT_A ──► Internet ──► NAT_B ──► │ + │ (NAT_A 的映射存在,包被转发) │ + │ │ + │ ◄── NAT_A ◄── Internet ◄── NAT_B ◄── │ + │ (NAT_B 的映射存在,包被转发) │ + │ │ + │ ◄───────── 双向通信建立 ──────────► │ +``` + +### 实现 + +**被控端打洞:** + +```rust +// rustdesk/src/rendezvous_mediator.rs:621-642 +async fn punch_udp_hole( + &self, + peer_addr: SocketAddr, + server: ServerPtr, + msg_punch: PunchHoleSent, +) -> ResultType<()> { + let mut msg_out = Message::new(); + msg_out.set_punch_hole_sent(msg_punch); + + // 创建 UDP socket + let (socket, addr) = new_direct_udp_for(&self.host).await?; + let data = msg_out.write_to_bytes()?; + + // 发送到 Rendezvous Server(会转发给控制端) + socket.send_to(&data, addr).await?; + + // 多次发送以增加成功率 + let socket_cloned = socket.clone(); + tokio::spawn(async move { + for _ in 0..2 { + let tm = (hbb_common::time_based_rand() % 20 + 10) as f32 / 1000.; + hbb_common::sleep(tm).await; + socket.send_to(&data, addr).await.ok(); + } + }); + + // 等待对方连接 + udp_nat_listen(socket_cloned, peer_addr, peer_addr, server).await?; + Ok(()) +} +``` + +**UDP 监听和 KCP 建立:** + +```rust +// rustdesk/src/rendezvous_mediator.rs:824-851 +async fn udp_nat_listen( + socket: Arc, + peer_addr: SocketAddr, + peer_addr_v4: SocketAddr, + server: ServerPtr, +) -> ResultType<()> { + // 连接到对方地址 + socket.connect(peer_addr).await?; + + // 执行 UDP 打洞 + let res = crate::punch_udp(socket.clone(), true).await?; + + // 建立 KCP 可靠传输层 + let stream = crate::kcp_stream::KcpStream::accept( + socket, + Duration::from_millis(CONNECT_TIMEOUT as _), + res, + ).await?; + + // 创建连接 + crate::server::create_tcp_connection(server, stream.1, peer_addr_v4, true).await?; + Ok(()) +} +``` + +### KCP 协议 + +RustDesk 在 UDP 上使用 KCP 提供可靠传输,KCP 特点: + +- 更激进的重传策略 +- 更低的延迟 +- 可配置的可靠性级别 + +## TCP 打洞 + +### 原理 + +TCP 打洞比 UDP 困难,因为 TCP 需要三次握手。技巧是让双方同时发起连接: + +``` + A NAT_A NAT_B B + │ │ │ │ + │ ─── SYN ───────────────►│─────────│────► (丢弃,无映射) │ + │ │ │ │ + │ (NAT_A 创建到 B 的映射) │ │ │ + │ │ │ │ + │ (丢弃,无映射) ◄─────────│─────────│◄─── SYN ───────────── │ + │ │ │ │ + │ │ │ (NAT_B 创建到 A 的映射) │ + │ │ │ │ + │ ─── SYN ───────────────►│─────────│────► SYN ───────────► │ + │ │ │ (映射存在,转发成功) │ + │ │ │ │ + │ ◄─── SYN+ACK ──────────│─────────│◄─── SYN+ACK ───────── │ + │ │ │ │ + │ ─── ACK ───────────────►│─────────│────► ACK ───────────► │ + │ │ │ │ + │ ◄─────────── 连接建立 ─────────────────────────────────────►│ +``` + +### 实现 + +```rust +// rustdesk/src/rendezvous_mediator.rs:604-617 +log::debug!("Punch tcp hole to {:?}", peer_addr); +let mut socket = { + // 1. 先连接 Rendezvous Server 获取本地地址 + let socket = connect_tcp(&*self.host, CONNECT_TIMEOUT).await?; + let local_addr = socket.local_addr(); + + // 2. 用相同的本地地址尝试连接对方 + // 这会在 NAT 上创建映射 + // 虽然连接会失败,但映射已建立 + allow_err!(socket_client::connect_tcp_local(peer_addr, Some(local_addr), 30).await); + + socket +}; + +// 3. 发送 PunchHoleSent 通知服务器 +// 服务器会转发给控制端 +let mut msg_out = Message::new(); +msg_out.set_punch_hole_sent(msg_punch); +socket.send_raw(msg_out.write_to_bytes()?).await?; + +// 4. 等待控制端连接 +// 由于已有映射,控制端的连接可以成功 +crate::accept_connection(server.clone(), socket, peer_addr, true).await; +``` + +## 局域网直连 + +### 检测同一局域网 + +```rust +// rustdesk-server/src/rendezvous_server.rs:721-728 +let same_intranet: bool = !ws + && (peer_is_lan && is_lan || { + match (peer_addr, addr) { + (SocketAddr::V4(a), SocketAddr::V4(b)) => a.ip() == b.ip(), + (SocketAddr::V6(a), SocketAddr::V6(b)) => a.ip() == b.ip(), + _ => false, + } + }); +``` + +### 局域网连接流程 + +``` +控制端 Rendezvous Server 被控端 + │ │ │ + │ PunchHoleRequest ────►│ │ + │ │ │ + │ │ (检测到同一局域网) │ + │ │ │ + │ │ FetchLocalAddr ──────►│ + │ │ │ + │ │◄────── LocalAddr ────────│ + │ │ (包含被控端内网地址) │ + │ │ │ + │◄─ PunchHoleResponse ──│ │ + │ (is_local=true) │ │ + │ (socket_addr=内网地址)│ │ + │ │ │ + │ ─────────── 直接连接内网地址 ────────────────────►│ +``` + +## IPv6 支持 + +IPv6 通常不需要 NAT 穿透,但 RustDesk 仍支持 IPv6 打洞以处理有状态防火墙: + +```rust +// rustdesk/src/rendezvous_mediator.rs:808-822 +async fn start_ipv6( + peer_addr_v6: SocketAddr, + peer_addr_v4: SocketAddr, + server: ServerPtr, +) -> bytes::Bytes { + crate::test_ipv6().await; + if let Some((socket, local_addr_v6)) = crate::get_ipv6_socket().await { + let server = server.clone(); + tokio::spawn(async move { + allow_err!(udp_nat_listen(socket.clone(), peer_addr_v6, peer_addr_v4, server).await); + }); + return local_addr_v6; + } + Default::default() +} +``` + +## 连接策略决策树 + +``` + 开始连接 + │ + ▼ + ┌───────────────┐ + │ NAT 类型检测 │ + └───────┬───────┘ + │ + ┌───────────────┼───────────────┐ + │ │ │ + ▼ ▼ ▼ + ASYMMETRIC UNKNOWN SYMMETRIC + │ │ │ + ▼ ▼ │ + ┌──────────┐ ┌──────────┐ │ + │ 尝试 UDP │ │ 尝试 TCP │ │ + │ 打洞 │ │ 打洞 │ │ + └────┬─────┘ └────┬─────┘ │ + │ │ │ + 成功 │ 失败 成功 │ 失败 │ + ▼ │ ▼ │ │ + ┌────────┐│ ┌────────┐│ │ + │UDP P2P ││ │TCP P2P ││ │ + └────────┘│ └────────┘│ │ + │ │ │ + └───────┬───────┘ │ + │ │ + ▼ │ + ┌───────────────┐ │ + │ 使用 Relay │◄─────────┘ + └───────────────┘ +``` + +## 性能优化 + +### 多路径尝试 + +RustDesk 同时尝试多种连接方式,选择最快成功的: + +```rust +// rustdesk/src/client.rs:342-364 +let mut connect_futures = Vec::new(); + +// 同时尝试 UDP 和 TCP +if udp.0.is_some() { + connect_futures.push(Self::_start_inner(..., udp).boxed()); +} +connect_futures.push(Self::_start_inner(..., (None, None)).boxed()); + +// 使用 select_ok 选择第一个成功的 +match select_ok(connect_futures).await { + Ok(conn) => Ok(conn), + Err(e) => Err(e), +} +``` + +### 超时控制 + +```rust +const CONNECT_TIMEOUT: u64 = 18_000; // 18 秒 +const REG_TIMEOUT: i32 = 30_000; // 30 秒 + +// 连接超时处理 +if let Ok(Ok((stream, addr))) = timeout(CONNECT_TIMEOUT, socket.accept()).await { + // 连接成功 +} else { + // 超时,尝试其他方式 +} +``` + +## 常见问题和解决方案 + +| 问题 | 原因 | 解决方案 | +|------|------|----------| +| 双 Symmetric NAT | 两端都是对称 NAT | 使用 Relay | +| 防火墙阻止 UDP | 企业防火墙 | 使用 TCP 或 WebSocket | +| 端口预测失败 | NAT 端口分配不规律 | 多次尝试或使用 Relay | +| IPv6 不通 | ISP 或防火墙问题 | 回退到 IPv4 | diff --git a/docs/report/rustdesk/08-onekvm-comparison.md b/docs/report/rustdesk/08-onekvm-comparison.md new file mode 100644 index 00000000..5fdf5494 --- /dev/null +++ b/docs/report/rustdesk/08-onekvm-comparison.md @@ -0,0 +1,401 @@ +# RustDesk 协议 vs One-KVM 实现对比分析 + +本文档对比分析 RustDesk 原始协议与 One-KVM 的实现差异。 + +## 1. 概述 + +One-KVM 作为 IP-KVM 解决方案,只实现了 RustDesk 协议的**被控端(Controlled)** 功能,不实现控制端(Controller)功能。这是设计决策,因为 KVM 设备只需要接收远程控制,不需要控制其他设备。 + +### 架构差异 + +| 方面 | RustDesk 原版 | One-KVM | +|------|---------------|---------| +| 角色 | 双向(控制端+被控端) | 单向(仅被控端) | +| 连接方式 | P2P + Relay | 仅 Relay (TCP) | +| NAT 穿透 | UDP/TCP 打洞 + TURN | 不支持 | +| 传输协议 | UDP/TCP | 仅 TCP | + +## 2. 已实现功能 + +### 2.1 Rendezvous 协议 (hbbs 通信) + +| 消息类型 | 实现状态 | 备注 | +|----------|----------|------| +| RegisterPeer | ✅ 已实现 | 注册设备到服务器 | +| RegisterPeerResponse | ✅ 已实现 | 处理注册响应 | +| RegisterPk | ✅ 已实现 | 注册公钥 | +| RegisterPkResponse | ✅ 已实现 | 处理公钥注册响应 | +| PunchHoleSent | ✅ 已实现 | 响应打洞请求 | +| FetchLocalAddr | ✅ 已实现 | 获取本地地址 | +| LocalAddr | ✅ 已实现 | 返回本地地址 | +| RequestRelay | ✅ 已实现 | 请求中继连接 | +| RelayResponse | ✅ 已实现 | 处理中继响应 | +| ConfigUpdate | ✅ 已实现 | 接收配置更新 | + +**实现文件**: `src/rustdesk/rendezvous.rs` (~829 行) + +```rust +// 核心结构 +pub struct RendezvousMediator { + config: RustDeskConfig, + key_pair: KeyPair, + signing_key: SigningKeyPair, + socket: UdpSocket, + status: Arc>, + // ... +} +``` + +### 2.2 连接协议 (客户端连接) + +| 消息类型 | 实现状态 | 备注 | +|----------|----------|------| +| SignedId | ✅ 已实现 | 签名身份验证 | +| PublicKey | ✅ 已实现 | 公钥交换 | +| Hash | ✅ 已实现 | 哈希挑战响应 | +| LoginRequest | ✅ 已实现 | 登录认证 | +| LoginResponse | ✅ 已实现 | 登录响应 | +| TestDelay | ✅ 已实现 | 延迟测试 | +| VideoFrame | ✅ 已实现 | 视频帧发送 | +| AudioFrame | ✅ 已实现 | 音频帧发送 | +| CursorData | ✅ 已实现 | 光标图像 | +| CursorPosition | ✅ 已实现 | 光标位置 | +| MouseEvent | ✅ 已实现 | 鼠标事件接收 | +| KeyEvent | ✅ 已实现 | 键盘事件接收 | + +**实现文件**: `src/rustdesk/connection.rs` (~1349 行) + +```rust +// 连接状态机 +pub enum ConnectionState { + WaitingForSignedId, + WaitingForPublicKey, + WaitingForHash, + WaitingForLogin, + Authenticated, + Streaming, +} +``` + +### 2.3 加密模块 + +| 功能 | 实现状态 | 备注 | +|------|----------|------| +| Curve25519 密钥对 | ✅ 已实现 | 用于加密 | +| Ed25519 签名密钥对 | ✅ 已实现 | 用于签名 | +| Ed25519 → Curve25519 转换 | ✅ 已实现 | 密钥派生 | +| XSalsa20-Poly1305 | ✅ 已实现 | 会话加密 (secretbox) | +| 密码哈希 | ✅ 已实现 | 单重/双重 SHA256 | +| 会话密钥协商 | ✅ 已实现 | 对称密钥派生 | + +**实现文件**: `src/rustdesk/crypto.rs` (~468 行) + +```rust +// 密钥对结构 +pub struct KeyPair { + secret_key: [u8; 32], // Curve25519 私钥 + public_key: [u8; 32], // Curve25519 公钥 +} + +pub struct SigningKeyPair { + secret_key: [u8; 64], // Ed25519 私钥 + public_key: [u8; 32], // Ed25519 公钥 +} +``` + +### 2.4 视频/音频流 + +| 编码格式 | 实现状态 | 备注 | +|----------|----------|------| +| H.264 | ✅ 已实现 | 主要格式 | +| H.265/HEVC | ✅ 已实现 | 高效编码 | +| VP8 | ✅ 已实现 | WebRTC 兼容 | +| VP9 | ✅ 已实现 | 高质量 | +| AV1 | ✅ 已实现 | 新一代编码 | +| Opus 音频 | ✅ 已实现 | 低延迟音频 | + +**实现文件**: `src/rustdesk/frame_adapters.rs` (~316 行) + +### 2.5 HID 事件 + +| 功能 | 实现状态 | 备注 | +|------|----------|------| +| 鼠标移动 | ✅ 已实现 | 绝对/相对坐标 | +| 鼠标按键 | ✅ 已实现 | 左/中/右键 | +| 鼠标滚轮 | ✅ 已实现 | 垂直滚动 | +| 键盘按键 | ✅ 已实现 | 按下/释放 | +| 控制键映射 | ✅ 已实现 | ControlKey → USB HID | +| X11 键码映射 | ✅ 已实现 | X11 → USB HID | + +**实现文件**: `src/rustdesk/hid_adapter.rs` (~386 行) + +### 2.6 协议帧编码 + +| 功能 | 实现状态 | 备注 | +|------|----------|------| +| BytesCodec | ✅ 已实现 | 变长帧编码 | +| 1-4 字节头 | ✅ 已实现 | 根据长度自动选择 | +| 最大 1GB 消息 | ✅ 已实现 | 与原版一致 | + +**实现文件**: `src/rustdesk/bytes_codec.rs` (~253 行) + +## 3. 未实现功能 + +### 3.1 NAT 穿透相关 + +| 功能 | 原因 | +|------|------| +| UDP 打洞 | One-KVM 仅使用 TCP 中继 | +| TCP 打洞 | 同上 | +| STUN/TURN | 不需要 NAT 类型检测 | +| TestNat | 同上 | +| P2P 直连 | 设计简化,仅支持中继 | + +### 3.2 客户端发起功能 + +| 功能 | 原因 | +|------|------| +| PunchHole (发起) | KVM 只接收连接 | +| RelayRequest | 同上 | +| ConnectPeer | 同上 | +| OnlineRequest | 不需要查询其他设备 | + +### 3.3 文件传输 + +| 功能 | 原因 | +|------|------| +| FileTransfer | 超出 KVM 功能范围 | +| FileAction | 同上 | +| FileResponse | 同上 | +| FileTransferBlock | 同上 | + +### 3.4 高级功能 + +| 功能 | 原因 | +|------|------| +| 剪贴板同步 | 超出 KVM 功能范围 | +| 多显示器切换 | One-KVM 使用单一视频源 | +| 虚拟显示器 | 不适用 | +| 端口转发 | 超出 KVM 功能范围 | +| 语音通话 | 不需要 | +| RDP 输入 | 不需要 | +| 插件系统 | 不支持 | +| 软件更新 | One-KVM 有自己的更新机制 | + +### 3.5 权限协商 + +| 功能 | 原因 | +|------|------| +| Option 消息 | One-KVM 假设完全控制权限 | +| 权限请求 | 同上 | +| PermissionInfo | 同上 | + +## 4. 实现差异 + +### 4.1 连接模式 + +**RustDesk 原版:** +``` +客户端 ──UDP打洞──> 被控端 (P2P 优先) + └──Relay──> 被控端 (回退) +``` + +**One-KVM:** +``` +RustDesk客户端 ──TCP中继──> hbbr服务器 ──> One-KVM设备 +``` + +One-KVM 只支持 TCP 中继连接,不支持 P2P 直连。这简化了实现,但可能增加延迟。 + +### 4.2 会话加密 + +**RustDesk 原版:** +- 支持 ChaCha20-Poly1305 (流式) +- 支持 XSalsa20-Poly1305 (secretbox) +- 动态协商加密方式 + +**One-KVM:** +- 仅支持 XSalsa20-Poly1305 (secretbox) +- 使用序列号作为 nonce + +```rust +// One-KVM 的加密实现 +fn encrypt_message(&mut self, plaintext: &[u8]) -> Vec { + let nonce = make_nonce(&self.send_nonce); + self.send_nonce = self.send_nonce.wrapping_add(1); + secretbox::seal(plaintext, &nonce, &self.session_key) +} +``` + +### 4.3 视频流方向 + +**RustDesk 原版:** +- 双向视频流(可控制和被控制) +- 远程桌面捕获 + +**One-KVM:** +- 单向视频流(仅发送) +- 从 V4L2 设备捕获 +- 集成到 One-KVM 的 VideoStreamManager + +```rust +// One-KVM 视频流集成 +pub async fn start_video_stream(&self, state: &AppState) { + let stream_manager = &state.video_stream_manager; + // 从 One-KVM 的视频管理器获取帧 +} +``` + +### 4.4 HID 事件处理 + +**RustDesk 原版:** +- 转发到远程系统的输入子系统 +- 使用 enigo 或 uinput + +**One-KVM:** +- 转发到 USB OTG/HID 设备 +- 控制物理 KVM 目标机器 + +```rust +// One-KVM HID 适配 +pub fn convert_mouse_event(event: &RustDeskMouseEvent) -> Option { + // 转换 RustDesk 鼠标事件到 One-KVM HID 事件 +} + +pub fn convert_key_event(event: &RustDeskKeyEvent) -> Option { + // 转换 RustDesk 键盘事件到 One-KVM HID 事件 +} +``` + +### 4.5 配置管理 + +**RustDesk 原版:** +- 使用 TOML/JSON 配置文件 +- 硬编码默认值 + +**One-KVM:** +- 集成到 SQLite 配置系统 +- Web UI 管理 +- 使用 typeshare 生成 TypeScript 类型 + +```rust +#[typeshare] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RustDeskConfig { + pub enabled: bool, + pub rendezvous_server: String, + pub device_id: String, + // ... +} +``` + +### 4.6 设备 ID 生成 + +**RustDesk 原版:** +- 基于 MAC 地址和硬件信息 +- 固定便携式 ID + +**One-KVM:** +- 随机生成 9 位数字 +- 存储在配置中 + +```rust +pub fn generate_device_id() -> String { + let mut rng = rand::thread_rng(); + let id: u32 = rng.gen_range(100_000_000..999_999_999); + id.to_string() +} +``` + +## 5. 协议兼容性 + +### 5.1 完全兼容 + +| 功能 | 说明 | +|------|------| +| Rendezvous 注册 | 可与官方 hbbs 服务器通信 | +| 中继连接 | 可通过官方 hbbr 服务器中继 | +| 加密握手 | 与 RustDesk 客户端兼容 | +| 视频编码 | 支持所有主流编码格式 | +| HID 事件 | 接收标准 RustDesk 输入事件 | + +### 5.2 部分兼容 + +| 功能 | 说明 | +|------|------| +| 密码认证 | 仅支持设备密码,不支持一次性密码 | +| 会话加密 | 仅 XSalsa20-Poly1305 | + +### 5.3 不兼容 + +| 功能 | 说明 | +|------|------| +| P2P 连接 | 客户端必须通过中继连接 | +| 文件传输 | 不支持 | +| 剪贴板 | 不支持 | + +## 6. 代码结构对比 + +### RustDesk 原版结构 + +``` +rustdesk/ +├── libs/hbb_common/ # 公共库 +│ ├── protos/ # Protobuf 定义 +│ └── src/ +├── src/ +│ ├── server/ # 被控端服务 +│ ├── client/ # 控制端 +│ ├── ui/ # 用户界面 +│ └── rendezvous_mediator.rs +``` + +### One-KVM 结构 + +``` +src/rustdesk/ +├── mod.rs # 模块导出 +├── config.rs # 配置类型 (~164 行) +├── crypto.rs # 加密模块 (~468 行) +├── bytes_codec.rs # 帧编码 (~253 行) +├── protocol.rs # 消息辅助 (~170 行) +├── rendezvous.rs # Rendezvous 中介 (~829 行) +├── connection.rs # 连接处理 (~1349 行) +├── hid_adapter.rs # HID 转换 (~386 行) +└── frame_adapters.rs # 视频/音频适配 (~316 行) +``` + +**总计**: ~3935 行代码 + +## 7. 总结 + +### 实现率统计 + +| 类别 | RustDesk 功能数 | One-KVM 实现数 | 实现率 | +|------|-----------------|----------------|--------| +| Rendezvous 协议 | 15+ | 10 | ~67% | +| 连接协议 | 30+ | 12 | ~40% | +| 加密功能 | 8 | 6 | 75% | +| 视频/音频 | 6 | 6 | 100% | +| HID 功能 | 6 | 6 | 100% | + +### 设计理念 + +One-KVM 的 RustDesk 实现专注于 **IP-KVM 核心功能**: + +1. **精简**: 只实现必要的被控端功能 +2. **可靠**: 使用 TCP 中继保证连接稳定性 +3. **集成**: 与 One-KVM 现有视频/HID 系统无缝集成 +4. **安全**: 完整实现加密和认证机制 + +### 客户端兼容性 + +One-KVM 可与标准 RustDesk 客户端配合使用: +- RustDesk 桌面客户端 (Windows/macOS/Linux) +- RustDesk 移动客户端 (Android/iOS) +- RustDesk Web 客户端 + +只需确保: +1. 配置相同的 Rendezvous 服务器 +2. 使用设备 ID 和密码连接 +3. 客户端支持中继连接 diff --git a/docs/system-architecture.md b/docs/system-architecture.md new file mode 100644 index 00000000..15210555 --- /dev/null +++ b/docs/system-architecture.md @@ -0,0 +1,876 @@ +# One-KVM 系统架构文档 + +## 1. 项目概述 + +One-KVM 是一个用 Rust 编写的轻量级、开源 IP-KVM 解决方案。它提供 BIOS 级别的远程服务器管理能力,支持视频流、键鼠控制、虚拟存储、电源管理和音频等功能。 + +### 1.1 核心特性 + +- **单一二进制部署**:Web UI + 后端一体化,无需额外配置文件 +- **双流模式**:支持 WebRTC(H264/H265/VP8/VP9)和 MJPEG 两种流模式 +- **USB OTG**:虚拟键鼠、虚拟存储、虚拟网卡 +- **ATX 电源控制**:GPIO/USB 继电器 +- **RustDesk 协议集成**:支持跨平台访问 +- **Vue3 SPA 前端**:支持中文/英文 +- **SQLite 配置存储**:无需配置文件 + +### 1.2 目标平台 + +| 平台 | 架构 | 用途 | +|------|------|------| +| aarch64-unknown-linux-gnu | ARM64 | 主要目标(Rockchip RK3328 等) | +| armv7-unknown-linux-gnueabihf | ARMv7 | 备选平台 | +| x86_64-unknown-linux-gnu | x86-64 | 开发/测试环境 | + +--- + +## 2. 系统架构图 + +### 2.1 整体架构 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ One-KVM System │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ Web Frontend (Vue3) │ │ +│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ +│ │ │ Console │ │ Settings │ │ Login │ │ Setup │ │ Virtual │ │ │ +│ │ │ View │ │ View │ │ View │ │ View │ │ Keyboard │ │ │ +│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ +│ │ │ │ │ +│ │ ┌─────────────────┴─────────────────┐ │ │ +│ │ │ Pinia State Store │ │ │ +│ │ └─────────────────┬─────────────────┘ │ │ +│ │ │ │ │ +│ │ ┌──────────────────────────────────────────────────────────────┐ │ │ +│ │ │ API Client Layer │ │ │ +│ │ │ HTTP REST │ WebSocket │ WebRTC Signaling │ MJPEG │ │ │ +│ │ └──────────────────────────────────────────────────────────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ │ HTTP/WS/WebRTC │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ Axum Web Server (routes.rs) │ │ +│ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │ +│ │ │ Public │ │ User │ │ Admin │ │ Static │ │ │ +│ │ │ Routes │ │ Routes │ │ Routes │ │ Files │ │ │ +│ │ └───────────┘ └───────────┘ └───────────┘ └───────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ AppState (state.rs) │ │ +│ │ ┌─────────────────────────────────────────────────────────────┐ │ │ +│ │ │ Central State Hub │ │ │ +│ │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ │ +│ │ │ │ConfigStore │ │SessionStore│ │ UserStore │ │ │ │ +│ │ │ │ (SQLite) │ │ (Memory) │ │ (SQLite) │ │ │ │ +│ │ │ └────────────┘ └────────────┘ └────────────┘ │ │ │ +│ │ │ │ │ │ +│ │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ │ +│ │ │ │ EventBus │ │ OtgService │ │ Extensions │ │ │ │ +│ │ │ │ (Broadcast)│ │ (USB) │ │ Manager │ │ │ │ +│ │ │ └────────────┘ └────────────┘ └────────────┘ │ │ │ +│ │ └─────────────────────────────────────────────────────────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ┌─────────────────────────────┼─────────────────────────────┐ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ +│ │ Video │ │ HID │ │ Audio │ │ +│ │ Module │ │ Module │ │ Module │ │ +│ ├────────────┤ ├────────────┤ ├────────────┤ │ +│ │ Capture │ │ Controller │ │ Capture │ │ +│ │ Encoder │ │ OTG Backend│ │ Encoder │ │ +│ │ Streamer │ │ CH9329 │ │ Pipeline │ │ +│ │ Pipeline │ │ DataChannel│ │ (Opus) │ │ +│ └────────────┘ └────────────┘ └────────────┘ │ +│ │ │ │ │ +│ └───────────────────────────┼──────────────────────────┘ │ +│ │ │ +│ ┌─────────────────────────────┼─────────────────────────────┐ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ +│ │ MSD │ │ ATX │ │ RustDesk │ │ +│ │ Module │ │ Module │ │ Module │ │ +│ ├────────────┤ ├────────────┤ ├────────────┤ │ +│ │ Controller │ │ Controller │ │ Service │ │ +│ │ Image Mgr │ │ Executor │ │ Rendezvous │ │ +│ │ Ventoy │ │ LED Monitor│ │ Connection │ │ +│ │ Drive │ │ WOL │ │ Protocol │ │ +│ └────────────┘ └────────────┘ └────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Hardware Layer │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ +│ │ V4L2 Video │ │ USB OTG │ │ GPIO │ │ ALSA │ │ +│ │ Device │ │ Gadget │ │ Sysfs │ │ Audio │ │ +│ │/dev/video* │ │ ConfigFS │ │ │ │ │ │ +│ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### 2.2 数据流架构 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Data Flow Overview │ +└─────────────────────────────────────────────────────────────────────────────┘ + + ┌─────────────────┐ + │ Target PC │ + └────────┬────────┘ + │ + ┌────────────────────────┼────────────────────────┐ + │ │ │ + ▼ ▼ ▼ +┌───────────────┐ ┌───────────────┐ ┌───────────────┐ +│ HDMI Capture │ │ USB Port │ │ GPIO/Relay │ +│ Card │ │ (OTG Mode) │ │ (ATX) │ +└───────┬───────┘ └───────┬───────┘ └───────┬───────┘ + │ │ │ + ▼ ▼ ▼ +┌───────────────┐ ┌───────────────┐ ┌───────────────┐ +│ /dev/video0 │ │ /dev/hidg* │ │ /sys/class/ │ +│ (V4L2) │ │ (USB Gadget) │ │ gpio/gpio* │ +└───────┬───────┘ └───────┬───────┘ └───────┬───────┘ + │ │ │ + ▼ ▼ ▼ +┌─────────────────────────────────────────────────────────────┐ +│ One-KVM Application │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Video │ │ HID │ │ ATX │ │ +│ │ Pipeline │ │ Controller │ │ Controller │ │ +│ └─────┬───────┘ └─────┬───────┘ └─────┬───────┘ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ ┌───────────────────────────────────────────────────────┐ │ +│ │ Event Bus │ │ +│ │ (tokio broadcast channel) │ │ +│ └───────────────────────────────────────────────────────┘ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ Web Server (Axum) │ │ +│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ +│ │ │ MJPEG │ │ WebRTC │ │WebSocket │ │ │ +│ │ │ Stream │ │ Stream │ │ Events │ │ │ +│ │ └──────────┘ └──────────┘ └──────────┘ │ │ +│ └─────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ │ │ + ▼ ▼ ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Client Browser │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Video │ │ Input │ │ Control │ │ +│ │ Display │ │ Events │ │ Panel │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## 3. 模块依赖关系 + +### 3.1 模块层次图 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Application Layer │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ main.rs ──► state.rs ──► web/routes.rs │ +│ │ │ +│ ┌───────────┼───────────┬───────────┬───────────┬───────────┐ │ +│ │ │ │ │ │ │ │ +│ ▼ ▼ ▼ ▼ ▼ ▼ │ +│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ +│ │video/│ │ hid/ │ │ msd/ │ │ atx/ │ │audio/│ │webrtc│ │ +│ └──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘ │ +│ │ │ │ │ │ │ │ +│ │ └──────────┼──────────┘ │ │ │ +│ │ │ │ │ │ +│ │ ┌─────▼─────┐ │ │ │ +│ │ │ otg/ │ │ │ │ +│ │ │ (OtgSvc) │ │ │ │ +│ │ └───────────┘ │ │ │ +│ │ │ │ │ +│ └──────────────────────────────────────────┼──────────┘ │ +│ │ │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ Infrastructure Layer │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ config/ │ │ auth/ │ │ events/ │ │extensions│ │ +│ │(ConfigSt)│ │(Session) │ │(EventBus)│ │(ExtMgr) │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +│ │ +│ ┌───────────────────────────────────────────────────────────┐ │ +│ │ rustdesk/ (RustDeskService) │ │ +│ │ connection.rs │ rendezvous.rs │ crypto.rs │ protocol.rs │ │ +│ └───────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### 3.2 依赖矩阵 + +| 模块 | 依赖的模块 | +|------|-----------| +| `main.rs` | state, config, auth, video, hid, msd, atx, audio, webrtc, web, rustdesk, events | +| `state.rs` | config, auth, video, hid, msd, atx, audio, webrtc, rustdesk, events, otg | +| `video/` | events, hwcodec (外部) | +| `hid/` | otg, events | +| `msd/` | otg, events | +| `atx/` | events | +| `audio/` | events | +| `webrtc/` | video, audio, hid, events | +| `web/` | state, auth, config, video, hid, msd, atx, audio, webrtc, events | +| `rustdesk/` | video, audio, hid, events | +| `otg/` | (无内部依赖) | +| `config/` | (无内部依赖) | +| `auth/` | config | +| `events/` | (无内部依赖) | + +--- + +## 4. 核心组件详解 + +### 4.1 AppState (state.rs) + +AppState 是整个应用的状态中枢,通过 `Arc` 包装的方式在所有 handler 之间共享。 + +```rust +pub struct AppState { + // 配置和存储 + config: ConfigStore, // SQLite 配置存储 + sessions: SessionStore, // 内存会话存储 + users: UserStore, // SQLite 用户存储 + + // 核心服务 + otg_service: Arc, // USB Gadget 统一管理 + stream_manager: Arc, // 视频流管理器 + hid: Arc, // HID 控制器 + msd: Arc>>, // MSD 控制器(可选) + atx: Arc>>, // ATX 控制器(可选) + audio: Arc, // 音频控制器 + rustdesk: Arc>>>, // RustDesk(可选) + extensions: Arc,// 扩展管理器 + + // 通信和生命周期 + events: Arc, // 事件总线 + shutdown_tx: broadcast::Sender<()>, // 关闭信号 + data_dir: PathBuf, // 数据目录 +} +``` + +### 4.2 视频流管道 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Video Pipeline Architecture │ +└─────────────────────────────────────────────────────────────────────────────┘ + +┌───────────────────┐ +│ V4L2 Device │ +│ /dev/video0 │ +└─────────┬─────────┘ + │ Raw MJPEG/YUYV/NV12 + ▼ +┌───────────────────┐ +│ VideoCapturer │ ◄─── src/video/capture.rs +│ (capture.rs) │ +└─────────┬─────────┘ + │ VideoFrame + ▼ +┌───────────────────────────────────────────────────────────────────────────┐ +│ SharedVideoPipeline │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ Decode Stage │ │ +│ │ ┌─────────────┐ │ │ +│ │ │ MJPEG → YUV │ turbojpeg / VAAPI │ │ +│ │ └─────────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ Convert Stage │ │ +│ │ ┌─────────────┐ │ │ +│ │ │YUV → Target │ libyuv (SIMD accelerated) │ │ +│ │ │ Format │ │ │ +│ │ └─────────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ Encode Stage │ │ +│ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │ +│ │ │ H264 │ │ H265 │ │ VP8 │ │ VP9 │ │ │ +│ │ │Encoder │ │Encoder │ │Encoder │ │Encoder │ │ │ +│ │ └────────┘ └────────┘ └────────┘ └────────┘ │ │ +│ │ │ (VAAPI/RKMPP/V4L2 M2M/Software) │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +└───────────────────────────────────────────────────────────────────────────┘ + │ + ├──────────────────────────────────────────┐ + │ │ + ▼ ▼ +┌───────────────────┐ ┌───────────────────┐ +│ MJPEG Streamer │ │ WebRTC Streamer │ +│ (HTTP Stream) │ │ (RTP Packets) │ +└───────────────────┘ └───────────────────┘ +``` + +### 4.3 OTG 服务架构 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ OTG Service Architecture │ +└─────────────────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────────────────┐ +│ OtgService (service.rs) │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ Public Interface │ │ +│ │ enable_hid() │ disable_hid() │ enable_msd() │ disable_msd() │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ OtgGadgetManager (manager.rs) │ │ +│ │ ┌───────────────────────────────────────────────────────────────┐ │ │ +│ │ │ Gadget Lifecycle │ │ │ +│ │ │ create_gadget() │ destroy_gadget() │ bind_udc() │ unbind() │ │ │ +│ │ └───────────────────────────────────────────────────────────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ┌────────────────────────┼────────────────────────┐ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ HID Function │ │ MSD Function │ │ Endpoint Alloc │ │ +│ │ (hid.rs) │ │ (msd.rs) │ │ (endpoint.rs) │ │ +│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ ConfigFS Operations │ │ +│ │ /sys/kernel/config/usb_gadget/one-kvm/ │ │ +│ │ ├── idVendor, idProduct, strings/ │ │ +│ │ ├── configs/c.1/ │ │ +│ │ │ └── functions/ (symlinks) │ │ +│ │ └── functions/ │ │ +│ │ ├── hid.usb0, hid.usb1, hid.usb2 │ │ +│ │ └── mass_storage.usb0 │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Linux Kernel │ +│ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ /dev/hidg* │ │ Mass Storage │ │ +│ │ (HID devices) │ │ Backend │ │ +│ └─────────────────┘ └─────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### 4.4 事件系统架构 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Event System Architecture │ +└─────────────────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Event Producers │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ Video │ │ HID │ │ MSD │ │ ATX │ │ Audio │ │ +│ │ Module │ │ Module │ │ Module │ │ Module │ │ Module │ │ +│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ +│ │ │ │ │ │ │ +│ └────────────┴────────────┼────────────┴────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ EventBus │ │ +│ │ (tokio broadcast channel) │ │ +│ │ ┌───────────────────────────────────────────────────────────────┐ │ │ +│ │ │ SystemEvent Enum │ │ │ +│ │ │ StreamStateChanged │ HidStateChanged │ MsdStateChanged │ │ │ +│ │ │ AtxStateChanged │ AudioStateChanged │ DeviceInfo │ Error │ │ │ +│ │ └───────────────────────────────────────────────────────────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ┌─────────────────────────┼─────────────────────────┐ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │WebSocket │ │ DeviceInfo│ │ Internal │ │ +│ │ Clients │ │Broadcaster│ │ Tasks │ │ +│ └──────────┘ └──────────┘ └──────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 5. 初始化流程 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Application Startup Flow │ +└─────────────────────────────────────────────────────────────────────────────┘ + +main() + │ + ├──► Parse CLI Arguments (clap) + │ - address, port, data_dir + │ - enable_https, ssl_cert, ssl_key + │ - verbosity (-v, -vv, -vvv) + │ + ├──► Initialize Logging (tracing) + │ + ├──► Create/Open SQLite Database + │ └─► ConfigStore::new() + │ └─► UserStore::new() + │ └─► SessionStore::new() + │ + ├──► Initialize Core Services + │ │ + │ ├──► EventBus::new() + │ │ + │ ├──► OtgService::new() + │ │ └─► Detect UDC device + │ │ + │ ├──► HidController::init() + │ │ └─► Select backend (OTG/CH9329/None) + │ │ └─► Request HID function from OtgService + │ │ + │ ├──► MsdController::init() (if configured) + │ │ └─► Request MSD function from OtgService + │ │ └─► Initialize Ventoy drive (if available) + │ │ + │ ├──► AtxController::init() (if configured) + │ │ └─► Setup GPIO pins + │ │ + │ ├──► AudioController::init() + │ │ └─► Open ALSA device + │ │ └─► Initialize Opus encoder + │ │ + │ ├──► VideoStreamManager::new() + │ │ └─► Initialize SharedVideoPipeline + │ │ └─► Setup encoder registry + │ │ + │ └──► RustDeskService::new() (if configured) + │ └─► Load/generate device ID and keys + │ └─► Connect to rendezvous server + │ + ├──► Create AppState + │ └─► Wrap all services in Arc<> + │ + ├──► Spawn Background Tasks + │ ├──► spawn_device_info_broadcaster() + │ ├──► extension_health_check_task() + │ └──► rustdesk_reconnect_task() + │ + ├──► Create Axum Router + │ └─► create_router(app_state) + │ + └──► Start HTTP/HTTPS Server + └─► axum::serve() or axum_server with TLS +``` + +--- + +## 6. 目录结构 + +``` +One-KVM-RUST/ +├── src/ # Rust 源代码 +│ ├── main.rs # 应用入口点 +│ ├── lib.rs # 库导出 +│ ├── state.rs # AppState 定义 +│ ├── error.rs # 错误类型定义 +│ │ +│ ├── video/ # 视频模块 +│ │ ├── mod.rs +│ │ ├── capture.rs # V4L2 采集 +│ │ ├── streamer.rs # 视频流服务 +│ │ ├── stream_manager.rs # 流管理器 +│ │ ├── shared_video_pipeline.rs # 共享视频管道 +│ │ ├── format.rs # 像素格式 +│ │ ├── frame.rs # 视频帧 +│ │ ├── convert.rs # 格式转换 +│ │ └── encoder/ # 编码器 +│ │ ├── mod.rs +│ │ ├── traits.rs +│ │ ├── h264.rs +│ │ ├── h265.rs +│ │ ├── vp8.rs +│ │ ├── vp9.rs +│ │ └── jpeg.rs +│ │ +│ ├── hid/ # HID 模块 +│ │ ├── mod.rs # HidController +│ │ ├── backend.rs # 后端抽象 +│ │ ├── otg.rs # OTG 后端 +│ │ ├── ch9329.rs # CH9329 串口后端 +│ │ ├── keymap.rs # 按键映射 +│ │ ├── types.rs # 类型定义 +│ │ ├── monitor.rs # 健康监视 +│ │ ├── datachannel.rs # DataChannel 适配 +│ │ └── websocket.rs # WebSocket 适配 +│ │ +│ ├── otg/ # USB OTG 模块 +│ │ ├── mod.rs +│ │ ├── service.rs # OtgService +│ │ ├── manager.rs # GadgetManager +│ │ ├── hid.rs # HID Function +│ │ ├── msd.rs # MSD Function +│ │ ├── configfs.rs # ConfigFS 操作 +│ │ ├── endpoint.rs # 端点分配 +│ │ └── report_desc.rs # HID 报告描述符 +│ │ +│ ├── msd/ # MSD 模块 +│ │ ├── mod.rs +│ │ ├── controller.rs # MsdController +│ │ ├── image.rs # 镜像管理 +│ │ ├── ventoy_drive.rs # Ventoy 驱动 +│ │ ├── monitor.rs # 健康监视 +│ │ └── types.rs # 类型定义 +│ │ +│ ├── atx/ # ATX 模块 +│ │ ├── mod.rs +│ │ ├── controller.rs # AtxController +│ │ ├── executor.rs # 动作执行器 +│ │ ├── types.rs # 类型定义 +│ │ ├── led.rs # LED 监视 +│ │ └── wol.rs # Wake-on-LAN +│ │ +│ ├── audio/ # 音频模块 +│ │ ├── mod.rs +│ │ ├── controller.rs # AudioController +│ │ ├── capture.rs # ALSA 采集 +│ │ ├── encoder.rs # Opus 编码 +│ │ ├── shared_pipeline.rs # 共享管道 +│ │ ├── monitor.rs # 健康监视 +│ │ └── device.rs # 设备枚举 +│ │ +│ ├── webrtc/ # WebRTC 模块 +│ │ ├── mod.rs +│ │ ├── webrtc_streamer.rs # WebRTC 管理器 +│ │ ├── universal_session.rs # 会话管理 +│ │ ├── video_track.rs # 视频轨道 +│ │ ├── rtp.rs # RTP 打包 +│ │ ├── h265_payloader.rs # H265 RTP +│ │ ├── peer.rs # PeerConnection +│ │ ├── config.rs # 配置 +│ │ ├── signaling.rs # 信令 +│ │ └── track.rs # 轨道基类 +│ │ +│ ├── auth/ # 认证模块 +│ │ ├── mod.rs +│ │ ├── user.rs # 用户管理 +│ │ ├── session.rs # 会话管理 +│ │ ├── password.rs # 密码哈希 +│ │ └── middleware.rs # Axum 中间件 +│ │ +│ ├── config/ # 配置模块 +│ │ ├── mod.rs +│ │ ├── schema.rs # 配置结构定义 +│ │ └── store.rs # SQLite 存储 +│ │ +│ ├── events/ # 事件模块 +│ │ └── mod.rs # EventBus +│ │ +│ ├── rustdesk/ # RustDesk 模块 +│ │ ├── mod.rs # RustDeskService +│ │ ├── connection.rs # 连接管理 +│ │ ├── rendezvous.rs # 渲染服务器通信 +│ │ ├── crypto.rs # NaCl 加密 +│ │ ├── config.rs # 配置 +│ │ ├── hid_adapter.rs # HID 适配 +│ │ ├── frame_adapters.rs # 帧格式转换 +│ │ ├── protocol.rs # 协议包装 +│ │ └── bytes_codec.rs # 帧编码 +│ │ +│ ├── extensions/ # 扩展模块 +│ │ └── mod.rs # ExtensionManager +│ │ +│ ├── web/ # Web 模块 +│ │ ├── mod.rs +│ │ ├── routes.rs # 路由定义 +│ │ ├── ws.rs # WebSocket +│ │ ├── audio_ws.rs # 音频 WebSocket +│ │ ├── static_files.rs # 静态文件 +│ │ └── handlers/ # API 处理器 +│ │ ├── mod.rs +│ │ └── config/ +│ │ +│ ├── stream/ # MJPEG 流 +│ │ └── mod.rs +│ │ +│ └── utils/ # 工具函数 +│ └── mod.rs +│ +├── web/ # Vue3 前端 +│ ├── src/ +│ │ ├── views/ # 页面组件 +│ │ ├── components/ # UI 组件 +│ │ ├── api/ # API 客户端 +│ │ ├── stores/ # Pinia 状态 +│ │ ├── router/ # 路由配置 +│ │ ├── i18n/ # 国际化 +│ │ └── types/ # TypeScript 类型 +│ └── package.json +│ +├── libs/ # 外部库 +│ ├── hwcodec/ # 硬件视频编码 +│ └── ventoy-img-rs/ # Ventoy 支持 +│ +├── protos/ # Protobuf 定义 +│ ├── message.proto # RustDesk 消息 +│ └── rendezvous.proto # RustDesk 渲染 +│ +├── docs/ # 文档 +├── scripts/ # 脚本 +├── Cargo.toml # Rust 配置 +├── build.rs # 构建脚本 +└── README.md +``` + +--- + +## 7. 安全架构 + +### 7.1 认证流程 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Authentication Flow │ +└─────────────────────────────────────────────────────────────────────────────┘ + +┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ +│ Client │ │ Axum │ │ Auth │ │ SQLite │ +│ Browser │ │ Server │ │ Module │ │ Database │ +└─────┬─────┘ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ + │ │ │ │ + │ POST /auth/login │ │ │ + │ {username, pass} │ │ │ + │───────────────────►│ │ │ + │ │ verify_user() │ │ + │ │───────────────────►│ │ + │ │ │ SELECT user │ + │ │ │───────────────────►│ + │ │ │◄───────────────────│ + │ │ │ │ + │ │ │ Argon2 verify │ + │ │ │ ────────────► │ + │ │ │ │ + │ │ session_token │ │ + │ │◄───────────────────│ │ + │ │ │ │ + │ Set-Cookie: │ │ │ + │ session_id=token │ │ │ + │◄───────────────────│ │ │ + │ │ │ │ + │ GET /api/... │ │ │ + │ Cookie: session │ │ │ + │───────────────────►│ │ │ + │ │ validate_session()│ │ + │ │───────────────────►│ │ + │ │ user_info │ │ + │ │◄───────────────────│ │ + │ │ │ │ + │ Response │ │ │ + │◄───────────────────│ │ │ +``` + +### 7.2 权限层级 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Permission Levels │ +└─────────────────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Public (No Auth) │ +│ ├── GET /health │ +│ ├── POST /auth/login │ +│ ├── GET /setup │ +│ └── POST /setup/init │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ User (Authenticated) │ +│ ├── GET /info (系统信息) │ +│ ├── GET /devices (设备列表) │ +│ ├── GET/POST /stream/* (流控制) │ +│ ├── POST /webrtc/* (WebRTC 信令) │ +│ ├── POST /hid/* (HID 控制) │ +│ ├── POST /audio/* (音频控制) │ +│ └── WebSocket endpoints (实时通信) │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ Admin (Admin Role) │ +│ ├── GET/PATCH /config/* (配置管理) │ +│ ├── POST /msd/* (MSD 操作) │ +│ ├── POST /atx/* (电源控制) │ +│ ├── POST /extensions/* (扩展管理) │ +│ ├── POST /rustdesk/* (RustDesk 配置) │ +│ └── POST /users/* (用户管理) │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 8. 部署架构 + +### 8.1 单机部署 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Single Binary Deployment │ +└─────────────────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────────────────┐ +│ ARM64 Device (e.g., Rockchip RK3328) │ +│ ┌───────────────────────────────────────────────────────────────────────┐ │ +│ │ one-kvm (single binary, ~15MB) │ │ +│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ +│ │ │ Embedded Assets (rust-embed, gzip compressed) │ │ │ +│ │ │ - index.html, app.js, app.css, assets/* │ │ │ +│ │ └─────────────────────────────────────────────────────────────────┘ │ │ +│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ +│ │ │ Runtime Data (data_dir) │ │ │ +│ │ │ - one-kvm.db (SQLite) │ │ │ +│ │ │ - images/ (MSD images) │ │ │ +│ │ │ - certs/ (SSL certificates) │ │ │ +│ │ └─────────────────────────────────────────────────────────────────┘ │ │ +│ └───────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ Hardware Connections: │ +│ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ │ +│ │ HDMI Input │ │ USB OTG Port │ │ GPIO Header │ │ +│ │ (/dev/video0) │ │ (USB Gadget) │ │ (ATX Control) │ │ +│ └───────────────────┘ └───────────────────┘ └───────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────────┘ + │ + │ USB Cable + ▼ +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Target PC │ +│ - Receives USB HID events (keyboard/mouse) │ +│ - Provides HDMI video output │ +│ - Can boot from virtual USB drive │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### 8.2 网络拓扑 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Network Topology │ +└─────────────────────────────────────────────────────────────────────────────┘ + + Internet + │ + │ + ┌───────────┴───────────┐ + │ │ + ▼ ▼ + ┌───────────────┐ ┌───────────────┐ + │ RustDesk │ │ Client │ + │ Server │ │ Browser │ + │ (hbbs/hbbr) │ │ │ + └───────────────┘ └───────────────┘ + │ │ + │ │ + └───────────┬───────────┘ + │ + ┌────┴────┐ + │ Router │ + │ NAT │ + └────┬────┘ + │ + Local Network + │ + ┌───────────┴───────────┐ + │ │ + ▼ ▼ + ┌───────────────┐ ┌───────────────┐ + │ One-KVM │───────│ Target PC │ + │ Device │ USB │ │ + │ :8080/:8443 │ HID │ │ + └───────────────┘ └───────────────┘ + +Access Methods: +1. Local: http://one-kvm.local:8080 +2. HTTPS: https://one-kvm.local:8443 +3. RustDesk: Via RustDesk client with device ID +``` + +--- + +## 9. 扩展点 + +### 9.1 添加新编码器 + +```rust +// 1. 实现 Encoder trait +impl Encoder for MyEncoder { + fn encode(&mut self, frame: &VideoFrame) -> Result>; + fn codec(&self) -> Codec; + fn bitrate(&self) -> u32; + // ... +} + +// 2. 在 registry 中注册 +encoder_registry.register("my-encoder", || Box::new(MyEncoder::new())); +``` + +### 9.2 添加新 HID 后端 + +```rust +// 1. 实现 HidBackend trait +impl HidBackend for MyBackend { + async fn send_keyboard(&self, event: &KeyboardEvent) -> Result<()>; + async fn send_mouse(&self, event: &MouseEvent) -> Result<()>; + fn info(&self) -> HidBackendInfo; + // ... +} + +// 2. 在 HidController::init() 中添加分支 +match config.backend { + HidBackendType::MyBackend => MyBackend::new(config), + // ... +} +``` + +### 9.3 添加新扩展 + +```rust +// 通过 ExtensionManager 管理外部进程 +extension_manager.register("my-extension", ExtensionConfig { + command: "my-binary", + args: vec!["--port", "9000"], + health_check: HealthCheckConfig::Http { url: "http://localhost:9000/health" }, +}); +``` + +--- + +## 10. 参考资料 + +- [Axum Web Framework](https://github.com/tokio-rs/axum) +- [webrtc-rs](https://github.com/webrtc-rs/webrtc) +- [V4L2 Documentation](https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/v4l2.html) +- [Linux USB Gadget](https://www.kernel.org/doc/html/latest/usb/gadget_configfs.html) +- [RustDesk Protocol](https://github.com/rustdesk/rustdesk) diff --git a/docs/tech-stack.md b/docs/tech-stack.md new file mode 100644 index 00000000..f03291fb --- /dev/null +++ b/docs/tech-stack.md @@ -0,0 +1,1007 @@ +# One-KVM 技术栈文档 + +## 1. 概述 + +One-KVM 采用现代化的 Rust + Vue3 技术栈,追求高性能、低资源占用和类型安全。本文档详细介绍项目使用的技术、库和开发规范。 + +--- + +## 2. 后端技术栈 + +### 2.1 核心语言和运行时 + +| 技术 | 版本 | 用途 | +|------|------|------| +| **Rust** | Edition 2021 | 主要开发语言 | +| **Tokio** | 1.x | 异步运行时 | + +#### Rust 特性使用 + +```rust +// Edition 2021 特性 +- async/await 异步编程 +- 模式匹配 (match, if let) +- 错误处理 (Result, ?) +- 智能指针 (Arc, Mutex, RwLock) +- trait 系统 +- 生命周期 +- 泛型 +``` + +### 2.2 Web 框架 + +| 库 | 版本 | 用途 | +|----|------|------| +| **axum** | 0.7 | Web 框架 | +| **axum-extra** | 0.9 | Cookie、TypedHeader 支持 | +| **tower-http** | 0.5 | CORS、压缩、追踪中间件 | +| **axum-server** | 0.7 | TLS/HTTPS 服务器 | + +#### Axum 使用模式 + +```rust +// 路由定义 +Router::new() + .route("/api/stream/start", post(handlers::stream_start)) + .route("/api/stream/stop", post(handlers::stream_stop)) + .with_state(app_state) + +// 处理器函数 +async fn stream_start( + State(state): State>, + Json(payload): Json, +) -> Result, AppError> + +// 中间件 +.layer(CorsLayer::permissive()) +.layer(CompressionLayer::new()) +.layer(TraceLayer::new_for_http()) +``` + +### 2.3 数据库 + +| 库 | 版本 | 用途 | +|----|------|------| +| **SQLx** | 0.8 | 异步 SQL 工具包 | +| **SQLite** | (bundled) | 嵌入式数据库 | + +#### 数据库设计 + +```sql +-- 配置表 (JSON blob 存储) +CREATE TABLE IF NOT EXISTS config ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + updated_at TEXT NOT NULL +); + +-- 用户表 +CREATE TABLE IF NOT EXISTS users ( + id TEXT PRIMARY KEY, + username TEXT UNIQUE NOT NULL, + password_hash TEXT NOT NULL, + role TEXT NOT NULL, + created_at TEXT NOT NULL +); +``` + +### 2.4 序列化 + +| 库 | 版本 | 用途 | +|----|------|------| +| **serde** | 1.x | 序列化框架 | +| **serde_json** | 1.x | JSON 序列化 | +| **prost** | 0.13 | Protobuf 序列化 (RustDesk) | + +### 2.5 日志和追踪 + +| 库 | 版本 | 用途 | +|----|------|------| +| **tracing** | 0.1 | 结构化日志 | +| **tracing-subscriber** | 0.3 | 日志订阅器 | + +#### 日志级别 + +```bash +-v # WARN + INFO +-vv # + DEBUG +-vvv # + TRACE +``` + +### 2.6 错误处理 + +| 库 | 版本 | 用途 | +|----|------|------| +| **thiserror** | 1.x | 错误类型派生 | +| **anyhow** | 1.x | 通用错误处理 | + +#### 错误处理模式 + +```rust +#[derive(Debug, thiserror::Error)] +pub enum AppError { + #[error("Authentication failed")] + AuthError, + + #[error("Resource not found: {0}")] + NotFound(String), + + #[error("Internal error: {0}")] + Internal(#[from] anyhow::Error), +} + +impl IntoResponse for AppError { + fn into_response(self) -> Response { + let (status, message) = match self { + AppError::AuthError => (StatusCode::UNAUTHORIZED, self.to_string()), + AppError::NotFound(_) => (StatusCode::NOT_FOUND, self.to_string()), + AppError::Internal(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Internal error".into()), + }; + (status, Json(json!({ "error": message }))).into_response() + } +} +``` + +### 2.7 认证和安全 + +| 库 | 版本 | 用途 | +|----|------|------| +| **argon2** | 0.5 | 密码哈希 | +| **rand** | 0.8 | 随机数生成 | +| **rustls** | 0.23 | TLS 实现 | +| **rcgen** | 0.13 | 证书生成 | +| **sodiumoxide** | 0.2 | NaCl 加密 (RustDesk) | +| **sha2** | 0.10 | SHA-256 哈希 | + +#### 密码哈希 + +```rust +use argon2::{Argon2, PasswordHasher, PasswordVerifier}; + +// 哈希密码 +let salt = SaltString::generate(&mut OsRng); +let hash = Argon2::default() + .hash_password(password.as_bytes(), &salt)? + .to_string(); + +// 验证密码 +Argon2::default() + .verify_password(password.as_bytes(), &parsed_hash)?; +``` + +### 2.8 视频处理 + +| 库 | 版本 | 用途 | +|----|------|------| +| **v4l** | 0.14 | V4L2 视频采集 | +| **turbojpeg** | 1.1 | JPEG 编码 (SIMD) | +| **hwcodec** | (vendored) | 硬件视频编码 | +| **libyuv** | (vendored) | YUV 格式转换 | + +#### 视频编码优先级 + +``` +1. VAAPI (Intel/AMD GPU) +2. RKMPP (Rockchip) +3. V4L2 M2M (通用硬件) +4. Software (libx264/libvpx) +``` + +#### 支持的像素格式 + +```rust +pub enum PixelFormat { + // 压缩格式 (优先) + Mjpeg, // Motion JPEG + Jpeg, // Static JPEG + + // YUV 4:2:2 打包格式 + Yuyv, // YUYV (最常见) + Yvyu, // YVYU + Uyvy, // UYVY + + // YUV 半平面格式 + Nv12, // NV12 (常见) + Nv16, // NV16 + Nv24, // NV24 + + // YUV 平面格式 + Yuv420, // I420/YU12 + Yvu420, // YV12 + + // RGB 格式 + Rgb565, // RGB565 + Rgb24, // RGB24 + Bgr24, // BGR24 + + // 灰度 + Grey, // 8-bit grayscale +} +``` + +### 2.9 音频处理 + +| 库 | 版本 | 用途 | +|----|------|------| +| **alsa** | 0.9 | ALSA 音频采集 | +| **audiopus** | 0.2 | Opus 编码 | + +#### 音频配置 + +```rust +// 采样参数 +const SAMPLE_RATE: u32 = 48000; +const CHANNELS: u16 = 2; +const FRAME_SIZE: usize = 960; // 20ms at 48kHz + +// 质量配置 +pub enum AudioQuality { + VeryLow, // 24 kbps + Low, // 48 kbps + Medium, // 64 kbps + High, // 96 kbps +} +``` + +### 2.10 WebRTC + +| 库 | 版本 | 用途 | +|----|------|------| +| **webrtc** | 0.14 | WebRTC 实现 | +| **rtp** | 0.14 | RTP 协议 | + +#### WebRTC 配置 + +```rust +// ICE 服务器配置 +pub struct IceServerConfig { + pub stun_servers: Vec, // STUN 服务器 + pub turn_servers: Vec, // TURN 服务器 +} + +// 默认 STUN +"stun:stun.l.google.com:19302" +``` + +### 2.11 硬件交互 + +| 库 | 版本 | 用途 | +|----|------|------| +| **nix** | 0.29 | Unix 系统调用 | +| **gpio-cdev** | 0.6 | GPIO 控制 | +| **serialport** | 4.x | 串口通信 | +| **libc** | 0.2 | C 库绑定 | + +#### GPIO 操作 + +```rust +// 使用 gpio-cdev +let chip = Chip::new("/dev/gpiochip0")?; +let line = chip.get_line(pin)?; +let handle = line.request(LineRequestFlags::OUTPUT, 0, "one-kvm")?; +handle.set_value(1)?; // 设置高电平 +``` + +#### 串口通信 (CH9329) + +```rust +// 打开串口 +let port = serialport::new(device, baud_rate) + .timeout(Duration::from_millis(100)) + .open()?; + +// 发送 HID 报告 +port.write(&hid_report)?; +``` + +### 2.12 并发同步 + +| 库 | 版本 | 用途 | +|----|------|------| +| **parking_lot** | 0.12 | 高性能锁 | +| **arc-swap** | 1.7 | 原子引用交换 | +| **tokio** | 1.x | 异步通道 | + +#### 同步模式 + +```rust +// 共享状态 +Arc> // 读多写少 +Arc> // 互斥访问 +Arc // 原子操作 + +// 通道 +tokio::sync::broadcast // 多生产者多消费者 +tokio::sync::mpsc // 多生产者单消费者 +tokio::sync::oneshot // 一次性通知 +``` + +### 2.13 网络和 HTTP + +| 库 | 版本 | 用途 | +|----|------|------| +| **reqwest** | 0.12 | HTTP 客户端 | +| **tokio-tungstenite** | 0.24 | WebSocket 客户端 | +| **urlencoding** | 2.x | URL 编码 | + +### 2.14 工具库 + +| 库 | 版本 | 用途 | +|----|------|------| +| **uuid** | 1.x | UUID 生成 | +| **chrono** | 0.4 | 时间处理 | +| **base64** | 0.22 | Base64 编码 | +| **bytes** | 1.x | 字节缓冲区 | +| **bytemuck** | 1.14 | 零拷贝类型转换 | +| **xxhash-rust** | 0.8 | 快速哈希 | +| **futures** | 0.3 | Future 工具 | +| **async-trait** | 0.1 | 异步 trait | + +### 2.15 静态资源嵌入 + +| 库 | 版本 | 用途 | +|----|------|------| +| **rust-embed** | 8.x | 资源嵌入 | +| **mime_guess** | 2.x | MIME 类型推断 | + +#### 资源嵌入模式 + +```rust +#[derive(RustEmbed)] +#[folder = "web/dist"] +#[include = "*.html"] +#[include = "*.js"] +#[include = "*.css"] +#[include = "assets/*"] +struct Assets; + +// Debug: 从文件系统读取 +// Release: 嵌入二进制 (gzip 压缩) +``` + +### 2.16 CLI + +| 库 | 版本 | 用途 | +|----|------|------| +| **clap** | 4.x | 命令行解析 | + +#### CLI 参数 + +```rust +#[derive(Parser)] +struct Args { + #[arg(short, long, default_value = "0.0.0.0")] + address: String, + + #[arg(short, long, default_value = "8080")] + port: u16, + + #[arg(short, long)] + data_dir: Option, + + #[arg(long)] + enable_https: bool, + + #[arg(short, long, action = clap::ArgAction::Count)] + verbose: u8, +} +``` + +### 2.17 类型生成 + +| 库 | 版本 | 用途 | +|----|------|------| +| **typeshare** | 1.0 | TypeScript 类型生成 | + +#### 类型共享 + +```rust +#[derive(Serialize, Deserialize)] +#[typeshare] +pub struct VideoConfig { + pub device: Option, + pub width: u32, + pub height: u32, + pub fps: u32, +} + +// 生成 TypeScript: +// export interface VideoConfig { +// device?: string; +// width: number; +// height: number; +// fps: number; +// } +``` + +--- + +## 3. 前端技术栈 + +### 3.1 核心框架 + +| 技术 | 版本 | 用途 | +|------|------|------| +| **Vue 3** | 3.5.x | UI 框架 | +| **Vue Router** | 4.6.x | 路由 | +| **Pinia** | 3.0.x | 状态管理 | +| **TypeScript** | 5.9.x | 类型系统 | + +#### Vue 3 组合式 API + +```vue + +``` + +### 3.2 UI 组件库 + +| 库 | 版本 | 用途 | +|----|------|------| +| **Radix Vue** | 1.9.x | 无头 UI 组件 | +| **Reka UI** | 2.6.x | UI 组件 | +| **shadcn-vue** | - | 组件样式 | +| **Lucide Vue** | 0.556.x | 图标库 | + +### 3.3 样式 + +| 技术 | 版本 | 用途 | +|------|------|------| +| **Tailwind CSS** | 4.1.x | 原子化 CSS | +| **tailwind-merge** | 3.4.x | 类名合并 | +| **class-variance-authority** | 0.7.x | 变体管理 | +| **tw-animate-css** | 1.4.x | 动画 | + +#### Tailwind 配置 + +```javascript +// tailwind.config.js +export default { + darkMode: 'class', + content: ['./src/**/*.{vue,ts}'], + theme: { + extend: { + colors: { + border: 'hsl(var(--border))', + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + }, + }, + }, +} +``` + +### 3.4 构建工具 + +| 工具 | 版本 | 用途 | +|------|------|------| +| **Vite** | 7.2.x | 构建工具 | +| **vue-tsc** | 3.1.x | 类型检查 | +| **PostCSS** | 8.5.x | CSS 处理 | +| **Autoprefixer** | 10.4.x | CSS 前缀 | + +#### Vite 配置 + +```typescript +// vite.config.ts +export default defineConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + }, + build: { + outDir: 'dist', + sourcemap: false, + } +}) +``` + +### 3.5 功能库 + +| 库 | 版本 | 用途 | +|----|------|------| +| **@vueuse/core** | 14.1.x | Vue 组合式工具 | +| **simple-keyboard** | 3.8.x | 虚拟键盘 | +| **opus-decoder** | 0.7.x | Opus 音频解码 | +| **uplot** | 1.6.x | 实时图表 | +| **vue-sonner** | 2.0.x | Toast 通知 | + +### 3.6 国际化 + +| 库 | 版本 | 用途 | +|----|------|------| +| **vue-i18n** | 9.14.x | 国际化 | + +#### 多语言支持 + +```typescript +// i18n/en-US.ts +export default { + common: { + start: 'Start', + stop: 'Stop', + settings: 'Settings', + }, + video: { + noSignal: 'No Signal', + streaming: 'Streaming', + }, +} + +// i18n/zh-CN.ts +export default { + common: { + start: '启动', + stop: '停止', + settings: '设置', + }, + video: { + noSignal: '无信号', + streaming: '正在推流', + }, +} +``` + +--- + +## 4. 外部依赖库 + +### 4.1 hwcodec (来自 RustDesk) + +硬件视频编码库,支持多种硬件加速: + +| 后端 | 平台 | 编码格式 | +|------|------|---------| +| **VAAPI** | Intel/AMD Linux | H264, H265, VP8, VP9 | +| **RKMPP** | Rockchip | H264, H265 | +| **V4L2 M2M** | 通用 Linux | H264 | +| **Software** | 通用 | H264 (x264) | + +### 4.2 libyuv (来自 Google) + +高性能 YUV/RGB 转换库: + +```rust +// 支持的转换 +MJPEG → YUV420 +YUYV → YUV420 +NV12 → YUV420 +RGB24 → YUV420 + +// SIMD 加速 +- SSE2/SSE4.1/AVX2 (x86) +- NEON (ARM) +``` + +### 4.3 ventoy-img-rs + +Ventoy 启动盘支持库: + +- 创建可启动 USB 镜像 +- 支持多 ISO 文件 +- exFAT 文件系统 + +--- + +## 5. 协议和规范 + +### 5.1 RustDesk 协议 + +使用 Protobuf 定义的消息格式: + +```protobuf +// protos/message.proto +message VideoFrame { + bytes data = 1; + int32 width = 2; + int32 height = 3; + VideoCodec codec = 4; +} + +message MouseEvent { + int32 x = 1; + int32 y = 2; + MouseButton button = 3; +} +``` + +### 5.2 WebRTC 规范 + +遵循标准 WebRTC 协议: + +- **ICE** (RFC 8445) - 连接建立 +- **DTLS** (RFC 6347) - 安全传输 +- **SRTP** (RFC 3711) - 媒体加密 +- **RTP** (RFC 3550) - 媒体传输 + +### 5.3 HID 报告描述符 + +USB HID 报告格式: + +```rust +// 键盘报告 (8 字节) +struct KeyboardReport { + modifiers: u8, // Ctrl, Shift, Alt, GUI + reserved: u8, + keys: [u8; 6], // 最多 6 个按键 +} + +// 鼠标报告 (相对模式, 4 字节) +struct MouseRelativeReport { + buttons: u8, + x: i8, + y: i8, + wheel: i8, +} + +// 鼠标报告 (绝对模式, 6 字节) +struct MouseAbsoluteReport { + buttons: u8, + x: u16, // 0-32767 + y: u16, // 0-32767 + wheel: i8, +} +``` + +### 5.4 V4L2 接口 + +Video4Linux2 采集接口: + +```rust +// 设备能力查询 +VIDIOC_QUERYCAP + +// 格式设置 +VIDIOC_S_FMT +VIDIOC_G_FMT + +// 缓冲区管理 +VIDIOC_REQBUFS +VIDIOC_QUERYBUF +VIDIOC_QBUF +VIDIOC_DQBUF + +// 流控制 +VIDIOC_STREAMON +VIDIOC_STREAMOFF +``` + +--- + +## 6. 开发规范 + +### 6.1 Rust 代码规范 + +#### 命名约定 + +```rust +// 结构体: PascalCase +pub struct VideoConfig { } + +// 函数/方法: snake_case +fn start_streaming() { } + +// 常量: SCREAMING_SNAKE_CASE +const MAX_FRAME_SIZE: usize = 1920 * 1080 * 4; + +// 模块: snake_case +mod video_capture; + +// trait: PascalCase +trait Encoder { } +``` + +#### 错误处理 + +```rust +// 使用 Result 返回可能失败的操作 +fn open_device() -> Result; + +// 使用 ? 传播错误 +let device = open_device()?; + +// 自定义错误类型使用 thiserror +#[derive(Debug, thiserror::Error)] +pub enum DeviceError { + #[error("Device not found: {0}")] + NotFound(String), +} +``` + +#### 异步代码 + +```rust +// 使用 async/await +async fn fetch_frame(&self) -> Result { + let frame = self.capture.read_frame().await?; + Ok(frame) +} + +// 使用 tokio::spawn 启动后台任务 +tokio::spawn(async move { + loop { + // 后台工作 + } +}); +``` + +### 6.2 TypeScript 代码规范 + +#### 类型定义 + +```typescript +// 使用 interface 定义数据结构 +interface VideoConfig { + device?: string + width: number + height: number + fps: number +} + +// 使用 type 定义联合类型 +type StreamState = 'idle' | 'starting' | 'streaming' | 'stopping' +``` + +#### 组合式 API + +```typescript +// 使用 +``` + +### 6.3 Git 提交规范 + +``` +(): + + + +