ironrdp_pdu/rdp/
client_info.rs

1use core::fmt;
2use std::io;
3
4use bitflags::bitflags;
5use ironrdp_core::{
6    ensure_fixed_part_size, ensure_size, invalid_field_err, write_padding, Decode, DecodeResult, Encode, EncodeResult,
7    ReadCursor, WriteCursor,
8};
9use num_derive::{FromPrimitive, ToPrimitive};
10use num_traits::{FromPrimitive as _, ToPrimitive as _};
11use thiserror::Error;
12
13use crate::utils::CharacterSet;
14use crate::{utils, PduError};
15
16const RECONNECT_COOKIE_LEN: usize = 28;
17const TIMEZONE_INFO_NAME_LEN: usize = 64;
18const COMPRESSION_TYPE_MASK: u32 = 0x0000_1E00;
19
20const CODE_PAGE_SIZE: usize = 4;
21const FLAGS_SIZE: usize = 4;
22const DOMAIN_LENGTH_SIZE: usize = 2;
23const USER_NAME_LENGTH_SIZE: usize = 2;
24const PASSWORD_LENGTH_SIZE: usize = 2;
25const ALTERNATE_SHELL_LENGTH_SIZE: usize = 2;
26const WORK_DIR_LENGTH_SIZE: usize = 2;
27
28const CLIENT_ADDRESS_FAMILY_SIZE: usize = 2;
29const CLIENT_ADDRESS_LENGTH_SIZE: usize = 2;
30const CLIENT_DIR_LENGTH_SIZE: usize = 2;
31const SESSION_ID_SIZE: usize = 4;
32const PERFORMANCE_FLAGS_SIZE: usize = 4;
33const RECONNECT_COOKIE_LENGTH_SIZE: usize = 2;
34const BIAS_SIZE: usize = 4;
35
36/// [2.2.1.11.1.1] Info Packet (TS_INFO_PACKET)
37///
38/// [2.2.1.11.1.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/732394f5-e2b5-4ac5-8a0a-35345386b0d1
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct ClientInfo {
41    pub credentials: Credentials,
42    pub code_page: u32,
43    pub flags: ClientInfoFlags,
44    pub compression_type: CompressionType,
45    pub alternate_shell: String,
46    pub work_dir: String,
47    pub extra_info: ExtendedClientInfo,
48}
49
50impl ClientInfo {
51    const NAME: &'static str = "ClientInfo";
52
53    pub const FIXED_PART_SIZE: usize = CODE_PAGE_SIZE
54        + FLAGS_SIZE
55        + DOMAIN_LENGTH_SIZE
56        + USER_NAME_LENGTH_SIZE
57        + PASSWORD_LENGTH_SIZE
58        + ALTERNATE_SHELL_LENGTH_SIZE
59        + WORK_DIR_LENGTH_SIZE;
60}
61
62impl Encode for ClientInfo {
63    fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
64        ensure_fixed_part_size!(in: dst);
65
66        let character_set = if self.flags.contains(ClientInfoFlags::UNICODE) {
67            CharacterSet::Unicode
68        } else {
69            CharacterSet::Ansi
70        };
71
72        dst.write_u32(self.code_page);
73
74        let flags_with_compression_type = self.flags.bits() | (self.compression_type.to_u32().unwrap() << 9);
75        dst.write_u32(flags_with_compression_type);
76
77        let domain = self.credentials.domain.clone().unwrap_or_default();
78        dst.write_u16(string_len(domain.as_str(), character_set));
79        dst.write_u16(string_len(self.credentials.username.as_str(), character_set));
80        dst.write_u16(string_len(self.credentials.password.as_str(), character_set));
81        dst.write_u16(string_len(self.alternate_shell.as_str(), character_set));
82        dst.write_u16(string_len(self.work_dir.as_str(), character_set));
83
84        utils::write_string_to_cursor(dst, domain.as_str(), character_set, true)?;
85        utils::write_string_to_cursor(dst, self.credentials.username.as_str(), character_set, true)?;
86        utils::write_string_to_cursor(dst, self.credentials.password.as_str(), character_set, true)?;
87        utils::write_string_to_cursor(dst, self.alternate_shell.as_str(), character_set, true)?;
88        utils::write_string_to_cursor(dst, self.work_dir.as_str(), character_set, true)?;
89
90        self.extra_info.encode(dst, character_set)?;
91
92        Ok(())
93    }
94
95    fn name(&self) -> &'static str {
96        Self::NAME
97    }
98
99    fn size(&self) -> usize {
100        let character_set = if self.flags.contains(ClientInfoFlags::UNICODE) {
101            CharacterSet::Unicode
102        } else {
103            CharacterSet::Ansi
104        };
105        let domain = self.credentials.domain.clone().unwrap_or_default();
106
107        CODE_PAGE_SIZE
108            + FLAGS_SIZE
109            + DOMAIN_LENGTH_SIZE
110            + USER_NAME_LENGTH_SIZE
111            + PASSWORD_LENGTH_SIZE
112            + ALTERNATE_SHELL_LENGTH_SIZE
113            + WORK_DIR_LENGTH_SIZE
114            + (string_len(domain.as_str(), character_set)
115                + string_len(self.credentials.username.as_str(), character_set)
116                + string_len(self.credentials.password.as_str(), character_set)
117                + string_len(self.alternate_shell.as_str(), character_set)
118                + string_len(self.work_dir.as_str(), character_set)) as usize
119            + character_set.to_usize().unwrap() * 5 // null terminator
120            + self.extra_info.size(character_set)
121    }
122}
123
124impl<'de> Decode<'de> for ClientInfo {
125    fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
126        ensure_fixed_part_size!(in: src);
127
128        let code_page = src.read_u32();
129        let flags_with_compression_type = src.read_u32();
130
131        let flags = ClientInfoFlags::from_bits(flags_with_compression_type & !COMPRESSION_TYPE_MASK)
132            .ok_or_else(|| invalid_field_err!("flags", "invalid ClientInfoFlags"))?;
133        let compression_type =
134            CompressionType::from_u8(((flags_with_compression_type & COMPRESSION_TYPE_MASK) >> 9) as u8)
135                .ok_or_else(|| invalid_field_err!("flags", "invalid CompressionType"))?;
136
137        let character_set = if flags.contains(ClientInfoFlags::UNICODE) {
138            CharacterSet::Unicode
139        } else {
140            CharacterSet::Ansi
141        };
142
143        // Sizes exclude the length of the mandatory null terminator
144        let nt = character_set.to_usize().unwrap();
145        let domain_size = src.read_u16() as usize + nt;
146        let user_name_size = src.read_u16() as usize + nt;
147        let password_size = src.read_u16() as usize + nt;
148        let alternate_shell_size = src.read_u16() as usize + nt;
149        let work_dir_size = src.read_u16() as usize + nt;
150        ensure_size!(in: src, size: domain_size + user_name_size + password_size + alternate_shell_size + work_dir_size);
151
152        let domain = utils::decode_string(src.read_slice(domain_size), character_set, true)?;
153        let username = utils::decode_string(src.read_slice(user_name_size), character_set, true)?;
154        let password = utils::decode_string(src.read_slice(password_size), character_set, true)?;
155
156        let domain = if domain.is_empty() { None } else { Some(domain) };
157        let credentials = Credentials {
158            username,
159            password,
160            domain,
161        };
162
163        let alternate_shell = utils::decode_string(src.read_slice(alternate_shell_size), character_set, true)?;
164        let work_dir = utils::decode_string(src.read_slice(work_dir_size), character_set, true)?;
165
166        let extra_info = ExtendedClientInfo::decode(src, character_set)?;
167
168        Ok(Self {
169            credentials,
170            code_page,
171            flags,
172            compression_type,
173            alternate_shell,
174            work_dir,
175            extra_info,
176        })
177    }
178}
179
180#[derive(Clone, PartialEq, Eq)]
181pub struct Credentials {
182    pub username: String,
183    pub password: String,
184    pub domain: Option<String>,
185}
186
187impl fmt::Debug for Credentials {
188    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189        // NOTE: do not show secret (user password)
190        f.debug_struct("Credentials")
191            .field("username", &self.username)
192            .field("domain", &self.domain)
193            .finish_non_exhaustive()
194    }
195}
196
197#[derive(Debug, Clone, PartialEq, Eq)]
198pub struct ExtendedClientInfo {
199    pub address_family: AddressFamily,
200    pub address: String,
201    pub dir: String,
202    pub optional_data: ExtendedClientOptionalInfo,
203}
204
205impl ExtendedClientInfo {
206    // const NAME: &'static str = "ExtendedClientInfo";
207
208    fn decode(src: &mut ReadCursor<'_>, character_set: CharacterSet) -> DecodeResult<Self> {
209        ensure_size!(in: src, size: CLIENT_ADDRESS_FAMILY_SIZE + CLIENT_ADDRESS_LENGTH_SIZE);
210
211        let address_family = AddressFamily::from_u16(src.read_u16());
212
213        // This size includes the length of the mandatory null terminator.
214        let address_size = src.read_u16() as usize;
215        ensure_size!(in: src, size: address_size + CLIENT_DIR_LENGTH_SIZE);
216
217        let address = utils::decode_string(src.read_slice(address_size), character_set, false)?;
218        // This size includes the length of the mandatory null terminator.
219        let dir_size = src.read_u16() as usize;
220        ensure_size!(in: src, size: dir_size);
221
222        let dir = utils::decode_string(src.read_slice(dir_size), character_set, false)?;
223
224        let optional_data = ExtendedClientOptionalInfo::decode(src)?;
225
226        Ok(Self {
227            address_family,
228            address,
229            dir,
230            optional_data,
231        })
232    }
233
234    fn encode(&self, dst: &mut WriteCursor<'_>, character_set: CharacterSet) -> EncodeResult<()> {
235        ensure_size!(in: dst, size: self.size(character_set));
236
237        dst.write_u16(self.address_family.as_u16());
238        // // + size of null terminator, which will write in the write_string function
239        dst.write_u16(string_len(self.address.as_str(), character_set) + character_set.to_u16().unwrap());
240        utils::write_string_to_cursor(dst, self.address.as_str(), character_set, true)?;
241        dst.write_u16(string_len(self.dir.as_str(), character_set) + character_set.to_u16().unwrap());
242        utils::write_string_to_cursor(dst, self.dir.as_str(), character_set, true)?;
243        self.optional_data.encode(dst)?;
244
245        Ok(())
246    }
247
248    fn size(&self, character_set: CharacterSet) -> usize {
249        CLIENT_ADDRESS_FAMILY_SIZE
250            + CLIENT_ADDRESS_LENGTH_SIZE
251            + string_len(self.address.as_str(), character_set) as usize
252            + character_set.to_usize().unwrap() // null terminator
253        + CLIENT_DIR_LENGTH_SIZE
254        + string_len(self.dir.as_str(), character_set) as usize
255            + character_set.to_usize().unwrap() // null terminator
256        + self.optional_data.size()
257    }
258}
259
260#[derive(Debug, Clone, PartialEq, Eq, Default)]
261pub struct ExtendedClientOptionalInfo {
262    timezone: Option<TimezoneInfo>,
263    session_id: Option<u32>,
264    performance_flags: Option<PerformanceFlags>,
265    reconnect_cookie: Option<[u8; RECONNECT_COOKIE_LEN]>,
266    // other fields are read by RdpVersion::Ten+
267}
268
269impl ExtendedClientOptionalInfo {
270    const NAME: &'static str = "ExtendedClientOptionalInfo";
271
272    /// Creates a new builder for [`ExtendedClientOptionalInfo`].
273    pub fn builder(
274    ) -> builder::ExtendedClientOptionalInfoBuilder<builder::ExtendedClientOptionalInfoBuilderStateSetTimeZone> {
275        builder::ExtendedClientOptionalInfoBuilder::<builder::ExtendedClientOptionalInfoBuilderStateSetTimeZone>::default()
276    }
277
278    pub fn timezone(&self) -> Option<&TimezoneInfo> {
279        self.timezone.as_ref()
280    }
281
282    pub fn session_id(&self) -> Option<u32> {
283        self.session_id
284    }
285
286    pub fn performance_flags(&self) -> Option<PerformanceFlags> {
287        self.performance_flags
288    }
289
290    pub fn reconnect_cookie(&self) -> Option<&[u8; RECONNECT_COOKIE_LEN]> {
291        self.reconnect_cookie.as_ref()
292    }
293}
294
295impl Encode for ExtendedClientOptionalInfo {
296    fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
297        ensure_size!(in: dst, size: self.size());
298
299        if let Some(ref timezone) = self.timezone {
300            timezone.encode(dst)?;
301        }
302        if let Some(session_id) = self.session_id {
303            dst.write_u32(session_id);
304        }
305        if let Some(performance_flags) = self.performance_flags {
306            dst.write_u32(performance_flags.bits());
307        }
308        if let Some(reconnect_cookie) = self.reconnect_cookie {
309            dst.write_u16(RECONNECT_COOKIE_LEN as u16);
310            dst.write_array(reconnect_cookie);
311        }
312
313        Ok(())
314    }
315
316    fn name(&self) -> &'static str {
317        Self::NAME
318    }
319
320    fn size(&self) -> usize {
321        let mut size = 0;
322
323        if let Some(ref timezone) = self.timezone {
324            size += timezone.size();
325        }
326        if self.session_id.is_some() {
327            size += SESSION_ID_SIZE;
328        }
329        if self.performance_flags.is_some() {
330            size += PERFORMANCE_FLAGS_SIZE;
331        }
332        if self.reconnect_cookie.is_some() {
333            size += RECONNECT_COOKIE_LENGTH_SIZE + RECONNECT_COOKIE_LEN;
334        }
335
336        size
337    }
338}
339
340impl<'de> Decode<'de> for ExtendedClientOptionalInfo {
341    fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
342        let mut optional_data = Self::default();
343
344        if src.len() < TimezoneInfo::FIXED_PART_SIZE {
345            return Ok(optional_data);
346        }
347        optional_data.timezone = Some(TimezoneInfo::decode(src)?);
348
349        if src.len() < 4 {
350            return Ok(optional_data);
351        }
352        optional_data.session_id = Some(src.read_u32());
353
354        if src.len() < 4 {
355            return Ok(optional_data);
356        }
357        optional_data.performance_flags = Some(
358            PerformanceFlags::from_bits(src.read_u32())
359                .ok_or_else(|| invalid_field_err!("performanceFlags", "invalid performance flags"))?,
360        );
361
362        if src.len() < 2 {
363            return Ok(optional_data);
364        }
365        let reconnect_cookie_size = src.read_u16();
366        if reconnect_cookie_size != RECONNECT_COOKIE_LEN as u16 && reconnect_cookie_size != 0 {
367            return Err(invalid_field_err!("cbAutoReconnectCookie", "invalid cookie size"));
368        }
369        if reconnect_cookie_size != 0 {
370            if src.len() < RECONNECT_COOKIE_LEN {
371                return Err(invalid_field_err!("cbAutoReconnectCookie", "missing cookie data"));
372            }
373            optional_data.reconnect_cookie = Some(src.read_array());
374        }
375
376        if src.len() < 2 * 2 {
377            return Ok(optional_data);
378        }
379        src.read_u16(); // reserved1
380        src.read_u16(); // reserved2
381
382        Ok(optional_data)
383    }
384}
385
386/// [2.2.1.11.1.1.1.1] Time Zone Information (TS_TIME_ZONE_INFORMATION)
387///
388/// The timezone info struct contains client time zone information.
389///
390/// [2.2.1.11.1.1.1.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/526ed635-d7a9-4d3c-bbe1-4e3fb17585f4
391#[derive(Debug, Clone, PartialEq, Eq)]
392pub struct TimezoneInfo {
393    pub bias: i32,
394    pub standard_name: String,
395    pub standard_date: OptionalSystemTime,
396    pub standard_bias: i32,
397    pub daylight_name: String,
398    pub daylight_date: OptionalSystemTime,
399    pub daylight_bias: i32,
400}
401
402impl TimezoneInfo {
403    const NAME: &'static str = "TimezoneInfo";
404
405    const FIXED_PART_SIZE: usize = BIAS_SIZE
406        + TIMEZONE_INFO_NAME_LEN
407        + SystemTime::FIXED_PART_SIZE
408        + BIAS_SIZE
409        + TIMEZONE_INFO_NAME_LEN
410        + SystemTime::FIXED_PART_SIZE
411        + BIAS_SIZE;
412}
413
414impl Encode for TimezoneInfo {
415    fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
416        ensure_fixed_part_size!(in: dst);
417
418        dst.write_i32(self.bias);
419
420        let mut standard_name = utils::to_utf16_bytes(self.standard_name.as_str());
421        standard_name.resize(TIMEZONE_INFO_NAME_LEN, 0);
422        dst.write_slice(&standard_name);
423
424        self.standard_date.encode(dst)?;
425        dst.write_i32(self.standard_bias);
426
427        let mut daylight_name = utils::to_utf16_bytes(self.daylight_name.as_str());
428        daylight_name.resize(TIMEZONE_INFO_NAME_LEN, 0);
429        dst.write_slice(&daylight_name);
430
431        self.daylight_date.encode(dst)?;
432        dst.write_i32(self.daylight_bias);
433
434        Ok(())
435    }
436
437    fn name(&self) -> &'static str {
438        Self::NAME
439    }
440
441    fn size(&self) -> usize {
442        Self::FIXED_PART_SIZE
443    }
444}
445
446impl<'de> Decode<'de> for TimezoneInfo {
447    fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
448        ensure_fixed_part_size!(in: src);
449
450        let bias = src.read_i32();
451        let standard_name = utils::decode_string(src.read_slice(TIMEZONE_INFO_NAME_LEN), CharacterSet::Unicode, false)?;
452        let standard_date = OptionalSystemTime::decode(src)?;
453        let standard_bias = src.read_i32();
454
455        let daylight_name = utils::decode_string(src.read_slice(TIMEZONE_INFO_NAME_LEN), CharacterSet::Unicode, false)?;
456        let daylight_date = OptionalSystemTime::decode(src)?;
457        let daylight_bias = src.read_i32();
458
459        Ok(Self {
460            bias,
461            standard_name,
462            standard_date,
463            standard_bias,
464            daylight_name,
465            daylight_date,
466            daylight_bias,
467        })
468    }
469}
470
471impl Default for TimezoneInfo {
472    fn default() -> Self {
473        Self {
474            bias: 0,
475            standard_name: String::new(),
476            standard_date: OptionalSystemTime(None),
477            standard_bias: 0,
478            daylight_name: String::new(),
479            daylight_date: OptionalSystemTime(None),
480            daylight_bias: 0,
481        }
482    }
483}
484
485#[derive(Debug, Clone, PartialEq, Eq)]
486pub struct SystemTime {
487    pub month: Month,
488    pub day_of_week: DayOfWeek,
489    pub day: DayOfWeekOccurrence,
490    pub hour: u16,
491    pub minute: u16,
492    pub second: u16,
493    pub milliseconds: u16,
494}
495
496impl SystemTime {
497    const NAME: &'static str = "SystemTime";
498
499    const FIXED_PART_SIZE: usize = 2 /* Year */ + 2 /* Month */ + 2 /* DoW */ + 2 /* Day */ + 2 /* Hour */ + 2 /* Minute */ + 2 /* Second */ + 2 /* Ms */;
500}
501
502#[derive(Debug, Clone, PartialEq, Eq)]
503pub struct OptionalSystemTime(pub Option<SystemTime>);
504
505impl Encode for OptionalSystemTime {
506    fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
507        ensure_size!(in: dst, size: self.size());
508
509        dst.write_u16(0); // year
510        if let Some(st) = &self.0 {
511            dst.write_u16(st.month.to_u16().unwrap());
512            dst.write_u16(st.day_of_week.to_u16().unwrap());
513            dst.write_u16(st.day.to_u16().unwrap());
514            dst.write_u16(st.hour);
515            dst.write_u16(st.minute);
516            dst.write_u16(st.second);
517            dst.write_u16(st.milliseconds);
518        } else {
519            write_padding!(dst, 2 * 7);
520        }
521
522        Ok(())
523    }
524
525    fn name(&self) -> &'static str {
526        SystemTime::NAME
527    }
528
529    fn size(&self) -> usize {
530        SystemTime::FIXED_PART_SIZE
531    }
532}
533
534impl<'de> Decode<'de> for OptionalSystemTime {
535    fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
536        ensure_size!(in: src, size: SystemTime::FIXED_PART_SIZE);
537
538        let _year = src.read_u16(); // This field MUST be set to zero.
539        let month = src.read_u16();
540        let day_of_week = src.read_u16();
541        let day = src.read_u16();
542        let hour = src.read_u16();
543        let minute = src.read_u16();
544        let second = src.read_u16();
545        let milliseconds = src.read_u16();
546
547        match (
548            Month::from_u16(month),
549            DayOfWeek::from_u16(day_of_week),
550            DayOfWeekOccurrence::from_u16(day),
551        ) {
552            (Some(month), Some(day_of_week), Some(day)) => Ok(Self(Some(SystemTime {
553                month,
554                day_of_week,
555                day,
556                hour,
557                minute,
558                second,
559                milliseconds,
560            }))),
561            _ => Ok(Self(None)),
562        }
563    }
564}
565
566#[repr(u16)]
567#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
568pub enum Month {
569    January = 1,
570    February = 2,
571    March = 3,
572    April = 4,
573    May = 5,
574    June = 6,
575    July = 7,
576    August = 8,
577    September = 9,
578    October = 10,
579    November = 11,
580    December = 12,
581}
582
583#[repr(u16)]
584#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
585pub enum DayOfWeek {
586    Sunday = 0,
587    Monday = 1,
588    Tuesday = 2,
589    Wednesday = 3,
590    Thursday = 4,
591    Friday = 5,
592    Saturday = 6,
593}
594
595#[repr(u16)]
596#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
597pub enum DayOfWeekOccurrence {
598    First = 1,
599    Second = 2,
600    Third = 3,
601    Fourth = 4,
602    Last = 5,
603}
604
605bitflags! {
606    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
607    pub struct PerformanceFlags: u32 {
608        const DISABLE_WALLPAPER = 0x0000_0001;
609        const DISABLE_FULLWINDOWDRAG = 0x0000_0002;
610        const DISABLE_MENUANIMATIONS = 0x0000_0004;
611        const DISABLE_THEMING = 0x0000_0008;
612        const RESERVED1 = 0x0000_0010;
613        const DISABLE_CURSOR_SHADOW = 0x0000_0020;
614        const DISABLE_CURSORSETTINGS = 0x0000_0040;
615        const ENABLE_FONT_SMOOTHING = 0x0000_0080;
616        const ENABLE_DESKTOP_COMPOSITION = 0x0000_0100;
617        const RESERVED2 = 0x8000_0000;
618    }
619}
620
621impl Default for PerformanceFlags {
622    fn default() -> Self {
623        Self::DISABLE_FULLWINDOWDRAG | Self::DISABLE_MENUANIMATIONS | Self::ENABLE_FONT_SMOOTHING
624    }
625}
626
627#[repr(transparent)]
628#[derive(Debug, Copy, Clone, PartialEq, Eq)]
629pub struct AddressFamily(u16);
630
631impl AddressFamily {
632    pub const INET: Self = Self(0x0002);
633    pub const INET_6: Self = Self(0x0017);
634}
635
636impl AddressFamily {
637    pub fn from_u16(val: u16) -> Self {
638        Self(val)
639    }
640
641    pub fn as_u16(self) -> u16 {
642        self.0
643    }
644}
645
646bitflags! {
647    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
648    pub struct ClientInfoFlags: u32 {
649        /// INFO_MOUSE
650        const MOUSE = 0x0000_0001;
651        /// INFO_DISABLECTRLALTDEL
652        const DISABLE_CTRL_ALT_DEL = 0x0000_0002;
653        /// INFO_AUTOLOGON
654        const AUTOLOGON = 0x0000_0008;
655        /// INFO_UNICODE
656        const UNICODE = 0x0000_0010;
657        /// INFO_MAXIMIZESHELL
658        const MAXIMIZE_SHELL = 0x0000_0020;
659        /// INFO_LOGONNOTIFY
660        const LOGON_NOTIFY = 0x0000_0040;
661        /// INFO_COMPRESSION
662        const COMPRESSION = 0x0000_0080;
663        /// INFO_ENABLEWINDOWSKEY
664        const ENABLE_WINDOWS_KEY = 0x0000_0100;
665        /// INFO_REMOTECONSOLEAUDIO
666        const REMOTE_CONSOLE_AUDIO = 0x0000_2000;
667        /// INFO_FORCE_ENCRYPTED_CS_PDU
668        const FORCE_ENCRYPTED_CS_PDU = 0x0000_4000;
669        /// INFO_RAIL
670        const RAIL = 0x0000_8000;
671        /// INFO_LOGONERRORS
672        const LOGON_ERRORS = 0x0001_0000;
673        /// INFO_MOUSE_HAS_WHEEL
674        const MOUSE_HAS_WHEEL = 0x0002_0000;
675        /// INFO_PASSWORD_IS_SC_PIN
676        const PASSWORD_IS_SC_PIN = 0x0004_0000;
677        /// INFO_NOAUDIOPLAYBACK
678        const NO_AUDIO_PLAYBACK = 0x0008_0000;
679        /// INFO_USING_SAVED_CREDS
680        const USING_SAVED_CREDS = 0x0010_0000;
681        /// INFO_AUDIOCAPTURE
682        const AUDIO_CAPTURE = 0x0020_0000;
683        /// INFO_VIDEO_DISABLE
684        const VIDEO_DISABLE = 0x0040_0000;
685        /// INFO_RESERVED1
686        const RESERVED1 = 0x0080_0000;
687        /// INFO_RESERVED1
688        const RESERVED2 = 0x0100_0000;
689        /// INFO_HIDEF_RAIL_SUPPORTED
690        const HIDEF_RAIL_SUPPORTED = 0x0200_0000;
691    }
692}
693
694#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
695pub enum CompressionType {
696    K8 = 0,
697    K64 = 1,
698    Rdp6 = 2,
699    Rdp61 = 3,
700}
701
702#[derive(Debug, Error)]
703pub enum ClientInfoError {
704    #[error("IO error")]
705    IOError(#[from] io::Error),
706    #[error("UTF-8 error")]
707    Utf8Error(#[from] std::string::FromUtf8Error),
708    #[error("invalid address family field")]
709    InvalidAddressFamily,
710    #[error("invalid flags field")]
711    InvalidClientInfoFlags,
712    #[error("invalid performance flags field")]
713    InvalidPerformanceFlags,
714    #[error("invalid reconnect cookie field")]
715    InvalidReconnectCookie,
716    #[error("PDU error: {0}")]
717    Pdu(PduError),
718}
719
720impl From<PduError> for ClientInfoError {
721    fn from(e: PduError) -> Self {
722        Self::Pdu(e)
723    }
724}
725
726fn string_len(value: &str, character_set: CharacterSet) -> u16 {
727    match character_set {
728        CharacterSet::Ansi => u16::try_from(value.len()).unwrap(),
729        CharacterSet::Unicode => u16::try_from(value.encode_utf16().count() * 2).unwrap(),
730    }
731}
732
733pub mod builder {
734    use core::marker::PhantomData;
735
736    use super::{ExtendedClientOptionalInfo, PerformanceFlags, TimezoneInfo, RECONNECT_COOKIE_LEN};
737
738    pub struct ExtendedClientOptionalInfoBuilderStateSetTimeZone;
739    pub struct ExtendedClientOptionalInfoBuilderStateSetSessionId;
740    pub struct ExtendedClientOptionalInfoBuilderStateSetPerformanceFlags;
741    pub struct ExtendedClientOptionalInfoBuilderStateSetReconnectCookie;
742    pub struct ExtendedClientOptionalInfoBuilderStateFinal;
743
744    // State machine-based builder for [`ExtendedClientOptionalInfo`].
745    //
746    // [`ExtendedClientOptionalInfo`] strictly requires to set all preceding optional fields before
747    // setting the next one, therefore we use a state machine to enforce this during the compile time.
748    #[derive(Debug, Clone, PartialEq, Eq)]
749    pub struct ExtendedClientOptionalInfoBuilder<State> {
750        inner: ExtendedClientOptionalInfo,
751        _phantom_data: PhantomData<State>,
752    }
753
754    impl<State> ExtendedClientOptionalInfoBuilder<State> {
755        pub fn build(self) -> ExtendedClientOptionalInfo {
756            self.inner
757        }
758    }
759
760    impl ExtendedClientOptionalInfoBuilder<ExtendedClientOptionalInfoBuilderStateSetTimeZone> {
761        pub fn new() -> Self {
762            Self {
763                inner: ExtendedClientOptionalInfo::default(),
764                _phantom_data: Default::default(),
765            }
766        }
767
768        pub fn timezone(
769            mut self,
770            timezone: TimezoneInfo,
771        ) -> ExtendedClientOptionalInfoBuilder<ExtendedClientOptionalInfoBuilderStateSetSessionId> {
772            self.inner.timezone = Some(timezone);
773            ExtendedClientOptionalInfoBuilder {
774                inner: self.inner,
775                _phantom_data: Default::default(),
776            }
777        }
778    }
779
780    impl Default for ExtendedClientOptionalInfoBuilder<ExtendedClientOptionalInfoBuilderStateSetTimeZone> {
781        fn default() -> Self {
782            Self::new()
783        }
784    }
785
786    impl ExtendedClientOptionalInfoBuilder<ExtendedClientOptionalInfoBuilderStateSetSessionId> {
787        pub fn session_id(
788            mut self,
789            session_id: u32,
790        ) -> ExtendedClientOptionalInfoBuilder<ExtendedClientOptionalInfoBuilderStateSetPerformanceFlags> {
791            self.inner.session_id = Some(session_id);
792            ExtendedClientOptionalInfoBuilder {
793                inner: self.inner,
794                _phantom_data: Default::default(),
795            }
796        }
797    }
798
799    impl ExtendedClientOptionalInfoBuilder<ExtendedClientOptionalInfoBuilderStateSetPerformanceFlags> {
800        pub fn performance_flags(
801            mut self,
802            performance_flags: PerformanceFlags,
803        ) -> ExtendedClientOptionalInfoBuilder<ExtendedClientOptionalInfoBuilderStateSetReconnectCookie> {
804            self.inner.performance_flags = Some(performance_flags);
805            ExtendedClientOptionalInfoBuilder {
806                inner: self.inner,
807                _phantom_data: Default::default(),
808            }
809        }
810    }
811
812    impl ExtendedClientOptionalInfoBuilder<ExtendedClientOptionalInfoBuilderStateSetReconnectCookie> {
813        pub fn reconnect_cookie(
814            mut self,
815            reconnect_cookie: [u8; RECONNECT_COOKIE_LEN],
816        ) -> ExtendedClientOptionalInfoBuilder<ExtendedClientOptionalInfoBuilderStateFinal> {
817            self.inner.reconnect_cookie = Some(reconnect_cookie);
818            ExtendedClientOptionalInfoBuilder {
819                inner: self.inner,
820                _phantom_data: Default::default(),
821            }
822        }
823    }
824}