ironrdp_pdu/basic_output/surface_commands/
mod.rs

1#[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// TS_SURFCMD
17#[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 /* cmdType */;
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// TS_SURFCMD_STREAM_SURF_BITS and TS_SURFCMD_SET_SURF_BITS
74#[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// TS_FRAME_MARKER
114#[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 /* frameAction */ + 4 /* frameId */;
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            // Sometimes Windows 10 RDP server sends not complete FrameMarker PDU (without frame ID),
155            // so we made frame ID field as optional (not officially)
156
157            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// TS_BITMAP_DATA_EX
168#[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 /* bpp */ + 1 /* flags */ + 1 /* reserved */ + 1 /* codecId */ + 2 /* width */ + 2 /* height */ + 4 /* len */;
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); // reserved
210        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// TS_COMPRESSED_BITMAP_HEADER_EX
271#[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 /* highUniqueId */ + 4 /* lowUniqueId */ + 8 /* tmMilli */ + 8 /* tmSeconds */;
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}