1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#[cfg(any(feature = "alloc", feature = "std"))]
pub use display_types::ParseWarning;
/// A non-fatal condition encountered while parsing or processing an EDID block.
///
/// Warnings are collected into [`ParsedEdid::warnings`][crate::ParsedEdid] (from the parser)
/// and into [`DisplayCapabilities::warnings`][crate::DisplayCapabilities] (from handlers).
/// In `alloc`/`std` builds each entry is a [`ParseWarning`]; use `downcast_ref` to recover the
/// concrete type. In bare `no_std` builds this enum is used directly.
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum EdidWarning {
/// An extension block with an unrecognised tag was encountered.
/// The inner value is the tag byte.
#[error("unknown extension block tag: {0:#04x}")]
UnknownExtension(u8),
/// A 18-byte descriptor could not be decoded.
#[error("descriptor could not be parsed")]
DescriptorParseFailed,
/// The manufacturer ID bytes do not encode a valid PNP ID.
///
/// Each of the three 5-bit fields must be in the range 1–26 (A–Z). Values of 0 or
/// 27–31 indicate a corrupted or unprogrammed EEPROM.
/// [`DisplayCapabilities::manufacturer`][crate::DisplayCapabilities::manufacturer]
/// is left as `None`.
#[error("manufacturer ID bytes do not encode a valid PNP ID")]
InvalidManufacturerId,
/// A data block inside an extension block declared a length that extends past the
/// end of the data block collection. Remaining data blocks in the collection are skipped.
#[error("data block length exceeds collection boundary")]
MalformedDataBlock,
/// A Detailed Timing Descriptor slot was skipped because the byte slice passed to the
/// decoder was shorter than the required 18 bytes.
///
/// This indicates a malformed extension block that claimed to contain a DTD but did
/// not supply enough data.
#[error("DTD slot skipped: slice is shorter than the required 18 bytes")]
DtdSlotTooShort,
/// A Detailed Timing Descriptor slot was skipped because the pixel clock value
/// would overflow during refresh rate calculation.
///
/// This indicates a malformed or corrupted EDID: valid pixel clocks are at most
/// a few hundred MHz (fits comfortably in a `u32` after scaling by 10 000).
#[error("DTD slot skipped: pixel clock overflow during refresh rate calculation")]
DtdPixelClockOverflow,
/// The number of extension blocks declared in the base block exceeds the parser's
/// safety limit. Only the first `limit` blocks were parsed; the rest were ignored.
///
/// Real displays have at most a handful of extension blocks. A very large
/// `extension_count` most likely indicates a malformed or hostile EDID.
#[error(
"extension block count {declared} exceeds limit {limit}; only {limit} blocks were parsed"
)]
ExtensionBlockLimitReached {
/// The value of `extension_count` as declared in the base block.
declared: usize,
/// The maximum number of extension blocks the parser will process.
limit: usize,
},
/// A DisplayID extension block carries an unrecognised version byte.
///
/// The inner value is the version byte found at offset 1 of the extension block.
/// The block is skipped; other extension blocks are processed normally.
#[error("unrecognised DisplayID version: {0:#04x}")]
DisplayIdVersionUnknown(u8),
/// The extension count declared in a DisplayID section header does not match the
/// number of `0x70`-tagged blocks actually present in the EDID stream.
///
/// The DisplayID section is processed with whichever fragments are available.
#[error(
"DisplayID extension count mismatch: header declares {declared} continuation block(s), \
found {found}"
)]
DisplayIdExtensionCountMismatch {
/// Extension count as declared in the DisplayID section header.
declared: u8,
/// Actual number of continuation blocks found (i.e. `total 0x70 blocks − 1`).
found: u8,
},
/// The DisplayID section checksum byte does not make the section sum to zero.
///
/// The checksum covers the three-byte section header plus all data block bytes; the byte
/// immediately following them must bring the running total to zero mod 256. Parsing
/// continues with whatever data is present in the fragment.
#[error("DisplayID section checksum is invalid")]
DisplayIdChecksumMismatch,
/// The `section_byte_count` field in the DisplayID section header is too large to fit
/// within the 128-byte extension block.
///
/// A valid DisplayID section holds at most 122 data bytes (bytes 4–125), with the
/// checksum at byte 126. A larger `section_byte_count` places the checksum outside
/// the block. The section is still parsed using the clamped available bytes.
#[error(
"DisplayID section_byte_count {0} places checksum outside the extension block (max 122)"
)]
DisplayIdSectionBytesOutOfRange(u8),
/// A DisplayID Transfer Characteristics block carries a reserved encoding byte.
///
/// Bits 7:6 of the first payload byte encode the sample bit depth; value `0b11` is
/// reserved. The inner value is the raw 2-bit field. The block is skipped.
#[error("DisplayID Transfer Characteristics block has reserved encoding byte: {0:#04x}")]
UnknownTransferEncoding(u8),
/// A DisplayID 2.x data block carries a revision byte the spec marks as reserved.
///
/// The block is parsed anyway with the fields defined for revision 0; consumers may
/// see incorrect values if the revision actually changed the wire format.
#[error("DisplayID 2.x block tag {tag:#04x} has unsupported revision {revision:#04x}")]
UnsupportedV2BlockRevision {
/// Block tag byte (first byte of the 3-byte data block header).
tag: u8,
/// Revision byte (second byte of the 3-byte data block header) as found.
revision: u8,
},
/// The byte slice length does not match the size implied by the extension count.
///
/// The EDID header declares `extension_count` extension blocks, so the expected
/// size is `(1 + extension_count) × 128` bytes. Extra bytes are ignored but may
/// indicate a driver bug, a KVM device, or a hotplug race.
#[error("EDID byte length {actual} does not match expected {expected}")]
SizeMismatch {
/// The size implied by the extension count: `(1 + extension_count) × 128`.
expected: usize,
/// The actual length of the byte slice passed to [`crate::parse_edid`].
actual: usize,
},
}
/// A fatal error that prevents useful parsing of an EDID byte stream.
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum EdidError {
/// The byte slice is shorter than a complete EDID block (128 bytes per block).
#[error("input is shorter than a complete EDID block")]
InvalidLength,
/// The fixed 8-byte EDID header was not found at offset 0.
#[error("EDID header not found at offset 0")]
InvalidHeader,
/// The checksum byte does not make the block sum to zero.
#[error("block checksum does not sum to zero")]
ChecksumMismatch,
}