Skip to main content

moqtap_client/
above_codec_rules.rs

1//! The rules an endpoint enforces that its own decoder cannot see, named once
2//! across the drafts that state them.
3//!
4//! # Why these do not live in the codec
5//!
6//! A decoder refuses a frame it cannot read. Every rule here is stated about a
7//! frame that reads perfectly well — an Object whose properties are legal bytes
8//! on a status that forbids properties, a GOAWAY that is a valid GOAWAY and is
9//! the second one, a Request ID that is a valid varint and belongs to the other
10//! endpoint's half of the number space. Being an *endpoint* rather than an
11//! observer is what turns each of them into an error, so each is raised on a
12//! receive path and never in the decoder.
13//!
14//! That placement is deliberate and it has a cost: a caller that reads
15//! [`crate::dispatch::ErrorCause::Codec`] to find out which rule a peer broke
16//! sees none of them, because the codec never raised anything.
17//! [`AboveCodecRule`] is the other half of that answer.
18//!
19//! # Two groups, one enum
20//!
21//! **What one frame says about itself.** Three rules, across six drafts, each
22//! comparing two fields of a single message: properties against a status, a
23//! payload against a status, a stream's first message type against the set of
24//! types that may open one. They reach here through
25//! `Connection::draft_specific_cause`, which every draft implements over its own
26//! `ConnectionError`.
27//!
28//! The drafts renamed two of the three and widened one, and none of the three
29//! changes altered what is required:
30//!
31//! - **Properties on a non-Normal status.** Drafts 15 and 16 call the block
32//!   *extension headers* and drafts 17 through 20 call it *properties*. Same
33//!   sentence, same consequence, one rule.
34//! - **A bidirectional stream's opening message.** Draft-16 permits exactly two
35//!   openers and names SUBSCRIBE_NAMESPACE as the second; drafts 17 through 20
36//!   permit any message that begins a request stream. The legal set grew, the
37//!   requirement did not.
38//! - **A payload on a status that permits none.** Drafts 17 through 20 only,
39//!   and the one of the three the drafts state *without* a close — see
40//!   [`AboveCodecRule::PayloadOnStatusDatagram`].
41//!
42//! **What a message says about the session it arrived in.** The rest, across
43//! all the drafts, each comparing a message against state this endpoint
44//! has been keeping: a Request ID against the sequence the peer's own ids
45//! follow, a GOAWAY against whether one has already arrived, a Track Alias
46//! against the track it already names, an Object's Location against the one the
47//! track ended at. They reach here through `EndpointError::fault`, which every
48//! draft implements over its own `EndpointError`.
49//!
50//! No frame carries the fact that decides any of them, so no decoder built for
51//! any draft could refuse one — the same reason the first group is here, one
52//! layer further out.
53//!
54//! # Which draft states which is not asked here
55//!
56//! It is answered per draft, beside the variant whose doc comment quotes that
57//! draft's own sentence — the same division of labour
58//! `Connection::codec_session_error_code` already has for the decoder's errors.
59//! A rule named here on ten drafts and not on the other four is not a claim
60//! that the other four permit it; it is a claim that this build enforces it
61//! where the text says to.
62//!
63//! # And a second enum, for the rules the decoder *did* refuse
64//!
65//! [`CodecRule`] is at the foot of this file and is the other half of the same
66//! job. Everything above is a rule no decoder could have caught; everything
67//! there is one a decoder caught and nothing carried the draft's own sentence
68//! for. The two are kept apart because they are two kinds of evidence — see
69//! that enum's own doc — and they share this file for one reason:
70//! `scripts/check-drafts.py` rule 8 reads [`RuleCitation`] rows out of **this
71//! path**, so a citation written anywhere else is a citation nothing checks.
72
73/// A rule a peer broke that no decoder could have caught.
74///
75/// Deliberately **not** `#[non_exhaustive]`. A consumer matching on this — a
76/// conformance report deciding what it will name a relay for — should find out
77/// about a new rule by failing to build, not by silently filing it under a
78/// wildcard arm.
79///
80/// The `close` that travels beside it in
81/// [`crate::dispatch::ErrorCause::PeerViolation`] is the negotiated draft's
82/// own, and several of these are stated with a close on some drafts and without
83/// one on others. The rule is the same rule either way; what the draft does
84/// about it is the other field.
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
86pub enum AboveCodecRule {
87    // ── What one frame says about itself ───────────────────────
88    /// An Object arrived carrying properties on a status that is not Normal.
89    ///
90    /// Drafts 15 and 16 state it of *extension headers* and drafts 17 through
91    /// 20 of *properties*; the block was renamed and the rule was not. Every
92    /// draft that states it answers it with a close, so a caller reading
93    /// [`crate::dispatch::ErrorCause::PeerViolation`] finds `close: Some` on
94    /// all six.
95    PropertiesOnNonNormalStatus,
96    /// A datagram whose header declared a status permitting no payload arrived
97    /// with bytes after the header.
98    ///
99    /// The one rule here the drafts state as a property of a conforming Object
100    /// rather than as a "MUST close the session" case, so `close` is `None` on
101    /// every draft that states it, and the datagram is refused with the session
102    /// left running. A caller that gates on `close: Some` will therefore never
103    /// see this one, which is the correct behaviour and not an oversight: a
104    /// relay cannot be published for breaking a rule its draft attaches no
105    /// consequence to.
106    PayloadOnStatusDatagram,
107    /// A bidirectional stream the peer opened began with a message type the
108    /// draft does not permit one to begin with.
109    ///
110    /// The permitted set differs — draft-16 allows the control stream and
111    /// SUBSCRIBE_NAMESPACE, drafts 17 through 20 allow any message that begins
112    /// a request stream — and the sentence that forbids the rest is the same on
113    /// all four. The offending stream is reset and the session closed before
114    /// the error is returned.
115    ///
116    /// Reached from both halves of this module: a draft's own `ConnectionError`
117    /// raises it where the connection owns the stream, and `EndpointError`
118    /// where the endpoint does. One rule, so one name.
119    BidiStreamOpener,
120
121    // ── What a message says about the session ──────────────────
122    /// A message arrived on a stream the draft does not place it on.
123    ///
124    /// The control stream carrying one the draft puts on a request stream, a
125    /// request stream carrying one that may not follow a request there, a
126    /// response arriving where no request is outstanding. Draft-16 states it of
127    /// NAMESPACE and NAMESPACE_DONE; drafts 18 through 20 state it of every
128    /// message their Table 5 gives a Stream value to.
129    ///
130    /// Distinct from [`Self::BidiStreamOpener`], which is about the *first*
131    /// message on a stream and is what decides what that stream is. This is
132    /// about a later one arriving somewhere it does not belong.
133    ///
134    /// # No draft states a close for it
135    ///
136    /// The rule is real — a NAMESPACE on the control stream names no request
137    /// and there is nothing for a receiver to do with it — and the
138    /// **consequence** would be ours to invent. The tempting one is
139    /// `Some(PROTOCOL_VIOLATION)`, on the reading that Section 3.3's opener
140    /// sentence covers a message on the wrong stream. It does not: that
141    /// sentence is about what a bidirectional stream may *begin* with, and a
142    /// message arriving on the control stream begins nothing.
143    ///
144    /// Read across all texts for a sentence that closes a session over
145    /// a message being in the wrong place, there is none. The drafts state
146    /// placement per message, mostly descriptively; drafts 18 through 20 add a
147    /// Stream column to their message table, whose only MUST is that a message
148    /// marked First is the first message on a new request stream — which is
149    /// [`Self::BidiStreamOpener`]'s subject and names no code; and the only
150    /// sentence in the range that closes a session over misplacement is drafts
151    /// 19 and 20's REQUEST_UPDATE sentence, which is
152    /// [`Self::RequestUpdateForTheWrongRequest`]'s.
153    ///
154    /// So a close here would be this build's model of the protocol rather than
155    /// a draft's. `session_error_code` answers `None` for every variant that
156    /// reaches this rule on every draft that raises it, and the receive paths
157    /// that raise them do not fail the session: the message is refused, the
158    /// caller is told, and the session runs on — which it can, because a
159    /// control message carries its own length and the next boundary on the
160    /// stream is known however this one was refused.
161    ///
162    /// One variant is covered by a draft sentence and is filed under the rule
163    /// that carries it rather than here. A REQUEST_UPDATE on the control stream
164    /// is, on drafts 19 and 20, squarely inside the sentence that closes over a
165    /// REQUEST_UPDATE outside the two cases those drafts permit — the first of
166    /// which is the request's own bidi stream — so it is
167    /// [`Self::RequestUpdateForTheWrongRequest`] there, with the citation that
168    /// rule already carries. On drafts 17 and 18 the same message answers
169    /// `None`, because those drafts describe the placement and attach no
170    /// consequence to it.
171    ///
172    /// The rule stays named. It is enforced, it is the peer's doing, and a
173    /// draft that attaches a consequence to it should arrive here as a citation
174    /// rather than as a rediscovery.
175    MessageOnTheWrongStream,
176    /// A response arrived on one request's stream naming a different request.
177    ///
178    /// Draft-16 only, and only because that draft has both halves at once: a
179    /// stream per namespace subscription *and* a Request ID on the messages
180    /// that travel it, so the two can disagree. Drafts 17 and later deleted the
181    /// id from responses, which deletes the disagreement.
182    ResponseNamesAnotherRequest,
183    /// A request stream's response half opened with something other than the
184    /// response the draft requires first.
185    ///
186    /// Drafts 18 through 20, of SUBSCRIBE_NAMESPACE and SUBSCRIBE_TRACKS: "If
187    /// the subscriber receives any message other than a REQUEST_OK or a
188    /// REQUEST_ERROR as the first message on the response half of the stream,
189    /// then it MUST close the session with a PROTOCOL_VIOLATION."
190    ResponseBeforeItsFirstResponse,
191    /// A GOAWAY arrived at a server that had no standing to be sent one.
192    ///
193    /// Draft-07 states the rule about the message — a server may not be sent a
194    /// GOAWAY at all — and drafts 08 through 20 about the migration URI it
195    /// carries, since only a server may tell a client where to reconnect. The
196    /// rename is the same shape as [`Self::PropertiesOnNonNormalStatus`]'s: the
197    /// text moved the prohibition onto the field, and what a conforming client
198    /// may send did not change.
199    GoAwayAtServer,
200    /// A second GOAWAY arrived where the draft allows one.
201    ///
202    /// Per session on every draft, and from draft-18 also per request stream —
203    /// that draft lets a GOAWAY migrate a single request, so one on each of two
204    /// request streams is two first GOAWAYs and not a repeat. Both are the same
205    /// sentence read at two scopes.
206    RepeatedGoAway,
207    /// A GOAWAY arrived on a request stream from the endpoint that sent the
208    /// request.
209    ///
210    /// Draft-23 Section 9.2: "Only the receiver of a request can send GOAWAY
211    /// on its request stream. If the receiver of a request receives a GOAWAY on
212    /// that request stream, it MUST close the session with a
213    /// PROTOCOL_VIOLATION."
214    GoAwayFromRequestSender,
215    /// A server received a Redirect carrying a Connect URI.
216    ///
217    /// Drafts 18 through 20. The same standing [`Self::GoAwayAtServer`] is
218    /// about, stated of the message that replaced GOAWAY's migration half.
219    RedirectUriAtServer,
220    /// A Redirect answering a namespace-scoped request carried a Track Name.
221    ///
222    /// Drafts 18 through 20: "an endpoint that receives a non-empty Track Name
223    /// in a Redirect for a namespace-scoped request MUST close the session with
224    /// a PROTOCOL_VIOLATION."
225    ///
226    /// Which requests are namespace-scoped is named in the first half of that
227    /// same sentence, and is described here rather than quoted because the list
228    /// is the half of it that moves: draft-18 names SUBSCRIBE_NAMESPACE and
229    /// PUBLISH_NAMESPACE, and drafts 19 and 20 add SUBSCRIBE_TRACKS. An
230    /// ellipsis across that list would join the clause before it to the clause
231    /// after it and make a sentence none of the three drafts has — which a
232    /// checker reports as *in no draft at all*, and which a reader cannot tell
233    /// from a transcription.
234    RedirectTrackNameOnNamespaceRequest,
235    /// A Request ID arrived whose least significant bit belongs to this
236    /// endpoint's half of the number space.
237    ///
238    /// Drafts 11 and later, which split the space by parity. Drafts 07 through
239    /// 10 have one shared sequence and state no parity rule, so they cannot
240    /// break this one.
241    RequestIdParity,
242    /// A new request carried a Request ID that is not the next one the peer's
243    /// own sequence calls for.
244    ///
245    /// A repeat and a skip alike. Drafts 11 through 16 state it as "a new
246    /// request with a Request ID that is not expected" and drafts 17 through 20
247    /// as "a duplicate Request ID". On draft-16 only a repeat is refused:
248    /// requests arrive on more than one stream, and moq-wg/moq-transport#1459
249    /// says to check duplicates rather than the sequence. Drafts 07 through 10
250    /// require the shared Subscribe ID to be "unique and monotonically
251    /// increasing". Three
252    /// phrasings of one requirement — that an id identifies exactly one request
253    /// for the life of a session.
254    RequestIdOutOfSequence,
255    /// A request carried a Request ID at or above the ceiling this endpoint
256    /// advertised.
257    ///
258    /// Drafts 07 through 16. The number measured against is the one **this**
259    /// endpoint sent, not the one the peer sent it, and the two are different
260    /// values. Draft-17 removed MAX_REQUEST_ID and the drafts after it have not
261    /// brought it back, so there is no ceiling left to exceed.
262    RequestIdCeiling,
263    /// A ceiling the peer raised did not increase.
264    ///
265    /// Every draft from 07 to 16 states it, and no one sentence covers them.
266    /// Drafts 11 through 13: "The Maximum Request ID MUST only increase within
267    /// a session, and receipt of a MAX_REQUEST_ID message with an equal or
268    /// smaller Request ID value is a 'Protocol Violation'."
269    ///
270    /// The other three eras say the same thing in different words. Drafts 07
271    /// through 10 state it of the Maximum Subscribe Id and MAX_SUBSCRIBE_ID;
272    /// drafts 14 and 15 carry draft-14's rename of the code, so the sentence
273    /// ends `is a PROTOCOL_VIOLATION` with no quotation marks on it; and
274    /// draft-16 splits it in two, keeping the first clause as a sentence of its
275    /// own and answering the second with a close. Draft-17 removed
276    /// MAX_REQUEST_ID and nothing after it restored one, so there is no ceiling
277    /// left for a peer to lower. A setup parameter carrying the same field is
278    /// held to the same sentence.
279    ///
280    /// The mirror — this endpoint asked to send a ceiling of its own that does
281    /// not increase — is a separate `EndpointError` variant on every draft that
282    /// has this one, so that the two never arrive as the same value. See
283    /// [`EndpointFault`] for why one variant covering both would have been a
284    /// silent wrong answer rather than an imprecise one.
285    MaxRequestIdDecreased,
286    /// A setup parameter arrived with a value of a kind the draft does not give
287    /// that parameter.
288    ///
289    /// Drafts 07 through 10, where MAX_SUBSCRIBE_ID is read out of the setup
290    /// block by hand. The frame decodes — a key-value pair holding bytes where
291    /// a varint was meant is a well-formed key-value pair — so the decoder
292    /// cannot see it and the endpoint reading the parameter is what does.
293    SetupParameterValue,
294    /// The peer used one Track Alias for two different tracks at once.
295    ///
296    /// Every draft states it, and every draft names a code of its own for it
297    /// rather than the general one — which is why the code travels beside the
298    /// rule instead of being assumed from it.
299    DuplicateTrackAlias,
300    /// Objects of one track arrived under more than one forwarding preference.
301    ///
302    /// Drafts 07 through 11: "it SHOULD close the session with an error of
303    /// 'Protocol Violation'". SHOULD, so the close is the caller's to make and
304    /// this build's is opt-in. The condition the drafts state it of narrows at
305    /// draft-11, from any two differing preferences to Objects arriving on both
306    /// Subgroup streams and datagrams for one SUBSCRIBE; the consequence is the
307    /// same clause either way, which is why the clause and not the whole
308    /// sentence is what is quoted.
309    ///
310    /// Drafts 12 through 15 raise it as well and answer it with nothing. That
311    /// is where a differing Forwarding Preference became one entry in the
312    /// Malformed Track list, and what those drafts require of a subscriber that
313    /// detects one is an UNSUBSCRIBE and an error to the application — see
314    /// [`Self::ObjectPastFinalObject`], which is another entry in the same
315    /// list. The quotation is held to drafts 07 through 11 for that reason:
316    /// ranging it across all nine would file four drafts' silence under a
317    /// sentence they dropped.
318    MixedForwardingPreference,
319    /// An Object with status END_OF_TRACK arrived somewhere the draft does not
320    /// allow one.
321    ///
322    /// Drafts 08 through 13: "the receiver MUST terminate the session".
323    EndOfTrackOutOfPlace,
324    /// An Object arrived past the Object the track had already ended at.
325    ///
326    /// Drafts 12 and later. A Malformed Track rather than a session error, and
327    /// what the drafts require of a subscriber that detects one moves twice
328    /// across that range. The words here are drafts 17 through 20's: "it MUST
329    /// cancel any corresponding subscription or fetches for that Track from
330    /// that publisher".
331    ///
332    /// Drafts 12 and 13 say UNSUBSCRIBE from the Track; drafts 14 through 16
333    /// say UNSUBSCRIBE any subscription and FETCH_CANCEL any fetch for that
334    /// Track from that publisher, which is the same operation named by the two
335    /// messages that perform it. All three are transport operations on the
336    /// requests and none of them is a close, so `close` is `None` wherever this
337    /// appears — the reading the range shares, and the reason one quotation
338    /// stands for the whole of it here.
339    ///
340    /// *Past* is the drafts' own Location comparison and not a reading of the
341    /// word: an Object in a later group is past the end whatever its own Object
342    /// ID is.
343    ObjectPastFinalObject,
344    /// An update arrived naming a request that cannot take one.
345    ///
346    /// A request the session has never carried, one that has already ended, one
347    /// of a kind the draft does not let a subscriber update. Drafts 12 through
348    /// 20 state some of these; drafts 19 and 20 gather them into one sentence —
349    /// "An endpoint that receives a REQUEST_UPDATE other than in the two cases
350    /// above MUST close the session with a PROTOCOL_VIOLATION."
351    RequestUpdateForTheWrongRequest,
352    /// More outstanding REQUEST_UPDATEs on one stream than this endpoint
353    /// advertised room for.
354    ///
355    /// Drafts 19 and 20: "If an endpoint receives a REQUEST_UPDATE on a stream
356    /// that already has MAX_REQUEST_UPDATES outstanding REQUEST_UPDATEs, it
357    /// MUST close the session with TOO_MANY_REQUEST_UPDATES."
358    ///
359    /// The ceiling beside it in the same section, MAX_FILTER_RANGES, is
360    /// answered with a REQUEST_ERROR instead, and nothing about either sentence
361    /// signals which — see [`EndpointFault::ThisEndpoint`], which is where that
362    /// one lands.
363    TooManyRequestUpdates,
364    /// Track Properties arrived on a REQUEST_OK answering something that is not
365    /// a TRACK_STATUS.
366    ///
367    /// Drafts 18 through 20: they "are empty in PUBLISH_OK, REQUEST_UPDATE_OK,
368    /// SUBSCRIBE_NAMESPACE_OK and PUBLISH_NAMESPACE_OK. If an endpoint receives
369    /// Track Properties in one of these messages it MUST close the session with
370    /// a PROTOCOL_VIOLATION."
371    TrackPropertiesOnNonTrackStatus,
372    /// A PUBLISH_STATE_NOTIFY arrived for something that is not a subscription,
373    /// or from the end of one that may not send it.
374    ///
375    /// Draft-20, one sentence covering both: "PUBLISH_STATE_NOTIFY applies only
376    /// to subscriptions, and is sent only by the publisher. An endpoint that
377    /// receives a PUBLISH_STATE_NOTIFY for any other request type, or from the
378    /// subscriber, MUST close the session with a PROTOCOL_VIOLATION."
379    StateNotifyOnTheWrongRequest,
380    /// A PUBLISH_STATE_NOTIFY arrived from a subscription's subscriber.
381    ///
382    /// Draft-23 Section 9.10 gives this half of the rule a code of its own: "A
383    /// publisher that receives a PUBLISH_STATE_NOTIFY MUST close the session
384    /// with ROLE_VIOLATION." Drafts 20 through 22 state both halves in one
385    /// sentence, under [`Self::StateNotifyOnTheWrongRequest`].
386    StateNotifyFromTheSubscriber,
387    /// A fill fetch stream opened against a request that asked for no fill.
388    ///
389    /// Draft-20, where `FILL_PARAMETERS` is the whole of the request: "Its
390    /// presence is what requests a fill fetch stream; a subscription with no
391    /// FILL_PARAMETERS opens none." Not a close — the draft states no
392    /// consequence, and the honest handling is `STOP_SENDING` on that stream
393    /// alone.
394    UnrequestedFillStream,
395    /// A SUBSCRIBE arrived for a namespace the peer had cancelled.
396    ///
397    /// Draft-07 alone: "it SHOULD close the session as a 'Protocol Violation'".
398    /// The mechanism it is stated about, ANNOUNCE_CANCEL against a namespace
399    /// the subscriber then subscribes under, survives the later drafts; the
400    /// sentence does not.
401    SubscribeAfterAnnounceCancel,
402    /// An UNSUBSCRIBE or REQUEST_UPDATE arrived naming a TRACK_STATUS.
403    ///
404    /// Drafts 13 through 18: a track status request is answered once and is
405    /// never a subscription, so there is nothing for either message to act on.
406    /// The drafts state it without a code, so `close` is `None`.
407    TrackStatusIsNotASubscription,
408    /// A message arrived naming a request this session has no record of.
409    ///
410    /// Stated by every draft of the messages that carry an id, and answered by
411    /// none of them with a code, so `close` is `None` throughout.
412    ///
413    /// This is narrower than it looks. Most of the variants that could carry it
414    /// are raised on both a receive path and a send path and answer
415    /// [`EndpointFault::EitherEnd`] instead; only the ones a draft raises on a
416    /// receive path alone reach this rule.
417    MessageNamesAnUnknownRequest,
418    /// The peer subscribed to a namespace prefix overlapping one it already has.
419    ///
420    /// Drafts 07 through 10, which catch it as the message arrives. From
421    /// draft-11 the same condition is caught where the answer is built, which
422    /// makes it a refusal this endpoint owes rather than a fault it observed —
423    /// see [`EndpointFault::ThisEndpoint`].
424    ///
425    /// Never a close on any draft: "it MUST respond with REQUEST_ERROR with
426    /// error code PREFIX_OVERLAP" is a reply, and a reply needs the request it
427    /// answers to have been taken.
428    NamespacePrefixOverlap,
429    /// An Object both received and signalled as not existing.
430    ///
431    /// Draft-23 Section 12.1: "An Object is received after it has been
432    /// recorded as not existing, or is signalled as not existing after it was
433    /// received (see Section 2.1.2)." A Malformed Track, with no session close.
434    ObjectExistenceContradicted,
435    /// A data stream ended with a FIN inside an Object.
436    ///
437    /// Draft-23 Section 11.3: "If a stream ends gracefully (i.e., the stream
438    /// terminates with a FIN) in the middle of a serialized Object, the
439    /// receiver SHOULD close the session with TRUNCATED_OBJECT."
440    TruncatedObject,
441}
442
443/// One draft's own words for one rule: the sentence, the section it sits in,
444/// and what that draft calls the code it answers the rule with.
445///
446/// # A citation with no draft in it is a citation about no draft
447///
448/// [`AboveCodecRule`] names a rule across every draft that states it, and that
449/// is what makes one rule one row in a conformance report rather than rows
450/// that happen to rhyme. The *sentence* cannot be shared that way: one
451/// quoted sentence per rule, printed beside whichever draft was negotiated,
452/// publishes words the negotiated draft does not contain.
453///
454/// [`AboveCodecRule::DuplicateTrackAlias`] is the worked example. Draft-18
455/// states it in Section 11.1 and spells the code DUPLICATE_TRACK_ALIAS;
456/// draft-07 states it in Section 6.4, in different words, and spells the same
457/// code Duplicate Track Alias. Publishing draft-18's row against a draft-07
458/// session would be three wrong facts at once — sentence, section and name —
459/// each dressed as evidence, and the row would read as checked.
460///
461/// The close *code* is a separate field and is not flattened with them. It
462/// comes from each draft's own `EndpointError::session_error_code`, and those
463/// per-draft tables answer 0x4 on drafts 07 through 10 and 0x5 from draft-11 on.
464/// Draft-07 numbers 0x5 Parameter Length Mismatch, so a row that borrows a
465/// neighbour's *name* for its own number names a different error entirely. The
466/// sentence, the section and the name are the three things a reader uses to
467/// check the number, which is why each is stored per run.
468///
469/// # Runs, and not a row per draft
470///
471/// Twenty-eight rules with a citation per draft is several hundred rows, and
472/// most of them
473/// would be one sentence written out again. A row here covers a **run**: every
474/// draft over which one sentence sits under one section. The sentence itself is
475/// a named constant, so a wording shared by three runs — or by two different
476/// rules, which is what happens where one sentence states both the parity rule
477/// and the sequence rule — is written once and pointed at from each.
478///
479/// Seventy-eight rows over fifty-five sentences cover every rule this build can
480/// publish. The row count is a fact about the drafts rather than about the
481/// representation: runs break where draft-14 renamed Protocol Violation to
482/// PROTOCOL_VIOLATION, where draft-16 changed terminate to close, and — far
483/// more often than either — where a section number moved under a sentence that
484/// did not change at all. The Track Alias rule alone runs 6.4, 7.4, 8.6, 8.7,
485/// 8.8, 9.8, 9.10, 9.9, 11.1 across the drafts, and only four of those eight
486/// moves coincide with a change of wording.
487///
488/// # What checks it
489///
490/// `scripts/check-drafts.py` rule 8 reads this table out of this file and holds
491/// every row against the drafts it names: that the sentence is in every draft
492/// of the run, that it sits in the section the row gives, and that the code
493/// name the row carries is a name the sentence itself uses. It is the only rule
494/// in that script that reads a Rust value rather than a comment, and it is here
495/// because a citation the gate cannot see is not a checked citation. A
496/// catalogue of sentences kept outside this workspace is walked by no gate
497/// here, so its rows are read by no machine at all.
498///
499/// # What is deliberately not here
500///
501/// Whether the negotiated draft answers the rule with a session close.
502/// `session_error_code` answers that, per draft, quoting the sentence that
503/// names it, and a second copy of that answer here could disagree with it. So a
504/// row whose [`Self::code_name`] is `None` is a draft that states the rule and
505/// names no code for it — **not** a draft that states no consequence. Two rules
506/// here are in that position on some of their drafts and neither is a gap: the
507/// end-of-Track rule says the receiver MUST terminate the session without
508/// naming which code, and drafts 07 through 10 state the Subscribe ID
509/// uniqueness requirement with no consequence at all, which is why those
510/// drafts' `session_error_code` answers `None` for it and no row is ever
511/// published there.
512#[derive(Debug, Clone, Copy, PartialEq, Eq)]
513pub struct RuleCitation {
514    /// The first and last draft this citation is claimed for, inclusive.
515    pub drafts: (u8, u8),
516    /// The section every draft in [`Self::drafts`] files the sentence under.
517    ///
518    /// One section for the whole run, because the run is cut wherever the
519    /// number moves. That is why a rule can have more rows than it has
520    /// wordings.
521    pub section: &'static str,
522    /// The sentence, in those drafts' own words.
523    ///
524    /// Verbatim, including what a transcriber would want to correct: the
525    /// cross-references the renderings carry *inside* the sentence, draft-14's
526    /// SUBSCRIBE_UDPATE, the stray backtick draft-15 renders before
527    /// PROTOCOL_VIOLATION, and draft-11's missing full stop. Every one of those
528    /// is load-bearing — the gate compares against the rendering, so a sentence
529    /// tidied up is a sentence no draft has.
530    pub sentence: &'static str,
531    /// What these drafts call the session error code they answer the rule with,
532    /// where the sentence names one.
533    ///
534    /// The name and not the number. The number is
535    /// `EndpointError::session_error_code`'s answer and travels beside the rule
536    /// already; what a reader cannot get from the number is that draft-07 calls
537    /// 0x4 Duplicate Track Alias while draft-14 calls 0x5
538    /// DUPLICATE_TRACK_ALIAS.
539    pub code_name: Option<&'static str>,
540}
541
542impl RuleCitation {
543    /// Whether this citation is claimed for `draft`.
544    #[must_use]
545    pub fn covers(&self, draft: u8) -> bool {
546        self.drafts.0 <= draft && draft <= self.drafts.1
547    }
548}
549
550// The sentences, one constant per wording. A wording shared by several
551// runs -- or by two rules, which is what the Request ID sentence is -- is
552// named here once and pointed at from each row that carries it. That is the
553// claim `a_wording_shared_across_runs_is_written_once` holds.
554//
555// Transcribed from the renderings and not tidied. The cross-references the
556// drafts put inside these sentences are part of them, draft-14's
557// SUBSCRIBE_UDPATE is the drafts' typo, the backtick before draft-15's
558// PROTOCOL_VIOLATION is in the rendering, and draft-11's forwarding
559// preference sentence really does end without a full stop. `check-drafts.py`
560// rule 8 compares each of these against the rendering it came from, so a
561// correction of the drafts' spelling is a red gate.
562const PROPERTIES_ON_STATUS_23: &str = "If an endpoint receives properties on an Object with \
563                                       status that is not Normal, it MUST close the session with \
564                                       INVALID_OBJECT_STATUS.";
565const BIDI_OPENER_23: &str = "Bidirectional streams MUST NOT begin with any other message type \
566                              unless negotiated. If they do, the peer MUST close the Session with \
567                              UNEXPECTED_MESSAGE.";
568const FIRST_RESPONSE_23: &str = "The publisher MUST send a single SUBSCRIBE_NAMESPACE_OK or \
569                                 SUBSCRIBE_NAMESPACE_ERROR as the first message on the response \
570                                 half of the stream; if the subscriber receives any other message \
571                                 first, it MUST close the session with UNEXPECTED_MESSAGE.";
572const GOAWAY_URI_CLOSE_23: &str = "If a server receives a GOAWAY with a non-zero New Session URI \
573                                   Length it MUST close the session with ROLE_VIOLATION.";
574const REPEATED_GOAWAY_23: &str = "The endpoint MUST close the session with UNEXPECTED_MESSAGE \
575                                  (Section 12.2) if it receives more than one GOAWAY on the \
576                                  control stream or on a single request stream.";
577const GOAWAY_FROM_REQUEST_SENDER: &str = "If the receiver of a request receives a GOAWAY on that \
578                                          request stream, it MUST close the session with a \
579                                          PROTOCOL_VIOLATION.";
580const REDIRECT_URI_AT_SERVER_23: &str = "If a server receives a Redirect with a non-zero Connect \
581                                         URI Length it MUST close the session with \
582                                         ROLE_VIOLATION.";
583const REDIRECT_TRACK_NAME_23: &str = "an endpoint that receives a non-empty Track Name in a \
584                                      Redirect for a namespace-scoped request MUST close the \
585                                      session with INVALID_FIELD.";
586const REQUEST_ID_PARITY_23: &str = "If an endpoint receives a Request ID where the least \
587                                    significant bit is incorrect for the sender, it MUST close \
588                                    the session with INVALID_REQUEST_ID.";
589const REQUEST_ID_DUPLICATE_23: &str = "If an endpoint detects a duplicate Request ID, it MUST \
590                                       close the session with INVALID_REQUEST_ID.";
591const UPDATE_WRONG_23: &str = "An endpoint that receives a REQUEST_UPDATE other than in the two \
592                               cases above MUST close the session with UNEXPECTED_MESSAGE.";
593const TRACK_PROPERTIES_23: &str = "Track Properties are populated in TRACK_STATUS_OK and \
594                                   FETCH_OK; they are empty in PUBLISH_OK, REQUEST_UPDATE_OK, \
595                                   SUBSCRIBE_NAMESPACE_OK and PUBLISH_NAMESPACE_OK. If an \
596                                   endpoint receives Track Properties in one of these messages it \
597                                   MUST close the session with INVALID_FIELD.";
598const STATE_NOTIFY_23: &str = "PUBLISH_STATE_NOTIFY applies only to subscriptions, and is sent \
599                               only by the publisher. An endpoint that receives a \
600                               PUBLISH_STATE_NOTIFY for any other request type MUST close the \
601                               session with UNEXPECTED_MESSAGE.";
602const STATE_NOTIFY_FROM_SUBSCRIBER: &str = "A publisher that receives a PUBLISH_STATE_NOTIFY MUST \
603                                            close the session with ROLE_VIOLATION.";
604const MESSAGE_LENGTH_23: &str = "If the length does not match the length of the Message Body, the \
605                                 receiver MUST close the session with MALFORMED_MESSAGE.";
606const TRUNCATED_OBJECT_23: &str =
607    "If a stream ends gracefully (i.e., the stream terminates with a \
608                                   FIN) in the middle of a serialized Object, the receiver SHOULD \
609                                   close the session with TRUNCATED_OBJECT.";
610const TRACK_NAME_MAX_23: &str = "The maximum total length of a Full Track Name is 4,096 bytes. \
611                                 The length of a Full Track Name is computed as the sum of the \
612                                 Track Namespace Field Length fields and the Track Name Length \
613                                 field. The length of a Track Namespace is the sum of the Track \
614                                 Namespace Field Length fields. If an endpoint receives a Track \
615                                 Namespace or a Full Track Name exceeding 4,096 bytes, it MUST \
616                                 close the session with FIELD_LENGTH_EXCEEDED.";
617const REASON_PHRASE_MAX_23: &str = "The reason phrase length has a maximum value of 1024 bytes. \
618                                    If an endpoint receives a length exceeding the maximum, it \
619                                    MUST close the session with FIELD_LENGTH_EXCEEDED.";
620const GOAWAY_URI_MAX_23: &str = "The maximum length of the New Session URI is 8,192 bytes. If an \
621                                 endpoint receives a length exceeding the maximum, it MUST close \
622                                 the session with FIELD_LENGTH_EXCEEDED.";
623const KVP_VALUE_MAX_23: &str = "The maximum length of a value is 2^16-1 bytes. If an endpoint \
624                                receives a length larger than the maximum, it MUST close the \
625                                session with FIELD_LENGTH_EXCEEDED.";
626const KVP_FORMATTING_23: &str = "If a receiver understands a Type, and the following Value or \
627                                 Length/Value does not match the serialization defined by that \
628                                 Type, the receiver MUST close the session with \
629                                 MALFORMED_KEY_VALUE.";
630const UNKNOWN_PARAMETER_23: &str = "All Message Parameters MUST be defined in the negotiated \
631                                    version of MOQT or negotiated via Setup Options. An endpoint \
632                                    that receives an unknown Message Parameter MUST close the \
633                                    session with INVALID_PARAMETER.";
634const PARAMETER_SCOPE_23: &str = "Each Message Parameter definition indicates the message types \
635                                  in which it can appear, and each control message definition \
636                                  lists the parameters it allows. If a parameter appears in some \
637                                  other type of message, the receiving endpoint MUST close the \
638                                  session with INVALID_PARAMETER.";
639const FILTER_TYPE_23: &str = "If an endpoint receives any other Location Filter Type, it MUST \
640                              close the session with INVALID_PARAMETER.";
641const END_GROUP_WRAP_23: &str = "If StartGroup + EndGroupDelta exceeds 2^64 - 1, the endpoint \
642                                 MUST close the session with DELTA_ENCODING_OVERFLOW.";
643const OBJECT_ID_WRAP_23: &str = "The Object ID Delta + 1 is added to the previous Object ID in \
644                                 the Subgroup stream if there was one. The Object ID is the \
645                                 Object ID Delta if it's the first Object in the Subgroup stream. \
646                                 If the resulting Object ID would be greater than 2^64 - 1, the \
647                                 endpoint MUST close the session with DELTA_ENCODING_OVERFLOW.";
648const INVALID_STREAM_TYPE_23: &str = "If an endpoint receives a stream header with any of these \
649                                      values, it MUST close the session with \
650                                      INVALID_STREAM_HEADER:";
651const INVALID_DATAGRAM_TYPE_23: &str = "If an endpoint receives a datagram with any of these \
652                                        values, it MUST close the session with \
653                                        INVALID_STREAM_HEADER:";
654const EXTENSION_HEADERS_ON_STATUS: &str = "If an endpoint receives extension headers on Objects \
655                                           with status that is not Normal, it MUST close the \
656                                           session with a PROTOCOL_VIOLATION.";
657const PROPERTIES_ON_STATUS: &str = "If an endpoint receives properties on an Object with status \
658                                    that is not Normal, it MUST close the session with a \
659                                    PROTOCOL_VIOLATION.";
660const BIDI_OPENER_16: &str = "Bidirectional streams MUST NOT begin with any other message type \
661                              unless negotiated. If they do, the peer MUST close the Session with \
662                              a Protocol Violation.";
663const BIDI_OPENER_17: &str = "Bidirectional streams MUST NOT begin with any other message type \
664                              unless negotiated. If they do, the peer MUST close the Session with \
665                              a PROTOCOL_VIOLATION.";
666const FIRST_RESPONSE: &str = "If the subscriber receives any message other than a REQUEST_OK or a \
667                              REQUEST_ERROR as the first message on the response half of the \
668                              stream, then it MUST close the session with a PROTOCOL_VIOLATION.";
669const FIRST_RESPONSE_22: &str = "The publisher MUST send a single SUBSCRIBE_NAMESPACE_OK or \
670                                 SUBSCRIBE_NAMESPACE_ERROR as the first message on the response \
671                                 half of the stream; if the subscriber receives any other message \
672                                 first, it MUST close the session with a PROTOCOL_VIOLATION.";
673const GOAWAY_AT_SERVER_07: &str =
674    "The server MUST terminate the session with a Protocol Violation \
675                                   (Section 3.5) if it receives a GOAWAY message.";
676const GOAWAY_URI_TERMINATE: &str = "If a server receives a GOAWAY with a non-zero New Session URI \
677                                    Length it MUST terminate the session with a Protocol \
678                                    Violation.";
679const GOAWAY_URI_TERMINATE_14: &str = "If a server receives a GOAWAY with a non-zero New Session \
680                                       URI Length it MUST terminate the session with a \
681                                       PROTOCOL_VIOLATION.";
682const GOAWAY_URI_CLOSE: &str = "If a server receives a GOAWAY with a non-zero New Session URI \
683                                Length it MUST close the session with a PROTOCOL_VIOLATION.";
684const REPEATED_GOAWAY_07: &str = "The client MUST terminate the session with a Protocol Violation \
685                                  (Section 3.5) if it receives multiple GOAWAY messages.";
686const REPEATED_GOAWAY_08: &str = "The endpoint MUST terminate the session with a Protocol \
687                                  Violation (Section 3.5) if it receives multiple GOAWAY messages.";
688const REPEATED_GOAWAY_10: &str = "The endpoint MUST terminate the session with a Protocol \
689                                  Violation (Section 3.4) if it receives multiple GOAWAY messages.";
690const REPEATED_GOAWAY_14: &str = "The endpoint MUST terminate the session with a \
691                                  PROTOCOL_VIOLATION (Section 3.4) if it receives multiple GOAWAY \
692                                  messages.";
693const REPEATED_GOAWAY_16: &str = "The endpoint MUST close the session with a PROTOCOL_VIOLATION \
694                                  (Section 3.4) if it receives multiple GOAWAY messages.";
695const REPEATED_GOAWAY_17: &str = "The endpoint MUST close the session with a PROTOCOL_VIOLATION \
696                                  (Section 3.5) if it receives multiple GOAWAY messages.";
697const REPEATED_GOAWAY_18: &str = "The endpoint MUST close the session with a PROTOCOL_VIOLATION \
698                                  (Section 3.5) if it receives more than one GOAWAY on the control \
699                                  stream or on a single request stream.";
700const REPEATED_GOAWAY_21: &str = "The endpoint MUST close the session with a PROTOCOL_VIOLATION \
701                                  (Section 12.2) if it receives more than one GOAWAY on the \
702                                  control stream or on a single request stream.";
703const REDIRECT_URI_AT_SERVER: &str = "If a server receives a Redirect with a non-zero Connect URI \
704                                      Length it MUST close the session with a PROTOCOL_VIOLATION.";
705const REDIRECT_TRACK_NAME: &str = "an endpoint that receives a non-empty Track Name in a Redirect \
706                                   for a namespace-scoped request MUST close the session with a \
707                                   PROTOCOL_VIOLATION.";
708const REQUEST_ID_11: &str = "If an endpoint receives a Request ID that is not valid for the peer, \
709                             or a new request with a Request ID that is not expected, it MUST \
710                             close the session with Invalid Request ID.";
711const REQUEST_ID_14: &str = "If an endpoint receives a Request ID that is not valid for the peer, \
712                             or a new request with a Request ID that is not expected, it MUST \
713                             close the session with INVALID_REQUEST_ID.";
714const REQUEST_ID_15: &str = "If an endpoint receives a Request ID that is not valid for the peer, \
715                             or a new request with a Request ID that is not the next in sequence \
716                             or exceeds the received MAX_REQUEST_ID, it MUST close the session \
717                             with INVALID_REQUEST_ID.";
718const REQUEST_ID_17: &str = "If an endpoint receives a Request ID where the least significant bit \
719                             is incorrect for the sender, or a duplicate Request ID, it MUST close \
720                             the session with INVALID_REQUEST_ID.";
721const SUBSCRIBE_ID_UNIQUE: &str = "Subscribe ID is a variable length integer that MUST be unique \
722                                   and monotonically increasing within a session and MUST be less \
723                                   than the session's Maximum Subscribe ID.";
724const CEILING_07: &str = "If a Subscribe ID equal or larger than this is received in any message, \
725                          including SUBSCRIBE, the publisher MUST close the session with an error \
726                          of 'Too Many Subscribes'.";
727const CEILING_08: &str = "If a Subscribe ID Section 7.4 equal or larger than this is received by \
728                          the publisher that sent the MAX_SUBSCRIBE_ID, the publisher MUST close \
729                          the session with an error of 'Too Many Subscribes'.";
730const CEILING_10: &str = "If a Subscribe ID Section 8.6 equal or larger than this is received by \
731                          the publisher that sent the MAX_SUBSCRIBE_ID, the publisher MUST close \
732                          the session with an error of 'Too Many Subscribes'.";
733const CEILING_11: &str = "If a Request ID equal or larger than this is received by the endpoint \
734                          that sent the MAX_REQUEST_ID in any request message (ANNOUNCE, FETCH, \
735                          SUBSCRIBE, SUBSCRIBE_ANNOUNCES or TRACK_STATUS_REQUEST), the endpoint \
736                          MUST close the session with an error of 'Too Many Requests'.";
737const CEILING_12: &str =
738    "If a Request ID equal to or larger than this is received by the endpoint \
739                          that sent the MAX_REQUEST_ID in any request message (ANNOUNCE, FETCH, \
740                          SUBSCRIBE, SUBSCRIBE_ANNOUNCES or TRACK_STATUS_REQUEST), the endpoint \
741                          MUST close the session with an error of 'Too Many Requests'.";
742const CEILING_13: &str = "If a Request ID equal or larger than this is received by the endpoint \
743                          that sent the MAX_REQUEST_ID in any request message (ANNOUNCE, FETCH, \
744                          SUBSCRIBE, SUBSCRIBE_NAMESPACE or TRACK_STATUS), the endpoint MUST close \
745                          the session with an error of 'Too Many Requests'.";
746const CEILING_14: &str =
747    "If a Request ID equal to or larger than this is received by the endpoint \
748                          that sent the MAX_REQUEST_ID in any request message (PUBLISH_NAMESPACE, \
749                          FETCH, SUBSCRIBE, SUBSCRIBE_NAMESPACE, SUBSCRIBE_UDPATE or \
750                          TRACK_STATUS), the endpoint MUST close the session with an error of \
751                          TOO_MANY_REQUESTS.";
752const CEILING_16: &str =
753    "If a Request ID equal to or larger than this is received by the endpoint \
754                          that sent the MAX_REQUEST_ID in any request message (PUBLISH_NAMESPACE, \
755                          FETCH, SUBSCRIBE, SUBSCRIBE_NAMESPACE, REQUEST_UPDATE or TRACK_STATUS), \
756                          the endpoint MUST close the session with an error of TOO_MANY_REQUESTS.";
757const MAX_ID_07: &str =
758    "The Maximum Subscribe Id MUST only increase within a session, and receipt \
759                         of a MAX_SUBSCRIBE_ID message with an equal or smaller Subscribe ID value \
760                         is a 'Protocol Violation'.";
761const MAX_ID_11: &str = "The Maximum Request ID MUST only increase within a session, and receipt \
762                         of a MAX_REQUEST_ID message with an equal or smaller Request ID value is \
763                         a 'Protocol Violation'.";
764const MAX_ID_14: &str = "The Maximum Request ID MUST only increase within a session, and receipt \
765                         of a MAX_REQUEST_ID message with an equal or smaller Request ID value is \
766                         a PROTOCOL_VIOLATION.";
767const MAX_ID_16: &str = "The Maximum Request ID MUST only increase within a session. If an \
768                         endpoint receives MAX_REQUEST_ID message with an equal or smaller Request \
769                         ID it MUST close the session with a PROTOCOL_VIOLATION.";
770const ALIAS_07: &str = "If the Track Alias is already being used for a different track, the \
771                        publisher MUST close the session with a Duplicate Track Alias error \
772                        (Section 3.5).";
773const ALIAS_10: &str = "If the Track Alias is already being used for a different track, the \
774                        publisher MUST close the session with a Duplicate Track Alias error \
775                        (Section 3.4).";
776const ALIAS_12: &str = "The same Track Alias MUST NOT be used to refer to two different Tracks \
777                        simultaneously. If a subscriber receives a SUBSCRIBE_OK that uses the same \
778                        Track Alias as a different track with an active subscription, it MUST \
779                        close the session with error 'Duplicate Track Alias'.";
780const ALIAS_14: &str = "The same Track Alias MUST NOT be used to refer to two different Tracks \
781                        simultaneously. If a subscriber receives a SUBSCRIBE_OK that uses the same \
782                        Track Alias as a different track with an active subscription, it MUST \
783                        close the session with error DUPLICATE_TRACK_ALIAS.";
784const ALIAS_15: &str = "The same Track Alias MUST NOT be used to refer to two different Tracks \
785                        simultaneously. If a subscriber receives a SUBSCRIBE_OK that uses the same \
786                        Track Alias as a different track with an Established subscription, it MUST \
787                        close the session with error DUPLICATE_TRACK_ALIAS.";
788const ALIAS_17: &str = "The same Track Alias MUST NOT be used by a publisher to refer to two \
789                        different Tracks simultaneously in the same session. If a subscriber \
790                        receives a SUBSCRIBE_OK that uses the same Track Alias as a different \
791                        track with an Established subscription, it MUST close the session with \
792                        error DUPLICATE_TRACK_ALIAS.";
793const ALIAS_18: &str = "The same Track Alias MUST NOT be used by a publisher to refer to two \
794                        different Tracks simultaneously in the same session. If a subscriber \
795                        receives a PUBLISH or SUBSCRIBE_OK that uses the same Track Alias as a \
796                        different Track with an Established subscription, it MUST close the \
797                        session with error DUPLICATE_TRACK_ALIAS.";
798const MIXED_PREFERENCE_07: &str =
799    "Every Track has a single 'Object Forwarding Preference' and the \
800                                   Original Publisher MUST NOT mix different forwarding \
801                                   preferences within a single track. If a subscriber receives \
802                                   different forwarding preferences for a track, it SHOULD close \
803                                   the session with an error of 'Protocol Violation'.";
804const MIXED_PREFERENCE_11: &str =
805    "Every Track has a single 'Object Forwarding Preference' and the \
806                                   Original Publisher MUST NOT mix different forwarding \
807                                   preferences within a single track. If a subscriber receives \
808                                   Objects via both Subgroup streams and Datagrams in response to \
809                                   a SUBSCRIBE, it SHOULD close the session with an error of \
810                                   'Protocol Violation'";
811const END_OF_TRACK_08: &str = "An object with this status that has a Group ID less than any other \
812                               Group ID, or an Object ID less than or equal to the largest in the \
813                               group, is a protocol error, and the receiver MUST terminate the \
814                               session.";
815const END_OF_TRACK_11: &str = "An object with this status that has a Group ID less than any other \
816                               GroupID, or an ObjectID less than or equal to the largest in the \
817                               specified group, is a protocol error, and the receiver MUST \
818                               terminate the session.";
819const UPDATE_WRONG_12: &str = "A publisher MUST terminate the session with a 'Protocol Violation' \
820                               if the SUBSCRIBE_UPDATE violates these rules or if the subscriber \
821                               specifies a request ID that has not existed within the Session.";
822const UPDATE_WRONG_14: &str =
823    "A publisher MUST terminate the session with a PROTOCOL_VIOLATION if \
824                               the SUBSCRIBE_UPDATE violates these rules or if the subscriber \
825                               specifies a request ID that has not existed within the Session.";
826const UPDATE_WRONG_15: &str = "This MUST match an existing Request ID. The publisher MUST close \
827                               the session with ` PROTOCOL_VIOLATION if the subscriber specifies \
828                               an invalid Subscription Request ID.";
829const UPDATE_WRONG_16: &str =
830    "This MUST match the Request ID of an existing request. The receiver \
831                               MUST close the session with PROTOCOL_VIOLATION if the sender \
832                               specifies an invalid Existing Request ID, or if the parameters \
833                               included in the REQUEST_UPDATE are invalid for the type of request \
834                               being modified.";
835const UPDATE_WRONG_19: &str = "An endpoint that receives a REQUEST_UPDATE other than in the two \
836                               cases above MUST close the session with a PROTOCOL_VIOLATION.";
837const TOO_MANY_UPDATES: &str = "If an endpoint receives a REQUEST_UPDATE on a stream that already \
838                                has MAX_REQUEST_UPDATES outstanding REQUEST_UPDATEs, it MUST close \
839                                the session with TOO_MANY_REQUEST_UPDATES.";
840const TRACK_PROPERTIES: &str = "Track Properties are populated in TRACK_STATUS_OK; they are empty \
841                                in PUBLISH_OK, REQUEST_UPDATE_OK, SUBSCRIBE_NAMESPACE_OK and \
842                                PUBLISH_NAMESPACE_OK. If an endpoint receives Track Properties in \
843                                one of these messages it MUST close the session with a \
844                                PROTOCOL_VIOLATION.";
845const STATE_NOTIFY: &str = "PUBLISH_STATE_NOTIFY applies only to subscriptions, and is sent only \
846                            by the publisher. An endpoint that receives a PUBLISH_STATE_NOTIFY for \
847                            any other request type, or from the subscriber, MUST close the session \
848                            with a PROTOCOL_VIOLATION.";
849const SUBSCRIBE_AFTER_CANCEL: &str =
850    "If a publisher receives new subscriptions for that namespace \
851                                      after receiving an ANNOUNCE_CANCEL, it SHOULD close the \
852                                      session as a 'Protocol Violation'.";
853
854impl AboveCodecRule {
855    /// Every draft run this rule has a checked citation for, oldest first.
856    ///
857    /// Exhaustive with no wildcard arm, for the reason the enum is not
858    /// `#[non_exhaustive]`: a rule added to this file arrives here as an
859    /// `E0004` and a decision about which drafts state it, rather than as an
860    /// empty slice nobody chose.
861    ///
862    /// An empty slice is one of those decisions and not an oversight. Nine
863    /// rules answer with one, in two groups.
864    ///
865    /// **Eight are rules no draft in range answers with a session close.** The
866    /// datagram payload rule, a response naming another request, a setup
867    /// parameter whose value is not of its type's kind, an Object past the one
868    /// the track ended at, a fill stream nobody asked for, an UNSUBSCRIBE or
869    /// REQUEST_UPDATE naming a TRACK_STATUS, a message naming a request the
870    /// session has no record of, and a namespace prefix overlapping one the
871    /// peer already has. Every one is a real rule this endpoint enforces, and
872    /// `session_error_code` already answers `None` for all of them, so nothing
873    /// downstream could publish one whatever this table said. They are written
874    /// out so that a draft attaching a consequence to one arrives here as a
875    /// build failure.
876    ///
877    /// **The ninth is [`Self::MessageOnTheWrongStream`].** It is enforced on
878    /// drafts 16 through 20 and no draft in that range states it, so it is
879    /// named, enforced and attributed to the peer while publishing nothing:
880    /// there is no sentence to publish and `close` is `None` on every draft.
881    /// The variant's own doc carries the reading across all texts that
882    /// establishes it, and the one variant that really is covered by a draft
883    /// sentence is filed under the rule whose sentence covers it. A draft that
884    /// attaches a consequence to this one arrives as a citation rather than as
885    /// a rediscovery.
886    #[must_use]
887    pub fn citations(self) -> &'static [RuleCitation] {
888        match self {
889            Self::PropertiesOnNonNormalStatus => &[
890                RuleCitation {
891                    drafts: (15, 16),
892                    section: "10.2.1.2",
893                    sentence: EXTENSION_HEADERS_ON_STATUS,
894                    code_name: Some("PROTOCOL_VIOLATION"),
895                },
896                RuleCitation {
897                    drafts: (17, 17),
898                    section: "10.2.1.2",
899                    sentence: PROPERTIES_ON_STATUS,
900                    code_name: Some("PROTOCOL_VIOLATION"),
901                },
902                RuleCitation {
903                    drafts: (18, 20),
904                    section: "11.2.1.2",
905                    sentence: PROPERTIES_ON_STATUS,
906                    code_name: Some("PROTOCOL_VIOLATION"),
907                },
908                RuleCitation {
909                    drafts: (21, 21),
910                    section: "11.1.3",
911                    sentence: PROPERTIES_ON_STATUS,
912                    code_name: Some("PROTOCOL_VIOLATION"),
913                },
914                RuleCitation {
915                    drafts: (22, 22),
916                    section: "11.1.2",
917                    sentence: PROPERTIES_ON_STATUS,
918                    code_name: Some("PROTOCOL_VIOLATION"),
919                },
920                RuleCitation {
921                    drafts: (23, 23),
922                    section: "11.1.2",
923                    sentence: PROPERTIES_ON_STATUS_23,
924                    code_name: Some("INVALID_OBJECT_STATUS"),
925                },
926            ],
927            Self::BidiStreamOpener => &[
928                RuleCitation {
929                    drafts: (16, 16),
930                    section: "3.3",
931                    sentence: BIDI_OPENER_16,
932                    code_name: Some("Protocol Violation"),
933                },
934                RuleCitation {
935                    drafts: (17, 20),
936                    section: "3.3",
937                    sentence: BIDI_OPENER_17,
938                    code_name: Some("PROTOCOL_VIOLATION"),
939                },
940                RuleCitation {
941                    drafts: (21, 22),
942                    section: "6.3",
943                    sentence: BIDI_OPENER_17,
944                    code_name: Some("PROTOCOL_VIOLATION"),
945                },
946                RuleCitation {
947                    drafts: (23, 23),
948                    section: "6.3",
949                    sentence: BIDI_OPENER_23,
950                    code_name: Some("UNEXPECTED_MESSAGE"),
951                },
952            ],
953            Self::ResponseBeforeItsFirstResponse => &[
954                RuleCitation {
955                    drafts: (18, 19),
956                    section: "10.18",
957                    sentence: FIRST_RESPONSE,
958                    code_name: Some("PROTOCOL_VIOLATION"),
959                },
960                RuleCitation {
961                    drafts: (20, 20),
962                    section: "10.19",
963                    sentence: FIRST_RESPONSE,
964                    code_name: Some("PROTOCOL_VIOLATION"),
965                },
966                RuleCitation {
967                    drafts: (21, 21),
968                    section: "9.15",
969                    sentence: FIRST_RESPONSE,
970                    code_name: Some("PROTOCOL_VIOLATION"),
971                },
972                RuleCitation {
973                    drafts: (22, 22),
974                    section: "4.2",
975                    sentence: FIRST_RESPONSE_22,
976                    code_name: Some("PROTOCOL_VIOLATION"),
977                },
978                RuleCitation {
979                    drafts: (23, 23),
980                    section: "4.2",
981                    sentence: FIRST_RESPONSE_23,
982                    code_name: Some("UNEXPECTED_MESSAGE"),
983                },
984            ],
985            Self::GoAwayAtServer => &[
986                RuleCitation {
987                    drafts: (7, 7),
988                    section: "6.3",
989                    sentence: GOAWAY_AT_SERVER_07,
990                    code_name: Some("Protocol Violation"),
991                },
992                RuleCitation {
993                    drafts: (8, 9),
994                    section: "7.3",
995                    sentence: GOAWAY_URI_TERMINATE,
996                    code_name: Some("Protocol Violation"),
997                },
998                RuleCitation {
999                    drafts: (10, 10),
1000                    section: "8.3",
1001                    sentence: GOAWAY_URI_TERMINATE,
1002                    code_name: Some("Protocol Violation"),
1003                },
1004                RuleCitation {
1005                    drafts: (11, 13),
1006                    section: "8.4",
1007                    sentence: GOAWAY_URI_TERMINATE,
1008                    code_name: Some("Protocol Violation"),
1009                },
1010                RuleCitation {
1011                    drafts: (14, 15),
1012                    section: "9.4",
1013                    sentence: GOAWAY_URI_TERMINATE_14,
1014                    code_name: Some("PROTOCOL_VIOLATION"),
1015                },
1016                RuleCitation {
1017                    drafts: (16, 16),
1018                    section: "9.4",
1019                    sentence: GOAWAY_URI_CLOSE,
1020                    code_name: Some("PROTOCOL_VIOLATION"),
1021                },
1022                RuleCitation {
1023                    drafts: (17, 17),
1024                    section: "9.5",
1025                    sentence: GOAWAY_URI_CLOSE,
1026                    code_name: Some("PROTOCOL_VIOLATION"),
1027                },
1028                RuleCitation {
1029                    drafts: (18, 20),
1030                    section: "10.4",
1031                    sentence: GOAWAY_URI_CLOSE,
1032                    code_name: Some("PROTOCOL_VIOLATION"),
1033                },
1034                RuleCitation {
1035                    drafts: (21, 22),
1036                    section: "9.2",
1037                    sentence: GOAWAY_URI_CLOSE,
1038                    code_name: Some("PROTOCOL_VIOLATION"),
1039                },
1040                RuleCitation {
1041                    drafts: (23, 23),
1042                    section: "9.2",
1043                    sentence: GOAWAY_URI_CLOSE_23,
1044                    code_name: Some("ROLE_VIOLATION"),
1045                },
1046            ],
1047            Self::RepeatedGoAway => &[
1048                RuleCitation {
1049                    drafts: (7, 7),
1050                    section: "6.3",
1051                    sentence: REPEATED_GOAWAY_07,
1052                    code_name: Some("Protocol Violation"),
1053                },
1054                RuleCitation {
1055                    drafts: (8, 9),
1056                    section: "7.3",
1057                    sentence: REPEATED_GOAWAY_08,
1058                    code_name: Some("Protocol Violation"),
1059                },
1060                RuleCitation {
1061                    drafts: (10, 10),
1062                    section: "8.3",
1063                    sentence: REPEATED_GOAWAY_10,
1064                    code_name: Some("Protocol Violation"),
1065                },
1066                RuleCitation {
1067                    drafts: (11, 13),
1068                    section: "8.4",
1069                    sentence: REPEATED_GOAWAY_10,
1070                    code_name: Some("Protocol Violation"),
1071                },
1072                RuleCitation {
1073                    drafts: (14, 15),
1074                    section: "9.4",
1075                    sentence: REPEATED_GOAWAY_14,
1076                    code_name: Some("PROTOCOL_VIOLATION"),
1077                },
1078                RuleCitation {
1079                    drafts: (16, 16),
1080                    section: "9.4",
1081                    sentence: REPEATED_GOAWAY_16,
1082                    code_name: Some("PROTOCOL_VIOLATION"),
1083                },
1084                RuleCitation {
1085                    drafts: (17, 17),
1086                    section: "9.5",
1087                    sentence: REPEATED_GOAWAY_17,
1088                    code_name: Some("PROTOCOL_VIOLATION"),
1089                },
1090                RuleCitation {
1091                    drafts: (18, 20),
1092                    section: "10.4",
1093                    sentence: REPEATED_GOAWAY_18,
1094                    code_name: Some("PROTOCOL_VIOLATION"),
1095                },
1096                RuleCitation {
1097                    drafts: (21, 22),
1098                    section: "9.2",
1099                    sentence: REPEATED_GOAWAY_21,
1100                    code_name: Some("PROTOCOL_VIOLATION"),
1101                },
1102                RuleCitation {
1103                    drafts: (23, 23),
1104                    section: "9.2",
1105                    sentence: REPEATED_GOAWAY_23,
1106                    code_name: Some("UNEXPECTED_MESSAGE"),
1107                },
1108            ],
1109            Self::GoAwayFromRequestSender => &[RuleCitation {
1110                drafts: (23, 23),
1111                section: "9.2",
1112                sentence: GOAWAY_FROM_REQUEST_SENDER,
1113                code_name: Some("PROTOCOL_VIOLATION"),
1114            }],
1115            Self::TruncatedObject => &[RuleCitation {
1116                drafts: (23, 23),
1117                section: "11.3",
1118                sentence: TRUNCATED_OBJECT_23,
1119                code_name: Some("TRUNCATED_OBJECT"),
1120            }],
1121            Self::StateNotifyFromTheSubscriber => &[RuleCitation {
1122                drafts: (23, 23),
1123                section: "9.10",
1124                sentence: STATE_NOTIFY_FROM_SUBSCRIBER,
1125                code_name: Some("ROLE_VIOLATION"),
1126            }],
1127            Self::RedirectUriAtServer => &[
1128                RuleCitation {
1129                    drafts: (18, 20),
1130                    section: "10.6.1",
1131                    sentence: REDIRECT_URI_AT_SERVER,
1132                    code_name: Some("PROTOCOL_VIOLATION"),
1133                },
1134                RuleCitation {
1135                    drafts: (21, 22),
1136                    section: "9.4.1",
1137                    sentence: REDIRECT_URI_AT_SERVER,
1138                    code_name: Some("PROTOCOL_VIOLATION"),
1139                },
1140                RuleCitation {
1141                    drafts: (23, 23),
1142                    section: "9.4.2.1",
1143                    sentence: REDIRECT_URI_AT_SERVER_23,
1144                    code_name: Some("ROLE_VIOLATION"),
1145                },
1146            ],
1147            Self::RedirectTrackNameOnNamespaceRequest => &[
1148                RuleCitation {
1149                    drafts: (18, 20),
1150                    section: "10.6.1",
1151                    sentence: REDIRECT_TRACK_NAME,
1152                    code_name: Some("PROTOCOL_VIOLATION"),
1153                },
1154                RuleCitation {
1155                    drafts: (21, 22),
1156                    section: "9.4.1",
1157                    sentence: REDIRECT_TRACK_NAME,
1158                    code_name: Some("PROTOCOL_VIOLATION"),
1159                },
1160                RuleCitation {
1161                    drafts: (23, 23),
1162                    section: "9.4.2.1",
1163                    sentence: REDIRECT_TRACK_NAME_23,
1164                    code_name: Some("INVALID_FIELD"),
1165                },
1166            ],
1167            Self::RequestIdParity => &[
1168                RuleCitation {
1169                    drafts: (11, 13),
1170                    section: "8.1",
1171                    sentence: REQUEST_ID_11,
1172                    code_name: Some("Invalid Request ID"),
1173                },
1174                RuleCitation {
1175                    drafts: (14, 14),
1176                    section: "9.1",
1177                    sentence: REQUEST_ID_14,
1178                    code_name: Some("INVALID_REQUEST_ID"),
1179                },
1180                RuleCitation {
1181                    drafts: (15, 16),
1182                    section: "9.1",
1183                    sentence: REQUEST_ID_15,
1184                    code_name: Some("INVALID_REQUEST_ID"),
1185                },
1186                RuleCitation {
1187                    drafts: (17, 17),
1188                    section: "9.1",
1189                    sentence: REQUEST_ID_17,
1190                    code_name: Some("INVALID_REQUEST_ID"),
1191                },
1192                RuleCitation {
1193                    drafts: (18, 20),
1194                    section: "10.1",
1195                    sentence: REQUEST_ID_17,
1196                    code_name: Some("INVALID_REQUEST_ID"),
1197                },
1198                RuleCitation {
1199                    drafts: (21, 22),
1200                    section: "6.4.2.1",
1201                    sentence: REQUEST_ID_17,
1202                    code_name: Some("INVALID_REQUEST_ID"),
1203                },
1204                RuleCitation {
1205                    drafts: (23, 23),
1206                    section: "6.4.2.1",
1207                    sentence: REQUEST_ID_PARITY_23,
1208                    code_name: Some("INVALID_REQUEST_ID"),
1209                },
1210            ],
1211            Self::RequestIdOutOfSequence => &[
1212                RuleCitation {
1213                    drafts: (7, 7),
1214                    section: "6.4",
1215                    sentence: SUBSCRIBE_ID_UNIQUE,
1216                    code_name: None,
1217                },
1218                RuleCitation {
1219                    drafts: (8, 9),
1220                    section: "7.4",
1221                    sentence: SUBSCRIBE_ID_UNIQUE,
1222                    code_name: None,
1223                },
1224                RuleCitation {
1225                    drafts: (10, 10),
1226                    section: "8.6",
1227                    sentence: SUBSCRIBE_ID_UNIQUE,
1228                    code_name: None,
1229                },
1230                RuleCitation {
1231                    drafts: (11, 13),
1232                    section: "8.1",
1233                    sentence: REQUEST_ID_11,
1234                    code_name: Some("Invalid Request ID"),
1235                },
1236                RuleCitation {
1237                    drafts: (14, 14),
1238                    section: "9.1",
1239                    sentence: REQUEST_ID_14,
1240                    code_name: Some("INVALID_REQUEST_ID"),
1241                },
1242                RuleCitation {
1243                    drafts: (15, 15),
1244                    section: "9.1",
1245                    sentence: REQUEST_ID_15,
1246                    code_name: Some("INVALID_REQUEST_ID"),
1247                },
1248                // Enforced as duplicates only, per moq-wg/moq-transport#1459.
1249                RuleCitation {
1250                    drafts: (16, 16),
1251                    section: "9.1",
1252                    sentence: REQUEST_ID_15,
1253                    code_name: Some("INVALID_REQUEST_ID"),
1254                },
1255                RuleCitation {
1256                    drafts: (17, 17),
1257                    section: "9.1",
1258                    sentence: REQUEST_ID_17,
1259                    code_name: Some("INVALID_REQUEST_ID"),
1260                },
1261                RuleCitation {
1262                    drafts: (18, 20),
1263                    section: "10.1",
1264                    sentence: REQUEST_ID_17,
1265                    code_name: Some("INVALID_REQUEST_ID"),
1266                },
1267                RuleCitation {
1268                    drafts: (21, 22),
1269                    section: "6.4.2.1",
1270                    sentence: REQUEST_ID_17,
1271                    code_name: Some("INVALID_REQUEST_ID"),
1272                },
1273                RuleCitation {
1274                    drafts: (23, 23),
1275                    section: "6.4.2.1",
1276                    sentence: REQUEST_ID_DUPLICATE_23,
1277                    code_name: Some("INVALID_REQUEST_ID"),
1278                },
1279            ],
1280            Self::RequestIdCeiling => &[
1281                RuleCitation {
1282                    drafts: (7, 7),
1283                    section: "6.20",
1284                    sentence: CEILING_07,
1285                    code_name: Some("Too Many Subscribes"),
1286                },
1287                RuleCitation {
1288                    drafts: (8, 9),
1289                    section: "7.20",
1290                    sentence: CEILING_08,
1291                    code_name: Some("Too Many Subscribes"),
1292                },
1293                RuleCitation {
1294                    drafts: (10, 10),
1295                    section: "8.4",
1296                    sentence: CEILING_10,
1297                    code_name: Some("Too Many Subscribes"),
1298                },
1299                RuleCitation {
1300                    drafts: (11, 11),
1301                    section: "8.5",
1302                    sentence: CEILING_11,
1303                    code_name: Some("Too Many Requests"),
1304                },
1305                RuleCitation {
1306                    drafts: (12, 12),
1307                    section: "8.5",
1308                    sentence: CEILING_12,
1309                    code_name: Some("Too Many Requests"),
1310                },
1311                RuleCitation {
1312                    drafts: (13, 13),
1313                    section: "8.5",
1314                    sentence: CEILING_13,
1315                    code_name: Some("Too Many Requests"),
1316                },
1317                RuleCitation {
1318                    drafts: (14, 15),
1319                    section: "9.5",
1320                    sentence: CEILING_14,
1321                    code_name: Some("TOO_MANY_REQUESTS"),
1322                },
1323                RuleCitation {
1324                    drafts: (16, 16),
1325                    section: "9.5",
1326                    sentence: CEILING_16,
1327                    code_name: Some("TOO_MANY_REQUESTS"),
1328                },
1329            ],
1330            Self::MaxRequestIdDecreased => &[
1331                RuleCitation {
1332                    drafts: (7, 7),
1333                    section: "6.20",
1334                    sentence: MAX_ID_07,
1335                    code_name: Some("Protocol Violation"),
1336                },
1337                RuleCitation {
1338                    drafts: (8, 9),
1339                    section: "7.20",
1340                    sentence: MAX_ID_07,
1341                    code_name: Some("Protocol Violation"),
1342                },
1343                RuleCitation {
1344                    drafts: (10, 10),
1345                    section: "8.4",
1346                    sentence: MAX_ID_07,
1347                    code_name: Some("Protocol Violation"),
1348                },
1349                RuleCitation {
1350                    drafts: (11, 13),
1351                    section: "8.5",
1352                    sentence: MAX_ID_11,
1353                    code_name: Some("Protocol Violation"),
1354                },
1355                RuleCitation {
1356                    drafts: (14, 15),
1357                    section: "9.5",
1358                    sentence: MAX_ID_14,
1359                    code_name: Some("PROTOCOL_VIOLATION"),
1360                },
1361                RuleCitation {
1362                    drafts: (16, 16),
1363                    section: "9.5",
1364                    sentence: MAX_ID_16,
1365                    code_name: Some("PROTOCOL_VIOLATION"),
1366                },
1367            ],
1368            Self::DuplicateTrackAlias => &[
1369                RuleCitation {
1370                    drafts: (7, 7),
1371                    section: "6.4",
1372                    sentence: ALIAS_07,
1373                    code_name: Some("Duplicate Track Alias"),
1374                },
1375                RuleCitation {
1376                    drafts: (8, 9),
1377                    section: "7.4",
1378                    sentence: ALIAS_07,
1379                    code_name: Some("Duplicate Track Alias"),
1380                },
1381                RuleCitation {
1382                    drafts: (10, 10),
1383                    section: "8.6",
1384                    sentence: ALIAS_10,
1385                    code_name: Some("Duplicate Track Alias"),
1386                },
1387                RuleCitation {
1388                    drafts: (11, 11),
1389                    section: "8.7",
1390                    sentence: ALIAS_10,
1391                    code_name: Some("Duplicate Track Alias"),
1392                },
1393                RuleCitation {
1394                    drafts: (12, 13),
1395                    section: "8.8",
1396                    sentence: ALIAS_12,
1397                    code_name: Some("Duplicate Track Alias"),
1398                },
1399                RuleCitation {
1400                    drafts: (14, 14),
1401                    section: "9.8",
1402                    sentence: ALIAS_14,
1403                    code_name: Some("DUPLICATE_TRACK_ALIAS"),
1404                },
1405                RuleCitation {
1406                    drafts: (15, 16),
1407                    section: "9.10",
1408                    sentence: ALIAS_15,
1409                    code_name: Some("DUPLICATE_TRACK_ALIAS"),
1410                },
1411                RuleCitation {
1412                    drafts: (17, 17),
1413                    section: "9.9",
1414                    sentence: ALIAS_17,
1415                    code_name: Some("DUPLICATE_TRACK_ALIAS"),
1416                },
1417                RuleCitation {
1418                    drafts: (18, 20),
1419                    section: "11.1",
1420                    sentence: ALIAS_18,
1421                    code_name: Some("DUPLICATE_TRACK_ALIAS"),
1422                },
1423                RuleCitation {
1424                    drafts: (21, 21),
1425                    section: "3.1.2",
1426                    sentence: ALIAS_18,
1427                    code_name: Some("DUPLICATE_TRACK_ALIAS"),
1428                },
1429                RuleCitation {
1430                    drafts: (22, 23),
1431                    section: "3.1.3",
1432                    sentence: ALIAS_18,
1433                    code_name: Some("DUPLICATE_TRACK_ALIAS"),
1434                },
1435            ],
1436            Self::MixedForwardingPreference => &[
1437                RuleCitation {
1438                    drafts: (7, 7),
1439                    section: "7",
1440                    sentence: MIXED_PREFERENCE_07,
1441                    code_name: Some("Protocol Violation"),
1442                },
1443                RuleCitation {
1444                    drafts: (8, 9),
1445                    section: "8",
1446                    sentence: MIXED_PREFERENCE_07,
1447                    code_name: Some("Protocol Violation"),
1448                },
1449                RuleCitation {
1450                    drafts: (10, 10),
1451                    section: "9",
1452                    sentence: MIXED_PREFERENCE_07,
1453                    code_name: Some("Protocol Violation"),
1454                },
1455                RuleCitation {
1456                    drafts: (11, 11),
1457                    section: "9",
1458                    sentence: MIXED_PREFERENCE_11,
1459                    code_name: Some("Protocol Violation"),
1460                },
1461            ],
1462            Self::EndOfTrackOutOfPlace => &[
1463                RuleCitation {
1464                    drafts: (8, 9),
1465                    section: "8.1.1.1",
1466                    sentence: END_OF_TRACK_08,
1467                    code_name: None,
1468                },
1469                RuleCitation {
1470                    drafts: (10, 10),
1471                    section: "9.1.1.1",
1472                    sentence: END_OF_TRACK_08,
1473                    code_name: None,
1474                },
1475                RuleCitation {
1476                    drafts: (11, 11),
1477                    section: "9.1.1.1",
1478                    sentence: END_OF_TRACK_11,
1479                    code_name: None,
1480                },
1481                RuleCitation {
1482                    drafts: (12, 13),
1483                    section: "9.2.1.1",
1484                    sentence: END_OF_TRACK_11,
1485                    code_name: None,
1486                },
1487            ],
1488            Self::RequestUpdateForTheWrongRequest => &[
1489                RuleCitation {
1490                    drafts: (12, 13),
1491                    section: "8.10",
1492                    sentence: UPDATE_WRONG_12,
1493                    code_name: Some("Protocol Violation"),
1494                },
1495                RuleCitation {
1496                    drafts: (14, 14),
1497                    section: "9.10",
1498                    sentence: UPDATE_WRONG_14,
1499                    code_name: Some("PROTOCOL_VIOLATION"),
1500                },
1501                RuleCitation {
1502                    drafts: (15, 15),
1503                    section: "9.11",
1504                    sentence: UPDATE_WRONG_15,
1505                    code_name: Some("PROTOCOL_VIOLATION"),
1506                },
1507                RuleCitation {
1508                    drafts: (16, 16),
1509                    section: "9.11",
1510                    sentence: UPDATE_WRONG_16,
1511                    code_name: Some("PROTOCOL_VIOLATION"),
1512                },
1513                RuleCitation {
1514                    drafts: (19, 20),
1515                    section: "10.9",
1516                    sentence: UPDATE_WRONG_19,
1517                    code_name: Some("PROTOCOL_VIOLATION"),
1518                },
1519                RuleCitation {
1520                    drafts: (21, 22),
1521                    section: "9.5",
1522                    sentence: UPDATE_WRONG_19,
1523                    code_name: Some("PROTOCOL_VIOLATION"),
1524                },
1525                RuleCitation {
1526                    drafts: (23, 23),
1527                    section: "9.5",
1528                    sentence: UPDATE_WRONG_23,
1529                    code_name: Some("UNEXPECTED_MESSAGE"),
1530                },
1531            ],
1532            Self::TooManyRequestUpdates => &[
1533                RuleCitation {
1534                    drafts: (19, 20),
1535                    section: "10.3.1.7",
1536                    sentence: TOO_MANY_UPDATES,
1537                    code_name: Some("TOO_MANY_REQUEST_UPDATES"),
1538                },
1539                RuleCitation {
1540                    drafts: (21, 23),
1541                    section: "9.1.7",
1542                    sentence: TOO_MANY_UPDATES,
1543                    code_name: Some("TOO_MANY_REQUEST_UPDATES"),
1544                },
1545            ],
1546            Self::TrackPropertiesOnNonTrackStatus => &[
1547                RuleCitation {
1548                    drafts: (18, 20),
1549                    section: "10.5",
1550                    sentence: TRACK_PROPERTIES,
1551                    code_name: Some("PROTOCOL_VIOLATION"),
1552                },
1553                RuleCitation {
1554                    drafts: (21, 22),
1555                    section: "9.3",
1556                    sentence: TRACK_PROPERTIES,
1557                    code_name: Some("PROTOCOL_VIOLATION"),
1558                },
1559                RuleCitation {
1560                    drafts: (23, 23),
1561                    section: "9.3",
1562                    sentence: TRACK_PROPERTIES_23,
1563                    code_name: Some("INVALID_FIELD"),
1564                },
1565            ],
1566            Self::StateNotifyOnTheWrongRequest => &[
1567                RuleCitation {
1568                    drafts: (20, 20),
1569                    section: "10.10",
1570                    sentence: STATE_NOTIFY,
1571                    code_name: Some("PROTOCOL_VIOLATION"),
1572                },
1573                RuleCitation {
1574                    drafts: (21, 22),
1575                    section: "9.10",
1576                    sentence: STATE_NOTIFY,
1577                    code_name: Some("PROTOCOL_VIOLATION"),
1578                },
1579                RuleCitation {
1580                    drafts: (23, 23),
1581                    section: "9.10",
1582                    sentence: STATE_NOTIFY_23,
1583                    code_name: Some("UNEXPECTED_MESSAGE"),
1584                },
1585            ],
1586            Self::SubscribeAfterAnnounceCancel => &[RuleCitation {
1587                drafts: (7, 7),
1588                section: "6.11",
1589                sentence: SUBSCRIBE_AFTER_CANCEL,
1590                code_name: Some("Protocol Violation"),
1591            }],
1592            // The eight with no consequence on any draft, and the one with no
1593            // sentence on any draft. See this function's own doc for which is
1594            // which and why they are written out rather than swallowed by a
1595            // wildcard.
1596            Self::PayloadOnStatusDatagram
1597            | Self::MessageOnTheWrongStream
1598            | Self::ResponseNamesAnotherRequest
1599            | Self::SetupParameterValue
1600            | Self::ObjectPastFinalObject
1601            | Self::UnrequestedFillStream
1602            | Self::TrackStatusIsNotASubscription
1603            | Self::MessageNamesAnUnknownRequest
1604            | Self::NamespacePrefixOverlap
1605            | Self::ObjectExistenceContradicted => &[],
1606        }
1607    }
1608
1609    /// This rule as `draft` states it, or `None` where that draft does not.
1610    ///
1611    /// `None` has two readings and the caller does not need to tell them apart:
1612    /// the draft may not state the rule at all, or it may state it in words
1613    /// nobody has yet checked. Both mean the same thing downstream — there is
1614    /// no sentence to publish, so there is no accusation to make — and that is
1615    /// the whole of why this returns an `Option` rather than falling back on a
1616    /// neighbouring draft's wording. A fallback publishes a sentence the
1617    /// negotiated draft does not contain, which is the defect this table exists
1618    /// to prevent.
1619    #[must_use]
1620    pub fn citation(self, draft: u8) -> Option<&'static RuleCitation> {
1621        self.citations().iter().find(|c| c.covers(draft))
1622    }
1623}
1624
1625/// A rule a peer broke that the negotiated draft's **decoder** did refuse,
1626/// named once across the drafts that state it.
1627///
1628/// # Why this is a second enum and not more variants of the first
1629///
1630/// [`AboveCodecRule`] is defined by what it is *not*: a rule stated about a
1631/// frame that reads perfectly well, which no decoder could refuse and which the
1632/// endpoint has to catch by comparing decoded fields against state it has been
1633/// keeping. Every rule here is the opposite. The frame did not read — a length
1634/// past its maximum, a discriminator the draft does not assign, a delta that
1635/// carries a number past the end of the varint space — and `CodecError` is what
1636/// says so. Folding the two into one enum would put *the decoder refused this*
1637/// and *the decoder could not have seen this* under one name, which is the
1638/// distinction [`crate::dispatch::ErrorCause`] exists to keep.
1639///
1640/// # Why it lives in this file rather than beside `CodecError`
1641///
1642/// Because of what reads it. `scripts/check-drafts.py` rule 8 walks **this
1643/// file** and holds every [`RuleCitation`] in it against the rendered
1644/// drafts: that the sentence is in every draft of the run, that it sits in the
1645/// section the row names, and that the code name is one the sentence itself
1646/// uses. A citation the gate cannot see is not a checked citation, and the
1647/// standard for adding one is deliberately steep: a sentence held against the
1648/// draft that will be cited, on each draft in the range claimed rather than on
1649/// a sample of it.
1650///
1651/// `moqtap-codec` is the other candidate and is the wrong one twice over: it is
1652/// where the *errors* live rather than where the drafts' answers to them do,
1653/// and nothing in `just drafts` reads a table there either.
1654///
1655/// # Where the quoted sentences are, and are not
1656///
1657/// In the constants below, one per wording, which is the only place the gate
1658/// looks. The variant docs here describe rather than quote, and use backticks
1659/// where they name a fragment of a draft's text. That is a departure from
1660/// [`AboveCodecRule`] above, and the reason is this group's ranges: nearly
1661/// every rule here says something different on the oldest drafts in its range
1662/// than on the newest, so a doc that quoted one wording beside a sentence
1663/// naming ten drafts would be claiming it for all ten. The checker has a rule
1664/// for exactly that and it would be right to fire.
1665///
1666/// # What is in this table
1667///
1668/// These are the rules a consumer's catalogue names as a list and does not
1669/// carry itself: a `rule_for` matching `CodecError` exhaustively has no
1670/// wildcard arm to file them under. Every one of them is a relay breaking a
1671/// rule the negotiated draft answers with a session close, and without a
1672/// citation here there is nothing to publish it as.
1673///
1674/// # Why the two Type-value rules are two
1675///
1676/// [`CodecRule::InvalidStreamTypeValue`] and
1677/// [`CodecRule::InvalidDatagramTypeValue`] are one condition as a reader thinks
1678/// of it — a Type inside its form holding a combination the draft rules out —
1679/// and two rules here, because drafts 16 through 23 state them as **two**
1680/// sentences in two sections, one about a datagram's Type and one about a
1681/// subgroup stream header's.
1682///
1683/// A [`RuleCitation`] is one sentence per draft: [`Self::citation`] takes the
1684/// first run that covers a draft and
1685/// `every_run_is_ordered_contiguous_and_in_range` forbids two runs claiming
1686/// one. So a single rule spanning both sentences could publish only one of
1687/// them, and would be right on half the frames it published and wrong on the
1688/// other half with nothing in the row to say which.
1689///
1690/// Splitting the rule here is only half of it, and the half that does not work
1691/// alone: the decoder has to hand over a value that says which sentence was
1692/// broken. `CodecError::InvalidStreamTypeValue` and
1693/// `CodecError::InvalidDatagramTypeValue` are that value. Deriving the
1694/// namespace from the raw number instead would be this build's reading rather
1695/// than the decoder's, because the two Type spaces overlap — which is the
1696/// reasoning `CodecError::InvalidField` already gets, arrived at from a
1697/// different direction.
1698///
1699/// # Two drafts where a close exists and no citation does
1700///
1701/// This table is not the inverse of `codec_session_error_code`, and two rows
1702/// are where the two come apart. Draft-20 answers both
1703/// [`Self::InvalidFilterType`] and [`Self::InvalidFetchType`] with
1704/// PROTOCOL_VIOLATION, and draft-20 states neither rule: it replaced the
1705/// subscription filter with a LOCATION_FILTER parameter whose optional fields
1706/// are found by length and carry no type discriminator, and it deleted the
1707/// Fetch Type field, both variant structures and the registry together.
1708/// Draft-21 is draft-20 in both respects. Draft-22 is not, for the first of
1709/// them: its `LOCATION_FILTER` opens with a `Location Filter Type`, an
1710/// unassigned one is a PROTOCOL_VIOLATION in its own words, and this table
1711/// cites it there.
1712///
1713/// Neither arm is reachable there. `moqtap-codec`'s draft-20 message reader
1714/// raises neither error and has nothing to raise it from, so the two closes are
1715/// inert rather than wrong, and both are kept for the reason draft-20's own
1716/// connection already gives: `CodecError` is shared across drafts and
1717/// is not `#[non_exhaustive]`, so every variant has to be answered on every
1718/// draft.
1719///
1720/// What the missing citation buys is that the inertness stops being
1721/// load-bearing. If a draft-20 decoder ever did raise one, a consumer would
1722/// find no sentence to publish and would name nobody — which is the right
1723/// answer for a rule draft-20 does not state, and it does not depend on an
1724/// unreachable arm staying unreachable.
1725///
1726/// Deliberately **not** `#[non_exhaustive]`, for the reason [`AboveCodecRule`]
1727/// is not: a consumer deciding what it will name a relay for should find out
1728/// about a new rule by failing to build.
1729#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1730pub enum CodecRule {
1731    // ── The four maxima ────────────────────────────────────────
1732    /// A Full Track Name longer than the drafts' maximum.
1733    ///
1734    /// Drafts 11 through 20, Section 2.4.1 throughout — the section never moves
1735    /// and the sentence changes three times, which is the opposite of the usual
1736    /// shape and why this rule has four runs where most have one wording per
1737    /// section.
1738    ///
1739    /// The maximum and its consequence are quoted together because the
1740    /// consequence alone is not evidence: through draft-15 it refers back to
1741    /// `exceeding this length`, and the length is in the sentence before it.
1742    ///
1743    /// One nuance the citation carries and this build's error message does not.
1744    /// On drafts 11 through 15 the sentence bounds the **Full Track Name**
1745    /// alone; draft-16 widens it to a Track Namespace or a Full Track Name.
1746    /// `CodecError::TrackNameTooLong` renders as *track namespace or full track
1747    /// name exceeds …* on all ten, which is the drafts' own scope only from 16
1748    /// on. A namespace over the maximum beside a short name is covered by no
1749    /// sentence on the older five, and their rows quote what those drafts do
1750    /// say rather than what this build measures.
1751    ///
1752    /// Drafts 07 through 10 state no maximum at all.
1753    TrackNameTooLong,
1754    /// A Reason Phrase longer than the drafts' maximum.
1755    ///
1756    /// Drafts 11 through 20. **The sentence has no full stop in any of them**:
1757    /// it runs straight into the next definition item, so every quotation here
1758    /// ends at the code name. Supplying the full stop a transcriber would want
1759    /// is exactly the truncation `check-drafts.py` rule 7 exists to catch, and
1760    /// it would file the row under a sentence no draft has.
1761    ///
1762    /// Four runs. The section moves twice — draft-14 inserts Section 1.3 Stream
1763    /// Management Terms and draft-17 inserts Section 1.4.1 Variable-Length
1764    /// Integers, pushing this down each time — and the wording changes twice,
1765    /// once for the code rename and once where the maximum stops being a
1766    /// `maximum length of 1024 bytes` and becomes a `maximum value of 1024
1767    /// bytes`.
1768    ReasonPhraseTooLong,
1769    /// A GOAWAY New Session URI longer than the drafts' maximum.
1770    ///
1771    /// Drafts 11 through 20, in the GOAWAY message's own section, which moves
1772    /// three times across the range.
1773    ///
1774    /// Drafts 11 through 16 spell it `The maxmimum length of the New Session
1775    /// URI is 8,192 bytes.` The typo is the drafts' and is reproduced in the
1776    /// constant, because the gate compares against the rendering: a sentence
1777    /// tidied up is a sentence no draft has. Draft-17 fixes it, which is one of
1778    /// the two wording breaks in this rule's four runs.
1779    GoAwayUriTooLong,
1780    /// A Key-Value-Pair whose value is longer than the drafts' maximum.
1781    ///
1782    /// Drafts 11 through 20, in the Key-Value-Pair structure's own section —
1783    /// 1.3.2 through draft-13, 1.4.2 on 14 through 16, 1.4.3 from draft-17,
1784    /// which is the same pair of insertions that moves the Reason Phrase rule.
1785    ///
1786    /// The sub-variant and not the arm. `CodecError::Kvp` also carries
1787    /// `MissingLength`, `UnexpectedEnd` and `VarInt`, which report how the bytes
1788    /// ran out rather than a rule an endpoint states, and all the drafts
1789    /// answer all three with no close. Only `ValueTooLong` reaches this rule,
1790    /// which is why the probe that consumes it matches the sub-variant.
1791    KvpValueTooLong,
1792
1793    // ── The key-value pair rules ───────────────────────────────
1794    /// A key-value pair whose value is not the serialization its own Type
1795    /// defines.
1796    ///
1797    /// Drafts 11 through 20, and the one rule in this table whose code is
1798    /// KEY_VALUE_FORMATTING_ERROR on every draft that states it rather than
1799    /// PROTOCOL_VIOLATION.
1800    ///
1801    /// Four runs for three reasons at once. Drafts 11 through 13 spell the code
1802    /// in prose inside quotation marks and drafts 14 and later in capitals;
1803    /// draft-16 changes the verb from terminate to close; and the section moves
1804    /// twice under both. Only the first of those three is visible in the code
1805    /// name a row publishes, which is the argument for publishing the sentence
1806    /// beside it.
1807    KeyValueFormatting,
1808    /// A control message carrying a Message Parameter whose type its draft does
1809    /// not define.
1810    ///
1811    /// Drafts 16 through 20, and **the rule whose answer reverses** rather than
1812    /// arrives. Drafts 07 through 10 say a receiver ignores an unrecognized
1813    /// parameter, and drafts 11 through 15 add `Receivers MUST allow duplicates
1814    /// of unknown parameters`, which presumes unknown parameters arrive and are
1815    /// carried. Raising this on any of those nine would close a session over an
1816    /// extension those drafts leave room for, so the decoder does not, and the
1817    /// run starts where the sentence does.
1818    ///
1819    /// Draft-16 is a run of its own for one word: it has the parameter
1820    /// negotiated via `Setup Parameters` where drafts 17 and later say `Setup
1821    /// Options`, which is the rename draft-17 made. Publishing the later
1822    /// wording for draft-16 would be quoting a sentence draft-16 has not got —
1823    /// the exact defect this table was built to remove, one draft wide.
1824    ///
1825    /// One namespace only. Every draft in range says a receiver ignores an
1826    /// unrecognised Setup Option, so an unknown type in a SETUP is carried and
1827    /// this is never raised for one.
1828    UnknownMessageParameter,
1829    /// A Message Parameter appearing in a message type its own definition does
1830    /// not name.
1831    ///
1832    /// Drafts 17 through 20. The other reversal, and a wider one: drafts 07
1833    /// through 16 end the same sentence `it MUST be ignored`.
1834    ///
1835    /// The sentence says close the **connection**, not close the session, on
1836    /// all four drafts. Reproduced as it stands. The drafts use the two words
1837    /// interchangeably here, and correcting one to the other would be this file
1838    /// speaking inside a draft's own quotation marks — which is a defect this
1839    /// table has already been found holding five times.
1840    ParameterOutOfScope,
1841    /// A parameter whose value is not the shape its own type implies.
1842    ///
1843    /// **Drafts 07 through 10 alone**, which makes it the only rule in either
1844    /// table that exists on the oldest four drafts and nowhere else. The
1845    /// sentence goes with the Parameter framing it describes, and drafts 11 and
1846    /// later replaced that framing with Key-Value-Pairs — where the
1847    /// neighbouring rule is [`Self::KeyValueFormatting`], under a different
1848    /// code.
1849    ///
1850    /// It is also the only rule here answered with neither PROTOCOL_VIOLATION
1851    /// nor KEY_VALUE_FORMATTING_ERROR: each of those four drafts assigns
1852    /// Parameter Length Mismatch a number of its own in the session termination
1853    /// registry, and the sentence names it.
1854    ParameterLengthMismatch,
1855
1856    // ── The discriminators a reader cannot get past ────────────
1857    /// A subscription filter naming a Filter Type the draft does not assign.
1858    ///
1859    /// Drafts 14 through 19 and draft-22, three runs. Two of them because
1860    /// **draft-14's sentence carries the draft's own missing word** — it has an
1861    /// endpoint `MUST be close the session`. Reproduced rather than corrected,
1862    /// for the reason [`RuleCitation::sentence`] gives. The third because
1863    /// draft-22's type is a different field: the `Location Filter Type` that
1864    /// opens a `LOCATION_FILTER` parameter, closed in a sentence of its own
1865    /// that names no endpoint at all.
1866    ///
1867    /// Drafts 07 through 13 state the rule and no consequence: a filter type
1868    /// other than the assigned ones `MUST be treated as error`, which names no
1869    /// code and no close. So the same value in the same place is a refused
1870    /// message on the first seven drafts and a dead session on the next six,
1871    /// and only the per-draft close table can tell them apart.
1872    ///
1873    /// Drafts 20 and 21 state it nowhere: their `LOCATION_FILTER` has no type
1874    /// field. See this enum's own doc for why a close remains in their tables
1875    /// and why the missing citation is what makes that safe.
1876    InvalidFilterType,
1877    /// A FETCH naming a Fetch Type the draft does not assign.
1878    ///
1879    /// The sentence next to the Filter Type one, moving the same way and
1880    /// carrying the same draft-14 missing word. Drafts 14 through 19; drafts 08
1881    /// through 13 say a Fetch Type outside the set `MUST be treated as an
1882    /// error` and state no consequence, and draft-07 has a FETCH with no Fetch
1883    /// Type field in it.
1884    ///
1885    /// Draft-20 deleted the field, both variant structures and the registry
1886    /// together, so it has nothing to state.
1887    ///
1888    /// Four runs for two wordings: the section moves three times across the
1889    /// range while the sentence stands still after draft-14.
1890    InvalidFetchType,
1891    /// A subscription filter parameter whose value is not a filter.
1892    ///
1893    /// Drafts 15 through 23. Its **code changes** within that range rather than
1894    /// only being spelled differently. Drafts 15 and 16 state it of this
1895    /// parameter directly and answer PROTOCOL_VIOLATION; drafts 17 through 22
1896    /// drop that sentence and leave the general key-value rule, which answers
1897    /// KEY_VALUE_FORMATTING_ERROR. A filter three bytes long inside a four-byte
1898    /// parameter ends a draft-16 session with one code and a draft-17 session
1899    /// with the other.
1900    ///
1901    /// Draft-23 raises it for a LOCATION_FILTER (draft-23 Section 9.19.9) or
1902    /// FILL_PARAMETERS (draft-23 Section 9.19.15) value whose fields run past
1903    /// the end of the message. Neither carries a length of its own, so the body
1904    /// is shorter than the message's fields, and draft-23 Section 9 answers
1905    /// that: "If the length does not match the length of the Message Body, the
1906    /// receiver MUST close the session with MALFORMED_MESSAGE."
1907    ///
1908    /// The later run cites the same sentence [`Self::KeyValueFormatting`] does,
1909    /// which is what a shared constant is for. The reading that a malformed
1910    /// filter is a Value that does not match the serialization its Type defines
1911    /// is this build's, and it is the same reading `codec_session_error_code`
1912    /// already makes on those drafts. Stated here rather than left to be
1913    /// noticed: the sentence is the drafts', and that this malformation is its
1914    /// case is a reading of it.
1915    ///
1916    /// A Filter Type outside the assigned set is not this rule. That is
1917    /// [`Self::InvalidFilterType`], whose own section names its own code.
1918    SubscriptionFilterMalformed,
1919    /// An AbsoluteRange filter whose End Group Delta carries the last Group ID
1920    /// past the end of the varint space.
1921    ///
1922    /// Drafts 18 through 23. Draft-17 introduced the delta and states the
1923    /// arithmetic with no consequence for overflowing it — that draft contains
1924    /// exactly one mention of the bound in the whole document and it is the
1925    /// Delta Type rule, not this one — so there an overflowing filter is a
1926    /// decode failure and nothing more.
1927    ///
1928    /// The draft-18 and draft-19 run keeps the clause before the consequence,
1929    /// because *the resulting Group ID* has no antecedent without it. The two
1930    /// are contiguous inside one paragraph, so it is a quotation and not a
1931    /// splice. Draft-20 rewrote the pair as one self-contained sentence naming
1932    /// its own operands, which is the second run; drafts 21 and 22 keep that
1933    /// sentence and state it in the LOCATION_FILTER section (9.20.10 and 9.20.9
1934    /// respectively). Draft-23 Section 9.19.9 keeps the condition and answers
1935    /// it with DELTA_ENCODING_OVERFLOW instead of PROTOCOL_VIOLATION.
1936    FilterEndGroupOverflow,
1937
1938    // ── The data plane ─────────────────────────────────────────
1939    /// A delta-encoded Object ID that would exceed the varint space once the
1940    /// delta is added to the previous Object ID on the same stream.
1941    ///
1942    /// Drafts 18 through 20 in Section 11.4.2 and drafts 21 and 22 in Section
1943    /// 11.3.1, two runs: the wording does not change and the section number
1944    /// does. Draft-23 Section 11.3.1 is a third run, answering with
1945    /// DELTA_ENCODING_OVERFLOW instead of PROTOCOL_VIOLATION.
1946    ///
1947    /// Drafts 14 through 17 carry the identical arithmetic in Section 10.4.2
1948    /// and state no consequence for overflowing it — all four of them, not
1949    /// draft-17 alone — and drafts 07 through 13 have no Object ID Delta to
1950    /// overflow.
1951    ///
1952    /// The three sentences are quoted unbroken, and no ellipsis may stand
1953    /// between the first and the third: eliding there splices across the
1954    /// sentence about the first Object in the Subgroup stream and makes a
1955    /// sentence none of the three drafts has.
1956    ObjectIdOverflow,
1957    /// A subgroup stream header's Type holding a combination its draft names as
1958    /// invalid.
1959    ///
1960    /// Drafts 16 through 23, five runs: the section moves twice and the wording
1961    /// changes twice, and the moves and the changes do not coincide:
1962    /// draft-16 Section 10.4.2 and draft-17 Section 10.4.2;
1963    /// draft-18 Section 11.4.2, draft-19 Section 11.4.2 and
1964    /// draft-20 Section 11.4.2; and draft-21 Section 11.3.1,
1965    /// draft-22 Section 11.3.1 and draft-23 Section 11.3.1. Drafts 16 through
1966    /// 19 enumerate the code points after the sentence and drafts 20 through
1967    /// 23 state the bit pattern alone.
1968    /// Draft-23's sentence answers with INVALID_STREAM_HEADER instead of
1969    /// PROTOCOL_VIOLATION.
1970    ///
1971    /// The sentence is quoted as far as the colon it ends on, because what
1972    /// follows it is a bulleted list of values rather than more sentence. That
1973    /// is the whole quotation and not an elision, so no ellipsis stands in for
1974    /// the list.
1975    ///
1976    /// Paired with [`CodecRule::InvalidDatagramTypeValue`], which is the same
1977    /// rule stated for the other Type space in a different section of the same
1978    /// drafts. They are two rules here because they are two sentences there,
1979    /// and because a consumer holding one refusal has to be able to publish the
1980    /// sentence it actually broke.
1981    InvalidStreamTypeValue,
1982    /// A datagram's Type holding a combination its draft names as invalid.
1983    ///
1984    /// Drafts 16 through 23, five runs, cut where
1985    /// [`CodecRule::InvalidStreamTypeValue`]'s are cut and at different
1986    /// numbers: draft-16 Section 10.3.1 and draft-17 Section 10.3.1;
1987    /// draft-18 Section 11.3.1, draft-19 Section 11.3.1 and
1988    /// draft-20 Section 11.3.1; and draft-21 Section 11.2.1,
1989    /// draft-22 Section 11.2.1 and draft-23 Section 11.2.1. Draft-23's
1990    /// sentence answers with INVALID_STREAM_HEADER.
1991    ///
1992    /// Draft-21 is why the two rules cannot share a run. It splits the section
1993    /// that held both, so the datagram sentence lands at 11.2.1 and the stream
1994    /// sentence at 11.3.1 — the one number that had been the datagram's on the
1995    /// three drafts before it.
1996    InvalidDatagramTypeValue,
1997    /// An end-of-Track Object stating an Object ID other than zero.
1998    ///
1999    /// **Drafts 08 through 10 alone**, and the one rule in this table whose
2000    /// sentence names no code: it ends with the receiver having to terminate
2001    /// the session and nothing more, so [`RuleCitation::code_name`] is `None`
2002    /// and a row publishes the number without a name for it. The number comes
2003    /// from `codec_session_error_code`, which answers PROTOCOL_VIOLATION — what
2004    /// those drafts answer every rule they close over without naming a code.
2005    /// The name is absent because the draft does not supply it, which is not
2006    /// the same thing as the draft stating no consequence.
2007    ///
2008    /// There is a look-alike sentence in the same subsection of the same three
2009    /// drafts, about Object Status 0x4 rather than 0x5, and the two differ by
2010    /// four words in two places: the neighbour has a Group ID *less than* any
2011    /// other and an Object ID *less than or equal to the largest in the group*,
2012    /// where this one has a Group ID *less than or equal to* any other and an
2013    /// Object ID *other than zero*. The neighbour is
2014    /// [`AboveCodecRule::EndOfTrackOutOfPlace`]'s sentence and is already cited
2015    /// above; citing it here would have put two rules under one wording and
2016    /// looked right.
2017    ///
2018    /// Draft-07 assigns no 0x5. Drafts 11 and later merge end of Track into
2019    /// 0x4 and permit either shape, so there is no longer a rule to break.
2020    EndOfTrackObjectId,
2021    /// An Object with Object Status 'Object Does Not Exist' carrying extension
2022    /// headers.
2023    ///
2024    /// Drafts 11 through 14, three runs: the section moves twice and draft-14
2025    /// respells the code. The rule names one status and no others, so
2026    /// extensions beside End of Group or End of Track are legal on those four
2027    /// drafts and are not this.
2028    ///
2029    /// Drafts 15 and later replaced the narrow form with the general one —
2030    /// extensions, later properties, permitted only beside Normal — which is a
2031    /// different rule with a different subject and reaches a consumer as
2032    /// [`AboveCodecRule::PropertiesOnNonNormalStatus`]. Drafts 07 through 10
2033    /// state neither form.
2034    ExtensionsOnNonExistentObject,
2035
2036    // ── And one that lived for exactly one draft ───────────────
2037    /// A request message's Required Request ID Delta naming a dependency below
2038    /// zero.
2039    ///
2040    /// **Draft-17 alone.** Draft-18 removed the field and mentions it only in
2041    /// its change log, so no other draft can state the rule.
2042    ///
2043    /// The only rule in this table answered with INVALID_REQUIRED_REQUEST_ID,
2044    /// and the only sentence in it carrying a character outside ASCII: the
2045    /// draft writes the comparison with a multiplication sign, and the gate
2046    /// compares against the rendering rather than against what a transcriber
2047    /// would have typed.
2048    InvalidRequiredRequestIdDelta,
2049}
2050
2051// The sentences these rules are stated in, one constant per wording, on the
2052// same terms as the block above: transcribed from the renderings and not
2053// tidied. Three of them carry something a transcriber would want to correct and
2054// must not -- drafts 11 through 16 render the GOAWAY URI maximum with
2055// `maxmimum`, and draft-14 renders both the Filter Type and the Fetch Type
2056// sentences with `MUST be close the session`. `check-drafts.py` rule 8 compares
2057// each of these against the rendering it came from, so a correction of the
2058// drafts' spelling is a red gate.
2059//
2060// Two more shapes worth naming before a reader meets them. The Reason Phrase
2061// sentences end without a full stop, because the rendering runs them into the
2062// next definition item and a supplied mark is a sentence no draft has. And
2063// `KVP_FORMATTING_16` is named from one rule and carried by two: the general
2064// key-value sentence is what drafts 17 through 20 leave a malformed
2065// subscription filter to, having dropped the filter's own.
2066const TRACK_NAME_MAX_11: &str = "The maximum total length of a Full Track Name is 4,096 bytes, \
2067                                 computed as the sum of the lengths of each Track Namespace tuple \
2068                                 field and the Track Name length field. If an endpoint receives a \
2069                                 Full Track Name exceeding this length, it MUST close the session \
2070                                 with a Protocol Violation.";
2071const TRACK_NAME_MAX_14: &str = "The maximum total length of a Full Track Name is 4,096 bytes, \
2072                                 computed as the sum of the lengths of each Track Namespace tuple \
2073                                 field and the Track Name length field. If an endpoint receives a \
2074                                 Full Track Name exceeding this length, it MUST close the session \
2075                                 with a PROTOCOL_VIOLATION.";
2076const TRACK_NAME_MAX_15: &str =
2077    "The maximum total length of a Full Track Name is 4,096 bytes. The \
2078                                 length of a Full Track Name is computed as the sum of the Track \
2079                                 Namespace Field Length fields and the Track Name Length field. If \
2080                                 an endpoint receives a Full Track Name exceeding this length, it \
2081                                 MUST close the session with a PROTOCOL_VIOLATION.";
2082const TRACK_NAME_MAX_16: &str =
2083    "The maximum total length of a Full Track Name is 4,096 bytes. The \
2084                                 length of a Full Track Name is computed as the sum of the Track \
2085                                 Namespace Field Length fields and the Track Name Length field. \
2086                                 The length of a Track Namespace is the sum of the Track Namespace \
2087                                 Field Length fields. If an endpoint receives a Track Namespace or \
2088                                 a Full Track Name exceeding 4,096 bytes, it MUST close the \
2089                                 session with a PROTOCOL_VIOLATION.";
2090const REASON_PHRASE_MAX_11: &str = "The reason phrase length has a maximum length of 1024 bytes. \
2091                                    If an endpoint receives a length exceeding the maximum, it \
2092                                    MUST close the session with a Protocol Violation";
2093const REASON_PHRASE_MAX_14: &str = "The reason phrase length has a maximum length of 1024 bytes. \
2094                                    If an endpoint receives a length exceeding the maximum, it \
2095                                    MUST close the session with a PROTOCOL_VIOLATION";
2096const REASON_PHRASE_MAX_15: &str =
2097    "The reason phrase length has a maximum value of 1024 bytes. If \
2098                                    an endpoint receives a length exceeding the maximum, it MUST \
2099                                    close the session with a PROTOCOL_VIOLATION";
2100const GOAWAY_URI_MAX_11: &str = "The maxmimum length of the New Session URI is 8,192 bytes. If an \
2101                                 endpoint receives a length exceeding the maximum, it MUST close \
2102                                 the session with a Protocol Violation.";
2103const GOAWAY_URI_MAX_14: &str = "The maxmimum length of the New Session URI is 8,192 bytes. If an \
2104                                 endpoint receives a length exceeding the maximum, it MUST close \
2105                                 the session with a PROTOCOL_VIOLATION.";
2106const GOAWAY_URI_MAX_17: &str = "The maximum length of the New Session URI is 8,192 bytes. If an \
2107                                 endpoint receives a length exceeding the maximum, it MUST close \
2108                                 the session with a PROTOCOL_VIOLATION.";
2109const KVP_VALUE_MAX_11: &str = "The maximum length of a value is 2^16-1 bytes. If an endpoint \
2110                                receives a length larger than the maximum, it MUST close the \
2111                                session with a Protocol Violation.";
2112const KVP_VALUE_MAX_16: &str = "The maximum length of a value is 2^16-1 bytes. If an endpoint \
2113                                receives a length larger than the maximum, it MUST close the \
2114                                session with a PROTOCOL_VIOLATION.";
2115const KVP_FORMATTING_11: &str = "If a receiver understands a Type, and the following Value or \
2116                                 Length/Value does not match the serialization defined by that \
2117                                 Type, the receiver MUST terminate the session with error code \
2118                                 \"Key-Value Formatting Error\".";
2119const KVP_FORMATTING_14: &str = "If a receiver understands a Type, and the following Value or \
2120                                 Length/Value does not match the serialization defined by that \
2121                                 Type, the receiver MUST terminate the session with error code \
2122                                 KEY_VALUE_FORMATTING_ERROR.";
2123const KVP_FORMATTING_16: &str = "If a receiver understands a Type, and the following Value or \
2124                                 Length/Value does not match the serialization defined by that \
2125                                 Type, the receiver MUST close the session with error code \
2126                                 KEY_VALUE_FORMATTING_ERROR.";
2127const UNKNOWN_PARAMETER_16: &str = "All Message Parameters MUST be defined in the negotiated \
2128                                    version of MOQT or negotiated via Setup Parameters. An \
2129                                    endpoint that receives an unknown Message Parameter MUST close \
2130                                    the session with PROTOCOL_VIOLATION.";
2131const UNKNOWN_PARAMETER_17: &str = "All Message Parameters MUST be defined in the negotiated \
2132                                    version of MOQT or negotiated via Setup Options. An endpoint \
2133                                    that receives an unknown Message Parameter MUST close the \
2134                                    session with PROTOCOL_VIOLATION.";
2135const PARAMETER_SCOPE: &str = "Each Message Parameter definition indicates the message types in \
2136                               which it can appear. If it appears in some other type of message, \
2137                               the receiving endpoint MUST close the connection with a \
2138                               PROTOCOL_VIOLATION.";
2139const PARAMETER_SCOPE_22: &str = "Each Message Parameter definition indicates the message types \
2140                                  in which it can appear, and each control message definition \
2141                                  lists the parameters it allows. If a parameter appears in some \
2142                                  other type of message, the receiving endpoint MUST close the \
2143                                  connection with a PROTOCOL_VIOLATION.";
2144const PARAMETER_LENGTH: &str = "If a receiver understands a parameter type, and the parameter \
2145                                length implied by that type does not match the Parameter Length \
2146                                field, the receiver MUST terminate the session with error code \
2147                                'Parameter Length Mismatch'.";
2148const FILTER_TYPE_14: &str =
2149    "An endpoint that receives a filter type other than the above MUST be \
2150                              close the session with PROTOCOL_VIOLATION.";
2151const FILTER_TYPE_15: &str = "An endpoint that receives a filter type other than the above MUST \
2152                              close the session with PROTOCOL_VIOLATION.";
2153const FILTER_TYPE_22: &str = "Any other Location Filter Type is a PROTOCOL_VIOLATION.";
2154const FETCH_TYPE_14: &str = "An endpoint that receives a Fetch Type other than 0x1, 0x2 or 0x3 \
2155                             MUST be close the session with a PROTOCOL_VIOLATION.";
2156const FETCH_TYPE_15: &str = "An endpoint that receives a Fetch Type other than 0x1, 0x2 or 0x3 \
2157                             MUST close the session with a PROTOCOL_VIOLATION.";
2158const FILTER_PARAMETER_15: &str = "If the length of the Subscription Filter does not match the \
2159                                   parameter length, the publisher MUST close the session with \
2160                                   PROTOCOL_VIOLATION.";
2161const END_GROUP_WRAP_18: &str =
2162    "Otherwise, the last Group ID to be delivered will be the Group ID \
2163                                 in Start Location plus the End Group Delta. If the resulting \
2164                                 Group ID would be greater than 2^64 - 1, the endpoint MUST close \
2165                                 the session with a PROTOCOL_VIOLATION.";
2166const END_GROUP_WRAP_20: &str =
2167    "If StartGroup + EndGroupDelta exceeds 2^64 - 1, the endpoint MUST \
2168                                 close the session with a PROTOCOL_VIOLATION.";
2169const OBJECT_ID_WRAP: &str = "The Object ID Delta + 1 is added to the previous Object ID in the \
2170                              Subgroup stream if there was one. The Object ID is the Object ID \
2171                              Delta if it's the first Object in the Subgroup stream. If the \
2172                              resulting Object ID would be greater than 2^64 - 1, the endpoint \
2173                              MUST close the session with a PROTOCOL_VIOLATION.";
2174// The two Type-value rules, four wordings between them. Each draft states both
2175// as a sentence introducing a bulleted list of the values it rules out, and
2176// what is quoted is the sentence: the list is bullets rather than prose, and a
2177// quotation reaching into it would be a quotation of a list.
2178//
2179// The wording changes once, at draft-20, which stopped enumerating the code
2180// points and states the bit pattern alone — so "these Type values" becomes
2181// "these values" and the drafts part into two groups on each rule. A needle
2182// carrying the word Type finds drafts 16 through 19 and silently misses 20 and
2183// 21; the invariant clause is "receives a stream header with any of these".
2184const INVALID_STREAM_TYPE_16: &str = "If an endpoint receives a stream header with any of these \
2185                                      Type values, it MUST close the session with a \
2186                                      PROTOCOL_VIOLATION:";
2187const INVALID_STREAM_TYPE_20: &str = "If an endpoint receives a stream header with any of these \
2188                                      values, it MUST close the session with a \
2189                                      PROTOCOL_VIOLATION:";
2190const INVALID_DATAGRAM_TYPE_16: &str = "If an endpoint receives a datagram with any of these Type \
2191                                        values, it MUST close the session with a \
2192                                        PROTOCOL_VIOLATION:";
2193const INVALID_DATAGRAM_TYPE_20: &str = "If an endpoint receives a datagram with any of these \
2194                                        values, it MUST close the session with a \
2195                                        PROTOCOL_VIOLATION:";
2196const END_OF_TRACK_OBJECT_ID: &str = "An object with this status that has a Group ID less than or \
2197                                      equal to any other Group ID, or an Object ID other than \
2198                                      zero, is a protocol error, and the receiver MUST terminate \
2199                                      the session.";
2200const EXTENSIONS_ON_NONEXISTENT_11: &str = "Any Object may have extension headers except those \
2201                                            with Object Status 'Object Does Not Exist'. If an \
2202                                            endpoint receives a non-existent Object containing \
2203                                            extension headers it MUST close the session with a \
2204                                            Protocol Violation.";
2205const EXTENSIONS_ON_NONEXISTENT_14: &str = "Any Object may have extension headers except those \
2206                                            with Object Status 'Object Does Not Exist'. If an \
2207                                            endpoint receives a non-existent Object containing \
2208                                            extension headers it MUST close the session with a \
2209                                            PROTOCOL_VIOLATION.";
2210const REQUIRED_REQUEST_ID_DELTA: &str = "An endpoint MUST close the session with \
2211                                         INVALID_REQUIRED_REQUEST_ID if it receives a delta where \
2212                                         2 × Required Request ID Delta exceeds the Request ID.";
2213
2214impl CodecRule {
2215    /// Every draft run this rule has a checked citation for, oldest first.
2216    ///
2217    /// Exhaustive with no wildcard arm, for the reason the enum is not
2218    /// `#[non_exhaustive]`: a rule added to this file arrives here as an
2219    /// `E0004` and a decision about which drafts state it.
2220    ///
2221    /// No arm answers with an empty slice, which is the one structural
2222    /// difference from [`AboveCodecRule::citations`]. There, nine rules do, and
2223    /// eight of them are rules no draft answers with a close. Here a rule with
2224    /// nothing to cite would have no reason to exist: the whole set is chosen
2225    /// by reading the per-draft `codec_session_error_code` tables for variants
2226    /// answered `Some` on at least one draft, and a rule stated in no draft is
2227    /// left out rather than written down with an empty slice. One rule is left
2228    /// out on other grounds entirely, and the enum's doc names it and says
2229    /// which.
2230    ///
2231    /// The runs are **not** the drafts that answer with a close, and the enum's
2232    /// doc says which two rows differ and why. A draft with a close and no
2233    /// citation publishes nothing, which is the safe direction; a draft with a
2234    /// citation and no close is a row nothing can reach, and
2235    /// `a_cited_draft_is_a_draft_that_closes` below asserts there are none.
2236    #[must_use]
2237    pub fn citations(self) -> &'static [RuleCitation] {
2238        match self {
2239            Self::TrackNameTooLong => &[
2240                RuleCitation {
2241                    drafts: (11, 13),
2242                    section: "2.4.1",
2243                    sentence: TRACK_NAME_MAX_11,
2244                    code_name: Some("Protocol Violation"),
2245                },
2246                RuleCitation {
2247                    drafts: (14, 14),
2248                    section: "2.4.1",
2249                    sentence: TRACK_NAME_MAX_14,
2250                    code_name: Some("PROTOCOL_VIOLATION"),
2251                },
2252                RuleCitation {
2253                    drafts: (15, 15),
2254                    section: "2.4.1",
2255                    sentence: TRACK_NAME_MAX_15,
2256                    code_name: Some("PROTOCOL_VIOLATION"),
2257                },
2258                RuleCitation {
2259                    drafts: (16, 20),
2260                    section: "2.4.1",
2261                    sentence: TRACK_NAME_MAX_16,
2262                    code_name: Some("PROTOCOL_VIOLATION"),
2263                },
2264                RuleCitation {
2265                    drafts: (21, 22),
2266                    section: "8.7",
2267                    sentence: TRACK_NAME_MAX_16,
2268                    code_name: Some("PROTOCOL_VIOLATION"),
2269                },
2270                RuleCitation {
2271                    drafts: (23, 23),
2272                    section: "8.7",
2273                    sentence: TRACK_NAME_MAX_23,
2274                    code_name: Some("FIELD_LENGTH_EXCEEDED"),
2275                },
2276            ],
2277            Self::ReasonPhraseTooLong => &[
2278                RuleCitation {
2279                    drafts: (11, 13),
2280                    section: "1.3.3",
2281                    sentence: REASON_PHRASE_MAX_11,
2282                    code_name: Some("Protocol Violation"),
2283                },
2284                RuleCitation {
2285                    drafts: (14, 14),
2286                    section: "1.4.3",
2287                    sentence: REASON_PHRASE_MAX_14,
2288                    code_name: Some("PROTOCOL_VIOLATION"),
2289                },
2290                RuleCitation {
2291                    drafts: (15, 16),
2292                    section: "1.4.3",
2293                    sentence: REASON_PHRASE_MAX_15,
2294                    code_name: Some("PROTOCOL_VIOLATION"),
2295                },
2296                RuleCitation {
2297                    drafts: (17, 20),
2298                    section: "1.4.4",
2299                    sentence: REASON_PHRASE_MAX_15,
2300                    code_name: Some("PROTOCOL_VIOLATION"),
2301                },
2302                RuleCitation {
2303                    drafts: (21, 22),
2304                    section: "8.5",
2305                    sentence: REASON_PHRASE_MAX_15,
2306                    code_name: Some("PROTOCOL_VIOLATION"),
2307                },
2308                RuleCitation {
2309                    drafts: (23, 23),
2310                    section: "8.5",
2311                    sentence: REASON_PHRASE_MAX_23,
2312                    code_name: Some("FIELD_LENGTH_EXCEEDED"),
2313                },
2314            ],
2315            Self::GoAwayUriTooLong => &[
2316                RuleCitation {
2317                    drafts: (11, 13),
2318                    section: "8.4",
2319                    sentence: GOAWAY_URI_MAX_11,
2320                    code_name: Some("Protocol Violation"),
2321                },
2322                RuleCitation {
2323                    drafts: (14, 16),
2324                    section: "9.4",
2325                    sentence: GOAWAY_URI_MAX_14,
2326                    code_name: Some("PROTOCOL_VIOLATION"),
2327                },
2328                RuleCitation {
2329                    drafts: (17, 17),
2330                    section: "9.5",
2331                    sentence: GOAWAY_URI_MAX_17,
2332                    code_name: Some("PROTOCOL_VIOLATION"),
2333                },
2334                RuleCitation {
2335                    drafts: (18, 20),
2336                    section: "10.4",
2337                    sentence: GOAWAY_URI_MAX_17,
2338                    code_name: Some("PROTOCOL_VIOLATION"),
2339                },
2340                RuleCitation {
2341                    drafts: (21, 22),
2342                    section: "9.2",
2343                    sentence: GOAWAY_URI_MAX_17,
2344                    code_name: Some("PROTOCOL_VIOLATION"),
2345                },
2346                RuleCitation {
2347                    drafts: (23, 23),
2348                    section: "9.2",
2349                    sentence: GOAWAY_URI_MAX_23,
2350                    code_name: Some("FIELD_LENGTH_EXCEEDED"),
2351                },
2352            ],
2353            Self::KvpValueTooLong => &[
2354                RuleCitation {
2355                    drafts: (11, 13),
2356                    section: "1.3.2",
2357                    sentence: KVP_VALUE_MAX_11,
2358                    code_name: Some("Protocol Violation"),
2359                },
2360                RuleCitation {
2361                    drafts: (14, 15),
2362                    section: "1.4.2",
2363                    sentence: KVP_VALUE_MAX_11,
2364                    code_name: Some("Protocol Violation"),
2365                },
2366                RuleCitation {
2367                    drafts: (16, 16),
2368                    section: "1.4.2",
2369                    sentence: KVP_VALUE_MAX_16,
2370                    code_name: Some("PROTOCOL_VIOLATION"),
2371                },
2372                RuleCitation {
2373                    drafts: (17, 20),
2374                    section: "1.4.3",
2375                    sentence: KVP_VALUE_MAX_16,
2376                    code_name: Some("PROTOCOL_VIOLATION"),
2377                },
2378                RuleCitation {
2379                    drafts: (21, 22),
2380                    section: "8.3",
2381                    sentence: KVP_VALUE_MAX_16,
2382                    code_name: Some("PROTOCOL_VIOLATION"),
2383                },
2384                RuleCitation {
2385                    drafts: (23, 23),
2386                    section: "8.3",
2387                    sentence: KVP_VALUE_MAX_23,
2388                    code_name: Some("FIELD_LENGTH_EXCEEDED"),
2389                },
2390            ],
2391            Self::KeyValueFormatting => &[
2392                RuleCitation {
2393                    drafts: (11, 13),
2394                    section: "1.3.2",
2395                    sentence: KVP_FORMATTING_11,
2396                    code_name: Some("Key-Value Formatting Error"),
2397                },
2398                RuleCitation {
2399                    drafts: (14, 15),
2400                    section: "1.4.2",
2401                    sentence: KVP_FORMATTING_14,
2402                    code_name: Some("KEY_VALUE_FORMATTING_ERROR"),
2403                },
2404                RuleCitation {
2405                    drafts: (16, 16),
2406                    section: "1.4.2",
2407                    sentence: KVP_FORMATTING_16,
2408                    code_name: Some("KEY_VALUE_FORMATTING_ERROR"),
2409                },
2410                RuleCitation {
2411                    drafts: (17, 20),
2412                    section: "1.4.3",
2413                    sentence: KVP_FORMATTING_16,
2414                    code_name: Some("KEY_VALUE_FORMATTING_ERROR"),
2415                },
2416                RuleCitation {
2417                    drafts: (21, 22),
2418                    section: "8.3",
2419                    sentence: KVP_FORMATTING_16,
2420                    code_name: Some("KEY_VALUE_FORMATTING_ERROR"),
2421                },
2422                RuleCitation {
2423                    drafts: (23, 23),
2424                    section: "8.3",
2425                    sentence: KVP_FORMATTING_23,
2426                    code_name: Some("MALFORMED_KEY_VALUE"),
2427                },
2428            ],
2429            Self::UnknownMessageParameter => &[
2430                RuleCitation {
2431                    drafts: (16, 16),
2432                    section: "9.2",
2433                    sentence: UNKNOWN_PARAMETER_16,
2434                    code_name: Some("PROTOCOL_VIOLATION"),
2435                },
2436                RuleCitation {
2437                    drafts: (17, 17),
2438                    section: "9.3",
2439                    sentence: UNKNOWN_PARAMETER_17,
2440                    code_name: Some("PROTOCOL_VIOLATION"),
2441                },
2442                RuleCitation {
2443                    drafts: (18, 20),
2444                    section: "10.2",
2445                    sentence: UNKNOWN_PARAMETER_17,
2446                    code_name: Some("PROTOCOL_VIOLATION"),
2447                },
2448                RuleCitation {
2449                    drafts: (21, 22),
2450                    section: "9.20",
2451                    sentence: UNKNOWN_PARAMETER_17,
2452                    code_name: Some("PROTOCOL_VIOLATION"),
2453                },
2454                RuleCitation {
2455                    drafts: (23, 23),
2456                    section: "9.19",
2457                    sentence: UNKNOWN_PARAMETER_23,
2458                    code_name: Some("INVALID_PARAMETER"),
2459                },
2460            ],
2461            Self::ParameterOutOfScope => &[
2462                RuleCitation {
2463                    drafts: (17, 17),
2464                    section: "9.3.1",
2465                    sentence: PARAMETER_SCOPE,
2466                    code_name: Some("PROTOCOL_VIOLATION"),
2467                },
2468                RuleCitation {
2469                    drafts: (18, 20),
2470                    section: "10.2.1",
2471                    sentence: PARAMETER_SCOPE,
2472                    code_name: Some("PROTOCOL_VIOLATION"),
2473                },
2474                RuleCitation {
2475                    drafts: (21, 21),
2476                    section: "9.20.1",
2477                    sentence: PARAMETER_SCOPE,
2478                    code_name: Some("PROTOCOL_VIOLATION"),
2479                },
2480                RuleCitation {
2481                    drafts: (22, 22),
2482                    section: "9.20.1",
2483                    sentence: PARAMETER_SCOPE_22,
2484                    code_name: Some("PROTOCOL_VIOLATION"),
2485                },
2486                RuleCitation {
2487                    drafts: (23, 23),
2488                    section: "9.19.1",
2489                    sentence: PARAMETER_SCOPE_23,
2490                    code_name: Some("INVALID_PARAMETER"),
2491                },
2492            ],
2493            Self::ParameterLengthMismatch => &[
2494                RuleCitation {
2495                    drafts: (7, 7),
2496                    section: "6.1",
2497                    sentence: PARAMETER_LENGTH,
2498                    code_name: Some("Parameter Length Mismatch"),
2499                },
2500                RuleCitation {
2501                    drafts: (8, 9),
2502                    section: "7.1",
2503                    sentence: PARAMETER_LENGTH,
2504                    code_name: Some("Parameter Length Mismatch"),
2505                },
2506                RuleCitation {
2507                    drafts: (10, 10),
2508                    section: "8.1",
2509                    sentence: PARAMETER_LENGTH,
2510                    code_name: Some("Parameter Length Mismatch"),
2511                },
2512            ],
2513            Self::InvalidFilterType => &[
2514                RuleCitation {
2515                    drafts: (14, 14),
2516                    section: "9.7",
2517                    sentence: FILTER_TYPE_14,
2518                    code_name: Some("PROTOCOL_VIOLATION"),
2519                },
2520                RuleCitation {
2521                    drafts: (15, 19),
2522                    section: "5.1.2",
2523                    sentence: FILTER_TYPE_15,
2524                    code_name: Some("PROTOCOL_VIOLATION"),
2525                },
2526                RuleCitation {
2527                    drafts: (22, 22),
2528                    section: "9.20.9",
2529                    sentence: FILTER_TYPE_22,
2530                    code_name: Some("PROTOCOL_VIOLATION"),
2531                },
2532                RuleCitation {
2533                    drafts: (23, 23),
2534                    section: "9.19.9",
2535                    sentence: FILTER_TYPE_23,
2536                    code_name: Some("INVALID_PARAMETER"),
2537                },
2538            ],
2539            Self::InvalidFetchType => &[
2540                RuleCitation {
2541                    drafts: (14, 14),
2542                    section: "9.16",
2543                    sentence: FETCH_TYPE_14,
2544                    code_name: Some("PROTOCOL_VIOLATION"),
2545                },
2546                RuleCitation {
2547                    drafts: (15, 16),
2548                    section: "9.16",
2549                    sentence: FETCH_TYPE_15,
2550                    code_name: Some("PROTOCOL_VIOLATION"),
2551                },
2552                RuleCitation {
2553                    drafts: (17, 17),
2554                    section: "9.14",
2555                    sentence: FETCH_TYPE_15,
2556                    code_name: Some("PROTOCOL_VIOLATION"),
2557                },
2558                RuleCitation {
2559                    drafts: (18, 19),
2560                    section: "10.12",
2561                    sentence: FETCH_TYPE_15,
2562                    code_name: Some("PROTOCOL_VIOLATION"),
2563                },
2564            ],
2565            Self::SubscriptionFilterMalformed => &[
2566                RuleCitation {
2567                    drafts: (15, 15),
2568                    section: "9.2.1.7",
2569                    sentence: FILTER_PARAMETER_15,
2570                    code_name: Some("PROTOCOL_VIOLATION"),
2571                },
2572                RuleCitation {
2573                    drafts: (16, 16),
2574                    section: "9.2.2.5",
2575                    sentence: FILTER_PARAMETER_15,
2576                    code_name: Some("PROTOCOL_VIOLATION"),
2577                },
2578                RuleCitation {
2579                    drafts: (17, 20),
2580                    section: "1.4.3",
2581                    sentence: KVP_FORMATTING_16,
2582                    code_name: Some("KEY_VALUE_FORMATTING_ERROR"),
2583                },
2584                RuleCitation {
2585                    drafts: (21, 22),
2586                    section: "8.3",
2587                    sentence: KVP_FORMATTING_16,
2588                    code_name: Some("KEY_VALUE_FORMATTING_ERROR"),
2589                },
2590                RuleCitation {
2591                    drafts: (23, 23),
2592                    section: "9",
2593                    sentence: MESSAGE_LENGTH_23,
2594                    code_name: Some("MALFORMED_MESSAGE"),
2595                },
2596            ],
2597            Self::FilterEndGroupOverflow => &[
2598                RuleCitation {
2599                    drafts: (18, 19),
2600                    section: "5.1.2",
2601                    sentence: END_GROUP_WRAP_18,
2602                    code_name: Some("PROTOCOL_VIOLATION"),
2603                },
2604                RuleCitation {
2605                    drafts: (20, 20),
2606                    section: "5.1.2",
2607                    sentence: END_GROUP_WRAP_20,
2608                    code_name: Some("PROTOCOL_VIOLATION"),
2609                },
2610                RuleCitation {
2611                    drafts: (21, 21),
2612                    section: "9.20.10",
2613                    sentence: END_GROUP_WRAP_20,
2614                    code_name: Some("PROTOCOL_VIOLATION"),
2615                },
2616                RuleCitation {
2617                    drafts: (22, 22),
2618                    section: "9.20.9",
2619                    sentence: END_GROUP_WRAP_20,
2620                    code_name: Some("PROTOCOL_VIOLATION"),
2621                },
2622                RuleCitation {
2623                    drafts: (23, 23),
2624                    section: "9.19.9",
2625                    sentence: END_GROUP_WRAP_23,
2626                    code_name: Some("DELTA_ENCODING_OVERFLOW"),
2627                },
2628            ],
2629            Self::ObjectIdOverflow => &[
2630                RuleCitation {
2631                    drafts: (18, 20),
2632                    section: "11.4.2",
2633                    sentence: OBJECT_ID_WRAP,
2634                    code_name: Some("PROTOCOL_VIOLATION"),
2635                },
2636                RuleCitation {
2637                    drafts: (21, 22),
2638                    section: "11.3.1",
2639                    sentence: OBJECT_ID_WRAP,
2640                    code_name: Some("PROTOCOL_VIOLATION"),
2641                },
2642                RuleCitation {
2643                    drafts: (23, 23),
2644                    section: "11.3.1",
2645                    sentence: OBJECT_ID_WRAP_23,
2646                    code_name: Some("DELTA_ENCODING_OVERFLOW"),
2647                },
2648            ],
2649            Self::InvalidStreamTypeValue => &[
2650                RuleCitation {
2651                    drafts: (16, 17),
2652                    section: "10.4.2",
2653                    sentence: INVALID_STREAM_TYPE_16,
2654                    code_name: Some("PROTOCOL_VIOLATION"),
2655                },
2656                RuleCitation {
2657                    drafts: (18, 19),
2658                    section: "11.4.2",
2659                    sentence: INVALID_STREAM_TYPE_16,
2660                    code_name: Some("PROTOCOL_VIOLATION"),
2661                },
2662                RuleCitation {
2663                    drafts: (20, 20),
2664                    section: "11.4.2",
2665                    sentence: INVALID_STREAM_TYPE_20,
2666                    code_name: Some("PROTOCOL_VIOLATION"),
2667                },
2668                RuleCitation {
2669                    drafts: (21, 22),
2670                    section: "11.3.1",
2671                    sentence: INVALID_STREAM_TYPE_20,
2672                    code_name: Some("PROTOCOL_VIOLATION"),
2673                },
2674                RuleCitation {
2675                    drafts: (23, 23),
2676                    section: "11.3.1",
2677                    sentence: INVALID_STREAM_TYPE_23,
2678                    code_name: Some("INVALID_STREAM_HEADER"),
2679                },
2680            ],
2681            Self::InvalidDatagramTypeValue => &[
2682                RuleCitation {
2683                    drafts: (16, 17),
2684                    section: "10.3.1",
2685                    sentence: INVALID_DATAGRAM_TYPE_16,
2686                    code_name: Some("PROTOCOL_VIOLATION"),
2687                },
2688                RuleCitation {
2689                    drafts: (18, 19),
2690                    section: "11.3.1",
2691                    sentence: INVALID_DATAGRAM_TYPE_16,
2692                    code_name: Some("PROTOCOL_VIOLATION"),
2693                },
2694                RuleCitation {
2695                    drafts: (20, 20),
2696                    section: "11.3.1",
2697                    sentence: INVALID_DATAGRAM_TYPE_20,
2698                    code_name: Some("PROTOCOL_VIOLATION"),
2699                },
2700                RuleCitation {
2701                    drafts: (21, 22),
2702                    section: "11.2.1",
2703                    sentence: INVALID_DATAGRAM_TYPE_20,
2704                    code_name: Some("PROTOCOL_VIOLATION"),
2705                },
2706                RuleCitation {
2707                    drafts: (23, 23),
2708                    section: "11.2.1",
2709                    sentence: INVALID_DATAGRAM_TYPE_23,
2710                    code_name: Some("INVALID_STREAM_HEADER"),
2711                },
2712            ],
2713            Self::EndOfTrackObjectId => &[
2714                RuleCitation {
2715                    drafts: (8, 9),
2716                    section: "8.1.1.1",
2717                    sentence: END_OF_TRACK_OBJECT_ID,
2718                    code_name: None,
2719                },
2720                RuleCitation {
2721                    drafts: (10, 10),
2722                    section: "9.1.1.1",
2723                    sentence: END_OF_TRACK_OBJECT_ID,
2724                    code_name: None,
2725                },
2726            ],
2727            Self::ExtensionsOnNonExistentObject => &[
2728                RuleCitation {
2729                    drafts: (11, 11),
2730                    section: "9.1.1.2",
2731                    sentence: EXTENSIONS_ON_NONEXISTENT_11,
2732                    code_name: Some("Protocol Violation"),
2733                },
2734                RuleCitation {
2735                    drafts: (12, 13),
2736                    section: "9.2.1.2",
2737                    sentence: EXTENSIONS_ON_NONEXISTENT_11,
2738                    code_name: Some("Protocol Violation"),
2739                },
2740                RuleCitation {
2741                    drafts: (14, 14),
2742                    section: "10.2.1.2",
2743                    sentence: EXTENSIONS_ON_NONEXISTENT_14,
2744                    code_name: Some("PROTOCOL_VIOLATION"),
2745                },
2746            ],
2747            Self::InvalidRequiredRequestIdDelta => &[RuleCitation {
2748                drafts: (17, 17),
2749                section: "9.2",
2750                sentence: REQUIRED_REQUEST_ID_DELTA,
2751                code_name: Some("INVALID_REQUIRED_REQUEST_ID"),
2752            }],
2753        }
2754    }
2755
2756    /// This rule as `draft` states it, or `None` where that draft does not.
2757    ///
2758    /// The same contract as [`AboveCodecRule::citation`], including the part
2759    /// that matters most: there is no fallback to a neighbouring draft's
2760    /// wording. `None` means there is no sentence to publish, so there is no
2761    /// accusation to make.
2762    #[must_use]
2763    pub fn citation(self, draft: u8) -> Option<&'static RuleCitation> {
2764        self.citations().iter().find(|c| c.covers(draft))
2765    }
2766}
2767
2768#[cfg(test)]
2769mod tests {
2770    use super::*;
2771
2772    /// Every rule this build enforces, so a sweep cannot silently narrow.
2773    ///
2774    /// Written out rather than derived, because there is nothing to derive it
2775    /// from: the enum is not iterable and adding a variant has to be a decision
2776    /// here as well as in `citations`. A rule added and not listed fails
2777    /// `every_variant_is_swept` below.
2778    const ALL: &[AboveCodecRule] = &[
2779        AboveCodecRule::PropertiesOnNonNormalStatus,
2780        AboveCodecRule::PayloadOnStatusDatagram,
2781        AboveCodecRule::BidiStreamOpener,
2782        AboveCodecRule::MessageOnTheWrongStream,
2783        AboveCodecRule::ResponseNamesAnotherRequest,
2784        AboveCodecRule::ResponseBeforeItsFirstResponse,
2785        AboveCodecRule::GoAwayAtServer,
2786        AboveCodecRule::RepeatedGoAway,
2787        AboveCodecRule::GoAwayFromRequestSender,
2788        AboveCodecRule::RedirectUriAtServer,
2789        AboveCodecRule::RedirectTrackNameOnNamespaceRequest,
2790        AboveCodecRule::RequestIdParity,
2791        AboveCodecRule::RequestIdOutOfSequence,
2792        AboveCodecRule::RequestIdCeiling,
2793        AboveCodecRule::MaxRequestIdDecreased,
2794        AboveCodecRule::SetupParameterValue,
2795        AboveCodecRule::DuplicateTrackAlias,
2796        AboveCodecRule::MixedForwardingPreference,
2797        AboveCodecRule::EndOfTrackOutOfPlace,
2798        AboveCodecRule::ObjectPastFinalObject,
2799        AboveCodecRule::RequestUpdateForTheWrongRequest,
2800        AboveCodecRule::TooManyRequestUpdates,
2801        AboveCodecRule::TrackPropertiesOnNonTrackStatus,
2802        AboveCodecRule::StateNotifyOnTheWrongRequest,
2803        AboveCodecRule::StateNotifyFromTheSubscriber,
2804        AboveCodecRule::UnrequestedFillStream,
2805        AboveCodecRule::SubscribeAfterAnnounceCancel,
2806        AboveCodecRule::TrackStatusIsNotASubscription,
2807        AboveCodecRule::MessageNamesAnUnknownRequest,
2808        AboveCodecRule::NamespacePrefixOverlap,
2809        AboveCodecRule::ObjectExistenceContradicted,
2810        AboveCodecRule::TruncatedObject,
2811    ];
2812
2813    /// Every rule the *decoder* raises that a draft answers with a close.
2814    ///
2815    /// The same shape as `ALL` and for the same reason. The two lists are kept
2816    /// apart rather than merged because the sweeps below ask both the same
2817    /// structural questions and nothing else: the two enums are two kinds of
2818    /// evidence and a test that could not say which one it had found would be
2819    /// the wrong test to fix a mix-up with.
2820    const ALL_CODEC: &[CodecRule] = &[
2821        CodecRule::TrackNameTooLong,
2822        CodecRule::ReasonPhraseTooLong,
2823        CodecRule::GoAwayUriTooLong,
2824        CodecRule::KvpValueTooLong,
2825        CodecRule::KeyValueFormatting,
2826        CodecRule::UnknownMessageParameter,
2827        CodecRule::ParameterOutOfScope,
2828        CodecRule::ParameterLengthMismatch,
2829        CodecRule::InvalidFilterType,
2830        CodecRule::InvalidFetchType,
2831        CodecRule::SubscriptionFilterMalformed,
2832        CodecRule::FilterEndGroupOverflow,
2833        CodecRule::ObjectIdOverflow,
2834        CodecRule::InvalidStreamTypeValue,
2835        CodecRule::InvalidDatagramTypeValue,
2836        CodecRule::EndOfTrackObjectId,
2837        CodecRule::ExtensionsOnNonExistentObject,
2838        CodecRule::InvalidRequiredRequestIdDelta,
2839    ];
2840
2841    /// Every run in both tables, so a sweep cannot be written for one of them
2842    /// and quietly answer for neither.
2843    ///
2844    /// The two enums do not share a trait and giving them one would be giving
2845    /// the library a shape it needs for nothing but this, so what is shared is
2846    /// the slice of runs rather than the rule that owns it. Where a test needs
2847    /// to name the rule it failed on, it asks its own list.
2848    fn every_run() -> Vec<&'static RuleCitation> {
2849        ALL.iter()
2850            .flat_map(|r| r.citations().iter())
2851            .chain(ALL_CODEC.iter().flat_map(|r| r.citations().iter()))
2852            .collect()
2853    }
2854
2855    /// The list above names every variant, so the sweeps below really are
2856    /// sweeps.
2857    ///
2858    /// `citations` is exhaustive, so a new variant is a build failure there;
2859    /// this list is not, so a new variant would silently drop out of every test
2860    /// in this module. Counting is the cheapest link between the two that does
2861    /// not need the enum to be iterable.
2862    #[test]
2863    fn every_variant_is_swept() {
2864        assert_eq!(ALL.len(), 32, "a rule was added or removed without updating ALL");
2865    }
2866
2867    /// And the same for the decoder's half.
2868    #[test]
2869    fn every_codec_rule_is_swept() {
2870        assert_eq!(ALL_CODEC.len(), 18, "a rule was added or removed without updating ALL_CODEC");
2871    }
2872
2873    /// A cited rule is a rule this build can actually raise on that draft.
2874    ///
2875    /// Not a claim about the drafts, and deliberately weaker than the one a
2876    /// reader might expect. What it rules out is a run written for a draft
2877    /// **outside** the range the enum's own doc claims for it — the failure
2878    /// that would put a sentence in front of a reader with no path to it. The
2879    /// stronger claim, that a cited draft is one whose
2880    /// `codec_session_error_code` answers `Some`, needs every draft's
2881    /// connection at once, and so belongs in a consumer that builds every
2882    /// draft rather than here.
2883    #[test]
2884    fn a_cited_draft_is_a_draft_that_closes() {
2885        for rule in ALL_CODEC {
2886            let runs = rule.citations();
2887            assert!(!runs.is_empty(), "{rule:?} is named with nothing to cite");
2888            for cite in runs {
2889                assert!(
2890                    moqtap_codec::version::DraftVersion::from_number(cite.drafts.0).is_some()
2891                        && moqtap_codec::version::DraftVersion::from_number(cite.drafts.1)
2892                            .is_some(),
2893                    "{rule:?} cites a draft outside the range this build implements"
2894                );
2895            }
2896        }
2897    }
2898
2899    /// A rule's runs are ordered, do not overlap, and name only drafts this
2900    /// build implements.
2901    ///
2902    /// Overlap is the failure that would be invisible otherwise: `citation`
2903    /// takes the first run that covers a draft, so two runs claiming draft-14
2904    /// would publish one of them and never say which.
2905    #[test]
2906    fn every_run_is_ordered_contiguous_and_in_range() {
2907        let runs: Vec<(String, &'static [RuleCitation])> = ALL
2908            .iter()
2909            .map(|r| (format!("{r:?}"), r.citations()))
2910            .chain(ALL_CODEC.iter().map(|r| (format!("{r:?}"), r.citations())))
2911            .collect();
2912        for (rule, citations) in runs {
2913            let mut last: Option<u8> = None;
2914            for cite in citations {
2915                let (lo, hi) = cite.drafts;
2916                assert!(lo <= hi, "{rule:?}: run ({lo}, {hi}) runs backwards");
2917                assert!(
2918                    moqtap_codec::version::DraftVersion::from_number(lo).is_some()
2919                        && moqtap_codec::version::DraftVersion::from_number(hi).is_some(),
2920                    "{rule:?}: out of range"
2921                );
2922                if let Some(prev) = last {
2923                    assert!(
2924                        prev < lo,
2925                        "{rule:?}: run at {lo} overlaps or repeats the one at {prev}"
2926                    );
2927                }
2928                last = Some(hi);
2929            }
2930        }
2931    }
2932
2933    /// One wording is written once.
2934    ///
2935    /// The claim the run representation rests on. Two rows carrying the same
2936    /// sentence must be pointing at the same constant, not at two copies of it
2937    /// — because two copies is how a correction reaches one of them and not the
2938    /// other, and the second copy then reads as a second draft's wording that
2939    /// happens to be identical.
2940    ///
2941    /// Compared by pointer as well as by value, which is what makes this a test
2942    /// about the representation rather than about the text.
2943    #[test]
2944    fn a_wording_shared_across_runs_is_written_once() {
2945        let mut seen: Vec<&'static str> = Vec::new();
2946        for cite in every_run() {
2947            match seen.iter().find(|s| **s == cite.sentence) {
2948                Some(first) => assert!(
2949                    std::ptr::eq(*first, cite.sentence),
2950                    "a sentence is repeated instead of naming the constant: {}",
2951                    &cite.sentence[..40.min(cite.sentence.len())]
2952                ),
2953                None => seen.push(cite.sentence),
2954            }
2955        }
2956        assert_eq!(
2957            seen.len(),
2958            122,
2959            "the sentence count moved; recount the cited sentences against the rendered drafts \
2960             and update this number"
2961        );
2962    }
2963
2964    /// The code name a row carries is a name its own sentence uses.
2965    ///
2966    /// The one claim in a row that the draft text cannot be searched for on its
2967    /// own: a section number resolves, a sentence resolves, and a code name is
2968    /// a word. Tying it to the sentence is what keeps it from drifting into
2969    /// this build's spelling of the code — which is exactly the direction it
2970    /// would drift, since `SessionErrorCode` spells every one of them in Rust.
2971    ///
2972    /// Underscores are folded to spaces because the drafts spell the same code
2973    /// both ways across the range, and this test is about the name being the
2974    /// sentence's rather than about which era it belongs to.
2975    #[test]
2976    fn the_code_name_is_a_name_the_sentence_uses() {
2977        for cite in every_run() {
2978            let Some(name) = cite.code_name else { continue };
2979            let sentence = cite.sentence.replace('_', " ").to_lowercase();
2980            let name = name.replace('_', " ").to_lowercase();
2981            assert!(sentence.contains(&name), "a run names a code its sentence does not: {name}");
2982        }
2983    }
2984
2985    /// The rule that made this table necessary, read at both ends of the range.
2986    ///
2987    /// One quoted sentence per rule published draft-18's words on every draft.
2988    /// This is the same rule asked twice, and every field of the answer differs
2989    /// — which is the whole claim, stated as an assertion rather than as a
2990    /// paragraph.
2991    #[test]
2992    fn the_track_alias_rule_is_a_different_citation_on_draft_07_and_draft_20() {
2993        let old = AboveCodecRule::DuplicateTrackAlias.citation(7).expect("draft-07 states it");
2994        let new = AboveCodecRule::DuplicateTrackAlias.citation(20).expect("draft-20 states it");
2995        assert_eq!(old.section, "6.4");
2996        assert_eq!(new.section, "11.1");
2997        assert_eq!(old.code_name, Some("Duplicate Track Alias"));
2998        assert_eq!(new.code_name, Some("DUPLICATE_TRACK_ALIAS"));
2999        assert_ne!(old.sentence, new.sentence);
3000        assert!(old.sentence.contains("already being used for a different track"));
3001        assert!(new.sentence.contains("PUBLISH or SUBSCRIBE_OK"));
3002    }
3003
3004    /// A draft that states a rule in no words this build has checked has no
3005    /// citation, and the gap is not filled from a neighbour.
3006    ///
3007    /// Drafts 17 and 18 are the concrete case. REQUEST_UPDATE moved onto the
3008    /// request's own stream and the field naming the request it modifies was
3009    /// deleted from the message, so there is nothing in either draft to name a
3010    /// request that cannot take an update — and neither draft closes a session
3011    /// over one. Draft-19 reintroduces the rule. A table that interpolated
3012    /// would answer draft-17 with draft-16's sentence about a field draft-17
3013    /// does not have.
3014    #[test]
3015    fn a_draft_that_does_not_state_a_rule_gets_no_citation_from_its_neighbours() {
3016        let rule = AboveCodecRule::RequestUpdateForTheWrongRequest;
3017        assert!(rule.citation(16).is_some());
3018        assert!(rule.citation(17).is_none(), "draft-17 deleted the field the rule is about");
3019        assert!(rule.citation(18).is_none(), "and draft-18 had not brought it back");
3020        assert!(rule.citation(19).is_some(), "draft-19 states it again, in new words");
3021    }
3022
3023    /// Rules the newest draft genuinely stopped stating.
3024    ///
3025    /// Empty is the normal state and the interesting one. A draft really can
3026    /// drop a rule - `RequestUpdateForTheWrongRequest` above is the worked
3027    /// case, deleted in drafts 17 and 18 along with the field it is about -
3028    /// so the sweep below cannot simply demand every rule on every draft. An
3029    /// entry here is the evidence for one of those, written down instead of
3030    /// absorbed; `RequestUpdateForTheWrongRequest` is not in it because
3031    /// draft-19 brought the rule back.
3032    const DROPPED_BY_THE_NEWEST_DRAFT: &[&str] = &[];
3033
3034    /// The newest draft is not quietly the first one to stop citing a rule.
3035    ///
3036    /// `check-draft-parity.py` rule 2 asks this of every draft list in the
3037    /// tree - a list naming draft N-1 and not draft N is the shape of one
3038    /// nobody brought forward - and it cannot ask it here: `citations`
3039    /// publishes runs as `(first, last)` tuples, and a tuple of integers is
3040    /// not a draft list. `check-drafts.py` rule 8 cannot ask it either. Rule 8
3041    /// holds every citation this table *makes* against that draft's rendered
3042    /// text, which is a strong check on the rows that exist and silent about
3043    /// the row that was never written. A rule left behind therefore publishes
3044    /// a finding with no sentence under it, on a green build.
3045    ///
3046    /// A run cannot be widened to cover the new draft, which is why this keeps
3047    /// happening: the drafts renumber, so a draft that keeps a sentence
3048    /// verbatim still files it under a different section and needs its own
3049    /// `RuleCitation`. Draft-21 moved every one of them.
3050    #[test]
3051    fn a_rule_the_previous_draft_states_is_cited_by_the_newest() {
3052        let newest = (7u8..=255)
3053            .take_while(|n| moqtap_codec::version::DraftVersion::from_number(*n).is_some())
3054            .last()
3055            .expect("this build implements at least one draft");
3056        let previous = newest - 1;
3057        assert!(
3058            moqtap_codec::version::DraftVersion::from_number(previous).is_some(),
3059            "the implemented drafts are contiguous, so the newest has a predecessor"
3060        );
3061
3062        let mut left_behind = Vec::new();
3063        for rule in ALL {
3064            if rule.citation(previous).is_some() && rule.citation(newest).is_none() {
3065                left_behind.push(format!("{rule:?}"));
3066            }
3067        }
3068        for rule in ALL_CODEC {
3069            if rule.citation(previous).is_some() && rule.citation(newest).is_none() {
3070                left_behind.push(format!("{rule:?}"));
3071            }
3072        }
3073        left_behind.retain(|name| !DROPPED_BY_THE_NEWEST_DRAFT.contains(&name.as_str()));
3074
3075        assert!(
3076            left_behind.is_empty(),
3077            "draft-{previous:02} states these rules and draft-{newest:02} cites none of \
3078             them: {left_behind:?}. Either that draft dropped the rule, in which case name \
3079             it in DROPPED_BY_THE_NEWEST_DRAFT beside the sentence that went away, or the \
3080             run was not brought forward - add a RuleCitation at the section the new draft \
3081             files the sentence under."
3082        );
3083    }
3084
3085    /// A rule with no sentence anywhere answers `None` on every draft.
3086    #[test]
3087    fn an_uncited_rule_names_no_draft() {
3088        for draft in moqtap_codec::version::DraftVersion::ALL.map(|d| d.number()) {
3089            assert!(
3090                AboveCodecRule::MessageOnTheWrongStream.citation(draft).is_none(),
3091                "draft-{draft:02} acquired a citation for a rule no draft states"
3092            );
3093        }
3094    }
3095}
3096
3097/// What one of a draft's *own* `ConnectionError` variants is — the ones outside
3098/// the ten every draft shares.
3099///
3100/// Returned by `Connection::draft_specific_cause`, which every draft
3101/// implements exhaustively over its own error type. `None` from that function
3102/// means the variant is one of the shared ten, which
3103/// [`crate::dispatch::AnyConnectionError`] classifies itself and never asks a
3104/// draft about.
3105///
3106/// The split is the whole point: the two are opposite findings, one this build
3107/// declining to do something and the other a relay having done something the
3108/// draft forbids. Without it both reach a consumer as prose.
3109#[derive(Debug, Clone, PartialEq, Eq)]
3110pub enum DraftSpecificCause {
3111    /// This endpoint refused the call. Nothing was written and no state moved.
3112    ///
3113    /// A message handed to the control stream that belongs on a request stream,
3114    /// a `respond_*` helper pointed at a request this endpoint opened, a FIN
3115    /// asked for before the response the draft requires first, an object asked
3116    /// for before the header it is framed against. Every one of them is this
3117    /// side being told to do something it will not do, and every one of them
3118    /// reaches [`crate::dispatch::ErrorCause::Facade`].
3119    ///
3120    /// # And one that is not a call at all
3121    ///
3122    /// `ConnectionError::ControlMessageNarrowing` is the odd member, and it is
3123    /// here for the reason the rest are rather than because it looks like them.
3124    /// Every draft's `recv_control` decodes into `AnyControlMessage` and then
3125    /// narrows the result to its own draft, and the arm that catches a
3126    /// narrowing that did not work is unreachable: the decoder was this draft's,
3127    /// so the variant can only be this draft's. Nothing pins that.
3128    ///
3129    /// Spelled as `CodecError::UnknownMessageType(0)` the placeholder would not
3130    /// stay inert. Every draft's `codec_session_error_code` answers that
3131    /// variant `Some(PROTOCOL_VIOLATION)`, so an unreachable arm being reached
3132    /// would reach a caller as *the peer sent a control message type this
3133    /// draft does not assign, and the session must be closed with a Protocol
3134    /// Violation* — with `0x00` riding along as the codepoint that proved it.
3135    /// A conformance report reading that publishes a named accusation against a
3136    /// relay, carrying a piece of evidence, for a defect in this build. Better
3137    /// dressed than any accusation it makes on real grounds, and false.
3138    ///
3139    /// So it is a variant of its own and it answers here. That does not make
3140    /// the arm reachable; it makes reaching it say *this build declined* rather
3141    /// than *the peer violated*, with no rule and no close code for anything
3142    /// downstream to read out of it.
3143    LocalRefusal,
3144    /// The peer broke a rule this endpoint enforces above its decoder.
3145    PeerViolation {
3146        /// Which rule, named the same way on every draft that states it.
3147        rule: AboveCodecRule,
3148        /// The session error code **this draft's own text** requires be sent
3149        /// for it, where it names one.
3150        ///
3151        /// Exactly the contract [`crate::dispatch::ErrorCause::Codec`] gives
3152        /// `close`, and for the same reason: a rule a draft attaches no
3153        /// consequence to is not grounds to name a peer.
3154        close: Option<u64>,
3155    },
3156    /// The decoder refused what the peer sent, and the draft's own variant
3157    /// carries the refusal with something the codec's error does not name.
3158    ///
3159    /// [`crate::dispatch::AnyConnectionError`] reports it as
3160    /// [`crate::dispatch::ErrorCause::Codec`], as it does a bare codec error.
3161    Codec {
3162        /// The decoder's refusal.
3163        error: moqtap_codec::error::CodecError,
3164        /// The session error code this draft's `codec_session_error_code`
3165        /// names for it, where it names one.
3166        close: Option<u64>,
3167    },
3168}
3169
3170/// Whose doing one of a draft's `EndpointError` variants is.
3171///
3172/// Returned by `EndpointError::fault`, which every draft implements
3173/// exhaustively over its own error type — no wildcard arm, so a variant added
3174/// to a draft is a compile error beside the sentence it enforces rather than a
3175/// silent arrival on the wrong side of this answer.
3176///
3177/// # The question it answers, and the one it does not
3178///
3179/// The endpoint's error type mixes two opposite findings under one name, and
3180/// they read alike as prose. Some of its variants are raised while **reading**
3181/// what the peer sent, and they are the peer's doing. The rest are raised while
3182/// this endpoint is **writing**, or refusing to, and they are this side's — the
3183/// call failed, nothing reached the wire, and there is nothing to say about the
3184/// relay.
3185///
3186/// The code the draft requires be sent is deliberately not carried here.
3187/// `EndpointError::session_error_code` already answers it, per draft, quoting
3188/// the sentence that names it, and a second copy of that table beside this one
3189/// could disagree with it. [`crate::dispatch::AnyConnectionError`] reads both
3190/// and pairs them.
3191///
3192/// # Why `Some(code)` is not the test
3193///
3194/// It looks like one: a variant the draft answers with a session close is a
3195/// variant a peer broke a rule to reach. It is not, and the counterexample is
3196/// concrete. On every draft from 07 to 16, the send path refusing to advertise
3197/// a ceiling that does not increase — `send_max_subscribe_id` on drafts 07
3198/// through 10, `send_max_request_id` on 11 through 16 — and a peer sending a
3199/// ceiling of its own that does not increase are one variant apart: under a
3200/// single variant `session_error_code` answers both `Some(PROTOCOL_VIOLATION)`,
3201/// so a local refusal to write a message publishes as a relay breaking the
3202/// ceiling rule. That rule is stated of MAX_SUBSCRIBE_ID on drafts 07 through
3203/// 10 and of MAX_REQUEST_ID on 11 through 16, and the section moves under it
3204/// four times across the ten: 6.20, 7.20, 8.4, 8.5 and 9.5 — see
3205/// [`AboveCodecRule::MaxRequestIdDecreased`]'s citations for which drafts each
3206/// of those covers. Each side has a variant of its own for that reason, and
3207/// this enum is what makes the collision visible.
3208#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3209pub enum EndpointFault {
3210    /// This endpoint's own doing: a call it refused, or state of its own that
3211    /// would not take one.
3212    ///
3213    /// Nothing reached the wire. A response offered for a request the draft
3214    /// requires be refused, a message handed to the wrong writer, an alias this
3215    /// endpoint was asked to give to a second track, a session that is closed
3216    /// or draining. Every one of them is evidence about this build and none of
3217    /// them is evidence about a peer, so all of them reach
3218    /// [`crate::dispatch::ErrorCause::Endpoint`], which
3219    /// [`crate::dispatch::AnyConnectionError::is_local`] counts.
3220    ///
3221    /// A peer can *cause* one of these without having broken anything this
3222    /// endpoint may name it for. A request whose filters the draft says to
3223    /// reject is refused here when a REQUEST_OK is offered for it, because the
3224    /// refusal is a reply and a reply needs the request to have been taken
3225    /// first; what failed is this side's attempt to accept it.
3226    ThisEndpoint,
3227    /// The peer did what the draft forbids, and this endpoint caught it
3228    /// reading.
3229    Peer(AboveCodecRule),
3230    /// Both are reachable through this variant and the variant cannot say
3231    /// which.
3232    ///
3233    /// Answered like [`Self::ThisEndpoint`] — reaching
3234    /// [`crate::dispatch::ErrorCause::Endpoint`], counted by
3235    /// [`crate::dispatch::AnyConnectionError::is_local`] — because that is the
3236    /// safe direction and the one this facade already took: a failure that has
3237    /// not been told apart is not evidence against a relay.
3238    ///
3239    /// Kept as its own answer rather than folded into `ThisEndpoint` so that
3240    /// the ones still to be told apart are a list the compiler can produce.
3241    /// There are two kinds:
3242    ///
3243    /// - **The state machines.** `Session`, `Subscription`, `Fetch`,
3244    ///   `Namespace`, `TrackStatus`, `PublishFlow`, `Setup` all render as
3245    ///   *invalid transition from X on event Y*, and the same sentence covers
3246    ///   this endpoint declining to send a message in a state that forbids it
3247    ///   and a peer having sent one. Telling those apart is a pass of its own.
3248    /// - **The unknown-request errors.** `UnknownRequest` and its earlier name
3249    ///   `UnknownSubscribe` are raised on more than sixty call sites per draft,
3250    ///   about half of them reading a response that names an id this session
3251    ///   never issued — the peer's doing — and about half of them a caller
3252    ///   naming one of its own that does not exist.
3253    ///
3254    /// Neither is published as anything: both answer `is_local` true. What this
3255    /// enum adds is that they are countable.
3256    EitherEnd,
3257}
3258
3259impl EndpointFault {
3260    /// The rule this fault names, or `None` where it names none.
3261    ///
3262    /// `None` for both of the non-peer answers, which is the same collapse
3263    /// [`crate::dispatch::ErrorCause`] performs: neither is grounds to name a
3264    /// relay, and a caller that needs to tell them apart has the variant.
3265    pub fn rule(self) -> Option<AboveCodecRule> {
3266        match self {
3267            Self::Peer(rule) => Some(rule),
3268            Self::ThisEndpoint | Self::EitherEnd => None,
3269        }
3270    }
3271}