feat(error): populate decode/encode error offsets from cursor positions#1275
Open
Greg Lamberson (glamberson) wants to merge 2 commits into
Open
feat(error): populate decode/encode error offsets from cursor positions#1275Greg Lamberson (glamberson) wants to merge 2 commits into
Greg Lamberson (glamberson) wants to merge 2 commits into
Conversation
deb0ac8 to
4cfaf9a
Compare
Comment on lines
116
to
121
| fn channel_by_id(&mut self, id: u32) -> DecodeResult<&mut DynamicChannel> { | ||
| let id = cast_length!("DRDYNVC", "", id)?; | ||
| let id = cast_length!(at: 0, "DRDYNVC", "", id)?; | ||
| self.dynamic_channels | ||
| .get_mut(id) | ||
| .ok_or_else(|| invalid_field_err!("DRDYNVC", "", "invalid channel id")) | ||
| .ok_or_else(|| invalid_field_err!(at: 0, "DRDYNVC", "", "invalid channel id")) | ||
| } |
There was a problem hiding this comment.
note: Here, we are using 0 for the offset because there is no relevant byte offset. In fact, we’re not even decoding anything, suggesting this function should not really return a DecodeResult. It’s fine to keep it that way with a dummy value for now, but we need to keep this in mind when performing this migration: we’ll have to change the error type of some functions, and we should not accomodate them too much in DecodeErrorKind
df0cec0 to
c18bd52
Compare
Reshapes the position-aware error approach per Devolutions#1266 review feedback. Moves position metadata out of the foundational `ironrdp-error` crate and onto the decode and encode `*ErrorKind` enum variants where it actually applies. Previously this PR added an optional `ErrorPosition { offset, field }` slot to `Error<Kind>`'s `ErrorMeta`, paying a metadata slot on every error in the workspace to serve only the decode and encode surfaces. The new shape places a non-optional `offset: usize` on each variant that can know one: - `DecodeErrorKind::{NotEnoughBytes, InvalidField, UnexpectedMessageType, UnsupportedVersion, UnsupportedValue}` gain `offset: usize` - `EncodeErrorKind::*` mirrors the same shape for encode-side errors - `*::Other` is unchanged; producers of `Other` typically lack stream-cursor access and the variant exists precisely for those cases The `in_field` dotted-path use case is dropped from this PR per the same review: `Error::set_context` already covers it. No parallel structured field-tracking channel is introduced. Trait and macro changes: - `NotEnoughBytesErr`, `InvalidFieldErr`, `UnexpectedMessageTypeErr`, `UnsupportedVersionErr`, `UnsupportedValueErr` all take an additional `offset: usize` parameter - The five corresponding `*_err!` macros gain `at:` and `in:` prefixes for explicit offset and cursor-extracted offset respectively - `OtherErr` and `other_err!` are unchanged - `cast_length!` and `cast_int!` macros pass `0` to the new offset slot (the macros do not have stream-cursor access) Call-site migration: - All 84 affected files updated to pass an explicit offset - Most existing call sites pass `0` (the documented "offset unknown" sentinel) since the cursor is not always in lexical scope at the call site - Cursor-aware sites can opt in via the new `in: cursor` macro syntax - A workspace sweep upgrading `at: 0` to `in: cursor` where applicable is a follow-up Display output includes the offset when known: `invalid \`field\` at offset 47: reason` Refs Devolutions#1257, Devolutions#1120. Supersedes the cross-cutting `ErrorPosition` approach that was on this branch before today's review.
Audit-driven sweep through every call site that passed `0` as the offset to a decode or encode error producer. Foundation in Devolutions#1266 documented `0` as the "offset unknown" sentinel; this commit replaces it with a real cursor position wherever one is in scope. Coverage: 408 call sites total reviewed via brace-depth enclosing-fn analysis. - 333 macro-form sites in `invalid_field_err!`, `not_enough_bytes_err!`, `unexpected_message_type_err!`, `unsupported_version_err!`, `unsupported_value_err!`, `cast_length!`, `cast_int!` upgraded from `at: 0` 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 inner offset is now `$buf.pos()` instead of `0`, enabled by refactoring `encode_string` from `dst: &mut [u8]` to `dst: &mut WriteCursor<'_>` so the cursor invariant holds for every caller. The two slice-form callers (`cliprdr/format_data/file_list.rs` and `rdp/capability_sets/input.rs`) were updated to pass the cursor directly and use `write_padding!` for fixed-width fields. - `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. - Doc comments for `cast_length!` and `cast_int!` updated to show the new in:/at: forms. Test snapshots that print 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 null_size at offset 4 (after the u32 cbSize read), pcb_v2_string_too_big at 18, x224 nego_request_unexpected_rdp_msg_type at 35. The `encode_string` API change is the only user-visible API change in this commit beyond Devolutions#1266's new error variant fields. Refs Devolutions#1266, Devolutions#1120.
c18bd52 to
8943637
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Depends on #1266. This PR stacks on the foundational reshape; the GitHub diff will collapse once #1266 merges. Both branches share base commit
e935058c.Summary
Audit-driven workspace sweep populating the offset field on every decode and encode error variant introduced by #1266. Where #1266 documented
0as the "offset unknown" sentinel and left existing call sites passing it, this PR walks the workspace and replaces it withcursor.pos()wherever aReadCursororWriteCursoris in lexical scope.Kept separate from #1266 so the foundational error-shape change stays a reviewable single-purpose PR.
Methodology
Per-site classification, not a heuristic substitution. For each call site that touches the new
offsetparameter:fn(brace-depth walk, not a regex over the file).&mut ReadCursor<...>or&mut WriteCursor<...>parameter.stream, for example, notsrc).at: 0,. The function genuinely has no buffer to position-from.macro_rules!body, handle the macro individually (adapt its signature to accept an offset, update each caller with the offset reachable at that call site).Audit script lives in the PR description history if you want to reproduce: walks all
at: 0,macro forms plus direct calls toinvalid_field_err(..., 0),unexpected_message_type_err(..., 0),unsupported_value_err(..., 0), andinvalid_field_err_with_source(..., 0, e).Coverage
408 call sites reviewed in total:
at: 0,in a cursor-bearing fnin: <cursor>using the parameter's actual nameat: 0,with no cursor in the enclosing fnat: 0(try_from impls on enum discriminants, new/with_* constructors, accessors on already-decoded structs, dimension validators,find_size(&[u8]))macro_rules!bodycursor.pos(), 1 (find_size) stays0Macro adapters
Three local helper macros hardcoded
0as the offset and were invoked from multiple call sites with differing cursor availability. Each macro was adapted to take an explicit offset argument; each caller now passes either0(no cursor in scope) orcursor.pos()(cursor in scope):validate_dimensions!inironrdp-displaycontrol/src/pdu/mod.rs. Two callers: constructor passes0, decode passessrc.pos().check_masks_alignment!inironrdp-pdu/src/basic_output/pointer/mod.rs. Four callers, all inside encode or decode methods.per_field_err!inironrdp-pdu/src/mcs.rs. This one captures cursor position into the returned closure (move |error| invalid_field_err_with_source(..., offset, error)) so PER read/write failures carry the byte offset of the field that failed. All 26 callers updated to passin: src,orin: dst,depending on whether they're in aper::read_*orper::write_*chain.Foundation-crate macro tightening
The bare forms of three macros were removed so the discipline is enforced at compile time rather than reviewed by hand:
ensure_size!: lost its bare buffer form; the remaining form always extracts$buf.pos(). To make this work,encode_stringwas refactored fromdst: &mut [u8]todst: &mut WriteCursor<'_>. The two callers using the slice form (cliprdr/format_data/file_list.rsandrdp/capability_sets/input.rs) were updated to pass the cursor directly and usewrite_padding!for fixed-width fields. This is the only user-visible API change in this PR.cast_length!andcast_int!: lost their bare forms. Callers must pickin: <cursor>to extract the position orat: <offset>to pass one explicitly. Doc comments updated.Test snapshots
expect_testfixtures that print fullErrordebug output were regenerated viaUPDATE_EXPECT=1and now record the real byte offsets where decode failures originate. Examples:pcb::null_sizepcb::truncatedpcb::pcb_v2_string_too_bigx224::nego_request_unexpected_rdp_msg_typex224::nego_confirm_unexpected_rdp_msg_typex224::cookie_without_cr_lf_error_decodeDiff
Single commit
deb0ac85on top of #1266'se935058c. 101 files changed, +788 / -796 (the negative delta is real: removing the bare-form macro arms shrinksmacros.rs, and many call sites became shorter after the cursor extraction).CI
cargo xtask check fmt | lints | locks | typos | testsgreen.cargo test --workspace --exclude ironrdp-web --exclude ironrdp-bench --testsgreen.Refs #1266, #1120.