Skip to content

WIP: Add GFX ([MS-RDPEGFX]) support #648

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ ironrdp-core = { version = "0.1", path = "crates/ironrdp-core" }
ironrdp-connector = { version = "0.2", path = "crates/ironrdp-connector" }
ironrdp-dvc = { version = "0.1", path = "crates/ironrdp-dvc" }
ironrdp-displaycontrol = { version = "0.1", path = "crates/ironrdp-displaycontrol" }
ironrdp-egfx = { version = "0.1", path = "crates/ironrdp-egfx" }
ironrdp-error = { version = "0.1", path = "crates/ironrdp-error" }
ironrdp-futures = { version = "0.1", path = "crates/ironrdp-futures" }
ironrdp-fuzzing = { path = "crates/ironrdp-fuzzing" }
Expand Down
1 change: 1 addition & 0 deletions crates/ironrdp-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ ironrdp = { workspace = true, features = [
"rdpsnd",
"cliprdr",
"displaycontrol",
"egfx",
"connector"
] }
ironrdp-cliprdr-native.workspace = true
Expand Down
1 change: 1 addition & 0 deletions crates/ironrdp-client/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ impl Config {
request_data: None,
pointer_software_rendering: true,
performance_flags: PerformanceFlags::default(),
support_gfx: true,
};

