diff --git a/crates/ironrdp-acceptor/src/channel_connection.rs b/crates/ironrdp-acceptor/src/channel_connection.rs index e531deb5e4..69abfcb30e 100644 --- a/crates/ironrdp-acceptor/src/channel_connection.rs +++ b/crates/ironrdp-acceptor/src/channel_connection.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; use ironrdp_connector::{ - ConnectorError, ConnectorErrorExt as _, ConnectorResult, MonotonicInstant, Sequence, State, Written, reason_err, + MonotonicInstant, Sequence, SequenceError, SequenceErrorExt as _, SequenceResult, State, Written, reason_err, }; use ironrdp_core::WriteBuf; use ironrdp_pdu::mcs; @@ -77,11 +77,11 @@ impl Sequence for ChannelConnectionSequence { input: &[u8], _received_at: Option, output: &mut WriteBuf, - ) -> ConnectorResult { + ) -> SequenceResult { let (written, next_state) = match core::mem::take(&mut self.state) { ChannelConnectionState::WaitErectDomainRequest => { let erect_domain_request = ironrdp_core::decode::>(input) - .map_err(ConnectorError::decode) + .map_err(SequenceError::decode) .map(|p| p.0)?; debug!(message = ?erect_domain_request, "Received"); @@ -91,7 +91,7 @@ impl Sequence for ChannelConnectionSequence { ChannelConnectionState::WaitAttachUserRequest => { let attach_user_request = ironrdp_core::decode::>(input) - .map_err(ConnectorError::decode) + .map_err(SequenceError::decode) .map(|p| p.0)?; debug!(message = ?attach_user_request, "Received"); @@ -108,7 +108,7 @@ impl Sequence for ChannelConnectionSequence { debug!(message = ?attach_user_confirm, "Send"); let written = - ironrdp_core::encode_buf(&X224(attach_user_confirm), output).map_err(ConnectorError::encode)?; + ironrdp_core::encode_buf(&X224(attach_user_confirm), output).map_err(SequenceError::encode)?; let next_state = match self.channel_ids.take() { Some(channel_ids) => ChannelConnectionState::WaitChannelJoinRequest { remaining: channel_ids }, @@ -120,7 +120,7 @@ impl Sequence for ChannelConnectionSequence { ChannelConnectionState::WaitChannelJoinRequest { mut remaining } => { let channel_request = ironrdp_core::decode::>(input) - .map_err(ConnectorError::decode) + .map_err(SequenceError::decode) .map(|p| p.0)?; debug!(message = ?channel_request, "Received"); @@ -156,7 +156,7 @@ impl Sequence for ChannelConnectionSequence { debug!(message = ?channel_confirm, "Send"); let written = - ironrdp_core::encode_buf(&X224(channel_confirm), output).map_err(ConnectorError::encode)?; + ironrdp_core::encode_buf(&X224(channel_confirm), output).map_err(SequenceError::encode)?; let next_state = if remaining.is_empty() { ChannelConnectionState::AllJoined diff --git a/crates/ironrdp-acceptor/src/connection.rs b/crates/ironrdp-acceptor/src/connection.rs index dfbab7d5b6..aeaa82cbc0 100644 --- a/crates/ironrdp-acceptor/src/connection.rs +++ b/crates/ironrdp-acceptor/src/connection.rs @@ -2,7 +2,7 @@ use core::any::TypeId; use core::mem; use ironrdp_connector::{ - ConnectorError, ConnectorErrorExt as _, ConnectorResult, DesktopSize, MonotonicInstant, Sequence, State, Written, + DesktopSize, MonotonicInstant, Sequence, SequenceError, SequenceErrorExt as _, SequenceResult, State, Written, encode_x224_packet, general_err, reason_err, }; use ironrdp_core::{WriteBuf, decode}; @@ -215,7 +215,7 @@ impl Acceptor { mut consumed: Acceptor, static_channels: StaticChannelSet, desktop_size: DesktopSize, - ) -> ConnectorResult { + ) -> SequenceResult { let AcceptorState::CapabilitiesSendServer { early_capability, channels, @@ -486,13 +486,13 @@ impl Sequence for Acceptor { input: &[u8], received_at: Option, output: &mut WriteBuf, - ) -> ConnectorResult { + ) -> SequenceResult { let prev_state = mem::take(&mut self.state); let (written, next_state) = match prev_state { AcceptorState::InitiationWaitRequest => { let connection_request = decode::>(input) - .map_err(ConnectorError::decode) + .map_err(SequenceError::decode) .map(|p| p.0)?; debug!(message = ?connection_request, "Received"); @@ -533,7 +533,7 @@ impl Sequence for Acceptor { debug!(message = ?failure, "Send"); - ironrdp_core::encode_buf(&X224(failure), output).map_err(ConnectorError::encode)?; + ironrdp_core::encode_buf(&X224(failure), output).map_err(SequenceError::encode)?; return Err(reason_err!( "security protocol mismatch", @@ -550,7 +550,7 @@ impl Sequence for Acceptor { debug!(message = ?connection_confirm, "Send"); let written = - ironrdp_core::encode_buf(&X224(connection_confirm), output).map_err(ConnectorError::encode)?; + ironrdp_core::encode_buf(&X224(connection_confirm), output).map_err(SequenceError::encode)?; ( Written::from_size(written)?, @@ -596,10 +596,10 @@ impl Sequence for Acceptor { protocol, } => { let x224_payload = decode::>>(input) - .map_err(ConnectorError::decode) + .map_err(SequenceError::decode) .map(|p| p.0)?; let settings_initial = - decode::(x224_payload.data.as_ref()).map_err(ConnectorError::decode)?; + decode::(x224_payload.data.as_ref()).map_err(SequenceError::decode)?; debug!(message = ?settings_initial, "Received"); @@ -721,7 +721,7 @@ impl Sequence for Acceptor { let settings_response = mcs::ConnectResponse { conference_create_response: gcc::ConferenceCreateResponse::new(self.user_channel_id, server_blocks) - .map_err(ConnectorError::decode)?, + .map_err(SequenceError::decode)?, called_connect_id: 1, domain_parameters: mcs::DomainParameters::target(), }; @@ -792,10 +792,9 @@ impl Sequence for Acceptor { early_capability, channels, } => { - let data: X224> = decode(input).map_err(ConnectorError::decode)?; + let data: X224> = decode(input).map_err(SequenceError::decode)?; let data = data.0; - let client_info: rdp::ClientInfoPdu = - decode(data.user_data.as_ref()).map_err(ConnectorError::decode)?; + let client_info: rdp::ClientInfoPdu = decode(data.user_data.as_ref()).map_err(SequenceError::decode)?; let auto_reconnect = client_info .client_info @@ -824,7 +823,7 @@ impl Sequence for Acceptor { util::encode_send_data_indication(self.user_channel_id, self.io_channel_id, &info, output)?; - return Err(ConnectorError::general("invalid credentials")); + return Err(general_err!("invalid credentials")); } } @@ -846,7 +845,7 @@ impl Sequence for Acceptor { channels, } => { let license: LicensePdu = LicensingErrorMessage::new_valid_client() - .map_err(ConnectorError::encode)? + .map_err(SequenceError::encode)? .into(); debug!(message = ?license, "Send"); @@ -929,7 +928,7 @@ impl Sequence for Acceptor { AcceptorState::CapabilitiesWaitConfirm { ref channels } => { let message = decode::>>(input) - .map_err(ConnectorError::decode) + .map_err(SequenceError::decode) .map(|p| p.0); let message = match message { Ok(msg) => msg, @@ -946,7 +945,7 @@ impl Sequence for Acceptor { match message { mcs::McsMessage::SendDataRequest(data) => { let capabilities_confirm = decode::(data.user_data.as_ref()) - .map_err(ConnectorError::decode); + .map_err(SequenceError::decode); let capabilities_confirm = match capabilities_confirm { Ok(capabilities_confirm) => capabilities_confirm, Err(e) => { @@ -964,7 +963,7 @@ impl Sequence for Acceptor { let ShareControlPdu::ClientConfirmActive(confirm) = capabilities_confirm.share_control_pdu else { - return Err(ConnectorError::general("expected client confirm active")); + return Err(general_err!("expected client confirm active")); }; ( diff --git a/crates/ironrdp-acceptor/src/credssp.rs b/crates/ironrdp-acceptor/src/credssp.rs index e665724dbf..d69f40b1cf 100644 --- a/crates/ironrdp-acceptor/src/credssp.rs +++ b/crates/ironrdp-acceptor/src/credssp.rs @@ -5,7 +5,7 @@ use ironrdp_connector::sspi::credssp::{ use ironrdp_connector::sspi::generator::{Generator, GeneratorState}; use ironrdp_connector::sspi::{self, AuthIdentity, KerberosServerConfig, NegotiateConfig, NetworkRequest, Username}; use ironrdp_connector::{ - ConnectorError, ConnectorErrorKind, ConnectorResult, ServerName, Written, custom_err, general_err, + ConnectorError, ConnectorErrorKind, ConnectorResult, ResultExt as _, ServerName, Written, custom_err, general_err, }; use ironrdp_core::{WriteBuf, other_err}; use ironrdp_pdu::PduHint; @@ -97,7 +97,9 @@ impl<'a> CredsspSequence<'a> { match &self.state { CredsspState::Ongoing => Ok(Some(&CREDSSP_TS_REQUEST_HINT)), CredsspState::Finished => Ok(None), - CredsspState::ServerError(err) => Err(custom_err!("Credssp server error", err.clone())), + CredsspState::ServerError(err) => { + Err(custom_err!("Credssp server error", err.clone())).map_err_as::() + } } } @@ -135,13 +137,16 @@ impl<'a> CredsspSequence<'a> { pub fn decode_client_message(&mut self, input: &[u8]) -> ConnectorResult> { match self.state { CredsspState::Ongoing => { - let message = TsRequest::from_buffer(input).map_err(|e| custom_err!("TsRequest", e))?; + let message = TsRequest::from_buffer(input) + .map_err(|e| custom_err!("TsRequest", e)) + .map_err_as::()?; debug!(?message, "Received"); Ok(Some(message)) } _ => Err(general_err!( "attempted to feed client request to CredSSP sequence in an unexpected state" - )), + )) + .map_err_as::(), } } @@ -171,11 +176,12 @@ impl<'a> CredsspSequence<'a> { ts_request .encode_ts_request(unfilled_buffer) - .map_err(|e| custom_err!("TsRequest", e))?; + .map_err(|e| custom_err!("TsRequest", e)) + .map_err_as::()?; output.advance(length); - Ok(Written::from_size(length)?) + Ok(Written::from_size(length).map_err_as::()?) } else { Ok(Written::Nothing) } diff --git a/crates/ironrdp-acceptor/src/finalization.rs b/crates/ironrdp-acceptor/src/finalization.rs index 3ad262a9d8..758ec9f761 100644 --- a/crates/ironrdp-acceptor/src/finalization.rs +++ b/crates/ironrdp-acceptor/src/finalization.rs @@ -1,5 +1,5 @@ use ironrdp_connector::{ - ConnectorError, ConnectorErrorExt as _, ConnectorResult, MonotonicInstant, Sequence, State, Written, + MonotonicInstant, Sequence, SequenceError, SequenceErrorExt as _, SequenceResult, State, Written, }; use ironrdp_core::WriteBuf; use ironrdp_pdu::rdp; @@ -85,7 +85,7 @@ impl Sequence for FinalizationSequence { input: &[u8], _received_at: Option, output: &mut WriteBuf, - ) -> ConnectorResult { + ) -> SequenceResult { let (written, next_state) = match core::mem::take(&mut self.state) { FinalizationState::WaitSynchronize => { let synchronize = decode_share_control(input); @@ -230,12 +230,12 @@ fn create_font_map() -> rdp::headers::ShareDataPdu { rdp::headers::ShareDataPdu::FontMap(rdp::finalization_messages::FontPdu::default()) } -fn decode_share_control(input: &[u8]) -> ConnectorResult { +fn decode_share_control(input: &[u8]) -> SequenceResult { let data_request = ironrdp_core::decode::>>(input) - .map_err(ConnectorError::decode) + .map_err(SequenceError::decode) .map(|p| p.0)?; let share_control = ironrdp_core::decode::(data_request.user_data.as_ref()) - .map_err(ConnectorError::decode)?; + .map_err(SequenceError::decode)?; Ok(share_control) } diff --git a/crates/ironrdp-acceptor/src/lib.rs b/crates/ironrdp-acceptor/src/lib.rs index a8a709687e..69644d7892 100644 --- a/crates/ironrdp-acceptor/src/lib.rs +++ b/crates/ironrdp-acceptor/src/lib.rs @@ -4,7 +4,7 @@ use ironrdp_async::{Framed, FramedRead, FramedWrite, NetworkClient, StreamWrapper, single_sequence_step}; use ironrdp_connector::sspi::credssp::EarlyUserAuthResult; use ironrdp_connector::sspi::{AuthIdentity, KerberosServerConfig, Username}; -use ironrdp_connector::{ConnectorResult, ServerName, custom_err, general_err}; +use ironrdp_connector::{ConnectorErrorKind, ConnectorResult, ResultExt as _, ServerName, custom_err, general_err}; use ironrdp_core::WriteBuf; use tracing::{debug, instrument, trace}; @@ -47,7 +47,9 @@ where return Ok(result); } - single_sequence_step(&mut framed, acceptor, &mut buf).await?; + single_sequence_step(&mut framed, acceptor, &mut buf) + .await + .map_err_as::()?; } } @@ -94,7 +96,9 @@ where if let Some(result) = acceptor.get_result() { return Ok((framed, result)); } - single_sequence_step(&mut framed, acceptor, &mut buf).await?; + single_sequence_step(&mut framed, acceptor, &mut buf) + .await + .map_err_as::()?; } } @@ -140,12 +144,14 @@ where buf.clear(); result .to_buffer(&mut *buf) - .map_err(|e| ironrdp_connector::custom_err!("to_buffer", e))?; + .map_err(|e| ironrdp_connector::custom_err!("to_buffer", e)) + .map_err_as::()?; let response = &buf[..result.buffer_len()]; framed .write_all(response) .await - .map_err(|e| ironrdp_connector::custom_err!("write all", e))?; + .map_err(|e| ironrdp_connector::custom_err!("write all", e)) + .map_err_as::()?; } result?; @@ -170,8 +176,11 @@ where let creds = acceptor .creds .as_ref() - .ok_or_else(|| general_err!("no credentials while doing credssp"))?; - let username = Username::new(&creds.username, None).map_err(|e| custom_err!("invalid username", e))?; + .ok_or_else(|| general_err!("no credentials while doing credssp")) + .map_err_as::()?; + let username = Username::new(&creds.username, None) + .map_err(|e| custom_err!("invalid username", e)) + .map_err_as::()?; let identity = AuthIdentity { username, password: creds.password.clone().into(), @@ -194,7 +203,8 @@ where let pdu = framed .read_by_hint(next_pdu_hint) .await - .map_err(|e| ironrdp_connector::custom_err!("read frame by hint", e))?; + .map_err(|e| ironrdp_connector::custom_err!("read frame by hint", e)) + .map_err_as::()?; trace!(length = pdu.len(), "PDU received"); @@ -216,7 +226,8 @@ where framed .write_all(response) .await - .map_err(|e| ironrdp_connector::custom_err!("write all", e))?; + .map_err(|e| ironrdp_connector::custom_err!("write all", e)) + .map_err_as::()?; } } diff --git a/crates/ironrdp-acceptor/src/util.rs b/crates/ironrdp-acceptor/src/util.rs index 4ed6aa0fa9..5ab063ee10 100644 --- a/crates/ironrdp-acceptor/src/util.rs +++ b/crates/ironrdp-acceptor/src/util.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use ironrdp_connector::{ConnectorError, ConnectorErrorExt as _, ConnectorResult}; +use ironrdp_connector::{SequenceError, SequenceErrorExt as _, SequenceResult}; use ironrdp_core::{Encode, WriteBuf, encode_vec}; use ironrdp_pdu::rdp; use ironrdp_pdu::x224::X224; @@ -10,11 +10,11 @@ pub(crate) fn encode_send_data_indication( channel_id: u16, user_msg: &T, buf: &mut WriteBuf, -) -> ConnectorResult +) -> SequenceResult where T: Encode, { - let user_data = encode_vec(user_msg).map_err(ConnectorError::encode)?; + let user_data = encode_vec(user_msg).map_err(SequenceError::encode)?; let pdu = ironrdp_pdu::mcs::SendDataIndication { initiator_id, @@ -22,7 +22,7 @@ where user_data: Cow::Owned(user_data), }; - let written = ironrdp_core::encode_buf(&X224(pdu), buf).map_err(ConnectorError::encode)?; + let written = ironrdp_core::encode_buf(&X224(pdu), buf).map_err(SequenceError::encode)?; Ok(written) } diff --git a/crates/ironrdp-activex/src/control.rs b/crates/ironrdp-activex/src/control.rs index fece544623..9995419d07 100644 --- a/crates/ironrdp-activex/src/control.rs +++ b/crates/ironrdp-activex/src/control.rs @@ -19,7 +19,7 @@ use ironrdp_client::rdp::{ }; use ironrdp_cliprdr::backend::{ClipboardMessage, ClipboardMessageProxy}; use ironrdp_cliprdr_native::WinClipboard; -use ironrdp_connector::{ConnectorError, ConnectorErrorKind, Credentials}; +use ironrdp_connector::{ConnectorError, ConnectorErrorKind, Credentials, SequenceErrorKind}; use ironrdp_core::{DecodeError, DecodeErrorKind, ReadCursor, encode_vec}; use ironrdp_input::{Database as InputDatabase, MouseButton, MousePosition, Operation, Scancode, WheelRotations}; use ironrdp_pdu::PduResult; @@ -639,14 +639,17 @@ fn certificate_prompt_enabled( fn trace_connection_failure(error: &ConnectorError) { let category = match error.kind() { - ConnectorErrorKind::Encode(_) => "Encode", - ConnectorErrorKind::Decode(_) => "Decode", + ConnectorErrorKind::Sequence(seq_err) => match seq_err.kind() { + SequenceErrorKind::Encode(_) => "Encode", + SequenceErrorKind::Decode(_) => "Decode", + SequenceErrorKind::Reason(_) => "Reason", + SequenceErrorKind::General => "General", + SequenceErrorKind::Custom => "Custom", + SequenceErrorKind::Negotiation(_) => "Negotiation", + _ => "Sequence", + }, ConnectorErrorKind::Credssp(_) => "CredSsp", - ConnectorErrorKind::Reason(_) => "Reason", ConnectorErrorKind::AccessDenied => "AccessDenied", - ConnectorErrorKind::General => "General", - ConnectorErrorKind::Custom => "Custom", - ConnectorErrorKind::Negotiation(_) => "Negotiation", _ => "Unknown", }; let location = error.location(); @@ -760,13 +763,18 @@ impl DisconnectInfo { fn from_connection_failure(error: &ConnectorError) -> Self { let description = match error.kind() { - ConnectorErrorKind::Encode(_) => "The RDP client could not encode a protocol message.", - ConnectorErrorKind::Decode(_) => "The RDP client received an invalid protocol message.", + ConnectorErrorKind::Sequence(seq_err) => match seq_err.kind() { + SequenceErrorKind::Encode(_) => "The RDP client could not encode a protocol message.", + SequenceErrorKind::Decode(_) => "The RDP client received an invalid protocol message.", + SequenceErrorKind::Reason(_) => "The RDP connection ended with a protocol reason.", + SequenceErrorKind::General | SequenceErrorKind::Custom => { + "The RDP client encountered an internal error." + } + SequenceErrorKind::Negotiation(_) => "RDP security negotiation failed.", + _ => "The RDP client encountered an unknown error.", + }, ConnectorErrorKind::Credssp(_) => "CredSSP authentication failed.", - ConnectorErrorKind::Reason(_) => "The RDP connection ended with a protocol reason.", ConnectorErrorKind::AccessDenied => "The RDP server denied access.", - ConnectorErrorKind::General | ConnectorErrorKind::Custom => "The RDP client encountered an internal error.", - ConnectorErrorKind::Negotiation(_) => "RDP security negotiation failed.", _ => "The RDP client encountered an unknown error.", }; Self { @@ -16408,7 +16416,13 @@ mod tests { let _ = std::fs::remove_file(&trace_path); let trace_guard = TestHostTracePath::install(trace_path.clone()); - trace_connection_failure(&ConnectorError::new("must not be traced", ConnectorErrorKind::Custom)); + trace_connection_failure(&ConnectorError::new( + "must not be traced", + ConnectorErrorKind::Sequence(ironrdp_connector::SequenceError::new( + "must not be traced", + SequenceErrorKind::Custom, + )), + )); drop(trace_guard); let trace = std::fs::read_to_string(&trace_path).expect("connection failure trace must be written"); let _ = std::fs::remove_file(trace_path); diff --git a/crates/ironrdp-async/src/connector.rs b/crates/ironrdp-async/src/connector.rs index 6cea284d5a..5172af12f6 100644 --- a/crates/ironrdp-async/src/connector.rs +++ b/crates/ironrdp-async/src/connector.rs @@ -2,8 +2,8 @@ use ironrdp_connector::credssp::{CredsspProcessGenerator, CredsspSequence, Kerbe use ironrdp_connector::sspi::credssp::ClientState; use ironrdp_connector::sspi::generator::GeneratorState; use ironrdp_connector::{ - ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult, ServerName, State as _, - general_err, + ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorErrorKind, ConnectorResult, + ResultExt as _, ServerName, State as _, general_err, }; use ironrdp_core::WriteBuf; use tracing::{debug, info, instrument, trace}; @@ -24,7 +24,9 @@ where info!("Begin connection procedure"); while !connector.should_perform_security_upgrade() { - single_sequence_step(framed, connector, &mut buf).await?; + single_sequence_step(framed, connector, &mut buf) + .await + .map_err_as::()?; } Ok(ShouldUpgrade) @@ -86,17 +88,22 @@ where // using `ClientConnector::complete_multitransport()` instead of // calling `connect_finalize`. buf.clear(); - let written = connector.skip_multitransport(&mut buf)?; + let written = connector + .skip_multitransport(&mut buf) + .map_err_as::()?; if written.size().is_some() { framed .write_all(buf.filled()) .await - .map_err(|e| ironrdp_connector::custom_err!("write all", e))?; + .map_err(|e| ironrdp_connector::custom_err!("write all", e)) + .map_err_as::()?; } continue; } - single_sequence_step(framed, &mut connector, &mut buf).await?; + single_sequence_step(framed, &mut connector, &mut buf) + .await + .map_err_as::()?; if let ClientConnectorState::Connected { result } = connector.state { break result; @@ -121,8 +128,7 @@ async fn resolve_generator( state = generator.resume(Ok(response)); } GeneratorState::Completed(client_state) => { - break client_state - .map_err(|e| ConnectorError::new("CredSSP", ironrdp_connector::ConnectorErrorKind::Credssp(e))); + break client_state.map_err(|e| ConnectorError::new("CredSSP", ConnectorErrorKind::Credssp(e))); } } } @@ -160,7 +166,8 @@ where framed .write_all(response) .await - .map_err(|e| ironrdp_connector::custom_err!("write all", e))?; + .map_err(|e| ironrdp_connector::custom_err!("write all", e)) + .map_err_as::()?; } let Some(next_pdu_hint) = sequence.next_pdu_hint() else { @@ -172,7 +179,8 @@ where let pdu = framed .read_by_hint(next_pdu_hint) .await - .map_err(|e| ironrdp_connector::custom_err!("read frame by hint", e))?; + .map_err(|e| ironrdp_connector::custom_err!("read frame by hint", e)) + .map_err_as::()?; trace!(length = pdu.len(), "PDU received"); @@ -204,7 +212,10 @@ where let selected_protocol = match connector.state { ClientConnectorState::Credssp { selected_protocol, .. } => selected_protocol, - _ => return Err(general_err!("invalid connector state for CredSSP sequence")), + _ => { + return Err(general_err!("invalid connector state for CredSSP sequence")) + .map_err_as::(); + } }; debug!(connector.state = connector.state.name(), "Begin CredSSP"); diff --git a/crates/ironrdp-async/src/framed.rs b/crates/ironrdp-async/src/framed.rs index 774858d988..8a3a220c4f 100644 --- a/crates/ironrdp-async/src/framed.rs +++ b/crates/ironrdp-async/src/framed.rs @@ -2,7 +2,7 @@ use std::io; use bytes::{Bytes, BytesMut}; use ironrdp_connector::MonotonicInstant; -use ironrdp_connector::{ConnectorResult, Sequence, Written}; +use ironrdp_connector::{Sequence, SequenceResult, Written}; use ironrdp_core::WriteBuf; use ironrdp_pdu::PduHint; use tracing::{debug, trace}; @@ -245,7 +245,7 @@ pub async fn single_sequence_step( framed: &mut Framed, sequence: &mut dyn Sequence, buf: &mut WriteBuf, -) -> ConnectorResult<()> +) -> SequenceResult<()> where S: FramedWrite + FramedRead, { @@ -258,7 +258,7 @@ pub async fn single_sequence_step_read( framed: &mut Framed, sequence: &mut dyn Sequence, buf: &mut WriteBuf, -) -> ConnectorResult +) -> SequenceResult where S: FramedRead, { @@ -288,7 +288,7 @@ async fn single_sequence_step_write( framed: &mut Framed, buf: &mut WriteBuf, written: Written, -) -> ConnectorResult<()> +) -> SequenceResult<()> where S: FramedWrite, { diff --git a/crates/ironrdp-blocking/src/connector.rs b/crates/ironrdp-blocking/src/connector.rs index 0e4e3ad585..5ced00cd43 100644 --- a/crates/ironrdp-blocking/src/connector.rs +++ b/crates/ironrdp-blocking/src/connector.rs @@ -5,8 +5,8 @@ use ironrdp_connector::sspi::credssp::ClientState; use ironrdp_connector::sspi::generator::GeneratorState; use ironrdp_connector::sspi::network_client::NetworkClient; use ironrdp_connector::{ - ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult, Sequence as _, - ServerName, State as _, general_err, + ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorErrorKind, ConnectorResult, + ResultExt as _, Sequence as _, SequenceResult, ServerName, State as _, general_err, }; use ironrdp_core::WriteBuf; use tracing::{debug, info, instrument, trace}; @@ -26,7 +26,7 @@ where info!("Begin connection procedure"); while !connector.should_perform_security_upgrade() { - single_sequence_step(framed, connector, &mut buf)?; + single_sequence_step(framed, connector, &mut buf).map_err_as::()?; } Ok(ShouldUpgrade) @@ -90,16 +90,19 @@ where // using `ClientConnector::complete_multitransport()` instead of // calling `connect_finalize`. buf.clear(); - let written = connector.skip_multitransport(&mut buf)?; + let written = connector + .skip_multitransport(&mut buf) + .map_err_as::()?; if written.size().is_some() { framed .write_all(buf.filled()) - .map_err(|e| ironrdp_connector::custom_err!("write all", e))?; + .map_err(|e| ironrdp_connector::custom_err!("write all", e)) + .map_err_as::()?; } continue; } - single_sequence_step(framed, &mut connector, &mut buf)?; + single_sequence_step(framed, &mut connector, &mut buf).map_err_as::()?; if let ClientConnectorState::Connected { result } = connector.state { break result; @@ -120,14 +123,13 @@ fn resolve_generator( loop { match state { GeneratorState::Suspended(request) => { - let response = network_client.send(&request).map_err(|e| { - ConnectorError::new("network client send", ironrdp_connector::ConnectorErrorKind::Credssp(e)) - })?; + let response = network_client + .send(&request) + .map_err(|e| ConnectorError::new("network client send", ConnectorErrorKind::Credssp(e)))?; state = generator.resume(Ok(response)); } GeneratorState::Completed(client_state) => { - break client_state - .map_err(|e| ConnectorError::new("CredSSP", ironrdp_connector::ConnectorErrorKind::Credssp(e))); + break client_state.map_err(|e| ConnectorError::new("CredSSP", ConnectorErrorKind::Credssp(e))); } } } @@ -150,7 +152,10 @@ where let selected_protocol = match connector.state { ClientConnectorState::Credssp { selected_protocol, .. } => selected_protocol, - _ => return Err(general_err!("invalid connector state for CredSSP sequence")), + _ => { + return Err(general_err!("invalid connector state for CredSSP sequence")) + .map_err_as::(); + } }; let (mut sequence, mut ts_request) = CredsspSequence::init( @@ -176,7 +181,8 @@ where trace!(response_len, "Send response"); framed .write_all(response) - .map_err(|e| ironrdp_connector::custom_err!("write all", e))?; + .map_err(|e| ironrdp_connector::custom_err!("write all", e)) + .map_err_as::()?; } let Some(next_pdu_hint) = sequence.next_pdu_hint() else { @@ -191,7 +197,8 @@ where let pdu = framed .read_by_hint(next_pdu_hint) - .map_err(|e| ironrdp_connector::custom_err!("read frame by hint", e))?; + .map_err(|e| ironrdp_connector::custom_err!("read frame by hint", e)) + .map_err_as::()?; trace!(length = pdu.len(), "PDU received"); @@ -211,7 +218,7 @@ pub fn single_sequence_step( framed: &mut Framed, connector: &mut ClientConnector, buf: &mut WriteBuf, -) -> ConnectorResult<()> +) -> SequenceResult<()> where S: Read + Write, { diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index 52927ae978..338c93b729 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -7,7 +7,9 @@ use std::sync::Arc; #[cfg(feature = "clipboard")] pub use ironrdp_cliprdr::backend::CliprdrBackendFactory; use ironrdp_connector::connection_activation::ConnectionActivationState; -use ironrdp_connector::{ConnectionResult, ConnectorResult}; +use ironrdp_connector::{ + ConnectionResult, ConnectorError, ConnectorErrorKind, ConnectorResult, ResultExt as _, SequenceErrorExt as _, +}; use ironrdp_core::WriteBuf; use ironrdp_displaycontrol::client::DisplayControlClient; use ironrdp_displaycontrol::pdu::MonitorLayoutEntry; @@ -107,7 +109,7 @@ pub enum RdpOutputEvent { width: NonZeroU16, height: NonZeroU16, }, - ConnectionFailure(ironrdp_connector::ConnectorError), + ConnectionFailure(ConnectorError), PointerDefault, PointerHidden, PointerPosition { @@ -527,12 +529,13 @@ impl RdpClient { _win_clipboard = Some(win_cb); } Err(e) => { + let error = ironrdp_connector::map_sequence_error(ironrdp_connector::custom_err!( + "Windows clipboard initialization", + e + )); let _ = self .output_event_sender - .send(RdpOutputEvent::ConnectionFailure(ironrdp_connector::custom_err!( - "Windows clipboard initialization", - e - ))) + .send(RdpOutputEvent::ConnectionFailure(error)) .await; return; } @@ -1044,7 +1047,8 @@ fn build_rdpdr_channel( let (backend, initial_drives) = factory .build_rdpdr_backend() - .map_err(|error| ironrdp_connector::custom_err!("build RDPDR backend", RdpdrBackendBuildError(error)))? + .map_err(|error| ironrdp_connector::custom_err!("build RDPDR backend", RdpdrBackendBuildError(error))) + .map_err_as::()? .into_parts(); #[cfg(feature = "smartcard")] @@ -1422,12 +1426,14 @@ async fn connect_direct( let dest = config.destination.to_string(); let stream = TcpStream::connect(&dest) .await - .map_err(|e| ironrdp_connector::custom_err!("TCP connect", e))?; + .map_err(|e| ironrdp_connector::custom_err!("TCP connect", e)) + .map_err_as::()?; #[cfg(feature = "vmconnect")] let pcb_deadline = tokio::time::Instant::now() + ironrdp_vmconnect::PCB_TRANSMIT_DEADLINE; let client_addr = stream .local_addr() - .map_err(|e| ironrdp_connector::custom_err!("get socket local address", e))?; + .map_err(|e| ironrdp_connector::custom_err!("get socket local address", e)) + .map_err_as::()?; let framed = ironrdp_tokio::TokioFramed::new(stream); let connector = build_connector( @@ -1472,7 +1478,8 @@ async fn connect_named_pipe( .read(true) .write(true) .open(&path) - .map_err(|e| ironrdp_connector::custom_err!("named pipe connect", e))?; + .map_err(|e| ironrdp_connector::custom_err!("named pipe connect", e)) + .map_err_as::()?; // Named pipes have no socket address; use a dummy loopback address for Client Info. let client_addr = SocketAddr::from(([127, 0, 0, 1], 0)); @@ -1516,7 +1523,8 @@ async fn connect_gateway( config.destination.port(), ) .await - .map_err(|e| ironrdp_connector::custom_err!("GW connect", e))?; + .map_err(|e| ironrdp_connector::custom_err!("GW connect", e)) + .map_err_as::()?; let framed = ironrdp_tokio::TokioFramed::new(gw_stream); @@ -1550,22 +1558,27 @@ async fn connect_rdcleanpath_transport( let hostname = rdcp .url .host_str() - .ok_or_else(|| ironrdp_connector::general_err!("host missing from the URL"))?; + .ok_or_else(|| ironrdp_connector::general_err!("host missing from the URL")) + .map_err_as::()?; let port = rdcp.url.port_or_known_default().unwrap_or(443); let socket = TcpStream::connect((hostname, port)) .await - .map_err(|e| ironrdp_connector::custom_err!("TCP connect", e))?; + .map_err(|e| ironrdp_connector::custom_err!("TCP connect", e)) + .map_err_as::()?; socket .set_nodelay(true) - .map_err(|e| ironrdp_connector::custom_err!("set TCP_NODELAY", e))?; + .map_err(|e| ironrdp_connector::custom_err!("set TCP_NODELAY", e)) + .map_err_as::()?; let client_addr = socket .local_addr() - .map_err(|e| ironrdp_connector::custom_err!("get socket local address", e))?; + .map_err(|e| ironrdp_connector::custom_err!("get socket local address", e)) + .map_err_as::()?; let (ws, _) = tokio_tungstenite::client_async_tls(rdcp.url.as_str(), socket) .await - .map_err(|e| ironrdp_connector::custom_err!("WS connect", e))?; + .map_err(|e| ironrdp_connector::custom_err!("WS connect", e)) + .map_err_as::()?; let ws = crate::ws::websocket_compat(ws); let mut framed = ironrdp_tokio::TokioFramed::new(ws); @@ -1674,7 +1687,9 @@ where ) .await }; - let (tls_stream, tls_cert) = tls_upgrade.map_err(|e| ironrdp_connector::custom_err!("TLS upgrade", e))?; + let (tls_stream, tls_cert) = tls_upgrade + .map_err(|e| ironrdp_connector::custom_err!("TLS upgrade", e)) + .map_err_as::()?; let upgraded = ironrdp_tokio::mark_as_upgraded(should_upgrade, &mut connector); @@ -1682,7 +1697,8 @@ where let mut upgraded_framed = ironrdp_tokio::TokioFramed::new_with_leftover(erased_stream, leftover_bytes); let server_public_key = ironrdp_tls::extract_tls_server_public_key(&tls_cert) - .ok_or_else(|| ironrdp_connector::general_err!("unable to extract tls server public key"))? + .ok_or_else(|| ironrdp_connector::general_err!("unable to extract tls server public key")) + .map_err_as::()? .to_owned(); let connection_result = ironrdp_tokio::connect_finalize( @@ -1712,10 +1728,12 @@ where { let vm_id = config .vm_id() - .ok_or_else(|| ironrdp_connector::general_err!("vmconnect path requires a VM ID"))?; + .ok_or_else(|| ironrdp_connector::general_err!("vmconnect path requires a VM ID")) + .map_err_as::()?; let mode = config .vmconnect_mode() - .ok_or_else(|| ironrdp_connector::general_err!("vmconnect path requires a console mode"))?; + .ok_or_else(|| ironrdp_connector::general_err!("vmconnect path requires a console mode")) + .map_err_as::()?; // MS-RDPEPS: complete PCB within ten seconds of TCP connection creation. let pcb_sent = tokio::time::timeout_at( @@ -1723,20 +1741,23 @@ where ironrdp_vmconnect::send_preconnection_blob(&mut framed, vm_id, mode), ) .await - .map_err(|_| ironrdp_connector::general_err!("timed out writing preconnection blob"))??; + .map_err(|_| ironrdp_connector::general_err!("timed out writing preconnection blob")) + .map_err_as::()??; debug!("TLS upgrade"); let (initial_stream, leftover_bytes) = framed.into_inner(); let (tls_stream, tls_cert) = ironrdp_tls::upgrade(initial_stream, config.destination.name()) .await - .map_err(|e| ironrdp_connector::custom_err!("TLS upgrade", e))?; + .map_err(|e| ironrdp_connector::custom_err!("TLS upgrade", e)) + .map_err_as::()?; let erased_stream: Box = Box::new(tls_stream); let mut upgraded_framed = ironrdp_tokio::TokioFramed::new_with_leftover(erased_stream, leftover_bytes); let server_public_key = ironrdp_tls::extract_tls_server_public_key(&tls_cert) - .ok_or_else(|| ironrdp_connector::general_err!("unable to extract tls server public key"))? + .ok_or_else(|| ironrdp_connector::general_err!("unable to extract tls server public key")) + .map_err_as::()? .to_owned(); let mut network_client = ReqwestNetworkClient::new(); @@ -1829,7 +1850,8 @@ where if let Some((vm_id, mode)) = vmconnect { let pcb_payload = ironrdp_vmconnect::preconnection_blob_payload(&vm_id, mode)?; ironrdp_rdcleanpath::RDCleanPathPdu::new_vmconnect_request(destination, proxy_auth_token, pcb_payload) - .map_err(|e| ironrdp_connector::custom_err!("build VMConnect RDCleanPath request", e))? + .map_err(|e| ironrdp_connector::custom_err!("build VMConnect RDCleanPath request", e)) + .map_err_as::()? } else { build_ordinary_rdcleanpath_request(connector, &mut buf, destination, proxy_auth_token)? } @@ -1846,33 +1868,39 @@ where ); let rdcleanpath_req = rdcleanpath_req .to_der() - .map_err(|e| ironrdp_connector::custom_err!("encode RDCleanPath request", e))?; + .map_err(|e| ironrdp_connector::custom_err!("encode RDCleanPath request", e)) + .map_err_as::()?; framed .write_all(&rdcleanpath_req) .await - .map_err(|e| ironrdp_connector::custom_err!("couldn't write RDCleanPath request", e))?; + .map_err(|e| ironrdp_connector::custom_err!("couldn't write RDCleanPath request", e)) + .map_err_as::()?; } { let rdcleanpath_res = framed .read_by_hint(&RDCLEANPATH_HINT) .await - .map_err(|e| ironrdp_connector::custom_err!("read RDCleanPath response", e))?; + .map_err(|e| ironrdp_connector::custom_err!("read RDCleanPath response", e)) + .map_err_as::()?; let rdcleanpath_res = ironrdp_rdcleanpath::RDCleanPathPdu::from_der(&rdcleanpath_res) - .map_err(|e| ironrdp_connector::custom_err!("decode RDCleanPath response", e))?; + .map_err(|e| ironrdp_connector::custom_err!("decode RDCleanPath response", e)) + .map_err_as::()?; debug!(message = ?rdcleanpath_res, "Received RDCleanPath PDU"); let (x224_connection_response, server_cert_chain) = match ( request_vmconnect, rdcleanpath_res .into_message() - .map_err(|e| ironrdp_connector::custom_err!("invalid RDCleanPath PDU", e))?, + .map_err(|e| ironrdp_connector::custom_err!("invalid RDCleanPath PDU", e)) + .map_err_as::()?, ) { (_, ironrdp_rdcleanpath::RDCleanPathMessage::Request { .. }) | (_, ironrdp_rdcleanpath::RDCleanPathMessage::VmConnectRequest { .. }) => { return Err(ironrdp_connector::general_err!( "received unexpected RDCleanPath type (request)" - )); + )) + .map_err_as::(); } ( false, @@ -1892,15 +1920,18 @@ where (true, ironrdp_rdcleanpath::RDCleanPathMessage::Response { .. }) => { return Err(ironrdp_connector::general_err!( "response from RDCleanPath includes X.224 for a VMConnect request" - )); + )) + .map_err_as::(); } (false, ironrdp_rdcleanpath::RDCleanPathMessage::VmConnectResponse { .. }) => { return Err(ironrdp_connector::general_err!( "response from RDCleanPath is missing X.224 for an ordinary request" - )); + )) + .map_err_as::(); } (_, ironrdp_rdcleanpath::RDCleanPathMessage::GeneralErr(error)) => { - return Err(ironrdp_connector::custom_err!("received RDCleanPath error", error)); + return Err(ironrdp_connector::custom_err!("received RDCleanPath error", error)) + .map_err_as::(); } ( _, @@ -1914,32 +1945,37 @@ where { if let ironrdp_pdu::nego::ConnectionConfirm::Failure { code } = x224_confirm.0 { let negotiation_failure = ironrdp_connector::NegotiationFailure::from(code); - return Err(ironrdp_connector::ConnectorError::new( + return Err(ironrdp_connector::SequenceError::negotiation( "RDP negotiation failed", - ironrdp_connector::ConnectorErrorKind::Negotiation(negotiation_failure), - )); + negotiation_failure, + )) + .map_err_as::(); } } return Err(ironrdp_connector::general_err!( "received RDCleanPath negotiation error" - )); + )) + .map_err_as::(); } }; let server_cert = server_cert_chain .into_iter() .next() - .ok_or_else(|| ironrdp_connector::general_err!("server cert chain missing from rdcleanpath response"))?; + .ok_or_else(|| ironrdp_connector::general_err!("server cert chain missing from rdcleanpath response")) + .map_err_as::()?; let cert = x509_cert::Certificate::from_der(server_cert.as_bytes()) - .map_err(|e| ironrdp_connector::custom_err!("server cert decode", e))?; + .map_err(|e| ironrdp_connector::custom_err!("server cert decode", e)) + .map_err_as::()?; let server_public_key = cert .tbs_certificate() .subject_public_key_info() .subject_public_key .as_bytes() - .ok_or_else(|| ironrdp_connector::general_err!("subject public key BIT STRING is not aligned"))? + .ok_or_else(|| ironrdp_connector::general_err!("subject public key BIT STRING is not aligned")) + .map_err_as::()? .to_owned(); let upgraded = match x224_connection_response { @@ -1948,12 +1984,15 @@ where else { return Err(ironrdp_connector::general_err!( "invalid connector state (wait confirm)" - )); + )) + .map_err_as::(); }; debug_assert!(connector.next_pdu_hint().is_some()); buf.clear(); - let written = connector.step(x224_connection_response.as_bytes(), None, &mut buf)?; + let written = connector + .step(x224_connection_response.as_bytes(), None, &mut buf) + .map_err_as::()?; debug_assert!(written.is_nothing()); let should_upgrade = ironrdp_tokio::skip_connect_begin(connector); @@ -1978,7 +2017,8 @@ where let _ = (framed, network_client, server_name, kerberos_config); return Err(ironrdp_connector::general_err!( "vmconnect response from RDCleanPath requires the vmconnect feature" - )); + )) + .map_err_as::(); } } }; @@ -1998,15 +2038,17 @@ fn build_ordinary_rdcleanpath_request( let ironrdp_connector::ClientConnectorState::ConnectionInitiationSendRequest = connector.state else { return Err(ironrdp_connector::general_err!( "invalid connector state (send request)" - )); + )) + .map_err_as::(); }; debug_assert!(connector.next_pdu_hint().is_none()); - let written = connector.step_no_input(buf)?; + let written = connector.step_no_input(buf).map_err_as::()?; let x224_pdu_len = written.size().expect("written size"); debug_assert_eq!(x224_pdu_len, buf.filled_len()); let x224_pdu = buf.filled().to_vec(); ironrdp_rdcleanpath::RDCleanPathPdu::new_request(x224_pdu, destination, proxy_auth_token, None) .map_err(|e| ironrdp_connector::custom_err!("new RDCleanPath request", e)) + .map_err_as::() } // ── Active session ──────────────────────────────────────────────────────────── diff --git a/crates/ironrdp-connector/src/channel_connection.rs b/crates/ironrdp-connector/src/channel_connection.rs index abf55a1d06..6527b09c97 100644 --- a/crates/ironrdp-connector/src/channel_connection.rs +++ b/crates/ironrdp-connector/src/channel_connection.rs @@ -7,7 +7,7 @@ use ironrdp_pdu::{PduHint, mcs}; use tracing::{debug, warn}; use crate::{ - ConnectorError, ConnectorErrorExt as _, ConnectorResult, MonotonicInstant, Sequence, State, Written, general_err, + MonotonicInstant, Sequence, SequenceError, SequenceErrorExt as _, SequenceResult, State, Written, general_err, reason_err, }; @@ -100,7 +100,7 @@ impl Sequence for ChannelConnectionSequence { input: &[u8], _received_at: Option, output: &mut WriteBuf, - ) -> ConnectorResult { + ) -> SequenceResult { let (written, next_state) = match mem::take(&mut self.state) { ChannelConnectionState::Consumed => { return Err(general_err!( @@ -117,7 +117,7 @@ impl Sequence for ChannelConnectionSequence { debug!(message = ?erect_domain_request, "Send"); let written = - ironrdp_core::encode_buf(&X224(erect_domain_request), output).map_err(ConnectorError::encode)?; + ironrdp_core::encode_buf(&X224(erect_domain_request), output).map_err(SequenceError::encode)?; ( Written::from_size(written)?, @@ -131,7 +131,7 @@ impl Sequence for ChannelConnectionSequence { debug!(message = ?attach_user_request, "Send"); let written = - ironrdp_core::encode_buf(&X224(attach_user_request), output).map_err(ConnectorError::encode)?; + ironrdp_core::encode_buf(&X224(attach_user_request), output).map_err(SequenceError::encode)?; ( Written::from_size(written)?, @@ -141,7 +141,7 @@ impl Sequence for ChannelConnectionSequence { ChannelConnectionState::WaitAttachUserConfirm => { let attach_user_confirm = ironrdp_core::decode::>(input) - .map_err(ConnectorError::decode) + .map_err(SequenceError::decode) .map(|p| p.0)?; let user_channel_id = attach_user_confirm.initiator_id; @@ -187,8 +187,8 @@ impl Sequence for ChannelConnectionSequence { debug!(message = ?channel_join_request, "Send"); - let written = ironrdp_core::encode_buf(&X224(channel_join_request), output) - .map_err(ConnectorError::encode)?; + let written = + ironrdp_core::encode_buf(&X224(channel_join_request), output).map_err(SequenceError::encode)?; total_written = total_written.checked_add(written).expect("small join request PDUs"); } @@ -207,7 +207,7 @@ impl Sequence for ChannelConnectionSequence { mut remaining_channel_ids, } => { let channel_join_confirm = ironrdp_core::decode::>(input) - .map_err(ConnectorError::decode) + .map_err(SequenceError::decode) .map(|p| p.0)?; debug!(message = ?channel_join_confirm, "Received"); diff --git a/crates/ironrdp-connector/src/connection.rs b/crates/ironrdp-connector/src/connection.rs index c8014f1e64..105e5c472a 100644 --- a/crates/ironrdp-connector/src/connection.rs +++ b/crates/ironrdp-connector/src/connection.rs @@ -18,8 +18,8 @@ use crate::connection_activation::{ }; use crate::license_exchange::{LicenseExchangeSequence, NoopLicenseCache}; use crate::{ - Config, ConnectorError, ConnectorErrorExt as _, ConnectorErrorKind, ConnectorResult, DesktopSize, MonotonicInstant, - NegotiationFailure, Sequence, State, Written, encode_x224_packet, general_err, reason_err, + Config, DesktopSize, MonotonicInstant, NegotiationFailure, Sequence, SequenceError, SequenceErrorExt as _, + SequenceResult, State, Written, encode_x224_packet, general_err, reason_err, }; /// Maximum number of `Initiate Multitransport Request` PDUs the server is @@ -383,7 +383,7 @@ impl ClientConnector { &mut self, security_protocol: nego::SecurityProtocol, output: &mut WriteBuf, - ) -> ConnectorResult { + ) -> SequenceResult { if !matches!(self.state, ClientConnectorState::ConnectionInitiationSendRequest) { return Err(reason_err!("Initiation", "connection initiation has already started")); } @@ -400,7 +400,7 @@ impl ClientConnector { self.encode_connection_request(security_protocol, output) } - fn enabled_security_protocols(&self) -> ConnectorResult { + fn enabled_security_protocols(&self) -> SequenceResult { let mut security_protocol = nego::SecurityProtocol::empty(); if self.config.enable_tls { @@ -432,7 +432,7 @@ impl ClientConnector { &mut self, security_protocol: nego::SecurityProtocol, output: &mut WriteBuf, - ) -> ConnectorResult { + ) -> SequenceResult { let connection_request = nego::ConnectionRequest { nego_data: self.config.request_data.clone().or_else(|| { self.config @@ -447,7 +447,7 @@ impl ClientConnector { debug!(message = ?connection_request, "Send"); - let written = ironrdp_core::encode_buf(&X224(connection_request), output).map_err(ConnectorError::encode)?; + let written = ironrdp_core::encode_buf(&X224(connection_request), output).map_err(SequenceError::encode)?; self.state = ClientConnectorState::ConnectionInitiationWaitConfirm { requested_protocol: security_protocol, }; @@ -631,7 +631,7 @@ impl ClientConnector { &mut self, result: MultitransportResult, output: &mut WriteBuf, - ) -> ConnectorResult { + ) -> SequenceResult { self.respond_to_multitransport("complete_multitransport", result, output) } @@ -644,7 +644,7 @@ impl ClientConnector { /// /// Returns an error if the connector is not in `MultitransportPending` /// state. - pub fn skip_multitransport(&mut self, output: &mut WriteBuf) -> ConnectorResult { + pub fn skip_multitransport(&mut self, output: &mut WriteBuf) -> SequenceResult { self.respond_to_multitransport( "skip_multitransport", MultitransportResult::Failure(rdp::multitransport::MultitransportResponsePdu::E_ABORT), @@ -669,7 +669,7 @@ impl ClientConnector { /// /// Borrows rather than consuming, so the caller can resolve this while the /// connector is still in a state it can act on. - fn multitransport_response_channel(&self, result: &MultitransportResult) -> ConnectorResult> { + fn multitransport_response_channel(&self, result: &MultitransportResult) -> SequenceResult> { let ClientConnectorState::MultitransportPending { message_channel_id, soft_sync, @@ -711,7 +711,7 @@ impl ClientConnector { caller: &str, result: MultitransportResult, output: &mut WriteBuf, - ) -> ConnectorResult { + ) -> SequenceResult { // The state is read, never taken. Everything this needs is a scalar, so // nothing has to be moved out of `self.state`, and the transition at the // end is the only mutation. That makes state preservation structural: an @@ -779,7 +779,7 @@ impl ClientConnector { message_channel_id: u16, user_channel_id: u16, output: &mut WriteBuf, - ) -> ConnectorResult { + ) -> SequenceResult { use ironrdp_pdu::rdp::autodetect::{ AutoDetectRequest, AutoDetectResponse, AutoDetectRspPdu, BW_RESULTS_CONNECT_TIME, BW_START_CONNECT_TIME, BW_STOP_CONNECT_TIME, @@ -939,7 +939,7 @@ fn advance_licensing_exchange( input: &[u8], received_at: Option, output: &mut WriteBuf, -) -> ConnectorResult<(Written, ClientConnectorState)> { +) -> SequenceResult<(Written, ClientConnectorState)> { let written = license_exchange.step(input, received_at, output)?; let next_state = if license_exchange.state.is_terminal() { @@ -1008,7 +1008,7 @@ impl Sequence for ClientConnector { input: &[u8], received_at: Option, output: &mut WriteBuf, - ) -> ConnectorResult { + ) -> SequenceResult { let (written, next_state) = match mem::take(&mut self.state) { // Invalid state ClientConnectorState::Consumed => { @@ -1025,7 +1025,7 @@ impl Sequence for ClientConnector { } ClientConnectorState::ConnectionInitiationWaitConfirm { requested_protocol } => { let connection_confirm = decode::>(input) - .map_err(ConnectorError::decode) + .map_err(SequenceError::decode) .map(|p| p.0)?; debug!(message = ?connection_confirm, "Received"); @@ -1034,9 +1034,9 @@ impl Sequence for ClientConnector { nego::ConnectionConfirm::Response { flags, protocol } => (flags, protocol), nego::ConnectionConfirm::Failure { code } => { error!(?code, "Received connection failure code"); - return Err(ConnectorError::new( + return Err(SequenceError::negotiation( "negotiation failure", - ConnectorErrorKind::Negotiation(NegotiationFailure::from(code)), + NegotiationFailure::from(code), )); } }; @@ -1106,7 +1106,7 @@ impl Sequence for ClientConnector { )?; let connect_initial = - mcs::ConnectInitial::with_gcc_blocks(client_gcc_blocks).map_err(ConnectorError::decode)?; + mcs::ConnectInitial::with_gcc_blocks(client_gcc_blocks).map_err(SequenceError::decode)?; debug!(message = ?connect_initial, "Send"); @@ -1119,10 +1119,10 @@ impl Sequence for ClientConnector { } ClientConnectorState::BasicSettingsExchangeWaitResponse { connect_initial } => { let x224_payload = decode::>>(input) - .map_err(ConnectorError::decode) + .map_err(SequenceError::decode) .map(|p| p.0)?; let connect_response = - decode::(x224_payload.data.as_ref()).map_err(ConnectorError::decode)?; + decode::(x224_payload.data.as_ref()).map_err(SequenceError::decode)?; debug!(message = ?connect_response, "Received"); @@ -1379,11 +1379,11 @@ impl Sequence for ClientConnector { message_channel_id, requests_seen, } => { - let ctx = mcs::decode_send_data_indication(input).map_err(ConnectorError::decode)?; + let ctx = mcs::decode_send_data_indication(input).map_err(SequenceError::decode)?; if Some(ctx.channel_id) == message_channel_id { let pdu = decode::(ctx.user_data) - .map_err(ConnectorError::decode)?; + .map_err(SequenceError::decode)?; if requests_seen >= MAX_MULTITRANSPORT_REQUESTS { return Err(reason_err!( @@ -1552,8 +1552,8 @@ pub fn encode_send_data_request( channel_id: u16, user_msg: &T, buf: &mut WriteBuf, -) -> ConnectorResult { - let user_data = encode_vec(user_msg).map_err(ConnectorError::encode)?; +) -> SequenceResult { + let user_data = encode_vec(user_msg).map_err(SequenceError::encode)?; let pdu = mcs::SendDataRequest { initiator_id, @@ -1561,7 +1561,7 @@ pub fn encode_send_data_request( user_data: Cow::Owned(user_data), }; - let written = ironrdp_core::encode_buf(&X224(pdu), buf).map_err(ConnectorError::encode)?; + let written = ironrdp_core::encode_buf(&X224(pdu), buf).map_err(SequenceError::encode)?; Ok(written) } @@ -1572,7 +1572,7 @@ fn create_gcc_blocks<'a>( selected_protocol: nego::SecurityProtocol, extended_client_data_supported: bool, static_channels: impl Iterator, -) -> ConnectorResult { +) -> SequenceResult { use ironrdp_pdu::gcc::{ ClientCoreData, ClientCoreOptionalData, ClientEarlyCapabilityFlags, ClientGccBlocks, ClientNetworkData, ClientSecurityData, ColorDepth, EncryptionMethod, HighColorDepth, MonitorOrientation, RdpVersion, diff --git a/crates/ironrdp-connector/src/connection_activation.rs b/crates/ironrdp-connector/src/connection_activation.rs index 672c310e4b..f212d121e0 100644 --- a/crates/ironrdp-connector/src/connection_activation.rs +++ b/crates/ironrdp-connector/src/connection_activation.rs @@ -7,8 +7,8 @@ use ironrdp_pdu::rdp::capability_sets::{ use tracing::{debug, warn}; use crate::{ - Config, ConnectionFinalizationSequence, ConnectorError, ConnectorErrorExt as _, ConnectorResult, DesktopSize, - MonotonicInstant, Sequence, State, Written, general_err, reason_err, + Config, ConnectionFinalizationSequence, DesktopSize, MonotonicInstant, Sequence, SequenceError, + SequenceErrorExt as _, SequenceResult, State, Written, general_err, reason_err, }; /// Represents the Capability Exchange and Connection Finalization phases @@ -130,7 +130,7 @@ impl Sequence for ConnectionActivationSequence { input: &[u8], received_at: Option, output: &mut ironrdp_core::WriteBuf, - ) -> ConnectorResult { + ) -> SequenceResult { let (written, next_state) = match mem::take(&mut self.state) { ConnectionActivationState::Consumed | ConnectionActivationState::Finalized { .. } => { return Err(general_err!( @@ -141,9 +141,9 @@ impl Sequence for ConnectionActivationSequence { debug!("Capabilities Exchange"); let send_data_indication_ctx = - ironrdp_pdu::mcs::decode_send_data_indication(input).map_err(ConnectorError::decode)?; + ironrdp_pdu::mcs::decode_send_data_indication(input).map_err(SequenceError::decode)?; let share_control_ctx = - rdp::headers::decode_share_control(send_data_indication_ctx).map_err(ConnectorError::decode)?; + rdp::headers::decode_share_control(send_data_indication_ctx).map_err(SequenceError::decode)?; debug!(message = ?share_control_ctx.pdu, "Received"); @@ -296,7 +296,7 @@ impl Sequence for ConnectionActivationSequence { client_confirm_active, output, ) - .map_err(ConnectorError::encode)?; + .map_err(SequenceError::encode)?; ( Written::from_size(written)?, @@ -449,7 +449,7 @@ fn remote_app_rail_capability( rail_support_level: RailSupportLevel, server_capability_sets: &[CapabilitySet], window_list: Option<&WindowList>, -) -> ConnectorResult> { +) -> SequenceResult> { if !remote_application_mode { return Ok(None); } @@ -485,7 +485,7 @@ fn create_client_confirm_active( mut server_capability_sets: Vec, desktop_size: DesktopSize, window_list: Option, -) -> ConnectorResult { +) -> SequenceResult { use ironrdp_pdu::rdp::capability_sets::{ BITMAP_CACHE_ENTRIES_NUM, Bitmap, BitmapCache, BitmapDrawingFlags, Brush, CacheDefinition, CacheEntry, ClientConfirmActive, CmdFlags, DemandActive, FrameAcknowledge, GLYPH_CACHE_NUM, General, GeneralExtraFlags, @@ -637,7 +637,7 @@ fn create_client_confirm_active( /// Bitmap Capability Set permits the server to select RDP 6.0 bitmap compression instead. /// /// [MS-RDPBCGR]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/49e7bcb9-a8d7-46f5-987e-46c63c44b2c4 -fn requested_bitmap_color_depth(bitmap: Option<&crate::BitmapConfig>) -> ConnectorResult { +fn requested_bitmap_color_depth(bitmap: Option<&crate::BitmapConfig>) -> SequenceResult { match bitmap.map_or(32, |bitmap| bitmap.color_depth) { 15 => Ok(15), 16 => Ok(16), diff --git a/crates/ironrdp-connector/src/connection_finalization.rs b/crates/ironrdp-connector/src/connection_finalization.rs index 672a1494ac..a882496b41 100644 --- a/crates/ironrdp-connector/src/connection_finalization.rs +++ b/crates/ironrdp-connector/src/connection_finalization.rs @@ -8,7 +8,7 @@ use ironrdp_pdu::rdp::{finalization_messages, server_error_info}; use tracing::{debug, warn}; use crate::{ - ConnectorError, ConnectorErrorExt as _, ConnectorResult, MonotonicInstant, Sequence, State, Written, general_err, + MonotonicInstant, Sequence, SequenceError, SequenceErrorExt as _, SequenceResult, State, Written, general_err, reason_err, }; @@ -93,7 +93,7 @@ impl Sequence for ConnectionFinalizationSequence { input: &[u8], _received_at: Option, output: &mut WriteBuf, - ) -> ConnectorResult { + ) -> SequenceResult { let (written, next_state) = match mem::take(&mut self.state) { ConnectionFinalizationState::Consumed => { return Err(general_err!( @@ -115,7 +115,7 @@ impl Sequence for ConnectionFinalizationSequence { message, output, ) - .map_err(ConnectorError::encode)?; + .map_err(SequenceError::encode)?; ( Written::from_size(written)?, @@ -139,7 +139,7 @@ impl Sequence for ConnectionFinalizationSequence { message, output, ) - .map_err(ConnectorError::encode)?; + .map_err(SequenceError::encode)?; ( Written::from_size(written)?, @@ -163,7 +163,7 @@ impl Sequence for ConnectionFinalizationSequence { message, output, ) - .map_err(ConnectorError::encode)?; + .map_err(SequenceError::encode)?; (Written::from_size(written)?, ConnectionFinalizationState::SendFontList) } @@ -180,7 +180,7 @@ impl Sequence for ConnectionFinalizationSequence { message, output, ) - .map_err(ConnectorError::encode)?; + .map_err(SequenceError::encode)?; ( Written::from_size(written)?, @@ -189,8 +189,8 @@ impl Sequence for ConnectionFinalizationSequence { } ConnectionFinalizationState::WaitForResponse => { - let ctx = ironrdp_pdu::mcs::decode_send_data_indication(input).map_err(ConnectorError::decode)?; - let ctx = ironrdp_pdu::rdp::headers::decode_share_data(ctx).map_err(ConnectorError::decode)?; + let ctx = ironrdp_pdu::mcs::decode_send_data_indication(input).map_err(SequenceError::decode)?; + let ctx = ironrdp_pdu::rdp::headers::decode_share_data(ctx).map_err(SequenceError::decode)?; debug!(message = ?ctx.pdu, "Received"); diff --git a/crates/ironrdp-connector/src/credssp.rs b/crates/ironrdp-connector/src/credssp.rs index 413023260d..0da1a6de8a 100644 --- a/crates/ironrdp-connector/src/credssp.rs +++ b/crates/ironrdp-connector/src/credssp.rs @@ -8,7 +8,8 @@ use sspi::{Secret, Username}; use tracing::debug; use crate::{ - ConnectorError, ConnectorErrorKind, ConnectorResult, Credentials, ServerName, Written, custom_err, general_err, + ConnectorError, ConnectorErrorKind, ConnectorResult, Credentials, ResultExt as _, ServerName, Written, custom_err, + general_err, }; #[derive(Debug, Clone)] @@ -22,7 +23,8 @@ impl KerberosConfig { let kdc_proxy_url = kdc_proxy_url .map(|url| url::Url::parse(&url)) .transpose() - .map_err(|e| custom_err!("invalid KDC URL", e))?; + .map_err(|e| custom_err!("invalid KDC URL", e)) + .map_err_as::()?; Ok(Self { kdc_proxy_url, hostname, @@ -101,7 +103,9 @@ impl CredsspSequence { ) -> ConnectorResult<(Self, credssp::TsRequest)> { let credentials: sspi::Credentials = match &credentials { Credentials::UsernamePassword { username, password } => { - let username = Username::new(username, domain).map_err(|e| custom_err!("invalid username", e))?; + let username = Username::new(username, domain) + .map_err(|e| custom_err!("invalid username", e)) + .map_err_as::()?; sspi::AuthIdentity { username, @@ -112,9 +116,11 @@ impl CredsspSequence { Credentials::SmartCard { pin, config } => match config { Some(config) => { let cert: Certificate = picky_asn1_der::from_bytes(&config.certificate) - .map_err(|_e| general_err!("can't parse certificate"))?; + .map_err(|_e| general_err!("can't parse certificate")) + .map_err_as::()?; let key = PrivateKey::from_pkcs1(&config.private_key) - .map_err(|_e| general_err!("can't parse private key"))?; + .map_err(|_e| general_err!("can't parse private key")) + .map_err_as::()?; let identity = sspi::SmartCardIdentity { username: extract_user_principal_name(&cert) .or_else(|| extract_user_name(&cert)) @@ -133,7 +139,7 @@ impl CredsspSequence { sspi::Credentials::SmartCard(Box::new(identity)) } None => { - return Err(general_err!("smart card configuration missing")); + return Err(general_err!("smart card configuration missing")).map_err_as::(); } }, }; @@ -180,13 +186,16 @@ impl CredsspSequence { pub fn decode_server_message(&mut self, input: &[u8]) -> ConnectorResult> { match self.state { CredsspState::Ongoing => { - let message = credssp::TsRequest::from_buffer(input).map_err(|e| custom_err!("TsRequest", e))?; + let message = credssp::TsRequest::from_buffer(input) + .map_err(|e| custom_err!("TsRequest", e)) + .map_err_as::()?; debug!(?message, "Received"); Ok(Some(message)) } CredsspState::EarlyUserAuthResult => { let early_user_auth_result = credssp::EarlyUserAuthResult::from_buffer(input) - .map_err(|e| custom_err!("EarlyUserAuthResult", e))?; + .map_err(|e| custom_err!("EarlyUserAuthResult", e)) + .map_err_as::()?; debug!(message = ?early_user_auth_result, "Received"); @@ -202,7 +211,8 @@ impl CredsspSequence { } _ => Err(general_err!( "attempted to feed server request to CredSSP sequence in an unexpected state" - )), + )) + .map_err_as::(), } } @@ -229,11 +239,15 @@ impl CredsspSequence { let written = write_credssp_request(ts_request_from_client, output)?; - Ok((Written::from_size(written)?, next_state)) + Ok(( + Written::from_size(written).map_err_as::()?, + next_state, + )) } CredsspState::EarlyUserAuthResult => Ok((Written::Nothing, CredsspState::Finished)), CredsspState::Finished => Err(general_err!("CredSSP sequence is already done")), - }?; + } + .map_err_as::()?; self.state = next_state; @@ -268,7 +282,8 @@ fn write_credssp_request(ts_request: credssp::TsRequest, output: &mut WriteBuf) ts_request .encode_ts_request(unfilled_buffer) - .map_err(|e| custom_err!("TsRequest", e))?; + .map_err(|e| custom_err!("TsRequest", e)) + .map_err_as::()?; output.advance(length); diff --git a/crates/ironrdp-connector/src/lib.rs b/crates/ironrdp-connector/src/lib.rs index 4a979dcaf1..327f5d23a3 100644 --- a/crates/ironrdp-connector/src/lib.rs +++ b/crates/ironrdp-connector/src/lib.rs @@ -9,6 +9,7 @@ pub mod connection_activation; mod connection_finalization; pub mod credssp; mod license_exchange; +mod sequence_error; mod server_name; use core::any::Any; @@ -30,8 +31,14 @@ pub use self::connection::{ }; pub use self::connection_finalization::{ConnectionFinalizationSequence, ConnectionFinalizationState}; pub use self::license_exchange::{LicenseExchangeSequence, LicenseExchangeState}; +pub use self::sequence_error::{SequenceError, SequenceErrorExt, SequenceErrorKind, SequenceResult, SequenceResultExt}; pub use self::server_name::ServerName; pub use crate::license_exchange::LicenseCache; +/// Re-exported so `connect_*`/`accept_*` boundary functions across +/// `ironrdp-async`, `ironrdp-blocking`, and `ironrdp-acceptor` can call +/// `.map_err_as::()` without a direct `ironrdp-error` +/// dependency. See the [`ironrdp_error::ErrorMapping`] impl on [`ConnectorErrorKind`] below. +pub use ironrdp_error::ResultExt; /// Provides user-friendly error messages for RDP negotiation failures #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -334,10 +341,10 @@ pub enum Written { impl Written { #[inline] - pub fn from_size(value: usize) -> ConnectorResult { + pub fn from_size(value: usize) -> SequenceResult { core::num::NonZeroUsize::new(value) .map(Self::Size) - .ok_or_else(|| ConnectorError::general("invalid written length (can't be zero)")) + .ok_or_else(|| SequenceError::general("invalid written length (can't be zero)")) } #[inline] @@ -397,9 +404,9 @@ pub trait Sequence: Send { input: &[u8], received_at: Option, output: &mut WriteBuf, - ) -> ConnectorResult; + ) -> SequenceResult; - fn step_no_input(&mut self, output: &mut WriteBuf) -> ConnectorResult { + fn step_no_input(&mut self, output: &mut WriteBuf) -> SequenceResult { self.step(&[], None, output) } } @@ -408,30 +415,28 @@ ironrdp_core::assert_obj_safe!(Sequence); pub type ConnectorResult = Result; +/// Nested top-level connect-flow union. +/// +/// Every in-workspace [`Sequence`] impl and its helpers return [`SequenceError`], +/// an sspi-free error type. `ConnectorError` is the coarser error type used at +/// connect boundaries (e.g. `connect_begin`/`connect_finalize`), nesting the +/// `SequenceError` produced while driving a sequence alongside the two other +/// connect-flow concerns that a `Sequence` impl never needs to know about: +/// CredSSP (which carries `sspi::Error`) and access-denied responses. #[non_exhaustive] #[derive(Debug)] pub enum ConnectorErrorKind { - Encode(ironrdp_core::EncodeError), - Decode(ironrdp_core::DecodeError), + Sequence(SequenceError), Credssp(sspi::Error), - Reason(String), AccessDenied, - General, - Custom, - Negotiation(NegotiationFailure), } impl fmt::Display for ConnectorErrorKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self { - ConnectorErrorKind::Encode(_) => write!(f, "encode error"), - ConnectorErrorKind::Decode(_) => write!(f, "decode error"), + ConnectorErrorKind::Sequence(error) => error.fmt(f), ConnectorErrorKind::Credssp(_) => write!(f, "CredSSP"), - ConnectorErrorKind::Reason(description) => write!(f, "reason: {description}"), ConnectorErrorKind::AccessDenied => write!(f, "access denied"), - ConnectorErrorKind::General => write!(f, "general error"), - ConnectorErrorKind::Custom => write!(f, "custom error"), - ConnectorErrorKind::Negotiation(failure) => write!(f, "negotiation failure: {failure}"), } } } @@ -439,58 +444,40 @@ impl fmt::Display for ConnectorErrorKind { impl core::error::Error for ConnectorErrorKind { fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { match &self { - ConnectorErrorKind::Encode(e) => Some(e), - ConnectorErrorKind::Decode(e) => Some(e), + ConnectorErrorKind::Sequence(e) => Some(e), ConnectorErrorKind::Credssp(e) => Some(e), - ConnectorErrorKind::Reason(_) => None, ConnectorErrorKind::AccessDenied => None, - ConnectorErrorKind::Custom => None, - ConnectorErrorKind::General => None, - ConnectorErrorKind::Negotiation(failure) => Some(failure), } } } pub type ConnectorError = ironrdp_error::Error; -pub trait ConnectorErrorExt { - fn encode(error: ironrdp_core::EncodeError) -> Self; - fn decode(error: ironrdp_core::DecodeError) -> Self; - fn general(context: &'static str) -> Self; - fn reason(context: &'static str, reason: impl Into) -> Self; - fn custom(context: &'static str, e: E) -> Self - where - E: core::error::Error + Sync + Send + 'static; -} - -impl ConnectorErrorExt for ConnectorError { - #[track_caller] - fn encode(error: ironrdp_core::EncodeError) -> Self { - Self::new("encode error", ConnectorErrorKind::Encode(error)) - } - - #[track_caller] - fn decode(error: ironrdp_core::DecodeError) -> Self { - Self::new("decode error", ConnectorErrorKind::Decode(error)) - } - - #[track_caller] - fn general(context: &'static str) -> Self { - Self::new(context, ConnectorErrorKind::General) - } - +/// Canonical `SequenceError -> ConnectorError` conversion. +/// +/// `ConnectorError` and `SequenceError` are both `ironrdp_error::Error<_>` +/// instantiations, so orphan rules forbid a direct `impl From +/// for ConnectorError` (neither `ConnectorError`'s nor `SequenceError`'s head +/// type is local to this crate — `ironrdp_error::Error` is). `ErrorMapping` is +/// `ironrdp-error`'s sanctioned mechanism for this instead: call sites use +/// `.map_err_as::()` immediately before the `?` that +/// performs the actual boundary crossing, at each `connect_*`/`accept_*` +/// function (see `ironrdp-async`, `ironrdp-blocking`, `ironrdp-acceptor`). +impl ironrdp_error::ErrorMapping for ConnectorErrorKind { #[track_caller] - fn reason(context: &'static str, reason: impl Into) -> Self { - Self::new(context, ConnectorErrorKind::Reason(reason.into())) + fn map_error(error: SequenceError) -> ConnectorError { + ConnectorError::new("sequence error", ConnectorErrorKind::Sequence(error)) } +} - #[track_caller] - fn custom(context: &'static str, e: E) -> Self - where - E: core::error::Error + Sync + Send + 'static, - { - Self::new(context, ConnectorErrorKind::Custom).with_source(e) - } +/// Maps a bare [`SequenceError`] value to a [`ConnectorError`]. +/// +/// Companion to [`ResultExt::map_err_as`] for call sites that need to convert an already-produced +/// `SequenceError` value directly, rather than mapping it while it flows through a `?`-propagated +/// `Result`. This comes up when a `SequenceError` must be reported out-of-band (e.g. sent through a +/// channel as a [`ConnectorError`]) instead of being returned from the current function. +pub fn map_sequence_error(error: SequenceError) -> ConnectorError { + >::map_error(error) } pub trait ConnectorResultExt { @@ -518,17 +505,17 @@ impl ConnectorResultExt for ConnectorResult { } } -pub fn encode_x224_packet(x224_msg: &T, buf: &mut WriteBuf) -> ConnectorResult +pub fn encode_x224_packet(x224_msg: &T, buf: &mut WriteBuf) -> SequenceResult where T: Encode, { - let x224_msg_buf = encode_vec(x224_msg).map_err(ConnectorError::encode)?; + let x224_msg_buf = encode_vec(x224_msg).map_err(SequenceError::encode)?; let pdu = x224::X224Data { data: std::borrow::Cow::Owned(x224_msg_buf), }; - let written = encode_buf(&X224(pdu), buf).map_err(ConnectorError::encode)?; + let written = encode_buf(&X224(pdu), buf).map_err(SequenceError::encode)?; Ok(written) } diff --git a/crates/ironrdp-connector/src/license_exchange.rs b/crates/ironrdp-connector/src/license_exchange.rs index 4ced01a52f..6d41d6ee34 100644 --- a/crates/ironrdp-connector/src/license_exchange.rs +++ b/crates/ironrdp-connector/src/license_exchange.rs @@ -10,9 +10,10 @@ use ironrdp_pdu::rdp::server_license::{self, LicenseInformation, LicensePdu, Ser use rand::RngCore as _; use tracing::{debug, error, info, trace}; -use super::{ConnectorError, ConnectorErrorExt as _, custom_err, general_err}; +use super::{SequenceError, SequenceErrorExt as _, custom_err, general_err}; use crate::{ - ConnectorResult, ConnectorResultExt as _, MonotonicInstant, Sequence, State, Written, encode_send_data_request, + ConnectorResult, MonotonicInstant, Sequence, SequenceResult, SequenceResultExt as _, State, Written, + encode_send_data_request, }; #[derive(Default, Debug)] @@ -124,7 +125,7 @@ impl Sequence for LicenseExchangeSequence { input: &[u8], _received_at: Option, output: &mut WriteBuf, - ) -> ConnectorResult { + ) -> SequenceResult { let (written, next_state) = match mem::take(&mut self.state) { LicenseExchangeState::Consumed => { return Err(general_err!( @@ -134,10 +135,10 @@ impl Sequence for LicenseExchangeSequence { LicenseExchangeState::NewLicenseRequest => { let send_data_indication_ctx = - ironrdp_pdu::mcs::decode_send_data_indication(input).map_err(ConnectorError::decode)?; + ironrdp_pdu::mcs::decode_send_data_indication(input).map_err(SequenceError::decode)?; let license_pdu = send_data_indication_ctx .decode_user_data::() - .map_err(ConnectorError::decode) + .map_err(SequenceError::decode) .with_context("decode during LicenseExchangeState::NewLicenseRequest")?; match license_pdu { @@ -164,7 +165,8 @@ impl Sequence for LicenseExchangeSequence { .transpose() }) .next() - .transpose()?; + .transpose() + .map_err(|e| custom_err!("get_license", e))?; if let Some(info) = license_info { match server_license::ClientLicenseInfo::from_server_license_request( @@ -268,11 +270,11 @@ impl Sequence for LicenseExchangeSequence { LicenseExchangeState::PlatformChallenge { encryption_data } => { let send_data_indication_ctx = - ironrdp_pdu::mcs::decode_send_data_indication(input).map_err(ConnectorError::decode)?; + ironrdp_pdu::mcs::decode_send_data_indication(input).map_err(SequenceError::decode)?; let license_pdu = send_data_indication_ctx .decode_user_data::() - .map_err(ConnectorError::decode) + .map_err(SequenceError::decode) .with_context("decode during LicenseExchangeState::PlatformChallenge")?; match license_pdu { @@ -322,11 +324,11 @@ impl Sequence for LicenseExchangeSequence { LicenseExchangeState::UpgradeLicense { encryption_data } => { let send_data_indication_ctx = - ironrdp_pdu::mcs::decode_send_data_indication(input).map_err(ConnectorError::decode)?; + ironrdp_pdu::mcs::decode_send_data_indication(input).map_err(SequenceError::decode)?; let license_pdu = send_data_indication_ctx .decode_user_data::() - .map_err(ConnectorError::decode) + .map_err(SequenceError::decode) .with_context("decode during SERVER_NEW_LICENSE/LicenseExchangeState::UpgradeLicense")?; match license_pdu { @@ -341,9 +343,11 @@ impl Sequence for LicenseExchangeSequence { let license_info = upgrade_license .new_license_info(&encryption_data) - .map_err(ConnectorError::decode)?; + .map_err(SequenceError::decode)?; - self.license_cache.store_license(license_info)? + self.license_cache + .store_license(license_info) + .map_err(|e| custom_err!("store_license", e))? } LicensePdu::LicensingErrorMessage(error_message) => { if error_message.error_code != server_license::LicenseErrorCode::StatusValidClient { diff --git a/crates/ironrdp-connector/src/macros.rs b/crates/ironrdp-connector/src/macros.rs index b604e92c72..6b6513a7ff 100644 --- a/crates/ironrdp-connector/src/macros.rs +++ b/crates/ironrdp-connector/src/macros.rs @@ -1,34 +1,34 @@ -/// Creates a `ConnectorError` with `General` kind +/// Creates a `SequenceError` with `General` kind /// /// Shorthand for /// ```rust -/// ::general(context) +/// ::general(context) /// ``` #[macro_export] macro_rules! general_err { - ( $context:expr $(,)? ) => {{ <$crate::ConnectorError as $crate::ConnectorErrorExt>::general($context) }}; + ( $context:expr $(,)? ) => {{ <$crate::SequenceError as $crate::SequenceErrorExt>::general($context) }}; } -/// Creates a `ConnectorError` with `Reason` kind +/// Creates a `SequenceError` with `Reason` kind /// /// Shorthand for /// ```rust -/// ::reason(context, reason) +/// ::reason(context, reason) /// ``` #[macro_export] macro_rules! reason_err { ( $context:expr, $($arg:tt)* ) => {{ - <$crate::ConnectorError as $crate::ConnectorErrorExt>::reason($context, format!($($arg)*)) + <$crate::SequenceError as $crate::SequenceErrorExt>::reason($context, format!($($arg)*)) }}; } -/// Creates a `ConnectorError` with `Custom` kind and a source error attached to it +/// Creates a `SequenceError` with `Custom` kind and a source error attached to it /// /// Shorthand for /// ```rust -/// ::custom(context, source) +/// ::custom(context, source) /// ``` #[macro_export] macro_rules! custom_err { - ( $context:expr, $source:expr $(,)? ) => {{ <$crate::ConnectorError as $crate::ConnectorErrorExt>::custom($context, $source) }}; + ( $context:expr, $source:expr $(,)? ) => {{ <$crate::SequenceError as $crate::SequenceErrorExt>::custom($context, $source) }}; } diff --git a/crates/ironrdp-connector/src/sequence_error.rs b/crates/ironrdp-connector/src/sequence_error.rs new file mode 100644 index 0000000000..added7a1a4 --- /dev/null +++ b/crates/ironrdp-connector/src/sequence_error.rs @@ -0,0 +1,129 @@ +//! Error type produced by [`Sequence`](crate::Sequence) implementors. +//! +//! `SequenceError` is the sspi-free error type returned while driving a single +//! PDU state machine (a [`Sequence`](crate::Sequence) impl and its helpers). It +//! never carries an `sspi::Error` and never needs to know about CredSSP or +//! access-denied semantics: those are connect-flow-level concerns owned by +//! [`ConnectorError`](crate::ConnectorError), which nests a `SequenceError` in +//! its own `Sequence` variant at each connect boundary (see the +//! `ErrorMapping for ConnectorErrorKind` impl). +//! +//! Keeping this type and its constructors in their own module (rather than +//! inline in `lib.rs`) keeps it shaped for the planned extraction into a +//! standalone `ironrdp-sequence` crate (tracked separately). + +use core::fmt; + +use crate::NegotiationFailure; + +pub type SequenceResult = Result; + +#[non_exhaustive] +#[derive(Debug)] +pub enum SequenceErrorKind { + Encode(ironrdp_core::EncodeError), + Decode(ironrdp_core::DecodeError), + Reason(String), + General, + Custom, + Negotiation(NegotiationFailure), +} + +impl fmt::Display for SequenceErrorKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self { + SequenceErrorKind::Encode(_) => write!(f, "encode error"), + SequenceErrorKind::Decode(_) => write!(f, "decode error"), + SequenceErrorKind::Reason(description) => write!(f, "reason: {description}"), + SequenceErrorKind::General => write!(f, "general error"), + SequenceErrorKind::Custom => write!(f, "custom error"), + SequenceErrorKind::Negotiation(failure) => write!(f, "negotiation failure: {failure}"), + } + } +} + +impl core::error::Error for SequenceErrorKind { + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { + match &self { + SequenceErrorKind::Encode(e) => Some(e), + SequenceErrorKind::Decode(e) => Some(e), + SequenceErrorKind::Reason(_) => None, + SequenceErrorKind::General => None, + SequenceErrorKind::Custom => None, + SequenceErrorKind::Negotiation(failure) => Some(failure), + } + } +} + +pub type SequenceError = ironrdp_error::Error; + +pub trait SequenceErrorExt { + fn encode(error: ironrdp_core::EncodeError) -> Self; + fn decode(error: ironrdp_core::DecodeError) -> Self; + fn general(context: &'static str) -> Self; + fn reason(context: &'static str, reason: impl Into) -> Self; + fn custom(context: &'static str, e: E) -> Self + where + E: core::error::Error + Sync + Send + 'static; + fn negotiation(context: &'static str, failure: NegotiationFailure) -> Self; +} + +impl SequenceErrorExt for SequenceError { + #[track_caller] + fn encode(error: ironrdp_core::EncodeError) -> Self { + Self::new("encode error", SequenceErrorKind::Encode(error)) + } + + #[track_caller] + fn decode(error: ironrdp_core::DecodeError) -> Self { + Self::new("decode error", SequenceErrorKind::Decode(error)) + } + + #[track_caller] + fn general(context: &'static str) -> Self { + Self::new(context, SequenceErrorKind::General) + } + + #[track_caller] + fn reason(context: &'static str, reason: impl Into) -> Self { + Self::new(context, SequenceErrorKind::Reason(reason.into())) + } + + #[track_caller] + fn custom(context: &'static str, e: E) -> Self + where + E: core::error::Error + Sync + Send + 'static, + { + Self::new(context, SequenceErrorKind::Custom).with_source(e) + } + + #[track_caller] + fn negotiation(context: &'static str, failure: NegotiationFailure) -> Self { + Self::new(context, SequenceErrorKind::Negotiation(failure)) + } +} + +pub trait SequenceResultExt { + #[must_use] + fn with_context(self, context: &'static str) -> Self; + #[must_use] + fn with_source(self, source: E) -> Self + where + E: core::error::Error + Sync + Send + 'static; +} + +impl SequenceResultExt for SequenceResult { + fn with_context(self, context: &'static str) -> Self { + self.map_err(|mut e| { + e.set_context(context); + e + }) + } + + fn with_source(self, source: E) -> Self + where + E: core::error::Error + Sync + Send + 'static, + { + self.map_err(|e| e.with_source(source)) + } +} diff --git a/crates/ironrdp-daemon/src/daemon.rs b/crates/ironrdp-daemon/src/daemon.rs index 2932efd961..b7fea08696 100644 --- a/crates/ironrdp-daemon/src/daemon.rs +++ b/crates/ironrdp-daemon/src/daemon.rs @@ -2191,7 +2191,7 @@ mod tests { let mut wait = Box::pin(daemon.rail_wait(Some(1), 1_000)); output_tx .send(ironrdp_client::rdp::RdpOutputEvent::ConnectionFailure( - ironrdp_connector::general_err!("test connection failure"), + ironrdp_connector::map_sequence_error(ironrdp_connector::general_err!("test connection failure")), )) .await .expect("send connection failure"); diff --git a/crates/ironrdp-testsuite-core/tests/connector/mod.rs b/crates/ironrdp-testsuite-core/tests/connector/mod.rs index 06f642f83d..a3b2e4cfe1 100644 --- a/crates/ironrdp-testsuite-core/tests/connector/mod.rs +++ b/crates/ironrdp-testsuite-core/tests/connector/mod.rs @@ -1 +1,14 @@ mod autodetect; + +use ironrdp_connector::{SequenceError, SequenceErrorExt as _}; + +#[test] +fn connector_error_display_preserves_sequence_error_detail() { + let sequence_error = SequenceError::reason("Capabilities Exchange", "server rejected the requested color depth"); + let connector_error = ironrdp_connector::map_sequence_error(sequence_error); + + assert_eq!( + connector_error.to_string(), + "[sequence error] [Capabilities Exchange] reason: server rejected the requested color depth" + ); +} diff --git a/crates/ironrdp-tokio/src/reqwest.rs b/crates/ironrdp-tokio/src/reqwest.rs index efda1b6061..2a5bc8c3e0 100644 --- a/crates/ironrdp-tokio/src/reqwest.rs +++ b/crates/ironrdp-tokio/src/reqwest.rs @@ -1,7 +1,7 @@ use core::net::{IpAddr, Ipv4Addr}; use ironrdp_connector::sspi::{self, Error, ErrorKind}; -use ironrdp_connector::{ConnectorResult, custom_err, general_err}; +use ironrdp_connector::{ConnectorErrorKind, ConnectorResult, ResultExt as _, custom_err, general_err}; use reqwest::Client; use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; use tokio::net::{TcpStream, UdpSocket}; @@ -43,34 +43,41 @@ impl ReqwestNetworkClient { let mut stream = TcpStream::connect(addr) .await .map_err(|e| Error::new(ErrorKind::NoAuthenticatingAuthority, format!("{e:?}"))) - .map_err(|e| custom_err!("failed to send KDC request over TCP", e))?; + .map_err(|e| custom_err!("failed to send KDC request over TCP", e)) + .map_err_as::()?; stream .write(data) .await .map_err(|e| Error::new(ErrorKind::NoAuthenticatingAuthority, format!("{e:?}"))) - .map_err(|e| custom_err!("failed to send KDC request over TCP", e))?; + .map_err(|e| custom_err!("failed to send KDC request over TCP", e)) + .map_err_as::()?; let len = stream .read_u32() .await .map_err(|e| Error::new(ErrorKind::NoAuthenticatingAuthority, format!("{e:?}"))) - .map_err(|e| custom_err!("failed to send KDC request over TCP", e))?; + .map_err(|e| custom_err!("failed to send KDC request over TCP", e)) + .map_err_as::()?; let len = usize::try_from(len) - .map_err(|_| general_err!("invalid buffer length: out of range integral type conversion"))?; + .map_err(|_| general_err!("invalid buffer length: out of range integral type conversion")) + .map_err_as::()?; let mut buf = vec![0; len + 4]; // Write the length prefix back as a big-endian u32 (matching the wire format). // `len` was originally read as a `u32` so this conversion cannot fail. - let len_u32 = u32::try_from(len).map_err(|_| general_err!("KDC TCP response length overflows u32"))?; + let len_u32 = u32::try_from(len) + .map_err(|_| general_err!("KDC TCP response length overflows u32")) + .map_err_as::()?; buf[0..4].copy_from_slice(&len_u32.to_be_bytes()); stream .read_exact(&mut buf[4..]) .await .map_err(|e| Error::new(ErrorKind::NoAuthenticatingAuthority, format!("{e:?}"))) - .map_err(|e| custom_err!("failed to send KDC request over TCP", e))?; + .map_err(|e| custom_err!("failed to send KDC request over TCP", e)) + .map_err_as::()?; Ok(buf) } @@ -78,14 +85,16 @@ impl ReqwestNetworkClient { async fn send_udp(&self, url: &Url, data: &[u8]) -> ConnectorResult> { let udp_socket = UdpSocket::bind((IpAddr::V4(Ipv4Addr::LOCALHOST), 0)) .await - .map_err(|e| custom_err!("cannot bind UDP socket", e))?; + .map_err(|e| custom_err!("cannot bind UDP socket", e)) + .map_err_as::()?; let addr = format!("{}:{}", url.host_str().unwrap_or_default(), url.port().unwrap_or(88)); udp_socket .send_to(data, addr) .await - .map_err(|e| custom_err!("failed to send UDP request", e))?; + .map_err(|e| custom_err!("failed to send UDP request", e)) + .map_err_as::()?; // 48 000 bytes: default maximum token len in Windows let mut buf = vec![0; 0xbb80]; @@ -93,11 +102,14 @@ impl ReqwestNetworkClient { let n = udp_socket .recv(&mut buf) .await - .map_err(|e| custom_err!("failed to receive UDP request", e))?; + .map_err(|e| custom_err!("failed to receive UDP request", e)) + .map_err_as::()?; let buf = &buf[0..n]; let mut reply_buf = Vec::with_capacity(n + 4); - let n = u32::try_from(n).map_err(|e| custom_err!("invalid length", e))?; + let n = u32::try_from(n) + .map_err(|e| custom_err!("invalid length", e)) + .map_err_as::()?; reply_buf.extend_from_slice(&n.to_be_bytes()); reply_buf.extend_from_slice(buf); @@ -112,14 +124,17 @@ impl ReqwestNetworkClient { .body(data.to_vec()) .send() .await - .map_err(|e| custom_err!("failed to send KDC request over proxy", e))? + .map_err(|e| custom_err!("failed to send KDC request over proxy", e)) + .map_err_as::()? .error_for_status() - .map_err(|e| custom_err!("KdcProxy", e))?; + .map_err(|e| custom_err!("KdcProxy", e)) + .map_err_as::()?; let body = response .bytes() .await - .map_err(|e| custom_err!("failed to receive KDC response", e))?; + .map_err(|e| custom_err!("failed to receive KDC response", e)) + .map_err_as::()?; // The type bytes::Bytes has a special From implementation for Vec. let body = Vec::from(body); diff --git a/crates/ironrdp-vmconnect/src/lib.rs b/crates/ironrdp-vmconnect/src/lib.rs index b9e6a2fc4d..b47c97a3c7 100644 --- a/crates/ironrdp-vmconnect/src/lib.rs +++ b/crates/ironrdp-vmconnect/src/lib.rs @@ -18,8 +18,8 @@ use ironrdp_async::{ }; use ironrdp_connector::credssp::{CredsspSequence, KerberosConfig}; use ironrdp_connector::{ - ClientConnector, ClientConnectorState, ConnectorError, ConnectorErrorExt as _, ConnectorResult, Credentials, - ServerName, State as _, custom_err, reason_err, + ClientConnector, ClientConnectorState, ConnectorErrorKind, ConnectorResult, Credentials, ResultExt as _, + SequenceError, SequenceErrorExt as _, ServerName, State as _, custom_err, reason_err, }; use ironrdp_core::{WriteBuf, encode_vec}; use ironrdp_pdu::nego::SecurityProtocol; @@ -68,7 +68,7 @@ pub fn encode_preconnection_blob(vm_id: &str, mode: Mode) -> ConnectorResult ConnectorResult { if vm_id.trim().is_empty() { - return Err(reason_err!("vmconnect", "vmconnect VM ID is empty")); + return Err(reason_err!("vmconnect", "vmconnect VM ID is empty")).map_err_as::(); } Ok(match mode { @@ -84,7 +84,8 @@ pub fn encode_preconnection_blob_payload(payload: String) -> ConnectorResult() } /// Write the Preconnection Blob on a pre-TLS stream. Returns a [`PcbSent`] for [`connect_front`]. @@ -101,7 +102,8 @@ where framed .write_all(&bytes) .await - .map_err(|e| custom_err!("write preconnection blob", e))?; + .map_err(|e| custom_err!("write preconnection blob", e)) + .map_err_as::()?; Ok(PcbSent) } @@ -157,11 +159,14 @@ where connector.config.autologon = false; buf.clear(); - connector.initiate_with_security_protocol(POST_CREDSSP_PROTOCOL, &mut buf)?; + connector + .initiate_with_security_protocol(POST_CREDSSP_PROTOCOL, &mut buf) + .map_err_as::()?; framed .write_all(buf.filled()) .await - .map_err(|e| custom_err!("write X.224 connection request", e))?; + .map_err(|e| custom_err!("write X.224 connection request", e)) + .map_err_as::()?; let should_upgrade = connect_begin(framed, connector).await?; ensure_selected_credssp(&connector.state)?; @@ -181,10 +186,10 @@ where /// (for example FFI `CredsspSequence::init_with_protocol`). pub fn prepare_connector(connector: &ClientConnector) -> ConnectorResult<()> { if !connector.config.enable_tls { - return Err(reason_err!("vmconnect", "vmconnect requires TLS")); + return Err(reason_err!("vmconnect", "vmconnect requires TLS")).map_err_as::(); } if !connector.config.enable_credssp { - return Err(reason_err!("vmconnect", "vmconnect requires CredSSP")); + return Err(reason_err!("vmconnect", "vmconnect requires CredSSP")).map_err_as::(); } Ok(()) } @@ -198,7 +203,8 @@ pub fn ensure_selected_credssp(state: &ClientConnectorState) -> ConnectorResult< "Initiation", "expected EnhancedSecurityUpgrade after Hyper-V X.224 initiation, got {}", other.name() - )); + )) + .map_err_as::(); } }; @@ -209,5 +215,6 @@ pub fn ensure_selected_credssp(state: &ClientConnectorState) -> ConnectorResult< "Initiation", "server must select HYBRID for a Hyper-V console, but it selected {selected}", )) + .map_err_as::() } } diff --git a/crates/ironrdp-web/src/error.rs b/crates/ironrdp-web/src/error.rs index 1498451088..35cbcc4acb 100644 --- a/crates/ironrdp-web/src/error.rs +++ b/crates/ironrdp-web/src/error.rs @@ -1,5 +1,5 @@ use iron_remote_desktop::{IronErrorKind, RDCleanPathDetails}; -use ironrdp::connector::{self, ConnectorErrorKind, sspi}; +use ironrdp::connector::{self, ConnectorErrorKind, SequenceError, SequenceErrorKind, sspi}; pub(crate) struct IronError { kind: IronErrorKind, @@ -51,7 +51,24 @@ impl From for IronError { .. }) => IronErrorKind::LogonFailure, ConnectorErrorKind::AccessDenied => IronErrorKind::AccessDenied, - ConnectorErrorKind::Negotiation(_) => IronErrorKind::NegotiationFailure, + ConnectorErrorKind::Sequence(seq_err) if matches!(seq_err.kind(), SequenceErrorKind::Negotiation(_)) => { + IronErrorKind::NegotiationFailure + } + _ => IronErrorKind::General, + }; + + Self { + kind, + source: anyhow::Error::new(e), + rdcleanpath_details: None, + } + } +} + +impl From for IronError { + fn from(e: SequenceError) -> Self { + let kind = match e.kind() { + SequenceErrorKind::Negotiation(_) => IronErrorKind::NegotiationFailure, _ => IronErrorKind::General, }; diff --git a/crates/ironrdp-web/src/network_client.rs b/crates/ironrdp-web/src/network_client.rs index 71bf8c1af4..29fc956518 100644 --- a/crates/ironrdp-web/src/network_client.rs +++ b/crates/ironrdp-web/src/network_client.rs @@ -1,6 +1,6 @@ use ironrdp::connector::sspi::generator::NetworkRequest; use ironrdp::connector::sspi::network_client::NetworkProtocol; -use ironrdp::connector::{ConnectorResult, custom_err, reason_err}; +use ironrdp::connector::{ConnectorErrorKind, ConnectorResult, ResultExt as _, custom_err, reason_err}; use ironrdp_futures::NetworkClient; use tracing::debug; @@ -18,10 +18,12 @@ impl NetworkClient for WasmNetworkClient { let response = gloo_net::http::Request::post(network_request.url.as_str()) .header("keep-alive", "true") .body(body) - .map_err(|e| custom_err!("failed to send KDC request", e))? + .map_err(|e| custom_err!("failed to send KDC request", e)) + .map_err_as::()? .send() .await - .map_err(|e| custom_err!("failed to send KDC request", e))?; + .map_err(|e| custom_err!("failed to send KDC request", e)) + .map_err_as::()?; if !response.ok() { return Err(reason_err!( @@ -29,17 +31,21 @@ impl NetworkClient for WasmNetworkClient { "HTTP status error ({} {})", response.status(), response.status_text(), - )); + )) + .map_err_as::(); } let body = response .binary() .await - .map_err(|e| custom_err!("failed to retrieve HTTP response", e))?; + .map_err(|e| custom_err!("failed to retrieve HTTP response", e)) + .map_err_as::()?; Ok(body) } - unsupported => Err(reason_err!("CredSSP", "unsupported protocol: {unsupported:?}")), + unsupported => { + Err(reason_err!("CredSSP", "unsupported protocol: {unsupported:?}")).map_err_as::() + } } } } diff --git a/ffi/src/error.rs b/ffi/src/error.rs index b7029719d0..125c08585d 100644 --- a/ffi/src/error.rs +++ b/ffi/src/error.rs @@ -2,7 +2,7 @@ use core::fmt::Display; use ironrdp::cliprdr::backend::ClipboardError; -use ironrdp::connector::ConnectorError; +use ironrdp::connector::{ConnectorError, SequenceError}; use ironrdp::session::SessionError; #[cfg(target_os = "windows")] use ironrdp_cliprdr_native::WinCliprdrError; @@ -39,8 +39,11 @@ impl From for Box { impl From for Box { fn from(value: ConnectorError) -> Self { let kind = match value.kind() { - ironrdp::connector::ConnectorErrorKind::Encode(_) => IronRdpErrorKind::EncodeError, - ironrdp::connector::ConnectorErrorKind::Decode(_) => IronRdpErrorKind::DecodeError, + ironrdp::connector::ConnectorErrorKind::Sequence(seq_err) => match seq_err.kind() { + ironrdp::connector::SequenceErrorKind::Encode(_) => IronRdpErrorKind::EncodeError, + ironrdp::connector::SequenceErrorKind::Decode(_) => IronRdpErrorKind::DecodeError, + _ => IronRdpErrorKind::Generic, + }, ironrdp::connector::ConnectorErrorKind::Credssp(_) => IronRdpErrorKind::CredsspError, ironrdp::connector::ConnectorErrorKind::AccessDenied => IronRdpErrorKind::AccessDenied, _ => IronRdpErrorKind::Generic, @@ -50,6 +53,20 @@ impl From for Box { } } +// A bare `SequenceError` can reach the FFI boundary directly (e.g. from a `Sequence::step` call +// driven straight through the FFI surface, without first being mapped to `ConnectorError`). +impl From for Box { + fn from(value: SequenceError) -> Self { + let kind = match value.kind() { + ironrdp::connector::SequenceErrorKind::Encode(_) => IronRdpErrorKind::EncodeError, + ironrdp::connector::SequenceErrorKind::Decode(_) => IronRdpErrorKind::DecodeError, + _ => IronRdpErrorKind::Generic, + }; + let repr = value.report().with_locations().to_string(); + make_ffi_error(repr, kind) + } +} + impl From for Box { fn from(value: SessionError) -> Self { let kind = match value.kind() {