Skip to content

feat(error): add ErrorClassification trait for triage-friendly Error<Kind> categories#1267

Open
Greg Lamberson (glamberson) wants to merge 1 commit into
Devolutions:masterfrom
lamco-admin:feat/ironrdp-error-classification
Open

feat(error): add ErrorClassification trait for triage-friendly Error<Kind> categories#1267
Greg Lamberson (glamberson) wants to merge 1 commit into
Devolutions:masterfrom
lamco-admin:feat/ironrdp-error-classification

Conversation

@glamberson
Copy link
Copy Markdown
Contributor

Summary

Introduces ironrdp_error::ErrorClassification, a trait that assigns each *ErrorKind variant a coarse [ErrorCategory] (one of Protocol, DataCorruption, InternalBug, Unknown). A convenience Error<Kind>::classify() method becomes available on Error<Kind> when Kind: ErrorClassification, forwarding to the inner kind.

Implements the trait for DecodeErrorKind and EncodeErrorKind in ironrdp-core, and re-exports ErrorCategory and ErrorClassification from ironrdp-core so consumers can pull them via the same crate they already use for DecodeError / EncodeError.

Closes out the third of the substantive ironrdp-error follow-ons from #1257 (after #1262 location capture, #1263 bail!/ensure! macros, and the position-aware metadata in #1266). Primary consumer is the structured-fuzzing oracle code in #1120.

Why this matters for fuzzing

Structured-fuzz oracles need to distinguish:

  • Expected outcome: the decoder correctly rejected a malformed input (Protocol, DataCorruption).
  • Bug signal: the decoder reached an internal invariant violation that should not happen for any input (InternalBug).

Today, an oracle can only differentiate by parsing the Display string of an error, which is fragile and tightly couples oracle code to formatting choices. After this PR, the oracle calls error.classify() and matches on an enum.

API

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ErrorCategory {
    Protocol,
    DataCorruption,
    InternalBug,
    Unknown,
}

pub trait ErrorClassification {
    fn classify(&self) -> ErrorCategory;
}

impl<Kind: ErrorClassification> Error<Kind> {
    pub fn classify(&self) -> ErrorCategory;
}

Initial impls in ironrdp-core

Kind Structured variants Other
DecodeErrorKind Protocol (peer-driven wire-format failures) Unknown
EncodeErrorKind InternalBug (we miscalculated something in our own serialisation path) Unknown

Other is intentionally Unknown because it carries a free-form description: &'static str and cannot be classified without inspecting that string at compile time.

DataCorruption is defined now but not used by DecodeErrorKind / EncodeErrorKind because those enums only carry low-level wire-format variants. It is reserved for the deeper-validation variants that other *ErrorKind enums in the workspace will adopt in follow-on PRs (e.g. session-layer integrity checks, license validation, capability negotiation mismatches).

Adoption pattern for other crates

The trait is intentionally not given a default impl returning Unknown. Per-crate adoption is opt-in: an *ErrorKind that wants classification implements the trait, and Error<Kind>::classify() becomes available on Error<Kind> accordingly. Kinds that have not been audited yet simply do not expose the convenience method. This forces classification work to be deliberate rather than silently degrading to Unknown for unaudited variants.

Scope

Strictly additive: no existing *ErrorKind enum signature changes, no existing constructor changes, no Display / Debug changes. The new trait method is purely opt-in for consumers who need categorisation.

This PR limits the per-crate impl work to ironrdp-core's two foundational kinds. Follow-on PRs may extend the impl to other workspace *ErrorKind types as their owners audit them.

Diff

  • crates/ironrdp-error/src/lib.rs (+73): ErrorCategory enum, ErrorClassification trait, Error<Kind>::classify() convenience impl block
  • crates/ironrdp-core/src/lib.rs (+2): re-exports ErrorCategory and ErrorClassification
  • crates/ironrdp-core/src/decode.rs (+19): ErrorClassification impl for DecodeErrorKind
  • crates/ironrdp-core/src/encode.rs (+20): ErrorClassification impl for EncodeErrorKind

Total: 4 files, +114, 0 deletions.

CI

cargo xtask check fmt | lints | locks | typos | tests all pass locally. ironrdp-core builds clean under --no-default-features (no_std) and --all-features.

Relationship to the other open error PRs

PR What it does Independent?
#1264 Removes the existing thiserror dependency from ironrdp-pdu by hand-rolling Display/Error impls Yes, independent
#1266 Adds optional byte-offset + symbolic field-context metadata to Error<Kind> Yes, independent
#1267 (this) Adds ErrorClassification trait + category enum + initial impls Yes, independent

All three are independent and can land in any order.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a lightweight, opt-in error triage mechanism by introducing a coarse ErrorCategory enum and an ErrorClassification trait in ironrdp-error, then wiring initial implementations for ironrdp-core’s DecodeErrorKind and EncodeErrorKind (and re-exporting the API from ironrdp-core).

Changes:

  • Introduces ironrdp_error::ErrorCategory and ironrdp_error::ErrorClassification, plus Error<Kind>::classify() when Kind: ErrorClassification.
  • Implements ErrorClassification for ironrdp_core::DecodeErrorKind and ironrdp_core::EncodeErrorKind.
  • Re-exports ErrorCategory and ErrorClassification from ironrdp-core to keep the common import path for consumers.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
crates/ironrdp-error/src/lib.rs Defines ErrorCategory, ErrorClassification, and adds Error<Kind>::classify() forwarding to the inner kind.
crates/ironrdp-core/src/lib.rs Re-exports ErrorCategory/ErrorClassification for easier downstream consumption via ironrdp-core.
crates/ironrdp-core/src/decode.rs Classifies DecodeErrorKind structured variants as Protocol, with Other as Unknown.
crates/ironrdp-core/src/encode.rs Classifies EncodeErrorKind structured variants as InternalBug, with Other as Unknown.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/ironrdp-error/src/lib.rs Outdated
…Kind> categories

Introduces ironrdp_error::{ErrorCategory, ErrorClassification} so consumers
can route errors by coarse category (Protocol, DataCorruption, InternalBug,
Unknown) without inspecting Display strings. Adds a convenience
Error<Kind>::classify() method that is available when Kind: ErrorClassification.

Implements the trait for the two foundational Kind types in ironrdp-core:
DecodeErrorKind classifies all structured variants as Protocol (peer-driven
wire-format failures) and Other as Unknown; EncodeErrorKind classifies all
structured variants as InternalBug (we miscalculated something in our own
serialisation path) and Other as Unknown. ironrdp-core re-exports the trait
and enum so consumers can pull them via the same crate they already use for
DecodeError/EncodeError type aliases.

Primary consumer is the structured-fuzzing oracle code in ironrdp-fuzzing
tracked under Devolutions#1120: categories let an oracle distinguish "decoder
correctly rejected malformed input" from "decoder hit an internal invariant
violation" without inspecting Display strings. The DataCorruption category
is defined now and reserved for the deeper-validation variants that other
*ErrorKind enums in the workspace will adopt in follow-on PRs.

Refs Devolutions#1257, Devolutions#1120.
@glamberson Greg Lamberson (glamberson) force-pushed the feat/ironrdp-error-classification branch from d48f20e to c336d58 Compare May 13, 2026 10:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants