ironrdp_cliprdr/pdu/
capabilities.rs

1use bitflags::bitflags;
2use ironrdp_core::{
3    cast_int, cast_length, ensure_fixed_part_size, ensure_size, invalid_field_err, Decode, DecodeError, DecodeResult,
4    Encode, EncodeResult, ReadCursor, WriteCursor,
5};
6use ironrdp_pdu::{impl_pdu_pod, read_padding, write_padding};
7
8use crate::pdu::PartialHeader;
9
10/// Represents `CLIPRDR_CAPS`
11#[derive(Debug, Default, Clone, PartialEq, Eq)]
12pub struct Capabilities {
13    pub capabilities: Vec<CapabilitySet>,
14}
15
16impl_pdu_pod!(Capabilities);
17
18impl Capabilities {
19    const NAME: &'static str = "CLIPRDR_CAPS";
20    const FIXED_PART_SIZE: usize = 2 /* capsLen */ + 2 /* padding */;
21
22    fn inner_size(&self) -> usize {
23        Self::FIXED_PART_SIZE + self.capabilities.iter().map(|c| c.size()).sum::<usize>()
24    }
25
26    pub fn new(version: ClipboardProtocolVersion, general_flags: ClipboardGeneralCapabilityFlags) -> Self {
27        let capabilities = vec![CapabilitySet::General(GeneralCapabilitySet { version, general_flags })];
28
29        Self { capabilities }
30    }
31
32    pub fn flags(&self) -> ClipboardGeneralCapabilityFlags {
33        // There is only one capability set in the capabilities field in current CLIPRDR version
34        self.capabilities
35            .first()
36            .map(|set| set.general().general_flags)
37            .unwrap_or_else(ClipboardGeneralCapabilityFlags::empty)
38    }
39
40    pub fn version(&self) -> ClipboardProtocolVersion {
41        self.capabilities
42            .first()
43            .map(|set| set.general().version)
44            .unwrap_or(ClipboardProtocolVersion::V1)
45    }
46
47    pub fn downgrade(&mut self, server_caps: &Self) {
48        let client_flags = self.flags();
49        let server_flags = self.flags();
50
51        let flags = client_flags & server_flags;
52        let version = self.version().downgrade(server_caps.version());
53
54        self.capabilities = vec![CapabilitySet::General(GeneralCapabilitySet {
55            version,
56            general_flags: flags,
57        })];
58    }
59}
60
61impl Encode for Capabilities {
62    fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
63        let header = PartialHeader::new(cast_int!("dataLen", self.inner_size())?);
64        header.encode(dst)?;
65
66        ensure_size!(in: dst, size: self.inner_size());
67
68        dst.write_u16(cast_length!(Self::NAME, "cCapabilitiesSets", self.capabilities.len())?);
69        write_padding!(dst, 2);
70
71        for capability in &self.capabilities {
72            capability.encode(dst)?;
73        }
74
75        Ok(())
76    }
77
78    fn name(&self) -> &'static str {
79        Self::NAME
80    }
81
82    fn size(&self) -> usize {
83        self.inner_size() + PartialHeader::SIZE
84    }
85}
86
87impl<'de> Decode<'de> for Capabilities {
88    fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
89        let _header = PartialHeader::decode(src)?;
90
91        ensure_fixed_part_size!(in: src);
92        let capabilities_count = src.read_u16();
93        read_padding!(src, 2);
94
95        let mut capabilities = Vec::with_capacity(usize::from(capabilities_count));
96
97        for _ in 0..capabilities_count {
98            let caps = CapabilitySet::decode(src)?;
99            capabilities.push(caps);
100        }
101
102        Ok(Self { capabilities })
103    }
104}
105
106/// Represents `CLIPRDR_CAPS_SET`
107#[derive(Debug, Clone, PartialEq, Eq)]
108pub enum CapabilitySet {
109    General(GeneralCapabilitySet),
110}
111
112impl_pdu_pod!(CapabilitySet);
113
114impl CapabilitySet {
115    const NAME: &'static str = "CLIPRDR_CAPS_SET";
116    const FIXED_PART_SIZE: usize = 2 /* type */ + 2 /* len */;
117
118    const CAPSTYPE_GENERAL: u16 = 0x0001;
119
120    pub fn general(&self) -> &GeneralCapabilitySet {
121        match self {
122            Self::General(value) => value,
123        }
124    }
125}
126
127impl From<GeneralCapabilitySet> for CapabilitySet {
128    fn from(value: GeneralCapabilitySet) -> Self {
129        Self::General(value)
130    }
131}
132
133impl Encode for CapabilitySet {
134    fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
135        let (caps, length) = match self {
136            Self::General(value) => {
137                let length = value.size() + Self::FIXED_PART_SIZE;
138                (value, length)
139            }
140        };
141
142        ensure_size!(in: dst, size: length);
143        dst.write_u16(Self::CAPSTYPE_GENERAL);
144        dst.write_u16(cast_int!("lengthCapability", length)?);
145        caps.encode(dst)
146    }
147
148    fn name(&self) -> &'static str {
149        Self::NAME
150    }
151
152    fn size(&self) -> usize {
153        let variable_size = match self {
154            Self::General(value) => value.size(),
155        };
156
157        Self::FIXED_PART_SIZE + variable_size
158    }
159}
160
161impl<'de> Decode<'de> for CapabilitySet {
162    fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
163        ensure_fixed_part_size!(in: src);
164
165        let caps_type = src.read_u16();
166        let _length = src.read_u16();
167
168        match caps_type {
169            Self::CAPSTYPE_GENERAL => {
170                let general = GeneralCapabilitySet::decode(src)?;
171                Ok(Self::General(general))
172            }
173            _ => Err(invalid_field_err!(
174                "capabilitySetType",
175                "invalid clipboard capability set type"
176            )),
177        }
178    }
179}
180
181/// Represents `CLIPRDR_GENERAL_CAPABILITY` without header
182#[derive(Debug, Clone, PartialEq, Eq)]
183pub struct GeneralCapabilitySet {
184    pub version: ClipboardProtocolVersion,
185    pub general_flags: ClipboardGeneralCapabilityFlags,
186}
187
188impl GeneralCapabilitySet {
189    const NAME: &'static str = "CLIPRDR_GENERAL_CAPABILITY";
190    const FIXED_PART_SIZE: usize = 4 /* version */ + 4 /* flags */;
191}
192
193impl Encode for GeneralCapabilitySet {
194    fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
195        ensure_fixed_part_size!(in: dst);
196
197        dst.write_u32(self.version.into());
198        dst.write_u32(self.general_flags.bits());
199
200        Ok(())
201    }
202
203    fn name(&self) -> &'static str {
204        Self::NAME
205    }
206
207    fn size(&self) -> usize {
208        Self::FIXED_PART_SIZE
209    }
210}
211
212impl<'de> Decode<'de> for GeneralCapabilitySet {
213    fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
214        ensure_fixed_part_size!(in: src);
215
216        let version: ClipboardProtocolVersion = src.read_u32().try_into()?;
217        let general_flags = ClipboardGeneralCapabilityFlags::from_bits_truncate(src.read_u32());
218
219        Ok(Self { version, general_flags })
220    }
221}
222
223/// Specifies the `Remote Desktop Protocol: Clipboard Virtual Channel Extension` version number.
224/// This field is for informational purposes and MUST NOT be used to make protocol capability
225/// decisions. The actual features supported are specified via [`ClipboardGeneralCapabilityFlags`]
226#[derive(Debug, Clone, Copy, PartialEq, Eq)]
227pub enum ClipboardProtocolVersion {
228    V1,
229    V2,
230}
231
232impl ClipboardProtocolVersion {
233    const VERSION_VALUE_V1: u32 = 0x0000_0001;
234    const VERSION_VALUE_V2: u32 = 0x0000_0002;
235
236    #[must_use]
237    pub fn downgrade(self, other: Self) -> Self {
238        if self != other {
239            return Self::V1;
240        }
241        self
242    }
243}
244
245impl From<ClipboardProtocolVersion> for u32 {
246    fn from(version: ClipboardProtocolVersion) -> Self {
247        match version {
248            ClipboardProtocolVersion::V1 => ClipboardProtocolVersion::VERSION_VALUE_V1,
249            ClipboardProtocolVersion::V2 => ClipboardProtocolVersion::VERSION_VALUE_V2,
250        }
251    }
252}
253
254impl TryFrom<u32> for ClipboardProtocolVersion {
255    type Error = DecodeError;
256
257    fn try_from(value: u32) -> Result<Self, Self::Error> {
258        match value {
259            Self::VERSION_VALUE_V1 => Ok(Self::V1),
260            Self::VERSION_VALUE_V2 => Ok(Self::V2),
261            _ => Err(invalid_field_err!("version", "Invalid clipboard capabilities version")),
262        }
263    }
264}
265
266bitflags! {
267    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
268    pub struct ClipboardGeneralCapabilityFlags: u32 {
269        /// The Long Format Name variant of the Format List PDU is supported
270        /// for exchanging updated format names. If this flag is not set, the
271        /// Short Format Name variant MUST be used. If this flag is set by both
272        /// protocol endpoints, then the Long Format Name variant MUST be
273        /// used.
274        const USE_LONG_FORMAT_NAMES = 0x0000_0002;
275        /// File copy and paste using stream-based operations are supported
276        /// using the File Contents Request PDU and File Contents Response
277        /// PDU.
278        const STREAM_FILECLIP_ENABLED = 0x0000_0004;
279        /// Indicates that any description of files to copy and paste MUST NOT
280        /// include the source path of the files.
281        const FILECLIP_NO_FILE_PATHS = 0x0000_0008;
282        /// Locking and unlocking of File Stream data on the clipboard is
283        /// supported using the Lock Clipboard Data PDU and Unlock Clipboard
284        /// Data PDU.
285        const CAN_LOCK_CLIPDATA = 0x0000_0010;
286        /// Indicates support for transferring files that are larger than
287        /// 4,294,967,295 bytes in size. If this flag is not set, then only files of
288        /// size less than or equal to 4,294,967,295 bytes can be exchanged
289        /// using the File Contents Request PDU and File Contents
290        /// Response PDU.
291        const HUGE_FILE_SUPPORT_ENABLED = 0x0000_0020;
292    }
293}