feat(server)!: add server_codecs_capabilities()

Teach the server to support customizable codecs set. Use the same
logic/parsing as the client codecs configuration.

Replace "with_remote_fx" with "codecs".

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
This commit is contained in:
Marc-André Lureau
2025-07-24 06:53:10 -04:00
committed by Benoît Cortier
parent 727c9b7710
commit d3aaa43c23
5 changed files with 110 additions and 62 deletions
@@ -32,9 +32,9 @@ pub use self::bitmap_cache::{
BitmapCache, BitmapCacheRev2, CacheEntry, CacheFlags, CellInfo, BITMAP_CACHE_ENTRIES_NUM,
};
pub use self::bitmap_codecs::{
client_codecs_capabilities, BitmapCodecs, CaptureFlags, Codec, CodecId, CodecProperty, EntropyBits, Guid, NsCodec,
RemoteFxContainer, RfxCaps, RfxCapset, RfxClientCapsContainer, RfxICap, RfxICapFlags, CODEC_ID_NONE,
CODEC_ID_REMOTEFX,
client_codecs_capabilities, server_codecs_capabilities, BitmapCodecs, CaptureFlags, Codec, CodecId, CodecProperty,
EntropyBits, Guid, NsCodec, RemoteFxContainer, RfxCaps, RfxCapset, RfxClientCapsContainer, RfxICap, RfxICapFlags,
CODEC_ID_NONE, CODEC_ID_REMOTEFX,
};
pub use self::brush::{Brush, SupportLevel};
pub use self::frame_acknowledge::FrameAcknowledge;
@@ -2,6 +2,7 @@
mod tests;
use core::fmt::{self, Debug};
use std::collections::HashMap;
use bitflags::bitflags;
use ironrdp_core::{
@@ -641,6 +642,30 @@ impl CodecId {
}
}
fn parse_codecs_config<'a>(codecs: &'a [&'a str]) -> Result<HashMap<&'a str, bool>, String> {
let mut result = HashMap::new();
for &codec_str in codecs {
if let Some(colon_index) = codec_str.find(':') {
let codec_name = &codec_str[0..colon_index];
let state_str = &codec_str[colon_index + 1..];
let state = match state_str {
"on" => true,
"off" => false,
_ => return Err(format!("Unhandled configuration: {state_str}")),
};
result.insert(codec_name, state);
} else {
// No colon found, assume it's "on"
result.insert(codec_str, true);
}
}
Ok(result)
}
/// This function generates a list of client codec capabilities based on the
/// provided configuration.
///
@@ -659,32 +684,6 @@ impl CodecId {
/// A vector of `Codec` structs representing the codec capabilities, or an error
/// suitable for CLI.
pub fn client_codecs_capabilities(config: &[&str]) -> Result<BitmapCodecs, String> {
use std::collections::HashMap;
fn parse_codecs_config<'a>(codecs: &'a [&'a str]) -> Result<HashMap<&'a str, bool>, String> {
let mut result = HashMap::new();
for &codec_str in codecs {
if let Some(colon_index) = codec_str.find(':') {
let codec_name = &codec_str[0..colon_index];
let state_str = &codec_str[colon_index + 1..];
let state = match state_str {
"on" => true,
"off" => false,
_ => return Err(format!("Unhandled configuration: {state_str}")),
};
result.insert(codec_name, state);
} else {
// No colon found, assume it's "on"
result.insert(codec_str, true);
}
}
Ok(result)
}
if config.contains(&"help") {
return Err(r#"
List of codecs:
@@ -692,6 +691,7 @@ List of codecs:
"#
.to_owned());
}
let mut config = parse_codecs_config(config)?;
let mut codecs = vec![];
@@ -715,3 +715,51 @@ List of codecs:
Ok(BitmapCodecs(codecs))
}
/// This function generates a list of server codec capabilities based on the
/// provided configuration.
///
/// # Arguments
///
/// * `config` - A slice of string slices that specifies which codecs to include
/// in the capabilities. Codecs can be explicitly turned on ("codec:on") or
/// off ("codec:off").
///
/// # List of codecs
///
/// * `remotefx` (on by default)
///
/// # Returns
///
/// A vector of `Codec` structs representing the codec capabilities, or an help message suitable
/// for CLI errors.
pub fn server_codecs_capabilities(config: &[&str]) -> Result<BitmapCodecs, String> {
if config.contains(&"help") {
return Err(r#"
List of codecs:
- `remotefx` (on by default)
"#
.to_owned());
}
let mut config = parse_codecs_config(config)?;
let mut codecs = vec![];
if config.remove("remotefx").unwrap_or(true) {
codecs.push(Codec {
id: 0,
property: CodecProperty::RemoteFx(RemoteFxContainer::ServerContainer(1)),
});
codecs.push(Codec {
id: 0,
property: CodecProperty::ImageRemoteFx(RemoteFxContainer::ServerContainer(1)),
});
}
let codec_names = config.keys().copied().collect::<Vec<_>>().join(", ");
if !codec_names.is_empty() {
return Err(format!("Unknown codecs: {codec_names}"));
}
Ok(BitmapCodecs(codecs))
}
+7 -6
View File
@@ -1,6 +1,7 @@
use core::net::SocketAddr;
use anyhow::Result;
use ironrdp_pdu::rdp::capability_sets::{server_codecs_capabilities, BitmapCodecs};
use tokio_rustls::TlsAcceptor;
use super::clipboard::CliprdrServerFactory;
@@ -25,7 +26,7 @@ pub struct WantsDisplay {
pub struct BuilderDone {
addr: SocketAddr,
security: RdpServerSecurity,
with_remote_fx: bool,
codecs: BitmapCodecs,
handler: Box<dyn RdpServerInputHandler>,
display: Box<dyn RdpServerDisplay>,
cliprdr_factory: Option<Box<dyn CliprdrServerFactory>>,
@@ -124,7 +125,7 @@ impl RdpServerBuilder<WantsDisplay> {
display: Box::new(display),
sound_factory: None,
cliprdr_factory: None,
with_remote_fx: true,
codecs: server_codecs_capabilities(&[]).unwrap(),
},
}
}
@@ -138,7 +139,7 @@ impl RdpServerBuilder<WantsDisplay> {
display: Box::new(NoopDisplay),
sound_factory: None,
cliprdr_factory: None,
with_remote_fx: true,
codecs: server_codecs_capabilities(&[]).unwrap(),
},
}
}
@@ -155,8 +156,8 @@ impl RdpServerBuilder<BuilderDone> {
self
}
pub fn with_remote_fx(mut self, enabled: bool) -> Self {
self.state.with_remote_fx = enabled;
pub fn with_bitmap_codecs(mut self, codecs: BitmapCodecs) -> Self {
self.state.codecs = codecs;
self
}
@@ -165,7 +166,7 @@ impl RdpServerBuilder<BuilderDone> {
RdpServerOptions {
addr: self.state.addr,
security: self.state.security,
with_remote_fx: self.state.with_remote_fx,
codecs: self.state.codecs,
},
self.state.handler,
self.state.display,
+1 -18
View File
@@ -12,7 +12,7 @@ pub(crate) fn capabilities(opts: &RdpServerOptions, size: DesktopSize) -> Vec<ca
capability_sets::CapabilitySet::Input(input_capabilities()),
capability_sets::CapabilitySet::VirtualChannel(virtual_channel_capabilities()),
capability_sets::CapabilitySet::MultiFragmentUpdate(multifragment_update()),
capability_sets::CapabilitySet::BitmapCodecs(bitmap_codecs(opts.with_remote_fx)),
capability_sets::CapabilitySet::BitmapCodecs(opts.codecs.clone()),
]
}
@@ -87,20 +87,3 @@ fn multifragment_update() -> capability_sets::MultifragmentUpdate {
max_request_size: 16_777_215,
}
}
fn bitmap_codecs(with_remote_fx: bool) -> capability_sets::BitmapCodecs {
let mut codecs = Vec::new();
if with_remote_fx {
codecs.push(capability_sets::Codec {
id: 0,
property: capability_sets::CodecProperty::RemoteFx(capability_sets::RemoteFxContainer::ServerContainer(1)),
});
codecs.push(capability_sets::Codec {
id: 0,
property: capability_sets::CodecProperty::ImageRemoteFx(
capability_sets::RemoteFxContainer::ServerContainer(1),
),
});
}
capability_sets::BitmapCodecs(codecs)
}
+25 -9
View File
@@ -13,7 +13,7 @@ use ironrdp_displaycontrol::server::{DisplayControlHandler, DisplayControlServer
use ironrdp_pdu::input::fast_path::{FastPathInput, FastPathInputEvent};
use ironrdp_pdu::input::InputEventPdu;
use ironrdp_pdu::mcs::{SendDataIndication, SendDataRequest};
use ironrdp_pdu::rdp::capability_sets::{BitmapCodecs, CapabilitySet, CmdFlags, GeneralExtraFlags};
use ironrdp_pdu::rdp::capability_sets::{BitmapCodecs, CapabilitySet, CmdFlags, CodecProperty, GeneralExtraFlags};
pub use ironrdp_pdu::rdp::client_info::Credentials;
use ironrdp_pdu::rdp::headers::{ServerDeactivateAll, ShareControlPdu};
use ironrdp_pdu::x224::X224;
@@ -38,7 +38,23 @@ use crate::{builder, capabilities, SoundServerFactory};
pub struct RdpServerOptions {
pub addr: SocketAddr,
pub security: RdpServerSecurity,
pub with_remote_fx: bool,
pub codecs: BitmapCodecs,
}
impl RdpServerOptions {
fn has_image_remote_fx(&self) -> bool {
self.codecs
.0
.iter()
.any(|codec| matches!(codec.property, CodecProperty::ImageRemoteFx(_)))
}
fn has_remote_fx(&self) -> bool {
self.codecs
.0
.iter()
.any(|codec| matches!(codec.property, CodecProperty::RemoteFx(_)))
}
}
#[derive(Clone)]
@@ -711,21 +727,21 @@ impl RdpServer {
// We should distinguish parameters for both modes,
// and somehow choose the "best", instead of picking
// the last parsed here.
rdp::capability_sets::CodecProperty::RemoteFx(
rdp::capability_sets::RemoteFxContainer::ClientContainer(c),
) if self.opts.with_remote_fx => {
CodecProperty::RemoteFx(rdp::capability_sets::RemoteFxContainer::ClientContainer(c))
if self.opts.has_remote_fx() =>
{
for caps in c.caps_data.0 .0 {
update_codecs.set_remotefx(Some((caps.entropy_bits, codec.id)));
}
}
rdp::capability_sets::CodecProperty::ImageRemoteFx(
rdp::capability_sets::RemoteFxContainer::ClientContainer(c),
) if self.opts.with_remote_fx => {
CodecProperty::ImageRemoteFx(rdp::capability_sets::RemoteFxContainer::ClientContainer(
c,
)) if self.opts.has_image_remote_fx() => {
for caps in c.caps_data.0 .0 {
update_codecs.set_remotefx(Some((caps.entropy_bits, codec.id)));
}
}
rdp::capability_sets::CodecProperty::NsCodec(_) => (),
CodecProperty::NsCodec(_) => (),
_ => (),
}
}