feat(video): 事务化切换与前端统一编排,增强视频输入格式支持

- 后端:切换事务+transition_id,/stream/mode 返回 switching/transition_id 与实际 codec

- 事件:新增 mode_switching/mode_ready,config/webrtc_ready/mode_changed 关联事务

- 编码/格式:扩展 NV21/NV16/NV24/RGB/BGR 输入与转换链路,RKMPP direct input 优化

- 前端:useVideoSession 统一切换,失败回退真实切回 MJPEG,菜单格式同步修复

- 清理:useVideoStream 降级为 MJPEG-only
This commit is contained in:
mofeng-git
2026-01-11 10:41:57 +08:00
parent 9feb74b72c
commit 206594e292
110 changed files with 3955 additions and 2251 deletions

View File

@@ -34,7 +34,10 @@ pub fn encode_frame(data: &[u8]) -> io::Result<Vec<u8>> {
let h = ((len << 2) as u32) | 0x3;
buf.extend_from_slice(&h.to_le_bytes());
} else {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "Message too large"));
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Message too large",
));
}
buf.extend_from_slice(data);
@@ -79,7 +82,10 @@ pub async fn read_frame<R: AsyncRead + Unpin>(reader: &mut R) -> io::Result<Byte
let (_, msg_len) = decode_header(first_byte[0], &header_rest);
if msg_len > MAX_PACKET_LENGTH {
return Err(io::Error::new(io::ErrorKind::InvalidData, "Message too large"));
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Message too large",
));
}
// Read message body
@@ -133,7 +139,10 @@ pub fn encode_frame_into(data: &[u8], buf: &mut BytesMut) -> io::Result<()> {
} else if len <= MAX_PACKET_LENGTH {
buf.put_u32_le(((len << 2) as u32) | 0x3);
} else {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "Message too large"));
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Message too large",
));
}
buf.extend_from_slice(data);
@@ -216,7 +225,10 @@ impl BytesCodec {
n >>= 2;
if n > self.max_packet_length {
return Err(io::Error::new(io::ErrorKind::InvalidData, "Message too large"));
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Message too large",
));
}
src.advance(head_len);
@@ -245,7 +257,10 @@ impl BytesCodec {
} else if len <= MAX_PACKET_LENGTH {
buf.put_u32_le(((len << 2) as u32) | 0x3);
} else {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "Message too large"));
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Message too large",
));
}
buf.extend(data);

View File

@@ -116,9 +116,9 @@ impl RustDeskConfig {
/// 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())
})
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
@@ -135,26 +135,29 @@ impl RustDeskConfig {
/// 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
let server = &self.rendezvous_server;
if !server.is_empty() {
let host = server.split(':').next().unwrap_or("");
if !host.is_empty() {
Some(format!("{}:21117", host))
self.relay_server
.as_ref()
.map(|s| {
if s.contains(':') {
s.clone()
} else {
format!("{}:21117", s)
}
})
.or_else(|| {
// Default: same host as rendezvous server
let server = &self.rendezvous_server;
if !server.is_empty() {
let host = server.split(':').next().unwrap_or("");
if !host.is_empty() {
Some(format!("{}:21117", host))
} else {
None
}
} else {
None
}
} else {
None
}
})
})
}
}
@@ -222,7 +225,10 @@ mod tests {
// Explicit relay server
config.relay_server = Some("relay.example.com".to_string());
assert_eq!(config.relay_addr(), Some("relay.example.com:21117".to_string()));
assert_eq!(
config.relay_addr(),
Some("relay.example.com:21117".to_string())
);
// No rendezvous server, relay is None
config.rendezvous_server = String::new();

View File

@@ -13,16 +13,16 @@ use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use bytes::{Bytes, BytesMut};
use sodiumoxide::crypto::box_;
use parking_lot::RwLock;
use protobuf::Message as ProtobufMessage;
use tokio::net::TcpStream;
use sodiumoxide::crypto::box_;
use tokio::net::tcp::OwnedWriteHalf;
use tokio::net::TcpStream;
use tokio::sync::{broadcast, mpsc, Mutex};
use tracing::{debug, error, info, warn};
use crate::audio::AudioController;
use crate::hid::{HidController, KeyboardEvent, KeyEventType, KeyboardModifiers};
use crate::hid::{HidController, KeyEventType, KeyboardEvent, KeyboardModifiers};
use crate::video::encoder::registry::{EncoderRegistry, VideoEncoderType};
use crate::video::encoder::BitratePreset;
use crate::video::stream_manager::VideoStreamManager;
@@ -33,10 +33,9 @@ use super::crypto::{self, KeyPair, SigningKeyPair};
use super::frame_adapters::{AudioFrameAdapter, VideoCodec, VideoFrameAdapter};
use super::hid_adapter::{convert_key_event, convert_mouse_event, mouse_type};
use super::protocol::{
message, misc, login_response,
KeyEvent, MouseEvent, Clipboard, Misc, LoginRequest, LoginResponse, PeerInfo,
IdPk, SignedId, Hash, TestDelay, ControlKey,
decode_message, HbbMessage, DisplayInfo, SupportedEncoding, OptionMessage, PublicKey,
decode_message, login_response, message, misc, Clipboard, ControlKey, DisplayInfo, Hash,
HbbMessage, IdPk, KeyEvent, LoginRequest, LoginResponse, Misc, MouseEvent, OptionMessage,
PeerInfo, PublicKey, SignedId, SupportedEncoding, TestDelay,
};
use sodiumoxide::crypto::secretbox;
@@ -268,7 +267,11 @@ impl Connection {
}
/// Handle an incoming TCP connection
pub async fn handle_tcp(&mut self, stream: TcpStream, peer_addr: SocketAddr) -> anyhow::Result<()> {
pub async fn handle_tcp(
&mut self,
stream: TcpStream,
peer_addr: SocketAddr,
) -> anyhow::Result<()> {
info!("New connection from {}", peer_addr);
*self.state.write() = ConnectionState::Handshaking;
@@ -279,7 +282,9 @@ impl Connection {
// Send our SignedId first (this is what RustDesk protocol expects)
// The SignedId contains our device ID and temporary public key
let signed_id_msg = self.create_signed_id_message(&self.device_id.clone());
let signed_id_bytes = signed_id_msg.write_to_bytes().map_err(|e| anyhow::anyhow!("Failed to encode SignedId: {}", e))?;
let signed_id_bytes = signed_id_msg
.write_to_bytes()
.map_err(|e| anyhow::anyhow!("Failed to encode SignedId: {}", e))?;
debug!("Sending SignedId with device_id={}", self.device_id);
self.send_framed_arc(&writer, &signed_id_bytes).await?;
@@ -402,7 +407,11 @@ impl Connection {
}
/// Send framed message using Arc<Mutex<OwnedWriteHalf>> with RustDesk's variable-length encoding
async fn send_framed_arc(&self, writer: &Arc<Mutex<OwnedWriteHalf>>, data: &[u8]) -> anyhow::Result<()> {
async fn send_framed_arc(
&self,
writer: &Arc<Mutex<OwnedWriteHalf>>,
data: &[u8],
) -> anyhow::Result<()> {
let mut w = writer.lock().await;
write_frame(&mut *w, data).await?;
Ok(())
@@ -480,7 +489,9 @@ impl Connection {
pk.symmetric_value.len()
);
if pk.asymmetric_value.is_empty() && pk.symmetric_value.is_empty() {
warn!("Received EMPTY PublicKey - client may have failed signature verification!");
warn!(
"Received EMPTY PublicKey - client may have failed signature verification!"
);
}
self.handle_peer_public_key(pk, writer).await?;
}
@@ -535,7 +546,7 @@ impl Connection {
info!("Received SignedId from peer, id_len={}", si.id.len());
self.handle_signed_id(si, writer).await?;
return Ok(());
},
}
message::Union::Hash(_) => "Hash",
message::Union::VideoFrame(_) => "VideoFrame",
message::Union::CursorData(_) => "CursorData",
@@ -564,16 +575,26 @@ impl Connection {
lr: &LoginRequest,
writer: &Arc<Mutex<OwnedWriteHalf>>,
) -> anyhow::Result<bool> {
info!("Login request from {} ({}), password_len={}", lr.my_id, lr.my_name, lr.password.len());
info!(
"Login request from {} ({}), password_len={}",
lr.my_id,
lr.my_name,
lr.password.len()
);
// Check if our server requires a password
if !self.password.is_empty() {
// Server requires password
if lr.password.is_empty() {
// Client sent empty password - tell them to enter password
info!("Empty password from {}, requesting password input", lr.my_id);
info!(
"Empty password from {}, requesting password input",
lr.my_id
);
let error_response = self.create_login_error_response("Empty Password");
let response_bytes = error_response.write_to_bytes().map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
let response_bytes = error_response
.write_to_bytes()
.map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
self.send_encrypted_arc(writer, &response_bytes).await?;
// Don't close connection - wait for retry with password
return Ok(false);
@@ -583,7 +604,9 @@ impl Connection {
if !self.verify_password(&lr.password) {
warn!("Wrong password from {}", lr.my_id);
let error_response = self.create_login_error_response("Wrong Password");
let response_bytes = error_response.write_to_bytes().map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
let response_bytes = error_response
.write_to_bytes()
.map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
self.send_encrypted_arc(writer, &response_bytes).await?;
// Don't close connection - wait for retry with correct password
return Ok(false);
@@ -601,7 +624,9 @@ impl Connection {
info!("Negotiated video codec: {:?}", negotiated);
let response = self.create_login_response(true);
let response_bytes = response.write_to_bytes().map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
let response_bytes = response
.write_to_bytes()
.map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
self.send_encrypted_arc(writer, &response_bytes).await?;
Ok(true)
}
@@ -679,7 +704,10 @@ impl Connection {
};
if let Some(preset) = preset {
info!("Client requested quality preset: {:?} (image_quality={})", preset, image_quality);
info!(
"Client requested quality preset: {:?} (image_quality={})",
preset, image_quality
);
if let Some(ref video_manager) = self.video_manager {
if let Err(e) = video_manager.set_bitrate_preset(preset).await {
warn!("Failed to set bitrate preset: {}", e);
@@ -729,7 +757,10 @@ impl Connection {
// Log custom_image_quality (accept but don't process)
if opt.custom_image_quality > 0 {
debug!("Client sent custom_image_quality: {} (ignored)", opt.custom_image_quality);
debug!(
"Client sent custom_image_quality: {} (ignored)",
opt.custom_image_quality
);
}
if opt.custom_fps > 0 {
debug!("Client requested FPS: {}", opt.custom_fps);
@@ -779,7 +810,10 @@ impl Connection {
let negotiated_codec = self.negotiated_codec.unwrap_or(VideoEncoderType::H264);
let task = tokio::spawn(async move {
info!("Starting video streaming for connection {} with codec {:?}", conn_id, negotiated_codec);
info!(
"Starting video streaming for connection {} with codec {:?}",
conn_id, negotiated_codec
);
if let Err(e) = run_video_streaming(
conn_id,
@@ -788,7 +822,9 @@ impl Connection {
state,
shutdown_tx,
negotiated_codec,
).await {
)
.await
{
error!("Video streaming error for connection {}: {}", conn_id, e);
}
@@ -815,13 +851,9 @@ impl Connection {
let task = tokio::spawn(async move {
info!("Starting audio streaming for connection {}", conn_id);
if let Err(e) = run_audio_streaming(
conn_id,
audio_controller,
audio_tx,
state,
shutdown_tx,
).await {
if let Err(e) =
run_audio_streaming(conn_id, audio_controller, audio_tx, state, shutdown_tx).await
{
error!("Audio streaming error for connection {}: {}", conn_id, e);
}
@@ -894,7 +926,10 @@ impl Connection {
self.encryption_enabled = true;
}
Err(e) => {
warn!("Failed to decrypt session key: {:?}, falling back to unencrypted", e);
warn!(
"Failed to decrypt session key: {:?}, falling back to unencrypted",
e
);
// Continue without encryption - some clients may not support it
self.encryption_enabled = false;
}
@@ -917,8 +952,13 @@ impl Connection {
// This tells the client what salt to use for password hashing
// Must be encrypted if session key was negotiated
let hash_msg = self.create_hash_message();
let hash_bytes = hash_msg.write_to_bytes().map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
debug!("Sending Hash message for password authentication (encrypted={})", self.encryption_enabled);
let hash_bytes = hash_msg
.write_to_bytes()
.map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
debug!(
"Sending Hash message for password authentication (encrypted={})",
self.encryption_enabled
);
self.send_encrypted_arc(writer, &hash_bytes).await?;
Ok(())
@@ -971,7 +1011,9 @@ impl Connection {
// If we haven't sent our SignedId yet, send it now
// (This handles the case where client sends SignedId before we do)
let signed_id_msg = self.create_signed_id_message(&self.device_id.clone());
let signed_id_bytes = signed_id_msg.write_to_bytes().map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
let signed_id_bytes = signed_id_msg
.write_to_bytes()
.map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
self.send_framed_arc(writer, &signed_id_bytes).await?;
Ok(())
@@ -1073,7 +1115,8 @@ impl Connection {
msg
} else {
let mut login_response = LoginResponse::new();
login_response.union = Some(login_response::Union::Error("Invalid password".to_string()));
login_response.union =
Some(login_response::Union::Error("Invalid password".to_string()));
login_response.enable_trusted_devices = false;
let mut msg = HbbMessage::new();
@@ -1133,7 +1176,9 @@ impl Connection {
let mut response = HbbMessage::new();
response.union = Some(message::Union::TestDelay(test_delay));
let data = response.write_to_bytes().map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
let data = response
.write_to_bytes()
.map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
self.send_encrypted_arc(writer, &data).await?;
debug!(
@@ -1161,10 +1206,7 @@ impl Connection {
/// The client will echo this back, allowing us to calculate RTT.
/// The measured delay is then included in future TestDelay messages
/// for the client to display.
async fn send_test_delay(
&mut self,
writer: &Arc<Mutex<OwnedWriteHalf>>,
) -> anyhow::Result<()> {
async fn send_test_delay(&mut self, writer: &Arc<Mutex<OwnedWriteHalf>>) -> anyhow::Result<()> {
// Get current time in milliseconds since epoch
let time_ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
@@ -1180,13 +1222,18 @@ impl Connection {
let mut msg = HbbMessage::new();
msg.union = Some(message::Union::TestDelay(test_delay));
let data = msg.write_to_bytes().map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
let data = msg
.write_to_bytes()
.map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
self.send_encrypted_arc(writer, &data).await?;
// Record when we sent this, so we can calculate RTT when client echoes back
self.last_test_delay_sent = Some(Instant::now());
debug!("TestDelay sent: time={}, last_delay={}ms", time_ms, self.last_delay);
debug!(
"TestDelay sent: time={}, last_delay={}ms",
time_ms, self.last_delay
);
Ok(())
}
@@ -1208,7 +1255,10 @@ impl Connection {
self.last_caps_lock = caps_lock_in_modifiers;
// Send CapsLock key press (down + up) to toggle state on target
if let Some(ref hid) = self.hid {
debug!("CapsLock state changed to {}, sending CapsLock key", caps_lock_in_modifiers);
debug!(
"CapsLock state changed to {}, sending CapsLock key",
caps_lock_in_modifiers
);
let caps_down = KeyboardEvent {
event_type: KeyEventType::Down,
key: 0x39, // USB HID CapsLock
@@ -1234,7 +1284,9 @@ impl Connection {
if let Some(kb_event) = convert_key_event(ke) {
debug!(
"Converted to HID: key=0x{:02X}, event_type={:?}, modifiers={:02X}",
kb_event.key, kb_event.event_type, kb_event.modifiers.to_hid_byte()
kb_event.key,
kb_event.event_type,
kb_event.modifiers.to_hid_byte()
);
// Send to HID controller if available
if let Some(ref hid) = self.hid {
@@ -1393,7 +1445,11 @@ impl ConnectionManager {
}
/// Accept a new connection
pub async fn accept_connection(&self, stream: TcpStream, peer_addr: SocketAddr) -> anyhow::Result<u32> {
pub async fn accept_connection(
&self,
stream: TcpStream,
peer_addr: SocketAddr,
) -> anyhow::Result<u32> {
let id = {
let mut next = self.next_id.write();
let id = *next;
@@ -1406,14 +1462,14 @@ impl ConnectionManager {
let hid = self.hid.read().clone();
let audio = self.audio.read().clone();
let video_manager = self.video_manager.read().clone();
let (mut conn, _rx) = Connection::new(id, &config, signing_keypair, hid, audio, video_manager);
let (mut conn, _rx) =
Connection::new(id, &config, signing_keypair, hid, audio, video_manager);
// Track connection state for external access
let state = conn.state.clone();
self.connections.write().push(Arc::new(RwLock::new(ConnectionInfo {
id,
state,
})));
self.connections
.write()
.push(Arc::new(RwLock::new(ConnectionInfo { id, state })));
// Spawn connection handler - Connection is moved, not locked
tokio::spawn(async move {
@@ -1466,7 +1522,10 @@ async fn run_video_streaming(
};
// Set the video codec on the shared pipeline before subscribing
info!("Setting video codec to {:?} for connection {}", negotiated_codec, conn_id);
info!(
"Setting video codec to {:?} for connection {}",
negotiated_codec, conn_id
);
if let Err(e) = video_manager.set_video_codec(webrtc_codec).await {
error!("Failed to set video codec: {}", e);
// Continue anyway, will use whatever codec the pipeline already has
@@ -1485,7 +1544,10 @@ async fn run_video_streaming(
let mut encoded_count: u64 = 0;
let mut last_log_time = Instant::now();
info!("Started shared video streaming for connection {} (codec: {:?})", conn_id, codec);
info!(
"Started shared video streaming for connection {} (codec: {:?})",
conn_id, codec
);
// Outer loop: handles pipeline restarts by re-subscribing
'subscribe_loop: loop {
@@ -1500,7 +1562,10 @@ async fn run_video_streaming(
Some(rx) => rx,
None => {
// Pipeline not ready yet, wait and retry
debug!("No encoded frame source available for connection {}, retrying...", conn_id);
debug!(
"No encoded frame source available for connection {}, retrying...",
conn_id
);
tokio::time::sleep(Duration::from_millis(100)).await;
continue 'subscribe_loop;
}
@@ -1619,13 +1684,19 @@ async fn run_audio_streaming(
Some(rx) => rx,
None => {
// Audio not available, wait and retry
debug!("No audio source available for connection {}, retrying...", conn_id);
debug!(
"No audio source available for connection {}, retrying...",
conn_id
);
tokio::time::sleep(Duration::from_millis(500)).await;
continue 'subscribe_loop;
}
};
info!("RustDesk connection {} subscribed to audio pipeline", conn_id);
info!(
"RustDesk connection {} subscribed to audio pipeline",
conn_id
);
// Send audio format message once before sending frames
if !audio_adapter.format_sent() {

View File

@@ -86,8 +86,12 @@ impl KeyPair {
/// Create from base64-encoded keys
pub fn from_base64(public_key: &str, secret_key: &str) -> Result<Self, CryptoError> {
let pk_bytes = BASE64.decode(public_key).map_err(|_| CryptoError::InvalidKeyLength)?;
let sk_bytes = BASE64.decode(secret_key).map_err(|_| CryptoError::InvalidKeyLength)?;
let pk_bytes = BASE64
.decode(public_key)
.map_err(|_| CryptoError::InvalidKeyLength)?;
let sk_bytes = BASE64
.decode(secret_key)
.map_err(|_| CryptoError::InvalidKeyLength)?;
Self::from_keys(&pk_bytes, &sk_bytes)
}
}
@@ -140,7 +144,10 @@ pub fn decrypt_with_key(
/// Compute a shared symmetric key from public/private keypair
/// This is the precomputed key for the NaCl box
pub fn precompute_key(their_public_key: &PublicKey, our_secret_key: &SecretKey) -> box_::PrecomputedKey {
pub fn precompute_key(
their_public_key: &PublicKey,
our_secret_key: &SecretKey,
) -> box_::PrecomputedKey {
box_::precompute(their_public_key, our_secret_key)
}
@@ -207,8 +214,8 @@ pub fn decrypt_symmetric_key(
return Err(CryptoError::InvalidKeyLength);
}
let their_pk = PublicKey::from_slice(their_temp_public_key)
.ok_or(CryptoError::InvalidKeyLength)?;
let their_pk =
PublicKey::from_slice(their_temp_public_key).ok_or(CryptoError::InvalidKeyLength)?;
// Use zero nonce as per RustDesk protocol
let nonce = box_::Nonce([0u8; box_::NONCEBYTES]);
@@ -294,8 +301,12 @@ impl SigningKeyPair {
/// Create from base64-encoded keys
pub fn from_base64(public_key: &str, secret_key: &str) -> Result<Self, CryptoError> {
let pk_bytes = BASE64.decode(public_key).map_err(|_| CryptoError::InvalidKeyLength)?;
let sk_bytes = BASE64.decode(secret_key).map_err(|_| CryptoError::InvalidKeyLength)?;
let pk_bytes = BASE64
.decode(public_key)
.map_err(|_| CryptoError::InvalidKeyLength)?;
let sk_bytes = BASE64
.decode(secret_key)
.map_err(|_| CryptoError::InvalidKeyLength)?;
Self::from_keys(&pk_bytes, &sk_bytes)
}
@@ -321,8 +332,7 @@ impl SigningKeyPair {
/// which is required by RustDesk's protocol where clients encrypt the
/// symmetric key using the public key from IdPk.
pub fn to_curve25519_pk(&self) -> Result<PublicKey, CryptoError> {
ed25519::to_curve25519_pk(&self.public_key)
.map_err(|_| CryptoError::KeyConversionFailed)
ed25519::to_curve25519_pk(&self.public_key).map_err(|_| CryptoError::KeyConversionFailed)
}
/// Convert Ed25519 secret key to Curve25519 secret key for decryption
@@ -330,14 +340,16 @@ impl SigningKeyPair {
/// This allows decrypting messages that were encrypted using the
/// converted public key.
pub fn to_curve25519_sk(&self) -> Result<SecretKey, CryptoError> {
ed25519::to_curve25519_sk(&self.secret_key)
.map_err(|_| CryptoError::KeyConversionFailed)
ed25519::to_curve25519_sk(&self.secret_key).map_err(|_| CryptoError::KeyConversionFailed)
}
}
/// Verify a signed message
/// Returns the original message if signature is valid
pub fn verify_signed(signed_message: &[u8], public_key: &sign::PublicKey) -> Result<Vec<u8>, CryptoError> {
pub fn verify_signed(
signed_message: &[u8],
public_key: &sign::PublicKey,
) -> Result<Vec<u8>, CryptoError> {
sign::verify(signed_message, public_key).map_err(|_| CryptoError::SignatureVerificationFailed)
}
@@ -374,7 +386,8 @@ mod tests {
let message = b"Hello, RustDesk!";
let (nonce, ciphertext) = encrypt_box(message, &bob.public_key, &alice.secret_key);
let plaintext = decrypt_box(&ciphertext, &nonce, &alice.public_key, &bob.secret_key).unwrap();
let plaintext =
decrypt_box(&ciphertext, &nonce, &alice.public_key, &bob.secret_key).unwrap();
assert_eq!(plaintext, message);
}

View File

@@ -7,9 +7,8 @@ use bytes::Bytes;
use protobuf::Message as ProtobufMessage;
use super::protocol::hbb::message::{
message as msg_union, misc as misc_union, video_frame as vf_union,
AudioFormat, AudioFrame, CursorData, CursorPosition,
EncodedVideoFrame, EncodedVideoFrames, Message, Misc, VideoFrame,
message as msg_union, misc as misc_union, video_frame as vf_union, AudioFormat, AudioFrame,
CursorData, CursorPosition, EncodedVideoFrame, EncodedVideoFrames, Message, Misc, VideoFrame,
};
/// Video codec type for RustDesk
@@ -63,7 +62,12 @@ impl VideoFrameAdapter {
/// Convert encoded video data to RustDesk Message (zero-copy version)
///
/// This version takes Bytes directly to avoid copying the frame data.
pub fn encode_frame_from_bytes(&mut self, data: Bytes, is_keyframe: bool, timestamp_ms: u64) -> Message {
pub fn encode_frame_from_bytes(
&mut self,
data: Bytes,
is_keyframe: bool,
timestamp_ms: u64,
) -> Message {
// Calculate relative timestamp
if self.seq == 0 {
self.timestamp_base = timestamp_ms;
@@ -104,13 +108,23 @@ impl VideoFrameAdapter {
/// Encode frame to bytes for sending (zero-copy version)
///
/// Takes Bytes directly to avoid copying the frame data.
pub fn encode_frame_bytes_zero_copy(&mut self, data: Bytes, is_keyframe: bool, timestamp_ms: u64) -> Bytes {
pub fn encode_frame_bytes_zero_copy(
&mut self,
data: Bytes,
is_keyframe: bool,
timestamp_ms: u64,
) -> Bytes {
let msg = self.encode_frame_from_bytes(data, is_keyframe, timestamp_ms);
Bytes::from(msg.write_to_bytes().unwrap_or_default())
}
/// Encode frame to bytes for sending
pub fn encode_frame_bytes(&mut self, data: &[u8], is_keyframe: bool, timestamp_ms: u64) -> Bytes {
pub fn encode_frame_bytes(
&mut self,
data: &[u8],
is_keyframe: bool,
timestamp_ms: u64,
) -> Bytes {
self.encode_frame_bytes_zero_copy(Bytes::copy_from_slice(data), is_keyframe, timestamp_ms)
}
@@ -234,15 +248,13 @@ mod tests {
let msg = adapter.encode_frame(&data, true, 0);
match &msg.union {
Some(msg_union::Union::VideoFrame(vf)) => {
match &vf.union {
Some(vf_union::Union::H264s(frames)) => {
assert_eq!(frames.frames.len(), 1);
assert!(frames.frames[0].key);
}
_ => panic!("Expected H264s"),
Some(msg_union::Union::VideoFrame(vf)) => match &vf.union {
Some(vf_union::Union::H264s(frames)) => {
assert_eq!(frames.frames.len(), 1);
assert!(frames.frames[0].key);
}
}
_ => panic!("Expected H264s"),
},
_ => panic!("Expected VideoFrame"),
}
}
@@ -256,15 +268,13 @@ mod tests {
assert!(adapter.format_sent());
match &msg.union {
Some(msg_union::Union::Misc(misc)) => {
match &misc.union {
Some(misc_union::Union::AudioFormat(fmt)) => {
assert_eq!(fmt.sample_rate, 48000);
assert_eq!(fmt.channels, 2);
}
_ => panic!("Expected AudioFormat"),
Some(msg_union::Union::Misc(misc)) => match &misc.union {
Some(misc_union::Union::AudioFormat(fmt)) => {
assert_eq!(fmt.sample_rate, 48000);
assert_eq!(fmt.channels, 2);
}
}
_ => panic!("Expected AudioFormat"),
},
_ => panic!("Expected Misc"),
}
}

View File

@@ -2,13 +2,13 @@
//!
//! Converts RustDesk HID events (KeyEvent, MouseEvent) to One-KVM HID events.
use protobuf::Enum;
use crate::hid::{
KeyboardEvent, KeyboardModifiers, KeyEventType,
MouseButton, MouseEvent as OneKvmMouseEvent, MouseEventType,
};
use super::protocol::{KeyEvent, MouseEvent, ControlKey};
use super::protocol::hbb::message::key_event as ke_union;
use super::protocol::{ControlKey, KeyEvent, MouseEvent};
use crate::hid::{
KeyEventType, KeyboardEvent, KeyboardModifiers, MouseButton, MouseEvent as OneKvmMouseEvent,
MouseEventType,
};
use protobuf::Enum;
/// Mouse event types from RustDesk protocol
/// mask = (button << 3) | event_type
@@ -32,7 +32,11 @@ pub mod mouse_button {
/// Convert RustDesk MouseEvent to One-KVM MouseEvent(s)
/// Returns a Vec because a single RustDesk event may need multiple One-KVM events
/// (e.g., move + button + scroll)
pub fn convert_mouse_event(event: &MouseEvent, screen_width: u32, screen_height: u32) -> Vec<OneKvmMouseEvent> {
pub fn convert_mouse_event(
event: &MouseEvent,
screen_width: u32,
screen_height: u32,
) -> Vec<OneKvmMouseEvent> {
let mut events = Vec::new();
// RustDesk uses absolute coordinates
@@ -243,10 +247,10 @@ fn parse_modifiers(event: &KeyEvent) -> KeyboardModifiers {
/// Convert RustDesk ControlKey to USB HID usage code
fn control_key_to_hid(key: i32) -> Option<u8> {
match key {
x if x == ControlKey::Alt as i32 => Some(0xE2), // Left Alt
x if x == ControlKey::Alt as i32 => Some(0xE2), // Left Alt
x if x == ControlKey::Backspace as i32 => Some(0x2A),
x if x == ControlKey::CapsLock as i32 => Some(0x39),
x if x == ControlKey::Control as i32 => Some(0xE0), // Left Ctrl
x if x == ControlKey::Control as i32 => Some(0xE0), // Left Ctrl
x if x == ControlKey::Delete as i32 => Some(0x4C),
x if x == ControlKey::DownArrow as i32 => Some(0x51),
x if x == ControlKey::End as i32 => Some(0x4D),
@@ -265,12 +269,12 @@ fn control_key_to_hid(key: i32) -> Option<u8> {
x if x == ControlKey::F12 as i32 => Some(0x45),
x if x == ControlKey::Home as i32 => Some(0x4A),
x if x == ControlKey::LeftArrow as i32 => Some(0x50),
x if x == ControlKey::Meta as i32 => Some(0xE3), // Left GUI/Windows
x if x == ControlKey::Meta as i32 => Some(0xE3), // Left GUI/Windows
x if x == ControlKey::PageDown as i32 => Some(0x4E),
x if x == ControlKey::PageUp as i32 => Some(0x4B),
x if x == ControlKey::Return as i32 => Some(0x28),
x if x == ControlKey::RightArrow as i32 => Some(0x4F),
x if x == ControlKey::Shift as i32 => Some(0xE1), // Left Shift
x if x == ControlKey::Shift as i32 => Some(0xE1), // Left Shift
x if x == ControlKey::Space as i32 => Some(0x2C),
x if x == ControlKey::Tab as i32 => Some(0x2B),
x if x == ControlKey::UpArrow as i32 => Some(0x52),
@@ -330,7 +334,7 @@ fn ascii_to_hid(ascii: u32) -> Option<u8> {
Some((ascii - 65 + 0x04) as u8)
}
// Numbers 0-9 (ASCII 48-57)
48 => Some(0x27), // 0
48 => Some(0x27), // 0
49..=57 => Some((ascii - 49 + 0x1E) as u8), // 1-9
// Common punctuation
32 => Some(0x2C), // Space
@@ -341,17 +345,17 @@ fn ascii_to_hid(ascii: u32) -> Option<u8> {
8 => Some(0x2A), // Backspace
127 => Some(0x4C), // Delete
// Symbols (US keyboard layout)
45 => Some(0x2D), // -
61 => Some(0x2E), // =
91 => Some(0x2F), // [
93 => Some(0x30), // ]
92 => Some(0x31), // \
59 => Some(0x33), // ;
39 => Some(0x34), // '
96 => Some(0x35), // `
44 => Some(0x36), // ,
46 => Some(0x37), // .
47 => Some(0x38), // /
45 => Some(0x2D), // -
61 => Some(0x2E), // =
91 => Some(0x2F), // [
93 => Some(0x30), // ]
92 => Some(0x31), // \
59 => Some(0x33), // ;
39 => Some(0x34), // '
96 => Some(0x35), // `
44 => Some(0x36), // ,
46 => Some(0x37), // .
47 => Some(0x38), // /
_ => None,
}
}
@@ -394,10 +398,10 @@ fn windows_vk_to_hid(vk: u32) -> Option<u8> {
})
}
// Numbers 0-9 (VK_0=0x30 to VK_9=0x39)
0x30 => Some(0x27), // 0
0x30 => Some(0x27), // 0
0x31..=0x39 => Some((vk - 0x31 + 0x1E) as u8), // 1-9
// Numpad 0-9 (VK_NUMPAD0=0x60 to VK_NUMPAD9=0x69)
0x60 => Some(0x62), // Numpad 0
0x60 => Some(0x62), // Numpad 0
0x61..=0x69 => Some((vk - 0x61 + 0x59) as u8), // Numpad 1-9
// Numpad operators
0x6A => Some(0x55), // Numpad *
@@ -451,7 +455,7 @@ fn x11_keycode_to_hid(keycode: u32) -> Option<u8> {
match keycode {
// Numbers: X11 keycode 10="1", 11="2", ..., 18="9", 19="0"
10..=18 => Some((keycode - 10 + 0x1E) as u8), // 1-9
19 => Some(0x27), // 0
19 => Some(0x27), // 0
// Punctuation
20 => Some(0x2D), // -
21 => Some(0x2E), // =
@@ -533,7 +537,9 @@ mod tests {
let events = convert_mouse_event(&event, 1920, 1080);
assert!(events.len() >= 2);
// Should have a button down event
assert!(events.iter().any(|e| e.event_type == MouseEventType::Down && e.button == Some(MouseButton::Left)));
assert!(events
.iter()
.any(|e| e.event_type == MouseEventType::Down && e.button == Some(MouseButton::Left)));
}
#[test]
@@ -542,7 +548,9 @@ mod tests {
let mut key_event = KeyEvent::new();
key_event.down = true;
key_event.press = false;
key_event.union = Some(ke_union::Union::ControlKey(EnumOrUnknown::new(ControlKey::Return)));
key_event.union = Some(ke_union::Union::ControlKey(EnumOrUnknown::new(
ControlKey::Return,
)));
let result = convert_key_event(&key_event);
assert!(result.is_some());

View File

@@ -205,7 +205,8 @@ impl RustDeskService {
self.connection_manager.set_audio(self.audio.clone());
// Set the video manager on connection manager for video streaming
self.connection_manager.set_video_manager(self.video_manager.clone());
self.connection_manager
.set_video_manager(self.video_manager.clone());
*self.rendezvous.write() = Some(mediator.clone());
@@ -231,105 +232,117 @@ impl RustDeskService {
let audio_punch = self.audio.clone();
let service_config_punch = self.config.clone();
mediator.set_punch_callback(Arc::new(move |peer_addr, rendezvous_addr, relay_server, uuid, socket_addr, device_id| {
let conn_mgr = connection_manager_punch.clone();
let video = video_manager_punch.clone();
let hid = hid_punch.clone();
let audio = audio_punch.clone();
let config = service_config_punch.clone();
mediator.set_punch_callback(Arc::new(
move |peer_addr, rendezvous_addr, relay_server, uuid, socket_addr, device_id| {
let conn_mgr = connection_manager_punch.clone();
let video = video_manager_punch.clone();
let hid = hid_punch.clone();
let audio = audio_punch.clone();
let config = service_config_punch.clone();
tokio::spawn(async move {
// Get relay_key from config (no public server fallback)
let relay_key = {
let cfg = config.read();
cfg.relay_key.clone().unwrap_or_default()
};
tokio::spawn(async move {
// Get relay_key from config (no public server fallback)
let relay_key = {
let cfg = config.read();
cfg.relay_key.clone().unwrap_or_default()
};
// Try P2P direct connection first
if let Some(addr) = peer_addr {
info!("Attempting P2P direct connection to {}", addr);
match punch::try_direct_connection(addr).await {
punch::PunchResult::DirectConnection(stream) => {
info!("P2P direct connection succeeded to {}", addr);
if let Err(e) = conn_mgr.accept_connection(stream, addr).await {
error!("Failed to accept P2P connection: {}", e);
// Try P2P direct connection first
if let Some(addr) = peer_addr {
info!("Attempting P2P direct connection to {}", addr);
match punch::try_direct_connection(addr).await {
punch::PunchResult::DirectConnection(stream) => {
info!("P2P direct connection succeeded to {}", addr);
if let Err(e) = conn_mgr.accept_connection(stream, addr).await {
error!("Failed to accept P2P connection: {}", e);
}
return;
}
punch::PunchResult::NeedRelay => {
info!("P2P direct connection failed, falling back to relay");
}
return;
}
punch::PunchResult::NeedRelay => {
info!("P2P direct connection failed, falling back to relay");
}
}
}
// Fall back to relay
if let Err(e) = handle_relay_request(
&rendezvous_addr,
&relay_server,
&uuid,
&socket_addr,
&device_id,
&relay_key,
conn_mgr,
video,
hid,
audio,
).await {
error!("Failed to handle relay request: {}", e);
}
});
}));
// Fall back to relay
if let Err(e) = handle_relay_request(
&rendezvous_addr,
&relay_server,
&uuid,
&socket_addr,
&device_id,
&relay_key,
conn_mgr,
video,
hid,
audio,
)
.await
{
error!("Failed to handle relay request: {}", e);
}
});
},
));
// Set the relay callback on the mediator
mediator.set_relay_callback(Arc::new(move |rendezvous_addr, relay_server, uuid, socket_addr, device_id| {
let conn_mgr = connection_manager.clone();
let video = video_manager.clone();
let hid = hid.clone();
let audio = audio.clone();
let config = service_config.clone();
mediator.set_relay_callback(Arc::new(
move |rendezvous_addr, relay_server, uuid, socket_addr, device_id| {
let conn_mgr = connection_manager.clone();
let video = video_manager.clone();
let hid = hid.clone();
let audio = audio.clone();
let config = service_config.clone();
tokio::spawn(async move {
// Get relay_key from config (no public server fallback)
let relay_key = {
let cfg = config.read();
cfg.relay_key.clone().unwrap_or_default()
};
tokio::spawn(async move {
// Get relay_key from config (no public server fallback)
let relay_key = {
let cfg = config.read();
cfg.relay_key.clone().unwrap_or_default()
};
if let Err(e) = handle_relay_request(
&rendezvous_addr,
&relay_server,
&uuid,
&socket_addr,
&device_id,
&relay_key,
conn_mgr,
video,
hid,
audio,
).await {
error!("Failed to handle relay request: {}", e);
}
});
}));
if let Err(e) = handle_relay_request(
&rendezvous_addr,
&relay_server,
&uuid,
&socket_addr,
&device_id,
&relay_key,
conn_mgr,
video,
hid,
audio,
)
.await
{
error!("Failed to handle relay request: {}", e);
}
});
},
));
// Set the intranet callback on the mediator for same-LAN connections
let connection_manager2 = self.connection_manager.clone();
mediator.set_intranet_callback(Arc::new(move |rendezvous_addr, peer_socket_addr, local_addr, relay_server, device_id| {
let conn_mgr = connection_manager2.clone();
mediator.set_intranet_callback(Arc::new(
move |rendezvous_addr, peer_socket_addr, local_addr, relay_server, device_id| {
let conn_mgr = connection_manager2.clone();
tokio::spawn(async move {
if let Err(e) = handle_intranet_request(
&rendezvous_addr,
&peer_socket_addr,
local_addr,
&relay_server,
&device_id,
conn_mgr,
).await {
error!("Failed to handle intranet request: {}", e);
}
});
}));
tokio::spawn(async move {
if let Err(e) = handle_intranet_request(
&rendezvous_addr,
&peer_socket_addr,
local_addr,
&relay_server,
&device_id,
conn_mgr,
)
.await
{
error!("Failed to handle intranet request: {}", e);
}
});
},
));
// Spawn rendezvous task
let status = self.status.clone();
@@ -471,7 +484,9 @@ impl RustDeskService {
// Save signing keypair (Ed25519)
let signing_pk = skp.public_key_base64();
let signing_sk = skp.secret_key_base64();
if config.signing_public_key.as_ref() != Some(&signing_pk) || config.signing_private_key.as_ref() != Some(&signing_sk) {
if config.signing_public_key.as_ref() != Some(&signing_pk)
|| config.signing_private_key.as_ref() != Some(&signing_sk)
{
config.signing_public_key = Some(signing_pk);
config.signing_private_key = Some(signing_sk);
changed = true;
@@ -522,13 +537,18 @@ async fn handle_relay_request(
_hid: Arc<HidController>,
_audio: Arc<AudioController>,
) -> anyhow::Result<()> {
info!("Handling relay request: rendezvous={}, relay={}, uuid={}", rendezvous_addr, relay_server, uuid);
info!(
"Handling relay request: rendezvous={}, relay={}, uuid={}",
rendezvous_addr, relay_server, uuid
);
// Step 1: Connect to RENDEZVOUS server and send RelayResponse
let rendezvous_socket_addr: SocketAddr = tokio::net::lookup_host(rendezvous_addr)
.await?
.next()
.ok_or_else(|| anyhow::anyhow!("Failed to resolve rendezvous server: {}", rendezvous_addr))?;
.ok_or_else(|| {
anyhow::anyhow!("Failed to resolve rendezvous server: {}", rendezvous_addr)
})?;
let mut rendezvous_stream = tokio::time::timeout(
Duration::from_millis(RELAY_CONNECT_TIMEOUT_MS),
@@ -537,12 +557,17 @@ async fn handle_relay_request(
.await
.map_err(|_| anyhow::anyhow!("Rendezvous connection timeout"))??;
debug!("Connected to rendezvous server at {}", rendezvous_socket_addr);
debug!(
"Connected to rendezvous server at {}",
rendezvous_socket_addr
);
// Send RelayResponse to rendezvous server with client's socket_addr
// IMPORTANT: Include our device ID so rendezvous server can look up and sign our public key
let relay_response = make_relay_response(uuid, socket_addr, relay_server, device_id);
let bytes = relay_response.write_to_bytes().map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
let bytes = relay_response
.write_to_bytes()
.map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
bytes_codec::write_frame(&mut rendezvous_stream, &bytes).await?;
debug!("Sent RelayResponse to rendezvous server for uuid={}", uuid);
@@ -568,7 +593,9 @@ async fn handle_relay_request(
// The licence_key is required if the relay server is configured with -k option
// The socket_addr is CRITICAL - the relay server uses it to match us with the peer
let request_relay = make_request_relay(uuid, relay_key, socket_addr);
let bytes = request_relay.write_to_bytes().map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
let bytes = request_relay
.write_to_bytes()
.map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
bytes_codec::write_frame(&mut stream, &bytes).await?;
debug!("Sent RequestRelay to relay server for uuid={}", uuid);
@@ -576,8 +603,13 @@ async fn handle_relay_request(
let peer_addr = rendezvous::AddrMangle::decode(socket_addr).unwrap_or(relay_addr);
// Step 3: Accept connection - relay server bridges the connection
connection_manager.accept_connection(stream, peer_addr).await?;
info!("Relay connection established for uuid={}, peer={}", uuid, peer_addr);
connection_manager
.accept_connection(stream, peer_addr)
.await?;
info!(
"Relay connection established for uuid={}, peer={}",
uuid, peer_addr
);
Ok(())
}
@@ -608,14 +640,15 @@ async fn handle_intranet_request(
debug!("Peer address from FetchLocalAddr: {:?}", peer_addr);
// Connect to rendezvous server via TCP with timeout
let mut stream = tokio::time::timeout(
Duration::from_secs(5),
TcpStream::connect(rendezvous_addr),
)
.await
.map_err(|_| anyhow::anyhow!("Timeout connecting to rendezvous server"))??;
let mut stream =
tokio::time::timeout(Duration::from_secs(5), TcpStream::connect(rendezvous_addr))
.await
.map_err(|_| anyhow::anyhow!("Timeout connecting to rendezvous server"))??;
info!("Connected to rendezvous server for intranet: {}", rendezvous_addr);
info!(
"Connected to rendezvous server for intranet: {}",
rendezvous_addr
);
// Build LocalAddr message with our local address (mangled)
let local_addr_bytes = AddrMangle::encode(local_addr);
@@ -626,7 +659,9 @@ async fn handle_intranet_request(
device_id,
env!("CARGO_PKG_VERSION"),
);
let bytes = msg.write_to_bytes().map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
let bytes = msg
.write_to_bytes()
.map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
// Send LocalAddr using RustDesk's variable-length framing
bytes_codec::write_frame(&mut stream, &bytes).await?;
@@ -640,11 +675,15 @@ async fn handle_intranet_request(
// Get peer address for logging/connection tracking
let effective_peer_addr = peer_addr.unwrap_or_else(|| {
// If we can't decode the peer address, use the rendezvous server address
rendezvous_addr.parse().unwrap_or_else(|_| "0.0.0.0:0".parse().unwrap())
rendezvous_addr
.parse()
.unwrap_or_else(|_| "0.0.0.0:0".parse().unwrap())
});
// Accept the connection - the stream is now a proxied connection to the client
connection_manager.accept_connection(stream, effective_peer_addr).await?;
connection_manager
.accept_connection(stream, effective_peer_addr)
.await?;
info!("Intranet connection established via rendezvous server proxy");
Ok(())

View File

@@ -14,22 +14,20 @@ pub mod hbb {
// Re-export commonly used types
pub use hbb::rendezvous::{
rendezvous_message, relay_response, punch_hole_response,
ConnType, ConfigUpdate, FetchLocalAddr, HealthCheck, KeyExchange, LocalAddr, NatType,
OnlineRequest, OnlineResponse, PeerDiscovery, PunchHole, PunchHoleRequest, PunchHoleResponse,
PunchHoleSent, RegisterPeer, RegisterPeerResponse, RegisterPk, RegisterPkResponse,
RelayResponse, RendezvousMessage, RequestRelay, SoftwareUpdate, TestNatRequest,
TestNatResponse,
punch_hole_response, relay_response, rendezvous_message, ConfigUpdate, ConnType,
FetchLocalAddr, HealthCheck, KeyExchange, LocalAddr, NatType, OnlineRequest, OnlineResponse,
PeerDiscovery, PunchHole, PunchHoleRequest, PunchHoleResponse, PunchHoleSent, RegisterPeer,
RegisterPeerResponse, RegisterPk, RegisterPkResponse, RelayResponse, RendezvousMessage,
RequestRelay, SoftwareUpdate, TestNatRequest, TestNatResponse,
};
// Re-export message.proto types
pub use hbb::message::{
message, misc, login_response, key_event,
AudioFormat, AudioFrame, Auth2FA, Clipboard, CursorData, CursorPosition, EncodedVideoFrame,
EncodedVideoFrames, Hash, IdPk, KeyEvent, LoginRequest, LoginResponse, MouseEvent, Misc,
OptionMessage, PeerInfo, PublicKey, SignedId, SupportedDecoding, VideoFrame, TestDelay,
Features, SupportedResolutions, WindowsSessions, Message as HbbMessage, ControlKey,
DisplayInfo, SupportedEncoding,
key_event, login_response, message, misc, AudioFormat, AudioFrame, Auth2FA, Clipboard,
ControlKey, CursorData, CursorPosition, DisplayInfo, EncodedVideoFrame, EncodedVideoFrames,
Features, Hash, IdPk, KeyEvent, LoginRequest, LoginResponse, Message as HbbMessage, Misc,
MouseEvent, OptionMessage, PeerInfo, PublicKey, SignedId, SupportedDecoding, SupportedEncoding,
SupportedResolutions, TestDelay, VideoFrame, WindowsSessions,
};
/// Helper to create a RendezvousMessage with RegisterPeer
@@ -80,7 +78,12 @@ pub fn make_punch_hole_sent(
/// IMPORTANT: The union field should be `Id` (our device ID), NOT `Pk`.
/// The rendezvous server will look up our registered public key using this ID,
/// sign it with the server's private key, and set the `pk` field before forwarding to client.
pub fn make_relay_response(uuid: &str, socket_addr: &[u8], relay_server: &str, device_id: &str) -> RendezvousMessage {
pub fn make_relay_response(
uuid: &str,
socket_addr: &[u8],
relay_server: &str,
device_id: &str,
) -> RendezvousMessage {
let mut rr = RelayResponse::new();
rr.socket_addr = socket_addr.to_vec().into();
rr.uuid = uuid.to_string();

View File

@@ -69,10 +69,7 @@ impl PunchHoleHandler {
///
/// Tries direct connection first, falls back to relay if needed.
/// Returns true if direct connection succeeded, false if relay is needed.
pub async fn handle_punch_hole(
&self,
peer_addr: Option<SocketAddr>,
) -> bool {
pub async fn handle_punch_hole(&self, peer_addr: Option<SocketAddr>) -> bool {
let peer_addr = match peer_addr {
Some(addr) => addr,
None => {
@@ -84,7 +81,11 @@ impl PunchHoleHandler {
match try_direct_connection(peer_addr).await {
PunchResult::DirectConnection(stream) => {
// Direct connection succeeded, accept it
match self.connection_manager.accept_connection(stream, peer_addr).await {
match self
.connection_manager
.accept_connection(stream, peer_addr)
.await
{
Ok(_) => {
info!("P2P direct connection established with {}", peer_addr);
true

View File

@@ -18,8 +18,8 @@ use tracing::{debug, error, info, warn};
use super::config::RustDeskConfig;
use super::crypto::{KeyPair, SigningKeyPair};
use super::protocol::{
rendezvous_message, make_punch_hole_sent, make_register_peer,
make_register_pk, NatType, RendezvousMessage, decode_rendezvous_message,
decode_rendezvous_message, make_punch_hole_sent, make_register_peer, make_register_pk,
rendezvous_message, NatType, RendezvousMessage,
};
/// Registration interval in milliseconds
@@ -81,7 +81,8 @@ pub type RelayCallback = Arc<dyn Fn(String, String, String, Vec<u8>, String) + S
/// Callback type for P2P punch hole requests
/// Parameters: peer_addr (decoded), relay_callback_params (rendezvous_addr, relay_server, uuid, socket_addr, device_id)
/// Returns: should call relay callback if P2P fails
pub type PunchCallback = Arc<dyn Fn(Option<SocketAddr>, String, String, String, Vec<u8>, String) + Send + Sync>;
pub type PunchCallback =
Arc<dyn Fn(Option<SocketAddr>, String, String, String, Vec<u8>, String) + Send + Sync>;
/// Callback type for intranet/local address connections
/// Parameters: rendezvous_addr, peer_socket_addr (mangled), local_addr, relay_server, device_id
@@ -232,7 +233,8 @@ impl RendezvousMediator {
if signing_guard.is_none() {
let config = self.config.read();
// Try to load from config first
if let (Some(pk), Some(sk)) = (&config.signing_public_key, &config.signing_private_key) {
if let (Some(pk), Some(sk)) = (&config.signing_public_key, &config.signing_private_key)
{
if let Ok(skp) = SigningKeyPair::from_base64(pk, sk) {
debug!("Loaded signing keypair from config");
*signing_guard = Some(skp.clone());
@@ -265,14 +267,20 @@ impl RendezvousMediator {
config.enabled, effective_server
);
if !config.enabled || effective_server.is_empty() {
info!("Rendezvous mediator not starting: enabled={}, server='{}'", config.enabled, effective_server);
info!(
"Rendezvous mediator not starting: enabled={}, server='{}'",
config.enabled, effective_server
);
return Ok(());
}
*self.status.write() = RendezvousStatus::Connecting;
let addr = config.rendezvous_addr();
info!("Starting rendezvous mediator for {} to {}", config.device_id, addr);
info!(
"Starting rendezvous mediator for {} to {}",
config.device_id, addr
);
// Resolve server address
let server_addr: SocketAddr = tokio::net::lookup_host(&addr)
@@ -376,7 +384,9 @@ impl RendezvousMediator {
let serial = *self.serial.read();
let msg = make_register_peer(&id, serial);
let bytes = msg.write_to_bytes().map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
let bytes = msg
.write_to_bytes()
.map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
socket.send(&bytes).await?;
Ok(())
}
@@ -393,7 +403,9 @@ impl RendezvousMediator {
debug!("Sending RegisterPk: id={}", id);
let msg = make_register_pk(&id, &uuid, pk, "");
let bytes = msg.write_to_bytes().map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
let bytes = msg
.write_to_bytes()
.map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
socket.send(&bytes).await?;
Ok(())
}
@@ -540,7 +552,7 @@ impl RendezvousMediator {
);
let msg = make_punch_hole_sent(
&ph.socket_addr.to_vec(), // Use peer's socket_addr, not ours
&ph.socket_addr.to_vec(), // Use peer's socket_addr, not ours
&id,
&ph.relay_server,
ph.nat_type.enum_value().unwrap_or(NatType::UNKNOWN_NAT),
@@ -570,9 +582,22 @@ impl RendezvousMediator {
// Use punch callback if set (tries P2P first, then relay)
// Otherwise fall back to relay callback directly
if let Some(callback) = self.punch_callback.read().as_ref() {
callback(peer_addr, rendezvous_addr, relay_server, uuid, ph.socket_addr.to_vec(), device_id);
callback(
peer_addr,
rendezvous_addr,
relay_server,
uuid,
ph.socket_addr.to_vec(),
device_id,
);
} else if let Some(callback) = self.relay_callback.read().as_ref() {
callback(rendezvous_addr, relay_server, uuid, ph.socket_addr.to_vec(), device_id);
callback(
rendezvous_addr,
relay_server,
uuid,
ph.socket_addr.to_vec(),
device_id,
);
}
}
}
@@ -591,7 +616,13 @@ impl RendezvousMediator {
let config = self.config.read().clone();
let rendezvous_addr = config.rendezvous_addr();
let device_id = config.device_id.clone();
callback(rendezvous_addr, relay_server, rr.uuid.clone(), rr.socket_addr.to_vec(), device_id);
callback(
rendezvous_addr,
relay_server,
rr.uuid.clone(),
rr.socket_addr.to_vec(),
device_id,
);
}
}
Some(rendezvous_message::Union::FetchLocalAddr(fla)) => {
@@ -602,7 +633,8 @@ impl RendezvousMediator {
peer_addr, fla.socket_addr.len(), fla.relay_server
);
// Respond with our local address for same-LAN direct connection
self.send_local_addr(socket, &fla.socket_addr, &fla.relay_server).await?;
self.send_local_addr(socket, &fla.socket_addr, &fla.relay_server)
.await?;
}
Some(rendezvous_message::Union::ConfigureUpdate(cu)) => {
info!("Received ConfigureUpdate, serial={}", cu.serial);