ironrdp_pdu/
lib.rs

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