Skip to main content
Version: 0.8.0
Docs.rs: ironrdp-connector

Overview

The ironrdp-connector crate provides abstract state machines for establishing RDP connections. It handles the complex multi-phase connection sequence:
  1. X.224 negotiation
  2. MCS connection
  3. Security exchange (CredSSP/NLA or legacy TLS)
  4. Capability negotiation
  5. Channel join
  6. 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)
state
fn(&self) -> &dyn State
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:
pub use sspi;
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

arbitrary
feature
Enable arbitrary::Arbitrary derives for fuzzing
qoi
feature
Enable QOI image codec (passed through to ironrdp-pdu)
qoiz
feature
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

Build docs developers (and LLMs) love