Skip to content

Commit eb6e325

Browse files
committed
feat: Add ironrdp-egfx
Based on ironrdp-pdu/gfx code. Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
1 parent f57cb87 commit eb6e325

File tree

16 files changed

+2636
-0
lines changed

16 files changed

+2636
-0
lines changed

Cargo.lock

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ ironrdp-core = { version = "0.1", path = "crates/ironrdp-core" }
3535
ironrdp-connector = { version = "0.2", path = "crates/ironrdp-connector" }
3636
ironrdp-dvc = { version = "0.1", path = "crates/ironrdp-dvc" }
3737
ironrdp-displaycontrol = { version = "0.1", path = "crates/ironrdp-displaycontrol" }
38+
ironrdp-egfx = { version = "0.1", path = "crates/ironrdp-egfx" }
3839
ironrdp-error = { version = "0.1", path = "crates/ironrdp-error" }
3940
ironrdp-futures = { version = "0.1", path = "crates/ironrdp-futures" }
4041
ironrdp-fuzzing = { path = "crates/ironrdp-fuzzing" }

crates/ironrdp-egfx/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+

crates/ironrdp-egfx/Cargo.toml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
[package]
2+
name = "ironrdp-egfx"
3+
version = "0.1.1"
4+
readme = "README.md"
5+
description = "Graphics pipeline dynamic channel extension implementation"
6+
edition.workspace = true
7+
license.workspace = true
8+
homepage.workspace = true
9+
repository.workspace = true
10+
authors.workspace = true
11+
keywords.workspace = true
12+
categories.workspace = true
13+
14+
[lib]
15+
doctest = false
16+
test = false
17+
18+
[dependencies]
19+
bit_field = "0.10.2"
20+
bitflags.workspace = true
21+
ironrdp-core.workspace = true
22+
ironrdp-dvc.workspace = true
23+
ironrdp-graphics.workspace = true
24+
ironrdp-pdu.workspace = true
25+
tracing.workspace = true
26+
27+
[lints]
28+
workspace = true

crates/ironrdp-egfx/LICENSE-APACHE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../LICENSE-APACHE

crates/ironrdp-egfx/LICENSE-MIT

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../LICENSE-MIT