Ok(Self {
Expand Down
51 changes: 49 additions & 2 deletions crates/ironrdp-client/src/rdp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ use ironrdp::connector::connection_activation::ConnectionActivationState;
use ironrdp::connector::{ConnectionResult, ConnectorResult};
use ironrdp::displaycontrol::client::DisplayControlClient;
use ironrdp::displaycontrol::pdu::MonitorLayoutEntry;
use ironrdp::egfx::client::{GraphicsPipelineClient, GraphicsPipelineHandler};
use ironrdp::egfx::pdu::{GfxPdu, StartFramePdu};
use ironrdp::graphics::image_processing::PixelFormat;
use ironrdp::pdu::input::fast_path::FastPathInputEvent;
use ironrdp::session::image::DecodedImage;
use ironrdp::session::{fast_path, ActiveStage, ActiveStageOutput, GracefulDisconnectReason, SessionResult};
use ironrdp::{cliprdr, connector, rdpdr, rdpsnd, session};
use ironrdp::{cliprdr, connector, egfx, rdpdr, rdpsnd, session};
use ironrdp_core::WriteBuf;
use ironrdp_rdpsnd_native::cpal;
use ironrdp_tokio::{single_sequence_step_read, split_tokio_framed, FramedWrite};
Expand Down Expand Up @@ -92,6 +94,49 @@ impl RdpClient {
}
}

struct GraphicsPipeline {
caps: Option<egfx::pdu::CapabilitySet>,
start_frame: Option<StartFramePdu>,
}

impl GraphicsPipeline {
fn new() -> Self {
Self {
caps: None,
start_frame: None,
}
}
}

impl GraphicsPipelineHandler for GraphicsPipeline {
fn capabilities(&self) -> Vec<egfx::pdu::CapabilitySet> {
vec![egfx::pdu::CapabilitySet::V8 {
flags: egfx::pdu::CapabilitiesV8Flags::empty(),
}]
}

fn handle_pdu(&mut self, pdu: GfxPdu) {
trace!(?pdu);
match pdu {
GfxPdu::CapabilitiesConfirm(pdu) => {
trace!(?pdu);
self.caps = Some(pdu.0);
}
GfxPdu::StartFrame(pdu) => {
trace!(?pdu);
self.start_frame = Some(pdu);
}
GfxPdu::EndFrame(pdu) => {
trace!(?pdu);
self.start_frame = None;
}
pdu => {
debug!(?pdu);
}
}
}
}

enum RdpControlFlow {
ReconnectWithNewSize { width: u16, height: u16 },
TerminatedGracefully(GracefulDisconnectReason),
Expand All @@ -118,7 +163,9 @@ async fn connect(
let mut connector = connector::ClientConnector::new(config.connector.clone())
.with_server_addr(server_addr)
.with_static_channel(
ironrdp::dvc::DrdynvcClient::new().with_dynamic_channel(DisplayControlClient::new(|_| Ok(Vec::new()))),
ironrdp::dvc::DrdynvcClient::new()
.with_dynamic_channel(DisplayControlClient::new(|_| Ok(Vec::new())))
.with_dynamic_channel(GraphicsPipelineClient::new(Box::new(GraphicsPipeline::new()))),
)
.with_static_channel(rdpsnd::client::Rdpsnd::new(Box::new(cpal::RdpsndBackend::new())))
.with_static_channel(rdpdr::Rdpdr::new(Box::new(NoopRdpdrBackend {}), "IronRDP".to_owned()).with_smartcard(0));
Expand Down
5 changes: 4 additions & 1 deletion crates/ironrdp-connector/src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -662,9 +662,12 @@ fn create_gcc_blocks<'a>(
| ClientEarlyCapabilityFlags::SUPPORT_ERR_INFO_PDU
| ClientEarlyCapabilityFlags::STRONG_ASYMMETRIC_KEYS
| ClientEarlyCapabilityFlags::SUPPORT_SKIP_CHANNELJOIN;

// TODO(#136): support for ClientEarlyCapabilityFlags::SUPPORT_STATUS_INFO_PDU

if config.support_gfx {
early_capability_flags |= ClientEarlyCapabilityFlags::SUPPORT_DYN_VC_GFX_PROTOCOL;
}

if max_color_depth == 32 {
early_capability_flags |= ClientEarlyCapabilityFlags::WANT_32_BPP_SESSION;
}
Expand Down
2 changes: 2 additions & 0 deletions crates/ironrdp-connector/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ pub struct Config {
/// If true, the INFO_AUTOLOGON flag is set in the [`ClientInfoPdu`](ironrdp_pdu::rdp::ClientInfoPdu)
pub autologon: bool,
pub license_cache: Option<Arc<dyn LicenseCache>>,
/// If true, the SUPPORT_DYN_VC_GFX_PROTOCOL flag is set
pub support_gfx: bool,

// FIXME(@CBenoit): these are client-only options, not part of the connector.
pub no_server_pointer: bool,
Expand Down
7 changes: 7 additions & 0 deletions crates/ironrdp-egfx/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

28 changes: 28 additions & 0 deletions crates/ironrdp-egfx/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[package]
name = "ironrdp-egfx"
version = "0.1.1"
readme = "README.md"
description = "Graphics pipeline dynamic channel extension implementation"
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
authors.workspace = true
keywords.workspace = true
categories.workspace = true

[lib]
doctest = false
test = false

[dependencies]
bit_field = "0.10.2"
bitflags.workspace = true
ironrdp-core.workspace = true
ironrdp-dvc.workspace = true
ironrdp-graphics.workspace = true
ironrdp-pdu.workspace = true
tracing.workspace = true

[lints]
workspace = true
1 change: 1 addition & 0 deletions crates/ironrdp-egfx/LICENSE-APACHE
1 change: 1 addition & 0 deletions crates/ironrdp-egfx/LICENSE-MIT
9 changes: 9 additions & 0 deletions crates/ironrdp-egfx/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# IronRDP Graphics Pipeline Virtual Channel Extension [MS-RDPEGFX][1] implementation.

Display pipeline Virtual Channel Extension [MS-RDPEGFX][1] implementation.

This library includes:
- Display pipeline DVC PDUs parsing
- Display pipeline DVC processing (TODO)

[1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpegfx/da5c75f9-cd99-450c-98c4-014a496942b0
70 changes: 70 additions & 0 deletions crates/ironrdp-egfx/src/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use ironrdp_core::{impl_as_any, ReadCursor};
use ironrdp_dvc::{DvcClientProcessor, DvcMessage, DvcProcessor};
use ironrdp_graphics::zgfx;
use ironrdp_pdu::{decode_cursor, decode_err, PduResult};
use tracing::trace;

use crate::{
pdu::{CapabilitiesAdvertisePdu, CapabilitiesV8Flags, CapabilitySet, GfxPdu},
CHANNEL_NAME,
};

pub trait GraphicsPipelineHandler: Send {
fn capabilities(&self) -> Vec<CapabilitySet> {
vec![CapabilitySet::V8 {
flags: CapabilitiesV8Flags::empty(),
}]
}

fn handle_pdu(&mut self, pdu: GfxPdu) {
trace!(?pdu);
}
}

/// A client for the Graphics Pipeline Virtual Channel.
pub struct GraphicsPipelineClient {
handler: Box<dyn GraphicsPipelineHandler>,
decompressor: zgfx::Decompressor,
decompressed_buffer: Vec<u8>,
}

impl GraphicsPipelineClient {
pub fn new(handler: Box<dyn GraphicsPipelineHandler>) -> Self {
Self {
handler,
decompressor: zgfx::Decompressor::new(),
decompressed_buffer: Vec::with_capacity(1024 * 16),
}
}
}

impl_as_any!(GraphicsPipelineClient);

impl DvcProcessor for GraphicsPipelineClient {
fn channel_name(&self) -> &str {
CHANNEL_NAME
}

fn start(&mut self, _channel_id: u32) -> PduResult<Vec<DvcMessage>> {
let pdu = GfxPdu::CapabilitiesAdvertise(CapabilitiesAdvertisePdu(self.handler.capabilities()));

Ok(vec![Box::new(pdu)])
}

fn process(&mut self, _channel_id: u32, payload: &[u8]) -> PduResult<Vec<DvcMessage>> {
self.decompressed_buffer.clear();
self.decompressor
.decompress(payload, &mut self.decompressed_buffer)
.map_err(|e| decode_err!(e))?;

let mut cursor = ReadCursor::new(self.decompressed_buffer.as_slice());
while !cursor.is_empty() {
let pdu = decode_cursor(&mut cursor).map_err(|e| decode_err!(e))?;
self.handler.handle_pdu(pdu);
}

Ok(vec![])
}
}

impl DvcClientProcessor for GraphicsPipelineClient {}
8 changes: 8 additions & 0 deletions crates/ironrdp-egfx/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://cdnweb.devolutions.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg")]

pub const CHANNEL_NAME: &str = "Microsoft::Windows::RDS::Graphics";

pub mod client;
pub mod pdu;
pub mod server;
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use core::fmt::Debug;
use core::fmt;

use bit_field::BitField;
use bitflags::bitflags;
use ironrdp_core::{
cast_length, ensure_fixed_part_size, ensure_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult,
ReadCursor, WriteCursor,
use ironrdp_pdu::{
cast_length, ensure_fixed_part_size, ensure_size, geometry::InclusiveRectangle, invalid_field_err, Decode,
DecodeResult, Encode, EncodeResult, ReadCursor, WriteCursor,
};

use crate::geometry::InclusiveRectangle;
use bit_field::BitField;
use bitflags::bitflags;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QuantQuality {
Expand Down Expand Up @@ -66,8 +65,8 @@ pub struct Avc420BitmapStream<'a> {
pub data: &'a [u8],
}

impl Debug for Avc420BitmapStream<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
impl fmt::Debug for Avc420BitmapStream<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Avc420BitmapStream")
.field("rectangles", &self.rectangles)
.field("quant_qual_vals", &self.quant_qual_vals)
Expand Down Expand Up @@ -158,7 +157,7 @@ impl Encode for Avc444BitmapStream<'_> {

let mut stream_info = 0u32;
stream_info.set_bits(0..30, cast_length!("stream1size", self.stream1.size())?);
stream_info.set_bits(30..32, self.encoding.bits() as u32);
stream_info.set_bits(30..32, self.encoding.bits().into());
dst.write_u32(stream_info);
self.stream1.encode(dst)?;
if let Some(stream) = self.stream2.as_ref() {
Expand Down Expand Up @@ -188,7 +187,7 @@ impl<'de> Decode<'de> for Avc444BitmapStream<'de> {

let stream_info = src.read_u32();
let stream_len = stream_info.get_bits(0..30);
let encoding = Encoding::from_bits_truncate(stream_info.get_bits(30..32) as u8);
let encoding = Encoding::from_bits_truncate(stream_info.get_bits(30..32).try_into().unwrap());

if stream_len == 0 {
if encoding == Encoding::LUMA_AND_CHROMA {
Expand Down
Loading
Loading