Skip to main content

sim_lib_midi_smf/
error.rs

1#![forbid(unsafe_code)]
2
3use thiserror::Error;
4
5/// Errors raised while reading or writing a Standard MIDI File.
6#[derive(Debug, Error, Clone, PartialEq, Eq)]
7pub enum SmfError {
8    /// The `MThd`/`MTrk` chunk header was malformed.
9    #[error("invalid header at byte {offset}")]
10    InvalidHeader {
11        /// Byte offset of the bad header.
12        offset: usize,
13    },
14    /// The byte stream ended before the structure was complete.
15    #[error("unexpected end of file at byte {offset}")]
16    UnexpectedEof {
17        /// Byte offset where more input was expected.
18        offset: usize,
19    },
20    /// A variable-length quantity was not terminated within four bytes.
21    #[error("invalid VLQ at byte {offset}")]
22    InvalidVlq {
23        /// Byte offset where the VLQ began.
24        offset: usize,
25    },
26    /// The header carried an invalid metrical or SMPTE division.
27    #[error("invalid SMF division 0x{raw:04x} at byte {offset}")]
28    InvalidDivision {
29        /// Byte offset of the division field.
30        offset: usize,
31        /// The raw division value.
32        raw: u16,
33    },
34    /// A configured defensive read limit was exceeded.
35    #[error("SMF {kind} limit exceeded at byte {offset}: requested {actual}, maximum {maximum}")]
36    LimitExceeded {
37        /// Byte offset of the value or event that crossed the limit.
38        offset: usize,
39        /// Resource being bounded.
40        kind: SmfLimitKind,
41        /// Requested or observed amount.
42        actual: usize,
43        /// Configured maximum.
44        maximum: usize,
45    },
46    /// A bounded allocation failed even though its requested size was within
47    /// the configured limits.
48    #[error("SMF allocation of {requested} items failed at byte {offset}")]
49    AllocationFailed {
50        /// Byte offset of the structure that required the allocation.
51        offset: usize,
52        /// Number of items requested.
53        requested: usize,
54    },
55    /// A data byte appeared with no running status in effect.
56    #[error("malformed running status at byte {offset}")]
57    MalformedRunningStatus {
58        /// Byte offset of the offending data byte.
59        offset: usize,
60    },
61    /// A status byte that the reader does not handle was encountered.
62    #[error("unsupported MIDI status 0x{status:02x} at byte {offset}")]
63    UnsupportedStatus {
64        /// Byte offset of the status byte.
65        offset: usize,
66        /// The unsupported status byte.
67        status: u8,
68    },
69    /// A channel message carried an out-of-range data byte.
70    #[error("invalid channel payload at byte {offset}")]
71    InvalidChannelData {
72        /// Byte offset of the bad data.
73        offset: usize,
74    },
75    /// A recognised meta event used a payload length forbidden by SMF.
76    #[error(
77        "invalid length {actual} for meta event 0x{type_byte:02x} at byte {offset}; expected {expected}"
78    )]
79    InvalidMetaLength {
80        /// Byte offset of the meta type byte.
81        offset: usize,
82        /// Meta event type.
83        type_byte: u8,
84        /// Required payload length.
85        expected: usize,
86        /// Encoded payload length.
87        actual: usize,
88    },
89    /// A track chunk ended without its required end-of-track meta event.
90    #[error("missing end-of-track event at byte {offset}")]
91    MissingEndOfTrack {
92        /// Byte offset immediately after the track chunk body.
93        offset: usize,
94    },
95    /// A system message used an invalid status, length, or data byte.
96    #[error("invalid system event 0x{status:02x} at byte {offset}")]
97    InvalidSystemEvent {
98        /// Byte offset of the status or first invalid data byte.
99        offset: usize,
100        /// System status byte.
101        status: u8,
102    },
103    /// The header format and the track count are inconsistent (for example,
104    /// format 0 with more than one track).
105    #[error("SMF format/track count mismatch")]
106    FormatTrackMismatch,
107    /// Format 2 contains independent patterns and cannot be flattened onto a
108    /// shared timeline without selecting a track.
109    #[error("SMF format 2 patterns require explicit track selection")]
110    IndependentPatternsCannotMerge,
111    /// The track count cannot be represented in the SMF header.
112    #[error("SMF track count {0} is outside 0..=65535")]
113    TrackCountOutOfRange(usize),
114    /// The ticks-per-quarter value cannot be represented as metrical SMF TPQ.
115    #[error("SMF ticks-per-quarter {0} cannot be written as metrical TPQ")]
116    TpqOutOfRange(u32),
117    /// An event time could not be represented exactly at the file resolution.
118    #[error("event time cannot be represented exactly at target TPQ")]
119    InexactEventTime,
120    /// Track events were not monotonic in absolute time, yielding a negative
121    /// delta.
122    #[error("track events are not monotonic in absolute time")]
123    NegativeDelta,
124    /// A track delta cannot be represented as an SMF four-byte VLQ.
125    #[error("SMF delta {0} exceeds the four-byte VLQ limit")]
126    DeltaOutOfRange(i64),
127    /// A track chunk body cannot be represented in the SMF chunk length field.
128    #[error("SMF chunk length {0} exceeds u32::MAX")]
129    ChunkTooLarge(usize),
130    /// A meta or SysEx payload length cannot be represented as an SMF four-byte
131    /// VLQ.
132    #[error("SMF payload length {0} exceeds the four-byte VLQ limit")]
133    PayloadTooLarge(usize),
134    /// Absolute tick accumulation overflowed the in-memory event time.
135    #[error("SMF absolute tick time overflow at byte {offset}")]
136    TimeOverflow {
137        /// Byte offset of the delta that overflowed.
138        offset: usize,
139    },
140}
141
142/// Defensive parser resources that can be bounded with [`SmfReadLimits`].
143///
144/// [`SmfReadLimits`]: crate::SmfReadLimits
145#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
146pub enum SmfLimitKind {
147    /// Complete input bytes.
148    FileBytes,
149    /// Header chunk body bytes.
150    HeaderBytes,
151    /// Declared track count.
152    TrackCount,
153    /// One track chunk body.
154    TrackBytes,
155    /// Events across the complete file.
156    EventCount,
157    /// One meta or system-exclusive payload.
158    EventPayloadBytes,
159    /// Meta and system-exclusive payload bytes across the complete file.
160    TotalPayloadBytes,
161}
162
163impl std::fmt::Display for SmfLimitKind {
164    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165        formatter.write_str(match self {
166            Self::FileBytes => "file-bytes",
167            Self::HeaderBytes => "header-bytes",
168            Self::TrackCount => "track-count",
169            Self::TrackBytes => "track-bytes",
170            Self::EventCount => "event-count",
171            Self::EventPayloadBytes => "event-payload-bytes",
172            Self::TotalPayloadBytes => "total-payload-bytes",
173        })
174    }
175}