Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions crates/ironrdp-ainput/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,8 @@ impl<'de> Decode<'de> for ServerPdu {
fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
ensure_fixed_part_size!(in: src);

let pdu_type =
ServerPduType::from_u16(src.read_u16()).ok_or_else(|| invalid_field_err!("pduType", "invalid pdu type"))?;
let pdu_type = ServerPduType::from_u16(src.read_u16())
.ok_or_else(|| invalid_field_err!("pduType", "invalid pdu type", at: 0))?;

let server_pdu = match pdu_type {
ServerPduType::Version => ServerPdu::Version(VersionPdu::decode(src)?),
Expand Down Expand Up @@ -256,8 +256,8 @@ impl<'de> Decode<'de> for ClientPdu {
fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
ensure_fixed_part_size!(in: src);

let pdu_type =
ClientPduType::from_u16(src.read_u16()).ok_or_else(|| invalid_field_err!("pduType", "invalid pdu type"))?;
let pdu_type = ClientPduType::from_u16(src.read_u16())
.ok_or_else(|| invalid_field_err!("pduType", "invalid pdu type", at: 0))?;

let client_pdu = match pdu_type {
ClientPduType::Mouse => ClientPdu::Mouse(MousePdu::decode(src)?),
Expand Down
3 changes: 1 addition & 2 deletions crates/ironrdp-cfg/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
mod target_addr;
pub use target_addr::{ParseTargetAddrError, TargetAddr, TargetHost};

use ironrdp_propertyset::PropertySet;
pub use target_addr::{ParseTargetAddrError, TargetAddr, TargetHost};

/// Error returned when the `server port` property value is outside the valid port range (1–65535).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down
12 changes: 6 additions & 6 deletions crates/ironrdp-cliprdr-format/src/bitmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,19 +243,19 @@ impl BitmapInfoHeader {

let width = src.read_i32();
check_invariant(width != i32::MIN && width.abs() <= 10_000)
.ok_or_else(|| invalid_field_err!("biWidth", "width is too big"))?;
.ok_or_else(|| invalid_field_err!("biWidth", "width is too big", at: 0))?;

let height = src.read_i32();
check_invariant(height != i32::MIN && height.abs() <= 10_000)
.ok_or_else(|| invalid_field_err!("biHeight", "height is too big"))?;
.ok_or_else(|| invalid_field_err!("biHeight", "height is too big", at: 0))?;

let planes = src.read_u16();
if planes != 1 {
return Err(invalid_field_err!("biPlanes", "invalid planes count"));
return Err(invalid_field_err!("biPlanes", "invalid planes count", at: 0));
}

let bit_count = src.read_u16();
check_invariant(bit_count <= 32).ok_or_else(|| invalid_field_err!("biBitCount", "invalid bit count"))?;
check_invariant(bit_count <= 32).ok_or_else(|| invalid_field_err!("biBitCount", "invalid bit count", at: 0))?;

let compression = BitmapCompression(src.read_u32());
let size_image = src.read_u32();
Expand Down Expand Up @@ -320,7 +320,7 @@ impl<'a> Decode<'a> for BitmapInfoHeader {
let size: usize = cast_int!("biSize", size)?;

if size != Self::FIXED_PART_SIZE {
return Err(invalid_field_err!("biSize", "invalid V1 bitmap info header size"));
return Err(invalid_field_err!("biSize", "invalid V1 bitmap info header size", at: 0));
}

Ok(header)
Expand Down Expand Up @@ -406,7 +406,7 @@ impl<'a> Decode<'a> for BitmapV5Header {
let size: usize = cast_int!("biSize", size)?;

if size != Self::FIXED_PART_SIZE {
return Err(invalid_field_err!("biSize", "invalid V5 bitmap info header size"));
return Err(invalid_field_err!("biSize", "invalid V5 bitmap info header size", at: 0));
}

let red_mask = src.read_u32();
Expand Down
6 changes: 2 additions & 4 deletions crates/ironrdp-cliprdr/src/pdu/capabilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,8 @@ impl<'de> Decode<'de> for CapabilitySet {
let general = GeneralCapabilitySet::decode(src)?;
Ok(Self::General(general))
}
_ => Err(invalid_field_err!(
"capabilitySetType",
"invalid clipboard capability set type"
)),
_ => Err(invalid_field_err!( "capabilitySetType",
"invalid clipboard capability set type", at: 0)),
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ impl ClientTemporaryDirectory<'_> {
let mut cursor = ReadCursor::new(&self.path_buffer);

read_string_from_cursor(&mut cursor, CharacterSet::Unicode, true)
.map_err(|_| invalid_field_err!("wszTempDir", "failed to decode temp dir path"))
.map_err(|_| invalid_field_err!("wszTempDir", "failed to decode temp dir path", at: 0))
}
}

Expand Down
37 changes: 14 additions & 23 deletions crates/ironrdp-cliprdr/src/pdu/file_contents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,18 +129,15 @@ impl<'a> FileContentsResponse<'a> {
/// Should not panic - the try_into conversion is guaranteed to succeed after length validation.
pub fn data_as_size(&self) -> DecodeResult<u64> {
if self.data.len() != 8 {
return Err(invalid_field_err!(
"requestedFileContentsData",
"SIZE response must be exactly 8 bytes per MS-RDPECLIP 2.2.5.4"
));
return Err(invalid_field_err!( "requestedFileContentsData",
"SIZE response must be exactly 8 bytes per MS-RDPECLIP 2.2.5.4", at: 0));
}

// Per length check above, this conversion is infallible.
let chunk: [u8; 8] = self
.data
.as_ref()
.try_into()
.map_err(|_| invalid_field_err!("requestedFileContentsData", "SIZE response data is not 8 bytes"))?;
let chunk: [u8; 8] =
self.data.as_ref().try_into().map_err(
|_| invalid_field_err!("requestedFileContentsData", "SIZE response data is not 8 bytes", at: 0),
)?;
Ok(u64::from_le_bytes(chunk))
}
}
Expand Down Expand Up @@ -182,7 +179,7 @@ impl<'de> Decode<'de> for FileContentsResponse<'de> {
ensure_size!(in: src, size: header.data_length());

if header.data_length() < Self::FIXED_PART_SIZE {
return Err(invalid_field_err!("requestedFileContentsData", "invalid data size"));
return Err(invalid_field_err!("requestedFileContentsData", "invalid data size", at: 0));
};

let data_size = header.data_length() - Self::FIXED_PART_SIZE;
Expand Down Expand Up @@ -287,28 +284,22 @@ impl<'de> Decode<'de> for FileContentsRequest {

// [MS-RDPECLIP] 2.2.5.3 - Validate lindex is non-negative
if index < 0 {
return Err(invalid_field_err!(
"lindex",
"file index must be non-negative per MS-RDPECLIP 2.2.5.3"
));
return Err(invalid_field_err!( "lindex",
"file index must be non-negative per MS-RDPECLIP 2.2.5.3", at: 0));
}

// [MS-RDPECLIP] 2.2.5.3 - Validate flags are spec-compliant
flags.validate().map_err(|e| invalid_field_err!("dwFlags", e))?;
flags.validate().map_err(|e| invalid_field_err!("dwFlags", e, at: 0))?;

// [MS-RDPECLIP] 2.2.5.3 - Validate SIZE request constraints
if flags.contains(FileContentsFlags::SIZE) {
if requested_size != 8 {
return Err(invalid_field_err!(
"cbRequested",
"SIZE request must have cbRequested=8 per MS-RDPECLIP 2.2.5.3"
));
return Err(invalid_field_err!( "cbRequested",
"SIZE request must have cbRequested=8 per MS-RDPECLIP 2.2.5.3", at: 0));
}
if position != 0 {
return Err(invalid_field_err!(
"position",
"SIZE request must have position=0 per MS-RDPECLIP 2.2.5.3"
));
return Err(invalid_field_err!( "position",
"SIZE request must have position=0 per MS-RDPECLIP 2.2.5.3", at: 0));
}
}

Expand Down
12 changes: 4 additions & 8 deletions crates/ironrdp-cliprdr/src/pdu/format_data/file_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,8 @@ impl Encode for FileDescriptor {
// UTF-16 encoding: each code unit is 2 bytes, plus 2 bytes for null terminator.
let encoded_len = wire_name.encode_utf16().count() * 2 + 2;
if NAME_LENGTH < encoded_len {
return Err(ironrdp_core::invalid_field_err!(
"cFileName",
"encoded wire name exceeds NAME_LENGTH (520 bytes)"
));
return Err(ironrdp_core::invalid_field_err!( "cFileName",
"encoded wire name exceeds NAME_LENGTH (520 bytes)", at: 0));
}

let written = encode_string(dst.remaining_mut(), &wire_name, CharacterSet::Unicode, true)?;
Expand Down Expand Up @@ -304,10 +302,8 @@ impl<'de> Decode<'de> for PackedFileList {
let file_count: usize = cast_length!(Self::NAME, "cItems", src.read_u32())?;

if MAX_FILE_COUNT < file_count {
return Err(ironrdp_core::invalid_field_err!(
"cItems",
"file count exceeds maximum of 100000"
));
return Err(ironrdp_core::invalid_field_err!( "cItems",
"file count exceeds maximum of 100000", at: 0));
}

// Cap pre-allocation against remaining bytes to prevent OOM from
Expand Down
2 changes: 1 addition & 1 deletion crates/ironrdp-cliprdr/src/pdu/format_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ impl<'de> Decode<'de> for FormatListResponse {
match header.message_flags {
ClipboardPduFlags::RESPONSE_OK => Ok(FormatListResponse::Ok),
ClipboardPduFlags::RESPONSE_FAIL => Ok(FormatListResponse::Fail),
_ => Err(invalid_field_err!("msgFlags", "invalid format list message flags")),
_ => Err(invalid_field_err!("msgFlags", "invalid format list message flags", at: 0)),
}
}
}
2 changes: 1 addition & 1 deletion crates/ironrdp-cliprdr/src/pdu/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ impl<'de> Decode<'de> for ClipboardPdu<'de> {
MSG_TYPE_FILE_CONTENTS_RESPONSE => ClipboardPdu::FileContentsResponse(FileContentsResponse::decode(src)?),
MSG_TYPE_LOCK_CLIPDATA => ClipboardPdu::LockData(LockDataId::decode(src)?),
MSG_TYPE_UNLOCK_CLIPDATA => ClipboardPdu::UnlockData(LockDataId::decode(src)?),
_ => return Err(invalid_field_err!("msgType", "unknown clipboard PDU type")),
_ => return Err(invalid_field_err!("msgType", "unknown clipboard PDU type", at: 0)),
};

Ok(pdu)
Expand Down
99 changes: 74 additions & 25 deletions crates/ironrdp-core/src/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,16 @@ pub type DecodeResult<T> = Result<T, DecodeError>;
/// An error type specifically for encoding operations, wrapping an [`DecodeErrorKind`].
pub type DecodeError = ironrdp_error::Error<DecodeErrorKind>;

/// Enum representing different kinds of decode errors.
/// Structured decode errors carry the fields needed to describe the failure,
/// including a byte `offset` when the error can be associated with
/// a position in the input stream.
///
/// The `offset` is the cursor position at, or nearest to, where the error was
/// detected. Producers without a stream cursor (a `try_from` on a primitive,
/// a constructor, a validator) pass `0`.
///
/// [`DecodeErrorKind::Other`] is reserved for errors that do not fit one of the
/// structured variants and therefore does not carry an offset.
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum DecodeErrorKind {
Expand All @@ -24,23 +33,40 @@ pub enum DecodeErrorKind {
received: usize,
/// Number of bytes expected.
expected: usize,
/// Byte offset in the input stream where the shortage was detected.
///
/// `0` indicates that the producer had no stream cursor in scope
/// (a `try_from` on a primitive, a constructor, a validator).
offset: usize,
},
/// Error when a field is invalid.
InvalidField {
/// Name of the invalid field.
field: &'static str,
/// Reason for invalidity.
reason: &'static str,
/// Byte offset in the input stream where the invalid field was decoded.
///
/// `0` indicates that the producer had no stream cursor in scope.
offset: usize,
},
/// Error when an unexpected message type is encountered.
UnexpectedMessageType {
/// The unexpected message type received.
got: u8,
/// Byte offset in the input stream where the unexpected type was read.
///
/// `0` indicates that the producer had no stream cursor in scope.
offset: usize,
},
/// Error when an unsupported version is encountered.
UnsupportedVersion {
/// The unsupported version received.
got: u8,
/// Byte offset in the input stream where the unsupported version was read.
///
/// `0` indicates that the producer had no stream cursor in scope.
offset: usize,
},
/// Error when an unsupported value is encountered (with allocation feature).
#[cfg(feature = "alloc")]
Expand All @@ -49,14 +75,26 @@ pub enum DecodeErrorKind {
name: &'static str,
/// The unsupported value.
value: String,
/// Byte offset in the input stream where the unsupported value was read.
///
/// `0` indicates that the producer had no stream cursor in scope.
offset: usize,
},
/// Error when an unsupported value is encountered (without allocation feature).
#[cfg(not(feature = "alloc"))]
UnsupportedValue {
/// Name of the unsupported value.
name: &'static str,
/// Byte offset in the input stream where the unsupported value was read.
///
/// `0` indicates that the producer had no stream cursor in scope.
offset: usize,
},
/// Generic error for other cases.
///
/// Does not carry an offset: producers of this variant typically do not
/// have stream-cursor access, and the variant exists precisely for those
/// cases.
Other {
/// Description of the error.
description: &'static str,
Expand All @@ -69,26 +107,30 @@ impl core::error::Error for DecodeErrorKind {}
impl fmt::Display for DecodeErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotEnoughBytes { received, expected } => write!(
Self::NotEnoughBytes {
received,
expected,
offset,
} => write!(
f,
"not enough bytes provided to decode: received {received} bytes, expected {expected} bytes"
"not enough bytes provided to decode at offset {offset}: received {received} bytes, expected {expected} bytes"
),
Self::InvalidField { field, reason } => {
write!(f, "invalid `{field}`: {reason}")
Self::InvalidField { field, reason, offset } => {
write!(f, "invalid `{field}` at offset {offset}: {reason}")
}
Self::UnexpectedMessageType { got } => {
write!(f, "invalid message type ({got})")
Self::UnexpectedMessageType { got, offset } => {
write!(f, "invalid message type ({got}) at offset {offset}")
}
Self::UnsupportedVersion { got } => {
write!(f, "unsupported version ({got})")
Self::UnsupportedVersion { got, offset } => {
write!(f, "unsupported version ({got}) at offset {offset}")
}
#[cfg(feature = "alloc")]
Self::UnsupportedValue { name, value } => {
write!(f, "unsupported {name} ({value})")
Self::UnsupportedValue { name, value, offset } => {
write!(f, "unsupported {name} ({value}) at offset {offset}")
}
#[cfg(not(feature = "alloc"))]
Self::UnsupportedValue { name } => {
write!(f, "unsupported {name}")
Self::UnsupportedValue { name, offset } => {
write!(f, "unsupported {name} at offset {offset}")
}
Self::Other { description } => {
write!(f, "other ({description})")
Expand All @@ -98,37 +140,44 @@ impl fmt::Display for DecodeErrorKind {
}
Comment thread
glamberson marked this conversation as resolved.

impl NotEnoughBytesErr for DecodeError {
fn not_enough_bytes(context: &'static str, received: usize, expected: usize) -> Self {
Self::new(context, DecodeErrorKind::NotEnoughBytes { received, expected })
fn not_enough_bytes(context: &'static str, received: usize, expected: usize, offset: usize) -> Self {
Self::new(
context,
DecodeErrorKind::NotEnoughBytes {
received,
expected,
offset,
},
)
}
}

impl InvalidFieldErr for DecodeError {
fn invalid_field(context: &'static str, field: &'static str, reason: &'static str) -> Self {
Self::new(context, DecodeErrorKind::InvalidField { field, reason })
fn invalid_field(context: &'static str, field: &'static str, reason: &'static str, offset: usize) -> Self {
Self::new(context, DecodeErrorKind::InvalidField { field, reason, offset })
}
}

impl UnexpectedMessageTypeErr for DecodeError {
fn unexpected_message_type(context: &'static str, got: u8) -> Self {
Self::new(context, DecodeErrorKind::UnexpectedMessageType { got })
fn unexpected_message_type(context: &'static str, got: u8, offset: usize) -> Self {
Self::new(context, DecodeErrorKind::UnexpectedMessageType { got, offset })
}
}

impl UnsupportedVersionErr for DecodeError {
fn unsupported_version(context: &'static str, got: u8) -> Self {
Self::new(context, DecodeErrorKind::UnsupportedVersion { got })
fn unsupported_version(context: &'static str, got: u8, offset: usize) -> Self {
Self::new(context, DecodeErrorKind::UnsupportedVersion { got, offset })
}
}

impl UnsupportedValueErr for DecodeError {
#[cfg(feature = "alloc")]
fn unsupported_value(context: &'static str, name: &'static str, value: String) -> Self {
Self::new(context, DecodeErrorKind::UnsupportedValue { name, value })
fn unsupported_value(context: &'static str, name: &'static str, value: String, offset: usize) -> Self {
Self::new(context, DecodeErrorKind::UnsupportedValue { name, value, offset })
}
#[cfg(not(feature = "alloc"))]
fn unsupported_value(context: &'static str, name: &'static str) -> Self {
Self::new(context, DecodeErrorKind::UnsupportedValue { name })
fn unsupported_value(context: &'static str, name: &'static str, offset: usize) -> Self {
Self::new(context, DecodeErrorKind::UnsupportedValue { name, offset })
}
}

Expand Down
Loading
Loading