ironrdp_pdu/
pcb.rs

1//! This module contains the RDP_PRECONNECTION_PDU_V1 and RDP_PRECONNECTION_PDU_V2 structures.
2
3use ironrdp_core::{
4    cast_length, ensure_fixed_part_size, ensure_size, invalid_field_err, invalid_field_err_with_source, read_padding,
5    write_padding, Decode, DecodeResult, Encode, EncodeResult, ReadCursor, WriteCursor,
6};
7
8use crate::Pdu;
9
10/// Preconnection PDU version
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct PcbVersion(pub u32);
13
14impl PcbVersion {
15    pub const V1: Self = Self(0x1);
16    pub const V2: Self = Self(0x2);
17}
18
19/// RDP preconnection PDU
20///
21/// The RDP_PRECONNECTION_PDU_V1 is used by the client to let the listening process
22/// know which RDP source the connection is intended for.
23///
24/// The RDP_PRECONNECTION_PDU_V2 extends the RDP_PRECONNECTION_PDU_V1 packet by
25/// adding a variable-size Unicode character string. The receiver of this PDU can
26/// use this string and the Id field of the RDP_PRECONNECTION_PDU_V1 packet to
27/// determine the RDP source. This string is opaque to the protocol.
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct PreconnectionBlob {
30    /// Preconnection PDU version
31    pub version: PcbVersion,
32    /// This field is used to uniquely identify the RDP source. Although the Id can be
33    /// as simple as a process ID, it is often client-specific or server-specific and
34    /// can be obfuscated.
35    pub id: u32,
36    /// V2 PCB string
37    pub v2_payload: Option<String>,
38}
39
40impl PreconnectionBlob {
41    pub const FIXED_PART_SIZE: usize = 16;
42}
43
44impl Pdu for PreconnectionBlob {
45    const NAME: &'static str = "PreconnectionBlob";
46}
47
48impl<'de> Decode<'de> for PreconnectionBlob {
49    fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
50        ensure_fixed_part_size!(in: src);
51
52        let pcb_size: usize = cast_length!("cbSize", src.read_u32())?;
53
54        if pcb_size < Self::FIXED_PART_SIZE {
55            return Err(invalid_field_err(
56                Self::NAME,
57                "cbSize",
58                "advertised size too small for Preconnection PDU V1",
59            ));
60        }
61
62        read_padding!(src, 4); // flags
63
64        // The version field SHOULD be initialized by the client and SHOULD be ignored by the server,
65        // as specified in sections 3.1.5.1 and 3.2.5.1.
66        // That’s why, following code doesn’t depend on the value of this field.
67        let version = PcbVersion(src.read_u32());
68
69        let id = src.read_u32();
70
71        let remaining_size = pcb_size - Self::FIXED_PART_SIZE;
72
73        ensure_size!(in: src, size: remaining_size);
74
75        if remaining_size >= 2 {
76            let cch_pcb = usize::from(src.read_u16());
77            let cb_pcb = cch_pcb * 2;
78
79            if remaining_size - 2 < cb_pcb {
80                return Err(invalid_field_err(
81                    Self::NAME,
82                    "cchPCB",
83                    "PCB string bigger than advertised size",
84                ));
85            }
86
87            let wsz_pcb_utf16 = src.read_slice(cb_pcb);
88
89            let payload = crate::utf16::read_utf16_string(wsz_pcb_utf16, Some(cch_pcb))
90                .map_err(|e| invalid_field_err_with_source(Self::NAME, "wszPCB", "bad UTF-16 string", e))?;
91
92            let leftover_size = remaining_size - 2 - cb_pcb;
93            src.advance(leftover_size); // Consume (unused) leftover data
94
95            Ok(Self {
96                version,
97                id,
98                v2_payload: Some(payload),
99            })
100        } else {
101            Ok(Self {
102                version,
103                id,
104                v2_payload: None,
105            })
106        }
107    }
108}
109
110impl Encode for PreconnectionBlob {
111    fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
112        if self.v2_payload.is_some() && self.version == PcbVersion::V1 {
113            return Err(invalid_field_err(
114                Self::NAME,
115                "version",
116                "there is no string payload in Preconnection PDU V1",
117            ));
118        }
119
120        let pcb_size = self.size();
121
122        ensure_size!(in: dst, size: pcb_size);
123
124        dst.write_u32(cast_length!("cbSize", pcb_size)?); // cbSize
125        write_padding!(dst, 4); // flags
126        dst.write_u32(self.version.0); // version
127        dst.write_u32(self.id); // id
128
129        if let Some(v2_payload) = &self.v2_payload {
130            // cchPCB
131            let utf16_character_count = v2_payload.chars().count() + 1; // +1 for null terminator
132            dst.write_u16(cast_length!("cchPCB", utf16_character_count)?);
133
134            // wszPCB
135            v2_payload.encode_utf16().for_each(|c| dst.write_u16(c));
136            dst.write_u16(0); // null terminator
137        }
138
139        Ok(())
140    }
141
142    fn name(&self) -> &'static str {
143        Self::NAME
144    }
145
146    fn size(&self) -> usize {
147        let fixed_part_size = Self::FIXED_PART_SIZE;
148
149        let variable_part = if let Some(v2_payload) = &self.v2_payload {
150            let utf16_encoded_len = crate::utf16::null_terminated_utf16_encoded_len(v2_payload);
151            2 + utf16_encoded_len
152        } else {
153            0
154        };
155
156        fixed_part_size + variable_part
157    }
158}