ironrdp_pdu/gcc/core_data/
client.rs

1use bitflags::bitflags;
2use ironrdp_core::{
3    ensure_fixed_part_size, ensure_size, invalid_field_err, write_padding, Decode, DecodeResult, Encode, EncodeResult,
4    ReadCursor, WriteCursor,
5};
6use num_derive::{FromPrimitive, ToPrimitive};
7use num_traits::{FromPrimitive as _, ToPrimitive as _};
8use tap::Pipe as _;
9
10use super::{RdpVersion, VERSION_SIZE};
11use crate::nego::SecurityProtocol;
12use crate::utils;
13
14pub const IME_FILE_NAME_SIZE: usize = 64;
15
16const DESKTOP_WIDTH_SIZE: usize = 2;
17const DESKTOP_HEIGHT_SIZE: usize = 2;
18const COLOR_DEPTH_SIZE: usize = 2;
19const SEC_ACCESS_SEQUENCE_SIZE: usize = 2;
20const KEYBOARD_LAYOUT_SIZE: usize = 4;
21const CLIENT_BUILD_SIZE: usize = 4;
22const CLIENT_NAME_SIZE: usize = 32;
23const KEYBOARD_TYPE_SIZE: usize = 4;
24const KEYBOARD_SUB_TYPE_SIZE: usize = 4;
25const KEYBOARD_FUNCTIONAL_KEYS_COUNT_SIZE: usize = 4;
26
27const POST_BETA_COLOR_DEPTH_SIZE: usize = 2;
28const CLIENT_PRODUCT_ID_SIZE: usize = 2;
29const SERIAL_NUMBER_SIZE: usize = 4;
30const HIGH_COLOR_DEPTH_SIZE: usize = 2;
31const SUPPORTED_COLOR_DEPTHS_SIZE: usize = 2;
32const EARLY_CAPABILITY_FLAGS_SIZE: usize = 2;
33const DIG_PRODUCT_ID_SIZE: usize = 64;
34const CONNECTION_TYPE_SIZE: usize = 1;
35const PADDING_SIZE: usize = 1;
36const SERVER_SELECTED_PROTOCOL_SIZE: usize = 4;
37const DESKTOP_PHYSICAL_WIDTH_SIZE: usize = 4;
38const DESKTOP_PHYSICAL_HEIGHT_SIZE: usize = 4;
39const DESKTOP_ORIENTATION_SIZE: usize = 2;
40const DESKTOP_SCALE_FACTOR_SIZE: usize = 4;
41const DEVICE_SCALE_FACTOR_SIZE: usize = 4;
42
43/// 2.2.1.3.2 Client Core Data (TS_UD_CS_CORE) (required part)
44///
45/// [2.2.1.3.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/00f1da4a-ee9c-421a-852f-c19f92343d73
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct ClientCoreData {
48    pub version: RdpVersion,
49    pub desktop_width: u16,
50    pub desktop_height: u16,
51    /// The requested color depth. Values in this field MUST be ignored if the postBeta2ColorDepth field is present.
52    pub color_depth: ColorDepth,
53    pub sec_access_sequence: SecureAccessSequence,
54    pub keyboard_layout: u32,
55    pub client_build: u32,
56    pub client_name: String,
57    pub keyboard_type: KeyboardType,
58    pub keyboard_subtype: u32,
59    pub keyboard_functional_keys_count: u32,
60    pub ime_file_name: String,
61    pub optional_data: ClientCoreOptionalData,
62}
63
64impl ClientCoreData {
65    const NAME: &'static str = "ClientCoreData";
66
67    const FIXED_PART_SIZE: usize = VERSION_SIZE
68        + DESKTOP_WIDTH_SIZE
69        + DESKTOP_HEIGHT_SIZE
70        + COLOR_DEPTH_SIZE
71        + SEC_ACCESS_SEQUENCE_SIZE
72        + KEYBOARD_LAYOUT_SIZE
73        + CLIENT_BUILD_SIZE
74        + CLIENT_NAME_SIZE
75        + KEYBOARD_TYPE_SIZE
76        + KEYBOARD_SUB_TYPE_SIZE
77        + KEYBOARD_FUNCTIONAL_KEYS_COUNT_SIZE
78        + IME_FILE_NAME_SIZE;
79
80    pub fn client_color_depth(&self) -> ClientColorDepth {
81        if let Some(high_color_depth) = self.optional_data.high_color_depth {
82            if let Some(early_capability_flags) = self.optional_data.early_capability_flags {
83                if early_capability_flags.contains(ClientEarlyCapabilityFlags::WANT_32_BPP_SESSION) {
84                    ClientColorDepth::Bpp32
85                } else {
86                    From::from(high_color_depth)
87                }
88            } else {
89                From::from(high_color_depth)
90            }
91        } else if let Some(post_beta_color_depth) = self.optional_data.post_beta2_color_depth {
92            From::from(post_beta_color_depth)
93        } else {
94            From::from(self.color_depth)
95        }
96    }
97}
98
99impl Encode for ClientCoreData {
100    fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
101        ensure_size!(in: dst, size: self.size());
102
103        let mut client_name_dst = utils::to_utf16_bytes(self.client_name.as_ref());
104        client_name_dst.resize(CLIENT_NAME_SIZE - 2, 0);
105        let mut ime_file_name_dst = utils::to_utf16_bytes(self.ime_file_name.as_ref());
106        ime_file_name_dst.resize(IME_FILE_NAME_SIZE - 2, 0);
107
108        dst.write_u32(self.version.0);
109        dst.write_u16(self.desktop_width);
110        dst.write_u16(self.desktop_height);
111        dst.write_u16(self.color_depth.to_u16().unwrap());
112        dst.write_u16(self.sec_access_sequence.to_u16().unwrap());
113        dst.write_u32(self.keyboard_layout);
114        dst.write_u32(self.client_build);
115        dst.write_slice(client_name_dst.as_ref());
116        dst.write_u16(0); // client name UTF-16 null terminator
117        dst.write_u32(self.keyboard_type.to_u32().unwrap());
118        dst.write_u32(self.keyboard_subtype);
119        dst.write_u32(self.keyboard_functional_keys_count);
120        dst.write_slice(ime_file_name_dst.as_ref());
121        dst.write_u16(0); // ime file name UTF-16 null terminator
122
123        self.optional_data.encode(dst)
124    }
125
126    fn name(&self) -> &'static str {
127        Self::NAME
128    }
129
130    fn size(&self) -> usize {
131        Self::FIXED_PART_SIZE + self.optional_data.size()
132    }
133}
134
135impl<'de> Decode<'de> for ClientCoreData {
136    fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
137        ensure_fixed_part_size!(in: src);
138
139        let version = src.read_u32().pipe(RdpVersion);
140        let desktop_width = src.read_u16();
141        let desktop_height = src.read_u16();
142        let color_depth = src
143            .read_u16()
144            .pipe(ColorDepth::from_u16)
145            .ok_or_else(|| invalid_field_err!("colorDepth", "invalid color depth"))?;
146        let sec_access_sequence = src
147            .read_u16()
148            .pipe(SecureAccessSequence::from_u16)
149            .ok_or_else(|| invalid_field_err!("secAccessSequence", "invalid secure access sequence"))?;
150        let keyboard_layout = src.read_u32();
151        let client_build = src.read_u32();
152
153        let client_name_buffer = src.read_slice(CLIENT_NAME_SIZE);
154        let client_name = utils::from_utf16_bytes(client_name_buffer)
155            .trim_end_matches('\u{0}')
156            .into();
157
158        let keyboard_type = src
159            .read_u32()
160            .pipe(KeyboardType::from_u32)
161            .ok_or_else(|| invalid_field_err!("keyboardType", "invalid keyboard type"))?;
162        let keyboard_subtype = src.read_u32();
163        let keyboard_functional_keys_count = src.read_u32();
164
165        let ime_file_name_buffer = src.read_slice(IME_FILE_NAME_SIZE);
166        let ime_file_name = utils::from_utf16_bytes(ime_file_name_buffer)
167            .trim_end_matches('\u{0}')
168            .into();
169
170        let optional_data = ClientCoreOptionalData::decode(src)?;
171
172        Ok(Self {
173            version,
174            desktop_width,
175            desktop_height,
176            color_depth,
177            sec_access_sequence,
178            keyboard_layout,
179            client_build,
180            client_name,
181            keyboard_type,
182            keyboard_subtype,
183            keyboard_functional_keys_count,
184            ime_file_name,
185            optional_data,
186        })
187    }
188}
189
190/// 2.2.1.3.2 Client Core Data (TS_UD_CS_CORE) (optional part)
191///
192/// For every field in this structure, the previous fields MUST be present in order to be a valid structure.
193/// It is incumbent on the user of this structure to ensure that the structure is valid.
194///
195/// [2.2.1.3.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/00f1da4a-ee9c-421a-852f-c19f92343d73
196#[derive(Debug, Clone, Default, PartialEq, Eq)]
197pub struct ClientCoreOptionalData {
198    /// The requested color depth. Values in this field MUST be ignored if the highColorDepth field is present.
199    pub post_beta2_color_depth: Option<ColorDepth>,
200    pub client_product_id: Option<u16>,
201    pub serial_number: Option<u32>,
202    /// The requested color depth.
203    pub high_color_depth: Option<HighColorDepth>,
204    /// Specifies the high color depths that the client is capable of supporting.
205    pub supported_color_depths: Option<SupportedColorDepths>,
206    pub early_capability_flags: Option<ClientEarlyCapabilityFlags>,
207    pub dig_product_id: Option<String>,
208    pub connection_type: Option<ConnectionType>,
209    pub server_selected_protocol: Option<SecurityProtocol>,
210    pub desktop_physical_width: Option<u32>,
211    pub desktop_physical_height: Option<u32>,
212    pub desktop_orientation: Option<u16>,
213    pub desktop_scale_factor: Option<u32>,
214    pub device_scale_factor: Option<u32>,
215}
216
217impl ClientCoreOptionalData {
218    const NAME: &'static str = "ClientCoreOptionalData";
219}
220
221impl Encode for ClientCoreOptionalData {
222    fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
223        ensure_size!(in: dst, size: self.size());
224
225        if let Some(value) = self.post_beta2_color_depth {
226            dst.write_u16(value.to_u16().unwrap());
227        }
228
229        if let Some(value) = self.client_product_id {
230            if self.post_beta2_color_depth.is_none() {
231                return Err(invalid_field_err!(
232                    "postBeta2ColorDepth",
233                    "postBeta2ColorDepth must be present"
234                ));
235            }
236            dst.write_u16(value);
237        }
238
239        if let Some(value) = self.serial_number {
240            if self.client_product_id.is_none() {
241                return Err(invalid_field_err!("clientProductId", "clientProductId must be present"));
242            }
243            dst.write_u32(value);
244        }
245
246        if let Some(value) = self.high_color_depth {
247            if self.serial_number.is_none() {
248                return Err(invalid_field_err!("serialNumber", "serialNumber must be present"));
249            }
250            dst.write_u16(value.to_u16().unwrap());
251        }
252
253        if let Some(value) = self.supported_color_depths {
254            if self.high_color_depth.is_none() {
255                return Err(invalid_field_err!("highColorDepth", "highColorDepth must be present"));
256            }
257            dst.write_u16(value.bits());
258        }
259
260        if let Some(value) = self.early_capability_flags {
261            if self.supported_color_depths.is_none() {
262                return Err(invalid_field_err!(
263                    "supportedColorDepths",
264                    "supportedColorDepths must be present"
265                ));
266            }
267            dst.write_u16(value.bits());
268        }
269
270        if let Some(ref value) = self.dig_product_id {
271            if self.early_capability_flags.is_none() {
272                return Err(invalid_field_err!(
273                    "earlyCapabilityFlags",
274                    "earlyCapabilityFlags must be present"
275                ));
276            }
277            let mut dig_product_id_buffer = utils::to_utf16_bytes(value);
278            dig_product_id_buffer.resize(DIG_PRODUCT_ID_SIZE - 2, 0);
279            dig_product_id_buffer.extend_from_slice([0; 2].as_ref()); // UTF-16 null terminator
280
281            dst.write_slice(dig_product_id_buffer.as_ref())
282        }
283
284        if let Some(value) = self.connection_type {
285            if self.dig_product_id.is_none() {
286                return Err(invalid_field_err!("digProductId", "digProductId must be present"));
287            }
288            dst.write_u8(value.to_u8().unwrap());
289            write_padding!(dst, 1);
290        }
291
292        if let Some(value) = self.server_selected_protocol {
293            if self.connection_type.is_none() {
294                return Err(invalid_field_err!("connectionType", "connectionType must be present"));
295            }
296            dst.write_u32(value.bits())
297        }
298
299        if let Some(value) = self.desktop_physical_width {
300            if self.server_selected_protocol.is_none() {
301                return Err(invalid_field_err!(
302                    "serverSelectedProtocol",
303                    "serverSelectedProtocol must be present"
304                ));
305            }
306            dst.write_u32(value);
307        }
308
309        if let Some(value) = self.desktop_physical_height {
310            if self.desktop_physical_width.is_none() {
311                return Err(invalid_field_err!(
312                    "desktopPhysicalWidth",
313                    "desktopPhysicalWidth must be present"
314                ));
315            }
316            dst.write_u32(value);
317        }
318
319        if let Some(value) = self.desktop_orientation {
320            if self.desktop_physical_height.is_none() {
321                return Err(invalid_field_err!(
322                    "desktopPhysicalHeight",
323                    "desktopPhysicalHeight must be present"
324                ));
325            }
326            dst.write_u16(value);
327        }
328
329        if let Some(value) = self.desktop_scale_factor {
330            if self.desktop_orientation.is_none() {
331                return Err(invalid_field_err!(
332                    "desktopOrientation",
333                    "desktopOrientation must be present"
334                ));
335            }
336            dst.write_u32(value);
337        }
338
339        if let Some(value) = self.device_scale_factor {
340            if self.desktop_scale_factor.is_none() {
341                return Err(invalid_field_err!(
342                    "desktopScaleFactor",
343                    "desktopScaleFactor must be present"
344                ));
345            }
346            dst.write_u32(value);
347        }
348
349        Ok(())
350    }
351
352    fn name(&self) -> &'static str {
353        Self::NAME
354    }
355
356    fn size(&self) -> usize {
357        let mut size = 0;
358
359        if self.post_beta2_color_depth.is_some() {
360            size += POST_BETA_COLOR_DEPTH_SIZE;
361        }
362        if self.client_product_id.is_some() {
363            size += CLIENT_PRODUCT_ID_SIZE;
364        }
365        if self.serial_number.is_some() {
366            size += SERIAL_NUMBER_SIZE;
367        }
368        if self.high_color_depth.is_some() {
369            size += HIGH_COLOR_DEPTH_SIZE;
370        }
371        if self.supported_color_depths.is_some() {
372            size += SUPPORTED_COLOR_DEPTHS_SIZE;
373        }
374        if self.early_capability_flags.is_some() {
375            size += EARLY_CAPABILITY_FLAGS_SIZE;
376        }
377        if self.dig_product_id.is_some() {
378            size += DIG_PRODUCT_ID_SIZE;
379        }
380        if self.connection_type.is_some() {
381            size += CONNECTION_TYPE_SIZE + PADDING_SIZE;
382        }
383        if self.server_selected_protocol.is_some() {
384            size += SERVER_SELECTED_PROTOCOL_SIZE;
385        }
386        if self.desktop_physical_width.is_some() {
387            size += DESKTOP_PHYSICAL_WIDTH_SIZE;
388        }
389        if self.desktop_physical_height.is_some() {
390            size += DESKTOP_PHYSICAL_HEIGHT_SIZE;
391        }
392        if self.desktop_orientation.is_some() {
393            size += DESKTOP_ORIENTATION_SIZE;
394        }
395        if self.desktop_scale_factor.is_some() {
396            size += DESKTOP_SCALE_FACTOR_SIZE;
397        }
398        if self.device_scale_factor.is_some() {
399            size += DEVICE_SCALE_FACTOR_SIZE;
400        }
401
402        size
403    }
404}
405
406macro_rules! try_or_return {
407    ($expr:expr, $ret:expr) => {
408        match $expr {
409            Ok(v) => v,
410            Err(_) => return Ok($ret),
411        }
412    };
413}
414
415impl<'de> Decode<'de> for ClientCoreOptionalData {
416    fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
417        let mut optional_data = Self::default();
418
419        optional_data.post_beta2_color_depth = Some(
420            ColorDepth::from_u16(try_or_return!(src.try_read_u16(), optional_data))
421                .ok_or_else(|| invalid_field_err!("postBeta2ColorDepth", "invalid color depth"))?,
422        );
423
424        optional_data.client_product_id = Some(try_or_return!(src.try_read_u16(), optional_data));
425        optional_data.serial_number = Some(try_or_return!(src.try_read_u32(), optional_data));
426
427        optional_data.high_color_depth = Some(
428            HighColorDepth::from_u16(try_or_return!(src.try_read_u16(), optional_data))
429                .ok_or_else(|| invalid_field_err!("highColorDepth", "invalid color depth"))?,
430        );
431
432        optional_data.supported_color_depths = Some(
433            SupportedColorDepths::from_bits(try_or_return!(src.try_read_u16(), optional_data))
434                .ok_or_else(|| invalid_field_err!("supportedColorDepths", "invalid supported color depths"))?,
435        );
436
437        optional_data.early_capability_flags = Some(
438            ClientEarlyCapabilityFlags::from_bits(try_or_return!(src.try_read_u16(), optional_data))
439                .ok_or_else(|| invalid_field_err!("earlyCapabilityFlags", "invalid early capability flags"))?,
440        );
441
442        if src.len() < DIG_PRODUCT_ID_SIZE {
443            return Ok(optional_data);
444        }
445
446        let dig_product_id = src.read_slice(DIG_PRODUCT_ID_SIZE);
447        optional_data.dig_product_id = Some(utils::from_utf16_bytes(dig_product_id).trim_end_matches('\u{0}').into());
448
449        optional_data.connection_type = Some(
450            ConnectionType::from_u8(try_or_return!(src.try_read_u8(), optional_data))
451                .ok_or_else(|| invalid_field_err!("connectionType", "invalid connection type"))?,
452        );
453
454        try_or_return!(src.try_read_u8(), optional_data);
455
456        optional_data.server_selected_protocol = Some(
457            SecurityProtocol::from_bits(try_or_return!(src.try_read_u32(), optional_data))
458                .ok_or_else(|| invalid_field_err!("serverSelectedProtocol", "invalid security protocol"))?,
459        );
460
461        optional_data.desktop_physical_width = Some(try_or_return!(src.try_read_u32(), optional_data));
462        // physical height must be present, if the physical width is present
463        optional_data.desktop_physical_height = Some(src.read_u32());
464
465        optional_data.desktop_orientation = Some(try_or_return!(src.try_read_u16(), optional_data));
466        optional_data.desktop_scale_factor = Some(try_or_return!(src.try_read_u32(), optional_data));
467        // device scale factor must be present, if the desktop scale factor is present
468        optional_data.device_scale_factor = Some(src.read_u32());
469
470        Ok(optional_data)
471    }
472}
473
474#[derive(Debug, Copy, Clone, PartialEq, Eq)]
475pub enum ClientColorDepth {
476    Bpp4,
477    Bpp8,
478    Rgb555Bpp16,
479    Rgb565Bpp16,
480    Bpp24,
481    Bpp32,
482}
483
484impl From<ColorDepth> for ClientColorDepth {
485    fn from(color_depth: ColorDepth) -> Self {
486        match color_depth {
487            ColorDepth::Bpp4 => ClientColorDepth::Bpp4,
488            ColorDepth::Bpp8 => ClientColorDepth::Bpp8,
489            ColorDepth::Rgb555Bpp16 => ClientColorDepth::Rgb555Bpp16,
490            ColorDepth::Rgb565Bpp16 => ClientColorDepth::Rgb565Bpp16,
491            ColorDepth::Bpp24 => ClientColorDepth::Bpp24,
492        }
493    }
494}
495
496impl From<HighColorDepth> for ClientColorDepth {
497    fn from(color_depth: HighColorDepth) -> Self {
498        match color_depth {
499            HighColorDepth::Bpp4 => ClientColorDepth::Bpp4,
500            HighColorDepth::Bpp8 => ClientColorDepth::Bpp8,
501            HighColorDepth::Rgb555Bpp16 => ClientColorDepth::Rgb555Bpp16,
502            HighColorDepth::Rgb565Bpp16 => ClientColorDepth::Rgb565Bpp16,
503            HighColorDepth::Bpp24 => ClientColorDepth::Bpp24,
504        }
505    }
506}
507
508#[repr(u16)]
509#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
510pub enum ColorDepth {
511    Bpp4 = 0xCA00,
512    Bpp8 = 0xCA01,
513    Rgb555Bpp16 = 0xCA02,
514    Rgb565Bpp16 = 0xCA03,
515    Bpp24 = 0xCA04,
516}
517
518#[repr(u16)]
519#[derive(Debug, Copy, Clone, FromPrimitive, ToPrimitive, Eq, Ord, PartialEq, PartialOrd)]
520pub enum HighColorDepth {
521    Bpp4 = 0x0004,
522    Bpp8 = 0x0008,
523    Rgb555Bpp16 = 0x000F,
524    Rgb565Bpp16 = 0x0010,
525    Bpp24 = 0x0018,
526}
527
528#[repr(u16)]
529#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
530pub enum SecureAccessSequence {
531    Del = 0xAA03,
532}
533
534#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
535pub enum KeyboardType {
536    IbmPcXt = 1,
537    OlivettiIco = 2,
538    IbmPcAt = 3,
539    IbmEnhanced = 4,
540    Nokia1050 = 5,
541    Nokia9140 = 6,
542    Japanese = 7,
543}
544
545#[repr(u8)]
546#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
547pub enum ConnectionType {
548    NotUsed = 0, // not used as ClientEarlyCapabilityFlags::VALID_CONNECTION_TYPE not set
549    Modem = 1,
550    BroadbandLow = 2,
551    Satellite = 3,
552    BroadbandHigh = 4,
553    Wan = 5,
554    Lan = 6,
555    Autodetect = 7,
556}
557
558bitflags! {
559    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
560    pub struct SupportedColorDepths: u16 {
561        const BPP24 = 1;
562        const BPP16 = 2;
563        const BPP15 = 4;
564        const BPP32 = 8;
565    }
566}
567
568bitflags! {
569    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
570    pub struct ClientEarlyCapabilityFlags: u16 {
571        const SUPPORT_ERR_INFO_PDU = 0x0001;
572        const WANT_32_BPP_SESSION = 0x0002;
573        const SUPPORT_STATUS_INFO_PDU = 0x0004;
574        const STRONG_ASYMMETRIC_KEYS = 0x0008;
575        const RELATIVE_MOUSE_INPUT = 0x0010;
576        const VALID_CONNECTION_TYPE = 0x0020;
577        const SUPPORT_MONITOR_LAYOUT_PDU = 0x0040;
578        const SUPPORT_NET_CHAR_AUTODETECT = 0x0080;
579        const SUPPORT_DYN_VC_GFX_PROTOCOL =0x0100;
580        const SUPPORT_DYNAMIC_TIME_ZONE = 0x0200;
581        const SUPPORT_HEART_BEAT_PDU = 0x0400;
582        const SUPPORT_SKIP_CHANNELJOIN = 0x0800;
583        // The source may set any bits
584        const _ = !0;
585    }
586}