Skip to main content

moqtap_codec/
error.rs

1/// Maximum control message payload length: 2^16 - 1 bytes.
2pub const MAX_MESSAGE_LENGTH: usize = 65535;
3/// Maximum reason phrase length: 1024 bytes.
4pub const MAX_REASON_PHRASE_LENGTH: usize = 1024;
5/// Maximum GOAWAY new session URI length: 8192 bytes.
6pub const MAX_GOAWAY_URI_LENGTH: usize = 8192;
7/// Maximum full track name length: 4096 bytes.
8pub const MAX_FULL_TRACK_NAME_LENGTH: usize = 4096;
9/// Maximum track namespace tuple size: 32 elements.
10pub const MAX_NAMESPACE_TUPLE_SIZE: usize = 32;
11
12/// Errors produced during MoQT message encoding and decoding.
13///
14/// # Adding a variant
15///
16/// This enum is deliberately **not** `#[non_exhaustive]`, so that a session-close
17/// table matching it exhaustively fails to compile until a new variant has been
18/// placed on each of the thirteen drafts — either among the rules that draft
19/// answers with a close or among the ones it names and does not. A wildcard arm
20/// would make those thirteen decisions silently, all in the direction of "no
21/// rule", and a missing arm and a deliberate exclusion look identical from
22/// inside such a table.
23#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone)]
24pub enum CodecError {
25    /// Unknown or unsupported message type identifier.
26    #[error("unknown message type: 0x{0:x}")]
27    UnknownMessageType(u64),
28    /// Not enough bytes in the buffer to complete decoding.
29    #[error("insufficient bytes")]
30    UnexpectedEnd,
31    /// Control message payload exceeds [`MAX_MESSAGE_LENGTH`].
32    #[error("message too long: {0} bytes (max {MAX_MESSAGE_LENGTH})")]
33    MessageTooLong(usize),
34    /// Variable-length integer encoding/decoding error.
35    #[error("varint error: {0}")]
36    VarInt(#[from] crate::varint::VarIntError),
37    /// Key-value pair encoding/decoding error.
38    #[error("kvp error: {0}")]
39    Kvp(#[from] crate::kvp::KvpError),
40    /// A decoded field value is not valid for its type.
41    #[error("invalid field value")]
42    InvalidField,
43    /// Namespace tuple element count is outside the range the caller allows.
44    ///
45    /// The upper bound is [`MAX_NAMESPACE_TUPLE_SIZE`] everywhere. The lower
46    /// bound is per-draft, and three wordings carry two rules. Drafts 07
47    /// through 14 define a Track Namespace as "an ordered N-tuple of bytes
48    /// where N can be between 1 and 32"; drafts 15 and 16 rename the elements
49    /// to Track Namespace Fields and keep the 1; drafts 17 and later lower it
50    /// to 0. So an empty tuple is refused only through draft-16.
51    #[error("namespace tuple size {0} is not allowed here (max {MAX_NAMESPACE_TUPLE_SIZE})")]
52    InvalidNamespaceTupleSize(usize),
53    /// A Track Namespace Field was declared with a length of zero.
54    ///
55    /// Drafts 16 through 19 all carry the same sentence in Section 2.4.1: "Each
56    /// Track Namespace Field Value MUST contain at least one byte. If an
57    /// endpoint receives a Track Namespace Field with a Track Namespace Field
58    /// Length of 0, it MUST close the session with a PROTOCOL_VIOLATION."
59    #[error("track namespace field is empty; each field must contain at least one byte")]
60    EmptyNamespaceField,
61    /// Full track name exceeds [`MAX_FULL_TRACK_NAME_LENGTH`].
62    #[error("track namespace or full track name exceeds {MAX_FULL_TRACK_NAME_LENGTH} bytes")]
63    TrackNameTooLong,
64    /// A requested range ends before it starts.
65    ///
66    /// Every draft states it, in the wording its fields have at the time.
67    /// Draft-07 Section 6.4 and Section 6.7: "EndGroup and EndObject MUST
68    /// specify the same or a later object than StartGroup and StartObject".
69    /// Draft-08 Section 7.4 through draft-14 Section 9.7, for the AbsoluteRange
70    /// filter: "End Group MUST specify the same or a larger Group than
71    /// specified in Start". Draft-12 Section 8.16 onwards, for FETCH: "End
72    /// Location MUST specify the same or a larger Location than Start
73    /// Location", which drafts 14 and later qualify with "for Standalone and
74    /// Absolute Joining Fetches".
75    ///
76    /// The two ends are reported as they appear on the wire, before the
77    /// draft's plus-one adjustments are undone, so the numbers here are the
78    /// ones a peer would read out of the frame.
79    #[error("range from group {0} object {1} ends at group {2} object {3}, which is earlier")]
80    InvalidRange(u64, u64, u64, u64),
81    /// A parameter's value is not the shape the parameter's own type implies.
82    ///
83    /// Drafts 07 through 10 state it once each, in the section that gives the
84    /// Parameter format: "If a receiver understands a parameter type, and the
85    /// parameter length implied by that type does not match the Parameter Length
86    /// field, the receiver MUST terminate the session with error code 'Parameter
87    /// Length Mismatch'." Each of those drafts assigns that code its own number
88    /// in the session termination registry. Drafts 11 and later drop the
89    /// sentence along with the Parameter framing it describes.
90    ///
91    /// The parameter reported is the one whose value disagreed with its type.
92    /// Which namespace the type is read in matters: a setup 0x02 is a
93    /// MAX_SUBSCRIBE_ID integer and a version-specific 0x02 is an
94    /// AUTHORIZATION INFO string.
95    #[error("parameter type {0} carries a value of the wrong length for its type")]
96    ParameterLengthMismatch(u64),
97    /// A key-value pair's value is not the serialization its own Type defines.
98    ///
99    /// Drafts 11 through 19 state it once each, in the section that gives the
100    /// Key-Value-Pair format. Drafts 14 and 15 word it: "If a receiver
101    /// understands a Type, and the following Value or Length/Value does not
102    /// match the serialization defined by that Type, the receiver MUST
103    /// terminate the session with error code KEY_VALUE_FORMATTING_ERROR".
104    /// Drafts 16 through 19 say close where those two say terminate, and
105    /// drafts 11 through 13 spell the code 'Key-Value Formatting Error'. Every
106    /// one of the nine assigns it a number in the session termination
107    /// registry.
108    ///
109    /// This is the successor to [`CodecError::ParameterLengthMismatch`], which
110    /// drafts 07 through 10 state about the Parameter framing they had instead.
111    /// The two never overlap: no draft states both, and the earlier rule is
112    /// about a declared length disagreeing with a type while this one is about
113    /// the bytes themselves.
114    ///
115    /// The rule is conditional on understanding the Type, so it reaches only the
116    /// types a draft defines a serialization for. A parameter this codec cannot
117    /// name carries bytes no rule here describes, and refusing it would close
118    /// sessions over extensions the drafts leave room for.
119    ///
120    /// `key` is the parameter type as the frame spelled it, in the namespace it
121    /// was read in — AUTHORIZATION TOKEN is 0x01 on draft-11 and 0x03 on drafts
122    /// 12 through 19, and a setup 0x01 is a PATH on draft-11 rather than a token
123    /// at all. `detail` says which way the value failed to match, because the
124    /// serialization is a structure rather than a length and "malformed" alone
125    /// leaves the reader to re-derive it.
126    #[error(
127        "key-value pair of type {key} does not match the serialization that type defines: {detail}"
128    )]
129    KeyValueFormatting {
130        /// The parameter type whose value did not match, as the frame spelled
131        /// it.
132        key: u64,
133        /// How the value failed to match the serialization.
134        detail: &'static str,
135    },
136    /// A control message carried a Message Parameter whose type its draft does
137    /// not define.
138    ///
139    /// Drafts 16, 17, 18 and 19 state it in the paragraph that introduces
140    /// parameters: "All Message Parameters MUST be defined in the negotiated
141    /// version of MOQT or negotiated via Setup Options. An endpoint that
142    /// receives an unknown Message Parameter MUST close the session with
143    /// PROTOCOL_VIOLATION." Drafts 17 and later add the reasoning — "Because the
144    /// receiver has to understand every Message Parameter, there is no need for
145    /// a mechanism to skip unknown parameters" — and draft-19 draws the
146    /// consequence for the framing: "Because unknown parameters cannot be
147    /// skipped, the block is bounded by a parameter count rather than a length."
148    /// Drafts 07 through 15 are the other way round and this must not reach
149    /// them. They say "Receivers MUST allow duplicates of unknown parameters",
150    /// which presumes unknown parameters arrive and are carried; refusing one
151    /// there would close a session over an extension those drafts leave room
152    /// for. Draft-16 is where the sentence narrows to *unknown **Setup**
153    /// Parameters*, in the same paragraph that adds the close — one edit, both
154    /// halves.
155    ///
156    /// Setup Parameters keep the older behaviour on every draft, which is why
157    /// this is about one namespace and not both. Drafts 16 through 19 all say a
158    /// receiver ignores an unrecognised Setup Option or Setup Parameter, so a
159    /// type unknown in that namespace is carried and only a type unknown in the
160    /// message namespace ends the session.
161    #[error("message parameter type {0} is not one this draft defines")]
162    UnknownMessageParameter(u64),
163    /// A Message Parameter appeared in a message type its own definition does
164    /// not name.
165    ///
166    /// Drafts 17, 18 and 19 only, and the split is the whole reason this is a
167    /// variant rather than a shared rule. All thirteen drafts state the first
168    /// half the same way and ten of them state the opposite consequence.
169    /// Draft-19 Section 10.2.1, draft-18 Section 10.2.1 and draft-17 Section
170    /// 9.3.1: "Each Message Parameter definition indicates the message types in
171    /// which it can appear. If it appears in some other type of message, the
172    /// receiving endpoint MUST close the connection with a PROTOCOL_VIOLATION."
173    /// Draft-16 Section 9.2.2 and, under the older name Version Specific
174    /// Parameters, drafts 07 through 15, end the same sentence "it MUST be
175    /// ignored". Raising this on any of those ten would close a session over
176    /// traffic they oblige an endpoint to tolerate, so the decoder never does.
177    ///
178    /// What each draft's own text settles, and what it leaves open:
179    ///
180    /// * A parameter's scope is the set of message types named as the
181    ///   destination of its "MAY appear in" sentence, with trailing and
182    ///   parenthesised qualifiers read as describing one of those destinations
183    ///   rather than adding another. Draft-17's LARGEST_OBJECT "MAY appear in
184    ///   SUBSCRIBE_OK, PUBLISH or in REQUEST_OK (in response to REQUEST_UPDATE
185    ///   or TRACK_STATUS)" scopes three message types, not five, which is how
186    ///   drafts 18 and 19 write the same rule once the responses have names of
187    ///   their own.
188    /// * Half the response names are one wire type. Draft-19 Section 10.5:
189    ///   "This document uses the shorthand PUBLISH_OK, REQUEST_UPDATE_OK,
190    ///   TRACK_STATUS_OK, SUBSCRIBE_NAMESPACE_OK, and PUBLISH_NAMESPACE_OK to
191    ///   refer to a REQUEST_OK sent in response to the corresponding request
192    ///   type." Which one a given REQUEST_OK is depends on the request its
193    ///   Request ID answers, which is session state and not in the frame, so
194    ///   every name that resolves to REQUEST_OK widens the same set and a
195    ///   REQUEST_OK is held only to their union.
196    ///
197    /// Both readings err toward carrying the parameter, which is the direction
198    /// a rule that ends sessions should be wrong in.
199    ///
200    /// `key` is the absolute parameter type after any delta encoding is
201    /// resolved, and `message_type` is the type id off the wire, so a log names
202    /// the pair the sender actually wrote.
203    #[error("message parameter type {key} may not appear in message type {message_type}")]
204    ParameterOutOfScope {
205        /// The parameter type that was out of scope, absolute.
206        key: u64,
207        /// The message type it arrived in, as its id on the wire.
208        message_type: u64,
209    },
210    /// A Message Parameter carried a value outside the range its type allows.
211    ///
212    /// Drafts 15 through 19 give several parameters a range and answer anything
213    /// outside it with a close. GROUP_ORDER: "The allowed values are Ascending
214    /// (0x1) or Descending (0x2). If an endpoint receives a value outside this
215    /// range, it MUST close the session with PROTOCOL_VIOLATION." FORWARD says
216    /// the same of 0 and 1. Drafts 15 and 16 add SUBSCRIBER_PRIORITY — "The
217    /// range is restricted to 0-255" — which drafts 17 and later do not need,
218    /// having made the parameter a uint8 so that nothing outside the range can
219    /// be spelled. Draft-15 alone carries DYNAMIC_GROUPS as a Message Parameter,
220    /// where "Values larger than 1 are a Protocol Violation"; draft-16 moved it
221    /// into the extension header namespace, which this codec carries as opaque
222    /// bytes.
223    ///
224    /// Not draft-15's PUBLISHER_PRIORITY. It says "Priorities above 255 are
225    /// invalid" and stops there, naming no consequence, where every parameter
226    /// above states one in the next clause. The contrast is within one section,
227    /// so the omission is the draft's and not an oversight to be read past.
228    ///
229    /// Nor the Group Order and Forward *fields* of drafts 07 through 14, which
230    /// are a different serialization and are answered separately — Forward by
231    /// [`CodecError::InvalidForward`], Group Order by nothing, for the reason
232    /// given there.
233    ///
234    /// `key` is the absolute parameter type, after any delta encoding is
235    /// resolved, so a log names the type the sender meant rather than the
236    /// increment it wrote.
237    #[error(
238        "message parameter type {key} carries value {value}, which is outside the range it allows"
239    )]
240    ParameterValueOutOfRange {
241        /// The parameter type whose value was out of range, absolute.
242        key: u64,
243        /// The value as it arrived.
244        value: u64,
245    },
246    /// A Track Extension or Track Property carried a value outside the range its
247    /// type allows.
248    ///
249    /// A separate namespace from [`CodecError::ParameterValueOutOfRange`], and
250    /// separate for a reason rather than for tidiness: the two registries assign
251    /// the same numbers to different things. Type 0x22 is the GROUP_ORDER
252    /// Message Parameter and it is also DEFAULT_PUBLISHER_GROUP_ORDER, which is
253    /// a property of the track rather than a preference expressed by one
254    /// subscriber. Type 0x30 is DYNAMIC_GROUPS in the second namespace and is
255    /// unassigned in the first from draft-16 on. A single variant covering both
256    /// would name a type without saying which table to read it in.
257    ///
258    /// Draft-16 states the rules of the extension header namespace and drafts
259    /// 17, 18 and 19 restate them of the Track Property namespace that replaced
260    /// it. Two types restrict their range and answer anything outside it with a
261    /// close. DEFAULT_PUBLISHER_GROUP_ORDER: "The allowed values are Ascending
262    /// (0x1) or Descending (0x2). If an endpoint receives a value outside this
263    /// range, it MUST close the session with PROTOCOL_VIOLATION." DYNAMIC_GROUPS:
264    /// "The allowed values are 0 or 1... If an endpoint receives a value larger
265    /// than 1, it MUST close the session with PROTOCOL_VIOLATION."
266    ///
267    /// Draft-16 adds a third, and only draft-16: "DELIVERY_TIMEOUT, if present,
268    /// MUST contain a value greater than 0. If an endpoint receives a
269    /// DELIVERY_TIMEOUT equal to 0 it MUST close the session with
270    /// PROTOCOL_VIOLATION." Draft-17 renamed the type to
271    /// OBJECT_DELIVERY_TIMEOUT and states no range for it, so the rule ends
272    /// where the name does.
273    ///
274    /// Not DEFAULT_PUBLISHER_PRIORITY, on any of the four. "Priorities above 255
275    /// are invalid" names no consequence, in a section where its neighbours
276    /// name one in the next clause. The same contrast excludes draft-15's
277    /// PUBLISHER_PRIORITY from [`CodecError::ParameterValueOutOfRange`], and it
278    /// is the draft's distinction both times.
279    ///
280    /// `key` is the absolute type, after any delta encoding is resolved.
281    #[error(
282        "track property type {key} carries value {value}, which is outside the range it allows"
283    )]
284    TrackPropertyValueOutOfRange {
285        /// The extension or property type whose value was out of range,
286        /// absolute.
287        key: u64,
288        /// The value as it arrived.
289        value: u64,
290    },
291    /// A Forward field carried a value other than zero or one.
292    ///
293    /// Drafts 11 through 14 carry the field, and state the rule in two wordings.
294    /// SUBSCRIBE and SUBSCRIBE_UPDATE, on all four: "Forward: If 1, Objects
295    /// matching the subscription are forwarded to the subscriber. If 0, Objects
296    /// are not forwarded to the subscriber. Any other value is a protocol error
297    /// and MUST terminate the session with a Protocol Violation". PUBLISH, added
298    /// in draft-12: "Any value other than 0 or 1 is a Protocol Violation."
299    /// Draft-14 spells both codes PROTOCOL_VIOLATION and is otherwise unchanged.
300    ///
301    /// PUBLISH_OK, also from draft-12, is the one site that names no
302    /// consequence: "Forward: The Forward State for this subscription, either 0
303    /// (don't forward) or 1 (forward)." It is reported here all the same. The
304    /// field carries the same two values in every message that has one, three of
305    /// the four sites state the close outright, and the sentence enumerates 0
306    /// and 1 without giving a third value any meaning — so this is the same rule
307    /// stated shorter, not a permission. Reading an omission the other way is
308    /// what put a wrong claim about Group Order into this codec: drafts 12
309    /// through 14 do state the 0x0 rule for SUBSCRIBE_OK, PUBLISH and FETCH_OK,
310    /// and a doc comment here asserted for a while that they did not.
311    ///
312    /// Drafts 07 through 10 have no such field. Drafts 15 and later carry
313    /// forwarding as the FORWARD parameter instead, under the same rule but a
314    /// different serialization; that form is
315    /// [`CodecError::ParameterValueOutOfRange`].
316    #[error("forward field carries {0}, which is neither zero nor one")]
317    InvalidForward(u8),
318    /// A subscription filter names a Filter Type no draft in its range assigns.
319    ///
320    /// All thirteen drafts state the rule and they do not state the same
321    /// consequence. Drafts 07 through 13: "A filter type other than the above
322    /// MUST be treated as error", which names no code and no close. Draft-14:
323    /// "An endpoint that receives a filter type other than the above MUST be
324    /// close the session with PROTOCOL_VIOLATION", the typo being the draft's.
325    /// Drafts 15 through 19 say the same without the typo. So the same value in
326    /// the same place is a refused message on the first seven drafts and a
327    /// session close on the last six, and only the per-draft session table can
328    /// tell them apart.
329    ///
330    /// The assigned set is not constant either. Drafts 07 and 08 assign 0x1 as
331    /// Latest Group, drafts 09 and 10 withdraw it and list three types, and
332    /// drafts 11 and later reinstate 0x1 as Next Group Start — a different
333    /// meaning at the same number. A decoder that accepts the union would read a
334    /// draft-09 SUBSCRIBE the draft requires it to reject.
335    ///
336    /// The serialization moves as well. Drafts 07 through 14 carry the Filter
337    /// Type as a field of SUBSCRIBE and its relatives; drafts 15 and later carry
338    /// it as the first field inside the length-prefixed filter parameter, where
339    /// nothing had been reading it at all.
340    #[error("filter type {0} is not one this draft assigns")]
341    InvalidFilterType(u64),
342    /// A FETCH names a Fetch Type no draft in its range assigns.
343    ///
344    /// The sentence next to the Filter Type one, and it moves the same way.
345    /// Drafts 08 through 13: "A Fetch Type other than 0x1, 0x2 or 0x3 MUST be
346    /// treated as an error", naming no code and no close — 0x3 being absent from
347    /// the sentence on drafts 08, 09 and 10, which assign only two types.
348    /// Draft-14: "An endpoint that receives a Fetch Type other than 0x1, 0x2 or
349    /// 0x3 MUST be close the session with a PROTOCOL_VIOLATION", carrying the
350    /// same missing word as its Filter Type sentence. Drafts 15 through 19 say it
351    /// without the typo. Draft-07 has no FETCH at all.
352    ///
353    /// Like the Filter Type, the value decides which fields follow it: a
354    /// Standalone fetch carries a Track Namespace, a Track Name and a range, and
355    /// a joining fetch carries a Request ID and an offset. A reader that cannot
356    /// name the type cannot find the end of the message, which is why the later
357    /// drafts answer it with a close rather than by ignoring the field.
358    #[error("fetch type {0} is not one this draft assigns")]
359    InvalidFetchType(u64),
360    /// A subscription filter parameter's value is not a filter.
361    ///
362    /// Drafts 15 and 16 state it of this parameter directly: "If the length of
363    /// the Subscription Filter does not match the parameter length, the publisher
364    /// MUST close the session with PROTOCOL_VIOLATION." Drafts 17 through 19
365    /// drop that sentence and leave the general one, which every draft from 15
366    /// on also carries: "If a receiver understands a Type, and the following
367    /// Value or Length/Value does not match the serialization defined by that
368    /// Type, the receiver MUST close the session with error code
369    /// KEY_VALUE_FORMATTING_ERROR."
370    ///
371    /// Two sentences, two codes, one malformation — which is why this is a
372    /// variant of its own rather than reported as
373    /// [`CodecError::KeyValueFormatting`]. A filter three bytes long inside a
374    /// four-byte parameter ends a draft-16 session with PROTOCOL_VIOLATION and a
375    /// draft-17 session with KEY_VALUE_FORMATTING_ERROR, and the session tables
376    /// are where that difference belongs.
377    ///
378    /// A Filter Type outside the assigned set is not this: the filter's own
379    /// section names PROTOCOL_VIOLATION for that on all five drafts, and it is
380    /// [`CodecError::InvalidFilterType`].
381    ///
382    /// `detail` says which way the value failed, because "malformed" alone
383    /// leaves the reader to work out whether the filter ran short or the
384    /// parameter ran long.
385    #[error("subscription filter parameter is not a filter: {detail}")]
386    SubscriptionFilterMalformed {
387        /// How the value failed to be a filter.
388        detail: &'static str,
389    },
390    /// An AbsoluteRange filter's End Group Delta carries the range past the end
391    /// of the number space.
392    ///
393    /// Drafts 17 and later replaced the absolute End Group with a delta measured
394    /// from the Start Location's Group, which makes the last group in range a
395    /// sum rather than a field. Drafts 18 and 19 answer the sum leaving the
396    /// range: "Otherwise, the last Group ID to be delivered will be the Group ID
397    /// in Start Location plus the End Group Delta. If the resulting Group ID
398    /// would be greater than 2^64 - 1, the endpoint MUST close the session with
399    /// a PROTOCOL_VIOLATION."
400    ///
401    /// Draft-17 introduced the delta and states no such sentence, so it is
402    /// absent from that draft's session table. Drafts 15 and 16 write the End
403    /// Group out in full and can express nothing to overflow.
404    #[error(
405        "filter start group {start_group} plus end group delta {delta} leaves the 64-bit range"
406    )]
407    FilterEndGroupOverflow {
408        /// The Group ID of the filter's Start Location.
409        start_group: u64,
410        /// The End Group Delta as the filter carried it.
411        delta: u64,
412    },
413    /// An end-of-track object states an Object ID other than zero.
414    ///
415    /// Drafts 08, 09 and 10 describe Object Status 0x5 as "end of Track. GroupID
416    /// is one greater than the largest group produced in this track and the
417    /// ObjectId is zero", and continue: "An object with this status that has a
418    /// Group ID less than or equal to any other Group ID, or an Object ID other
419    /// than zero, is a protocol error, and the receiver MUST terminate the
420    /// session." The Group ID half needs the largest group seen on the track and
421    /// is not settleable from one header; the Object ID half is, and is what this
422    /// reports. Draft-07 assigns no 0x5 and drafts 11 and later drop the
423    /// sentence.
424    #[error("end-of-track object states object id {0}; an end of track ends at object zero")]
425    EndOfTrackObjectId(u64),
426    /// A delta-encoded key would exceed 2^64 - 1 once the delta is added to the
427    /// previous key.
428    ///
429    /// Drafts 16, 17, 18 and 19 all say, in Section 1.4.3: "The previous Type
430    /// value plus the Delta Type MUST NOT be greater than 2^64 - 1. If a Delta
431    /// Type is received that would be too large, the Session MUST be closed
432    /// with a PROTOCOL_VIOLATION." Delta encoding arrives with draft-16;
433    /// drafts 15 and earlier write absolute types and cannot reach this.
434    #[error("delta-encoded key {0} + {1} exceeds 2^64 - 1")]
435    KeyDeltaOverflow(u64, u64),
436    /// A caller asked to encode a parameter list that is not in ascending order
437    /// by type.
438    ///
439    /// Drafts 17, 18 and 19 require it in as many words: "Parameters MUST be
440    /// serialized in ascending order by Type." Draft-16 delta-encodes types
441    /// without stating that sentence, but the constraint is the same there and
442    /// is structural rather than stated: the delta is an unsigned difference
443    /// from the previous type, so a descending pair has no representation at
444    /// all. Encoding one anyway wraps the subtraction and emits a nine-byte
445    /// delta the peer resolves to an unrelated key, which is why draft-16
446    /// reports this too.
447    #[error("parameter type {1} follows {0}; parameters must be in ascending order by type")]
448    ParametersOutOfOrder(u64, u64),
449    /// The same parameter type appears twice in one message, and its definition
450    /// does not allow that.
451    ///
452    /// Every draft from 07 to 19 states the sender's half: "Senders MUST NOT
453    /// repeat the same Parameter Type in a message" — drafts 11 and later
454    /// adding "unless the parameter definition explicitly allows multiple
455    /// instances of that type to be sent in a single message." The receiver's
456    /// half is a SHOULD, and from draft-11 it carries a limit that makes the
457    /// rule asymmetric: "Receivers MUST allow duplicates of unknown
458    /// parameters." A receiver may therefore refuse a repeat only of a type its
459    /// own draft names, while a sender may repeat nothing it is not granted.
460    ///
461    /// One type is granted repeats, from draft-11 onward: AUTHORIZATION TOKEN,
462    /// numbered 0x01 on draft-11 and 0x03 on drafts 12 through 19. It is not
463    /// reported here. Drafts 07 through 10 state neither the exemption clause
464    /// nor the unknown-duplicates sentence, so on those four the rule is
465    /// symmetric and every repeat is refused in both directions.
466    ///
467    /// Setup Parameters and Version Specific Parameters are separate
468    /// namespaces that assign different meanings to the same number, so the
469    /// exemption is per namespace: on draft-11, 0x01 is the repeatable
470    /// AUTHORIZATION TOKEN in a SUBSCRIBE and the non-repeatable PATH in a
471    /// CLIENT_SETUP.
472    #[error("parameter type {0} appears more than once")]
473    DuplicateParameter(u64),
474    /// A delta-encoded Object ID would exceed 2^64 - 1 once the delta is added
475    /// to the previous Object ID on the same stream.
476    ///
477    /// Draft-18 Section 11.4.2 and draft-19 Section 11.4.2: "The Object ID
478    /// Delta + 1 is added to the previous Object ID in the Subgroup stream if
479    /// there was one... If the resulting Object ID would be greater than
480    /// 2^64 - 1, the endpoint MUST close the session with a
481    /// PROTOCOL_VIOLATION." Draft-17
482    /// Section 10.4.2 describes the same arithmetic and states no consequence,
483    /// so on that draft this is a decode failure and nothing more.
484    ///
485    /// Distinct from [`CodecError::InvalidField`], which the object reader
486    /// previously answered with: a caller could not tell the wrap from a dozen
487    /// unrelated malformations, and so could not act on the rule.
488    #[error("object id {0} + {1} + 1 exceeds 2^64 - 1")]
489    ObjectIdOverflow(u64, u64),
490    /// An Object with Object Status 'Object Does Not Exist' carries extension
491    /// headers.
492    ///
493    /// Drafts 11 through 14 state it once each — draft-11 Section 9.1.1.2,
494    /// drafts 12 and 13 Section 9.2.1.2, draft-14 Section 10.2.1.2 — and the
495    /// first three word it: "Any Object may have extension headers except those
496    /// with Object Status 'Object Does Not Exist'. If an endpoint receives a
497    /// non-existent Object containing extension headers it MUST close the
498    /// session with a Protocol Violation." Draft-14 states the same sentence
499    /// with the code spelled PROTOCOL_VIOLATION.
500    ///
501    /// The rule names one status and no others, so extensions beside End of
502    /// Group or End of Track are legal on those four drafts and are not
503    /// reported here. Drafts 15 and later replaced this narrow form with the
504    /// general one — extensions, later properties, are permitted only beside
505    /// Normal — which is a different rule with a different subject and is
506    /// reported by the carriers' own `extensions_permitted` predicates.
507    ///
508    /// Drafts 07 through 10 state neither form.
509    ///
510    /// All three carriers that announce a status reach this, in both
511    /// directions: an object on a subgroup stream, an object on a fetch stream,
512    /// and a status datagram. A plain datagram has no status field and is the
513    /// one carrier that cannot break the rule.
514    ///
515    /// Distinct from [`CodecError::InvalidField`], which these four drafts
516    /// previously answered with: a caller could not tell this rule from a dozen
517    /// unrelated malformations, so a session could not be closed over it
518    /// without closing sessions the drafts do not ask to be closed.
519    #[error("object with status 'object does not exist' carries {0} bytes of extension headers")]
520    ExtensionsOnNonExistentObject(usize),
521    /// An object arrived carrying a payload the draft gives it no room for.
522    ///
523    /// All thirteen drafts state the rule, in two phrasings. Drafts 07 through
524    /// 18 say it of the status code — draft-07 Section 7.1.1.1, drafts 08 and
525    /// 09 Section 8.1.1.1, drafts 10 and 11 Section 9.1.1.1, drafts 12 and 13
526    /// Section 9.2.1.1, drafts 14 through 17 Section 10.2.1.1, draft-18 Section
527    /// 11.2.1.1: "Any object with a status code other than zero MUST have an
528    /// empty payload." Draft-19 Section 11.2.1.1 states it of a registry
529    /// instead: "An Object MUST have an empty payload unless its Object Status
530    /// value is registered as permitting a payload in the Object Status
531    /// registry (Section 15.9). Of the values defined in this document, only
532    /// Normal (0x0) permits a payload." The two agree on every status those
533    /// documents define and differ in what a later one may add.
534    ///
535    /// `detail` separates the two ways an object can break it, because they are
536    /// different mistakes and the second is the one a decoder can be fooled by:
537    ///
538    /// * The framing already said there would be no payload. A datagram whose
539    ///   Type sets the STATUS bit carries a status in the payload's place, so
540    ///   bytes after it are not a short payload or an odd one — they are bytes
541    ///   the frame does not define. This bites hardest at status Normal, whose
542    ///   status alone would report a payload as permitted, and a caller that
543    ///   treats what is left as the payload hands the application content the
544    ///   publisher never framed as content.
545    /// * The status forbids one. The registry rule above, reached where a
546    ///   caller holds a status and a payload together and the framing has not
547    ///   already ruled one of them out.
548    ///
549    /// **No draft turns this into a close.** The sentence is a MUST on the
550    /// sender with no receiver action named, and the "SHOULD be treated as a
551    /// protocol error" beside it belongs to the neighbouring rule about
552    /// unassigned status values. So all thirteen session-close tables place it
553    /// among the rules they state and do not end a session over — which is a
554    /// decision this variant makes visible, and one
555    /// [`CodecError::InvalidField`] was making by accident.
556    ///
557    /// Distinct from that variant for the reason
558    /// [`CodecError::ExtensionsOnNonExistentObject`] is: an error a caller
559    /// cannot name is one no test can assert and no log can explain.
560    #[error("object with status {status} carries {len} bytes of payload; {detail}")]
561    PayloadNotPermitted {
562        /// The object's status as it arrived on the wire.
563        status: u64,
564        /// How many bytes followed it.
565        len: usize,
566        /// Which of the two rules the bytes broke.
567        detail: &'static str,
568    },
569    /// A request message's Required Request ID Delta names a dependency below
570    /// zero.
571    ///
572    /// Draft-17 Section 9.2: "An endpoint MUST close the session with
573    /// INVALID_REQUIRED_REQUEST_ID if it receives a delta where 2 × Required
574    /// Request ID Delta exceeds the Request ID." Draft-18 removed the field.
575    #[error("required request id delta {1} is too large for request id {0}")]
576    InvalidRequiredRequestIdDelta(u64, u64),
577    /// A unidirectional stream announced a type its draft's stream table does
578    /// not assign.
579    ///
580    /// Every draft from 07 to 19 requires the session to end for this, in one
581    /// of two phrasings. Draft-07 and drafts 17 through 19 say "An endpoint
582    /// that receives an unknown stream type MUST close the session"; drafts 08
583    /// through 16 fold the streams and the datagrams into one sentence, "an
584    /// unknown stream or datagram type", whose second half is
585    /// [`CodecError::UnknownDatagramType`]. No draft in the range is silent,
586    /// and neither phrasing is a hint that the other draft's rule is weaker.
587    ///
588    /// What the tables assign moves across the range, so the set this reports
589    /// on is per-draft rather than shared. Draft-07 has a single table covering
590    /// streams and datagrams together, which is why an OBJECT_DATAGRAM type at
591    /// the head of a draft-07 stream is *not* unknown; drafts 08 onward split
592    /// them into two tables with independent numbering; drafts 17 through 19
593    /// add SETUP, and drafts 18 and 19 add PADDING, both of which are assigned
594    /// stream types that a subgroup reader must refuse without reporting this.
595    ///
596    /// A stream announcing an assigned type this reader cannot read is not this
597    /// error: the value is one the draft defines, and the disagreement is with
598    /// the caller rather than with the draft. Those stay
599    /// [`CodecError::InvalidField`]. The distinction is the whole point of the
600    /// variant — reporting an assigned type as unknown closes sessions over
601    /// streams the draft permits, which is the more costly way to be wrong.
602    #[error("stream type {0} is not one this draft assigns")]
603    UnknownStreamType(u64),
604    /// A datagram announced a type its draft's datagram table does not assign.
605    ///
606    /// The datagram half of the rule above, and stated by all thirteen drafts
607    /// for the same reason: drafts 08 through 16 name streams and datagrams in
608    /// one sentence, and drafts 17, 18 and 19 give the datagrams their own —
609    /// "An endpoint that receives an unknown datagram type MUST close the
610    /// session." Draft-07 alone numbers its datagrams in the stream table, so
611    /// its datagram types are reported by [`CodecError::UnknownStreamType`] and
612    /// this variant is never produced there.
613    ///
614    /// Separate from the stream variant because the two number spaces are
615    /// separate from draft-08 on: 0x05 is FETCH_HEADER on a stream and an
616    /// assigned OBJECT_DATAGRAM form in several drafts' datagram tables, so a
617    /// single variant could not say which table had been consulted.
618    #[error("datagram type {0} is not one this draft assigns")]
619    UnknownDatagramType(u64),
620    /// A Type value inside the form its draft defines, but one the draft
621    /// separately names as invalid.
622    ///
623    /// Distinct from the two variants above, which report a value no table
624    /// assigns. Drafts 16 through 19 describe their subgroup and datagram Types
625    /// as bit fields rather than as a list of code points, and then rule out
626    /// particular bit combinations *within* the form — a subgroup Type whose
627    /// SUBGROUP_ID_MODE holds the reserved value, or a datagram Type asking to
628    /// be both an object status and an end-of-group marker. The enclosing form
629    /// is assigned, so calling these unknown would misname them; the drafts
630    /// call them invalid and require a close with PROTOCOL_VIOLATION.
631    ///
632    /// `detail` names which combination was seen, because the rule is a list
633    /// rather than a single condition and a log that says only "invalid" leaves
634    /// the reader to re-derive the bits.
635    #[error("type {raw:#x} is one this draft names as invalid: {detail}")]
636    InvalidTypeValue {
637        /// The Type value as it arrived, before any narrowing to a byte.
638        raw: u64,
639        /// Which of the draft's lists it fell into.
640        detail: &'static str,
641    },
642    /// A ContentExists field carried a value other than zero or one.
643    ///
644    /// Drafts 07 through 13 all state it in the same words: "Content Exists: 1
645    /// if an object has been published on this track, 0 if not. If 0, then the
646    /// Largest Group ID and Largest Object ID fields will not be present. Any
647    /// other value is a protocol error and MUST terminate the session with a
648    /// Protocol Violation". Draft-14 states the same sentence with the code
649    /// spelled PROTOCOL_VIOLATION. Draft-07 states it for SUBSCRIBE_OK
650    /// in Section 6.15 and SUBSCRIBE_DONE in Section 6.19; drafts 08 through 11
651    /// keep the SUBSCRIBE_OK site alone; drafts 12, 13 and 14 add PUBLISH.
652    /// Draft-15 removed the field, replacing it with the presence or absence of
653    /// a LARGEST_OBJECT parameter, so no draft from 15 on has one.
654    ///
655    /// The field is a single byte and it decides whether two more follow, so a
656    /// value that is neither leaves a reader with no way to know where the
657    /// message ends.
658    #[error("content exists field carries {0}, which is neither zero nor one")]
659    InvalidContentExists(u8),
660    /// A control message's declared Length disagrees with the fields it
661    /// carries.
662    ///
663    /// All thirteen drafts state it in the same paragraph that gives the
664    /// message type registry, and only the code changes: drafts 07 through 10
665    /// say "If the length does not match the length of the message content, the
666    /// receiver MUST close the session", naming no code; drafts 11 through 18
667    /// name the Message Payload and answer with PROTOCOL_VIOLATION; draft-19
668    /// renames the field to Message Body and keeps the code.
669    ///
670    /// Both directions are the same rule and both are reported here. Fields that
671    /// stop short leave bytes unread — a trailing field the sender wrote and
672    /// this reader does not know about, or one it read at the wrong width.
673    /// Fields that run past the end wanted more bytes than the Length allowed,
674    /// which is the same disagreement seen from the other side.
675    /// The second case is why this cannot be left as
676    /// [`CodecError::UnexpectedEnd`]. That variant means *the message is still
677    /// arriving* everywhere else, and a reader loops on it rather than closing;
678    /// inside a buffer already bounded by the declared Length there is nothing
679    /// left to arrive, so running out there means something else entirely.
680    ///
681    /// Distinct from [`CodecError::InvalidField`], which the leftover-bytes case
682    /// previously answered with: a caller could not tell it from a dozen
683    /// unrelated malformations, so a session could not be closed over it.
684    #[error("control message declares {declared} bytes of payload; {detail}")]
685    ControlMessageLengthMismatch {
686        /// The Length field the sender wrote.
687        declared: usize,
688        /// Which way the two disagreed, in words, because the useful number is
689        /// different in each direction and neither is knowable in the other.
690        detail: &'static str,
691    },
692    /// Reason phrase exceeds [`MAX_REASON_PHRASE_LENGTH`].
693    #[error("reason phrase exceeds {MAX_REASON_PHRASE_LENGTH} bytes")]
694    ReasonPhraseTooLong,
695    /// GOAWAY URI exceeds [`MAX_GOAWAY_URI_LENGTH`].
696    #[error("GOAWAY URI exceeds {MAX_GOAWAY_URI_LENGTH} bytes")]
697    GoAwayUriTooLong,
698    /// Draft not implemented or not enabled via feature flag.
699    #[error("unsupported draft: {0}")]
700    UnsupportedDraft(String),
701}