diff --git a/crates/ironrdp-dvc/src/client.rs b/crates/ironrdp-dvc/src/client.rs index 27806d8639..282457b1f0 100644 --- a/crates/ironrdp-dvc/src/client.rs +++ b/crates/ironrdp-dvc/src/client.rs @@ -470,11 +470,19 @@ impl DrdynvcClient { fn process_data(&mut self, data: DrdynvcDataPdu) -> PduResult> { let channel_id = data.channel_id(); - let messages = self - .dynamic_channels - .get_by_channel_id_mut(channel_id) - .ok_or_else(|| pdu_other_err!("access to non existing DVC channel"))? - .process(data)?; + let Some(channel) = self.dynamic_channels.get_by_channel_id_mut(channel_id) else { + // A server can send data on a channel before it sees the client decline it in the + // Create Response (GNOME Remote Desktop does this for AUDIO_PLAYBACK_DVC). The data has + // nowhere to go; dropping it is enough, ending the session over it is not warranted. + // Logged at debug, as `DrdynvcServer` logs a declined channel: it is not a fault, and a + // server may keep sending such data for as long as the session lasts. + debug!( + channel_id, + "Dropping data for a dynamic virtual channel that is not open" + ); + return Ok(Vec::new()); + }; + let messages = channel.process(data)?; encode_dvc_messages(channel_id, messages, ChannelFlags::empty()).map_err(|e| encode_err!(e)) } diff --git a/crates/ironrdp-testsuite-core/tests/dvc/client.rs b/crates/ironrdp-testsuite-core/tests/dvc/client.rs index a7838de40a..5b2004a44c 100644 --- a/crates/ironrdp-testsuite-core/tests/dvc/client.rs +++ b/crates/ironrdp-testsuite-core/tests/dvc/client.rs @@ -88,6 +88,41 @@ fn exchange_capabilities(client: &mut DrdynvcClient) { client.process(&caps).expect("Capabilities Request should be processed"); } +#[test] +fn data_for_a_channel_that_is_not_open_is_dropped() { + // A server can send data on a channel before it sees the client decline it in the Create + // Response (GNOME Remote Desktop does this for AUDIO_PLAYBACK_DVC). The session must survive it. + let mut client = DrdynvcClient::new(); + client + .attach_established_dynamic_channel(7, RecordedDvc::default()) + .expect("recorded channel should attach"); + let data = |channel_id: u32, payload: &[u8]| { + encode_vec(&DrdynvcServerPdu::Data(DrdynvcDataPdu::Data(DataPdu::new( + channel_id, + payload.to_vec(), + )))) + .expect("DVC data should encode") + }; + + assert!( + client + .process(&data(4, b"declined")) + .expect("data for a channel that is not open should be dropped, not fail") + .is_empty() + ); + assert!( + client + .process(&data(7, b"open")) + .expect("open channels are unaffected") + .is_empty() + ); + + let channel = client + .get_dvc_by_channel_id::(7) + .expect("recorded channel should remain attached"); + assert_eq!(channel.processor().received, vec![b"open".to_vec()]); +} + #[test] fn established_dynamic_channel_routes_recorded_data_without_negotiation() { let mut client = DrdynvcClient::new();