ironrdp_pdu/
lib.rs

1#![cfg_attr(doc, doc = include_str!("../README.md"))]
2#![doc(html_logo_url = "https://cdnweb.devolutions.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg")]
3#![allow(clippy::arithmetic_side_effects)] // FIXME: remove
4#![allow(clippy::cast_lossless)] // FIXME: remove
5#![allow(clippy::cast_possible_truncation)] // FIXME: remove
6#![allow(clippy::cast_possible_wrap)] // FIXME: remove
7#![allow(clippy::cast_sign_loss)] // FIXME: remove
8
9use core::fmt;
10
11// TODO(#583): uncomment once re-exports are removed.
12// use ironrdp_core::{unexpected_message_type_err, DecodeResult, EncodeResult, ReadCursor};
13use ironrdp_error::Source;
14
15mod macros;
16
17pub mod codecs;
18pub mod gcc;
19pub mod geometry;
20pub mod input;
21pub mod mcs;
22pub mod nego;
23pub mod pcb;
24pub mod rdp;
25pub mod tpdu;
26pub mod tpkt;
27pub mod utf16;
28pub mod utils;
29pub mod x224;
30
31pub(crate) mod basic_output;
32pub(crate) mod ber;
33pub(crate) mod crypto;
34pub(crate) mod per;
35
36pub use crate::basic_output::{bitmap, fast_path, pointer, surface_commands};
37pub use crate::rdp::vc::dvc;
38
39pub type PduResult<T> = Result<T, PduError>;
40
41pub type PduError = ironrdp_error::Error<PduErrorKind>;
42
43#[non_exhaustive]
44#[derive(Clone, Debug)]
45pub enum PduErrorKind {
46    Encode,
47    Decode,
48    Other { description: &'static str },
49}
50
51pub trait PduErrorExt {
52    fn decode<E: Source>(context: &'static str, source: E) -> Self;
53
54    fn encode<E: Source>(context: &'static str, source: E) -> Self;
55}
56
57impl PduErrorExt for PduError {
58    fn decode<E: Source>(context: &'static str, source: E) -> Self {
59        Self::new(context, PduErrorKind::Decode).with_source(source)
60    }
61
62    fn encode<E: Source>(context: &'static str, source: E) -> Self {
63        Self::new(context, PduErrorKind::Encode).with_source(source)
64    }
65}
66
67impl core::error::Error for PduErrorKind {}
68
69impl fmt::Display for PduErrorKind {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        match self {
72            Self::Encode => {
73                write!(f, "encode error")
74            }
75            Self::Decode => {
76                write!(f, "decode error")
77            }
78            Self::Other { description } => {
79                write!(f, "other ({description})")
80            }
81        }
82    }
83}
84
85/// An RDP PDU.
86pub trait Pdu {
87    /// Name associated to this PDU.
88    const NAME: &'static str;
89}
90
91#[derive(Debug, Copy, Clone, PartialEq, Eq)]
92#[repr(u8)]
93pub enum Action {
94    FastPath = 0x00,
95    X224 = 0x03,
96}
97
98impl Action {
99    pub fn from_fp_output_header(fp_output_header: u8) -> Result<Self, u8> {
100        match fp_output_header & 0b11 {
101            0x00 => Ok(Self::FastPath),
102            0x03 => Ok(Self::X224),
103            unknown_action_bits => Err(unknown_action_bits),
104        }
105    }
106
107    pub fn as_u8(self) -> u8 {
108        self as u8
109    }
110}
111
112#[derive(Debug, Copy, Clone, PartialEq, Eq)]
113pub struct PduInfo {
114    pub action: Action,
115    pub length: usize,
116}
117
118/// Finds next RDP PDU size by reading the next few bytes.
119pub fn find_size(bytes: &[u8]) -> DecodeResult<Option<PduInfo>> {
120    macro_rules! ensure_enough {
121        ($bytes:expr, $len:expr) => {
122            if $bytes.len() < $len {
123                return Ok(None);
124            }
125        };
126    }
127
128    ensure_enough!(bytes, 1);
129    let fp_output_header = bytes[0];
130
131    let action = Action::from_fp_output_header(fp_output_header)
132        .map_err(|unknown_action| unexpected_message_type_err("fpOutputHeader", unknown_action))?;
133
134    match action {
135        Action::X224 => {
136            ensure_enough!(bytes, tpkt::TpktHeader::SIZE);
137            let tpkt = tpkt::TpktHeader::read(&mut ReadCursor::new(bytes))?;
138
139            Ok(Some(PduInfo {
140                action,
141                length: tpkt.packet_length(),
142            }))
143        }
144        Action::FastPath => {
145            ensure_enough!(bytes, 2);
146            let a = bytes[1];
147
148            let fast_path_length = if a & 0x80 != 0 {
149                ensure_enough!(bytes, 3);
150                let b = bytes[2];
151
152                ((u16::from(a) & !0x80) << 8) + u16::from(b)
153            } else {
154                u16::from(a)
155            };
156
157            Ok(Some(PduInfo {
158                action,
159                length: usize::from(fast_path_length),
160            }))
161        }
162    }
163}
164
165pub trait PduHint: Send + Sync + fmt::Debug + 'static {
166    /// Finds next PDU size by reading the next few bytes.
167    ///
168    /// Returns `Some((hint_matching, size))` if the size is known.
169    /// Returns `None` if the size cannot be determined yet.
170    fn find_size(&self, bytes: &[u8]) -> DecodeResult<Option<(bool, usize)>>;
171}
172
173// Matches both X224 and FastPath pdus
174#[derive(Clone, Copy, Debug)]
175pub struct RdpHint;
176
177pub const RDP_HINT: RdpHint = RdpHint;
178
179impl PduHint for RdpHint {
180    fn find_size(&self, bytes: &[u8]) -> DecodeResult<Option<(bool, usize)>> {
181        find_size(bytes).map(|opt| opt.map(|info| (true, info.length)))
182    }
183}
184
185#[derive(Clone, Copy, Debug)]
186pub struct X224Hint;
187
188pub const X224_HINT: X224Hint = X224Hint;
189
190impl PduHint for X224Hint {
191    fn find_size(&self, bytes: &[u8]) -> DecodeResult<Option<(bool, usize)>> {
192        match find_size(bytes)? {
193            Some(pdu_info) => {
194                let res = (pdu_info.action == Action::X224, pdu_info.length);
195                Ok(Some(res))
196            }
197            None => Ok(None),
198        }
199    }
200}
201
202#[derive(Clone, Copy, Debug)]
203pub struct FastPathHint;
204
205pub const FAST_PATH_HINT: FastPathHint = FastPathHint;
206
207impl PduHint for FastPathHint {
208    fn find_size(&self, bytes: &[u8]) -> DecodeResult<Option<(bool, usize)>> {
209        match find_size(bytes)? {
210            Some(pdu_info) => {
211                let res = (pdu_info.action == Action::FastPath, pdu_info.length);
212                Ok(Some(res))
213            }
214            None => Ok(None),
215        }
216    }
217}
218
219// Private! Used by the macros.
220#[doc(hidden)]
221pub use ironrdp_core;
222
223// -- Temporary re-exports to ease teleport’s migration to the newer versions -- //
224// TODO(#583): remove once Teleport migrated to the newer item paths.
225// NOTE: #[deprecated] has no effect on re-exports, so this is mostly for documenting the code at this point.
226#[doc(hidden)]
227#[deprecated(since = "0.1.0", note = "use ironrdp_core::{ReadCursor, WriteCursor}")]
228pub mod cursor {
229    pub use ironrdp_core::{ReadCursor, WriteCursor};
230}
231
232#[doc(hidden)]
233#[deprecated(since = "0.1.0", note = "use ironrdp_core::WriteBuf")]
234pub mod write_buf {
235    pub use ironrdp_core::WriteBuf;
236}
237
238#[doc(hidden)]
239#[deprecated(since = "0.1.0", note = "use ironrdp_core")]
240pub use ironrdp_core::*;
241
242#[doc(hidden)]
243#[deprecated(since = "0.1.0")]
244#[macro_export]
245macro_rules! custom_err {
246    ( $description:expr, $source:expr $(,)? ) => {{
247        $crate::PduError::new(
248            $description,
249            $crate::PduErrorKind::Other {
250                description: $description,
251            },
252        )
253        .with_source($source)
254    }};
255    ( $source:expr $(,)? ) => {{
256        $crate::custom_err!($crate::function!(), $source)
257    }};
258}
259
260#[doc(hidden)]
261#[deprecated(since = "0.1.0", note = "use ironrdp_core::other_err")]
262pub use crate::pdu_other_err as other_err;