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}