Skip to content

Commit deb0ac8

Browse files
committed
feat(error): populate decode/encode error offsets from cursor positions
Audit-driven sweep through every call site that passed `0` as the offset to a decode or encode error producer. Foundation in e935058 documented `0` as the "offset unknown" sentinel; this commit replaces it with a real cursor position wherever one is in scope. Coverage: 408 callsites total. - 333 macro-form sites (`at: 0,` inside `invalid_field_err!`, `not_enough_bytes_err!`, `unexpected_message_type_err!`, `unsupported_version_err!`, `unsupported_value_err!`, `cast_length!`, `cast_int!`) upgraded to `in: <cursor>`. The cursor's name comes from the immediate enclosing function signature: most are `src`, several `dst`, a few `cursor`, and the BER routines use `stream`. - 9 direct-function-call sites (`invalid_field_err(...)`, `unexpected_message_type_err(...)`, `unsupported_value_err(...)`, `invalid_field_err_with_source(...)`) updated to pass `src.pos()` or `dst.pos()` from the enclosing decode/encode method. - The `per_field_err!` helper macro in `ironrdp-pdu/src/mcs.rs` now takes `in: $cursor` and captures the cursor's position into the returned closure, so PER read/write failures carry the byte offset of the field that failed. All 26 callers updated. - The `validate_dimensions!` and `check_masks_alignment!` local macros in displaycontrol/pointer now take an explicit `$offset:expr`. Their call sites pass `0` from constructors and `<cursor>.pos()` from decode/encode paths. - 57 sites remain at `at: 0`. These are in `try_from` impls on enum discriminants, `new`/`with_*` constructors, accessors on already decoded structs, dimension/size validators, and one `find_size` helper that takes `&[u8]` and reports byte-0 errors. None of them have a cursor in scope, so no offset is knowable. Foundation-crate macros were tightened to enforce the discipline at compile time: - `ensure_size!`'s bare buffer form was removed once `encode_string` was refactored to take `&mut WriteCursor<'_>`; the remaining form always extracts `$buf.pos()`. - `cast_length!` and `cast_int!` no longer accept a bare form. Callers must pick `in: <cursor>` to extract the position or `at: <offset>` to pass one explicitly. Both arms were applied per-site rather than mass-migrated. - Doc comments for `cast_length!` and `cast_int!` updated to show the new in:/at: forms. Test snapshots that printed full `Error` debug output were regenerated through `UPDATE_EXPECT=1`; the new fixtures now record the real byte offsets where decode failures originate. Examples: - `pcb.rs::null_size`: offset 4 (after reading u32 cbSize) - `pcb.rs::pcb_v2_string_too_big`: offset 18 - `x224::nego_request_unexpected_rdp_msg_type`: offset 35 - `x224::nego_confirm_unexpected_rdp_msg_type`: offset 12 - `x224::cookie_without_cr_lf_error_decode`: offset 20 The `encode_string` API change is a side effect of the macro tightening and is the only user-visible API change in this commit beyond the new error variant fields landed in e935058. Refs #1266, #1120.
1 parent e935058 commit deb0ac8

101 files changed

Lines changed: 788 additions & 796 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/ironrdp-ainput/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ impl<'de> Decode<'de> for ServerPdu {
160160
ensure_fixed_part_size!(in: src);
161161

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

165165
let server_pdu = match pdu_type {
166166
ServerPduType::Version => ServerPdu::Version(VersionPdu::decode(src)?),
@@ -257,7 +257,7 @@ impl<'de> Decode<'de> for ClientPdu {
257257
ensure_fixed_part_size!(in: src);
258258

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

262262
let client_pdu = match pdu_type {
263263
ClientPduType::Mouse => ClientPdu::Mouse(MousePdu::decode(src)?),

crates/ironrdp-cliprdr-format/src/bitmap.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -243,19 +243,19 @@ impl BitmapInfoHeader {
243243

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

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

252252
let planes = src.read_u16();
253253
if planes != 1 {
254-
return Err(invalid_field_err!(at: 0, "biPlanes", "invalid planes count"));
254+
return Err(invalid_field_err!(in: src, "biPlanes", "invalid planes count"));
255255
}
256256

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

260260
let compression = BitmapCompression(src.read_u32());
261261
let size_image = src.read_u32();
@@ -301,7 +301,7 @@ impl BitmapInfoHeader {
301301

302302
impl Encode for BitmapInfoHeader {
303303
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
304-
let size = cast_int!("biSize", Self::FIXED_PART_SIZE)?;
304+
let size = cast_int!(in: dst, "biSize", Self::FIXED_PART_SIZE)?;
305305
self.encode_with_size(dst, size)
306306
}
307307

@@ -317,10 +317,10 @@ impl Encode for BitmapInfoHeader {
317317
impl<'a> Decode<'a> for BitmapInfoHeader {
318318
fn decode(src: &mut ReadCursor<'a>) -> DecodeResult<Self> {
319319
let (header, size) = Self::decode_with_size(src)?;
320-
let size: usize = cast_int!("biSize", size)?;
320+
let size: usize = cast_int!(in: src, "biSize", size)?;
321321

322322
if size != Self::FIXED_PART_SIZE {
323-
return Err(invalid_field_err!(at: 0, "biSize", "invalid V1 bitmap info header size"));
323+
return Err(invalid_field_err!(in: src, "biSize", "invalid V1 bitmap info header size"));
324324
}
325325

326326
Ok(header)
@@ -369,7 +369,7 @@ impl Encode for BitmapV5Header {
369369
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
370370
ensure_fixed_part_size!(in: dst);
371371

372-
let size = cast_int!("biSize", Self::FIXED_PART_SIZE)?;
372+
let size = cast_int!(in: dst, "biSize", Self::FIXED_PART_SIZE)?;
373373
self.v1.encode_with_size(dst, size)?;
374374

375375
dst.write_u32(self.red_mask);
@@ -403,10 +403,10 @@ impl<'a> Decode<'a> for BitmapV5Header {
403403
ensure_fixed_part_size!(in: src);
404404

405405
let (header_v1, size) = BitmapInfoHeader::decode_with_size(src)?;
406-
let size: usize = cast_int!("biSize", size)?;
406+
let size: usize = cast_int!(in: src, "biSize", size)?;
407407

408408
if size != Self::FIXED_PART_SIZE {
409-
return Err(invalid_field_err!(at: 0, "biSize", "invalid V5 bitmap info header size"));
409+
return Err(invalid_field_err!(in: src, "biSize", "invalid V5 bitmap info header size"));
410410
}
411411

412412
let red_mask = src.read_u32();

crates/ironrdp-cliprdr/src/pdu/capabilities.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,12 @@ impl Capabilities {
6060

6161
impl Encode for Capabilities {
6262
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
63-
let header = PartialHeader::new(cast_int!("dataLen", self.inner_size())?);
63+
let header = PartialHeader::new(cast_int!(in: dst, "dataLen", self.inner_size())?);
6464
header.encode(dst)?;
6565

6666
ensure_size!(in: dst, size: self.inner_size());
6767

68-
dst.write_u16(cast_length!(Self::NAME, "cCapabilitiesSets", self.capabilities.len())?);
68+
dst.write_u16(cast_length!(in: dst, Self::NAME, "cCapabilitiesSets", self.capabilities.len())?);
6969
write_padding!(dst, 2);
7070

7171
for capability in &self.capabilities {
@@ -141,7 +141,7 @@ impl Encode for CapabilitySet {
141141

142142
ensure_size!(in: dst, size: length);
143143
dst.write_u16(Self::CAPSTYPE_GENERAL);
144-
dst.write_u16(cast_int!("lengthCapability", length)?);
144+
dst.write_u16(cast_int!(in: dst, "lengthCapability", length)?);
145145
caps.encode(dst)
146146
}
147147

@@ -170,7 +170,7 @@ impl<'de> Decode<'de> for CapabilitySet {
170170
let general = GeneralCapabilitySet::decode(src)?;
171171
Ok(Self::General(general))
172172
}
173-
_ => Err(invalid_field_err!(at: 0, "capabilitySetType",
173+
_ => Err(invalid_field_err!(in: src, "capabilitySetType",
174174
"invalid clipboard capability set type"
175175
)),
176176
}

crates/ironrdp-cliprdr/src/pdu/client_temporary_directory.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ impl ClientTemporaryDirectory<'_> {
5858

5959
impl Encode for ClientTemporaryDirectory<'_> {
6060
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
61-
let header = PartialHeader::new(cast_int!("dataLen", Self::INNER_SIZE)?);
61+
let header = PartialHeader::new(cast_int!(in: dst, "dataLen", Self::INNER_SIZE)?);
6262
header.encode(dst)?;
6363

6464
ensure_size!(in: dst, size: Self::INNER_SIZE);

crates/ironrdp-cliprdr/src/pdu/file_contents.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ impl Encode for FileContentsResponse<'_> {
151151
ClipboardPduFlags::RESPONSE_OK
152152
};
153153

154-
let header = PartialHeader::new_with_flags(cast_int!("dataLen", self.inner_size())?, flags);
154+
let header = PartialHeader::new_with_flags(cast_int!(in: dst, "dataLen", self.inner_size())?, flags);
155155
header.encode(dst)?;
156156

157157
ensure_size!(in: dst, size: self.inner_size());
@@ -180,7 +180,7 @@ impl<'de> Decode<'de> for FileContentsResponse<'de> {
180180
ensure_size!(in: src, size: header.data_length());
181181

182182
if header.data_length() < Self::FIXED_PART_SIZE {
183-
return Err(invalid_field_err!(at: 0, "requestedFileContentsData", "invalid data size"));
183+
return Err(invalid_field_err!(in: src, "requestedFileContentsData", "invalid data size"));
184184
};
185185

186186
let data_size = header.data_length() - Self::FIXED_PART_SIZE;
@@ -231,7 +231,7 @@ impl Encode for FileContentsRequest {
231231
/// Callers that build these PDUs are responsible for setting fields correctly;
232232
/// use [`FileContentsFlags::validate`] to check flag consistency.
233233
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
234-
let header = PartialHeader::new(cast_int!("dataLen", self.inner_size())?);
234+
let header = PartialHeader::new(cast_int!(in: dst, "dataLen", self.inner_size())?);
235235
header.encode(dst)?;
236236

237237
ensure_size!(in: dst, size: self.inner_size());
@@ -285,23 +285,23 @@ impl<'de> Decode<'de> for FileContentsRequest {
285285

286286
// [MS-RDPECLIP] 2.2.5.3 - Validate lindex is non-negative
287287
if index < 0 {
288-
return Err(invalid_field_err!(at: 0, "lindex",
288+
return Err(invalid_field_err!(in: src, "lindex",
289289
"file index must be non-negative per MS-RDPECLIP 2.2.5.3"
290290
));
291291
}
292292

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

296296
// [MS-RDPECLIP] 2.2.5.3 - Validate SIZE request constraints
297297
if flags.contains(FileContentsFlags::SIZE) {
298298
if requested_size != 8 {
299-
return Err(invalid_field_err!(at: 0, "cbRequested",
299+
return Err(invalid_field_err!(in: src, "cbRequested",
300300
"SIZE request must have cbRequested=8 per MS-RDPECLIP 2.2.5.3"
301301
));
302302
}
303303
if position != 0 {
304-
return Err(invalid_field_err!(at: 0, "position",
304+
return Err(invalid_field_err!(in: src, "position",
305305
"SIZE request must have position=0 per MS-RDPECLIP 2.2.5.3"
306306
));
307307
}

crates/ironrdp-cliprdr/src/pdu/format_data/file_list.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -184,13 +184,12 @@ impl Encode for FileDescriptor {
184184
// UTF-16 encoding: each code unit is 2 bytes, plus 2 bytes for null terminator.
185185
let encoded_len = wire_name.encode_utf16().count() * 2 + 2;
186186
if NAME_LENGTH < encoded_len {
187-
return Err(ironrdp_core::invalid_field_err!(at: 0, "cFileName",
187+
return Err(ironrdp_core::invalid_field_err!(in: dst, "cFileName",
188188
"encoded wire name exceeds NAME_LENGTH (520 bytes)"
189189
));
190190
}
191191

192-
let written = encode_string(dst.remaining_mut(), &wire_name, CharacterSet::Unicode, true)?;
193-
dst.advance(written);
192+
let written = encode_string(dst, &wire_name, CharacterSet::Unicode, true)?;
194193

195194
// Pad with zeroes, overriding any previously written data
196195
write_padding!(dst, NAME_LENGTH - written);
@@ -272,7 +271,7 @@ impl Encode for PackedFileList {
272271
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
273272
ensure_fixed_part_size!(in: dst);
274273

275-
dst.write_u32(cast_length!(Self::NAME, "cItems", self.files.len())?);
274+
dst.write_u32(cast_length!(in: dst, Self::NAME, "cItems", self.files.len())?);
276275

277276
for file in &self.files {
278277
file.encode(dst)?;
@@ -293,10 +292,10 @@ impl Encode for PackedFileList {
293292
impl<'de> Decode<'de> for PackedFileList {
294293
fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
295294
ensure_fixed_part_size!(in: src);
296-
let file_count: usize = cast_length!(Self::NAME, "cItems", src.read_u32())?;
295+
let file_count: usize = cast_length!(in: src, Self::NAME, "cItems", src.read_u32())?;
297296

298297
if MAX_FILE_COUNT < file_count {
299-
return Err(ironrdp_core::invalid_field_err!(at: 0, "cItems",
298+
return Err(ironrdp_core::invalid_field_err!(in: src, "cItems",
300299
"file count exceeds maximum of 100000"
301300
));
302301
}

crates/ironrdp-cliprdr/src/pdu/format_data/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ impl Encode for FormatDataResponse<'_> {
177177
ClipboardPduFlags::RESPONSE_OK
178178
};
179179

180-
let header = PartialHeader::new_with_flags(cast_int!("dataLen", self.data.len())?, flags);
180+
let header = PartialHeader::new_with_flags(cast_int!(in: dst, "dataLen", self.data.len())?, flags);
181181
header.encode(dst)?;
182182

183183
ensure_size!(in: dst, size: self.data.len());
@@ -228,7 +228,7 @@ impl FormatDataRequest {
228228

229229
impl Encode for FormatDataRequest {
230230
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
231-
let header = PartialHeader::new(cast_int!("dataLen", Self::FIXED_PART_SIZE)?);
231+
let header = PartialHeader::new(cast_int!(in: dst, "dataLen", Self::FIXED_PART_SIZE)?);
232232
header.encode(dst)?;
233233

234234
ensure_fixed_part_size!(in: dst);

crates/ironrdp-cliprdr/src/pdu/format_list.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ impl Encode for FormatList<'_> {
391391
ClipboardPduFlags::empty()
392392
};
393393

394-
let header = PartialHeader::new_with_flags(cast_int!("dataLen", self.encoded_formats.len())?, header_flags);
394+
let header = PartialHeader::new_with_flags(cast_int!(in: dst, "dataLen", self.encoded_formats.len())?, header_flags);
395395
header.encode(dst)?;
396396

397397
ensure_size!(in: dst, size: self.encoded_formats.len());
@@ -449,7 +449,7 @@ impl<'de> Decode<'de> for FormatListResponse {
449449
match header.message_flags {
450450
ClipboardPduFlags::RESPONSE_OK => Ok(FormatListResponse::Ok),
451451
ClipboardPduFlags::RESPONSE_FAIL => Ok(FormatListResponse::Fail),
452-
_ => Err(invalid_field_err!(at: 0, "msgFlags", "invalid format list message flags")),
452+
_ => Err(invalid_field_err!(in: src, "msgFlags", "invalid format list message flags")),
453453
}
454454
}
455455
}

crates/ironrdp-cliprdr/src/pdu/lock.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ impl LockDataId {
1818

1919
impl Encode for LockDataId {
2020
fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
21-
let header = PartialHeader::new(cast_int!("dataLen", Self::FIXED_PART_SIZE)?);
21+
let header = PartialHeader::new(cast_int!(in: dst, "dataLen", Self::FIXED_PART_SIZE)?);
2222
header.encode(dst)?;
2323

2424
ensure_fixed_part_size!(in: dst);

crates/ironrdp-cliprdr/src/pdu/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ impl<'de> Decode<'de> for ClipboardPdu<'de> {
243243
MSG_TYPE_FILE_CONTENTS_RESPONSE => ClipboardPdu::FileContentsResponse(FileContentsResponse::decode(src)?),
244244
MSG_TYPE_LOCK_CLIPDATA => ClipboardPdu::LockData(LockDataId::decode(src)?),
245245
MSG_TYPE_UNLOCK_CLIPDATA => ClipboardPdu::UnlockData(LockDataId::decode(src)?),
246-
_ => return Err(invalid_field_err!(at: 0, "msgType", "unknown clipboard PDU type")),
246+
_ => return Err(invalid_field_err!(in: src, "msgType", "unknown clipboard PDU type")),
247247
};
248248

249249
Ok(pdu)

0 commit comments

Comments
 (0)