crates/ironrdp-egfx/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# IronRDP Graphics Pipeline Virtual Channel Extension [MS-RDPEGFX][1] implementation.
2+
3+
Display pipeline Virtual Channel Extension [MS-RDPEGFX][1] implementation.
4+
5+
This library includes:
6+
- Display pipeline DVC PDUs parsing
7+
- Display pipeline DVC processing (TODO)
8+
9+
[1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpegfx/da5c75f9-cd99-450c-98c4-014a496942b0

crates/ironrdp-egfx/src/client.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use ironrdp_core::{impl_as_any, ReadCursor};
2+
use ironrdp_dvc::{DvcClientProcessor, DvcMessage, DvcProcessor};
3+
use ironrdp_graphics::zgfx;
4+
use ironrdp_pdu::{decode_cursor, decode_err, PduResult};
5+
use tracing::trace;
6+
7+
use crate::{
8+
pdu::{CapabilitiesAdvertisePdu, CapabilitiesV8Flags, CapabilitySet, GfxPdu},
9+
CHANNEL_NAME,
10+
};
11+
12+
pub trait GraphicsPipelineHandler: Send {
13+
fn capabilities(&self) -> Vec<CapabilitySet> {
14+
vec![CapabilitySet::V8 {
15+
flags: CapabilitiesV8Flags::empty(),
16+
}]
17+
}
18+
19+
fn handle_pdu(&mut self, pdu: GfxPdu) {
20+
trace!(?pdu);
21+
}
22+
}
23+
24+
/// A client for the Graphics Pipeline Virtual Channel.
25+
pub struct GraphicsPipelineClient {
26+
handler: Box<dyn GraphicsPipelineHandler>,
27+
decompressor: zgfx::Decompressor,
28+
decompressed_buffer: Vec<u8>,
29+
}
30+
31+
impl GraphicsPipelineClient {
32+
pub fn new(handler: Box<dyn GraphicsPipelineHandler>) -> Self {
33+
Self {
34+
handler,
35+
decompressor: zgfx::Decompressor::new(),
36+
decompressed_buffer: Vec::with_capacity(1024 * 16),
37+
}
38+
}
39+
}
40+
41+
impl_as_any!(GraphicsPipelineClient);
42+
43+
impl DvcProcessor for GraphicsPipelineClient {
44+
fn channel_name(&self) -> &str {
45+
CHANNEL_NAME
46+
}
47+
48+
fn start(&mut self, _channel_id: u32) -> PduResult<Vec<DvcMessage>> {
49+
let pdu = GfxPdu::CapabilitiesAdvertise(CapabilitiesAdvertisePdu(self.handler.capabilities()));
50+
51+
Ok(vec![Box::new(pdu)])
52+
}
53+
54+
fn process(&mut self, _channel_id: u32, payload: &[u8]) -> PduResult<Vec<DvcMessage>> {
55+
self.decompressed_buffer.clear();
56+
self.decompressor
57+
.decompress(payload, &mut self.decompressed_buffer)
58+
.map_err(|e| decode_err!(e))?;
59+
60+
let mut cursor = ReadCursor::new(self.decompressed_buffer.as_slice());
61+
while !cursor.is_empty() {
62+
let pdu = decode_cursor(&mut cursor).map_err(|e| decode_err!(e))?;
63+
self.handler.handle_pdu(pdu);
64+
}
65+
66+
Ok(vec![])
67+
}
68+
}
69+
70+
impl DvcClientProcessor for GraphicsPipelineClient {}

crates/ironrdp-egfx/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#![doc = include_str!("../README.md")]
2+
#![doc(html_logo_url = "https://cdnweb.devolutions.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg")]
3+
4+
pub const CHANNEL_NAME: &str = "Microsoft::Windows::RDS::Graphics";
5+
6+
pub mod client;
7+
pub mod pdu;
8+
pub mod server;

crates/ironrdp-egfx/src/pdu/avc.rs

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
use core::fmt;
2+
3+
use ironrdp_pdu::{
4+
cast_length, ensure_fixed_part_size, ensure_size, geometry::InclusiveRectangle, invalid_field_err, Decode,
5+
DecodeResult, Encode, EncodeResult, ReadCursor, WriteCursor,
6+
};
7+
8+
use bit_field::BitField;
9+
use bitflags::bitflags;
10+
11+
#[derive(Debug, Clone, PartialEq, Eq)]
12+
pub struct QuantQuality {
13+
pub quantization_parameter: u8,
14+
pub progressive: bool,
15+
pub quality: u8,
16+
}
17+
18+
impl QuantQuality {
19+
const NAME: &'static str = "GfxQuantQuality";
20+
21+
const FIXED_PART_SIZE: usize = 1 /* data */ + 1 /* quality */;
22+
}
23+
24+
impl Encode for QuantQuality {
25+
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
26+
ensure_fixed_part_size!(in: dst);
27+
28+
let mut data = 0u8;
29+
data.set_bits(0..6, self.quantization_parameter);
30+
data.set_bit(7, self.progressive);
31+
dst.write_u8(data);
32+
dst.write_u8(self.quality);
33+
Ok(())
34+
}
35+
36+
fn name(&self) -> &'static str {
37+
Self::NAME
38+
}
39+
40+
fn size(&self) -> usize {
41+
Self::FIXED_PART_SIZE
42+
}
43+
}
44+
45+
impl<'de> Decode<'de> for QuantQuality {
46+
fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
47+
ensure_fixed_part_size!(in: src);
48+
49+
let data = src.read_u8();
50+
let qp = data.get_bits(0..6);
51+
let progressive = data.get_bit(7);
52+
let quality = src.read_u8();
53+
Ok(QuantQuality {
54+
quantization_parameter: qp,
55+
progressive,
56+
quality,
57+
})
58+
}
59+
}
60+
61+
#[derive(Clone, PartialEq, Eq)]
62+
pub struct Avc420BitmapStream<'a> {
63+
pub rectangles: Vec<InclusiveRectangle>,
64+
pub quant_qual_vals: Vec<QuantQuality>,
65+
pub data: &'a [u8],
66+
}
67+
68+
impl fmt::Debug for Avc420BitmapStream<'_> {
69+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70+
f.debug_struct("Avc420BitmapStream")
71+
.field("rectangles", &self.rectangles)
72+
.field("quant_qual_vals", &self.quant_qual_vals)
73+
.field("data_len", &self.data.len())
74+
.finish()
75+
}
76+
}
77+
78+
impl Avc420BitmapStream<'_> {
79+
const NAME: &'static str = "Avc420BitmapStream";
80+
81+
const FIXED_PART_SIZE: usize = 4 /* nRect */;
82+
}
83+
84+
impl Encode for Avc420BitmapStream<'_> {
85+
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
86+
ensure_size!(in: dst, size: self.size());
87+
88+
dst.write_u32(cast_length!("len", self.rectangles.len())?);
89+
for rectangle in &self.rectangles {
90+
rectangle.encode(dst)?;
91+
}
92+
for quant_qual_val in &self.quant_qual_vals {
93+
quant_qual_val.encode(dst)?;
94+
}
95+
dst.write_slice(self.data);
96+
97+
Ok(())
98+
}
99+
100+
fn name(&self) -> &'static str {
101+
Self::NAME
102+
}
103+
104+
fn size(&self) -> usize {
105+
// Each rectangle is 8 bytes and 2 bytes for each quant val
106+
Self::FIXED_PART_SIZE + self.rectangles.len() * 10 + self.data.len()
107+
}
108+
}
109+
110+
impl<'de> Decode<'de> for Avc420BitmapStream<'de> {
111+
fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
112+
ensure_fixed_part_size!(in: src);
113+
114+
let num_regions = src.read_u32();
115+
let mut rectangles = Vec::with_capacity(num_regions as usize);
116+
let mut quant_qual_vals = Vec::with_capacity(num_regions as usize);
117+
for _ in 0..num_regions {
118+
rectangles.push(InclusiveRectangle::decode(src)?);
119+
}
120+
for _ in 0..num_regions {
121+
quant_qual_vals.push(QuantQuality::decode(src)?);
122+
}
123+
let data = src.remaining();
124+
Ok(Avc420BitmapStream {
125+
rectangles,
126+
quant_qual_vals,
127+
data,
128+
})
129+
}
130+
}
131+
132+
bitflags! {
133+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
134+
pub struct Encoding: u8 {
135+
const LUMA_AND_CHROMA = 0x00;
136+
const LUMA = 0x01;
137+
const CHROMA = 0x02;
138+
}
139+
}
140+
141+
#[derive(Debug, Clone, PartialEq, Eq)]
142+
pub struct Avc444BitmapStream<'a> {
143+
pub encoding: Encoding,
144+
pub stream1: Avc420BitmapStream<'a>,
145+
pub stream2: Option<Avc420BitmapStream<'a>>,
146+
}
147+
148+
impl Avc444BitmapStream<'_> {
149+
const NAME: &'static str = "Avc444BitmapStream";
150+
151+
const FIXED_PART_SIZE: usize = 4 /* streamInfo */;
152+
}
153+
154+
impl Encode for Avc444BitmapStream<'_> {
155+
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
156+
ensure_fixed_part_size!(in: dst);
157+
158+
let mut stream_info = 0u32;
159+
stream_info.set_bits(0..30, cast_length!("stream1size", self.stream1.size())?);
160+
stream_info.set_bits(30..32, self.encoding.bits().into());
161+
dst.write_u32(stream_info);
162+
self.stream1.encode(dst)?;
163+
if let Some(stream) = self.stream2.as_ref() {
164+
stream.encode(dst)?;
165+
}
166+
Ok(())
167+
}
168+
169+
fn name(&self) -> &'static str {
170+
Self::NAME
171+
}
172+
173+
fn size(&self) -> usize {
174+
let stream2_size = if let Some(stream) = self.stream2.as_ref() {
175+
stream.size()
176+
} else {
177+
0
178+
};
179+
180+
Self::FIXED_PART_SIZE + self.stream1.size() + stream2_size
181+
}
182+
}
183+
184+
impl<'de> Decode<'de> for Avc444BitmapStream<'de> {
185+
fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
186+
ensure_fixed_part_size!(in: src);
187+
188+
let stream_info = src.read_u32();
189+
let stream_len = stream_info.get_bits(0..30);
190+
let encoding = Encoding::from_bits_truncate(stream_info.get_bits(30..32).try_into().unwrap());
191+
192+
if stream_len == 0 {
193+
if encoding == Encoding::LUMA_AND_CHROMA {
194+
return Err(invalid_field_err!("encoding", "invalid encoding"));
195+
}
196+
197+
let stream1 = Avc420BitmapStream::decode(src)?;
198+
Ok(Avc444BitmapStream {
199+
encoding,
200+
stream1,
201+
stream2: None,
202+
})
203+
} else {
204+
let (mut stream1, mut stream2) = src.split_at(stream_len as usize);
205+
let stream1 = Avc420BitmapStream::decode(&mut stream1)?;
206+
let stream2 = if encoding == Encoding::LUMA_AND_CHROMA {
207+
Some(Avc420BitmapStream::decode(&mut stream2)?)
208+
} else {
209+
None
210+
};
211+
Ok(Avc444BitmapStream {
212+
encoding,
213+
stream1,
214+
stream2,
215+
})
216+
}
217+
}
218+
}

0 commit comments

Comments
 (0)