ironrdp_pdu/basic_output/surface_commands/
mod.rs1#[cfg(test)]
2mod tests;
3
4use bitflags::bitflags;
5use ironrdp_core::{
6 cast_length, ensure_fixed_part_size, ensure_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult,
7 ReadCursor, WriteCursor,
8};
9use num_derive::FromPrimitive;
10use num_traits::FromPrimitive as _;
11
12use crate::geometry::ExclusiveRectangle;
13
14pub const SURFACE_COMMAND_HEADER_SIZE: usize = 2;
15
16#[derive(Debug, Clone, PartialEq, Eq)]
18pub enum SurfaceCommand<'a> {
19 SetSurfaceBits(SurfaceBitsPdu<'a>),
20 FrameMarker(FrameMarkerPdu),
21 StreamSurfaceBits(SurfaceBitsPdu<'a>),
22}
23
24impl SurfaceCommand<'_> {
25 const NAME: &'static str = "TS_SURFCMD";
26 const FIXED_PART_SIZE: usize = 2 ;
27}
28
29impl Encode for SurfaceCommand<'_> {
30 fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
31 ensure_size!(in: dst, size: self.size());
32
33 let cmd_type = SurfaceCommandType::from(self);
34 dst.write_u16(cmd_type.as_u16());
35
36 match self {
37 Self::SetSurfaceBits(pdu) | Self::StreamSurfaceBits(pdu) => pdu.encode(dst),
38 Self::FrameMarker(pdu) => pdu.encode(dst),
39 }?;
40
41 Ok(())
42 }
43
44 fn name(&self) -> &'static str {
45 Self::NAME
46 }
47
48 fn size(&self) -> usize {
49 SURFACE_COMMAND_HEADER_SIZE
50 + match self {
51 Self::SetSurfaceBits(pdu) | Self::StreamSurfaceBits(pdu) => pdu.size(),
52 Self::FrameMarker(pdu) => pdu.size(),
53 }
54 }
55}
56
57impl<'de> Decode<'de> for SurfaceCommand<'de> {
58 fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
59 ensure_fixed_part_size!(in: src);
60
61 let cmd_type = src.read_u16();
62 let cmd_type = SurfaceCommandType::from_u16(cmd_type)
63 .ok_or_else(|| invalid_field_err!("cmdType", "invalid surface command"))?;
64
65 match cmd_type {
66 SurfaceCommandType::SetSurfaceBits => Ok(Self::SetSurfaceBits(SurfaceBitsPdu::decode(src)?)),
67 SurfaceCommandType::FrameMarker => Ok(Self::FrameMarker(FrameMarkerPdu::decode(src)?)),
68 SurfaceCommandType::StreamSurfaceBits => Ok(Self::StreamSurfaceBits(SurfaceBitsPdu::decode(src)?)),
69 }
70 }
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct SurfaceBitsPdu<'a> {
76 pub destination: ExclusiveRectangle,
77 pub extended_bitmap_data: ExtendedBitmapDataPdu<'a>,
78}
79
80impl SurfaceBitsPdu<'_> {
81 const NAME: &'static str = "TS_SURFCMD_x_SURFACE_BITS_PDU";
82}
83
84impl Encode for SurfaceBitsPdu<'_> {
85 fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
86 self.destination.encode(dst)?;
87 self.extended_bitmap_data.encode(dst)?;
88
89 Ok(())
90 }
91
92 fn name(&self) -> &'static str {
93 Self::NAME
94 }
95
96 fn size(&self) -> usize {
97 self.destination.size() + self.extended_bitmap_data.size()
98 }
99}
100
101impl<'de> Decode<'de> for SurfaceBitsPdu<'de> {
102 fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
103 let destination = ExclusiveRectangle::decode(src)?;
104 let extended_bitmap_data = ExtendedBitmapDataPdu::decode(src)?;
105
106 Ok(Self {
107 destination,
108 extended_bitmap_data,
109 })
110 }
111}
112
113#[derive(Debug, Clone, PartialEq, Eq)]
115pub struct FrameMarkerPdu {
116 pub frame_action: FrameAction,
117 pub frame_id: Option<u32>,
118}
119
120impl FrameMarkerPdu {
121 const NAME: &'static str = "TS_FRAME_MARKER_PDU";
122 const FIXED_PART_SIZE: usize = 2 + 4 ;
123}
124
125impl Encode for FrameMarkerPdu {
126 fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
127 ensure_fixed_part_size!(in: dst);
128
129 dst.write_u16(self.frame_action.as_u16());
130 dst.write_u32(self.frame_id.unwrap_or(0));
131
132 Ok(())
133 }
134
135 fn name(&self) -> &'static str {
136 Self::NAME
137 }
138
139 fn size(&self) -> usize {
140 Self::FIXED_PART_SIZE
141 }
142}
143
144impl<'de> Decode<'de> for FrameMarkerPdu {
145 fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
146 ensure_size!(in: src, size: 2);
147
148 let frame_action = src.read_u16();
149
150 let frame_action = FrameAction::from_u16(frame_action)
151 .ok_or_else(|| invalid_field_err!("frameAction", "invalid frame action"))?;
152
153 let frame_id = if src.is_empty() {
154 None
158 } else {
159 ensure_size!(in: src, size: 4);
160 Some(src.read_u32())
161 };
162
163 Ok(Self { frame_action, frame_id })
164 }
165}
166
167#[derive(Clone, PartialEq, Eq)]
169pub struct ExtendedBitmapDataPdu<'a> {
170 pub bpp: u8,
171 pub codec_id: u8,
172 pub width: u16,
173 pub height: u16,
174 pub header: Option<BitmapDataHeader>,
175 pub data: &'a [u8],
176}
177
178impl core::fmt::Debug for ExtendedBitmapDataPdu<'_> {
179 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
180 f.debug_struct("ExtendedBitmapDataPdu")
181 .field("bpp", &self.bpp)
182 .field("codec_id", &self.codec_id)
183 .field("width", &self.width)
184 .field("height", &self.height)
185 .field("header", &self.header)
186 .field("data_len", &self.data.len())
187 .finish()
188 }
189}
190
191impl ExtendedBitmapDataPdu<'_> {
192 const NAME: &'static str = "TS_BITMAP_DATA_EX";
193 const FIXED_PART_SIZE: usize = 1 + 1 + 1 + 1 + 2 + 2 + 4 ;
194}
195
196impl Encode for ExtendedBitmapDataPdu<'_> {
197 fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
198 ensure_size!(in: dst, size: self.size());
199
200 let data_len = cast_length!("bitmap data length", self.data.len())?;
201
202 dst.write_u8(self.bpp);
203 let flags = if self.header.is_some() {
204 BitmapDataFlags::COMPRESSED_BITMAP_HEADER_PRESENT
205 } else {
206 BitmapDataFlags::empty()
207 };
208 dst.write_u8(flags.bits());
209 dst.write_u8(0); dst.write_u8(self.codec_id);
211 dst.write_u16(self.width);
212 dst.write_u16(self.height);
213 dst.write_u32(data_len);
214 if let Some(header) = &self.header {
215 header.encode(dst)?;
216 }
217 dst.write_slice(self.data);
218
219 Ok(())
220 }
221
222 fn name(&self) -> &'static str {
223 Self::NAME
224 }
225
226 fn size(&self) -> usize {
227 Self::FIXED_PART_SIZE + self.header.as_ref().map_or(0, |h| h.size()) + self.data.len()
228 }
229}
230
231impl<'de> Decode<'de> for ExtendedBitmapDataPdu<'de> {
232 fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
233 ensure_fixed_part_size!(in: src);
234
235 let bpp = src.read_u8();
236 let flags = BitmapDataFlags::from_bits_truncate(src.read_u8());
237 let _reserved = src.read_u8();
238 let codec_id = src.read_u8();
239 let width = src.read_u16();
240 let height = src.read_u16();
241 let data_length = cast_length!("bitmap data length", src.read_u32())?;
242
243 let expected_remaining_size = if flags.contains(BitmapDataFlags::COMPRESSED_BITMAP_HEADER_PRESENT) {
244 data_length + BitmapDataHeader::ENCODED_SIZE
245 } else {
246 data_length
247 };
248
249 ensure_size!(in: src, size: expected_remaining_size);
250
251 let header = if flags.contains(BitmapDataFlags::COMPRESSED_BITMAP_HEADER_PRESENT) {
252 Some(BitmapDataHeader::decode(src)?)
253 } else {
254 None
255 };
256
257 let data = src.read_slice(data_length);
258
259 Ok(Self {
260 bpp,
261 codec_id,
262 width,
263 height,
264 header,
265 data,
266 })
267 }
268}
269
270#[derive(Debug, Clone, PartialEq, Eq)]
272pub struct BitmapDataHeader {
273 pub high_unique_id: u32,
274 pub low_unique_id: u32,
275 pub tm_milliseconds: u64,
276 pub tm_seconds: u64,
277}
278
279impl BitmapDataHeader {
280 const NAME: &'static str = "TS_COMPRESSED_BITMAP_HEADER_EX";
281 const FIXED_PART_SIZE: usize = 4 + 4 + 8 + 8 ;
282
283 pub const ENCODED_SIZE: usize = Self::FIXED_PART_SIZE;
284}
285
286impl Encode for BitmapDataHeader {
287 fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
288 ensure_fixed_part_size!(in: dst);
289
290 dst.write_u32(self.high_unique_id);
291 dst.write_u32(self.low_unique_id);
292 dst.write_u64(self.tm_milliseconds);
293 dst.write_u64(self.tm_seconds);
294
295 Ok(())
296 }
297
298 fn name(&self) -> &'static str {
299 Self::NAME
300 }
301
302 fn size(&self) -> usize {
303 Self::FIXED_PART_SIZE
304 }
305}
306
307impl Decode<'_> for BitmapDataHeader {
308 fn decode(src: &mut ReadCursor<'_>) -> DecodeResult<Self> {
309 ensure_fixed_part_size!(in: src);
310
311 let high_unique_id = src.read_u32();
312 let low_unique_id = src.read_u32();
313 let tm_milliseconds = src.read_u64();
314 let tm_seconds = src.read_u64();
315
316 Ok(Self {
317 high_unique_id,
318 low_unique_id,
319 tm_milliseconds,
320 tm_seconds,
321 })
322 }
323}
324
325#[derive(Debug, Copy, Clone, PartialEq, FromPrimitive)]
326#[repr(u16)]
327enum SurfaceCommandType {
328 SetSurfaceBits = 0x01,
329 FrameMarker = 0x04,
330 StreamSurfaceBits = 0x06,
331}
332
333impl SurfaceCommandType {
334 #[expect(
335 clippy::as_conversions,
336 reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive"
337 )]
338 fn as_u16(self) -> u16 {
339 self as u16
340 }
341}
342
343impl From<&SurfaceCommand<'_>> for SurfaceCommandType {
344 fn from(command: &SurfaceCommand<'_>) -> Self {
345 match command {
346 SurfaceCommand::SetSurfaceBits(_) => Self::SetSurfaceBits,
347 SurfaceCommand::FrameMarker(_) => Self::FrameMarker,
348 SurfaceCommand::StreamSurfaceBits(_) => Self::StreamSurfaceBits,
349 }
350 }
351}
352
353#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive)]
354#[repr(u16)]
355pub enum FrameAction {
356 Begin = 0x00,
357 End = 0x01,
358}
359
360impl FrameAction {
361 #[expect(
362 clippy::as_conversions,
363 reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive"
364 )]
365 pub fn as_u16(self) -> u16 {
366 self as u16
367 }
368}
369
370bitflags! {
371 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
372 struct BitmapDataFlags: u8 {
373 const COMPRESSED_BITMAP_HEADER_PRESENT = 0x01;
374 }
375}