ironrdp_cliprdr/pdu/
mod.rs

1//! This module implements RDP clipboard channel PDUs encode/decode logic as defined in
2//! [MS-RDPECLIP]: Remote Desktop Protocol: Clipboard Virtual Channel Extension
3
4mod capabilities;
5mod client_temporary_directory;
6mod file_contents;
7mod format_data;
8mod format_list;
9mod lock;
10
11pub use self::capabilities::*;
12pub use self::client_temporary_directory::*;
13pub use self::file_contents::*;
14pub use self::format_data::*;
15pub use self::format_list::*;
16pub use self::lock::*;
17
18#[rustfmt::skip]
19use bitflags::bitflags;
20use ironrdp_core::{
21    ensure_fixed_part_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult, ReadCursor, WriteCursor,
22};
23use ironrdp_svc::SvcEncode;
24
25const MSG_TYPE_MONITOR_READY: u16 = 0x0001;
26const MSG_TYPE_FORMAT_LIST: u16 = 0x0002;
27const MSG_TYPE_FORMAT_LIST_RESPONSE: u16 = 0x0003;
28const MSG_TYPE_FORMAT_DATA_REQUEST: u16 = 0x0004;
29const MSG_TYPE_FORMAT_DATA_RESPONSE: u16 = 0x0005;
30const MSG_TYPE_TEMPORARY_DIRECTORY: u16 = 0x0006;
31const MSG_TYPE_CAPABILITIES: u16 = 0x0007;
32const MSG_TYPE_FILE_CONTENTS_REQUEST: u16 = 0x0008;
33const MSG_TYPE_FILE_CONTENTS_RESPONSE: u16 = 0x0009;
34const MSG_TYPE_LOCK_CLIPDATA: u16 = 0x000A;
35const MSG_TYPE_UNLOCK_CLIPDATA: u16 = 0x000B;
36
37pub const FORMAT_ID_PALETTE: u32 = 9;
38pub const FORMAT_ID_METAFILE: u32 = 3;
39pub const FORMAT_NAME_FILE_LIST: &str = "FileGroupDescriptorW";
40
41/// Header without message type included
42struct PartialHeader {
43    message_flags: ClipboardPduFlags,
44    data_length: u32,
45}
46
47impl PartialHeader {
48    const NAME: &'static str = "CLIPRDR_HEADER";
49    const FIXED_PART_SIZE: usize = 2 /* flags */ + 4 /* len */;
50    const SIZE: usize = Self::FIXED_PART_SIZE;
51
52    pub(crate) fn new(inner_data_length: u32) -> Self {
53        Self::new_with_flags(inner_data_length, ClipboardPduFlags::empty())
54    }
55
56    pub(crate) fn new_with_flags(data_length: u32, message_flags: ClipboardPduFlags) -> Self {
57        Self {
58            message_flags,
59            data_length,
60        }
61    }
62
63    pub(crate) fn data_length(&self) -> usize {
64        usize::try_from(self.data_length).expect("BUG: Upcasting u32 -> usize should be infallible")
65    }
66}
67
68impl<'de> Decode<'de> for PartialHeader {
69    fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
70        ensure_fixed_part_size!(in: src);
71
72        let message_flags = ClipboardPduFlags::from_bits_truncate(src.read_u16());
73        let data_length = src.read_u32();
74
75        Ok(Self {
76            message_flags,
77            data_length,
78        })
79    }
80}
81
82impl Encode for PartialHeader {
83    fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
84        ensure_fixed_part_size!(in: dst);
85
86        dst.write_u16(self.message_flags.bits());
87        dst.write_u32(self.data_length);
88
89        Ok(())
90    }
91
92    fn name(&self) -> &'static str {
93        Self::NAME
94    }
95
96    fn size(&self) -> usize {
97        Self::FIXED_PART_SIZE
98    }
99}
100
101/// Clipboard channel message PDU
102#[derive(Debug, Clone, PartialEq, Eq)]
103pub enum ClipboardPdu<'a> {
104    MonitorReady,
105    FormatList(FormatList<'a>),
106    FormatListResponse(FormatListResponse),
107    FormatDataRequest(FormatDataRequest),
108    FormatDataResponse(FormatDataResponse<'a>),
109    TemporaryDirectory(ClientTemporaryDirectory<'a>),
110    Capabilities(Capabilities),
111    FileContentsRequest(FileContentsRequest),
112    FileContentsResponse(FileContentsResponse<'a>),
113    LockData(LockDataId),
114    UnlockData(LockDataId),
115}
116
117impl ClipboardPdu<'_> {
118    const NAME: &'static str = "ClipboardPdu";
119    const FIXED_PART_SIZE: usize = 2 /* type */;
120
121    pub fn message_name(&self) -> &'static str {
122        match self {
123            ClipboardPdu::MonitorReady => "CLIPRDR_MONITOR_READY",
124            ClipboardPdu::FormatList(_) => "CLIPRDR_FORMAT_LIST",
125            ClipboardPdu::FormatListResponse(_) => "CLIPRDR_FORMAT_LIST_RESPONSE",
126            ClipboardPdu::FormatDataRequest(_) => "CLIPRDR_FORMAT_DATA_REQUEST",
127            ClipboardPdu::FormatDataResponse(_) => "CLIPRDR_FORMAT_DATA_RESPONSE",
128            ClipboardPdu::TemporaryDirectory(_) => "CLIPRDR_TEMP_DIRECTORY",
129            ClipboardPdu::Capabilities(_) => "CLIPRDR_CAPABILITIES",
130            ClipboardPdu::FileContentsRequest(_) => "CLIPRDR_FILECONTENTS_REQUEST",
131            ClipboardPdu::FileContentsResponse(_) => "CLIPRDR_FILECONTENTS_RESPONSE",
132            ClipboardPdu::LockData(_) => "CLIPRDR_LOCK_CLIPDATA",
133            ClipboardPdu::UnlockData(_) => "CLIPRDR_UNLOCK_CLIPDATA",
134        }
135    }
136}
137
138impl Encode for ClipboardPdu<'_> {
139    fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
140        ensure_fixed_part_size!(in: dst);
141
142        let write_empty_pdu = |dst: &mut WriteCursor<'_>| {
143            let header = PartialHeader::new(0);
144            header.encode(dst)
145        };
146
147        match self {
148            ClipboardPdu::MonitorReady => {
149                dst.write_u16(MSG_TYPE_MONITOR_READY);
150                write_empty_pdu(dst)
151            }
152            ClipboardPdu::FormatList(pdu) => {
153                dst.write_u16(MSG_TYPE_FORMAT_LIST);
154                pdu.encode(dst)
155            }
156            ClipboardPdu::FormatListResponse(pdu) => {
157                dst.write_u16(MSG_TYPE_FORMAT_LIST_RESPONSE);
158                pdu.encode(dst)
159            }
160            ClipboardPdu::FormatDataRequest(pdu) => {
161                dst.write_u16(MSG_TYPE_FORMAT_DATA_REQUEST);
162                pdu.encode(dst)
163            }
164            ClipboardPdu::FormatDataResponse(pdu) => {
165                dst.write_u16(MSG_TYPE_FORMAT_DATA_RESPONSE);
166                pdu.encode(dst)
167            }
168            ClipboardPdu::TemporaryDirectory(pdu) => {
169                dst.write_u16(MSG_TYPE_TEMPORARY_DIRECTORY);
170                pdu.encode(dst)
171            }
172            ClipboardPdu::Capabilities(pdu) => {
173                dst.write_u16(MSG_TYPE_CAPABILITIES);
174                pdu.encode(dst)
175            }
176            ClipboardPdu::FileContentsRequest(pdu) => {
177                dst.write_u16(MSG_TYPE_FILE_CONTENTS_REQUEST);
178                pdu.encode(dst)
179            }
180            ClipboardPdu::FileContentsResponse(pdu) => {
181                dst.write_u16(MSG_TYPE_FILE_CONTENTS_RESPONSE);
182                pdu.encode(dst)
183            }
184            ClipboardPdu::LockData(pdu) => {
185                dst.write_u16(MSG_TYPE_LOCK_CLIPDATA);
186                pdu.encode(dst)
187            }
188            ClipboardPdu::UnlockData(pdu) => {
189                dst.write_u16(MSG_TYPE_UNLOCK_CLIPDATA);
190                pdu.encode(dst)
191            }
192        }
193    }
194
195    fn name(&self) -> &'static str {
196        Self::NAME
197    }
198
199    fn size(&self) -> usize {
200        let empty_size = PartialHeader::SIZE;
201
202        let variable_size = match self {
203            ClipboardPdu::MonitorReady => empty_size,
204            ClipboardPdu::FormatList(pdu) => pdu.size(),
205            ClipboardPdu::FormatListResponse(pdu) => pdu.size(),
206            ClipboardPdu::FormatDataRequest(pdu) => pdu.size(),
207            ClipboardPdu::FormatDataResponse(pdu) => pdu.size(),
208            ClipboardPdu::TemporaryDirectory(pdu) => pdu.size(),
209            ClipboardPdu::Capabilities(pdu) => pdu.size(),
210            ClipboardPdu::FileContentsRequest(pdu) => pdu.size(),
211            ClipboardPdu::FileContentsResponse(pdu) => pdu.size(),
212            ClipboardPdu::LockData(pdu) => pdu.size(),
213            ClipboardPdu::UnlockData(pdu) => pdu.size(),
214        };
215
216        Self::FIXED_PART_SIZE + variable_size
217    }
218}
219
220impl SvcEncode for ClipboardPdu<'_> {}
221
222impl<'de> Decode<'de> for ClipboardPdu<'de> {
223    fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
224        ensure_fixed_part_size!(in: src);
225
226        let read_empty_pdu = |src: &mut ReadCursor<'de>| -> DecodeResult<()> {
227            let _header = PartialHeader::decode(src)?;
228            Ok(())
229        };
230
231        let pdu = match src.read_u16() {
232            MSG_TYPE_MONITOR_READY => {
233                read_empty_pdu(src)?;
234                ClipboardPdu::MonitorReady
235            }
236            MSG_TYPE_FORMAT_LIST => ClipboardPdu::FormatList(FormatList::decode(src)?),
237            MSG_TYPE_FORMAT_LIST_RESPONSE => ClipboardPdu::FormatListResponse(FormatListResponse::decode(src)?),
238            MSG_TYPE_FORMAT_DATA_REQUEST => ClipboardPdu::FormatDataRequest(FormatDataRequest::decode(src)?),
239            MSG_TYPE_FORMAT_DATA_RESPONSE => ClipboardPdu::FormatDataResponse(FormatDataResponse::decode(src)?),
240            MSG_TYPE_TEMPORARY_DIRECTORY => ClipboardPdu::TemporaryDirectory(ClientTemporaryDirectory::decode(src)?),
241            MSG_TYPE_CAPABILITIES => ClipboardPdu::Capabilities(Capabilities::decode(src)?),
242            MSG_TYPE_FILE_CONTENTS_REQUEST => ClipboardPdu::FileContentsRequest(FileContentsRequest::decode(src)?),
243            MSG_TYPE_FILE_CONTENTS_RESPONSE => ClipboardPdu::FileContentsResponse(FileContentsResponse::decode(src)?),
244            MSG_TYPE_LOCK_CLIPDATA => ClipboardPdu::LockData(LockDataId::decode(src)?),
245            MSG_TYPE_UNLOCK_CLIPDATA => ClipboardPdu::UnlockData(LockDataId::decode(src)?),
246            _ => return Err(invalid_field_err!("msgType", "Unknown clipboard PDU type")),
247        };
248
249        Ok(pdu)
250    }
251}
252
253bitflags! {
254    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
255    /// Represents `msgFlags` field of `CLIPRDR_HEADER` structure
256    pub struct ClipboardPduFlags: u16 {
257        /// Used by the Format List Response PDU, Format Data Response PDU, and File
258        /// Contents Response PDU to indicate that the associated request Format List PDU,
259        /// Format Data Request PDU, and File Contents Request PDU were processed
260        /// successfully
261        const RESPONSE_OK = 0x0001;
262        /// Used by the Format List Response PDU, Format Data Response PDU, and File
263        /// Contents Response PDU to indicate that the associated Format List PDU, Format
264        /// Data Request PDU, and File Contents Request PDU were not processed successful
265        const RESPONSE_FAIL = 0x0002;
266        /// Used by the Short Format Name variant of the Format List Response PDU to indicate
267        /// that the format names are in ASCII 8
268        const ASCII_NAMES = 0x0004;
269    }
270}