From cd23497722866eb3f052d182e6e752f7c5f3c85e Mon Sep 17 00:00:00 2001 From: rdeangel Date: Wed, 10 Jun 2026 22:06:41 +0100 Subject: [PATCH] fix(connector): stay in CapabilitiesExchange when activation handles DeactivateAll PR #1254 taught the inner ConnectionActivationSequence to skip a Server Deactivate All PDU received before Server Demand Active (sent by e.g. Windows Server and gnome-remote-desktop during a Deactivation-Reactivation Sequence, MS-RDPBCGR 1.3.1.3): it returns Written::Nothing and stays in CapabilitiesExchange. However, the outer ClientConnector::step() only accepted ConnectionFinalization as the resulting inner state, so the same scenario still failed with "invalid state (this is a bug)" during the initial connection sequence. Mirror the inner behavior in the outer state machine and keep waiting for the Demand Active PDU. Fixes #1362 Co-Authored-By: Claude Fable 5 --- crates/ironrdp-connector/src/connection.rs | 8 +++++++ .../tests/session/connection_activation.rs | 22 ++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/crates/ironrdp-connector/src/connection.rs b/crates/ironrdp-connector/src/connection.rs index 95b53bd4a..55ab284bb 100644 --- a/crates/ironrdp-connector/src/connection.rs +++ b/crates/ironrdp-connector/src/connection.rs @@ -558,6 +558,14 @@ impl Sequence for ClientConnector { written, ClientConnectorState::ConnectionFinalization { connection_activation }, ), + // The inner sequence stays in CapabilitiesExchange when it receives a + // Server Deactivate All PDU before the Server Demand Active PDU (sent + // by e.g. Windows Server and gnome-remote-desktop); mirror it here and + // wait for the next input. + ConnectionActivationState::CapabilitiesExchange { .. } => ( + written, + ClientConnectorState::CapabilitiesExchange { connection_activation }, + ), _ => return Err(general_err!("invalid state (this is a bug)")), } } diff --git a/crates/ironrdp-testsuite-core/tests/session/connection_activation.rs b/crates/ironrdp-testsuite-core/tests/session/connection_activation.rs index 79887d371..d865dba60 100644 --- a/crates/ironrdp-testsuite-core/tests/session/connection_activation.rs +++ b/crates/ironrdp-testsuite-core/tests/session/connection_activation.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use ironrdp_connector::connection_activation::{ConnectionActivationSequence, ConnectionActivationState}; -use ironrdp_connector::{Credentials, DesktopSize, Sequence as _, Written}; +use ironrdp_connector::{ClientConnector, ClientConnectorState, Credentials, DesktopSize, Sequence as _, Written}; use ironrdp_core::{WriteBuf, encode_vec}; use ironrdp_pdu::gcc; use ironrdp_pdu::mcs::{McsMessage, SendDataIndication}; @@ -95,6 +95,26 @@ fn deactivate_all_during_capabilities_exchange_stays_in_same_state() { ); } +#[test] +fn client_connector_stays_in_capabilities_exchange_on_deactivate_all() { + let config = test_config(); + let mut connector = ClientConnector::new(config.clone(), "127.0.0.1:3389".parse().unwrap()); + connector.state = ClientConnectorState::CapabilitiesExchange { + connection_activation: ConnectionActivationSequence::new(config, IO_CHANNEL_ID, USER_CHANNEL_ID), + }; + + let frame = encode_server_share_control(ShareControlPdu::ServerDeactivateAll(ServerDeactivateAll)); + let mut output = WriteBuf::new(); + + let written = connector.step(&frame, &mut output).unwrap(); + + assert_eq!(written, Written::Nothing); + assert!( + matches!(connector.state, ClientConnectorState::CapabilitiesExchange { .. }), + "outer connector state should remain CapabilitiesExchange after DeactivateAll" + ); +} + #[test] fn demand_active_after_deactivate_all_transitions_to_connection_finalization() { let config = test_config();