Overview
The ironrdp-connector crate provides abstract state machines for establishing RDP connections. It handles the complex multi-phase connection sequence:
- X.224 negotiation
- MCS connection
- Security exchange (CredSSP/NLA or legacy TLS)
- Capability negotiation
- Channel join
- Connection finalization
The connector is transport-agnostic - it produces PDUs to send and consumes PDUs received, but does not perform I/O.
Core Types
Config
pub struct Config {
pub desktop_size: DesktopSize,
pub desktop_scale_factor: u32,
pub enable_tls: bool,
pub enable_credssp: bool,
pub credentials: Credentials,
pub domain: Option<String>,
pub client_build: u32,
pub client_name: String,
pub keyboard_type: gcc::KeyboardType,
pub keyboard_layout: u32,
pub bitmap: Option<BitmapConfig>,
pub alternate_shell: String,
pub performance_flags: PerformanceFlags,
pub compression_type: Option<client_info::CompressionType>,
// ... many more fields
}
Configuration for the RDP connection. See ironrdp_pdu::rdp::capability_sets and ironrdp_pdu::gcc for related types.
Credentials
pub enum Credentials {
UsernamePassword {
username: String,
password: String,
},
SmartCard {
pin: String,
config: Option<SmartCardIdentity>,
},
}
DesktopSize
pub struct DesktopSize {
pub width: u16,
pub height: u16,
}
BitmapConfig
pub struct BitmapConfig {
pub lossy_compression: bool,
pub color_depth: u32,
pub codecs: BitmapCodecs,
}
State Machine Traits
Sequence
pub trait Sequence: Send {
fn next_pdu_hint(&self) -> Option<&dyn PduHint>;
fn state(&self) -> &dyn State;
fn step(&mut self, input: &[u8], output: &mut WriteBuf) -> ConnectorResult<Written>;
fn step_no_input(&mut self, output: &mut WriteBuf) -> ConnectorResult<Written>;
}
Base trait for all connection state machines.
Methods:
next_pdu_hint
fn(&self) -> Option<&dyn PduHint>
Returns a hint for detecting the next PDU boundary (for framing)
Returns the current state (for introspection/logging)
step
fn(&mut self, input: &[u8], output: &mut WriteBuf) -> ConnectorResult<Written>
Process input PDU and generate output PDU(s)
step_no_input
fn(&mut self, output: &mut WriteBuf) -> ConnectorResult<Written>
Generate output PDU(s) without processing input (for initial requests)
State
pub trait State: Send + fmt::Debug + 'static {
fn name(&self) -> &'static str;
fn is_terminal(&self) -> bool;
fn as_any(&self) -> &dyn Any;
}
Represents the current state of a sequence.
Written
pub enum Written {
Nothing,
Size(NonZeroUsize),
}
Return value from step() indicating how many bytes were written.
Connection Sequences
ClientConnector
pub struct ClientConnector { /* ... */ }
impl ClientConnector {
pub fn new(config: Config, server_name: ServerName) -> ConnectorResult<Self>;
}
pub enum ClientConnectorState {
NegotiationRequest,
McsErect,
// ... other states
Connected,
}
pub struct ConnectionResult {
pub io_channel_id: u16,
pub user_channel_id: u16,
pub desktop_size: DesktopSize,
pub no_server_pointer: bool,
pub pointer_software_rendering: bool,
}
Example:
use ironrdp_connector::{Config, ClientConnector, Sequence, ServerName};
use ironrdp_core::WriteBuf;
let config = Config { /* ... */ };
let server_name = ServerName::new("example.com".into());
let mut connector = ClientConnector::new(config, server_name)?;
let mut output = WriteBuf::new();
// Send initial request
connector.step_no_input(&mut output)?;
let to_send = output.filled();
// ... send to_send over network
loop {
// ... receive data into input_buf
output.clear();
let written = connector.step(&input_buf, &mut output)?;
if written.is_nothing() {
// No output to send
} else {
let to_send = output.filled();
// ... send to_send over network
}
if connector.state().is_terminal() {
break;
}
}
let result = connector.result().unwrap();
println!("Connected! IO channel: {}", result.io_channel_id);
ChannelConnectionSequence
pub struct ChannelConnectionSequence { /* ... */ }
pub enum ChannelConnectionState {
SendChannelJoinRequest { channel_id: u16 },
ExpectChannelJoinConfirm { channel_id: u16 },
Finalized,
}
Handles joining static virtual channels after the main connection.
LicenseExchangeSequence
pub struct LicenseExchangeSequence { /* ... */ }
pub enum LicenseExchangeState {
Start,
ExpectLicenseRequest,
ExpectPlatformChallenge,
Finished,
}
Handles RDP licensing exchange.
ConnectionFinalizationSequence
pub struct ConnectionFinalizationSequence { /* ... */ }
pub enum ConnectionFinalizationState {
SendClientConfirmActive,
SendSynchronize,
ExpectSynchronize,
ExpectControlCooperate,
// ... more states
Finalized,
}
Finalizes the connection after capability exchange.
CredSSP Support
This crate re-exports the sspi crate for CredSSP/NLA:
CredSSP provides network-level authentication before the full RDP connection.
Error Handling
pub type ConnectorResult<T> = Result<T, ConnectorError>;
pub type ConnectorError = ironrdp_error::Error<ConnectorErrorKind>;
#[non_exhaustive]
pub enum ConnectorErrorKind {
Encode(ironrdp_core::EncodeError),
Decode(ironrdp_core::DecodeError),
Credssp(sspi::Error),
Reason(String),
AccessDenied,
General,
Custom,
Negotiation(NegotiationFailure),
}
NegotiationFailure
pub struct NegotiationFailure(ironrdp_pdu::nego::FailureCode);
impl fmt::Display for NegotiationFailure {
// Provides user-friendly error messages
}
Example errors:
- “server requires Enhanced RDP Security with CredSSP”
- “server only supports Standard RDP Security”
- “server lacks valid authentication certificate”
Utilities
encode_x224_packet
pub fn encode_x224_packet<T: Encode>(
x224_msg: &T,
buf: &mut WriteBuf
) -> ConnectorResult<usize>
Helper to wrap a PDU in X.224 framing.
encode_send_data_request
pub fn encode_send_data_request(
initiator_id: u16,
channel_id: u16,
data: &[u8],
buf: &mut WriteBuf,
) -> ConnectorResult<usize>
Encode an MCS Send Data Request PDU.
Features
Enable arbitrary::Arbitrary derives for fuzzing
Enable QOI image codec (passed through to ironrdp-pdu)
Enable QOI + Zstd compressed images
Usage Notes
Transport Agnostic:
The connector does NOT perform network I/O. You provide input bytes from the network and send output bytes to the network. This design allows:
- Synchronous or asynchronous I/O
- TCP, TLS, or any custom transport
- Easy testing and fuzzing
State Introspection:
Use state().name() for logging:tracing::info!("Connection state: {}", connector.state().name());
Use state_is::<T>() or state_downcast::<T>() to check specific states.
Security Recommendations:
Set enable_tls = false to enforce NLA (CredSSP). Legacy TLS security (without NLA) has a large attack surface and should be avoided.See the Config::enable_tls documentation for security considerations.
See Also