ironrdp_session/
fast_path.rs

1use std::sync::Arc;
2
3use ironrdp_core::{decode_cursor, DecodeErrorKind, ReadCursor, WriteBuf};
4use ironrdp_graphics::image_processing::PixelFormat;
5use ironrdp_graphics::pointer::{DecodedPointer, PointerBitmapTarget};
6use ironrdp_graphics::rdp6::BitmapStreamDecoder;
7use ironrdp_graphics::rle::RlePixelFormat;
8use ironrdp_pdu::codecs::rfx::FrameAcknowledgePdu;
9use ironrdp_pdu::fast_path::{FastPathHeader, FastPathUpdate, FastPathUpdatePdu, Fragmentation};
10use ironrdp_pdu::geometry::{InclusiveRectangle, Rectangle as _};
11use ironrdp_pdu::pointer::PointerUpdateData;
12use ironrdp_pdu::rdp::capability_sets::{CodecId, CODEC_ID_NONE, CODEC_ID_REMOTEFX};
13use ironrdp_pdu::rdp::headers::ShareDataPdu;
14use ironrdp_pdu::surface_commands::{FrameAction, FrameMarkerPdu, SurfaceCommand};
15use tracing::{debug, trace, warn};
16
17use crate::image::DecodedImage;
18use crate::pointer::PointerCache;
19use crate::{custom_err, reason_err, rfx, SessionError, SessionErrorExt as _, SessionResult};
20
21#[derive(Debug)]
22pub enum UpdateKind {
23    None,
24    Region(InclusiveRectangle),
25    PointerDefault,
26    PointerHidden,
27    PointerPosition { x: u16, y: u16 },
28    PointerBitmap(Arc<DecodedPointer>),
29}
30
31pub struct Processor {
32    complete_data: CompleteData,
33    rfx_handler: rfx::DecodingContext,
34    marker_processor: FrameMarkerProcessor,
35    bitmap_stream_decoder: BitmapStreamDecoder,
36    pointer_cache: PointerCache,
37    use_system_pointer: bool,
38    mouse_pos_update: Option<(u16, u16)>,
39    enable_server_pointer: bool,
40    pointer_software_rendering: bool,
41    #[cfg(feature = "qoiz")]
42    zdctx: zstd_safe::DCtx<'static>,
43}
44
45impl Processor {
46    pub fn update_mouse_pos(&mut self, x: u16, y: u16) {
47        self.mouse_pos_update = Some((x, y));
48    }
49
50    /// Process input fast path frame and return list of updates.
51    pub fn process(
52        &mut self,
53        image: &mut DecodedImage,
54        input: &[u8],
55        output: &mut WriteBuf,
56    ) -> SessionResult<Vec<UpdateKind>> {
57        let mut processor_updates = Vec::new();
58
59        if let Some((x, y)) = self.mouse_pos_update.take() {
60            if let Some(rect) = image.move_pointer(x, y)? {
61                processor_updates.push(UpdateKind::Region(rect));
62            }
63        }
64
65        let mut input = ReadCursor::new(input);
66
67        let header = decode_cursor::<FastPathHeader>(&mut input).map_err(SessionError::decode)?;
68        trace!(fast_path_header = ?header, "Received Fast-Path packet");
69
70        let update_pdu = decode_cursor::<FastPathUpdatePdu<'_>>(&mut input).map_err(SessionError::decode)?;
71        trace!(fast_path_update_fragmentation = ?update_pdu.fragmentation);
72
73        let processed_complete_data = self
74            .complete_data
75            .process_data(update_pdu.data, update_pdu.fragmentation);
76
77        let update_code = update_pdu.update_code;
78
79        let Some(data) = processed_complete_data else {
80            return Ok(Vec::new());
81        };
82
83        let update = FastPathUpdate::decode_with_code(data.as_slice(), update_code);
84
85        match update {
86            Ok(FastPathUpdate::SurfaceCommands(surface_commands)) => {
87                trace!("Received Surface Commands: {} pieces", surface_commands.len());
88                let update_region = self.process_surface_commands(image, output, surface_commands)?;
89                processor_updates.push(UpdateKind::Region(update_region));
90            }
91            Ok(FastPathUpdate::Bitmap(bitmap_update)) => {
92                trace!("Received bitmap update");
93
94                let mut buf = Vec::new();
95                let mut update_kind = UpdateKind::None;
96
97                for update in bitmap_update.rectangles {
98                    trace!("{update:?}");
99                    buf.clear();
100
101                    // Bitmap data is either compressed or uncompressed, depending
102                    // on whether the BITMAP_COMPRESSION flag is present in the
103                    // flags field.
104                    let update_rectangle = if update
105                        .compression_flags
106                        .contains(ironrdp_pdu::bitmap::Compression::BITMAP_COMPRESSION)
107                    {
108                        if update.bits_per_pixel == 32 {
109                            // Compressed bitmaps at a color depth of 32 bpp are compressed using RDP 6.0
110                            // Bitmap Compression and stored inside an RDP 6.0 Bitmap Compressed Stream
111                            // structure ([MS-RDPEGDI] section 2.2.2.5.1).
112                            debug!("32 bpp compressed RDP6_BITMAP_STREAM");
113
114                            match self.bitmap_stream_decoder.decode_bitmap_stream_to_rgb24(
115                                update.bitmap_data,
116                                &mut buf,
117                                usize::from(update.width),
118                                usize::from(update.height),
119                            ) {
120                                Ok(()) => image.apply_rgb24(&buf, &update.rectangle, true)?,
121                                Err(err) => {
122                                    warn!("Invalid RDP6_BITMAP_STREAM: {err}");
123                                    update.rectangle.clone()
124                                }
125                            }
126                        } else {
127                            // Compressed bitmaps not in 32 bpp format are compressed using Interleaved
128                            // RLE and encapsulated in an RLE Compressed Bitmap Stream structure (section
129                            // 2.2.9.1.1.3.1.2.4).
130                            debug!(bpp = update.bits_per_pixel, "Non-32 bpp compressed RLE_BITMAP_STREAM",);
131
132                            match ironrdp_graphics::rle::decompress(
133                                update.bitmap_data,
134                                &mut buf,
135                                usize::from(update.width),
136                                usize::from(update.height),
137                                usize::from(update.bits_per_pixel),
138                            ) {
139                                Ok(RlePixelFormat::Rgb16) => image.apply_rgb16_bitmap(&buf, &update.rectangle)?,
140
141                                // TODO: support other pixel formats…
142                                Ok(format @ (RlePixelFormat::Rgb8 | RlePixelFormat::Rgb15 | RlePixelFormat::Rgb24)) => {
143                                    warn!("Received RLE-compressed bitmap with unsupported color depth: {format:?}");
144                                    update.rectangle.clone()
145                                }
146
147                                Err(e) => {
148                                    warn!("Invalid RLE-compressed bitmap: {e}");
149                                    update.rectangle.clone()
150                                }
151                            }
152                        }
153                    } else {
154                        // Uncompressed bitmap data is formatted as a bottom-up, left-to-right series of
155                        // pixels. Each pixel is a whole number of bytes. Each row contains a multiple of
156                        // four bytes (including up to three bytes of padding, as necessary).
157                        trace!("Uncompressed raw bitmap");
158
159                        match update.bits_per_pixel {
160                            16 => image.apply_rgb16_bitmap(update.bitmap_data, &update.rectangle)?,
161                            // TODO: support other pixel formats…
162                            unsupported => {
163                                warn!("Invalid raw bitmap with {unsupported} bytes per pixels");
164                                update.rectangle.clone()
165                            }
166                        }
167                    };
168
169                    match update_kind {
170                        UpdateKind::Region(current) => {
171                            update_kind = UpdateKind::Region(current.union(&update_rectangle))
172                        }
173                        _ => update_kind = UpdateKind::Region(update_rectangle),
174                    }
175                }
176
177                processor_updates.push(update_kind);
178            }
179            Ok(FastPathUpdate::Pointer(update)) => {
180                if !self.enable_server_pointer {
181                    return Ok(processor_updates);
182                }
183
184                let bitmap_target = if self.pointer_software_rendering {
185                    PointerBitmapTarget::Software
186                } else {
187                    PointerBitmapTarget::Accelerated
188                };
189
190                match update {
191                    PointerUpdateData::SetHidden => {
192                        processor_updates.push(UpdateKind::PointerHidden);
193                        if self.pointer_software_rendering && !self.use_system_pointer {
194                            self.use_system_pointer = true;
195                            if let Some(rect) = image.hide_pointer()? {
196                                processor_updates.push(UpdateKind::Region(rect));
197                            }
198                        }
199                    }
200                    PointerUpdateData::SetDefault => {
201                        processor_updates.push(UpdateKind::PointerDefault);
202                        if self.pointer_software_rendering && !self.use_system_pointer {
203                            self.use_system_pointer = true;
204                            if let Some(rect) = image.hide_pointer()? {
205                                processor_updates.push(UpdateKind::Region(rect));
206                            }
207                        }
208                    }
209                    PointerUpdateData::SetPosition(position) => {
210                        if self.use_system_pointer || !self.pointer_software_rendering {
211                            processor_updates.push(UpdateKind::PointerPosition {
212                                x: position.x,
213                                y: position.y,
214                            });
215                        } else if let Some(rect) = image.move_pointer(position.x, position.y)? {
216                            processor_updates.push(UpdateKind::Region(rect));
217                        }
218                    }
219                    PointerUpdateData::Color(pointer) => {
220                        let cache_index = pointer.cache_index;
221
222                        let decoded_pointer = Arc::new(
223                            DecodedPointer::decode_color_pointer_attribute(&pointer, bitmap_target)
224                                .expect("Failed to decode color pointer attribute"),
225                        );
226
227                        let _ = self
228                            .pointer_cache
229                            .insert(usize::from(cache_index), Arc::clone(&decoded_pointer));
230
231                        if !self.pointer_software_rendering {
232                            processor_updates.push(UpdateKind::PointerBitmap(Arc::clone(&decoded_pointer)));
233                        } else if let Some(rect) = image.update_pointer(decoded_pointer)? {
234                            processor_updates.push(UpdateKind::Region(rect));
235                        }
236                    }
237                    PointerUpdateData::Cached(cached) => {
238                        let cache_index = cached.cache_index;
239
240                        if let Some(cached_pointer) = self.pointer_cache.get(usize::from(cache_index)) {
241                            // Disable system pointer
242                            processor_updates.push(UpdateKind::PointerHidden);
243                            self.use_system_pointer = false;
244                            // Send graphics update
245                            if !self.pointer_software_rendering {
246                                processor_updates.push(UpdateKind::PointerBitmap(Arc::clone(&cached_pointer)));
247                            } else if let Some(rect) = image.update_pointer(cached_pointer)? {
248                                processor_updates.push(UpdateKind::Region(rect));
249                            } else {
250                                // In case pointer was hidden previously
251                                if let Some(rect) = image.show_pointer()? {
252                                    processor_updates.push(UpdateKind::Region(rect));
253                                }
254                            }
255                        } else {
256                            warn!("Cached pointer not found {}", cache_index);
257                        }
258                    }
259                    PointerUpdateData::New(pointer) => {
260                        let cache_index = pointer.color_pointer.cache_index;
261
262                        let decoded_pointer = Arc::new(
263                            DecodedPointer::decode_pointer_attribute(&pointer, bitmap_target)
264                                .expect("Failed to decode pointer attribute"),
265                        );
266
267                        let _ = self
268                            .pointer_cache
269                            .insert(usize::from(cache_index), Arc::clone(&decoded_pointer));
270
271                        if !self.pointer_software_rendering {
272                            processor_updates.push(UpdateKind::PointerBitmap(Arc::clone(&decoded_pointer)));
273                        } else if let Some(rect) = image.update_pointer(decoded_pointer)? {
274                            processor_updates.push(UpdateKind::Region(rect));
275                        }
276                    }
277                    PointerUpdateData::Large(pointer) => {
278                        let cache_index = pointer.cache_index;
279
280                        let decoded_pointer: Arc<DecodedPointer> = Arc::new(
281                            DecodedPointer::decode_large_pointer_attribute(&pointer, bitmap_target)
282                                .expect("Failed to decode large pointer attribute"),
283                        );
284
285                        let _ = self
286                            .pointer_cache
287                            .insert(usize::from(cache_index), Arc::clone(&decoded_pointer));
288
289                        if !self.pointer_software_rendering {
290                            processor_updates.push(UpdateKind::PointerBitmap(Arc::clone(&decoded_pointer)));
291                        } else if let Some(rect) = image.update_pointer(decoded_pointer)? {
292                            processor_updates.push(UpdateKind::Region(rect));
293                        }
294                    }
295                };
296            }
297            Err(e) => {
298                // FIXME: This seems to be a way of special-handling the error case in FastPathUpdate::decode_cursor_with_code
299                // to ignore the unsupported update PDUs, but this is a fragile logic and the rationale behind it is not
300                // obvious.
301                if let DecodeErrorKind::InvalidField { field, reason } = e.kind {
302                    warn!(field, reason, "Received invalid Fast-Path update");
303                    processor_updates.push(UpdateKind::None);
304                } else {
305                    return Err(custom_err!("Fast-Path", e));
306                }
307            }
308        };
309
310        Ok(processor_updates)
311    }
312
313    fn process_surface_commands(
314        &mut self,
315        image: &mut DecodedImage,
316        output: &mut WriteBuf,
317        surface_commands: Vec<SurfaceCommand<'_>>,
318    ) -> SessionResult<InclusiveRectangle> {
319        let mut update_rectangle = None;
320
321        for command in surface_commands {
322            match command {
323                SurfaceCommand::SetSurfaceBits(bits) | SurfaceCommand::StreamSurfaceBits(bits) => {
324                    let codec_id = CodecId::from_u8(bits.extended_bitmap_data.codec_id).ok_or_else(|| {
325                        reason_err!(
326                            "Fast-Path",
327                            "unexpected codec ID: {:x}",
328                            bits.extended_bitmap_data.codec_id
329                        )
330                    })?;
331
332                    trace!(?codec_id, "Surface bits");
333
334                    let destination = bits.destination;
335                    // TODO(@pacmancoder): Correct rectangle conversion logic should
336                    // be revisited when `rectangle_processing.rs` from
337                    // `ironrdp-graphics` will be refactored to use generic `Rectangle`
338                    // trait instead of hardcoded `InclusiveRectangle`.
339                    let destination = InclusiveRectangle {
340                        left: destination.left,
341                        top: destination.top,
342                        right: destination.right - 1,
343                        bottom: destination.bottom - 1,
344                    };
345                    match codec_id {
346                        CODEC_ID_NONE => {
347                            let ext_data = bits.extended_bitmap_data;
348                            match ext_data.bpp {
349                                32 => {
350                                    let rectangle =
351                                        image.apply_rgb32_bitmap(ext_data.data, PixelFormat::BgrX32, &destination)?;
352                                    update_rectangle = update_rectangle
353                                        .map(|rect: InclusiveRectangle| rect.union(&rectangle))
354                                        .or(Some(rectangle));
355                                }
356                                bpp => {
357                                    warn!("Unsupported bpp: {bpp}")
358                                }
359                            }
360                        }
361                        CODEC_ID_REMOTEFX => {
362                            let mut data = ReadCursor::new(bits.extended_bitmap_data.data);
363                            while !data.is_empty() {
364                                let (_frame_id, rectangle) = self.rfx_handler.decode(image, &destination, &mut data)?;
365                                update_rectangle = update_rectangle
366                                    .map(|rect: InclusiveRectangle| rect.union(&rectangle))
367                                    .or(Some(rectangle));
368                            }
369                        }
370                        #[cfg(feature = "qoi")]
371                        ironrdp_pdu::rdp::capability_sets::CODEC_ID_QOI => {
372                            qoi_apply(
373                                image,
374                                destination,
375                                bits.extended_bitmap_data.data,
376                                &mut update_rectangle,
377                            )?;
378                        }
379                        #[cfg(feature = "qoiz")]
380                        ironrdp_pdu::rdp::capability_sets::CODEC_ID_QOIZ => {
381                            let compressed = &bits.extended_bitmap_data.data;
382                            let mut input = zstd_safe::InBuffer::around(compressed);
383                            let mut data = vec![0; compressed.len() * 4];
384                            let mut pos = 0;
385                            loop {
386                                let mut output = zstd_safe::OutBuffer::around_pos(data.as_mut_slice(), pos);
387                                self.zdctx
388                                    .decompress_stream(&mut output, &mut input)
389                                    .map_err(zstd_safe::get_error_name)
390                                    .map_err(|e| reason_err!("zstd", "{}", e))?;
391                                pos = output.pos();
392                                if pos == output.capacity() {
393                                    data.resize(data.capacity() * 2, 0);
394                                } else {
395                                    break;
396                                }
397                            }
398
399                            qoi_apply(image, destination, &data, &mut update_rectangle)?;
400                        }
401                        _ => {
402                            warn!("Unsupported codec ID: {}", bits.extended_bitmap_data.codec_id);
403                        }
404                    }
405                }
406                SurfaceCommand::FrameMarker(marker) => {
407                    trace!(
408                        "Frame marker: action {:?} with ID #{}",
409                        marker.frame_action,
410                        marker.frame_id.unwrap_or(0)
411                    );
412                    self.marker_processor.process(&marker, output)?;
413                }
414            }
415        }
416
417        Ok(update_rectangle.unwrap_or_else(InclusiveRectangle::empty))
418    }
419}
420
421#[cfg(feature = "qoi")]
422fn qoi_apply(
423    image: &mut DecodedImage,
424    destination: InclusiveRectangle,
425    data: &[u8],
426    update_rectangle: &mut Option<InclusiveRectangle>,
427) -> SessionResult<()> {
428    let (header, decoded) = qoi::decode_to_vec(data).map_err(|e| reason_err!("QOI decode", "{}", e))?;
429    match header.channels {
430        qoi::Channels::Rgb => {
431            let rectangle = image.apply_rgb24(&decoded, &destination, false)?;
432
433            *update_rectangle = update_rectangle
434                .as_ref()
435                .map(|rect: &InclusiveRectangle| rect.union(&rectangle))
436                .or(Some(rectangle));
437        }
438        qoi::Channels::Rgba => {
439            warn!("Unsupported RGBA QOI data");
440        }
441    }
442    Ok(())
443}
444
445pub struct ProcessorBuilder {
446    pub io_channel_id: u16,
447    pub user_channel_id: u16,
448    /// Ignore server pointer updates.
449    pub enable_server_pointer: bool,
450    /// Use software rendering mode for pointer bitmap generation. When this option is active,
451    /// `UpdateKind::PointerBitmap` will not be generated. Remote pointer will be drawn
452    /// via software rendering on top of the output image.
453    pub pointer_software_rendering: bool,
454}
455
456impl ProcessorBuilder {
457    pub fn build(self) -> Processor {
458        Processor {
459            complete_data: CompleteData::new(),
460            rfx_handler: rfx::DecodingContext::new(),
461            marker_processor: FrameMarkerProcessor::new(self.user_channel_id, self.io_channel_id),
462            bitmap_stream_decoder: BitmapStreamDecoder::default(),
463            pointer_cache: PointerCache::default(),
464            use_system_pointer: true,
465            mouse_pos_update: None,
466            enable_server_pointer: self.enable_server_pointer,
467            pointer_software_rendering: self.pointer_software_rendering,
468            #[cfg(feature = "qoiz")]
469            zdctx: zstd_safe::DCtx::default(),
470        }
471    }
472}
473
474#[derive(Debug, PartialEq)]
475struct CompleteData {
476    fragmented_data: Option<Vec<u8>>,
477}
478
479impl CompleteData {
480    fn new() -> Self {
481        Self { fragmented_data: None }
482    }
483
484    fn process_data(&mut self, data: &[u8], fragmentation: Fragmentation) -> Option<Vec<u8>> {
485        match fragmentation {
486            Fragmentation::Single => {
487                self.check_data_is_empty();
488
489                Some(data.to_vec())
490            }
491            Fragmentation::First => {
492                self.check_data_is_empty();
493
494                self.fragmented_data = Some(data.to_vec());
495
496                None
497            }
498            Fragmentation::Next => {
499                self.append_data(data);
500
501                None
502            }
503            Fragmentation::Last => {
504                self.append_data(data);
505
506                self.fragmented_data.take()
507            }
508        }
509    }
510
511    fn check_data_is_empty(&mut self) {
512        if self.fragmented_data.is_some() {
513            warn!("Skipping pending Fast-Path Update internal multiple elements data");
514            self.fragmented_data = None;
515        }
516    }
517
518    fn append_data(&mut self, data: &[u8]) {
519        if let Some(fragmented_data) = self.fragmented_data.as_mut() {
520            fragmented_data.extend_from_slice(data);
521        } else {
522            warn!("Got unexpected Next fragmentation PDU without prior First fragmentation PDU");
523        }
524    }
525}
526
527struct FrameMarkerProcessor {
528    user_channel_id: u16,
529    io_channel_id: u16,
530}
531
532impl FrameMarkerProcessor {
533    fn new(user_channel_id: u16, io_channel_id: u16) -> Self {
534        Self {
535            user_channel_id,
536            io_channel_id,
537        }
538    }
539
540    fn process(&mut self, marker: &FrameMarkerPdu, output: &mut WriteBuf) -> SessionResult<()> {
541        match marker.frame_action {
542            FrameAction::Begin => Ok(()),
543            FrameAction::End => {
544                ironrdp_connector::legacy::encode_share_data(
545                    self.user_channel_id,
546                    self.io_channel_id,
547                    0,
548                    ShareDataPdu::FrameAcknowledge(FrameAcknowledgePdu {
549                        frame_id: marker.frame_id.unwrap_or(0),
550                    }),
551                    output,
552                )
553                .map_err(crate::legacy::map_error)?;
554
555                Ok(())
556            }
557        }
558    }
559}