feat: 添加 RustDesk 协议支持和项目文档

- 新增 RustDesk 模块,支持与 RustDesk 客户端连接
  - 实现会合服务器协议和 P2P 连接
  - 支持 NaCl 加密和密钥交换
  - 添加视频帧和 HID 事件适配器
- 添加 Protobuf 协议定义 (message.proto, rendezvous.proto)
- 新增完整项目文档
  - 各功能模块文档 (video, hid, msd, otg, webrtc 等)
  - hwcodec 和 RustDesk 协议技术报告
  - 系统架构和技术栈文档
- 更新 Web 前端 RustDesk 配置界面和 API
This commit is contained in:
mofeng-git
2025-12-31 18:59:52 +08:00
parent 61323a7664
commit a8a3b6c66b
57 changed files with 20830 additions and 0 deletions

210
src/rustdesk/config.rs Normal file
View File

@@ -0,0 +1,210 @@
//! RustDesk Configuration
//!
//! Configuration types for the RustDesk protocol integration.
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
/// RustDesk configuration
#[typeshare]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct RustDeskConfig {
/// Enable RustDesk protocol
pub enabled: bool,
/// Rendezvous server address (hbbs), e.g., "rs.example.com" or "192.168.1.100"
/// Port defaults to 21116 if not specified
pub rendezvous_server: String,
/// Relay server address (hbbr), if different from rendezvous server
/// Usually the same host as rendezvous server but different port (21117)
pub relay_server: Option<String>,
/// Device ID (9-digit number), auto-generated if empty
pub device_id: String,
/// Device password for client authentication
#[typeshare(skip)]
pub device_password: String,
/// Public key for encryption (Curve25519, base64 encoded), auto-generated
#[typeshare(skip)]
pub public_key: Option<String>,
/// Private key for encryption (Curve25519, base64 encoded), auto-generated
#[typeshare(skip)]
pub private_key: Option<String>,
/// Signing public key (Ed25519, base64 encoded), auto-generated
/// Used for SignedId verification by clients
#[typeshare(skip)]
pub signing_public_key: Option<String>,
/// Signing private key (Ed25519, base64 encoded), auto-generated
/// Used for signing SignedId messages
#[typeshare(skip)]
pub signing_private_key: Option<String>,
/// UUID for rendezvous server registration (persisted to avoid UUID_MISMATCH)
#[typeshare(skip)]
pub uuid: Option<String>,
}
impl Default for RustDeskConfig {
fn default() -> Self {
Self {
enabled: false,
rendezvous_server: String::new(),
relay_server: None,
device_id: generate_device_id(),
device_password: generate_random_password(),
public_key: None,
private_key: None,
signing_public_key: None,
signing_private_key: None,
uuid: None,
}
}
}
impl RustDeskConfig {
/// Check if the configuration is valid for starting the service
pub fn is_valid(&self) -> bool {
self.enabled
&& !self.rendezvous_server.is_empty()
&& !self.device_id.is_empty()
&& !self.device_password.is_empty()
}
/// Generate a new random device ID
pub fn generate_device_id() -> String {
generate_device_id()
}
/// Generate a new random password
pub fn generate_password() -> String {
generate_random_password()
}
/// Get or generate the UUID for rendezvous registration
/// Returns (uuid_bytes, is_new) where is_new indicates if a new UUID was generated
pub fn ensure_uuid(&mut self) -> ([u8; 16], bool) {
if let Some(ref uuid_str) = self.uuid {
// Try to parse existing UUID
if let Ok(uuid) = uuid::Uuid::parse_str(uuid_str) {
return (*uuid.as_bytes(), false);
}
}
// Generate new UUID
let new_uuid = uuid::Uuid::new_v4();
self.uuid = Some(new_uuid.to_string());
(*new_uuid.as_bytes(), true)
}
/// Get the UUID bytes (returns None if not set)
pub fn get_uuid_bytes(&self) -> Option<[u8; 16]> {
self.uuid.as_ref().and_then(|s| {
uuid::Uuid::parse_str(s).ok().map(|u| *u.as_bytes())
})
}
/// Get the rendezvous server address with default port
pub fn rendezvous_addr(&self) -> String {
if self.rendezvous_server.contains(':') {
self.rendezvous_server.clone()
} else {
format!("{}:21116", self.rendezvous_server)
}
}
/// Get the relay server address with default port
pub fn relay_addr(&self) -> Option<String> {
self.relay_server.as_ref().map(|s| {
if s.contains(':') {
s.clone()
} else {
format!("{}:21117", s)
}
}).or_else(|| {
// Default: same host as rendezvous server
if !self.rendezvous_server.is_empty() {
let host = self.rendezvous_server.split(':').next().unwrap_or("");
if !host.is_empty() {
Some(format!("{}:21117", host))
} else {
None
}
} else {
None
}
})
}
}
/// Generate a random 9-digit device ID
pub fn generate_device_id() -> String {
use rand::Rng;
let mut rng = rand::thread_rng();
let id: u32 = rng.gen_range(100_000_000..999_999_999);
id.to_string()
}
/// Generate a random 8-character password
pub fn generate_random_password() -> String {
use rand::Rng;
const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
let mut rng = rand::thread_rng();
(0..8)
.map(|_| {
let idx = rng.gen_range(0..CHARSET.len());
CHARSET[idx] as char
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_device_id_generation() {
let id = generate_device_id();
assert_eq!(id.len(), 9);
assert!(id.chars().all(|c| c.is_ascii_digit()));
}
#[test]
fn test_password_generation() {
let password = generate_random_password();
assert_eq!(password.len(), 8);
assert!(password.chars().all(|c| c.is_alphanumeric()));
}
#[test]
fn test_rendezvous_addr() {
let mut config = RustDeskConfig::default();
config.rendezvous_server = "example.com".to_string();
assert_eq!(config.rendezvous_addr(), "example.com:21116");
config.rendezvous_server = "example.com:21116".to_string();
assert_eq!(config.rendezvous_addr(), "example.com:21116");
}
#[test]
fn test_relay_addr() {
let mut config = RustDeskConfig::default();
// No server configured
assert!(config.relay_addr().is_none());
// Rendezvous server configured, relay defaults to same host
config.rendezvous_server = "example.com".to_string();
assert_eq!(config.relay_addr(), Some("example.com:21117".to_string()));
// Explicit relay server
config.relay_server = Some("relay.example.com".to_string());
assert_eq!(config.relay_addr(), Some("relay.example.com:21117".to_string()));
}
}