Skip to main content

piaf/model/
diagnostics.rs

1/// A reference-counted, type-erased warning value.
2///
3/// Any type that implements [`core::error::Error`] + [`Send`] + [`Sync`] + `'static` can be
4/// wrapped in a `ParseWarning`. The built-in library variants use [`EdidWarning`], but
5/// custom handlers may push their own error types without wrapping them in `EdidWarning`.
6///
7/// Using [`Arc`][crate::model::prelude::Arc] (rather than `Box`) means `ParseWarning` is
8/// [`Clone`], which lets warnings be copied from [`crate::ParsedEdid`] into
9/// [`crate::DisplayCapabilities`] without consuming the parsed result.
10///
11/// To inspect a specific variant, use the inherent `downcast_ref` method available on
12/// `dyn core::error::Error + Send + Sync + 'static` in `std` builds:
13///
14/// ```text
15/// for w in caps.iter_warnings() {
16///     if let Some(ew) = (**w).downcast_ref::<EdidWarning>() { ... }
17/// }
18/// ```
19#[cfg(any(feature = "alloc", feature = "std"))]
20pub type ParseWarning = crate::model::prelude::Arc<dyn core::error::Error + Send + Sync + 'static>;
21
22/// A non-fatal condition encountered while parsing or processing an EDID block.
23///
24/// Warnings are collected into [`ParsedEdid::warnings`][crate::ParsedEdid] (from the parser)
25/// and into [`DisplayCapabilities::warnings`][crate::DisplayCapabilities] (from handlers).
26/// In `alloc`/`std` builds each entry is a [`ParseWarning`]; use `downcast_ref` to recover the
27/// concrete type. In bare `no_std` builds this enum is used directly.
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
30pub enum EdidWarning {
31    /// An extension block with an unrecognised tag was encountered.
32    /// The inner value is the tag byte.
33    #[error("unknown extension block tag: {0:#04x}")]
34    UnknownExtension(u8),
35    /// A 18-byte descriptor could not be decoded.
36    #[error("descriptor could not be parsed")]
37    DescriptorParseFailed,
38    /// The manufacturer ID bytes do not encode a valid PNP ID.
39    ///
40    /// Each of the three 5-bit fields must be in the range 1–26 (A–Z). Values of 0 or
41    /// 27–31 indicate a corrupted or unprogrammed EEPROM.
42    /// [`DisplayCapabilities::manufacturer`][crate::DisplayCapabilities::manufacturer]
43    /// is left as `None`.
44    #[error("manufacturer ID bytes do not encode a valid PNP ID")]
45    InvalidManufacturerId,
46    /// A data block inside an extension block declared a length that extends past the
47    /// end of the data block collection. Remaining data blocks in the collection are skipped.
48    #[error("data block length exceeds collection boundary")]
49    MalformedDataBlock,
50    /// A Detailed Timing Descriptor slot was skipped because the byte slice passed to the
51    /// decoder was shorter than the required 18 bytes.
52    ///
53    /// This indicates a malformed extension block that claimed to contain a DTD but did
54    /// not supply enough data.
55    #[error("DTD slot skipped: slice is shorter than the required 18 bytes")]
56    DtdSlotTooShort,
57    /// A Detailed Timing Descriptor slot was skipped because the pixel clock value
58    /// would overflow during refresh rate calculation.
59    ///
60    /// This indicates a malformed or corrupted EDID: valid pixel clocks are at most
61    /// a few hundred MHz (fits comfortably in a `u32` after scaling by 10 000).
62    #[error("DTD slot skipped: pixel clock overflow during refresh rate calculation")]
63    DtdPixelClockOverflow,
64    /// The number of extension blocks declared in the base block exceeds the parser's
65    /// safety limit. Only the first `limit` blocks were parsed; the rest were ignored.
66    ///
67    /// Real displays have at most a handful of extension blocks. A very large
68    /// `extension_count` most likely indicates a malformed or hostile EDID.
69    #[error(
70        "extension block count {declared} exceeds limit {limit}; only {limit} blocks were parsed"
71    )]
72    ExtensionBlockLimitReached {
73        /// The value of `extension_count` as declared in the base block.
74        declared: usize,
75        /// The maximum number of extension blocks the parser will process.
76        limit: usize,
77    },
78    /// The byte slice length does not match the size implied by the extension count.
79    ///
80    /// The EDID header declares `extension_count` extension blocks, so the expected
81    /// size is `(1 + extension_count) × 128` bytes. Extra bytes are ignored but may
82    /// indicate a driver bug, a KVM device, or a hotplug race.
83    #[error("EDID byte length {actual} does not match expected {expected}")]
84    SizeMismatch {
85        /// The size implied by the extension count: `(1 + extension_count) × 128`.
86        expected: usize,
87        /// The actual length of the byte slice passed to [`crate::parse_edid`].
88        actual: usize,
89    },
90}
91
92/// A fatal error that prevents useful parsing of an EDID byte stream.
93#[derive(Debug, Clone, PartialEq, thiserror::Error)]
94pub enum EdidError {
95    /// The byte slice is shorter than a complete EDID block (128 bytes per block).
96    #[error("input is shorter than a complete EDID block")]
97    InvalidLength,
98    /// The fixed 8-byte EDID header was not found at offset 0.
99    #[error("EDID header not found at offset 0")]
100    InvalidHeader,
101    /// The checksum byte does not make the block sum to zero.
102    #[error("block checksum does not sum to zero")]
103    ChecksumMismatch,
104}