Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions crates/ironrdp-dvc/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,19 +396,21 @@ impl DrdynvcClient {
return Err(pdu_other_err!("soft-sync request selected an unavailable tunnel"));
}

let mut selected_channels = Vec::new();
// The server lists every channel it intends to move, including channels this client
// declined with NO_LISTENER: Windows lists CoreInput, MouseCursor, Video and Geometry
// next to the graphics pipeline. Those channels carry no data, so they are skipped
// one by one. Refusing the whole tunnel because of them would leave the channels
// this client did open stranded on TCP while the server already sends on the tunnel.
for channel_id in list.channel_ids() {
if self.dynamic_channels.get_by_channel_id(*channel_id).is_none() {
selected_channels.clear();
break;
if self.dynamic_channels.get_by_channel_id(*channel_id).is_some() {
tunnel_channels.insert(*channel_id, list.tunnel_type());
} else {
debug!(
channel_id,
tunnel_type = ?list.tunnel_type(),
"Soft-Sync lists a dynamic channel this client did not open; ignoring it"
);
}
selected_channels.push(*channel_id);
}
if selected_channels.is_empty() && !list.channel_ids().is_empty() {
continue;
}
for channel_id in selected_channels {
tunnel_channels.insert(channel_id, list.tunnel_type());
}
tunnels_to_switch.push(list.tunnel_type());
}
Expand Down
37 changes: 36 additions & 1 deletion crates/ironrdp-testsuite-core/tests/dvc/client.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use ironrdp_core::{encode_vec, impl_as_any};
use ironrdp_core::{Decode as _, ReadCursor, encode_vec, impl_as_any};
use ironrdp_dvc::ironrdp_pdu::{PduResult, pdu_other_err};
use ironrdp_dvc::pdu::{
DataPdu, DrdynvcClientPdu, DrdynvcDataPdu, DrdynvcServerPdu, SoftSyncChannelList, SoftSyncRequestPdu,
Expand Down Expand Up @@ -134,6 +134,41 @@ fn soft_sync_rejects_a_tunnel_that_became_unavailable_before_migration() {
assert!(!client.has_channels_on_tunnel(SoftSyncTunnelType::RELIABLE_UDP));
}

#[test]
fn soft_sync_skips_channels_the_client_did_not_open() {
let mut client = DrdynvcClient::new();
client
.attach_established_dynamic_channel(7, RecordedDvc::default())
.expect("recorded channel should attach");
client.enable_soft_sync_tunnel(SoftSyncTunnelType::RELIABLE_UDP);

// Windows lists the channels this client answered with NO_LISTENER (2, 6, 8..=12) next to
// the graphics pipeline it accepted (7). The tunnel must still be switched for channel 7.
let request = encode_vec(&DrdynvcServerPdu::SoftSyncRequest(SoftSyncRequestPdu::new(vec![
SoftSyncChannelList::new(SoftSyncTunnelType::RELIABLE_UDP, vec![2, 6, 7, 8, 9, 10, 11, 12]),
])))
.expect("Soft-Sync request should encode");

let responses = client.process(&request).expect("Soft-Sync request should be accepted");
let [response] = responses.as_slice() else {
panic!("expected exactly one Soft-Sync response");
};
let encoded = response
.encode_unframed_pdu()
.expect("Soft-Sync response should encode");
let DrdynvcClientPdu::SoftSyncResponse(response) =
DrdynvcClientPdu::decode(&mut ReadCursor::new(&encoded)).expect("Soft-Sync response should decode")
else {
panic!("expected a Soft-Sync response");
};
assert_eq!(response.tunnels_to_switch(), &[SoftSyncTunnelType::RELIABLE_UDP]);

assert!(client.soft_sync_complete());
assert_eq!(client.tunnel_for_channel(7), Some(SoftSyncTunnelType::RELIABLE_UDP));
assert_eq!(client.tunnel_for_channel(2), None);
assert!(client.has_channels_on_tunnel(SoftSyncTunnelType::RELIABLE_UDP));
}

#[test]
fn message_batch_rejects_a_mismatched_channel_id() {
let message = SvcMessage::from(DrdynvcClientPdu::Data(DrdynvcDataPdu::Data(DataPdu::new(
Expand Down
Loading