Skip to main content

moqtap_client/draft21/
endpoint.rs

1#![allow(missing_docs)]
2//! Draft-21 MoQT endpoint.
3//!
4//! Major changes from draft-19:
5//!
6//! * **FETCH lost its `Fetch Type`** and with it the Standalone and Joining
7//!   Fetch structures (Section 9.11). Track Namespace and Track Name are
8//!   inline fields, the range travels in the `LOCATION_FILTER` parameter, and
9//!   [`Endpoint::fetch`] therefore takes a parameter list where draft-19's took
10//!   four location varints. `joining_fetch` and `absolute_joining_fetch` are
11//!   gone, as is `INVALID_JOINING_REQUEST_ID` (REQUEST_ERROR 0x32) and every
12//!   rule this endpoint enforced with it.
13//! * **Fill fetch streams replace the joining fetch** (Sections 3.4 and
14//!   3.4.1). A SUBSCRIBE or REQUEST_UPDATE carrying `FILL_PARAMETERS` (0x23)
15//!   asks the publisher for a unidirectional stream that opens with a
16//!   FETCH_HEADER carrying the *subscription's* Request ID. This endpoint
17//!   records the request in [`Endpoint::fill_requested`] so an arriving
18//!   FETCH_HEADER can be told from a fetch's, and counts the streams so a
19//!   PUBLISH_DONE's Stream Count can include them.
20//! * **PUBLISH_STATE_NOTIFY (0x22) is new** (Section 9.10): unilateral, from
21//!   the publisher, on a subscription's stream, answered with nothing and
22//!   explicitly outside the `MAX_REQUEST_UPDATES` ceiling.
23//! * **Ranges are inclusive at both ends** (Sections 3.3.1, 9.11, 9.12), so
24//!   no end location in this module carries draft-19's `+ 1`.
25//! * `INCLUDE_PROPERTIES` (0x35) is new; `SUBSCRIPTION_ENDED` (PUBLISH_DONE
26//!   0x3) and `VERSION_NEGOTIATION_FAILED` (session 0x15) are unassigned.
27//! * Six parameters dropped PUBLISH_OK from their scope and several gained
28//!   PUBLISH; only `EXPIRES` still names PUBLISH_OK. The table that decides it
29//!   lives in the codec.
30
31use std::collections::{HashMap, HashSet};
32use std::sync::{Arc, Mutex};
33
34use crate::draft21::fetch::{FetchError, FetchState, FetchStateMachine};
35use crate::draft21::fill::{self, FillError, LocationFilter};
36use crate::draft21::namespace::{
37    NamespaceError, PublishNamespaceState, PublishNamespaceStateMachine, SubscribeNamespaceState,
38    SubscribeNamespaceStateMachine,
39};
40use crate::draft21::publish::{
41    PublishError as PublishFlowError, PublishState, PublishStateMachine,
42};
43use crate::draft21::session::request_id::{RequestIdAllocator, RequestIdError, Role};
44use crate::draft21::session::setup::{self, SetupError};
45
46use moqtap_codec::draft21::data_stream::GroupOrder;
47use moqtap_codec::kvp::KvpValue;
48use moqtap_codec::range_filter::{self, RangeFilterError};
49use moqtap_codec::varint::Moqt18;
50
51/// The MAX_REQUEST_UPDATES Setup Option, Section 9.1.7.
52const MAX_REQUEST_UPDATES: u64 = 0x08;
53
54/// The MAX_FILTER_RANGES Setup Option, Section 9.1.6.
55const MAX_FILTER_RANGES: u64 = 0x06;
56
57/// The Message Parameters a request carries, or nothing for a message that is
58/// not a request.
59///
60/// Every one of the seven request kinds carries a parameter list, so the empty
61/// arm is only reached by a caller that is not looking at a request. Section
62/// 3.4 names five message types a Range Filter may appear in, and this does
63/// not narrow to them: a filter parameter arriving where the section does not
64/// list it is a scope question, and scope is answered by the parameter registry
65/// rather than here. What matters for the ceiling is that a filter this endpoint
66/// has no budget for is not accepted, wherever it turned up.
67fn request_parameters(msg: &ControlMessage) -> &[KeyValuePair] {
68    match msg {
69        ControlMessage::Subscribe(m) => &m.parameters,
70        ControlMessage::Publish(m) => &m.parameters,
71        ControlMessage::Fetch(m) => &m.parameters,
72        ControlMessage::PublishNamespace(m) => &m.parameters,
73        ControlMessage::SubscribeNamespace(m) => &m.parameters,
74        ControlMessage::SubscribeTracks(m) => &m.parameters,
75        ControlMessage::TrackStatus(m) => &m.parameters,
76        _ => &[],
77    }
78}
79
80/// The Range Filter parameters in `parameters`, dropping the removals.
81///
82/// A Range Filter whose value is empty is Section 3.3.2's removal form — "In
83/// REQUEST_UPDATE, Length of 0 removes the filter" — so it names a filter rather
84/// than being one, and carrying it forward would leave a filter of no ranges in
85/// force for the life of the request.
86fn range_filter_parameters(parameters: &[KeyValuePair]) -> Vec<KeyValuePair> {
87    parameters
88        .iter()
89        .filter(|p| range_filter::is_range_filter(p.key.into_inner()))
90        .filter(|p| !matches!(&p.value, KvpValue::Bytes(bytes) if bytes.is_empty()))
91        .cloned()
92        .collect()
93}
94
95/// Apply a REQUEST_UPDATE's filter parameters to the set already in force.
96///
97/// Replacement is by Parameter Type and takes the whole type with it: "non-zero
98/// replaces it entirely". A type the update does not mention is left alone,
99/// which is the sentence after it — "If a filter parameter is omitted from
100/// REQUEST_UPDATE, it is unchanged" — and is why this merges rather than
101/// measuring the update on its own.
102fn apply_filter_update(in_force: &mut Vec<KeyValuePair>, update: &[KeyValuePair]) {
103    let mentioned: Vec<u64> = update
104        .iter()
105        .map(|p| p.key.into_inner())
106        .filter(|key| range_filter::is_range_filter(*key))
107        .collect();
108    in_force.retain(|p| !mentioned.contains(&p.key.into_inner()));
109    in_force.extend(range_filter_parameters(update));
110}
111
112/// Read a Setup Option's value as a variable-length integer, or 0 if it is
113/// absent.
114///
115/// Zero is also the default every numeric option in Section 9.1 takes when
116/// it is not sent, so an absent option and an explicit zero mean the same thing
117/// and do not need to be told apart.
118fn setup_varint(options: &[KeyValuePair], key: u64) -> u64 {
119    let key = VarInt::from_u64(key).expect("option key fits a varint");
120    options
121        .iter()
122        .find(|o| o.key == key)
123        .and_then(|o| match &o.value {
124            KvpValue::Varint(v) => Some(v.into_inner()),
125            KvpValue::Bytes(bytes) => {
126                let mut cursor = &bytes[..];
127                let parsed = VarInt::decode_moqt::<Moqt18>(&mut cursor).ok()?;
128                cursor.is_empty().then(|| parsed.into_inner())
129            }
130        })
131        .unwrap_or(0)
132}
133use crate::draft21::session::state::{SessionError, SessionState, SessionStateMachine};
134use crate::draft21::subscription::{
135    SubscriptionError, SubscriptionState, SubscriptionStateMachine,
136};
137use crate::draft21::track_status::{TrackStatusError, TrackStatusState, TrackStatusStateMachine};
138use crate::malformed_tracks::{MalformedTrackCondition, MalformedTracks};
139use crate::track_locations::{ObjectLocation, ObjectRole, TrackLocations, TrackObjects};
140use moqtap_codec::draft21::error_codes::{
141    PublishDoneStatusCode, RequestErrorCode, SessionErrorCode,
142};
143use moqtap_codec::draft21::message::{
144    self, ControlMessage, Fetch, GoAway, MessageType, Publish, PublishDone, PublishNamespace,
145    PublishSkipped, PublishStateNotify, RequestError, RequestOk, RequestUpdate, Setup, Subscribe,
146    SubscribeNamespace, SubscribeOk, SubscribeTracks, FILL_PARAMETERS,
147};
148use moqtap_codec::kvp::KeyValuePair;
149use moqtap_codec::types::*;
150use moqtap_codec::varint::VarInt;
151
152/// Why a peer's request must be answered with REQUEST_ERROR and INVALID_FILTER.
153///
154/// Draft-21 gives the Range Filters four rules and answers every one of them
155/// the same way — "MUST reject this with REQUEST_ERROR with error code
156/// INVALID_FILTER" — so this says which rule was broken rather than which code
157/// to send. [`FilterRejection::request_error_code`] is the code, and it is the
158/// same for all four.
159///
160/// None of these is a session close, which is the whole reason the type exists.
161/// A rejection is a reply, and a reply names the Request ID of the request it
162/// answers, so the endpoint has to have taken the request in order to refuse it.
163/// The rejection is recorded against that id and spent by the REQUEST_ERROR that
164/// answers it.
165#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
166pub enum FilterRejection {
167    /// The peer sent a Range Filter and this endpoint advertised no budget.
168    ///
169    /// Section 9.1.6: "The default value is 0, so if not specified, the peer
170    /// MUST NOT send any such filter parameters." Zero is the default and it
171    /// means none allowed — the opposite of what zero means in the option
172    /// beside it, MAX_REQUEST_UPDATES, where it means no limit. The two sit in
173    /// consecutive subsections and read opposite ways.
174    #[error("this endpoint advertised no MAX_FILTER_RANGES, so no range filter is allowed")]
175    NoBudgetAdvertised,
176    /// More Ranges than MAX_FILTER_RANGES allows, counted across every filter.
177    #[error("{ranges} ranges across the request's filters, where {limit} were advertised")]
178    TooManyRanges {
179        /// The total the request carried.
180        ranges: usize,
181        /// The total this endpoint advertised.
182        limit: u64,
183    },
184    /// Two filters with the same Parameter Type, SetID and Property Type.
185    ///
186    /// Repeats of a type are otherwise expected, so the key is the whole triple:
187    /// "The Track Property filter parameter MAY appear multiple times in a
188    /// SUBSCRIBE_TRACKS message"
189    #[error("two filters share parameter type {0:#x}, set {1} and property type {2:?}")]
190    RepeatedFilter(u64, u8, Option<u64>),
191    /// A filter this codec could not read, or one that broke a rule of its own.
192    #[error("a range filter is not usable: {0}")]
193    Unreadable(#[from] RangeFilterError),
194}
195
196impl FilterRejection {
197    /// The REQUEST_ERROR code every one of these is answered with.
198    pub fn request_error_code(&self) -> RequestErrorCode {
199        RequestErrorCode::InvalidFilter
200    }
201}
202
203/// Errors that can occur during endpoint operations.
204#[derive(Debug, thiserror::Error)]
205pub enum EndpointError {
206    #[error("session error: {0}")]
207    Session(#[from] SessionError),
208    #[error("request ID error: {0}")]
209    RequestId(#[from] RequestIdError),
210    #[error("subscription error: {0}")]
211    Subscription(#[from] SubscriptionError),
212    #[error("fetch error: {0}")]
213    Fetch(#[from] FetchError),
214    /// A `LOCATION_FILTER` or `FILL_PARAMETERS` value this endpoint was asked
215    /// to build is one draft-21 does not define. Nothing was written.
216    #[error("fill parameter error: {0}")]
217    Fill(#[from] FillError),
218    #[error("namespace error: {0}")]
219    Namespace(#[from] NamespaceError),
220    #[error("track status error: {0}")]
221    TrackStatus(#[from] TrackStatusError),
222    #[error("publish flow error: {0}")]
223    PublishFlow(#[from] PublishFlowError),
224    #[error("setup error: {0}")]
225    Setup(#[from] SetupError),
226    #[error("unknown request ID: {0}")]
227    UnknownRequest(u64),
228    #[error(
229        "response message received on control stream; draft-21 responses belong on bidi request streams"
230    )]
231    ResponseOnControlStream,
232    /// A REQUEST_UPDATE arrived on the control stream.
233    ///
234    /// Draft-21 Table 5 gives REQUEST_UPDATE the Stream value "Request", and
235    /// Section 9.5 requires it on the same bidi stream as the request it
236    /// modifies. One on the control stream modifies nothing, which makes it a
237    /// case Section 9.5 says MUST close the session.
238    #[error(
239        "REQUEST_UPDATE received on the control stream; it belongs on its request's own stream"
240    )]
241    RequestUpdateOnControlStream,
242    /// A NAMESPACE, NAMESPACE_DONE or PUBLISH_SKIPPED arrived on the control
243    /// stream.
244    ///
245    /// Draft-21 Table 5 gives all three the Stream value "Request": NAMESPACE
246    /// (0x8, Section 9.16) and NAMESPACE_DONE (0xE, Section 9.17) belong on
247    /// the SUBSCRIBE_NAMESPACE request stream whose namespace they report, and
248    /// PUBLISH_SKIPPED (0xF, Section 9.19) on the SUBSCRIBE_TRACKS stream
249    /// whose namespace it names a skipped track in — "All PUBLISH_SKIPPED
250    /// messages are in response to a SUBSCRIBE_TRACKS". Only SETUP is
251    /// "Control" alone; GOAWAY is the one message the
252    /// table lists as "Control, Request". One of these three on the control
253    /// stream names no request, so nothing can be done with it.
254    #[error("{0} received on the control stream; draft-21 Table 5 places it on a request stream")]
255    RequestMessageOnControlStream(&'static str),
256    /// A REQUEST_UPDATE named a request that cannot be updated, or none.
257    ///
258    /// Draft-21 Section 9.5: "An endpoint that receives a REQUEST_UPDATE
259    /// other than in the two cases above MUST close the session with a
260    /// PROTOCOL_VIOLATION." TRACK_STATUS is called out in Section 9.13 as one
261    /// such case: "the subscriber cannot send REQUEST_UPDATE."
262    #[error("REQUEST_UPDATE for request {0}, which is not an updatable outstanding request")]
263    UnexpectedRequestUpdate(u64),
264    /// Track Properties on a REQUEST_OK answering something other than a
265    /// TRACK_STATUS.
266    ///
267    /// Draft-21 Section 9.3: they "are empty in PUBLISH_OK,
268    /// REQUEST_UPDATE_OK, SUBSCRIBE_NAMESPACE_OK and PUBLISH_NAMESPACE_OK. If
269    /// an endpoint receives Track Properties in one of these messages it MUST
270    /// close the session with a PROTOCOL_VIOLATION."
271    #[error(
272        "track properties on the REQUEST_OK answering request {0}, which is not a TRACK_STATUS"
273    )]
274    TrackPropertiesOnNonTrackStatus(u64),
275    /// A server received a Redirect naming a Connect URI.
276    ///
277    /// Draft-21 Section 9.4.1: "If a server receives a Redirect with a
278    /// non-zero Connect URI Length it MUST close the session with a
279    /// PROTOCOL_VIOLATION." As with GOAWAY, only a client is redirected.
280    #[error("Redirect carrying a Connect URI received at a server")]
281    RedirectUriAtServer,
282    /// A Redirect answering a namespace-scoped request carried a Track Name.
283    ///
284    /// Draft-21 Section 9.4.1: "Track Name is not meaningful for
285    /// namespace-scoped requests (SUBSCRIBE_NAMESPACE, PUBLISH_NAMESPACE,
286    /// SUBSCRIBE_TRACKS) and MUST be empty; an endpoint that receives a
287    /// non-empty Track Name in a Redirect for a namespace-scoped request MUST
288    /// close the session with a PROTOCOL_VIOLATION." Draft-18 names the same
289    /// rule with SUBSCRIBE_TRACKS left out of the list.
290    #[error("Redirect for namespace-scoped request {0} carries a track name")]
291    RedirectTrackNameOnNamespaceRequest(u64),
292    /// A server received a GOAWAY carrying a New Session URI.
293    ///
294    /// Draft-21 Section 9.2: "If a server receives a GOAWAY with a non-zero
295    /// New Session URI Length it MUST close the session with a
296    /// PROTOCOL_VIOLATION." Only a client can be redirected.
297    #[error("GOAWAY carrying a New Session URI received at a server")]
298    GoAwayUriAtServer,
299    /// The peer reused a Request ID it had already spent.
300    ///
301    /// Draft-21 Section 6.4.2.1: "If an endpoint receives a Request ID where the
302    /// least significant bit is incorrect for the sender, or a duplicate
303    /// Request ID, it MUST close the session with INVALID_REQUEST_ID."
304    #[error("request {0} was already used by the peer")]
305    DuplicateRequestId(u64),
306    /// A bidirectional stream the peer opened began with a message that does
307    /// not open a request stream.
308    ///
309    /// Draft-21 Section 6.3: "Bidirectional streams MUST NOT begin with any
310    /// other message type unless negotiated. If they do, the peer MUST close
311    /// the Session with a PROTOCOL_VIOLATION."
312    #[error("{0:?} does not begin a request stream")]
313    NotARequest(MessageType),
314    /// A message that this endpoint may not write on a request stream the peer
315    /// opened was handed to the responder path. Nothing was written and no
316    /// state moved.
317    #[error("{0:?} is not a message a responder writes on a peer's request stream")]
318    NotAResponse(MessageType),
319    /// A message arrived on a request stream the peer opened that may not
320    /// follow a request there.
321    ///
322    /// This endpoint is the responder on such a stream, so a response arriving
323    /// on it is the peer answering its own request.
324    #[error("{0:?} may not follow a request on a stream the peer opened")]
325    UnexpectedOnPeerRequestStream(MessageType),
326    /// A namespace-scoped request's response half opened with something other
327    /// than REQUEST_OK or REQUEST_ERROR.
328    ///
329    /// Draft-21 Sections 9.15 and 9.18, of SUBSCRIBE_NAMESPACE and
330    /// SUBSCRIBE_TRACKS alike: "The publisher will respond with REQUEST_OK or
331    /// REQUEST_ERROR on the response half of the stream. If the subscriber
332    /// receives any message other than a REQUEST_OK or a REQUEST_ERROR as the
333    /// first message on the response half of the stream, then it MUST close the
334    /// session with a PROTOCOL_VIOLATION."
335    #[error("request {0} answered with {1:?} before its REQUEST_OK or REQUEST_ERROR")]
336    ResponseBeforeTheFirstResponse(u64, MessageType),
337    /// Track Properties were put on a REQUEST_OK answering something other
338    /// than a TRACK_STATUS, on the way out. Nothing was written.
339    ///
340    /// The send-side mirror of
341    /// [`TrackPropertiesOnNonTrackStatus`](Self::TrackPropertiesOnNonTrackStatus):
342    /// Section 9.3 answers receiving them with a session close, so writing
343    /// them would hand a conforming peer a reason to close this session. This
344    /// one is not fatal — nothing reached the wire, so there is nothing for the
345    /// peer to object to.
346    #[error("request {0} is not a TRACK_STATUS; only its response carries track properties")]
347    TrackPropertiesOnOutgoingRequestOk(u64),
348    /// A REQUEST_UPDATE arrived on a stream that had already used up the
349    /// concurrency this endpoint advertised.
350    ///
351    /// Draft-21 Section 9.1.7: "If an endpoint receives a REQUEST_UPDATE on
352    /// a stream that already has MAX_REQUEST_UPDATES outstanding
353    /// REQUEST_UPDATEs, it MUST close the session with
354    /// TOO_MANY_REQUEST_UPDATES."
355    #[error("request {0} already has the {1} outstanding REQUEST_UPDATEs it was allowed")]
356    TooManyRequestUpdates(u64, u64),
357    /// A REQUEST_OK was offered for a request whose Range Filters this endpoint
358    /// is required to reject.
359    ///
360    /// Not fatal, and deliberately not raised where the filter arrives. Every
361    /// Range Filter rule in Section 3.3.2 is answered with a REQUEST_ERROR, and
362    /// a REQUEST_ERROR names the Request ID of the request it answers — so the
363    /// request has to be taken before it can be refused. What this stops is the
364    /// other answer: accepting the request the draft says to reject leaves the
365    /// subscriber with a subscription whose filters this endpoint never agreed
366    /// to apply, and a publisher that then forwards by its own reading of them.
367    #[error("request {0} must be answered with REQUEST_ERROR: {1}")]
368    FilterMustBeRejected(u64, FilterRejection),
369    /// A REQUEST_OK or REQUEST_ERROR was offered as the answer to a
370    /// REQUEST_UPDATE on a stream with no update waiting for one.
371    ///
372    /// Section 9.5 requires "exactly one REQUEST_OK or REQUEST_ERROR message
373    /// indicating if the update was successful", so an answer with nothing to
374    /// answer is one the peer will read as belonging to an update it never
375    /// sent. Not fatal: nothing was written.
376    ///
377    /// A SUBSCRIBE is answered with SUBSCRIBE_OK and a FETCH with FETCH_OK, so
378    /// on those two streams a REQUEST_OK can be nothing but an update's answer
379    /// and this is what a mistimed one produces. On the five kinds REQUEST_OK
380    /// answers itself, the first one is the request's and only the ones after
381    /// it can reach here.
382    #[error("request {0} has no REQUEST_UPDATE waiting for an answer")]
383    NoUpdateToAnswer(u64),
384    /// An update was refused and the subscription it belongs to was then ended
385    /// under some status other than the one that names why.
386    ///
387    /// Section 9.5.1: "When a REQUEST_UPDATE is unsuccessful, the publisher MUST
388    /// also terminate the subscription by sending a PUBLISH_DONE with error
389    /// code UPDATE_FAILED." The REQUEST_ERROR is half of what that sentence
390    /// asks for and the termination is the other half, so this endpoint holds
391    /// the request to it: whatever else the caller writes first, the
392    /// termination it does write says so.
393    #[error("request {request}'s update was refused, so its PUBLISH_DONE must carry {required}")]
394    WrongUpdateFailureStatus {
395        /// The request whose update was refused.
396        request: u64,
397        /// The status code the termination must carry.
398        required: u64,
399    },
400    #[error("session not active")]
401    NotActive,
402    #[error("session is draining, no new requests allowed")]
403    Draining,
404    /// A second GOAWAY arrived on the control stream.
405    ///
406    /// The GOAWAY that says the peer is going away is one message, and the
407    /// draft answers a repeat of it with a session close rather than with an
408    /// error about the second message: there is no state a second one could
409    /// move that the first has not already moved.
410    #[error("a second GOAWAY arrived on the control stream")]
411    RepeatedGoAway,
412    /// A second GOAWAY arrived on one request's stream.
413    ///
414    /// The count is per stream rather than per session: this draft lets a
415    /// GOAWAY migrate a single request, so one on each of two request streams
416    /// is two first GOAWAYs and not a repeat.
417    #[error("a second GOAWAY arrived on request {0}'s stream")]
418    RepeatedGoAwayOnRequestStream(
419        /// The Request ID of the stream that carried both.
420        u64,
421    ),
422    /// The peer named a Track Alias it is already using for another track.
423    ///
424    /// Draft-21 Section 3.1.2: "The same Track Alias MUST NOT be used by a
425    /// publisher to refer to two different Tracks simultaneously in the same
426    /// session. If a subscriber receives a PUBLISH or SUBSCRIBE_OK that uses
427    /// the same Track Alias as a different Track with an Established
428    /// subscription, it MUST close the session with error
429    /// DUPLICATE_TRACK_ALIAS."
430    ///
431    /// The session is over: this endpoint's own state has moved to Closed and
432    /// the code the transport should close with is in
433    /// [`EndpointError::session_error_code`].
434    #[error("track alias {alias} already names request {established}'s track; request {offered} names a different one")]
435    DuplicateTrackAlias {
436        /// The alias both tracks are named by.
437        alias: u64,
438        /// The request whose Established subscription holds the alias.
439        established: u64,
440        /// The request whose message arrived naming it for another track.
441        offered: u64,
442    },
443    /// This endpoint was asked to give a Track Alias to a second track.
444    ///
445    /// The same rule as [`EndpointError::DuplicateTrackAlias`] read at the end
446    /// that chooses the alias. Section 3.1.2 states it as a prohibition on the
447    /// publisher before it states what the subscriber does about one: "The
448    /// same Track Alias MUST NOT be used by a publisher to refer to two different Tracks
449    /// simultaneously in the same session."
450    ///
451    /// The message is refused instead of built, and nothing else moves: no
452    /// Request ID is spent, no publish flow is created, and the session stays
453    /// as it was. The alias never reaches the peer, so there is nothing for
454    /// the peer to close over.
455    #[error("track alias {alias} already names request {held}'s track")]
456    TrackAliasInUse {
457        /// The alias that is already spoken for.
458        alias: u64,
459        /// The request whose live flow holds it.
460        held: u64,
461    },
462    /// An object arriving after the track's final object.
463    ///
464    /// Section 12.1 lists the condition: "An Object is received whose
465    /// Group and Object ID are larger
466    /// than the final Object in the Track.
467    /// The final Object in a Track is the Object with Status END_OF_TRACK or
468    /// the last Object sent in a FETCH whose response indicated End of Track."
469    ///
470    /// **Larger is Section 8.2's comparison and not a reading of the
471    /// words.** That section puts one Location below another when "A.Group <
472    /// B.Group || (A.Group == B.Group && A.Object < B.Object)", so an Object in
473    /// a later group is past the end whatever its own Object ID is.
474    ///
475    /// **A Malformed Track and not a session error**, and on this draft not a
476    /// message either. Section 12.1 answers its whole list at once with "it
477    /// MUST cancel any corresponding subscription or fetches for that Track
478    /// from that publisher", where cancelling a request is the transport
479    /// operation Section 6.4.2.3 describes. This is the error half; the
480    /// requests to cancel are named by
481    /// [`Endpoint::requests_for_malformed_track`].
482    #[error(
483        "the object at group {group}, object {object} on track alias {alias} arrived \
484         after the track's final object at group {final_group}, object {final_object}"
485    )]
486    ObjectPastFinalObject {
487        /// The Track Alias the offending object named.
488        alias: u64,
489        /// The Group ID it named.
490        group: u64,
491        /// The Object ID it named.
492        object: u64,
493        /// The Group ID of the object the track ended at.
494        final_group: u64,
495        /// The Object ID of the object the track ended at.
496        final_object: u64,
497    },
498
499    /// A PUBLISH_STATE_NOTIFY arrived for a request that is not a
500    /// subscription.
501    ///
502    /// Section 9.10: "PUBLISH_STATE_NOTIFY applies only to subscriptions, and
503    /// is sent only by the publisher. An endpoint that receives a
504    /// PUBLISH_STATE_NOTIFY for any other request type, or from the subscriber,
505    /// MUST close the session with a PROTOCOL_VIOLATION."
506    ///
507    /// This is the first half of that sentence. The session is over: this
508    /// endpoint's own state has moved to Closed and the code the transport
509    /// should close with is in [`EndpointError::session_error_code`].
510    #[error("PUBLISH_STATE_NOTIFY for request {0}, which is not a subscription")]
511    StateNotifyForNonSubscription(u64),
512
513    /// A PUBLISH_STATE_NOTIFY arrived from the subscriber.
514    ///
515    /// The second half of the same sentence. A subscription's publisher is the
516    /// end that did not send the SUBSCRIBE, so one arriving on a stream this
517    /// endpoint opened with a SUBSCRIBE is the publisher's and one arriving on
518    /// a stream the peer opened with a SUBSCRIBE is the subscriber's — which is
519    /// what this refuses, and what the section answers with a session close.
520    #[error(
521        "PUBLISH_STATE_NOTIFY on request {0} may only be sent by that subscription's publisher"
522    )]
523    StateNotifyFromSubscriber(u64),
524
525    /// A PUBLISH_STATE_NOTIFY was offered for a request this endpoint does not
526    /// publish a subscription for. Nothing was written and no state moved.
527    ///
528    /// The send-side mirror of the two above, and not fatal for the same reason
529    /// [`EndpointError::TrackPropertiesOnOutgoingRequestOk`] is not: nothing
530    /// reached the wire, so there is nothing for the peer to object to. What it
531    /// prevents is handing a conforming peer a message Section 9.10 tells it to
532    /// close the session over.
533    #[error("request {0} is not a subscription this endpoint publishes, so it sends no PUBLISH_STATE_NOTIFY")]
534    StateNotifyThisEndpointMayNotSend(u64),
535
536    /// A fill fetch stream opened against a request that asked for no fill.
537    ///
538    /// Section 3.4 makes `FILL_PARAMETERS` the whole of the request: "Its
539    /// presence is what requests a fill fetch stream; a subscription with no
540    /// FILL_PARAMETERS opens none." A FETCH_HEADER naming a subscription that
541    /// never asked is a stream this endpoint has nothing to attribute, and one
542    /// naming a request that is not a subscription at all is worse.
543    ///
544    /// Not a session close. Draft-21 states no rule for it — a fill fetch
545    /// stream has no FETCH_OK and no REQUEST_ERROR, so there is no answer to
546    /// send and no sentence naming a code — and the honest handling is
547    /// `STOP_SENDING` on that stream alone, which Section 3.4.1 gives the
548    /// subscriber outright and says "does not affect the subscription".
549    #[error("request {0} opened a fill fetch stream without asking for a fill")]
550    UnrequestedFillStream(u64),
551    /// The peer subscribed to a namespace prefix overlapping one it is
552    /// already subscribed to.
553    ///
554    /// Section 9.15: "Within a session, if a publisher receives a
555    /// SUBSCRIBE_NAMESPACE with a Track Namespace Prefix that shares a common
556    /// prefix with an established SUBSCRIBE_NAMESPACE, it MUST respond with
557    /// REQUEST_ERROR with error code PREFIX_OVERLAP."
558    ///
559    /// Section 9.18: "Within a session, if a publisher receives a
560    /// SUBSCRIBE_TRACKS with a Track Namespace Prefix that shares a common
561    /// prefix with an established SUBSCRIBE_TRACKS, it MUST respond with
562    /// REQUEST_ERROR with error code PREFIX_OVERLAP."
563    ///
564    /// Section 12.3: "SUBSCRIBE_NAMESPACE and SUBSCRIBE_TRACKS have
565    /// independent overlap spaces, so a SUBSCRIBE_NAMESPACE and a
566    /// SUBSCRIBE_TRACKS may share the same prefix."
567    ///
568    /// Taken when the message arrives, which is the moment the sentence
569    /// names, and read again when an answer is built: a request this endpoint
570    /// may not accept is one no later call can accept.
571    ///
572    /// The refusal itself is not this error. It is a message the peer is
573    /// owed, so the request is recorded like any other and refused through
574    /// the same call that refuses any other, under the code the sentence
575    /// names.
576    #[error(
577        "request {request} subscribes to a namespace prefix overlapping request {established}"
578    )]
579    PeerPrefixOverlap {
580        /// The request that arrived.
581        request: u64,
582        /// The namespace subscription it overlaps.
583        established: u64,
584    },
585    /// A namespace subscription that overlaps another was refused under a
586    /// code other than the one the sentence names.
587    ///
588    /// A rule that names the code its refusal carries is not satisfied by a
589    /// refusal under any other, because the peer reads the code to learn what
590    /// went wrong. Draft-19 had a second rule of this shape — a Joining Fetch
591    /// naming no live subscription took INVALID_JOINING_REQUEST_ID and nothing
592    /// else — and draft-20 deleted the mechanism and the code together, so this
593    /// is the only one left.
594    #[error("request {request} overlaps a namespace subscription and must be refused with code {required:#x}")]
595    WrongOverlapRefusal {
596        /// The request being refused.
597        request: u64,
598        /// The code the sentence names for it.
599        required: u64,
600    },
601}
602
603/// Whether two namespace prefixes overlap.
604///
605/// Section 9.15: "Within a session, if a publisher receives a
606/// SUBSCRIBE_NAMESPACE with a Track Namespace Prefix that shares a common
607/// prefix with an established SUBSCRIBE_NAMESPACE, it MUST respond with
608/// REQUEST_ERROR with error code PREFIX_OVERLAP."
609///
610/// Section 9.18: "Within a session, if a publisher receives a
611/// SUBSCRIBE_TRACKS with a Track Namespace Prefix that shares a common prefix
612/// with an established SUBSCRIBE_TRACKS, it MUST respond with REQUEST_ERROR
613/// with error code PREFIX_OVERLAP."
614///
615/// Section 12.3: "SUBSCRIBE_NAMESPACE and SUBSCRIBE_TRACKS have independent
616/// overlap spaces, so a SUBSCRIBE_NAMESPACE and a SUBSCRIBE_TRACKS may share
617/// the same prefix."
618///
619/// A namespace matches a namespace subscription when the subscription's
620/// prefix is a prefix of it, so two prefixes select overlapping sets of
621/// namespaces exactly when one of them is a prefix of the other. Equal
622/// prefixes are that case as well: every prefix is a prefix of itself, and
623/// two equal ones select the same set.
624///
625/// "Shares a common prefix with" is read as the relation drafts 07 through 14
626/// spell out at greater length. Taken at its word it would forbid every
627/// second namespace subscription in a session, since any two prefixes share
628/// the empty one, and nothing else in this draft supports that.
629fn prefixes_overlap(a: &[Vec<u8>], b: &[Vec<u8>]) -> bool {
630    let shared = a.len().min(b.len());
631    a[..shared] == b[..shared]
632}
633
634/// Parameter Type of TRACK_NAMESPACE_PREFIX.
635const TRACK_NAMESPACE_PREFIX: u64 = 0x34;
636
637/// The Track Namespace Prefix a REQUEST_UPDATE asks a namespace subscription
638/// to move to, and `None` when it asks for no such move.
639///
640/// Section 9.20.21: "The TRACK_NAMESPACE_PREFIX parameter (Parameter Type
641/// 0x34) uses the Track Namespace encoding described in Section 8.7. It MAY
642/// appear in REQUEST_UPDATE for a SUBSCRIBE_NAMESPACE or SUBSCRIBE_TRACKS
643/// request. It updates the Track Namespace Prefix for that subscription."
644///
645/// A prefix of no fields is a value rather than a removal. Section 2.4.1 puts
646/// a Track Namespace at "between 0 and 32 Track Namespace Fields", an empty
647/// prefix selects every namespace, and Section 9.5 leaves no other reading
648/// open: "There is no mechanism to remove a parameter from a request."
649///
650/// The last one wins when a message carries the parameter twice. That is the
651/// rule Section 9.5 gives for two messages -- "Parameter values from later
652/// REQUEST_UPDATE messages override values from earlier ones" -- applied
653/// inside one, which is the only reading under which a repeat means anything.
654///
655/// A value that is not a whole Track Namespace answers `None`, so an update
656/// carrying one leaves the prefix where it was. The decoder that built the
657/// message has already refused a malformed one; this is the arm that keeps a
658/// hand-built message from moving a subscription to half a prefix.
659fn updated_prefix(parameters: &[KeyValuePair]) -> Option<TrackNamespace> {
660    parameters.iter().rfind(|p| p.key.into_inner() == TRACK_NAMESPACE_PREFIX).and_then(|p| match &p
661        .value
662    {
663        KvpValue::Bytes(bytes) => {
664            let mut cursor = &bytes[..];
665            let prefix = TrackNamespace::decode_allow_empty_moqt::<Moqt18>(&mut cursor).ok()?;
666            cursor.is_empty().then_some(prefix)
667        }
668        KvpValue::Varint(_) => None,
669    })
670}
671
672impl EndpointError {
673    /// Whose doing this is — the peer's, or this endpoint's, or a variant that
674    /// cannot say.
675    ///
676    /// The companion of [`EndpointError::session_error_code`], which answers
677    /// *what the draft requires be done about it*. Neither answers the other's
678    /// question and the pair is what a caller needs: a code without a side
679    /// names nobody, and a side without a code is not grounds to publish
680    /// anything.
681    ///
682    /// Exhaustive, with no wildcard arm, so a variant added to this draft's
683    /// `EndpointError` is a compile error here rather than a silent arrival on
684    /// the wrong side of the answer. See
685    /// [`EndpointFault`](crate::above_codec_rules::EndpointFault) for the three
686    /// answers and for the collision that made the third one necessary.
687    pub fn fault(&self) -> crate::above_codec_rules::EndpointFault {
688        use crate::above_codec_rules::{AboveCodecRule as Rule, EndpointFault as Fault};
689
690        match self {
691            // Raised on both a receive path and a send path, so the
692            // variant cannot say which end is at fault. The state machines
693            // render as `invalid transition from X on event Y` whichever end
694            // asked for the transition, and the unknown-request errors name
695            // an id that may be one the peer sent or one a caller here made
696            // up.
697            EndpointError::Session(..)
698            | EndpointError::Subscription(..)
699            | EndpointError::Fetch(..)
700            | EndpointError::Fill(..)
701            | EndpointError::Namespace(..)
702            | EndpointError::TrackStatus(..)
703            | EndpointError::PublishFlow(..)
704            | EndpointError::Setup(..)
705            | EndpointError::UnknownRequest(..) => Fault::EitherEnd,
706
707            // Raised on the way out. Nothing reached the wire, so none of
708            // these is evidence about a peer — including the ones a peer
709            // caused, where what failed is this side's attempt to accept
710            // something the draft says to refuse.
711            EndpointError::NotAResponse(..)
712            | EndpointError::TrackPropertiesOnOutgoingRequestOk(..)
713            | EndpointError::FilterMustBeRejected(..)
714            | EndpointError::NoUpdateToAnswer(..)
715            | EndpointError::WrongUpdateFailureStatus { .. }
716            | EndpointError::NotActive
717            | EndpointError::Draining
718            | EndpointError::TrackAliasInUse { .. }
719            | EndpointError::StateNotifyThisEndpointMayNotSend(..)
720            | EndpointError::PeerPrefixOverlap { .. }
721            | EndpointError::WrongOverlapRefusal { .. } => Fault::ThisEndpoint,
722
723            // Raised reading what the peer sent.
724            EndpointError::NotARequest(..) => Fault::Peer(Rule::BidiStreamOpener),
725            EndpointError::DuplicateTrackAlias { .. } => Fault::Peer(Rule::DuplicateTrackAlias),
726            EndpointError::GoAwayUriAtServer => Fault::Peer(Rule::GoAwayAtServer),
727            EndpointError::ResponseOnControlStream
728            | EndpointError::RequestMessageOnControlStream(..)
729            | EndpointError::UnexpectedOnPeerRequestStream(..) => {
730                Fault::Peer(Rule::MessageOnTheWrongStream)
731            }
732            EndpointError::ObjectPastFinalObject { .. } => Fault::Peer(Rule::ObjectPastFinalObject),
733            EndpointError::RedirectTrackNameOnNamespaceRequest(..) => {
734                Fault::Peer(Rule::RedirectTrackNameOnNamespaceRequest)
735            }
736            EndpointError::RedirectUriAtServer => Fault::Peer(Rule::RedirectUriAtServer),
737            EndpointError::RepeatedGoAway | EndpointError::RepeatedGoAwayOnRequestStream(..) => {
738                Fault::Peer(Rule::RepeatedGoAway)
739            }
740            EndpointError::DuplicateRequestId(..) => Fault::Peer(Rule::RequestIdOutOfSequence),
741            // A REQUEST_UPDATE on the control stream is this rule and not
742            // `MessageOnTheWrongStream`. Section 9.5 states the rule as two
743            // permitted cases and closes over everything else — "The sender of
744            // a request (SUBSCRIBE, PUBLISH, FETCH, PUBLISH_NAMESPACE,
745            // SUBSCRIBE_NAMESPACE, SUBSCRIBE_TRACKS) can later send a
746            // REQUEST_UPDATE on the same bidi stream as the request to modify
747            // it. A subscriber can also send REQUEST_UPDATE to modify
748            // parameters of a subscription established with PUBLISH." — and
749            // the first case is *on the same bidi stream as the request*. One
750            // that arrives on the control stream is in neither case, so it is
751            // squarely inside "other than in the two cases above", which is the
752            // sentence this rule cites. Filed under the wrong-stream rule it
753            // would carry a close no draft states; filed here, it carries the
754            // one draft-19 and draft-21 do.
755            EndpointError::RequestUpdateOnControlStream
756            | EndpointError::UnexpectedRequestUpdate(..) => {
757                Fault::Peer(Rule::RequestUpdateForTheWrongRequest)
758            }
759            EndpointError::ResponseBeforeTheFirstResponse(..) => {
760                Fault::Peer(Rule::ResponseBeforeItsFirstResponse)
761            }
762            EndpointError::StateNotifyForNonSubscription(..)
763            | EndpointError::StateNotifyFromSubscriber(..) => {
764                Fault::Peer(Rule::StateNotifyOnTheWrongRequest)
765            }
766            EndpointError::TooManyRequestUpdates(..) => Fault::Peer(Rule::TooManyRequestUpdates),
767            EndpointError::TrackPropertiesOnNonTrackStatus(..) => {
768                Fault::Peer(Rule::TrackPropertiesOnNonTrackStatus)
769            }
770            EndpointError::UnrequestedFillStream(..) => Fault::Peer(Rule::UnrequestedFillStream),
771
772            // The Request ID rules, which are the peer's: every one of them is
773            // read off an id the peer put on the wire. This draft carries no
774            // MAX_REQUEST_ID, so neither ceiling arm can fire — the allocator
775            // opens at `u64::MAX` and nothing ever lowers it - and they are
776            // answered rather than left out so that a draft restoring the
777            // message does not restore a hole with it.
778            EndpointError::RequestId(e) => match e {
779                RequestIdError::Decreased(..) => Fault::Peer(Rule::MaxRequestIdDecreased),
780                RequestIdError::ExceedsMax(..) => Fault::Peer(Rule::RequestIdCeiling),
781                RequestIdError::WrongParity(..) => Fault::Peer(Rule::RequestIdParity),
782                // This endpoint has spent the budget the peer granted it.
783                RequestIdError::Blocked => Fault::ThisEndpoint,
784            },
785        }
786    }
787
788    /// The code to close the session with, when draft-21 says this error is
789    /// fatal to the session rather than to one request.
790    ///
791    /// `None` means the error is recoverable: the caller may report it and
792    /// keep the session running. `Some` means the draft requires a close, and
793    /// the endpoint has already moved its own session state to
794    /// [`SessionState::Closed`] — the code is what the transport should carry.
795    ///
796    /// The two codes are not interchangeable. Section 6.3 gives
797    /// PROTOCOL_VIOLATION for a bidirectional stream that begins with the wrong
798    /// message type; Section 6.4.2.1 gives INVALID_REQUEST_ID for a Request ID
799    /// with the wrong least significant bit or a duplicate one. A peer checking
800    /// close codes can tell the two apart, so this must too.
801    pub fn session_error_code(&self) -> Option<SessionErrorCode> {
802        match self {
803            EndpointError::RequestUpdateOnControlStream
804            | EndpointError::UnexpectedRequestUpdate(_)
805            | EndpointError::TrackPropertiesOnNonTrackStatus(_)
806            | EndpointError::RedirectUriAtServer
807            | EndpointError::RedirectTrackNameOnNamespaceRequest(_)
808            | EndpointError::ResponseBeforeTheFirstResponse(..)
809            | EndpointError::NotARequest(_)
810            | EndpointError::GoAwayUriAtServer
811            | EndpointError::RepeatedGoAway
812            | EndpointError::RepeatedGoAwayOnRequestStream(_)
813            // Section 9.10 names the code in the sentence that states the
814            // rule: a PUBLISH_STATE_NOTIFY "for any other request type, or from
815            // the subscriber, MUST close the session with a
816            // PROTOCOL_VIOLATION".
817            | EndpointError::StateNotifyForNonSubscription(_)
818            | EndpointError::StateNotifyFromSubscriber(_) => {
819                Some(SessionErrorCode::ProtocolViolation)
820            }
821            EndpointError::DuplicateRequestId(_)
822            | EndpointError::RequestId(RequestIdError::WrongParity(..)) => {
823                Some(SessionErrorCode::InvalidRequestId)
824            }
825            EndpointError::TooManyRequestUpdates(..) => {
826                Some(SessionErrorCode::TooManyRequestUpdates)
827            }
828            // Section 3.1.2 names this code in the sentence that states the
829            // rule, and names no other. A close carrying PROTOCOL_VIOLATION
830            // would tell the peer a different thing went wrong.
831            EndpointError::DuplicateTrackAlias { .. } => {
832                Some(SessionErrorCode::DuplicateTrackAlias)
833            }
834            // Stated rather than left to the arm below, because the neighbour
835            // above makes the opposite choice about a rule of the same shape.
836            // MAX_REQUEST_UPDATES is a ceiling the draft answers with a session
837            // close; MAX_FILTER_RANGES is a ceiling it answers with a
838            // REQUEST_ERROR. Two consecutive subsections, two different answers,
839            // and nothing about either sentence signals which.
840            EndpointError::FilterMustBeRejected(..) => None,
841            _ => None,
842        }
843    }
844}
845
846pub struct Endpoint {
847    role: Role,
848    session: SessionStateMachine,
849    request_ids: RequestIdAllocator,
850    subscriptions: HashMap<u64, SubscriptionStateMachine>,
851    fetches: HashMap<u64, FetchStateMachine>,
852    /// Which subscriptions have asked for a fill fetch stream, and how many of
853    /// their streams are open.
854    ///
855    /// Keyed by the subscription's Request ID, which is also the Request ID a
856    /// fill fetch stream's FETCH_HEADER carries — Section 3.4: "the SUBSCRIBE
857    /// Request ID for the initial fill, or the REQUEST_UPDATE Request ID for a
858    /// subsequent fill", and a REQUEST_UPDATE travels on its request's own
859    /// stream under that request's Request ID (Section 9.5). So the two ids
860    /// the section distinguishes are the same number, and the same section
861    /// nonetheless says a subscription "can have multiple fill fetch streams
862    /// open at once, each identified by its Request ID". **The draft is
863    /// inconsistent here and the identifier cannot separate them**, so this
864    /// counts the open streams rather than pretending to tell them apart, which
865    /// is what a PUBLISH_DONE's Stream Count needs anyway.
866    ///
867    /// An entry appears when a SUBSCRIBE or a REQUEST_UPDATE carrying
868    /// `FILL_PARAMETERS` is sent or received, and is never removed: Section
869    /// 9.20.16 keeps `FILL_PARAMETERS` off the sticky-parameter rules, but a
870    /// subscription that asked once is a subscription whose later FETCH_HEADERs
871    /// are attributable, and a Request ID is spent once so an entry can never
872    /// come to describe a different request.
873    fills: HashMap<u64, FillStreams>,
874    subscribe_namespaces: HashMap<u64, SubscribeNamespaceStateMachine>,
875    subscribe_tracks: HashMap<u64, SubscribeNamespaceStateMachine>,
876    publish_namespaces: HashMap<u64, PublishNamespaceStateMachine>,
877    track_statuses: HashMap<u64, TrackStatusStateMachine>,
878    publishes: HashMap<u64, PublishStateMachine>,
879    goaway_uri: Option<Vec<u8>>,
880    /// The peer's requests whose stream has already carried a GOAWAY.
881    ///
882    /// Section 9.2 makes the second GOAWAY on one request stream a session
883    /// close while leaving a first one on every other stream legal, so the
884    /// count cannot live on the session. Held apart from the per-kind request
885    /// maps because a GOAWAY says nothing about which kind of request it
886    /// migrates.
887    goaway_request_streams: HashSet<u64>,
888    /// Every Request ID the peer has spent, whether or not the request it
889    /// opened is still live.
890    ///
891    /// Draft-21 Section 6.4.2.1 makes a duplicate Request ID a session close, and
892    /// "duplicate" is about the id ever having been used, not about the
893    /// request still being open. Deriving it from the per-kind maps instead
894    /// would answer wrongly the moment those maps are ever pruned, so the rule
895    /// is stated once, here, and this set is never pruned.
896    peer_request_ids: HashSet<u64>,
897    /// Every request the **peer** opened a stream with, as it arrived, keyed
898    /// by the Request ID it carries.
899    ///
900    ///
901    /// One map for all seven kinds, because one entry point takes all seven:
902    /// [`receive_request_on_stream`](Self::receive_request_on_stream) is the
903    /// only thing that writes here, so what is in here is a request, and it
904    /// is the peer's, by construction. How far each one has got stays in the
905    /// per-kind map beside this endpoint's own requests, which is where every
906    /// later message on the stream reads it and where the parity of a Request
907    /// ID keeps the two ends apart.
908    ///
909    /// What a map of state machines cannot hold is the request. Section 3.1 has
910    /// the subscriber "either accepts or rejects the subscription", and what is
911    /// being accepted or rejected -- the track, the namespace prefix, the
912    /// parameters -- is named in the request and nowhere else once the message
913    /// has been dropped.
914    inbound_requests: HashMap<u64, ControlMessage>,
915    /// The request each namespace subscription of the peer's overlapped when
916    /// it arrived, keyed by the Request ID of the one that arrived.
917    ///
918    /// The prefix is judged where the sentence says it is judged, on receipt,
919    /// and the verdict is read again when the answer is written. An entry
920    /// means this endpoint owes that request a REQUEST_ERROR and may send it
921    /// nothing else.
922    overlapping_namespace_subscriptions: HashMap<u64, u64>,
923    /// The Track Namespace Prefix a REQUEST_UPDATE asks one of the peer's
924    /// namespace subscriptions to move to, held until that update is
925    /// answered.
926    ///
927    /// Section 9.5.2 ties the move to the acceptance: "If the update is
928    /// accepted, NAMESPACE and NAMESPACE_DONE messages following the
929    /// REQUEST_OK will contain Track Namespace suffixes relative to the
930    /// updated prefix." Until then the subscription still selects what the
931    /// peer opened it with, so a later request is weighed against the prefix
932    /// in `inbound_requests` and not against this one.
933    updated_namespace_prefixes: HashMap<u64, TrackNamespace>,
934    /// The subscription an unanswered prefix update would collide with, when
935    /// it would, keyed by the Request ID of the one being moved.
936    ///
937    /// Separate from `overlapping_namespace_subscriptions` because one
938    /// Request ID can be carrying both verdicts at once: the request that
939    /// opened the stream has one and an update on it has another, and they
940    /// are settled by different messages on that same stream.
941    overlapping_prefix_updates: HashMap<u64, u64>,
942    /// The MAX_REQUEST_UPDATES this endpoint put in its own SETUP.
943    ///
944    /// The peer's value is a different number and belongs to the sending side,
945    /// which this type has no path for: draft-21 gives the endpoint no
946    /// REQUEST_UPDATE builder, so there is nothing here to hold back.
947    ///
948    /// Zero means no limit rather than none allowed, which is the opposite of
949    /// how MAX_REQUEST_ID reads. Section 9.1.7 says so outright - "A value
950    /// of 0 means the endpoint does not limit REQUEST_UPDATE concurrency. If
951    /// not present, the default value is 0" - so an endpoint that never sends
952    /// the option is not limiting anything, and a check that read zero as a
953    /// ceiling would refuse the first update of every session.
954    advertised_max_request_updates: u64,
955    /// Per request stream, how many REQUEST_UPDATEs the peer has sent that this
956    /// endpoint has not yet answered.
957    ///
958    /// "Outstanding" is Section 9.1.7's word and it is per stream, not per
959    /// session: "Each REQUEST_OK or REQUEST_ERROR response restores one credit
960    /// on that stream."
961    outstanding_peer_updates: HashMap<u64, u64>,
962    /// Per request stream, how many REQUEST_UPDATEs are still waiting for the
963    /// answer Section 9.5 requires.
964    ///
965    /// A second count rather than a reading of the one above, because the two
966    /// sentences count different things and disagree on exactly one response.
967    /// Section 9.1.7 says "Each REQUEST_OK or REQUEST_ERROR response
968    /// restores one credit on that stream" — every response, including the
969    /// REQUEST_OK that answers a SUBSCRIBE_NAMESPACE rather than an update. That
970    /// is the sender's accounting rule as much as the receiver's, and an
971    /// endpoint that credited more carefully than the peer does would close a
972    /// conforming session, so the credit above stays literal.
973    ///
974    /// This one is about which response answers which message, and there the
975    /// request's own REQUEST_OK answers the request. Keeping them apart is what
976    /// lets a response be recognised as an update's answer without changing
977    /// what the peer is allowed to send.
978    unanswered_peer_updates: HashMap<u64, u64>,
979    /// The requests whose refused update has not been followed by the
980    /// PUBLISH_DONE that ends them.
981    ///
982    /// Emptied as each is written. A request is in here for exactly as long as
983    /// this endpoint owes the peer the second half of a refusal.
984    owed_update_failures: HashSet<u64>,
985    /// The peer's requests whose own response has already been written.
986    ///
987    /// Section 9.5 gives an update the same two answers a request has, and on
988    /// the five kinds REQUEST_OK answers, the message that answers the request
989    /// and the message that answers an update are the same message. Nothing on
990    /// the wire tells them apart, so both endpoints resolve it by order: the
991    /// first response on a stream answers the request that opened it and the
992    /// ones after it answer updates. This set is that order, recorded.
993    answered_peer_requests: HashSet<u64>,
994    /// The MAX_FILTER_RANGES this endpoint put in its own SETUP.
995    ///
996    /// Section 9.1.6 makes it a ceiling on the peer's total number of Ranges
997    /// (Start/End pairs) allowed concurrently in all Range filter parameters for
998    /// a given subscription or fetch, and fixes what its absence means: "The
999    /// default value is 0, so if not specified, the peer MUST NOT send any such
1000    /// filter parameters."
1001    ///
1002    /// So zero is none allowed, and it is the default. The option in the
1003    /// subsection after this one, MAX_REQUEST_UPDATES, reads its zero the other
1004    /// way — no limit — and the two are otherwise the same shape. Reading either
1005    /// with the other's rule is a working implementation that is wrong in one
1006    /// direction or the other for every session.
1007    advertised_max_filter_ranges: u64,
1008    /// Requests whose Range Filters this endpoint owes the peer a REQUEST_ERROR
1009    /// about.
1010    ///
1011    /// Keyed by Request ID, because that is what the reply names. Entries are
1012    /// spent by the REQUEST_ERROR that answers them, and a REQUEST_OK offered
1013    /// for one is refused: taking the request in and then accepting it would
1014    /// leave the peer with a subscription whose filters were never agreed.
1015    peer_filter_rejections: HashMap<u64, FilterRejection>,
1016    /// The Range Filter parameters currently in force on each of the peer's
1017    /// requests.
1018    ///
1019    /// Section 3.3.2 makes the ceiling a property of the request rather than of
1020    /// the message that carried it — "the total number of Ranges allowed in all
1021    /// Range Filter parameters for a given subscription or fetch" — and lets a
1022    /// REQUEST_UPDATE rewrite the set: "In REQUEST_UPDATE, Length of 0 removes
1023    /// the filter; non-zero replaces it entirely. If a filter parameter is
1024    /// omitted from REQUEST_UPDATE, it is unchanged."
1025    ///
1026    /// So the count that matters is of what is in force after the update, and
1027    /// the parameters an update leaves alone are part of it. Held as the
1028    /// parameters rather than as decoded filters because replacement is by
1029    /// Parameter Type, which is the key of the pair.
1030    peer_request_filters: HashMap<u64, Vec<KeyValuePair>>,
1031    /// What Full Track Name the peer has attached each Track Alias to, per
1032    /// Request ID.
1033    ///
1034    /// Section 3.1.2 forbids one alias naming two tracks at once, and the "at
1035    /// once" is what makes this a table rather than a set: an alias the peer
1036    /// used for a track whose subscription has ended is free again. The table
1037    /// therefore records the binding and reads liveness back off the request's
1038    /// own state machine, rather than keeping a second copy of it that every
1039    /// path ending a subscription would have to remember to prune.
1040    track_bindings: HashMap<u64, TrackBinding>,
1041    /// The track each fetch this endpoint made is for.
1042    ///
1043    /// Not in `track_bindings`, because that table exists to answer questions
1044    /// about Track Aliases and a fetch has none: its objects arrive on a stream
1045    /// that opens by naming the Request ID.
1046    ///
1047    /// Every fetch names its own track on this draft, which is one thing the
1048    /// FETCH rewrite made simpler. Draft-19's Joining Fetch named none and took
1049    /// the joined subscription's, so the track had to be resolved through the
1050    /// join as the fetch was made rather than at the withdrawal — a fill
1051    /// outlives the subscription it was asked from, so a lookup through the
1052    /// join came up empty exactly while there was still a fetch to cancel.
1053    /// Draft-21 Section 9.11 deleted the joining fetch; a fill fetch stream
1054    /// belongs to its subscription and is tracked in `fills`.
1055    fetch_tracks: HashMap<u64, FetchTrack>,
1056    /// Which tracks this endpoint has given up on, and what for.
1057    ///
1058    /// Behind a lock because the note is taken on the data plane, where this
1059    /// endpoint is reached through `&self`.
1060    malformed: Mutex<MalformedTracks>,
1061    /// How far each track's objects have reached, and where a track ended.
1062    ///
1063    /// An `Arc` because a subgroup stream measures its objects against one
1064    /// track for as long as it runs, and the handle it holds outlives any
1065    /// single call into this endpoint.
1066    locations: Arc<Mutex<TrackLocations>>,
1067}
1068
1069/// What one subscription's fill fetch streams amount to, from the point of
1070/// view of an endpoint that has to attribute an arriving FETCH_HEADER and
1071/// count the streams a PUBLISH_DONE reports.
1072///
1073/// There is no state machine here on purpose. A fill fetch stream has no
1074/// FETCH_OK and no REQUEST_ERROR (Section 3.4.1), so it has none of the
1075/// states [`FetchStateMachine`] exists to keep apart: it is opened, it carries
1076/// objects, and it ends with a FIN meaning the fill is complete or a reset
1077/// meaning it failed. Neither ending touches the subscription — "Resetting or
1078/// cancelling a fill fetch stream, by either endpoint, does not affect the
1079/// subscription, which continues to deliver objects using subscribe subgroups
1080/// and datagrams."
1081#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1082struct FillStreams {
1083    /// How many of this subscription's fill fetch streams are open.
1084    open: usize,
1085    /// How many have been opened over the life of the subscription, ended or
1086    /// not.
1087    ///
1088    /// Section 9.9 makes the count a PUBLISH_DONE field: Stream Count is the
1089    /// total the publisher opened, "including streams that contained no Objects
1090    /// (e.g., an empty Subgroup) and including any fill fetch streams". So the
1091    /// number that matters is cumulative and the open count cannot answer it.
1092    opened: u64,
1093    /// The Group Order the fill's Objects arrive in, resolved from the request
1094    /// that asked for the fill by [`fill::group_order`].
1095    ///
1096    /// Held here because nothing on a fill fetch stream says which direction its
1097    /// Group ID Deltas count in (Section 11.4.1.1) and the answer is in a
1098    /// control message the stream never sees. Kept per subscription rather than
1099    /// per stream for the reason the field above is: a fill fetch stream carries
1100    /// its subscription's Request ID and nothing else, so two of them cannot be
1101    /// told apart here. A REQUEST_UPDATE that asks for a second fill with a
1102    /// different order **replaces** this, which is right for the stream it opens
1103    /// and wrong for one still running under the older order; a caller in that
1104    /// position restarts the reader itself with
1105    /// [`begin_fetch_objects`](crate::draft21::connection::FramedRecvStream::begin_fetch_objects).
1106    group_order: GroupOrder,
1107}
1108
1109/// The track one fetch is for, which the fetch's own state machine does not
1110/// hold.
1111#[derive(Debug, Clone)]
1112struct FetchTrack {
1113    namespace: TrackNamespace,
1114    name: Vec<u8>,
1115}
1116
1117/// A Track Alias the peer has attached to a Full Track Name, and the request
1118/// whose lifetime the attachment follows.
1119#[derive(Debug, Clone)]
1120struct TrackBinding {
1121    namespace: TrackNamespace,
1122    name: Vec<u8>,
1123    /// The alias, once the peer has named one.
1124    ///
1125    /// A SUBSCRIBE this endpoint sends names a track and waits for its alias,
1126    /// so the binding exists with no alias in it from the moment the request
1127    /// is made until its SUBSCRIBE_OK arrives. A PUBLISH carries both at once
1128    /// and is never in that state.
1129    alias: Option<u64>,
1130    kind: BindingKind,
1131}
1132
1133/// Which of the two sequences Section 3.1 names established the subscription
1134/// that owns a binding, and so which state machine says whether it still has
1135/// one.
1136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1137enum BindingKind {
1138    /// This endpoint's SUBSCRIBE, established by the peer's SUBSCRIBE_OK.
1139    Subscribe,
1140    /// A PUBLISH, established by the PUBLISH_OK answering it - whichever end
1141    /// sent which. Both are held in the same map, which Request ID parity
1142    /// keeps from colliding.
1143    Publish,
1144}
1145
1146impl Endpoint {
1147    pub fn new(role: Role) -> Self {
1148        Self {
1149            role,
1150            session: SessionStateMachine::new(),
1151            request_ids: RequestIdAllocator::new(role),
1152            subscriptions: HashMap::new(),
1153            fetches: HashMap::new(),
1154            fills: HashMap::new(),
1155            subscribe_namespaces: HashMap::new(),
1156            subscribe_tracks: HashMap::new(),
1157            publish_namespaces: HashMap::new(),
1158            track_statuses: HashMap::new(),
1159            publishes: HashMap::new(),
1160            goaway_uri: None,
1161            goaway_request_streams: HashSet::new(),
1162            peer_request_ids: HashSet::new(),
1163            inbound_requests: HashMap::new(),
1164            overlapping_namespace_subscriptions: HashMap::new(),
1165            updated_namespace_prefixes: HashMap::new(),
1166            overlapping_prefix_updates: HashMap::new(),
1167            advertised_max_request_updates: 0,
1168            outstanding_peer_updates: HashMap::new(),
1169            unanswered_peer_updates: HashMap::new(),
1170            owed_update_failures: HashSet::new(),
1171            answered_peer_requests: HashSet::new(),
1172            advertised_max_filter_ranges: 0,
1173            peer_filter_rejections: HashMap::new(),
1174            peer_request_filters: HashMap::new(),
1175            track_bindings: HashMap::new(),
1176            fetch_tracks: HashMap::new(),
1177            malformed: Mutex::new(MalformedTracks::new()),
1178            locations: Arc::new(Mutex::new(TrackLocations::new())),
1179        }
1180    }
1181
1182    /// The Track Alias the peer attached to `request_id`, once it has named
1183    /// one.
1184    ///
1185    /// Answers for a subscription this endpoint asked for from the moment its
1186    /// SUBSCRIBE_OK arrives, and for one the peer offered from the moment its
1187    /// PUBLISH does. `None` before that, and for a Request ID this session has
1188    /// no track for.
1189    pub fn track_alias_for(&self, request_id: VarInt) -> Option<VarInt> {
1190        let alias = self.track_bindings.get(&request_id.into_inner())?.alias?;
1191        VarInt::from_u64(alias).ok()
1192    }
1193
1194    /// The refusal Section 3.1.2 requires when `alias` already names a
1195    /// different track that still has an Established subscription, or `None`
1196    /// when it is free.
1197    ///
1198    /// # Why the set is read rather than kept
1199    ///
1200    /// "Established" is a subscription state Section 3.1 defines, and both
1201    /// state machines here already hold it: a subscription reaches it on
1202    /// SUBSCRIBE_OK and a publish on PUBLISH_OK, and each leaves it on the
1203    /// message that ends the flow. Asking them is what makes an alias free
1204    /// again the moment its track's subscription ends, with nothing to prune
1205    /// on the way out - and a path that ended a subscription without telling
1206    /// this table would otherwise leave the alias held forever and refuse the
1207    /// peer's next, conforming, use of it.
1208    ///
1209    /// # Why the request's own binding is skipped
1210    ///
1211    /// A SUBSCRIBE_OK is judged before its own alias is written down, so the
1212    /// skip is not what keeps it from finding itself. A PUBLISH is not: a
1213    /// second PUBLISH under a Request ID already bound is refused by the
1214    /// duplicate-Request-ID rule before it reaches here, and comparing a
1215    /// request against its own binding would answer the wrong rule if that one
1216    /// ever moved.
1217    fn conflicting_track_alias(
1218        &self,
1219        request_id: u64,
1220        alias: u64,
1221        namespace: &TrackNamespace,
1222        name: &[u8],
1223    ) -> Option<EndpointError> {
1224        for (&id, binding) in &self.track_bindings {
1225            if id == request_id || binding.alias != Some(alias) {
1226                continue;
1227            }
1228            if binding.namespace == *namespace && binding.name == name {
1229                continue;
1230            }
1231            if self.binding_is_established(id, binding.kind) {
1232                return Some(EndpointError::DuplicateTrackAlias {
1233                    alias,
1234                    established: id,
1235                    offered: request_id,
1236                });
1237            }
1238        }
1239        None
1240    }
1241
1242    /// The request already using `alias` for a track other than (`namespace`,
1243    /// `name`), or `None` when this endpoint may give the alias to that track.
1244    ///
1245    /// Separate from [`Self::conflicting_track_alias`] because the two answer
1246    /// different questions about the same table. That one judges a message
1247    /// that has arrived and ends the session over it; this one judges one that
1248    /// has not been built and declines to build it.
1249    fn alias_held_elsewhere(
1250        &self,
1251        alias: u64,
1252        namespace: &TrackNamespace,
1253        name: &[u8],
1254    ) -> Option<EndpointError> {
1255        self.track_bindings.iter().find_map(|(&id, binding)| {
1256            let other_track = binding.namespace != *namespace || binding.name != name;
1257            (binding.alias == Some(alias)
1258                && other_track
1259                && self.binding_is_in_use(id, binding.kind))
1260            .then_some(EndpointError::TrackAliasInUse { alias, held: id })
1261        })
1262    }
1263
1264    /// Whether a binding's request has put its alias in play at all. Broader
1265    /// than [`Self::binding_is_established`], and the two sentences are why.
1266    /// What a subscriber must close over is qualified - "the same Track Alias
1267    /// as a different Track with an Established subscription" - and the
1268    /// prohibition on the publisher is not: "The same Track Alias MUST NOT be
1269    /// used by a publisher to refer to two different Tracks simultaneously in
1270    /// the same session." Once a PUBLISH carrying an alias has been sent,
1271    /// giving that alias to a second track is what that sentence forbids,
1272    /// answered or not.
1273    fn binding_is_in_use(&self, id: u64, kind: BindingKind) -> bool {
1274        match kind {
1275            BindingKind::Subscribe => {
1276                self.subscriptions.get(&id).is_some_and(|sm| sm.state() != SubscriptionState::Done)
1277            }
1278            BindingKind::Publish => {
1279                self.publishes.get(&id).is_some_and(|sm| sm.state() != PublishState::Done)
1280            }
1281        }
1282    }
1283
1284    /// Whether the request that owns a binding still has an Established
1285    /// subscription.
1286    fn binding_is_established(&self, id: u64, kind: BindingKind) -> bool {
1287        match kind {
1288            BindingKind::Subscribe => self
1289                .subscriptions
1290                .get(&id)
1291                .is_some_and(|sm| sm.state() == SubscriptionState::Active),
1292            BindingKind::Publish => {
1293                self.publishes.get(&id).is_some_and(|sm| sm.state() == PublishState::Active)
1294            }
1295        }
1296    }
1297
1298    /// The track a live binding has given `alias` to.
1299    ///
1300    /// Read rather than kept: a binding whose request has ended holds nothing,
1301    /// and an alias that is free again may name a different track next.
1302    fn track_for_alias(&self, alias: u64) -> Option<(&TrackNamespace, &[u8])> {
1303        self.track_bindings.iter().find_map(|(&id, binding)| {
1304            (binding.alias == Some(alias) && self.binding_is_in_use(id, binding.kind))
1305                .then_some((&binding.namespace, binding.name.as_slice()))
1306        })
1307    }
1308
1309    /// Whether this endpoint *receives* the track a request names.
1310    ///
1311    /// The sentence to be answered is a subscriber's - "cancel any
1312    /// corresponding subscription or fetches for that Track from that
1313    /// publisher" - so a request that makes this endpoint the publisher is not
1314    /// one of them. A SUBSCRIBE in the table is always this endpoint's own,
1315    /// because a SUBSCRIBE the peer sends makes this endpoint the publisher and
1316    /// leaves no binding. A PUBLISH is in the table either way round, and
1317    /// Request ID parity is what separates them: the offer the peer made is the
1318    /// one this endpoint receives a track through.
1319    fn receives_through(&self, id: u64, kind: BindingKind) -> bool {
1320        match kind {
1321            BindingKind::Subscribe => true,
1322            BindingKind::Publish => self.request_ids.validate_peer_id(id).is_ok(),
1323        }
1324    }
1325
1326    /// The record a stream carrying `alias`'s objects measures them against.
1327    ///
1328    /// `None` for an alias no live binding names: an object for one breaks a
1329    /// different rule, and measuring it against a track this endpoint never
1330    /// asked for would answer that one with the wrong sentence.
1331    pub fn track_objects(&self, alias: u64) -> Option<TrackObjects> {
1332        let (namespace, name) = self.track_for_alias(alias)?;
1333        Some(TrackObjects::new(
1334            Arc::clone(&self.locations),
1335            namespace.clone(),
1336            name.to_vec(),
1337            alias,
1338        ))
1339    }
1340
1341    /// Record or judge one object that arrived outside a subgroup stream, and
1342    /// report Section 12.1's Malformed Track when it arrived after the place
1343    /// an end-of-track object put the end.
1344    ///
1345    /// One rule and not two. The placement rule drafts 08 through 13 state
1346    /// about an end-of-track object is not in this draft, so an object that
1347    /// ends a track here is judged against nothing and only settles where the
1348    /// track stopped.
1349    ///
1350    /// `&self`, because the call site is the data plane's.
1351    pub fn note_received_object(
1352        &self,
1353        alias: u64,
1354        at: ObjectLocation,
1355        role: ObjectRole,
1356    ) -> Result<(), EndpointError> {
1357        let Some(objects) = self.track_objects(alias) else { return Ok(()) };
1358        objects.note_past_final(at, role).map_err(|end| EndpointError::ObjectPastFinalObject {
1359            alias,
1360            group: at.group,
1361            object: at.object,
1362            final_group: end.group,
1363            final_object: end.object,
1364        })
1365    }
1366
1367    /// The condition a track was withdrawn for, or `None` for a track this
1368    /// endpoint has found nothing wrong with.
1369    ///
1370    /// # Why this takes a track and not the alias the object carried
1371    ///
1372    /// An alias only means anything through a live binding, and the withdrawal
1373    /// ends the binding it would have been resolved through. An accessor taking
1374    /// an alias would therefore answer `None` from the instant it had something
1375    /// to say. The record is keyed on the track, and so is this.
1376    pub fn malformed_track(
1377        &self,
1378        namespace: &TrackNamespace,
1379        name: &[u8],
1380    ) -> Option<MalformedTrackCondition> {
1381        self.malformed
1382            .lock()
1383            .unwrap_or_else(|poisoned| poisoned.into_inner())
1384            .condition(namespace, name)
1385    }
1386
1387    /// Note a track as malformed and name every request through which this
1388    /// endpoint is receiving it, so the caller can cancel them.
1389    ///
1390    /// **This is the whole of what this crate can do here, and the reason is
1391    /// structural rather than a shortfall.** Section 12.1 asks a subscriber
1392    /// that detects a Malformed Track to "cancel any corresponding subscription
1393    /// or fetches for that Track from that publisher", and on this draft
1394    /// cancelling a request means resetting the request's own bidirectional
1395    /// stream - Section 6.4.2.3. Every request lives at the front of a
1396    /// stream of its own, and `Connection::recv_on_request_stream` hands that
1397    /// stream to the caller as a `RequestStream`. So the stream this answer
1398    /// operates on is not the endpoint's to touch, and no amount of state here
1399    /// changes that. What the endpoint can do is say which requests they are.
1400    ///
1401    /// # Nothing here ends a request
1402    ///
1403    /// Deliberately, and it is what keeps the answer to one. The flows are read
1404    /// and not moved, so a caller that passes each id to
1405    /// `Connection::cancel_request_stream` ends the request there - and the
1406    /// binding stops being in use, so a second object past the end finds no
1407    /// track for the alias and names nothing. The request ending is still what
1408    /// closes the loop, exactly as it is on the drafts that answer with a
1409    /// message; what changed is which side ends it. A caller that ignores the
1410    /// list gets named the same requests again, which is honest: the condition
1411    /// really did fire again.
1412    ///
1413    /// # What is named, and what is not
1414    ///
1415    /// Requests through which this endpoint *receives* the track: a SUBSCRIBE
1416    /// this endpoint sent, a PUBLISH the peer sent, and a fetch this endpoint
1417    /// made. A request that makes this endpoint the publisher is not one of
1418    /// them. Empty for an alias no live binding names,
1419    /// and empty for a track whose only requests are ones this endpoint
1420    /// publishes.
1421    ///
1422    /// Sorted, because a `HashMap` iterates in no order and two requests for
1423    /// one track is a shape a peer can produce.
1424    pub fn requests_for_malformed_track(
1425        &self,
1426        alias: u64,
1427        condition: MalformedTrackCondition,
1428    ) -> Vec<VarInt> {
1429        let Some((namespace, name)) = self.track_for_alias(alias) else { return Vec::new() };
1430        let (namespace, name) = (namespace.clone(), name.to_vec());
1431        self.malformed
1432            .lock()
1433            .unwrap_or_else(|poisoned| poisoned.into_inner())
1434            .note(&namespace, &name, condition);
1435        let mut ids: Vec<u64> = self
1436            .track_bindings
1437            .iter()
1438            .filter(|(&id, binding)| {
1439                binding.namespace == namespace
1440                    && binding.name == name
1441                    && self.binding_is_in_use(id, binding.kind)
1442                    && self.receives_through(id, binding.kind)
1443            })
1444            .map(|(&id, _)| id)
1445            .chain(self.fetch_tracks.iter().filter_map(|(&id, track)| {
1446                (track.namespace == namespace
1447                    && track.name == name
1448                    && self.fetches.get(&id).is_some_and(|sm| sm.state() != FetchState::Done))
1449                .then_some(id)
1450            }))
1451            .collect();
1452        ids.sort_unstable();
1453        ids.into_iter().filter_map(|id| VarInt::from_u64(id).ok()).collect()
1454    }
1455
1456    /// The conflict a SUBSCRIBE_OK's alias has with the tracks already bound.
1457    ///
1458    /// Separate from [`Self::conflicting_track_alias`] because the track a
1459    /// SUBSCRIBE_OK is about is not in the SUBSCRIBE_OK: it is the one this
1460    /// endpoint's own SUBSCRIBE asked for, which is why the request has to be
1461    /// looked up before the alias can be judged.
1462    fn conflicting_alias_for_subscribe_ok(&self, id: u64, alias: u64) -> Option<EndpointError> {
1463        let binding = self.track_bindings.get(&id)?;
1464        self.conflicting_track_alias(id, alias, &binding.namespace, &binding.name)
1465    }
1466
1467    pub fn role(&self) -> Role {
1468        self.role
1469    }
1470
1471    /// Returns the role of the peer, which is the other one.
1472    pub fn peer_role(&self) -> Role {
1473        match self.role {
1474            Role::Client => Role::Server,
1475            Role::Server => Role::Client,
1476        }
1477    }
1478
1479    /// Hold a refused update's ending to the status the draft names, and
1480    /// retire the obligation once that ending is written.
1481    ///
1482    /// Both routes to a PUBLISH_DONE come through here. A subscription this
1483    /// endpoint accepted is ended by a response on the peer's stream; a PUBLISH
1484    /// this endpoint sent is ended on its own, which does not go through the
1485    /// response path at all. The rule is the same either way, so it is stated
1486    /// once and called twice rather than written where each route happened to
1487    /// need it.
1488    fn require_update_failure_status(
1489        &mut self,
1490        id: u64,
1491        status_code: VarInt,
1492    ) -> Result<(), EndpointError> {
1493        if !self.owed_update_failures.contains(&id) {
1494            return Ok(());
1495        }
1496        let required = PublishDoneStatusCode::UpdateFailed as u64;
1497        if status_code.into_inner() != required {
1498            return Err(EndpointError::WrongUpdateFailureStatus { request: id, required });
1499        }
1500        self.owed_update_failures.remove(&id);
1501        Ok(())
1502    }
1503
1504    /// Whether the peer has sent a REQUEST_UPDATE on this request that has not
1505    /// been answered yet.
1506    ///
1507    /// Draft-21 Section 9.5: "A subscriber can also send REQUEST_UPDATE to
1508    /// modify parameters of a subscription established with PUBLISH", and the
1509    /// receiver of one "MUST respond with exactly one REQUEST_OK or
1510    /// REQUEST_ERROR message indicating if the update was successful".
1511    ///
1512    /// Asked by the connection layer, which otherwise writes nothing on a
1513    /// stream this endpoint opened. An update the peer sent on a PUBLISH is
1514    /// the one thing on such a stream that this endpoint has to answer, and
1515    /// this is what tells it apart from a response to its own request.
1516    pub fn has_unanswered_update(&self, request_id: VarInt) -> bool {
1517        self.unanswered_peer_updates.get(&request_id.into_inner()).is_some_and(|&n| n > 0)
1518    }
1519
1520    /// Whether the request `id` names a subscription this endpoint publishes.
1521    ///
1522    /// Draft-21 Section 9.5.1 gives a refused REQUEST_UPDATE three different
1523    /// consequences and picks between them by what was being updated: "When a
1524    /// REQUEST_UPDATE is unsuccessful, the publisher MUST also terminate the
1525    /// subscription by sending a PUBLISH_DONE with error code UPDATE_FAILED.
1526    /// When a REQUEST_UPDATE fails for a FETCH, the publisher MUST reset the
1527    /// FETCH data stream. When a REQUEST_UPDATE fails for a SUBSCRIBE_NAMESPACE,
1528    /// SUBSCRIBE_TRACKS or PUBLISH_NAMESPACE, the responder MUST close the bidi
1529    /// stream (see Section 6.4.2.2)."
1530    ///
1531    /// Only the first of the three is a message, and only the first is owed
1532    /// here. Recording it for the other two is what would hold their streams
1533    /// open past the close the third sentence requires.
1534    ///
1535    /// Two requests leave this endpoint publishing: a SUBSCRIBE the peer sent,
1536    /// and a PUBLISH this endpoint sent. Either can carry an update from the
1537    /// other side, and either is ended by a PUBLISH_DONE written from here. A
1538    /// peer's PUBLISH is neither, because the peer is the publisher on it, and
1539    /// it is told apart by having arrived rather than been sent.
1540    fn publishes_a_subscription(&self, id: u64) -> bool {
1541        matches!(self.inbound_requests.get(&id), Some(ControlMessage::Subscribe(_)))
1542            || (self.publishes.contains_key(&id) && !self.inbound_requests.contains_key(&id))
1543    }
1544
1545    /// Whether this request's refused update still owes the peer the
1546    /// PUBLISH_DONE that ends it.
1547    ///
1548    /// Asked by the connection layer, which owns the stream that termination
1549    /// has to be written on and therefore has to know not to close it. A
1550    /// REQUEST_ERROR answering the request itself ends the exchange and takes
1551    /// the stream with it; one answering an update does not, and nothing in
1552    /// the message tells the two apart.
1553    pub fn owes_update_failure(&self, request_id: VarInt) -> bool {
1554        self.owed_update_failures.contains(&request_id.into_inner())
1555    }
1556
1557    pub fn session_state(&self) -> SessionState {
1558        self.session.state()
1559    }
1560
1561    pub fn goaway_uri(&self) -> Option<&[u8]> {
1562        self.goaway_uri.as_deref()
1563    }
1564
1565    pub fn active_subscription_count(&self) -> usize {
1566        self.subscriptions.len()
1567    }
1568
1569    pub fn active_fetch_count(&self) -> usize {
1570        self.fetches.len()
1571    }
1572
1573    pub fn active_subscribe_namespace_count(&self) -> usize {
1574        self.subscribe_namespaces.len()
1575    }
1576
1577    pub fn active_subscribe_tracks_count(&self) -> usize {
1578        self.subscribe_tracks.len()
1579    }
1580
1581    pub fn active_publish_namespace_count(&self) -> usize {
1582        self.publish_namespaces.len()
1583    }
1584
1585    pub fn active_track_status_count(&self) -> usize {
1586        self.track_statuses.len()
1587    }
1588
1589    pub fn active_publish_count(&self) -> usize {
1590        self.publishes.len()
1591    }
1592
1593    /// How many Request IDs the peer has spent on this session.
1594    ///
1595    /// Nothing here removes an entry, so this only grows. A responder that
1596    /// wants a ceiling on peer-created state has to impose one itself — see
1597    /// the note on [`receive_request_on_stream`](Self::receive_request_on_stream).
1598    pub fn peer_request_count(&self) -> usize {
1599        self.peer_request_ids.len()
1600    }
1601
1602    // -- Session lifecycle ------------------------------------------
1603
1604    pub fn connect(&mut self) -> Result<(), EndpointError> {
1605        self.session.on_connect()?;
1606        Ok(())
1607    }
1608
1609    pub fn close(&mut self) -> Result<(), EndpointError> {
1610        self.session.on_close()?;
1611        Ok(())
1612    }
1613
1614    // -- Unified SETUP ----------------------------------------------
1615
1616    /// Generate a SETUP message. Both client and server use the same message
1617    /// type; only the role (and the order of send/receive) distinguishes them.
1618    pub fn send_setup(
1619        &mut self,
1620        options: Vec<KeyValuePair>,
1621    ) -> Result<ControlMessage, EndpointError> {
1622        let msg = Setup { options };
1623        setup::validate_setup(&msg, self.role)?;
1624        self.advertised_max_request_updates = setup_varint(&msg.options, MAX_REQUEST_UPDATES);
1625        self.advertised_max_filter_ranges = setup_varint(&msg.options, MAX_FILTER_RANGES);
1626        Ok(ControlMessage::Setup(msg))
1627    }
1628
1629    /// Process an incoming SETUP message. Transitions the session to Active.
1630    pub fn receive_setup(&mut self, msg: &Setup) -> Result<(), EndpointError> {
1631        setup::validate_setup(msg, self.peer_role())?;
1632        self.session.on_setup_complete()?;
1633        Ok(())
1634    }
1635
1636    // -- GoAway -----------------------------------------------------
1637
1638    pub fn receive_goaway(&mut self, msg: &GoAway) -> Result<(), EndpointError> {
1639        // Draft-21 Section 9.2: "If a server receives a GOAWAY with a
1640        // non-zero New Session URI Length it MUST close the session with a
1641        // PROTOCOL_VIOLATION." Migration is something a server offers a
1642        // client, never the other way round, so the URI is refused here rather
1643        // than stored and later followed.
1644        if self.role == Role::Server && !msg.new_session_uri.is_empty() {
1645            return Err(self.fail_session(EndpointError::GoAwayUriAtServer));
1646        }
1647        // Draft-21 Section 9.2: "The endpoint MUST close the session with a
1648        // PROTOCOL_VIOLATION (Section 12.2) if it receives more than one GOAWAY on the
1649        // control stream or on a single request stream." Draining is reached
1650        // from nowhere else - `on_goaway` is its only entry and this method is
1651        // that method's only caller - so the session state is the record of
1652        // the first GOAWAY having arrived. This is the control-stream half;
1653        // the per-stream half is in `receive_goaway_on_request_stream`.
1654        if self.session.state() == SessionState::Draining {
1655            return Err(self.fail_session(EndpointError::RepeatedGoAway));
1656        }
1657        self.session.on_goaway()?;
1658        self.goaway_uri = Some(msg.new_session_uri.clone());
1659        Ok(())
1660    }
1661
1662    /// Record that the session is over because the peer broke a rule the draft
1663    /// answers with a session close, and hand the error back unchanged.
1664    ///
1665    /// The state move is what makes the violation stick: every request entry
1666    /// point goes through [`require_active_or_err`](Self::require_active_or_err),
1667    /// so a caller that ignores the returned error still cannot start anything
1668    /// new. The close on the wire is the connection layer's job — see
1669    /// [`EndpointError::session_error_code`] for the code it should use.
1670    fn fail_session(&mut self, err: EndpointError) -> EndpointError {
1671        // `on_close` accepts SetupExchange, Active and Draining. A violation
1672        // seen in Connecting or Closed leaves the state machine alone: there
1673        // is no session to close, and the error itself is still the answer.
1674        //
1675        // SetupExchange is in that set because the Termination section says
1676        // "The Transport Session can be terminated at any point", and the
1677        // Setup exchange is a point. So a violation caught while the setup is
1678        // still in flight does close the session, and the discarded result is
1679        // safe because that is one of the states `on_close` accepts.
1680        let _ = self.session.on_close();
1681        err
1682    }
1683
1684    fn require_active_or_err(&self) -> Result<(), EndpointError> {
1685        match self.session.state() {
1686            SessionState::Active => Ok(()),
1687            SessionState::Draining => Err(EndpointError::Draining),
1688            _ => Err(EndpointError::NotActive),
1689        }
1690    }
1691
1692    // -- Subscribe flow ---------------------------------------------
1693
1694    pub fn subscribe(
1695        &mut self,
1696        track_namespace: TrackNamespace,
1697        track_name: Vec<u8>,
1698        parameters: Vec<KeyValuePair>,
1699    ) -> Result<(VarInt, ControlMessage), EndpointError> {
1700        self.require_active_or_err()?;
1701        let req_id = self.request_ids.allocate()?;
1702
1703        let mut sm = SubscriptionStateMachine::new();
1704        sm.on_subscribe_sent()?;
1705        self.subscriptions.insert(req_id.into_inner(), sm);
1706        // Section 9.20.16: the presence of FILL_PARAMETERS "is what requests a
1707        // fill fetch stream". Noted so that the FETCH_HEADER the publisher
1708        // opens next — which carries this SUBSCRIBE's Request ID, not a
1709        // fetch's — can be attributed to this subscription rather than
1710        // answered with "unknown request".
1711        self.note_fill_requested(req_id.into_inner(), &parameters);
1712        // The track is recorded here because this is the only place it is
1713        // known: a SUBSCRIBE_OK names an alias and not the track it is for.
1714        self.track_bindings.insert(
1715            req_id.into_inner(),
1716            TrackBinding {
1717                namespace: track_namespace.clone(),
1718                name: track_name.clone(),
1719                alias: None,
1720                kind: BindingKind::Subscribe,
1721            },
1722        );
1723
1724        let msg = ControlMessage::Subscribe(Subscribe {
1725            request_id: req_id,
1726            track_namespace,
1727            track_name,
1728            parameters,
1729        });
1730        Ok((req_id, msg))
1731    }
1732
1733    /// Process an incoming SUBSCRIBE_OK. Draft-21: no request_id on wire; the
1734    /// caller supplies the `request_id` of the bidi stream on which the
1735    /// response arrived.
1736    pub fn receive_subscribe_ok(
1737        &mut self,
1738        request_id: VarInt,
1739        msg: &SubscribeOk,
1740    ) -> Result<(), EndpointError> {
1741        let id = request_id.into_inner();
1742        if !self.subscriptions.contains_key(&id) {
1743            return Err(EndpointError::UnknownRequest(id));
1744        }
1745        let alias = msg.track_alias.into_inner();
1746        // Judged before the transition, so that the subscription this message
1747        // is about is not yet Established and cannot be found as its own
1748        // conflict, and so that a refused SUBSCRIBE_OK leaves no alias behind.
1749        if let Some(conflict) = self.conflicting_alias_for_subscribe_ok(id, alias) {
1750            return Err(self.fail_session(conflict));
1751        }
1752        let sm = self.subscriptions.get_mut(&id).expect("checked above");
1753        sm.on_subscribe_ok()?;
1754        if let Some(binding) = self.track_bindings.get_mut(&id) {
1755            binding.alias = Some(alias);
1756        }
1757        Ok(())
1758    }
1759
1760    /// Take one of the REQUEST_UPDATE credits this endpoint advertised for
1761    /// `id`'s stream.
1762    ///
1763    /// Section 9.1.7 puts the limit on the number *outstanding*, so the
1764    /// count is a running balance rather than a total: it goes up on each
1765    /// REQUEST_UPDATE received and down on each REQUEST_OK or REQUEST_ERROR
1766    /// this endpoint writes back, and a peer that keeps pace never reaches the
1767    /// ceiling however many updates it sends.
1768    ///
1769    /// # Errors
1770    ///
1771    /// [`EndpointError::TooManyRequestUpdates`], which answers
1772    /// `Some(TooManyRequestUpdates)` - its own close code, not the general
1773    /// PROTOCOL_VIOLATION - and the session is failed before it returns.
1774    fn spend_update_credit(&mut self, id: u64) -> Result<(), EndpointError> {
1775        let limit = self.advertised_max_request_updates;
1776        if limit == 0 {
1777            return Ok(());
1778        }
1779        let outstanding = self.outstanding_peer_updates.entry(id).or_insert(0);
1780        if *outstanding >= limit {
1781            return Err(self.fail_session(EndpointError::TooManyRequestUpdates(id, limit)));
1782        }
1783        *outstanding += 1;
1784        Ok(())
1785    }
1786
1787    /// Give back the credit a REQUEST_OK or REQUEST_ERROR restores.
1788    ///
1789    /// Called for every response this endpoint writes, whether or not the
1790    /// stream ever carried an update: a stream with no outstanding updates has
1791    /// nothing to restore and the saturating subtraction says so, which is
1792    /// cheaper than deciding first whether the response is answering an update
1793    /// or the original request.
1794    fn restore_update_credit(&mut self, id: u64) {
1795        if let Some(outstanding) = self.outstanding_peer_updates.get_mut(&id) {
1796            *outstanding = outstanding.saturating_sub(1);
1797        }
1798    }
1799
1800    /// Process a REQUEST_UPDATE that arrived on the bidi request stream
1801    /// identified by `request_id`.
1802    ///
1803    /// Draft-21 Section 9.5: "The sender of a request (SUBSCRIBE, PUBLISH,
1804    /// FETCH, PUBLISH_NAMESPACE, SUBSCRIBE_NAMESPACE, SUBSCRIBE_TRACKS) can
1805    /// later send a REQUEST_UPDATE on the same bidi stream as the request to
1806    /// modify it. A subscriber can also send REQUEST_UPDATE to modify
1807    /// parameters of a subscription established with PUBLISH." Anything else
1808    /// "MUST close the session with a PROTOCOL_VIOLATION", which is what
1809    /// [`EndpointError::UnexpectedRequestUpdate`] carries — TRACK_STATUS most
1810    /// of all, since Section 9.13 says outright that "the subscriber cannot
1811    /// send REQUEST_UPDATE".
1812    ///
1813    /// The message carries a Request ID of its own and the stream carries one
1814    /// too. They name the same request when the peer is conforming; a
1815    /// disagreement means the update was sent on a stream that is not its
1816    /// request's, which is the same violation, so it is refused rather than
1817    /// silently resolved to one of the two.
1818    ///
1819    /// Only SUBSCRIBE-established subscriptions have a state-machine event for
1820    /// this. That is not an omission: an update changes a request's parameters
1821    /// and not its lifecycle, so for the other five kinds the update is a
1822    /// self-transition with nothing to record.
1823    pub fn receive_request_update(
1824        &mut self,
1825        request_id: VarInt,
1826        msg: &RequestUpdate,
1827    ) -> Result<(), EndpointError> {
1828        let id = request_id.into_inner();
1829        if msg.request_id.into_inner() != id {
1830            return Err(self.fail_session(EndpointError::UnexpectedRequestUpdate(
1831                msg.request_id.into_inner(),
1832            )));
1833        }
1834        self.spend_update_credit(id)?;
1835        // The two cases are about who is sending, and only one of them is
1836        // about which request. Case one is the peer updating a request the
1837        // peer made, whatever its kind; case two is a subscriber updating a
1838        // subscription this endpoint established with PUBLISH. A SUBSCRIBE, a
1839        // FETCH or a namespace request this endpoint made is in neither, and
1840        // an update on one is a violation rather than a request to apply.
1841        //
1842        // They are not symmetrical about timing either. Case one allows an
1843        // update "later" and says nothing about the answer, so a request of
1844        // the peer's may be updated before this endpoint has answered it.
1845        // Case two rests on the subscription existing, and Section 3.1 says
1846        // when it does: "Once either of these sequences is successful, the
1847        // subscription moves to the Established state and can be updated by
1848        // the subscriber using REQUEST_UPDATE." A PUBLISH still waiting for
1849        // its answer is Pending, and an update on one is outside both cases.
1850        //
1851        // Checked before the state machine below moves, so a session that is
1852        // closing does not leave a subscription updated on the way out.
1853        let one_of_the_two_cases = self.inbound_requests.contains_key(&id)
1854            || self.binding_is_established(id, BindingKind::Publish);
1855        if !one_of_the_two_cases {
1856            return Err(self.fail_session(EndpointError::UnexpectedRequestUpdate(id)));
1857        }
1858        let updatable = if let Some(sm) = self.subscriptions.get_mut(&id) {
1859            sm.on_subscribe_update()?;
1860            true
1861        } else {
1862            self.publishes.contains_key(&id)
1863                || self.fetches.contains_key(&id)
1864                || self.subscribe_namespaces.contains_key(&id)
1865                || self.subscribe_tracks.contains_key(&id)
1866                || self.publish_namespaces.contains_key(&id)
1867        };
1868        if !updatable {
1869            return Err(self.fail_session(EndpointError::UnexpectedRequestUpdate(id)));
1870        }
1871        *self.unanswered_peer_updates.entry(id).or_insert(0) += 1;
1872
1873        // Section 3.4.1: "A publisher opens a fill fetch stream when it
1874        // processes a SUBSCRIBE or REQUEST_UPDATE that carries FILL_PARAMETERS
1875        // while Forward State is 1", and "A REQUEST_UPDATE that does not carry
1876        // FILL_PARAMETERS does not open a new fill fetch stream." So an update
1877        // is the second place a fill can be asked for, and the only place a
1878        // second one can. The Forward State half of that sentence is the
1879        // publisher's decision about whether to open the stream, not this
1880        // endpoint's about whether a stream is attributable, so it is not
1881        // applied here: a fill fetch stream that arrives is one to account
1882        // for, whatever this endpoint thought the Forward State was.
1883        self.note_fill_requested(id, &msg.parameters);
1884
1885        // The ceiling is on what the request carries after the update, so the
1886        // update is merged into the set in force and the whole set measured.
1887        // Recorded rather than refused for the reason the request itself is:
1888        // the answer is a REQUEST_ERROR, which names a Request ID.
1889        let mut in_force = self.peer_request_filters.remove(&id).unwrap_or_default();
1890        apply_filter_update(&mut in_force, &msg.parameters);
1891        if let Some(rejection) = self.filter_verdict(&in_force) {
1892            self.peer_filter_rejections.insert(id, rejection);
1893        }
1894        self.peer_request_filters.insert(id, in_force);
1895
1896        // Section 9.5.2 gives an update one thing to move that no draft before
1897        // this one lets a request change: "A subscriber can update the Track
1898        // Namespace Prefix of an established SUBSCRIBE_NAMESPACE or
1899        // SUBSCRIBE_TRACKS by including the TRACK_NAMESPACE_PREFIX parameter
1900        // (Section 9.20.21) in a REQUEST_UPDATE."
1901        //
1902        // The two kinds keep their own ground -- "The overlap restriction
1903        // applies independently per type" -- so which set the new prefix is
1904        // weighed against is decided by what the peer opened the stream with.
1905        //
1906        // A request of any other kind has no prefix for the parameter to move,
1907        // and nothing in this draft says what to do with the parameter when it
1908        // turns up on one, so it is left alone rather than guessed at.
1909        if let Some(prefix) = updated_prefix(&msg.parameters) {
1910            let namespace = matches!(
1911                self.inbound_requests.get(&id),
1912                Some(ControlMessage::SubscribeNamespace(_))
1913            );
1914            let tracks =
1915                matches!(self.inbound_requests.get(&id), Some(ControlMessage::SubscribeTracks(_)));
1916            if namespace || tracks {
1917                let collides = if tracks {
1918                    self.peer_tracks_overlap(&prefix, Some(id))
1919                } else {
1920                    self.peer_namespace_overlap(&prefix, Some(id))
1921                };
1922                match collides {
1923                    Some(established) => {
1924                        self.overlapping_prefix_updates.insert(id, established);
1925                    }
1926                    // A later update clears an earlier one's verdict along with
1927                    // its prefix, which is what a receiver "applying only the
1928                    // cumulative result" is entitled to do.
1929                    None => {
1930                        self.overlapping_prefix_updates.remove(&id);
1931                    }
1932                }
1933                self.updated_namespace_prefixes.insert(id, prefix);
1934            }
1935        }
1936        Ok(())
1937    }
1938
1939    pub fn receive_publish_done(
1940        &mut self,
1941        request_id: VarInt,
1942        _msg: &PublishDone,
1943    ) -> Result<(), EndpointError> {
1944        let id = request_id.into_inner();
1945        let sm = self.subscriptions.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
1946        sm.on_publish_done()?;
1947        Ok(())
1948    }
1949
1950    /// Process a PUBLISH_STATE_NOTIFY that arrived on the subscription's bidi
1951    /// request stream, new in draft-20 (Section 9.10).
1952    ///
1953    /// # Nothing here moves, and that is the message
1954    ///
1955    /// "This message is informative and no action is required by the
1956    /// recipient." It reports that the subscription's state changed for a
1957    /// reason other than a REQUEST_UPDATE the subscriber sent, carries only the
1958    /// parameters whose values changed, and gets **no reply**: unlike a
1959    /// REQUEST_UPDATE it is "a unilateral notification: the receiver does not
1960    /// respond with REQUEST_OK or REQUEST_ERROR, and the message is not subject
1961    /// to the MAX_REQUEST_UPDATES limit". So this spends no update credit and
1962    /// leaves nothing owed — a notify routed through
1963    /// [`receive_request_update`](Self::receive_request_update) instead would
1964    /// consume a credit the peer never spent and leave this endpoint owing an
1965    /// answer that must not be sent.
1966    ///
1967    /// What the parameters mean is the caller's: the values changed at the
1968    /// publisher, and the publisher "MUST include the LARGEST_OBJECT parameter"
1969    /// — if known — "so the subscriber can determine the point in the Track at
1970    /// which the change took effect". A message that omits it is still a
1971    /// well-formed frame, so nothing here refuses one.
1972    ///
1973    /// # Errors
1974    ///
1975    /// [`EndpointError::StateNotifyForNonSubscription`] when `request_id` names
1976    /// something other than a subscription, and
1977    /// [`EndpointError::StateNotifyFromSubscriber`] when it names a
1978    /// subscription on which this endpoint is the publisher rather than the
1979    /// subscriber. Section 9.10 answers both with a session close, and both
1980    /// are returned after this endpoint's own session state has moved to
1981    /// Closed.
1982    pub fn receive_publish_state_notify(
1983        &mut self,
1984        request_id: VarInt,
1985        _msg: &PublishStateNotify,
1986    ) -> Result<(), EndpointError> {
1987        let id = request_id.into_inner();
1988        // The two ways a subscription exists, from Section 3.1: this endpoint
1989        // sent a SUBSCRIBE, or the peer sent a PUBLISH. Either way the peer is
1990        // the publisher, which is the sender this section permits. A
1991        // subscription the peer opened with a SUBSCRIBE makes this endpoint the
1992        // publisher, and a notify on it came from the subscriber.
1993        let ours = self.subscriptions.contains_key(&id) && !self.inbound_requests.contains_key(&id);
1994        let peers_publish =
1995            self.publishes.contains_key(&id) && self.inbound_requests.contains_key(&id);
1996        if ours || peers_publish {
1997            return Ok(());
1998        }
1999        let a_subscription =
2000            self.subscriptions.contains_key(&id) || self.publishes.contains_key(&id);
2001        let err = if a_subscription {
2002            EndpointError::StateNotifyFromSubscriber(id)
2003        } else {
2004            EndpointError::StateNotifyForNonSubscription(id)
2005        };
2006        Err(self.fail_session(err))
2007    }
2008
2009    // -- Fetch flow -------------------------------------------------
2010
2011    /// Send a FETCH.
2012    ///
2013    /// # This is not draft-19's FETCH
2014    ///
2015    /// Draft-21 Section 9.11 rebuilt the message. The `Fetch Type` field, the
2016    /// Standalone Fetch and Joining Fetch structures and the Fetch Type
2017    /// registry are gone; the Track Namespace and the Track Name are inline
2018    /// fields, and **the range travels in the `LOCATION_FILTER` parameter**
2019    /// (Section 3.3.1) rather than in the message. So this takes the same three
2020    /// arguments [`subscribe`](Self::subscribe) does, and the four location
2021    /// varints draft-19 took are now something the caller puts in
2022    /// `parameters` — see [`fetch_range`](Self::fetch_range), which does it.
2023    ///
2024    /// A FETCH with no `LOCATION_FILTER` covers `{0,0}` through Largest Object,
2025    /// inclusive.
2026    ///
2027    /// **The code point did not change.** A draft-19 peer fed one of these
2028    /// reads the Number of Track Namespace Fields count as a Fetch Type and
2029    /// mis-parses in silence; there is no in-band version signal, only the
2030    /// ALPN.
2031    ///
2032    /// `parameters` are the request's own, as they are on every other request
2033    /// this endpoint makes.
2034    pub fn fetch(
2035        &mut self,
2036        track_namespace: TrackNamespace,
2037        track_name: Vec<u8>,
2038        parameters: Vec<KeyValuePair>,
2039    ) -> Result<(VarInt, ControlMessage), EndpointError> {
2040        self.require_active_or_err()?;
2041        let req_id = self.request_ids.allocate()?;
2042
2043        let mut sm = FetchStateMachine::new();
2044        sm.on_fetch_sent()?;
2045        self.fetches.insert(req_id.into_inner(), sm);
2046        // Before the two of them are moved into the message below: a fetch
2047        // that has left no track behind is one no withdrawal can name.
2048        self.fetch_tracks.insert(
2049            req_id.into_inner(),
2050            FetchTrack { namespace: track_namespace.clone(), name: track_name.clone() },
2051        );
2052
2053        let msg = ControlMessage::Fetch(Fetch {
2054            request_id: req_id,
2055            track_namespace,
2056            track_name,
2057            parameters,
2058        });
2059        Ok((req_id, msg))
2060    }
2061
2062    /// Send a FETCH for one range, carried as a `LOCATION_FILTER` parameter.
2063    ///
2064    /// [`fetch`](Self::fetch) with the filter put into `parameters` at the
2065    /// position ascending Parameter Type order requires. The convenience is
2066    /// worth a method because Section 9.20 makes that order a wire rule and the
2067    /// codec's encoder refuses a descending pair rather than emitting one.
2068    ///
2069    /// **The range is inclusive at both ends** (Sections 3.3.1 and 9.11), and
2070    /// nothing here adds or subtracts one. Draft-19's `End Location` was "the
2071    /// last Object, plus 1; or 0 to indicate the entire Group"; a caller that
2072    /// ports that arithmetic forward fetches one object too many, and one that
2073    /// ports the `0`-means-whole-group convention fetches a single object where
2074    /// it meant a group. [`LocationFilter`]
2075    /// carries the same warning at each constructor.
2076    pub fn fetch_range(
2077        &mut self,
2078        track_namespace: TrackNamespace,
2079        track_name: Vec<u8>,
2080        range: &LocationFilter,
2081        mut parameters: Vec<KeyValuePair>,
2082    ) -> Result<(VarInt, ControlMessage), EndpointError> {
2083        let filter = range.parameter()?;
2084        fill::insert_parameter(&mut parameters, filter);
2085        self.fetch(track_namespace, track_name, parameters)
2086    }
2087
2088    // -- Fill fetch streams -----------------------------------------
2089    //
2090    // Draft-21 Sections 3.4 and 3.4.1, new in this draft, and what replaced
2091    // the joining fetch. There is no message here and no state machine: a fill
2092    // is asked for by a parameter on a SUBSCRIBE or a REQUEST_UPDATE, answered
2093    // by a unidirectional stream, and ended by that stream being finished or
2094    // reset.
2095
2096    /// Note that the request under `request_id` asked for a fill fetch stream.
2097    ///
2098    /// Called for this endpoint's own requests and for the peer's alike, from
2099    /// wherever a SUBSCRIBE or a REQUEST_UPDATE carrying `FILL_PARAMETERS`
2100    /// passes through, so both ends of a session can attribute the stream that
2101    /// follows.
2102    ///
2103    /// The Group Order the fill will be read in is resolved here rather than
2104    /// when the stream arrives, because this is the last place the request's
2105    /// parameters are in hand: a FETCH_HEADER carries a Request ID and nothing
2106    /// else. See [`fill::group_order`] for the three-step resolution and for
2107    /// why an unstated order is Ascending. A value the resolver refuses cannot
2108    /// come off the wire — the codec holds a received `GROUP_ORDER` to
2109    /// `{1, 2}` before this sees it — so the fallback here is the same
2110    /// Ascending default rather than an error this entry point has no way to
2111    /// report.
2112    fn note_fill_requested(&mut self, request_id: u64, parameters: &[KeyValuePair]) {
2113        if !parameters.iter().any(|p| p.key.into_inner() == FILL_PARAMETERS) {
2114            return;
2115        }
2116        let group_order = fill::group_order(parameters).unwrap_or(GroupOrder::Ascending);
2117        let entry =
2118            self.fills.entry(request_id).or_insert(FillStreams { open: 0, opened: 0, group_order });
2119        entry.group_order = group_order;
2120    }
2121
2122    /// Whether the request under `request_id` has asked for a fill fetch
2123    /// stream.
2124    ///
2125    /// This is what tells an arriving FETCH_HEADER apart from a fetch's.
2126    /// Section 3.4 puts the *subscription's* Request ID on a fill fetch
2127    /// stream's header, so a reader that knew only about fetches would answer a
2128    /// perfectly ordinary fill with "unknown request".
2129    pub fn fill_requested(&self, request_id: VarInt) -> bool {
2130        self.fills.contains_key(&request_id.into_inner())
2131    }
2132
2133    /// How many of this subscription's fill fetch streams are open.
2134    pub fn open_fill_streams(&self, request_id: VarInt) -> usize {
2135        self.fills.get(&request_id.into_inner()).map_or(0, |f| f.open)
2136    }
2137
2138    /// How many fill fetch streams this subscription has opened in total,
2139    /// ended or not.
2140    ///
2141    /// Section 9.9 makes PUBLISH_DONE's Stream Count the total the publisher
2142    /// opened for the subscription, "including streams that contained no
2143    /// Objects (e.g., an empty Subgroup) and including any fill fetch streams".
2144    /// Draft-19 counted no fill streams because it had none, so this number is
2145    /// new work for a publisher building that field.
2146    pub fn fill_streams_opened(&self, request_id: VarInt) -> u64 {
2147        self.fills.get(&request_id.into_inner()).map_or(0, |f| f.opened)
2148    }
2149
2150    /// The Group Order this subscription's fill fetch streams deliver in, or
2151    /// `None` if it never asked for a fill.
2152    ///
2153    /// What [`accept_fill_stream`](crate::draft21::connection::Connection::accept_fill_stream)
2154    /// starts the arriving stream's object reader with. Resolved once, from the
2155    /// request's own parameters, by [`fill::group_order`] — see there for the
2156    /// order of precedence and for why an unstated order is Ascending.
2157    pub fn fill_group_order(&self, request_id: VarInt) -> Option<GroupOrder> {
2158        self.fills.get(&request_id.into_inner()).map(|f| f.group_order)
2159    }
2160
2161    /// Record a fill fetch stream opening for `request_id`.
2162    ///
2163    /// Section 3.4: "a subscription can have multiple fill fetch streams open
2164    /// at once", and "opening a new fill fetch stream does not implicitly
2165    /// cancel any previously opened fill fetch streams", so this counts rather
2166    /// than replaces.
2167    ///
2168    /// # Errors
2169    ///
2170    /// [`EndpointError::UnrequestedFillStream`] when the request never carried
2171    /// `FILL_PARAMETERS`. Not fatal to the session — see the variant.
2172    pub fn on_fill_stream_opened(&mut self, request_id: VarInt) -> Result<(), EndpointError> {
2173        let id = request_id.into_inner();
2174        let Some(fill) = self.fills.get_mut(&id) else {
2175            return Err(EndpointError::UnrequestedFillStream(id));
2176        };
2177        fill.open += 1;
2178        fill.opened += 1;
2179        Ok(())
2180    }
2181
2182    /// Record a fill fetch stream ending, whether by FIN or by reset.
2183    ///
2184    /// One entry point for both endings on purpose. Section 3.4.1 gives them
2185    /// different meanings to the application — a FIN says the fill range was
2186    /// delivered, a reset says the fill failed — and the same meaning to the
2187    /// subscription, which is none: "Resetting or cancelling a fill fetch
2188    /// stream, by either endpoint, does not affect the subscription, which
2189    /// continues to deliver objects using subscribe subgroups and datagrams."
2190    /// Nothing here touches the subscription's state machine, and that is the
2191    /// point.
2192    ///
2193    /// A stream this endpoint never saw open is ignored rather than refused: a
2194    /// reset can arrive for a stream whose header was never read.
2195    pub fn on_fill_stream_ended(&mut self, request_id: VarInt) {
2196        if let Some(fill) = self.fills.get_mut(&request_id.into_inner()) {
2197            fill.open = fill.open.saturating_sub(1);
2198        }
2199    }
2200
2201    pub fn receive_fetch_ok(
2202        &mut self,
2203        request_id: VarInt,
2204        _msg: &message::FetchOk,
2205    ) -> Result<(), EndpointError> {
2206        let id = request_id.into_inner();
2207        let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2208        sm.on_fetch_ok()?;
2209        Ok(())
2210    }
2211
2212    pub fn on_fetch_stream_fin(&mut self, request_id: VarInt) -> Result<(), EndpointError> {
2213        let id = request_id.into_inner();
2214        let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2215        sm.on_stream_fin()?;
2216        Ok(())
2217    }
2218
2219    pub fn on_fetch_stream_reset(&mut self, request_id: VarInt) -> Result<(), EndpointError> {
2220        let id = request_id.into_inner();
2221        let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2222        sm.on_stream_reset()?;
2223        Ok(())
2224    }
2225
2226    // -- Subscribe Namespace flow -----------------------------------
2227
2228    pub fn subscribe_namespace(
2229        &mut self,
2230        namespace_prefix: TrackNamespace,
2231        parameters: Vec<KeyValuePair>,
2232    ) -> Result<(VarInt, ControlMessage), EndpointError> {
2233        self.require_active_or_err()?;
2234        let req_id = self.request_ids.allocate()?;
2235
2236        let mut sm = SubscribeNamespaceStateMachine::new();
2237        sm.on_subscribe_namespace_sent()?;
2238        self.subscribe_namespaces.insert(req_id.into_inner(), sm);
2239
2240        let msg = ControlMessage::SubscribeNamespace(SubscribeNamespace {
2241            request_id: req_id,
2242            namespace_prefix,
2243            parameters,
2244        });
2245        Ok((req_id, msg))
2246    }
2247
2248    // -- Subscribe Tracks flow (new in draft-18) --------------------
2249
2250    pub fn subscribe_tracks(
2251        &mut self,
2252        namespace_prefix: TrackNamespace,
2253        parameters: Vec<KeyValuePair>,
2254    ) -> Result<(VarInt, ControlMessage), EndpointError> {
2255        self.require_active_or_err()?;
2256        let req_id = self.request_ids.allocate()?;
2257
2258        // Reuse the SubscribeNamespace state machine — the lifecycle is the
2259        // same (request → ok/error → done) and adding a parallel state
2260        // machine purely to disambiguate would be churn.
2261        let mut sm = SubscribeNamespaceStateMachine::new();
2262        sm.on_subscribe_namespace_sent()?;
2263        self.subscribe_tracks.insert(req_id.into_inner(), sm);
2264        self.note_fill_requested(req_id.into_inner(), &parameters);
2265
2266        let msg = ControlMessage::SubscribeTracks(SubscribeTracks {
2267            request_id: req_id,
2268            namespace_prefix,
2269            parameters,
2270        });
2271        Ok((req_id, msg))
2272    }
2273
2274    // -- Publish Namespace flow -------------------------------------
2275
2276    pub fn publish_namespace(
2277        &mut self,
2278        track_namespace: TrackNamespace,
2279        parameters: Vec<KeyValuePair>,
2280    ) -> Result<(VarInt, ControlMessage), EndpointError> {
2281        self.require_active_or_err()?;
2282        let req_id = self.request_ids.allocate()?;
2283
2284        let mut sm = PublishNamespaceStateMachine::new();
2285        sm.on_publish_namespace_sent()?;
2286        self.publish_namespaces.insert(req_id.into_inner(), sm);
2287
2288        let msg = ControlMessage::PublishNamespace(PublishNamespace {
2289            request_id: req_id,
2290            track_namespace,
2291            parameters,
2292        });
2293        Ok((req_id, msg))
2294    }
2295
2296    // -- Track Status flow ------------------------------------------
2297
2298    pub fn track_status(
2299        &mut self,
2300        track_namespace: TrackNamespace,
2301        track_name: Vec<u8>,
2302        parameters: Vec<KeyValuePair>,
2303    ) -> Result<(VarInt, ControlMessage), EndpointError> {
2304        self.require_active_or_err()?;
2305        let req_id = self.request_ids.allocate()?;
2306        let mut sm = TrackStatusStateMachine::new();
2307        sm.on_track_status_sent()?;
2308        self.track_statuses.insert(req_id.into_inner(), sm);
2309
2310        let msg = ControlMessage::TrackStatus(message::TrackStatus {
2311            request_id: req_id,
2312            track_namespace,
2313            track_name,
2314            parameters,
2315        });
2316        Ok((req_id, msg))
2317    }
2318
2319    // -- Publish flow (publisher side) ------------------------------
2320
2321    pub fn publish(
2322        &mut self,
2323        track_namespace: TrackNamespace,
2324        track_name: Vec<u8>,
2325        track_alias: VarInt,
2326        parameters: Vec<KeyValuePair>,
2327        track_properties: Vec<KeyValuePair>,
2328    ) -> Result<(VarInt, ControlMessage), EndpointError> {
2329        self.require_active_or_err()?;
2330        // Section 3.1.2: "The same Track Alias MUST NOT be used by a publisher to refer to
2331        // two different Tracks simultaneously in the same session." Refused before the
2332        // Request ID is allocated, so a refusal spends nothing.
2333        let alias = track_alias.into_inner();
2334        if let Some(refusal) = self.alias_held_elsewhere(alias, &track_namespace, &track_name) {
2335            return Err(refusal);
2336        }
2337        let req_id = self.request_ids.allocate()?;
2338        let mut sm = PublishStateMachine::new();
2339        sm.on_publish_sent()?;
2340        self.publishes.insert(req_id.into_inner(), sm);
2341        // The alias and the track travel together in a PUBLISH, so the binding
2342        // is complete the moment the message is built.
2343        self.track_bindings.insert(
2344            req_id.into_inner(),
2345            TrackBinding {
2346                namespace: track_namespace.clone(),
2347                name: track_name.clone(),
2348                alias: Some(alias),
2349                kind: BindingKind::Publish,
2350            },
2351        );
2352
2353        let msg = ControlMessage::Publish(Publish {
2354            request_id: req_id,
2355            track_namespace,
2356            track_name,
2357            track_alias,
2358            parameters,
2359            track_properties,
2360        });
2361        Ok((req_id, msg))
2362    }
2363
2364    pub fn send_publish_done(
2365        &mut self,
2366        request_id: VarInt,
2367        status_code: VarInt,
2368        stream_count: VarInt,
2369        reason_phrase: Vec<u8>,
2370    ) -> Result<ControlMessage, EndpointError> {
2371        let id = request_id.into_inner();
2372        // Before the state machine moves, so an ending under the wrong status
2373        // leaves the publication where it was. A PUBLISH this endpoint sent is
2374        // ended here rather than through the response path, and a refused
2375        // update on it owes the same ending as one on a peer's SUBSCRIBE.
2376        self.require_update_failure_status(id, status_code)?;
2377        let sm = self.publishes.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2378        sm.on_publish_done_sent()?;
2379        Ok(ControlMessage::PublishDone(PublishDone { status_code, stream_count, reason_phrase }))
2380    }
2381
2382    // -- Consolidated responses (per-bidi-stream routing) -----------
2383
2384    /// Process an incoming REQUEST_OK on the bidi stream identified by
2385    /// `request_id`. Draft-21: PUBLISH_OK is a REQUEST_OK alias, so this
2386    /// handler also resolves outstanding PUBLISH requests.
2387    ///
2388    /// # Track Properties are refused except on TRACK_STATUS_OK
2389    ///
2390    /// Draft-21 Section 9.3: Track Properties "are populated in
2391    /// TRACK_STATUS_OK; they are empty in PUBLISH_OK, REQUEST_UPDATE_OK,
2392    /// SUBSCRIBE_NAMESPACE_OK and PUBLISH_NAMESPACE_OK. If an endpoint
2393    /// receives Track Properties in one of these messages it MUST close the
2394    /// session with a PROTOCOL_VIOLATION." The codec cannot make that check —
2395    /// REQUEST_OK is one wire form and only the request stream says which of
2396    /// the five shapes it is. This is the layer that knows, because finding
2397    /// the request id in one of the maps below is what names the shape.
2398    pub fn receive_request_ok(
2399        &mut self,
2400        request_id: VarInt,
2401        msg: &RequestOk,
2402    ) -> Result<(), EndpointError> {
2403        let id = request_id.into_inner();
2404        if !msg.track_properties.is_empty() && !self.track_statuses.contains_key(&id) {
2405            return Err(self.fail_session(EndpointError::TrackPropertiesOnNonTrackStatus(id)));
2406        }
2407        if let Some(sm) = self.publishes.get_mut(&id) {
2408            sm.on_publish_ok()?;
2409            return Ok(());
2410        }
2411        if let Some(sm) = self.subscribe_namespaces.get_mut(&id) {
2412            sm.on_subscribe_namespace_ok()?;
2413            return Ok(());
2414        }
2415        if let Some(sm) = self.subscribe_tracks.get_mut(&id) {
2416            sm.on_subscribe_namespace_ok()?;
2417            return Ok(());
2418        }
2419        if let Some(sm) = self.publish_namespaces.get_mut(&id) {
2420            sm.on_publish_namespace_ok()?;
2421            return Ok(());
2422        }
2423        if let Some(sm) = self.track_statuses.get_mut(&id) {
2424            sm.on_track_status_ok()?;
2425            return Ok(());
2426        }
2427        Err(EndpointError::UnknownRequest(id))
2428    }
2429
2430    /// Process an incoming REQUEST_ERROR on the bidi stream identified by
2431    /// `request_id`.
2432    pub fn receive_request_error(
2433        &mut self,
2434        request_id: VarInt,
2435        msg: &RequestError,
2436    ) -> Result<(), EndpointError> {
2437        let id = request_id.into_inner();
2438        if let Some(redirect) = &msg.redirect {
2439            // Draft-21 Section 9.4.1: "If a server receives a Redirect with a
2440            // non-zero Connect URI Length it MUST close the session with a
2441            // PROTOCOL_VIOLATION." Left unchecked, a Redirect sends a server
2442            // chasing a URI a client picked.
2443            if self.role == Role::Server && !redirect.connect_uri.is_empty() {
2444                return Err(self.fail_session(EndpointError::RedirectUriAtServer));
2445            }
2446            // Same section: "Track Name is not meaningful for namespace-scoped
2447            // requests (SUBSCRIBE_NAMESPACE, PUBLISH_NAMESPACE,
2448            // SUBSCRIBE_TRACKS) and MUST be empty; an endpoint that receives a
2449            // non-empty Track Name in a Redirect for a namespace-scoped request
2450            // MUST close the session with a PROTOCOL_VIOLATION." Which request
2451            // this answers is known only here, from the map the stream's id is
2452            // in.
2453            let namespace_scoped = self.subscribe_namespaces.contains_key(&id)
2454                || self.subscribe_tracks.contains_key(&id)
2455                || self.publish_namespaces.contains_key(&id);
2456            if namespace_scoped && !redirect.track_name.is_empty() {
2457                return Err(
2458                    self.fail_session(EndpointError::RedirectTrackNameOnNamespaceRequest(id))
2459                );
2460            }
2461        }
2462        if let Some(sm) = self.subscriptions.get_mut(&id) {
2463            sm.on_subscribe_error()?;
2464            return Ok(());
2465        }
2466        if let Some(sm) = self.fetches.get_mut(&id) {
2467            sm.on_fetch_error()?;
2468            return Ok(());
2469        }
2470        if let Some(sm) = self.publishes.get_mut(&id) {
2471            sm.on_publish_error()?;
2472            return Ok(());
2473        }
2474        if let Some(sm) = self.subscribe_namespaces.get_mut(&id) {
2475            sm.on_subscribe_namespace_error()?;
2476            return Ok(());
2477        }
2478        if let Some(sm) = self.subscribe_tracks.get_mut(&id) {
2479            sm.on_subscribe_namespace_error()?;
2480            return Ok(());
2481        }
2482        if let Some(sm) = self.publish_namespaces.get_mut(&id) {
2483            sm.on_publish_namespace_error()?;
2484            return Ok(());
2485        }
2486        if let Some(sm) = self.track_statuses.get_mut(&id) {
2487            sm.on_track_status_error()?;
2488            return Ok(());
2489        }
2490        Err(EndpointError::UnknownRequest(id))
2491    }
2492
2493    /// Record that a request was cancelled at its stream.
2494    ///
2495    /// This draft withdraws a request by terminating the bidirectional stream
2496    /// it was made on rather than by sending a message. Section 6.4.2.3: "Once
2497    /// a request stream has been opened, the request MAY be cancelled by either
2498    /// endpoint. Senders cancel requests if the response is no longer of
2499    /// interest; Receivers cancel requests if they are unable to or choose not
2500    /// to respond."
2501    ///
2502    /// Both of those reach here. A request the peer opened and one this
2503    /// endpoint opened share a map and cannot collide, because their Request
2504    /// IDs have opposite least significant bits, so one method records a cancel
2505    /// from whichever side performed it.
2506    ///
2507    /// The request moves to its end state, which is what makes the record worth
2508    /// keeping: a response arriving afterwards is refused rather than applied to
2509    /// a request that is over. The Request ID is not released — nothing here
2510    /// reuses one — and the per-request bookkeeping keyed by it is left alone,
2511    /// since a cancelled request's stream carries nothing more.
2512    ///
2513    /// Nothing is written on the wire. The reset that goes with this is
2514    /// [`Connection::cancel_request_stream`], which calls this first and
2515    /// terminates the stream only if it returns `Ok`.
2516    ///
2517    /// # Errors
2518    ///
2519    /// [`EndpointError::UnknownRequest`] when no request of any kind carries
2520    /// that id, and the kind's own `InvalidTransition` when the request has not
2521    /// been written yet.
2522    ///
2523    /// [`Connection::cancel_request_stream`]: crate::draft21::connection::Connection::cancel_request_stream
2524    pub fn cancel_request(&mut self, request_id: VarInt) -> Result<(), EndpointError> {
2525        let id = request_id.into_inner();
2526        if let Some(sm) = self.subscriptions.get_mut(&id) {
2527            sm.on_request_cancelled()?;
2528            return Ok(());
2529        }
2530        if let Some(sm) = self.fetches.get_mut(&id) {
2531            sm.on_request_cancelled()?;
2532            return Ok(());
2533        }
2534        if let Some(sm) = self.publishes.get_mut(&id) {
2535            sm.on_request_cancelled()?;
2536            return Ok(());
2537        }
2538        if let Some(sm) = self.subscribe_namespaces.get_mut(&id) {
2539            sm.on_request_cancelled()?;
2540            return Ok(());
2541        }
2542        if let Some(sm) = self.subscribe_tracks.get_mut(&id) {
2543            sm.on_request_cancelled()?;
2544            return Ok(());
2545        }
2546        if let Some(sm) = self.publish_namespaces.get_mut(&id) {
2547            sm.on_request_cancelled()?;
2548            return Ok(());
2549        }
2550        if let Some(sm) = self.track_statuses.get_mut(&id) {
2551            sm.on_request_cancelled()?;
2552            return Ok(());
2553        }
2554        Err(EndpointError::UnknownRequest(id))
2555    }
2556
2557    // -- PublishSkipped / Namespace announcements -------------------
2558
2559    pub fn receive_namespace(&mut self, _msg: &message::Namespace) -> Result<(), EndpointError> {
2560        Ok(())
2561    }
2562
2563    pub fn receive_namespace_done(
2564        &mut self,
2565        _msg: &message::NamespaceDone,
2566    ) -> Result<(), EndpointError> {
2567        Ok(())
2568    }
2569
2570    pub fn receive_publish_skipped(&mut self, _msg: &PublishSkipped) -> Result<(), EndpointError> {
2571        Ok(())
2572    }
2573
2574    // -- Unified message dispatch -----------------------------------
2575
2576    /// Dispatch a message that arrived on the control stream.
2577    ///
2578    /// Draft-21 Table 5 gives every message a Stream value, and only two of
2579    /// them name the control stream: SETUP is "Control", GOAWAY is "Control,
2580    /// Request". Everything else is "Request", so this method's job is to take
2581    /// those two and refuse the messages that identify a request they have no
2582    /// stream to name.
2583    ///
2584    /// Four are refused for that reason. REQUEST_UPDATE modifies the request
2585    /// its stream carries (Section 9.5). NAMESPACE and NAMESPACE_DONE report
2586    /// namespaces on the SUBSCRIBE_NAMESPACE request stream that asked for them
2587    /// (Sections 9.16 and 9.17), and PUBLISH_SKIPPED names a track that will
2588    /// not be published on the SUBSCRIBE_TRACKS stream that asked for it
2589    /// (Section 9.19). All four route through
2590    /// [`receive_response_on_stream`](Self::receive_response_on_stream), which
2591    /// has the request ID they need.
2592    pub fn receive_message(&mut self, msg: ControlMessage) -> Result<(), EndpointError> {
2593        match msg {
2594            ControlMessage::Setup(ref m) => self.receive_setup(m),
2595            ControlMessage::GoAway(ref m) => self.receive_goaway(m),
2596            ControlMessage::RequestUpdate(_) => {
2597                Err(self.fail_session(EndpointError::RequestUpdateOnControlStream))
2598            }
2599            // Refused, and the session left running. No draft states a close
2600            // for a message arriving on a stream it does not belong on — see
2601            // `session_error_code`, which answers `None` for them. A
2602            // control message carries its own length, so the next boundary is
2603            // known and a refused message costs the session nothing; that is
2604            // the answer `ResponseOnControlStream` below has always given.
2605            //
2606            // REQUEST_UPDATE above is the one that is not like these: Section
2607            // 9.5 does close over one that arrives outside the two cases it
2608            // names, so it keeps both the close and `fail_session`.
2609            ControlMessage::Namespace(_) => {
2610                Err(EndpointError::RequestMessageOnControlStream("NAMESPACE"))
2611            }
2612            ControlMessage::NamespaceDone(_) => {
2613                Err(EndpointError::RequestMessageOnControlStream("NAMESPACE_DONE"))
2614            }
2615            ControlMessage::PublishSkipped(_) => {
2616                Err(EndpointError::RequestMessageOnControlStream("PUBLISH_SKIPPED"))
2617            }
2618            // Section 9.10 puts PUBLISH_STATE_NOTIFY on "a subscription's
2619            // bidirectional stream", and the subscription it reports on is the
2620            // one that stream carries. One on the control stream names no
2621            // subscription, so there is nothing it could be about.
2622            //
2623            // Not the close Section 9.10 names, and the difference is worth
2624            // being exact about. That sentence closes over a
2625            // PUBLISH_STATE_NOTIFY "for any other request type, or from the
2626            // subscriber" — both of which are readings of the request its
2627            // Request ID names, and one on the control stream names no request
2628            // at all. Stretching the sentence to cover it would be this build
2629            // reading a rule into a draft, so this takes the same answer as its
2630            // three neighbours above.
2631            ControlMessage::PublishStateNotify(_) => {
2632                Err(EndpointError::RequestMessageOnControlStream("PUBLISH_STATE_NOTIFY"))
2633            }
2634            ControlMessage::SubscribeOk(_)
2635            | ControlMessage::PublishDone(_)
2636            | ControlMessage::FetchOk(_)
2637            | ControlMessage::RequestOk(_)
2638            | ControlMessage::RequestError(_) => Err(EndpointError::ResponseOnControlStream),
2639            _ => Ok(()),
2640        }
2641    }
2642
2643    /// Hold the response half of a namespace-scoped request to the rule that
2644    /// its first message answers the request.
2645    ///
2646    /// Sections 9.15 and 9.18 state it once each, for SUBSCRIBE_NAMESPACE
2647    /// and for SUBSCRIBE_TRACKS: "The publisher will respond with REQUEST_OK or
2648    /// REQUEST_ERROR on the response half of the stream. If the subscriber
2649    /// receives any message other than a REQUEST_OK or a REQUEST_ERROR as the
2650    /// first message on the response half of the stream, then it MUST close the
2651    /// session with a PROTOCOL_VIOLATION." Draft-18's own change log records it
2652    /// as new work rather than as a clarification, so drafts 17 and earlier are
2653    /// deliberately not held to it: they say nothing about which message comes
2654    /// first, and refusing one there would close a session over traffic those
2655    /// drafts permit.
2656    ///
2657    /// # Why only these two requests
2658    ///
2659    /// They are the two whose response half carries more than an answer.
2660    /// SUBSCRIBE_NAMESPACE goes on to carry NAMESPACE and NAMESPACE_DONE and
2661    /// SUBSCRIBE_TRACKS goes on to carry PUBLISH_SKIPPED, and those are exactly the
2662    /// messages that could arrive before the answer and be taken for it. A
2663    /// SUBSCRIBE has SUBSCRIBE_OK as its own first message and no such
2664    /// ambiguity, which is why the rule is written where it is.
2665    ///
2666    /// # What "first" is read from
2667    ///
2668    /// The request's own state machine. `Pending` means the request went out
2669    /// and nothing has come back, so it is the same question asked of the state
2670    /// rather than of a second counter that could disagree with it. A request
2671    /// this endpoint did not open, or one already answered, is not this rule's
2672    /// subject and passes through.
2673    ///
2674    /// # Errors
2675    ///
2676    /// [`EndpointError::ResponseBeforeTheFirstResponse`], which answers
2677    /// `Some(ProtocolViolation)`, and the session is failed before it returns.
2678    fn require_the_first_response_first(
2679        &mut self,
2680        id: u64,
2681        msg: &ControlMessage,
2682    ) -> Result<(), EndpointError> {
2683        let awaiting = [self.subscribe_namespaces.get(&id), self.subscribe_tracks.get(&id)]
2684            .into_iter()
2685            .flatten()
2686            .any(|sm| sm.state() == SubscribeNamespaceState::Pending);
2687        if !awaiting {
2688            return Ok(());
2689        }
2690        if matches!(msg, ControlMessage::RequestOk(_) | ControlMessage::RequestError(_)) {
2691            return Ok(());
2692        }
2693        let ty = msg.message_type();
2694        Err(self.fail_session(EndpointError::ResponseBeforeTheFirstResponse(id, ty)))
2695    }
2696
2697    /// Dispatch a message that arrived on the bidi request stream identified
2698    /// by `request_id`.
2699    ///
2700    /// Beyond the five responses this also takes the messages draft-21 Table 5
2701    /// places on a request stream without their being answers to it.
2702    ///
2703    /// REQUEST_UPDATE modifies the request the stream carries (Section 9.5).
2704    /// GOAWAY is listed "Control, Request" because "A GOAWAY MAY also be sent
2705    /// on a request stream to initiate migration of that individual request"
2706    /// (Section 9.2); draft-19 removed GOAWAY's Request ID and left one wire
2707    /// form for both places, and draft-21 keeps it that way.
2708    ///
2709    /// NAMESPACE (0x8) and NAMESPACE_DONE (0xE) arrive on the
2710    /// SUBSCRIBE_NAMESPACE request stream that asked for the namespaces they
2711    /// report, and PUBLISH_SKIPPED (0xF) on the SUBSCRIBE_TRACKS stream that
2712    /// asked for the track it says will not be published. Table 5 marks all
2713    /// three "Request", so this is where they land; the control stream refuses
2714    /// them.
2715    ///
2716    /// PUBLISH_STATE_NOTIFY (0x22) is draft-21's addition to that set, and the
2717    /// one message here that is neither a response nor a request: Section 9.10
2718    /// puts it on "a subscription's bidirectional stream" and answers it with
2719    /// nothing at all.
2720    pub fn receive_response_on_stream(
2721        &mut self,
2722        request_id: VarInt,
2723        msg: ControlMessage,
2724    ) -> Result<(), EndpointError> {
2725        self.require_the_first_response_first(request_id.into_inner(), &msg)?;
2726        match msg {
2727            ControlMessage::SubscribeOk(ref m) => self.receive_subscribe_ok(request_id, m),
2728            ControlMessage::PublishDone(ref m) => self.receive_publish_done(request_id, m),
2729            ControlMessage::FetchOk(ref m) => self.receive_fetch_ok(request_id, m),
2730            // New in draft-20 and the one message on a request stream that is
2731            // neither a response nor a request: Section 9.10 puts it on "a
2732            // subscription's bidirectional stream" and answers it with nothing.
2733            ControlMessage::PublishStateNotify(ref m) => {
2734                self.receive_publish_state_notify(request_id, m)
2735            }
2736            ControlMessage::RequestOk(ref m) => self.receive_request_ok(request_id, m),
2737            ControlMessage::RequestError(ref m) => self.receive_request_error(request_id, m),
2738            ControlMessage::RequestUpdate(ref m) => self.receive_request_update(request_id, m),
2739            ControlMessage::GoAway(ref m) => self.receive_goaway_on_request_stream(request_id, m),
2740            ControlMessage::Namespace(ref m) => self.receive_namespace(m),
2741            ControlMessage::NamespaceDone(ref m) => self.receive_namespace_done(m),
2742            ControlMessage::PublishSkipped(ref m) => self.receive_publish_skipped(m),
2743            _ => Err(EndpointError::ResponseOnControlStream),
2744        }
2745    }
2746
2747    /// Process a GOAWAY that arrived on one request stream rather than on the
2748    /// control stream.
2749    ///
2750    /// Draft-21 Section 9.2: "A GOAWAY MAY also be sent on a request stream
2751    /// to initiate migration of that individual request. Upon receiving a
2752    /// GOAWAY on a request stream, the endpoint SHOULD re-issue that specific
2753    /// request on a session at the specified URI". The session keeps running —
2754    /// only this request is being moved — so the session state machine is not
2755    /// touched and no draining event follows. The server-side URI rule of the
2756    /// same section still applies.
2757    ///
2758    /// # Errors
2759    ///
2760    /// [`EndpointError::RepeatedGoAwayOnRequestStream`] if this request's
2761    /// stream has already carried one. The session is over: this endpoint's own
2762    /// state has moved to Closed and the code the transport should close with
2763    /// is in [`EndpointError::session_error_code`].
2764    pub fn receive_goaway_on_request_stream(
2765        &mut self,
2766        request_id: VarInt,
2767        msg: &GoAway,
2768    ) -> Result<(), EndpointError> {
2769        if self.role == Role::Server && !msg.new_session_uri.is_empty() {
2770            return Err(self.fail_session(EndpointError::GoAwayUriAtServer));
2771        }
2772        let id = request_id.into_inner();
2773        if self.subscriptions.contains_key(&id)
2774            || self.publishes.contains_key(&id)
2775            || self.fetches.contains_key(&id)
2776            || self.subscribe_namespaces.contains_key(&id)
2777            || self.subscribe_tracks.contains_key(&id)
2778            || self.publish_namespaces.contains_key(&id)
2779            || self.track_statuses.contains_key(&id)
2780        {
2781            // Section 9.2 counts per stream, so this is a separate first
2782            // GOAWAY on every request and a repeat only on the one that has
2783            // already carried one. The set is fed only by GOAWAYs accepted
2784            // here, and never pruned: a Request ID is spent once, so an entry
2785            // can never come to describe a different request.
2786            if self.goaway_request_streams.insert(id) {
2787                Ok(())
2788            } else {
2789                Err(self.fail_session(EndpointError::RepeatedGoAwayOnRequestStream(id)))
2790            }
2791        } else {
2792            Err(EndpointError::UnknownRequest(id))
2793        }
2794    }
2795
2796    // -- Responder side: requests the peer opened a stream with -----
2797
2798    /// Refuse a bidirectional stream the peer opened with a message type that
2799    /// does not begin a request, and end the session.
2800    ///
2801    /// Draft-21 Section 6.3: "Bidirectional streams MUST NOT begin with any
2802    /// other message type unless negotiated. If they do, the peer MUST close
2803    /// the Session with a PROTOCOL_VIOLATION." The returned error answers
2804    /// `Some(ProtocolViolation)` from
2805    /// [`EndpointError::session_error_code`], which is what tells the
2806    /// connection layer to put the close on the wire.
2807    pub fn refuse_non_request(&mut self, ty: MessageType) -> EndpointError {
2808        self.fail_session(EndpointError::NotARequest(ty))
2809    }
2810
2811    /// Register a request the **peer** opened a bidirectional stream with, and
2812    /// hand back the Request ID it carries.
2813    ///
2814    /// The mirror of [`receive_response_on_stream`](Self::receive_response_on_stream):
2815    /// that one is fed what comes back on a stream this endpoint opened, this
2816    /// one is fed the first message on a stream the peer opened.
2817    ///
2818    /// # What it enforces, and with which code
2819    ///
2820    /// Draft-21 Section 6.4.2.1: "If an endpoint receives a Request ID where the
2821    /// least significant bit is incorrect for the sender, or a duplicate
2822    /// Request ID, it MUST close the session with INVALID_REQUEST_ID." Both
2823    /// halves are checked here and both are returned as errors that answer
2824    /// `Some(InvalidRequestId)` from
2825    /// [`EndpointError::session_error_code`]. A message that opens no request
2826    /// stream at all is a different rule with a different code — see
2827    /// [`refuse_non_request`](Self::refuse_non_request).
2828    ///
2829    /// # Why there are no separate inbound maps
2830    ///
2831    /// The peer's ids and this endpoint's ids have opposite least significant
2832    /// bits, so they cannot collide. A peer's request goes into the same
2833    /// `HashMap` its outbound twin would, keyed the same way, and only the
2834    /// transition names differ — `on_subscribe_received` where the outbound
2835    /// path calls `on_subscribe_sent`.
2836    ///
2837    /// # Peer-controlled growth
2838    ///
2839    /// Every accepted request adds an entry that nothing removes, and the peer
2840    /// chooses how many to open. [`peer_request_count`](Self::peer_request_count)
2841    /// is what a responder can watch to impose its own ceiling; this method
2842    /// imposes none.
2843    pub fn receive_request_on_stream(
2844        &mut self,
2845        msg: &ControlMessage,
2846    ) -> Result<VarInt, EndpointError> {
2847        let request_id = match msg {
2848            ControlMessage::Subscribe(m) => m.request_id,
2849            ControlMessage::Publish(m) => m.request_id,
2850            ControlMessage::Fetch(m) => m.request_id,
2851            ControlMessage::PublishNamespace(m) => m.request_id,
2852            ControlMessage::SubscribeNamespace(m) => m.request_id,
2853            ControlMessage::SubscribeTracks(m) => m.request_id,
2854            ControlMessage::TrackStatus(m) => m.request_id,
2855            other => return Err(self.refuse_non_request(other.message_type())),
2856        };
2857        self.require_active_or_err()?;
2858
2859        let id = request_id.into_inner();
2860        if let Err(e) = self.request_ids.validate_peer_id(id) {
2861            return Err(self.fail_session(EndpointError::RequestId(e)));
2862        }
2863        // `insert` answers false when the id was already present, which is
2864        // exactly the duplicate the section names. Doing the check and the
2865        // record in one step means no path can record an id it did not check.
2866        if !self.peer_request_ids.insert(id) {
2867            return Err(self.fail_session(EndpointError::DuplicateRequestId(id)));
2868        }
2869
2870        match msg {
2871            ControlMessage::Subscribe(m) => {
2872                // Section 9.20.16: the presence of FILL_PARAMETERS "is what
2873                // requests a fill fetch stream". Noted as the SUBSCRIBE
2874                // arrives, because this endpoint is the publisher on a stream
2875                // the peer opened and the fill is a stream it owes.
2876                self.note_fill_requested(id, &m.parameters);
2877                let mut sm = SubscriptionStateMachine::new();
2878                sm.on_subscribe_received()?;
2879                self.subscriptions.insert(id, sm);
2880            }
2881            ControlMessage::Publish(m) => {
2882                let alias = m.track_alias.into_inner();
2883                if let Some(conflict) =
2884                    self.conflicting_track_alias(id, alias, &m.track_namespace, &m.track_name)
2885                {
2886                    return Err(self.fail_session(conflict));
2887                }
2888                let mut sm = PublishStateMachine::new();
2889                sm.on_publish_received()?;
2890                self.publishes.insert(id, sm);
2891                // A PUBLISH names its track and its alias in the one message,
2892                // so the binding is complete on arrival. It counts against the
2893                // next one only once this endpoint has answered PUBLISH_OK,
2894                // which is what moves its state machine to Active.
2895                self.track_bindings.insert(
2896                    id,
2897                    TrackBinding {
2898                        namespace: m.track_namespace.clone(),
2899                        name: m.track_name.clone(),
2900                        alias: Some(alias),
2901                        kind: BindingKind::Publish,
2902                    },
2903                );
2904            }
2905            // Nothing is judged here that draft-19 judged. Its FETCH could
2906            // name a subscription to join, and a Joining Fetch naming none was
2907            // a REQUEST_ERROR this endpoint had to owe; draft-21 Section 9.11
2908            // deleted the field, both structures and
2909            // `INVALID_JOINING_REQUEST_ID` with them, so a FETCH now names a
2910            // track like a SUBSCRIBE and there is nothing about it to refuse
2911            // in advance.
2912            ControlMessage::Fetch(_) => {
2913                let mut sm = FetchStateMachine::new();
2914                sm.on_fetch_received()?;
2915                self.fetches.insert(id, sm);
2916            }
2917            ControlMessage::PublishNamespace(_) => {
2918                let mut sm = PublishNamespaceStateMachine::new();
2919                sm.on_publish_namespace_received()?;
2920                self.publish_namespaces.insert(id, sm);
2921            }
2922            ControlMessage::SubscribeNamespace(m) => {
2923                if let Some(established) = self.peer_namespace_overlap(&m.namespace_prefix, None) {
2924                    self.overlapping_namespace_subscriptions.insert(id, established);
2925                }
2926                let mut sm = SubscribeNamespaceStateMachine::new();
2927                sm.on_subscribe_namespace_received()?;
2928                self.subscribe_namespaces.insert(id, sm);
2929            }
2930            // The seventh request kind, and the one draft-17 does not have.
2931            // It gets its own map for the same reason the outbound path gives
2932            // it one: Section 9.18 makes SUBSCRIBE_TRACKS and
2933            // SUBSCRIBE_NAMESPACE independent overlap spaces, so a responder
2934            // that merged them would answer the wrong request.
2935            ControlMessage::SubscribeTracks(m) => {
2936                // Its own space: a SUBSCRIBE_TRACKS is weighed against the
2937                // SUBSCRIBE_TRACKS the peer has open and against nothing else,
2938                // so one prefix may carry one of each.
2939                if let Some(established) = self.peer_tracks_overlap(&m.namespace_prefix, None) {
2940                    self.overlapping_namespace_subscriptions.insert(id, established);
2941                }
2942                // Section 9.20.16 lists SUBSCRIBE and REQUEST_UPDATE and not
2943                // this message, and Section 9.18.1 names it anyway: "To join
2944                // Tracks initiated via the resulting PUBLISHes, the subscriber
2945                // can specify a Location Filter and optionally include
2946                // FILL_PARAMETERS". The codec's scope table takes the wider
2947                // reading, so this records what arrives under it.
2948                self.note_fill_requested(id, &m.parameters);
2949                let mut sm = SubscribeNamespaceStateMachine::new();
2950                sm.on_subscribe_namespace_received()?;
2951                self.subscribe_tracks.insert(id, sm);
2952            }
2953            ControlMessage::TrackStatus(_) => {
2954                let mut sm = TrackStatusStateMachine::new();
2955                sm.on_track_status_received()?;
2956                self.track_statuses.insert(id, sm);
2957            }
2958            // Unreachable: the match above returned for every other variant.
2959            other => return Err(self.refuse_non_request(other.message_type())),
2960        }
2961
2962        // The request as it arrived, recorded last so that nothing above it
2963        // can leave one behind for a request it went on to refuse.
2964        self.inbound_requests.insert(id, msg.clone());
2965
2966        // After the request is recorded, never before. The answer to every
2967        // Range Filter rule is a REQUEST_ERROR, which names the Request ID of
2968        // the request it answers, so refusing here would leave nothing to
2969        // answer with.
2970        let parameters = request_parameters(msg);
2971        if let Some(rejection) = self.filter_verdict(parameters) {
2972            self.peer_filter_rejections.insert(id, rejection);
2973        }
2974        self.peer_request_filters.insert(id, range_filter_parameters(parameters));
2975        Ok(request_id)
2976    }
2977
2978    // -- What the peer asked for ------------------------------------
2979
2980    /// The SUBSCRIBE the peer sent under `request_id` and this endpoint has
2981    /// not answered yet.
2982    ///
2983    /// `None` once it has been answered, for an identifier this session has
2984    /// carried no SUBSCRIBE under, and for one this endpoint spent on a
2985    /// request of its own -- whose state is in the same map, but which never
2986    /// arrived here. The record itself lives on past the answer, because the
2987    /// subscription it opened runs on after it, for as long as the request
2988    /// stream does.
2989    pub fn pending_subscribe(&self, request_id: VarInt) -> Option<&Subscribe> {
2990        let id = request_id.into_inner();
2991        let unanswered = self
2992            .subscriptions
2993            .get(&id)
2994            .is_some_and(|sm| sm.state() == SubscriptionState::Subscribing);
2995        match self.inbound_requests.get(&id) {
2996            Some(ControlMessage::Subscribe(msg)) if unanswered => Some(msg),
2997            _ => None,
2998        }
2999    }
3000
3001    /// How many SUBSCRIBEs the peer has sent that are still waiting for an
3002    /// answer.
3003    pub fn pending_subscribe_count(&self) -> usize {
3004        self.inbound_requests
3005            .iter()
3006            .filter(|(id, msg)| {
3007                matches!(msg, ControlMessage::Subscribe(_))
3008                    && self
3009                        .subscriptions
3010                        .get(*id)
3011                        .is_some_and(|sm| sm.state() == SubscriptionState::Subscribing)
3012            })
3013            .count()
3014    }
3015
3016    /// The PUBLISH the peer sent under `request_id` and this endpoint has not
3017    /// answered yet.
3018    ///
3019    /// `None` once it has been answered, for an identifier this session has
3020    /// carried no PUBLISH under, and for one this endpoint spent on a request
3021    /// of its own -- whose state is in the same map, but which never arrived
3022    /// here. The record itself lives on past the answer, because the
3023    /// subscription the offer opened runs on after it, for as long as the
3024    /// request stream does.
3025    pub fn pending_publish(&self, request_id: VarInt) -> Option<&message::Publish> {
3026        let id = request_id.into_inner();
3027        let unanswered =
3028            self.publishes.get(&id).is_some_and(|sm| sm.state() == PublishState::Publishing);
3029        match self.inbound_requests.get(&id) {
3030            Some(ControlMessage::Publish(msg)) if unanswered => Some(msg),
3031            _ => None,
3032        }
3033    }
3034
3035    /// How many offers the peer has sent that are still waiting for an
3036    /// answer.
3037    pub fn pending_publish_count(&self) -> usize {
3038        self.inbound_requests
3039            .iter()
3040            .filter(|(id, msg)| {
3041                matches!(msg, ControlMessage::Publish(_))
3042                    && self
3043                        .publishes
3044                        .get(*id)
3045                        .is_some_and(|sm| sm.state() == PublishState::Publishing)
3046            })
3047            .count()
3048    }
3049
3050    /// The FETCH the peer sent under `request_id` and this endpoint has not
3051    /// answered yet.
3052    ///
3053    /// `None` once it has been answered, for an identifier this session has
3054    /// carried no FETCH under, and for one this endpoint spent on a request
3055    /// of its own -- whose state is in the same map, but which never arrived
3056    /// here. The record itself lives on past the answer, because the fetch is
3057    /// not over until its response stream is.
3058    pub fn pending_fetch(&self, request_id: VarInt) -> Option<&Fetch> {
3059        let id = request_id.into_inner();
3060        let unanswered = self
3061            .fetches
3062            .get(&id)
3063            .is_some_and(|sm| matches!(sm.state(), FetchState::Pending | FetchState::Unanswered));
3064        match self.inbound_requests.get(&id) {
3065            Some(ControlMessage::Fetch(msg)) if unanswered => Some(msg),
3066            _ => None,
3067        }
3068    }
3069
3070    /// How many FETCHes the peer has sent that are still waiting for an
3071    /// answer.
3072    pub fn pending_fetch_count(&self) -> usize {
3073        self.inbound_requests
3074            .iter()
3075            .filter(|(id, msg)| {
3076                matches!(msg, ControlMessage::Fetch(_))
3077                    && self.fetches.get(*id).is_some_and(|sm| {
3078                        matches!(sm.state(), FetchState::Pending | FetchState::Unanswered)
3079                    })
3080            })
3081            .count()
3082    }
3083
3084    /// The PUBLISH_NAMESPACE the peer sent under `request_id` and this
3085    /// endpoint has not answered yet.
3086    ///
3087    /// `None` once it has been answered, for an identifier this session has
3088    /// carried no PUBLISH_NAMESPACE under, and for one this endpoint spent on
3089    /// a request of its own -- whose state is in the same map, but which
3090    /// never arrived here. The record itself lives on past the answer,
3091    /// because an announcement that was accepted stands until it is
3092    /// withdrawn.
3093    pub fn pending_publish_namespace(&self, request_id: VarInt) -> Option<&PublishNamespace> {
3094        let id = request_id.into_inner();
3095        let unanswered = self
3096            .publish_namespaces
3097            .get(&id)
3098            .is_some_and(|sm| sm.state() == PublishNamespaceState::Pending);
3099        match self.inbound_requests.get(&id) {
3100            Some(ControlMessage::PublishNamespace(msg)) if unanswered => Some(msg),
3101            _ => None,
3102        }
3103    }
3104
3105    /// How many announcements the peer has sent that are still waiting for an
3106    /// answer.
3107    pub fn pending_publish_namespace_count(&self) -> usize {
3108        self.inbound_requests
3109            .iter()
3110            .filter(|(id, msg)| {
3111                matches!(msg, ControlMessage::PublishNamespace(_))
3112                    && self
3113                        .publish_namespaces
3114                        .get(*id)
3115                        .is_some_and(|sm| sm.state() == PublishNamespaceState::Pending)
3116            })
3117            .count()
3118    }
3119
3120    /// The earliest namespace subscription the peer has made whose prefix
3121    /// overlaps `prefix`, and `None` when there is none.
3122    ///
3123    /// Only ones that have not ended count: the sentence weighs the arriving
3124    /// prefix against an "established" one, so one the peer has withdrawn and
3125    /// one this endpoint refused are both past. Drafts 07 through 11 say "an
3126    /// earlier" instead and count those too.
3127    ///
3128    /// One that has arrived and has not been answered does count. It is not
3129    /// established yet, but this endpoint is the one about to establish it,
3130    /// and accepting both would leave the session holding exactly the pair
3131    /// the sentence exists to prevent.
3132    ///
3133    /// The record of what the peer sent is what tells the two directions
3134    /// apart. The state machines live in one map per kind whichever end
3135    /// opened the request, so a prefix this endpoint asked about would be
3136    /// indistinguishable there; only requests that arrived are written into
3137    /// `inbound_requests`.
3138    ///
3139    /// The lowest Request ID wins when more than one overlaps, so the answer
3140    /// does not depend on the order a map happens to iterate in.
3141    ///
3142    /// `except` is the subscription a REQUEST_UPDATE is moving, which Section
3143    /// 9.20.21 weighs against "another active subscription of the same type"
3144    /// and therefore not against the prefix it is leaving behind.
3145    fn peer_namespace_overlap(&self, prefix: &TrackNamespace, except: Option<u64>) -> Option<u64> {
3146        self.inbound_requests
3147            .iter()
3148            .filter(|(&id, _)| Some(id) != except)
3149            .filter_map(|(&id, msg)| match msg {
3150                ControlMessage::SubscribeNamespace(m) => Some((id, &m.namespace_prefix)),
3151                _ => None,
3152            })
3153            .filter(|(id, _)| {
3154                self.subscribe_namespaces
3155                    .get(id)
3156                    .is_some_and(|sm| sm.state() != SubscribeNamespaceState::Done)
3157            })
3158            .filter_map(|(id, p)| prefixes_overlap(&p.0, &prefix.0).then_some(id))
3159            .min()
3160    }
3161
3162    /// The earliest track subscription the peer has made whose prefix
3163    /// overlaps `prefix`, and `None` when there is none.
3164    ///
3165    /// Only ones that have not ended count: the sentence weighs the arriving
3166    /// prefix against an "established" one, so one the peer has withdrawn and
3167    /// one this endpoint refused are both past. Drafts 07 through 11 say "an
3168    /// earlier" instead and count those too.
3169    ///
3170    /// One that has arrived and has not been answered does count. It is not
3171    /// established yet, but this endpoint is the one about to establish it,
3172    /// and accepting both would leave the session holding exactly the pair
3173    /// the sentence exists to prevent.
3174    ///
3175    /// The record of what the peer sent is what tells the two directions
3176    /// apart. The state machines live in one map per kind whichever end
3177    /// opened the request, so a prefix this endpoint asked about would be
3178    /// indistinguishable there; only requests that arrived are written into
3179    /// `inbound_requests`.
3180    ///
3181    /// The lowest Request ID wins when more than one overlaps, so the answer
3182    /// does not depend on the order a map happens to iterate in.
3183    ///
3184    /// `except` is the subscription a REQUEST_UPDATE is moving, which Section
3185    /// 9.20.21 weighs against "another active subscription of the same type"
3186    /// and therefore not against the prefix it is leaving behind.
3187    fn peer_tracks_overlap(&self, prefix: &TrackNamespace, except: Option<u64>) -> Option<u64> {
3188        self.inbound_requests
3189            .iter()
3190            .filter(|(&id, _)| Some(id) != except)
3191            .filter_map(|(&id, msg)| match msg {
3192                ControlMessage::SubscribeTracks(m) => Some((id, &m.namespace_prefix)),
3193                _ => None,
3194            })
3195            .filter(|(id, _)| {
3196                self.subscribe_tracks
3197                    .get(id)
3198                    .is_some_and(|sm| sm.state() != SubscribeNamespaceState::Done)
3199            })
3200            .filter_map(|(id, p)| prefixes_overlap(&p.0, &prefix.0).then_some(id))
3201            .min()
3202    }
3203
3204    /// The SUBSCRIBE_NAMESPACE the peer sent under `request_id` and this
3205    /// endpoint has not answered yet.
3206    ///
3207    /// `None` once it has been answered, for an identifier this session has
3208    /// carried no SUBSCRIBE_NAMESPACE under, and for one this endpoint spent
3209    /// on a request of its own -- whose state is in the same map, but which
3210    /// never arrived here. The record itself lives on past the answer,
3211    /// because a namespace subscription lasts as long as its stream does.
3212    pub fn pending_subscribe_namespace(&self, request_id: VarInt) -> Option<&SubscribeNamespace> {
3213        let id = request_id.into_inner();
3214        let unanswered = self
3215            .subscribe_namespaces
3216            .get(&id)
3217            .is_some_and(|sm| sm.state() == SubscribeNamespaceState::Pending);
3218        match self.inbound_requests.get(&id) {
3219            Some(ControlMessage::SubscribeNamespace(msg)) if unanswered => Some(msg),
3220            _ => None,
3221        }
3222    }
3223
3224    /// How many namespace subscriptions the peer has sent that are still
3225    /// waiting for an answer.
3226    pub fn pending_subscribe_namespace_count(&self) -> usize {
3227        self.inbound_requests
3228            .iter()
3229            .filter(|(id, msg)| {
3230                matches!(msg, ControlMessage::SubscribeNamespace(_))
3231                    && self
3232                        .subscribe_namespaces
3233                        .get(*id)
3234                        .is_some_and(|sm| sm.state() == SubscribeNamespaceState::Pending)
3235            })
3236            .count()
3237    }
3238
3239    /// The SUBSCRIBE_TRACKS the peer sent under `request_id` and this
3240    /// endpoint has not answered yet.
3241    ///
3242    /// `None` once it has been answered, for an identifier this session has
3243    /// carried no SUBSCRIBE_TRACKS under, and for one this endpoint spent on
3244    /// a request of its own -- whose state is in the same map, but which
3245    /// never arrived here. The record itself lives on past the answer,
3246    /// because the subscription it opened lasts as long as its stream does.
3247    pub fn pending_subscribe_tracks(&self, request_id: VarInt) -> Option<&SubscribeTracks> {
3248        let id = request_id.into_inner();
3249        let unanswered = self
3250            .subscribe_tracks
3251            .get(&id)
3252            .is_some_and(|sm| sm.state() == SubscribeNamespaceState::Pending);
3253        match self.inbound_requests.get(&id) {
3254            Some(ControlMessage::SubscribeTracks(msg)) if unanswered => Some(msg),
3255            _ => None,
3256        }
3257    }
3258
3259    /// How many track subscriptions the peer has sent that are still waiting
3260    /// for an answer.
3261    pub fn pending_subscribe_tracks_count(&self) -> usize {
3262        self.inbound_requests
3263            .iter()
3264            .filter(|(id, msg)| {
3265                matches!(msg, ControlMessage::SubscribeTracks(_))
3266                    && self
3267                        .subscribe_tracks
3268                        .get(*id)
3269                        .is_some_and(|sm| sm.state() == SubscribeNamespaceState::Pending)
3270            })
3271            .count()
3272    }
3273
3274    /// The TRACK_STATUS the peer sent under `request_id` and this endpoint
3275    /// has not answered yet.
3276    ///
3277    /// `None` once it has been answered, for an identifier this session has
3278    /// carried no TRACK_STATUS under, and for one this endpoint spent on a
3279    /// request of its own -- whose state is in the same map, but which never
3280    /// arrived here. The record itself lives on past the answer, because an
3281    /// update may still name it once it has been answered.
3282    pub fn pending_track_status(&self, request_id: VarInt) -> Option<&message::TrackStatus> {
3283        let id = request_id.into_inner();
3284        let unanswered =
3285            self.track_statuses.get(&id).is_some_and(|sm| sm.state() == TrackStatusState::Pending);
3286        match self.inbound_requests.get(&id) {
3287            Some(ControlMessage::TrackStatus(msg)) if unanswered => Some(msg),
3288            _ => None,
3289        }
3290    }
3291
3292    /// How many track statuses the peer has sent that are still waiting for
3293    /// an answer.
3294    pub fn pending_track_status_count(&self) -> usize {
3295        self.inbound_requests
3296            .iter()
3297            .filter(|(id, msg)| {
3298                matches!(msg, ControlMessage::TrackStatus(_))
3299                    && self
3300                        .track_statuses
3301                        .get(*id)
3302                        .is_some_and(|sm| sm.state() == TrackStatusState::Pending)
3303            })
3304            .count()
3305    }
3306
3307    /// Whether the Range Filters in `parameters` are ones this endpoint may
3308    /// accept.
3309    ///
3310    /// The three rules that need more than one parameter to see, in the order
3311    /// that reports the most specific fault: whether the filters read at all,
3312    /// then whether any budget was advertised, then whether the request stays
3313    /// inside it, then whether two filters share a key. A request with no Range
3314    /// Filter at all is not measured against the ceiling, so an endpoint that
3315    /// advertised nothing still takes ordinary requests — which is the whole of
3316    /// the traffic today, since draft-21 is the first draft with these
3317    /// parameters.
3318    fn filter_verdict(&self, parameters: &[KeyValuePair]) -> Option<FilterRejection> {
3319        let filters = match range_filter::decode_all_moqt::<Moqt18>(parameters) {
3320            Ok(filters) => filters,
3321            Err(e) => return Some(FilterRejection::Unreadable(e)),
3322        };
3323        if filters.is_empty() {
3324            return None;
3325        }
3326        if self.advertised_max_filter_ranges == 0 {
3327            return Some(FilterRejection::NoBudgetAdvertised);
3328        }
3329        let ranges = range_filter::total_ranges(&filters);
3330        if ranges as u64 > self.advertised_max_filter_ranges {
3331            return Some(FilterRejection::TooManyRanges {
3332                ranges,
3333                limit: self.advertised_max_filter_ranges,
3334            });
3335        }
3336        if let Some((parameter_type, set_id, property_type)) =
3337            range_filter::first_repeated_key(&filters)
3338        {
3339            return Some(FilterRejection::RepeatedFilter(parameter_type, set_id, property_type));
3340        }
3341        None
3342    }
3343
3344    /// Why request `id` must be answered with a REQUEST_ERROR, if it must.
3345    ///
3346    /// The caller builds the message; [`FilterRejection::request_error_code`]
3347    /// gives the code and the variant gives the reason phrase. Answering it
3348    /// clears the record.
3349    pub fn filter_rejection(&self, id: VarInt) -> Option<&FilterRejection> {
3350        self.peer_filter_rejections.get(&id.into_inner())
3351    }
3352
3353    /// Whether this response answers a REQUEST_UPDATE rather than the request
3354    /// that opened the stream. Section 9.5 gives an update the same two
3355    /// answers a request has: "The receiver of a REQUEST_UPDATE MUST respond
3356    /// with exactly one REQUEST_OK or REQUEST_ERROR message indicating if the
3357    /// update was successful, unless it is coalescing failed updates to produce
3358    /// just one REQUEST_ERROR for multiple REQUEST_UPDATE messages." Nothing in
3359    /// either message says which of the two it is answering, so the question is
3360    /// settled twice over.
3361    ///
3362    /// A SUBSCRIBE is answered with SUBSCRIBE_OK and a FETCH with FETCH_OK, so a
3363    /// REQUEST_OK on one of those streams has no other message it could be
3364    /// answering. That is the half that needs no ordering.
3365    ///
3366    /// Everywhere else it is ordering: the first REQUEST_OK or REQUEST_ERROR on
3367    /// a stream answers the request that opened it, and the ones after it answer
3368    /// updates. Both endpoints have to resolve it the same way and neither has
3369    /// anything else to resolve it with.
3370    fn answers_an_update(&self, id: u64, msg: &ControlMessage) -> bool {
3371        if !matches!(msg, ControlMessage::RequestOk(_) | ControlMessage::RequestError(_)) {
3372            return false;
3373        }
3374        // A request this endpoint made is answered by the peer, so a response
3375        // written here can only be answering an update the peer sent on it.
3376        // Section 9.5 names the one request that works that way: "A subscriber
3377        // can also send REQUEST_UPDATE to modify parameters of a subscription
3378        // established with PUBLISH."
3379        if self.publishes.contains_key(&id) && !self.inbound_requests.contains_key(&id) {
3380            return true;
3381        }
3382        if matches!(msg, ControlMessage::RequestOk(_))
3383            && (self.subscriptions.contains_key(&id) || self.fetches.contains_key(&id))
3384        {
3385            return true;
3386        }
3387        self.answered_peer_requests.contains(&id)
3388    }
3389
3390    /// Record a response as the answer to one or more outstanding updates.
3391    ///
3392    /// A REQUEST_OK answers exactly one: "The receiver MUST still send a
3393    /// REQUEST_OK for each successful update". A REQUEST_ERROR may answer every
3394    /// update still waiting, because Section 9.5.1 permits the receiver to
3395    /// coalesce them — "If the coalesced REQUEST_UPDATE results in
3396    /// REQUEST_ERROR, only a single REQUEST_ERROR will be sent and the sender of
3397    /// the REQUEST_UPDATEs will not always be able to determine which caused an
3398    /// error." Draft-17 has no such paragraph, and its endpoint answers one
3399    /// update per message in both directions.
3400    ///
3401    /// The credit MAX_REQUEST_UPDATES counts is restored once whatever the
3402    /// answer covered, which is Section 9.1.7 read literally: "Each
3403    /// REQUEST_OK or REQUEST_ERROR response restores one credit on that stream."
3404    /// The peer restores by the same sentence, so an endpoint that gave back one
3405    /// per coalesced update would be crediting a peer that is not.
3406    ///
3407    /// No state machine moves. An update changes a request's parameters and not
3408    /// its lifecycle, so a subscription that was Active before its update was
3409    /// answered is Active after it, whichever answer went out.
3410    ///
3411    /// # Errors
3412    ///
3413    /// [`EndpointError::NoUpdateToAnswer`], and nothing is written or spent.
3414    ///
3415    /// [`EndpointError::PeerPrefixOverlap`] and
3416    /// [`EndpointError::WrongOverlapRefusal`] when the update asked to move a
3417    /// namespace subscription onto a prefix that overlaps another of its kind
3418    /// and the answer is not the REQUEST_ERROR the sentence names. The same
3419    /// two the request itself is refused with, because it is the same rule
3420    /// under the same code; which of the two moments raised it is told apart
3421    /// by which call returned.
3422    fn answer_an_update(&mut self, id: u64, msg: &ControlMessage) -> Result<(), EndpointError> {
3423        let unanswered = self.unanswered_peer_updates.get(&id).copied().unwrap_or(0);
3424        if unanswered == 0 {
3425            return Err(EndpointError::NoUpdateToAnswer(id));
3426        }
3427        // The update's half of the overlap rule, and the reason it needs a
3428        // verdict of its own: an update is answered by the same two messages
3429        // on the same stream as the request, so the moment one of them is
3430        // about to be written is the one place both the answer and the code
3431        // the sentence names are known. Section 9.20.21: "If the new prefix
3432        // would share a common prefix with another active subscription of the
3433        // same type in the same session, the receiver MUST respond with
3434        // REQUEST_ERROR with error code PREFIX_OVERLAP."
3435        if let Some(&established) = self.overlapping_prefix_updates.get(&id) {
3436            let ControlMessage::RequestError(err) = msg else {
3437                return Err(EndpointError::PeerPrefixOverlap { request: id, established });
3438            };
3439            let required = RequestErrorCode::PrefixOverlap as u64;
3440            if err.error_code.into_inner() != required {
3441                return Err(EndpointError::WrongOverlapRefusal { request: id, required });
3442            }
3443        }
3444        let answered = if matches!(msg, ControlMessage::RequestError(_)) { unanswered } else { 1 };
3445        self.unanswered_peer_updates.insert(id, unanswered - answered);
3446        self.restore_update_credit(id);
3447        // The same rule the request's own REQUEST_ERROR spends: a filter this
3448        // endpoint owes an error about has been answered.
3449        if matches!(msg, ControlMessage::RequestError(_)) {
3450            self.peer_filter_rejections.remove(&id);
3451        }
3452        // The prefix moves on the acceptance and not before: "If the update is
3453        // accepted, NAMESPACE and NAMESPACE_DONE messages following the
3454        // REQUEST_OK will contain Track Namespace suffixes relative to the
3455        // updated prefix." A REQUEST_ERROR drops it and the subscription goes
3456        // on selecting what it selected before the update was sent.
3457        //
3458        // Nothing else is recomputed. A subscription this endpoint has already
3459        // refused keeps that verdict when ground it wanted comes free, because
3460        // the sentence that refused it names the moment it arrived and that
3461        // moment has passed.
3462        let accepted = matches!(msg, ControlMessage::RequestOk(_));
3463        if let Some(prefix) = self.updated_namespace_prefixes.remove(&id) {
3464            if accepted {
3465                match self.inbound_requests.get_mut(&id) {
3466                    Some(ControlMessage::SubscribeNamespace(m)) => m.namespace_prefix = prefix,
3467                    Some(ControlMessage::SubscribeTracks(m)) => m.namespace_prefix = prefix,
3468                    // Unreachable: nothing else is ever written above.
3469                    _ => {}
3470                }
3471            }
3472        }
3473        if !accepted {
3474            self.overlapping_prefix_updates.remove(&id);
3475        }
3476        Ok(())
3477    }
3478
3479    /// Drive the state machine for a message this endpoint is about to write
3480    /// on a request stream the peer opened.
3481    ///
3482    /// The mirror of [`receive_response_on_stream`](Self::receive_response_on_stream),
3483    /// and the reason the transitions are named `*_sent` rather than reusing
3484    /// the received-side ones: the state edges coincide, so a mis-dispatch
3485    /// would otherwise succeed silently instead of naming the wrong event in
3486    /// an `InvalidTransition`.
3487    ///
3488    /// Beyond the four responses this also takes the three messages draft-21
3489    /// Table 5 places on a request stream that a responder writes after its
3490    /// response: NAMESPACE and NAMESPACE_DONE on a SUBSCRIBE_NAMESPACE stream
3491    /// (Sections 9.16 and 9.17) and PUBLISH_SKIPPED on a SUBSCRIBE_TRACKS
3492    /// stream (Section 9.19). All three require the request to have been
3493    /// accepted first, because each machine only leaves Pending on its
3494    /// REQUEST_OK.
3495    ///
3496    /// The caller writes `msg` only after this returns `Ok`. What it cannot
3497    /// undo is the opposite order: a write that fails afterwards leaves the
3498    /// state machine one step ahead of the wire, the same asymmetry the
3499    /// outbound request path already carries.
3500    /// Drive the state machines for a response this endpoint is about to write
3501    /// on a request stream the peer opened.
3502    ///
3503    /// The caller writes the message only after this returns `Ok`, so a
3504    /// response the endpoint refuses never reaches the wire.
3505    ///
3506    /// # Errors
3507    ///
3508    /// [`EndpointError::UnknownRequest`] when no request of the answering kind
3509    /// carries that identifier, [`EndpointError::NotAResponse`] when the
3510    /// message is not one, [`EndpointError::PeerPrefixOverlap`] and
3511    /// [`EndpointError::WrongOverlapRefusal`] for a namespace subscription that
3512    /// overlapped one already open or that a REQUEST_UPDATE asked to move onto
3513    /// an overlapping prefix, [`EndpointError::FilterMustBeRejected`] for a request whose Range Filters this draft says to reject, and each flow's own
3514    /// `InvalidTransition` for a request already answered.
3515    pub fn send_response_on_stream(
3516        &mut self,
3517        request_id: VarInt,
3518        msg: &ControlMessage,
3519    ) -> Result<(), EndpointError> {
3520        let id = request_id.into_inner();
3521        // A request whose Range Filters the draft says to reject may be
3522        // answered with a REQUEST_ERROR and with nothing else. Checked across
3523        // every acceptance rather than inside the REQUEST_OK arm, because
3524        // SUBSCRIBE and FETCH — the two requests that most obviously carry these
3525        // filters — are accepted with SUBSCRIBE_OK and FETCH_OK instead.
3526        if matches!(
3527            msg,
3528            ControlMessage::SubscribeOk(_)
3529                | ControlMessage::FetchOk(_)
3530                | ControlMessage::RequestOk(_)
3531        ) {
3532            if let Some(rejection) = self.peer_filter_rejections.get(&id) {
3533                return Err(EndpointError::FilterMustBeRejected(id, rejection.clone()));
3534            }
3535        }
3536        // An answer to an update reaches none of the arms below: the request it
3537        // belongs to has its own lifecycle and an update does not move it.
3538        // A refused update leaves this endpoint owing the peer a termination,
3539        // and the draft names the status that termination must carry. So the
3540        // request's ending is what the debt governs: a PUBLISH_DONE under any
3541        // other status is refused, and everything else the caller may still
3542        // have to write for this request - the answer to a second update it
3543        // has not answered yet - is left alone, because the sentence orders
3544        // nothing.
3545        if let ControlMessage::PublishDone(done) = msg {
3546            self.require_update_failure_status(id, done.status_code)?;
3547        }
3548        if self.answers_an_update(id, msg) {
3549            self.answer_an_update(id, msg)?;
3550            // Refusing an update is half of what the draft asks for, and which
3551            // half it is depends on what was being updated. A subscription is
3552            // owed the PUBLISH_DONE that ends it, recorded here so that the
3553            // next ending written for this request has to be that one. A
3554            // namespace request, a fetch and a track status are owed no
3555            // message at all, and recording one for them would keep a stream
3556            // open that has nothing left to carry.
3557            if matches!(msg, ControlMessage::RequestError(_)) && self.publishes_a_subscription(id) {
3558                self.owed_update_failures.insert(id);
3559            }
3560            return Ok(());
3561        }
3562        // A namespace subscription that overlapped one already open when it
3563        // arrived has one answer available to it, and this is where both the
3564        // answer and its code are known. An update's answer returned above, so
3565        // nothing here judges one.
3566        if let Some(&established) = self.overlapping_namespace_subscriptions.get(&id) {
3567            let ControlMessage::RequestError(err) = msg else {
3568                return Err(EndpointError::PeerPrefixOverlap { request: id, established });
3569            };
3570            let required = RequestErrorCode::PrefixOverlap as u64;
3571            if err.error_code.into_inner() != required {
3572                return Err(EndpointError::WrongOverlapRefusal { request: id, required });
3573            }
3574        }
3575        match msg {
3576            ControlMessage::SubscribeOk(_) => {
3577                let sm =
3578                    self.subscriptions.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
3579                sm.on_subscribe_ok_sent()?;
3580            }
3581            ControlMessage::FetchOk(_) => {
3582                let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
3583                sm.on_fetch_ok_sent()?;
3584            }
3585            ControlMessage::PublishDone(_) => {
3586                let sm =
3587                    self.subscriptions.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
3588                sm.on_publish_done_sent()?;
3589            }
3590            // Section 9.10, on the way out. Nothing moves: the message is
3591            // unilateral, so there is no answer to wait for and no lifecycle
3592            // edge to take. What is checked is the one thing writing it could
3593            // get wrong — a subscription this endpoint does not publish, which
3594            // is a message the peer must close the session over.
3595            ControlMessage::PublishStateNotify(_) => {
3596                if !self.subscriptions.contains_key(&id) || !self.inbound_requests.contains_key(&id)
3597                {
3598                    return Err(EndpointError::StateNotifyThisEndpointMayNotSend(id));
3599                }
3600            }
3601            // REQUEST_OK answers the kinds that have no response message of
3602            // their own, PUBLISH among them since draft-18 folded PUBLISH_OK
3603            // into it. The probe order does not matter: the maps are keyed by
3604            // Request ID and one id belongs to one request.
3605            ControlMessage::RequestOk(m) => {
3606                // Section 9.3 makes receiving Track Properties on anything but
3607                // a TRACK_STATUS response a session close, so putting them on
3608                // one of the others would be handing the peer a reason to close
3609                // this session. Refused before the write rather than after.
3610                if !m.track_properties.is_empty() && !self.track_statuses.contains_key(&id) {
3611                    return Err(EndpointError::TrackPropertiesOnOutgoingRequestOk(id));
3612                }
3613                if let Some(sm) = self.publishes.get_mut(&id) {
3614                    sm.on_publish_ok_sent()?;
3615                } else if let Some(sm) = self.subscribe_namespaces.get_mut(&id) {
3616                    sm.on_subscribe_namespace_ok_sent()?;
3617                } else if let Some(sm) = self.subscribe_tracks.get_mut(&id) {
3618                    sm.on_subscribe_namespace_ok_sent()?;
3619                } else if let Some(sm) = self.publish_namespaces.get_mut(&id) {
3620                    sm.on_publish_namespace_ok_sent()?;
3621                } else if let Some(sm) = self.track_statuses.get_mut(&id) {
3622                    sm.on_track_status_ok_sent()?;
3623                } else {
3624                    return Err(EndpointError::UnknownRequest(id));
3625                }
3626            }
3627            ControlMessage::RequestError(_) => {
3628                if let Some(sm) = self.subscriptions.get_mut(&id) {
3629                    sm.on_subscribe_error_sent()?;
3630                } else if let Some(sm) = self.fetches.get_mut(&id) {
3631                    // Draft-19 held one refusal of a FETCH to one code:
3632                    // INVALID_JOINING_REQUEST_ID, for a Joining Fetch naming no
3633                    // live subscription. Draft-21 Section 9.11 deleted the
3634                    // joining mechanism and Section 16.11.2 unassigned the code,
3635                    // so every FETCH refusal is now the caller's choice.
3636                    sm.on_fetch_error_sent()?;
3637                } else if let Some(sm) = self.publishes.get_mut(&id) {
3638                    sm.on_publish_error_sent()?;
3639                } else if let Some(sm) = self.subscribe_namespaces.get_mut(&id) {
3640                    sm.on_subscribe_namespace_error_sent()?;
3641                } else if let Some(sm) = self.subscribe_tracks.get_mut(&id) {
3642                    sm.on_subscribe_namespace_error_sent()?;
3643                } else if let Some(sm) = self.publish_namespaces.get_mut(&id) {
3644                    sm.on_publish_namespace_error_sent()?;
3645                } else if let Some(sm) = self.track_statuses.get_mut(&id) {
3646                    sm.on_track_status_error_sent()?;
3647                } else {
3648                    return Err(EndpointError::UnknownRequest(id));
3649                }
3650            }
3651            ControlMessage::Namespace(_) => {
3652                let sm = self
3653                    .subscribe_namespaces
3654                    .get_mut(&id)
3655                    .ok_or(EndpointError::UnknownRequest(id))?;
3656                sm.on_namespace_sent()?;
3657            }
3658            ControlMessage::NamespaceDone(_) => {
3659                let sm = self
3660                    .subscribe_namespaces
3661                    .get_mut(&id)
3662                    .ok_or(EndpointError::UnknownRequest(id))?;
3663                sm.on_namespace_done_sent()?;
3664            }
3665            ControlMessage::PublishSkipped(_) => {
3666                let sm =
3667                    self.subscribe_tracks.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
3668                sm.on_publish_skipped_sent()?;
3669            }
3670            other => return Err(EndpointError::NotAResponse(other.message_type())),
3671        }
3672        // After the match, not before it: the caller writes `msg` only once
3673        // this returns `Ok`, so a response that was refused restores nothing.
3674        if matches!(msg, ControlMessage::RequestOk(_) | ControlMessage::RequestError(_)) {
3675            self.restore_update_credit(id);
3676        }
3677        // Reached only by a response that answered the request itself, since an
3678        // update's answer returned above. From here on, a REQUEST_OK or
3679        // REQUEST_ERROR on this stream can only be answering an update.
3680        if matches!(
3681            msg,
3682            ControlMessage::SubscribeOk(_)
3683                | ControlMessage::FetchOk(_)
3684                | ControlMessage::RequestOk(_)
3685                | ControlMessage::RequestError(_)
3686        ) {
3687            self.answered_peer_requests.insert(id);
3688        }
3689        // For the same reason, and it matters more here: a rejection cleared by
3690        // a REQUEST_ERROR that was itself refused would leave the request
3691        // acceptable on the next attempt.
3692        if matches!(msg, ControlMessage::RequestError(_)) {
3693            self.peer_filter_rejections.remove(&id);
3694        }
3695        Ok(())
3696    }
3697
3698    /// Dispatch a message that arrived on a request stream the **peer** opened,
3699    /// after the request that opened it.
3700    ///
3701    /// Nothing that arrives here is a response: this endpoint is the responder
3702    /// on such a stream, so a SUBSCRIBE_OK or REQUEST_ERROR turning up is the
3703    /// peer answering its own request, and it is refused with
3704    /// [`EndpointError::UnexpectedOnPeerRequestStream`].
3705    ///
3706    /// Four messages are expected instead.
3707    ///
3708    /// REQUEST_UPDATE, because draft-21 Section 9.5 puts it on the request's
3709    /// own stream: "The sender of a request (SUBSCRIBE, PUBLISH, FETCH,
3710    /// PUBLISH_NAMESPACE, SUBSCRIBE_NAMESPACE, SUBSCRIBE_TRACKS) can later send
3711    /// a REQUEST_UPDATE on the same bidi stream as the request to modify it."
3712    /// It goes through the same [`receive_request_update`](Self::receive_request_update)
3713    /// the requester side uses, so the peer's update is held to the same rule
3714    /// its own would be: the message's Request ID must name the stream's
3715    /// request, and a kind the section does not allow to be updated —
3716    /// TRACK_STATUS, by Section 9.13 — closes the session.
3717    ///
3718    /// GOAWAY, because Section 9.2 lets one arrive on a request stream to
3719    /// migrate that request alone, in either direction.
3720    ///
3721    /// PUBLISH_DONE, because a peer that sent PUBLISH is the publisher and ends
3722    /// the publication it opened.
3723    ///
3724    /// PUBLISH_STATE_NOTIFY, new in draft-20 (Section 9.10), for the same
3725    /// reason: a peer that sent PUBLISH is the publisher, and the publisher is
3726    /// the one end that may send it. On a stream the peer opened with a
3727    /// SUBSCRIBE the roles are the other way round, and it is refused.
3728    pub fn receive_on_peer_request_stream(
3729        &mut self,
3730        request_id: VarInt,
3731        msg: ControlMessage,
3732    ) -> Result<(), EndpointError> {
3733        let id = request_id.into_inner();
3734        match msg {
3735            ControlMessage::RequestUpdate(ref m) => self.receive_request_update(request_id, m),
3736            // Section 9.2 puts no direction on it: "A GOAWAY MAY also be
3737            // sent on a request stream to initiate migration of that individual
3738            // request." A request the peer opened is a request stream, so a
3739            // GOAWAY on one migrates it the same way.
3740            ControlMessage::GoAway(ref m) => self.receive_goaway_on_request_stream(request_id, m),
3741            ControlMessage::PublishDone(_) => {
3742                let sm = self.publishes.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
3743                sm.on_publish_done_received()?;
3744                Ok(())
3745            }
3746            // A PUBLISH the peer opened the stream with makes the peer the
3747            // publisher and this endpoint the subscriber, so a notify on it is
3748            // from the end Section 9.10 permits. On a stream the peer opened
3749            // with a SUBSCRIBE the roles are the other way round, and
3750            // `receive_publish_state_notify` refuses it with the code the
3751            // section names.
3752            ControlMessage::PublishStateNotify(ref m) => {
3753                self.receive_publish_state_notify(request_id, m)
3754            }
3755            other => Err(EndpointError::UnexpectedOnPeerRequestStream(other.message_type())),
3756        }
3757    }
3758}
3759
3760// -- Responder-side state machine transitions -----------------------
3761//
3762// Each one is the same edge as an existing requester-side transition — the
3763// graph does not change with the direction — but under a name that says which
3764// way the message went, so a mis-dispatch names the responder event in the
3765// `InvalidTransition` it produces instead of quietly succeeding.
3766//
3767// They are written against the public surface of the machines rather than
3768// against the state field, so the rejected-state error has to be rebuilt to
3769// carry the responder event name.
3770
3771impl SubscriptionStateMachine {
3772    /// Idle -> Subscribing (SUBSCRIBE received from the peer).
3773    pub fn on_subscribe_received(&mut self) -> Result<(), SubscriptionError> {
3774        self.on_subscribe_sent().map_err(|_| SubscriptionError::InvalidTransition {
3775            from: self.state(),
3776            event: "on_subscribe_received".to_string(),
3777        })
3778    }
3779
3780    /// Subscribing -> Active (SUBSCRIBE_OK written on the peer's stream).
3781    pub fn on_subscribe_ok_sent(&mut self) -> Result<(), SubscriptionError> {
3782        self.on_subscribe_ok().map_err(|_| SubscriptionError::InvalidTransition {
3783            from: self.state(),
3784            event: "on_subscribe_ok_sent".to_string(),
3785        })
3786    }
3787
3788    /// Subscribing -> Done (REQUEST_ERROR written on the peer's stream).
3789    pub fn on_subscribe_error_sent(&mut self) -> Result<(), SubscriptionError> {
3790        self.on_subscribe_error().map_err(|_| SubscriptionError::InvalidTransition {
3791            from: self.state(),
3792            event: "on_subscribe_error_sent".to_string(),
3793        })
3794    }
3795
3796    /// Active -> Done (PUBLISH_DONE written on the peer's stream).
3797    pub fn on_publish_done_sent(&mut self) -> Result<(), SubscriptionError> {
3798        self.on_publish_done().map_err(|_| SubscriptionError::InvalidTransition {
3799            from: self.state(),
3800            event: "on_publish_done_sent".to_string(),
3801        })
3802    }
3803}
3804
3805impl FetchStateMachine {
3806    /// Idle -> Pending (FETCH received from the peer).
3807    pub fn on_fetch_received(&mut self) -> Result<(), FetchError> {
3808        self.on_fetch_sent().map_err(|_| FetchError::InvalidTransition {
3809            from: self.state(),
3810            event: "on_fetch_received".to_string(),
3811        })
3812    }
3813
3814    /// Pending -> Receiving, Unanswered -> Done (FETCH_OK written on the
3815    /// peer's stream).
3816    ///
3817    /// The state is named for the requester's view; for a responder the same
3818    /// node means the objects are being served rather than received. It is the
3819    /// same node in the graph, with the same edges, so it keeps its name.
3820    pub fn on_fetch_ok_sent(&mut self) -> Result<(), FetchError> {
3821        self.on_fetch_ok().map_err(|_| FetchError::InvalidTransition {
3822            from: self.state(),
3823            event: "on_fetch_ok_sent".to_string(),
3824        })
3825    }
3826
3827    /// Pending | Unanswered -> Done (REQUEST_ERROR written on the peer's
3828    /// stream).
3829    pub fn on_fetch_error_sent(&mut self) -> Result<(), FetchError> {
3830        self.on_fetch_error().map_err(|_| FetchError::InvalidTransition {
3831            from: self.state(),
3832            event: "on_fetch_error_sent".to_string(),
3833        })
3834    }
3835
3836    /// Receiving -> Done, Pending -> Unanswered (this endpoint finished the
3837    /// fetch data stream).
3838    pub fn on_stream_fin_sent(&mut self) -> Result<(), FetchError> {
3839        self.on_stream_fin().map_err(|_| FetchError::InvalidTransition {
3840            from: self.state(),
3841            event: "on_stream_fin_sent".to_string(),
3842        })
3843    }
3844}
3845
3846impl PublishStateMachine {
3847    /// Idle -> Publishing (PUBLISH received from the peer).
3848    pub fn on_publish_received(&mut self) -> Result<(), PublishFlowError> {
3849        self.on_publish_sent().map_err(|_| PublishFlowError::InvalidTransition {
3850            from: self.state(),
3851            event: "on_publish_received".to_string(),
3852        })
3853    }
3854
3855    /// Publishing -> Active (REQUEST_OK written on the peer's stream).
3856    ///
3857    /// Draft-18 folded PUBLISH_OK into REQUEST_OK and draft-21 keeps it that
3858    /// way, so the message that walks this edge is a REQUEST_OK here where on
3859    /// draft-17 it was a PUBLISH_OK of its own.
3860    pub fn on_publish_ok_sent(&mut self) -> Result<(), PublishFlowError> {
3861        self.on_publish_ok().map_err(|_| PublishFlowError::InvalidTransition {
3862            from: self.state(),
3863            event: "on_publish_ok_sent".to_string(),
3864        })
3865    }
3866
3867    /// Publishing -> Done (REQUEST_ERROR written on the peer's stream).
3868    pub fn on_publish_error_sent(&mut self) -> Result<(), PublishFlowError> {
3869        self.on_publish_error().map_err(|_| PublishFlowError::InvalidTransition {
3870            from: self.state(),
3871            event: "on_publish_error_sent".to_string(),
3872        })
3873    }
3874
3875    /// Active -> Done (PUBLISH_DONE received from the publishing peer).
3876    pub fn on_publish_done_received(&mut self) -> Result<(), PublishFlowError> {
3877        self.on_publish_done_sent().map_err(|_| PublishFlowError::InvalidTransition {
3878            from: self.state(),
3879            event: "on_publish_done_received".to_string(),
3880        })
3881    }
3882}
3883
3884impl PublishNamespaceStateMachine {
3885    /// Idle -> Pending (PUBLISH_NAMESPACE received from the peer).
3886    pub fn on_publish_namespace_received(&mut self) -> Result<(), NamespaceError> {
3887        self.on_publish_namespace_sent().map_err(|_| NamespaceError::InvalidTransition {
3888            from: format!("{:?}", self.state()),
3889            event: "on_publish_namespace_received".to_string(),
3890        })
3891    }
3892
3893    /// Pending -> Active (REQUEST_OK written on the peer's stream).
3894    pub fn on_publish_namespace_ok_sent(&mut self) -> Result<(), NamespaceError> {
3895        self.on_publish_namespace_ok().map_err(|_| NamespaceError::InvalidTransition {
3896            from: format!("{:?}", self.state()),
3897            event: "on_publish_namespace_ok_sent".to_string(),
3898        })
3899    }
3900
3901    /// Pending -> Done (REQUEST_ERROR written on the peer's stream).
3902    pub fn on_publish_namespace_error_sent(&mut self) -> Result<(), NamespaceError> {
3903        self.on_publish_namespace_error().map_err(|_| NamespaceError::InvalidTransition {
3904            from: format!("{:?}", self.state()),
3905            event: "on_publish_namespace_error_sent".to_string(),
3906        })
3907    }
3908}
3909
3910impl SubscribeNamespaceStateMachine {
3911    /// Idle -> Pending (SUBSCRIBE_NAMESPACE or SUBSCRIBE_TRACKS received from
3912    /// the peer).
3913    pub fn on_subscribe_namespace_received(&mut self) -> Result<(), NamespaceError> {
3914        self.on_subscribe_namespace_sent().map_err(|_| NamespaceError::InvalidTransition {
3915            from: format!("{:?}", self.state()),
3916            event: "on_subscribe_namespace_received".to_string(),
3917        })
3918    }
3919
3920    /// Pending -> Active (REQUEST_OK written on the peer's stream).
3921    pub fn on_subscribe_namespace_ok_sent(&mut self) -> Result<(), NamespaceError> {
3922        self.on_subscribe_namespace_ok().map_err(|_| NamespaceError::InvalidTransition {
3923            from: format!("{:?}", self.state()),
3924            event: "on_subscribe_namespace_ok_sent".to_string(),
3925        })
3926    }
3927
3928    /// Pending -> Done (REQUEST_ERROR written on the peer's stream).
3929    pub fn on_subscribe_namespace_error_sent(&mut self) -> Result<(), NamespaceError> {
3930        self.on_subscribe_namespace_error().map_err(|_| NamespaceError::InvalidTransition {
3931            from: format!("{:?}", self.state()),
3932            event: "on_subscribe_namespace_error_sent".to_string(),
3933        })
3934    }
3935
3936    /// Active -> Active (NAMESPACE written on the peer's SUBSCRIBE_NAMESPACE
3937    /// stream).
3938    ///
3939    /// Draft-21 Section 9.16 puts NAMESPACE "on the response stream of a
3940    /// SUBSCRIBE_NAMESPACE request", and Section 9.15 has the publisher send
3941    /// them only once the request has been accepted — "If the
3942    /// SUBSCRIBE_NAMESPACE is successful, the publisher will send matching
3943    /// NAMESPACE messages on the response stream." Requiring Active is what
3944    /// makes one written ahead of the REQUEST_OK an error rather than a frame
3945    /// on the wire.
3946    ///
3947    /// Draft-17 has no such edge: its message table has no Stream column and
3948    /// nothing there moves NAMESPACE off the control stream.
3949    pub fn on_namespace_sent(&mut self) -> Result<(), NamespaceError> {
3950        self.require_active("on_namespace_sent")
3951    }
3952
3953    /// Active -> Active (NAMESPACE_DONE written on the peer's
3954    /// SUBSCRIBE_NAMESPACE stream).
3955    ///
3956    /// Section 9.17: "All NAMESPACE_DONE messages are in response to a
3957    /// SUBSCRIBE_NAMESPACE". The namespace subscription outlives it — Section
3958    /// 9.17 has the publisher go on sending NAMESPACE and NAMESPACE_DONE "when
3959    /// there are changes to the namespaces being published" — so this ends one
3960    /// namespace, not the request, and the state does not move.
3961    pub fn on_namespace_done_sent(&mut self) -> Result<(), NamespaceError> {
3962        self.require_active("on_namespace_done_sent")
3963    }
3964
3965    /// Active -> Active (PUBLISH_SKIPPED written on the peer's
3966    /// SUBSCRIBE_TRACKS stream).
3967    ///
3968    /// Section 9.19: "All PUBLISH_SKIPPED messages are in response to a
3969    /// SUBSCRIBE_TRACKS". One skipped track says nothing about the rest, so
3970    /// like the two above this is a self-transition on an accepted request.
3971    pub fn on_publish_skipped_sent(&mut self) -> Result<(), NamespaceError> {
3972        self.require_active("on_publish_skipped_sent")
3973    }
3974
3975    /// The shared body of the three self-transitions above: accept the event
3976    /// when the request has been answered with REQUEST_OK, and name the event
3977    /// that was refused otherwise.
3978    fn require_active(&self, event: &str) -> Result<(), NamespaceError> {
3979        if self.state() == SubscribeNamespaceState::Active {
3980            Ok(())
3981        } else {
3982            Err(NamespaceError::InvalidTransition {
3983                from: format!("{:?}", self.state()),
3984                event: event.to_string(),
3985            })
3986        }
3987    }
3988}
3989
3990impl TrackStatusStateMachine {
3991    /// Idle -> Pending (TRACK_STATUS received from the peer).
3992    pub fn on_track_status_received(&mut self) -> Result<(), TrackStatusError> {
3993        self.on_track_status_sent().map_err(|_| TrackStatusError::InvalidTransition {
3994            from: self.state(),
3995            event: "on_track_status_received".to_string(),
3996        })
3997    }
3998
3999    /// Pending -> Done (REQUEST_OK written on the peer's stream).
4000    pub fn on_track_status_ok_sent(&mut self) -> Result<(), TrackStatusError> {
4001        self.on_track_status_ok().map_err(|_| TrackStatusError::InvalidTransition {
4002            from: self.state(),
4003            event: "on_track_status_ok_sent".to_string(),
4004        })
4005    }
4006
4007    /// Pending -> Done (REQUEST_ERROR written on the peer's stream).
4008    pub fn on_track_status_error_sent(&mut self) -> Result<(), TrackStatusError> {
4009        self.on_track_status_error().map_err(|_| TrackStatusError::InvalidTransition {
4010            from: self.state(),
4011            event: "on_track_status_error_sent".to_string(),
4012        })
4013    }
4014}
4015
4016#[cfg(test)]
4017mod responder_tests {
4018    use super::*;
4019    use crate::draft21::namespace::SubscribeNamespaceState;
4020    use crate::draft21::publish::PublishState;
4021    use crate::draft21::subscription::SubscriptionState;
4022    use moqtap_codec::kvp::KvpValue;
4023
4024    fn active_client() -> Endpoint {
4025        let mut ep = Endpoint::new(Role::Client);
4026        ep.connect().unwrap();
4027        let _ = ep.send_setup(vec![]).unwrap();
4028        ep.receive_setup(&Setup { options: vec![] }).unwrap();
4029        ep
4030    }
4031
4032    fn v(n: u64) -> VarInt {
4033        VarInt::from_u64(n).unwrap()
4034    }
4035
4036    fn ns() -> TrackNamespace {
4037        TrackNamespace(vec![b"live".to_vec()])
4038    }
4039
4040    fn peer_subscribe(id: u64) -> ControlMessage {
4041        ControlMessage::Subscribe(Subscribe {
4042            request_id: v(id),
4043            track_namespace: ns(),
4044            track_name: b"video".to_vec(),
4045            parameters: vec![],
4046        })
4047    }
4048
4049    fn peer_publish(id: u64) -> ControlMessage {
4050        ControlMessage::Publish(Publish {
4051            request_id: v(id),
4052            track_namespace: ns(),
4053            track_name: b"video".to_vec(),
4054            track_alias: v(7),
4055            parameters: vec![],
4056            track_properties: vec![],
4057        })
4058    }
4059
4060    fn request_ok() -> ControlMessage {
4061        ControlMessage::RequestOk(RequestOk { parameters: vec![], track_properties: vec![] })
4062    }
4063
4064    fn publish_done() -> ControlMessage {
4065        ControlMessage::PublishDone(PublishDone {
4066            status_code: v(0),
4067            stream_count: v(0),
4068            reason_phrase: Vec::new(),
4069        })
4070    }
4071
4072    /// The peer's requests and this endpoint's share one map per kind, and the
4073    /// opposite Request ID parity is what keeps them apart. Both directions
4074    /// are registered here and both are still there afterwards, which is the
4075    /// consequence a collision would destroy.
4076    #[test]
4077    fn a_peers_request_lives_beside_our_own_in_the_same_map() {
4078        let mut ep = active_client();
4079        let (ours, _) = ep.subscribe(ns(), b"video".to_vec(), vec![]).unwrap();
4080        assert_eq!(ours.into_inner(), 0, "a client allocates even Request IDs");
4081
4082        let theirs = ep.receive_request_on_stream(&peer_subscribe(1)).unwrap();
4083        assert_eq!(theirs.into_inner(), 1);
4084        assert_eq!(
4085            ep.active_subscription_count(),
4086            2,
4087            "the peer's subscription displaced ours in the map",
4088        );
4089        assert_eq!(ep.peer_request_count(), 1);
4090    }
4091
4092    /// Draft-21 Section 6.4.2.1: a Request ID whose least significant bit is
4093    /// wrong for the sender MUST close the session with INVALID_REQUEST_ID.
4094    /// The close is observable twice over — the endpoint stops accepting
4095    /// requests, and the code the connection layer will put on the wire is the
4096    /// one the section names.
4097    #[test]
4098    fn a_peer_id_with_our_own_parity_closes_the_session() {
4099        let mut ep = active_client();
4100        // 2 is even, so it is an id this client allocates, not one the server
4101        // may send.
4102        let err = ep.receive_request_on_stream(&peer_subscribe(2)).unwrap_err();
4103        assert_eq!(err.session_error_code(), Some(SessionErrorCode::InvalidRequestId));
4104        assert_eq!(err.to_string(), "request ID error: request ID 2 has wrong parity for Server");
4105        assert_eq!(ep.session_state(), SessionState::Closed);
4106        assert_eq!(ep.active_subscription_count(), 0, "a refused request was registered anyway");
4107        assert!(matches!(
4108            ep.receive_request_on_stream(&peer_subscribe(1)),
4109            Err(EndpointError::NotActive),
4110        ));
4111    }
4112
4113    /// The other half of the same sentence: a duplicate Request ID is also
4114    /// INVALID_REQUEST_ID. The id is remembered even though the first request
4115    /// is still open, which is why the second is caught.
4116    #[test]
4117    fn a_repeated_peer_request_id_closes_the_session() {
4118        let mut ep = active_client();
4119        ep.receive_request_on_stream(&peer_subscribe(1)).unwrap();
4120        let err = ep.receive_request_on_stream(&peer_publish(1)).unwrap_err();
4121        assert_eq!(err.session_error_code(), Some(SessionErrorCode::InvalidRequestId));
4122        assert_eq!(err.to_string(), "request 1 was already used by the peer");
4123        assert_eq!(ep.session_state(), SessionState::Closed);
4124        assert_eq!(ep.active_publish_count(), 0, "the duplicate was registered anyway");
4125    }
4126
4127    /// Draft-21 Section 6.3 gives a different code for a different rule: a
4128    /// bidirectional stream that begins with the wrong message type is a
4129    /// PROTOCOL_VIOLATION, not an INVALID_REQUEST_ID.
4130    #[test]
4131    fn a_stream_that_opens_no_request_closes_the_session_with_protocol_violation() {
4132        let mut ep = active_client();
4133        let not_a_request =
4134            ControlMessage::GoAway(GoAway { new_session_uri: Vec::new(), timeout: v(0) });
4135        let err = ep.receive_request_on_stream(&not_a_request).unwrap_err();
4136        assert_eq!(err.session_error_code(), Some(SessionErrorCode::ProtocolViolation));
4137        assert_eq!(err.to_string(), "GoAway does not begin a request stream");
4138        assert_eq!(ep.session_state(), SessionState::Closed);
4139    }
4140
4141    /// A peer's SUBSCRIBE runs the same graph our own does, in the other
4142    /// direction: received, then answered, then ended with PUBLISH_DONE by
4143    /// this endpoint rather than by the peer.
4144    #[test]
4145    fn answering_a_peers_subscribe_walks_the_subscription_to_done() {
4146        let mut ep = active_client();
4147        let id = ep.receive_request_on_stream(&peer_subscribe(1)).unwrap();
4148
4149        let ok = ControlMessage::SubscribeOk(SubscribeOk {
4150            track_alias: v(7),
4151            parameters: vec![],
4152            track_properties: vec![],
4153        });
4154        ep.send_response_on_stream(id, &ok).unwrap();
4155        assert_eq!(ep.subscriptions[&1].state(), SubscriptionState::Active);
4156
4157        ep.send_response_on_stream(id, &publish_done()).unwrap();
4158        assert_eq!(ep.subscriptions[&1].state(), SubscriptionState::Done);
4159    }
4160
4161    /// The responder transitions are separate from the requester ones so a
4162    /// mis-dispatch names the responder event rather than succeeding quietly.
4163    #[test]
4164    fn a_responder_transition_out_of_order_names_the_responder_event() {
4165        let mut ep = active_client();
4166        let id = ep.receive_request_on_stream(&peer_subscribe(1)).unwrap();
4167        // PUBLISH_DONE before SUBSCRIBE_OK: the subscription is not Active.
4168        let err = ep.send_response_on_stream(id, &publish_done()).unwrap_err();
4169        assert_eq!(
4170            err.to_string(),
4171            "subscription error: invalid transition from Subscribing on event on_publish_done_sent",
4172        );
4173    }
4174
4175    /// Draft-18 folded PUBLISH_OK into REQUEST_OK and draft-21 keeps the fold,
4176    /// so a peer's PUBLISH is accepted with REQUEST_OK here where draft-17
4177    /// answers with a PUBLISH_OK of its own. The peer is then the publisher,
4178    /// so PUBLISH_DONE comes back from it on the same stream — the one
4179    /// direction the requester path never has to handle, because there we are
4180    /// the publisher.
4181    #[test]
4182    fn a_peers_publish_is_accepted_with_request_ok_and_ended_by_the_peer() {
4183        let mut ep = active_client();
4184        let id = ep.receive_request_on_stream(&peer_publish(1)).unwrap();
4185        ep.send_response_on_stream(id, &request_ok()).unwrap();
4186        assert_eq!(ep.publishes[&1].state(), PublishState::Active);
4187
4188        ep.receive_on_peer_request_stream(id, publish_done()).unwrap();
4189        assert_eq!(ep.publishes[&1].state(), PublishState::Done);
4190        assert!(
4191            ep.receive_on_peer_request_stream(id, publish_done()).is_err(),
4192            "a second PUBLISH_DONE was accepted on a publication already Done",
4193        );
4194    }
4195
4196    /// This endpoint is the responder on a stream the peer opened, so a
4197    /// response arriving there is the peer answering itself. Routing it to the
4198    /// response dispatcher would look up a request we never made; refusing it
4199    /// is what the origin marker buys.
4200    #[test]
4201    fn a_response_on_a_peer_opened_stream_is_refused() {
4202        let mut ep = active_client();
4203        let id = ep.receive_request_on_stream(&peer_subscribe(1)).unwrap();
4204        let ok = ControlMessage::SubscribeOk(SubscribeOk {
4205            track_alias: v(7),
4206            parameters: vec![],
4207            track_properties: vec![],
4208        });
4209        let err = ep.receive_on_peer_request_stream(id, ok).unwrap_err();
4210        assert_eq!(
4211            err.to_string(),
4212            "SubscribeOk may not follow a request on a stream the peer opened",
4213        );
4214    }
4215
4216    /// A peer's REQUEST_UPDATE is held to the same rule the requester side is:
4217    /// draft-21 Section 9.5 puts it on its request's own stream, and the
4218    /// audited receive path already refuses one whose Request ID names a
4219    /// different request. Routing the peer's updates through that same handler
4220    /// is what keeps the two directions from disagreeing.
4221    #[test]
4222    fn a_peers_request_update_is_held_to_its_own_stream() {
4223        let mut ep = active_client();
4224        let id = ep.receive_request_on_stream(&peer_subscribe(1)).unwrap();
4225        let ok = ControlMessage::SubscribeOk(SubscribeOk {
4226            track_alias: v(7),
4227            parameters: vec![],
4228            track_properties: vec![],
4229        });
4230        ep.send_response_on_stream(id, &ok).unwrap();
4231
4232        let update = |named: u64| {
4233            ControlMessage::RequestUpdate(RequestUpdate {
4234                request_id: v(named),
4235                parameters: vec![],
4236            })
4237        };
4238        ep.receive_on_peer_request_stream(id, update(1)).unwrap();
4239        assert_eq!(ep.subscriptions[&1].state(), SubscriptionState::Active);
4240
4241        // An update naming a different request than its stream is the
4242        // violation the section answers with a close.
4243        let err = ep.receive_on_peer_request_stream(id, update(3)).unwrap_err();
4244        assert!(matches!(err, EndpointError::UnexpectedRequestUpdate(3)), "{err}");
4245        assert_eq!(ep.session_state(), SessionState::Closed);
4246    }
4247
4248    /// SUBSCRIBE_TRACKS (0x51) is the seventh request kind, added in draft-18
4249    /// and absent from draft-17. A responder that carried draft-17's six over
4250    /// would refuse it as a non-request and close the session, so the
4251    /// consequence checked is that it is registered and answerable.
4252    ///
4253    /// Section 9.18 keeps its overlap space independent of
4254    /// SUBSCRIBE_NAMESPACE's, which is why it lands in its own map.
4255    #[test]
4256    fn a_peers_subscribe_tracks_is_a_request_of_its_own() {
4257        let mut ep = active_client();
4258        let tracks = ControlMessage::SubscribeTracks(SubscribeTracks {
4259            request_id: v(1),
4260            namespace_prefix: ns(),
4261            parameters: vec![],
4262        });
4263        let id = ep.receive_request_on_stream(&tracks).unwrap();
4264        assert_eq!(ep.active_subscribe_tracks_count(), 1);
4265        assert_eq!(
4266            ep.active_subscribe_namespace_count(),
4267            0,
4268            "SUBSCRIBE_TRACKS landed among the namespace subscriptions",
4269        );
4270        ep.send_response_on_stream(id, &request_ok()).unwrap();
4271        assert_eq!(ep.subscribe_tracks[&1].state(), SubscribeNamespaceState::Active);
4272
4273        // Section 9.19 puts PUBLISH_SKIPPED on this stream, and only on this
4274        // stream: a SUBSCRIBE_NAMESPACE has no such message.
4275        let skipped = ControlMessage::PublishSkipped(PublishSkipped {
4276            namespace_suffix: ns(),
4277            track_name: b"video".to_vec(),
4278        });
4279        ep.send_response_on_stream(id, &skipped).unwrap();
4280
4281        let mut other = active_client();
4282        let sub_ns = other
4283            .receive_request_on_stream(&ControlMessage::SubscribeNamespace(SubscribeNamespace {
4284                request_id: v(1),
4285                namespace_prefix: ns(),
4286                parameters: vec![],
4287            }))
4288            .unwrap();
4289        other.send_response_on_stream(sub_ns, &request_ok()).unwrap();
4290        assert!(
4291            other.send_response_on_stream(sub_ns, &skipped).is_err(),
4292            "PUBLISH_SKIPPED was accepted on a SUBSCRIBE_NAMESPACE stream",
4293        );
4294    }
4295
4296    /// Draft-21 Table 5 gives NAMESPACE and NAMESPACE_DONE the Stream value
4297    /// "Request", and Section 9.15 has the publisher send them only once the
4298    /// SUBSCRIBE_NAMESPACE has been accepted: "If the SUBSCRIBE_NAMESPACE is
4299    /// successful, the publisher will send matching NAMESPACE messages on the
4300    /// response stream."
4301    ///
4302    /// Draft-17 has neither edge — its message table has no Stream column — so
4303    /// this is the half of the responder a port from draft-17 would leave out.
4304    #[test]
4305    fn namespaces_are_announced_on_the_peers_subscribe_namespace_stream() {
4306        let mut ep = active_client();
4307        let id = ep
4308            .receive_request_on_stream(&ControlMessage::SubscribeNamespace(SubscribeNamespace {
4309                request_id: v(1),
4310                namespace_prefix: ns(),
4311                parameters: vec![],
4312            }))
4313            .unwrap();
4314
4315        let namespace = ControlMessage::Namespace(message::Namespace { namespace_suffix: ns() });
4316        let err = ep.send_response_on_stream(id, &namespace).unwrap_err();
4317        assert_eq!(
4318            err.to_string(),
4319            "namespace error: invalid transition from Pending on event on_namespace_sent",
4320            "a NAMESPACE was allowed ahead of the REQUEST_OK that accepts the request",
4321        );
4322
4323        ep.send_response_on_stream(id, &request_ok()).unwrap();
4324        ep.send_response_on_stream(id, &namespace).unwrap();
4325        ep.send_response_on_stream(
4326            id,
4327            &ControlMessage::NamespaceDone(message::NamespaceDone { namespace_suffix: ns() }),
4328        )
4329        .unwrap();
4330        // One namespace ending does not end the subscription to the prefix.
4331        assert_eq!(ep.subscribe_namespaces[&1].state(), SubscribeNamespaceState::Active);
4332    }
4333
4334    /// Draft-21 Section 9.3 answers Track Properties on a REQUEST_OK that is
4335    /// not a TRACK_STATUS response with a session close. The receive path
4336    /// already refuses them; a responder that writes them would be handing a
4337    /// conforming peer that reason, so the send path refuses them too — and
4338    /// without closing this session, because nothing reached the wire.
4339    #[test]
4340    fn track_properties_are_refused_on_the_way_out_too() {
4341        let properties = vec![KeyValuePair { key: v(0x04), value: KvpValue::Varint(v(1000)) }];
4342        let with_properties = ControlMessage::RequestOk(RequestOk {
4343            parameters: vec![],
4344            track_properties: properties.clone(),
4345        });
4346
4347        let mut ep = active_client();
4348        let id = ep
4349            .receive_request_on_stream(&ControlMessage::SubscribeNamespace(SubscribeNamespace {
4350                request_id: v(1),
4351                namespace_prefix: ns(),
4352                parameters: vec![],
4353            }))
4354            .unwrap();
4355        let err = ep.send_response_on_stream(id, &with_properties).unwrap_err();
4356        assert!(matches!(err, EndpointError::TrackPropertiesOnOutgoingRequestOk(1)), "{err}");
4357        assert_eq!(err.session_error_code(), None, "refusing our own write closed the session");
4358        assert_eq!(
4359            ep.subscribe_namespaces[&1].state(),
4360            SubscribeNamespaceState::Pending,
4361            "the refused response moved the state machine anyway",
4362        );
4363        // The request is still answerable without them.
4364        ep.send_response_on_stream(id, &request_ok()).unwrap();
4365
4366        // TRACK_STATUS_OK is the shape that carries them.
4367        let mut ep = active_client();
4368        let id = ep
4369            .receive_request_on_stream(&ControlMessage::TrackStatus(message::TrackStatus {
4370                request_id: v(1),
4371                track_namespace: ns(),
4372                track_name: b"video".to_vec(),
4373                parameters: vec![],
4374            }))
4375            .unwrap();
4376        ep.send_response_on_stream(id, &with_properties).unwrap();
4377    }
4378}
4379
4380#[cfg(test)]
4381mod tests {
4382    use super::*;
4383    use moqtap_codec::kvp::KvpValue;
4384
4385    fn v(n: u64) -> VarInt {
4386        VarInt::from_u64_moqt(n)
4387    }
4388
4389    fn ns(label: &str) -> TrackNamespace {
4390        TrackNamespace(vec![label.as_bytes().to_vec()])
4391    }
4392
4393    fn peer_subscribe(id: u64, label: &str) -> ControlMessage {
4394        ControlMessage::Subscribe(Subscribe {
4395            request_id: v(id),
4396            track_namespace: ns(label),
4397            track_name: b"video".to_vec(),
4398            parameters: vec![],
4399        })
4400    }
4401
4402    fn peer_fetch(id: u64, label: &str) -> ControlMessage {
4403        ControlMessage::Fetch(Fetch {
4404            request_id: v(id),
4405            track_namespace: ns(label),
4406            track_name: b"video".to_vec(),
4407            parameters: vec![],
4408        })
4409    }
4410
4411    fn peer_subscribe_namespace(id: u64, label: &str) -> ControlMessage {
4412        ControlMessage::SubscribeNamespace(SubscribeNamespace {
4413            request_id: v(id),
4414            namespace_prefix: ns(label),
4415            parameters: vec![],
4416        })
4417    }
4418
4419    fn peer_subscribe_tracks(id: u64, label: &str) -> ControlMessage {
4420        ControlMessage::SubscribeTracks(SubscribeTracks {
4421            request_id: v(id),
4422            namespace_prefix: ns(label),
4423            parameters: vec![],
4424        })
4425    }
4426
4427    fn peer_publish_namespace(id: u64, label: &str) -> ControlMessage {
4428        ControlMessage::PublishNamespace(PublishNamespace {
4429            request_id: v(id),
4430            track_namespace: ns(label),
4431            parameters: vec![],
4432        })
4433    }
4434
4435    fn peer_publish(id: u64, label: &str) -> ControlMessage {
4436        ControlMessage::Publish(Publish {
4437            request_id: v(id),
4438            track_namespace: ns(label),
4439            track_name: b"video".to_vec(),
4440            track_alias: v(id + 100),
4441            parameters: vec![],
4442            track_properties: vec![],
4443        })
4444    }
4445
4446    fn active(role: Role) -> Endpoint {
4447        let mut ep = Endpoint::new(role);
4448        ep.connect().unwrap();
4449        ep.receive_setup(&Setup { options: vec![] }).unwrap();
4450        assert_eq!(ep.session_state(), SessionState::Active);
4451        ep
4452    }
4453
4454    fn update(id: u64) -> RequestUpdate {
4455        RequestUpdate { request_id: v(id), parameters: vec![] }
4456    }
4457
4458    /// A session-fatal error must leave the endpoint unable to carry on: the
4459    /// state machine is Closed and every new request is refused. Asserting the
4460    /// error alone would let a caller ignore it and keep the session running,
4461    /// which is the behaviour draft-21 Section 9.5 forbids.
4462    fn assert_session_failed(ep: &mut Endpoint, err: EndpointError) {
4463        assert_eq!(
4464            err.session_error_code(),
4465            Some(SessionErrorCode::ProtocolViolation),
4466            "{err} should be fatal to the session"
4467        );
4468        assert_eq!(ep.session_state(), SessionState::Closed);
4469        assert!(matches!(
4470            ep.subscribe(ns("a"), b"b".to_vec(), vec![]),
4471            Err(EndpointError::NotActive)
4472        ));
4473    }
4474
4475    /// The other answer, and the one this file gives to a rule draft-21
4476    /// states no consequence for: the message is refused and the session runs
4477    /// on.
4478    ///
4479    /// The three request-stream messages below take this helper rather than
4480    /// [`Self::assert_session_failed`]. Section 6.3's opener sentence does not
4481    /// reach a message arriving on the control stream — it is about what a
4482    /// bidirectional stream may *begin* with — and no sentence in draft-21
4483    /// closes a session over a message being in the wrong place. Table 5's
4484    /// Stream column says where each message is sent and attaches no
4485    /// consequence to a peer that sends one elsewhere.
4486    ///
4487    /// REQUEST_UPDATE is the one exception and takes the helper above: Section
4488    /// 9.5 names two cases and closes over everything else, so a
4489    /// REQUEST_UPDATE on the control stream really is a close the draft asks
4490    /// for.
4491    ///
4492    /// Recovery is not an aspiration here. A control message carries its own
4493    /// length, so the next boundary on the stream is known however this one
4494    /// was refused, and the endpoint that follows really can carry on — which
4495    /// is what the third assertion checks rather than assumes.
4496    fn assert_refused_without_closing(ep: &mut Endpoint, err: &EndpointError) {
4497        assert_eq!(
4498            err.session_error_code(),
4499            None,
4500            "no sentence in draft-21 answers {err} with a close"
4501        );
4502        assert_eq!(ep.session_state(), SessionState::Active);
4503        ep.subscribe(ns("a"), b"b".to_vec(), vec![])
4504            .expect("the session survives a message it could not place");
4505    }
4506
4507    /// Draft-21 Table 5 gives REQUEST_UPDATE the Stream value "Request", and
4508    /// Section 9.5 has it sent "on the same bidi stream as the request".
4509    ///
4510    /// Before the placement was corrected the control stream accepted it and
4511    /// the request stream refused it. Routing the request-stream case back
4512    /// through the catch-all arm produces, at the first call below:
4513    ///
4514    /// ```text
4515    /// called `Result::unwrap()` on an `Err` value: ResponseOnControlStream
4516    /// ```
4517    ///
4518    /// and leaving the control-stream arm in place produces, at the assertion
4519    /// after it:
4520    ///
4521    /// ```text
4522    /// assertion failed: matches!(err, EndpointError::RequestUpdateOnControlStream)
4523    /// ```
4524    #[test]
4525    fn a_request_update_belongs_on_its_request_stream_and_not_the_control_stream() {
4526        let mut ep = active(Role::Client);
4527        // A PUBLISH this endpoint made and the peer accepted. Section 9.5
4528        // allows an update on exactly that among the requests made here, so
4529        // this drives the routing question without also being a violation.
4530        let (id, _) = ep.publish(ns("live"), b"video".to_vec(), v(7), vec![], vec![]).unwrap();
4531        ep.receive_request_ok(id, &RequestOk { parameters: vec![], track_properties: vec![] })
4532            .unwrap();
4533
4534        // The request stream is where it belongs, and the request-stream
4535        // dispatcher is the route that has to accept it.
4536        ep.receive_response_on_stream(id, ControlMessage::RequestUpdate(update(id.into_inner())))
4537            .unwrap();
4538
4539        // The control stream is not, and the draft answers that with a close.
4540        let err =
4541            ep.receive_message(ControlMessage::RequestUpdate(update(id.into_inner()))).unwrap_err();
4542        assert!(matches!(err, EndpointError::RequestUpdateOnControlStream), "{err}");
4543        assert_session_failed(&mut ep, err);
4544    }
4545
4546    /// The three messages draft-21 Table 5 places on a request stream that are
4547    /// not responses: NAMESPACE (0x8), NAMESPACE_DONE (0xE) and PUBLISH_SKIPPED
4548    /// (0xF).
4549    ///
4550    /// Table 5's Stream column reads "Request" for all three, the same value it
4551    /// gives REQUEST_UPDATE. Only SETUP is "Control" on its own; GOAWAY is
4552    /// "Control, Request". The placement had all three exactly inverted — the
4553    /// control stream accepted them and returned `Ok`, and the request stream
4554    /// fell through to the catch-all and refused them — so a conforming peer
4555    /// sending NAMESPACE on the SUBSCRIBE_NAMESPACE stream that asked for it
4556    /// had its announcement dropped.
4557    ///
4558    /// Restoring the control-stream arms (`Namespace(ref m) =>
4559    /// self.receive_namespace(m)` and its two siblings) fails this test at the
4560    /// second half with:
4561    ///
4562    /// ```text
4563    /// NAMESPACE must be refused on the control stream: Ok(())
4564    /// ```
4565    ///
4566    /// and removing the request-stream arms fails it at the first half with:
4567    ///
4568    /// ```text
4569    /// NAMESPACE belongs on a request stream: ResponseOnControlStream
4570    /// ```
4571    #[test]
4572    fn namespace_and_publish_skipped_belong_on_a_request_stream() {
4573        /// A message name paired with a way to build a fresh one, since each
4574        /// case needs two copies and `ControlMessage` is consumed by both
4575        /// dispatchers.
4576        type Case = (&'static str, fn() -> ControlMessage);
4577
4578        let cases: [Case; 3] = [
4579            ("NAMESPACE", || {
4580                ControlMessage::Namespace(message::Namespace { namespace_suffix: ns("live") })
4581            }),
4582            ("NAMESPACE_DONE", || {
4583                ControlMessage::NamespaceDone(message::NamespaceDone {
4584                    namespace_suffix: ns("live"),
4585                })
4586            }),
4587            ("PUBLISH_SKIPPED", || {
4588                ControlMessage::PublishSkipped(PublishSkipped {
4589                    namespace_suffix: ns("live"),
4590                    track_name: b"video".to_vec(),
4591                })
4592            }),
4593        ];
4594
4595        for (name, build) in cases {
4596            let mut ep = active(Role::Client);
4597            let id = ep.subscribe_namespace(ns("live"), vec![]).unwrap().0;
4598
4599            // Sections 9.15 and 9.18 make REQUEST_OK or REQUEST_ERROR the
4600            // first message on this stream, so the answer comes before the
4601            // messages that follow it. Sending the NAMESPACE first is a
4602            // different rule's violation and would answer this test's question
4603            // with that rule's error.
4604            ep.receive_response_on_stream(
4605                id,
4606                ControlMessage::RequestOk(RequestOk {
4607                    parameters: vec![],
4608                    track_properties: vec![],
4609                }),
4610            )
4611            .expect("REQUEST_OK answers the namespace subscription");
4612
4613            // Where Table 5 puts it.
4614            ep.receive_response_on_stream(id, build())
4615                .unwrap_or_else(|e| panic!("{name} belongs on a request stream: {e:?}"));
4616
4617            // Where it does not. Refused, and the session left running.
4618            let err = match ep.receive_message(build()) {
4619                Err(e) => e,
4620                Ok(()) => panic!("{name} must be refused on the control stream: Ok(())"),
4621            };
4622            assert!(
4623                matches!(err, EndpointError::RequestMessageOnControlStream(m) if m == name),
4624                "{name} on the control stream gave {err}"
4625            );
4626            assert_refused_without_closing(&mut ep, &err);
4627        }
4628    }
4629
4630    /// Draft-21 Section 9.5: "An endpoint that receives a REQUEST_UPDATE
4631    /// other than in the two cases above MUST close the session with a
4632    /// PROTOCOL_VIOLATION." Section 9.13 names TRACK_STATUS as one such case:
4633    /// "the subscriber cannot send REQUEST_UPDATE."
4634    ///
4635    /// A handler that consulted only `subscriptions`, answering both of these
4636    /// with a recoverable per-request error that leaves the session running,
4637    /// gives at the first assertion below:
4638    ///
4639    /// ```text
4640    /// unknown request ID: 40
4641    /// ```
4642    #[test]
4643    fn a_request_update_naming_a_non_updatable_request_closes_the_session() {
4644        // An id nothing was ever issued under.
4645        let mut ep = active(Role::Client);
4646        let err = ep.receive_request_update(v(40), &update(40)).unwrap_err();
4647        assert!(matches!(err, EndpointError::UnexpectedRequestUpdate(40)), "{err}");
4648        assert_session_failed(&mut ep, err);
4649
4650        // TRACK_STATUS, which the draft rules out by name.
4651        let mut ep = active(Role::Client);
4652        let (id, _) = ep.track_status(ns("live"), b"video".to_vec(), vec![]).unwrap();
4653        let err = ep.receive_request_update(id, &update(id.into_inner())).unwrap_err();
4654        assert!(matches!(err, EndpointError::UnexpectedRequestUpdate(_)), "{err}");
4655        assert_session_failed(&mut ep, err);
4656    }
4657
4658    /// Draft-21 Section 9.5's first case reaches every request kind: "The
4659    /// sender of a request (SUBSCRIBE, PUBLISH, FETCH, PUBLISH_NAMESPACE,
4660    /// SUBSCRIBE_NAMESPACE, SUBSCRIBE_TRACKS) can later send a REQUEST_UPDATE
4661    /// on the same bidi stream as the request to modify it."
4662    ///
4663    /// # What it catches
4664    ///
4665    /// Removing the five-map lookup, so that only a SUBSCRIBE resolves:
4666    ///
4667    /// ```text
4668    /// request 3 should be updatable: REQUEST_UPDATE for request 3, which is
4669    /// not an updatable outstanding request
4670    /// ```
4671    ///
4672    /// That cut is not evidence for this gate on its own. It reddens
4673    /// twenty-eight tests across the crate, because every update on a fetch or
4674    /// a namespace request comes through the same lookup — which makes it a
4675    /// poor ablation and a fair measure of how much rests on the line.
4676    #[test]
4677    fn an_update_from_the_requester_resolves_against_every_request_kind() {
4678        let mut ep = active(Role::Client);
4679        let mut ids = Vec::new();
4680        for msg in [
4681            peer_subscribe(1, "a"),
4682            peer_fetch(3, "b"),
4683            peer_subscribe_namespace(5, "c"),
4684            peer_subscribe_tracks(7, "d"),
4685            peer_publish_namespace(9, "e"),
4686            peer_publish(11, "f"),
4687        ] {
4688            ids.push(ep.receive_request_on_stream(&msg).unwrap());
4689        }
4690        for id in ids {
4691            ep.receive_request_update(id, &update(id.into_inner()))
4692                .unwrap_or_else(|e| panic!("request {} should be updatable: {e}", id.into_inner()));
4693        }
4694        assert_eq!(ep.session_state(), SessionState::Active);
4695    }
4696
4697    /// Section 9.5's second case: "A subscriber can also send REQUEST_UPDATE
4698    /// to modify parameters of a subscription established with PUBLISH."
4699    ///
4700    /// The publication is this endpoint's, so this is the one request of its
4701    /// own that it may be sent an update on, and the one place the sender rule
4702    /// and the request-kind rule disagree.
4703    #[test]
4704    fn an_update_on_a_publish_this_endpoint_made_is_the_second_case() {
4705        let mut ep = active(Role::Client);
4706        let (id, _) = ep.publish(ns("live"), b"video".to_vec(), v(7), vec![], vec![]).unwrap();
4707        ep.receive_request_ok(id, &RequestOk { parameters: vec![], track_properties: vec![] })
4708            .unwrap();
4709        ep.receive_request_update(id, &update(id.into_inner())).unwrap();
4710        assert_eq!(ep.session_state(), SessionState::Active);
4711    }
4712
4713    /// The second case needs the subscription to exist before it can be
4714    /// updated, and the first case does not.
4715    ///
4716    /// Section 3.1: "Once either of these sequences is successful, the
4717    /// subscription moves to the Established state and can be updated by the
4718    /// subscriber using REQUEST_UPDATE." A PUBLISH this endpoint has sent and
4719    /// the peer has not answered is Pending, so a subscriber updating it is
4720    /// updating a subscription that does not exist yet.
4721    ///
4722    /// The first case rests on something else and keeps its own timing:
4723    /// Section 9.5 lets the sender of a request update it "later" with
4724    /// nothing said about the answer, and five of the six kinds it names are
4725    /// not subscriptions at all. Both halves are here so that the asymmetry is
4726    /// the thing asserted rather than a side effect of one of them.
4727    ///
4728    /// # What it catches
4729    ///
4730    /// Asking only whether the request exists, which is all the map on its own
4731    /// can answer: it holds a PUBLISH from the moment it is sent, and the
4732    /// peer's answer is what makes it a subscription.
4733    ///
4734    /// ```text
4735    /// a PUBLISH still waiting for its answer is not an established one: ()
4736    /// ```
4737    ///
4738    /// It reddens this gate and nothing else in the client or the proxy, and
4739    /// the gate's second half stays green under it — which is what that
4740    /// half is for. A cut that tightened both cases would redden it too.
4741    #[test]
4742    fn only_an_established_publication_may_be_updated_by_its_subscriber() {
4743        // Case two, before the answer: the subscription is not established.
4744        let mut ep = active(Role::Client);
4745        let (id, _) = ep.publish(ns("live"), b"video".to_vec(), v(7), vec![], vec![]).unwrap();
4746        let err = ep
4747            .receive_request_update(id, &update(id.into_inner()))
4748            .expect_err("a PUBLISH still waiting for its answer is not an established one");
4749        assert!(matches!(err, EndpointError::UnexpectedRequestUpdate(_)), "{err}");
4750        assert_session_failed(&mut ep, err);
4751
4752        // Case one, before the answer: allowed, and on the same beat.
4753        let mut ep = active(Role::Client);
4754        let peer = ep.receive_request_on_stream(&peer_subscribe(1, "live")).unwrap();
4755        ep.receive_request_update(peer, &update(peer.into_inner()))
4756            .expect("the sender of a request may update it before it is answered");
4757        assert_eq!(ep.session_state(), SessionState::Active);
4758    }
4759
4760    /// Section 9.5: "An endpoint that receives a REQUEST_UPDATE other than in
4761    /// the two cases above MUST close the session with a PROTOCOL_VIOLATION."
4762    ///
4763    /// Neither case reaches a SUBSCRIBE, FETCH or namespace request this
4764    /// endpoint made. Those are modified by the endpoint that made them, which
4765    /// is this one, and an update arriving on one came from the side that has
4766    /// no say over it.
4767    ///
4768    /// # What it catches
4769    ///
4770    /// Reading the Request ID's map and not the direction the update came
4771    /// from, which is what this draft did: all five request kinds this
4772    /// endpoint can make were updatable by whoever asked.
4773    ///
4774    /// ```text
4775    /// an update on a request this endpoint made is neither case: ()
4776    /// ```
4777    ///
4778    /// It reddens this gate and nothing else in the client or the proxy. The
4779    /// `()` is the `Ok` the call returned, which is the whole defect: the
4780    /// update was applied and the session carried on.
4781    #[test]
4782    fn an_update_on_a_request_this_endpoint_made_closes_the_session() {
4783        for which in 0..5 {
4784            let mut ep = active(Role::Client);
4785            let (id, _) = match which {
4786                0 => ep.subscribe(ns("live"), b"video".to_vec(), vec![]).unwrap(),
4787                1 => ep.fetch(ns("live"), b"video".to_vec(), vec![]).unwrap(),
4788                2 => ep.subscribe_namespace(ns("live"), vec![]).unwrap(),
4789                3 => ep.subscribe_tracks(ns("live"), vec![]).unwrap(),
4790                _ => ep.publish_namespace(ns("live"), vec![]).unwrap(),
4791            };
4792            let err = ep
4793                .receive_request_update(id, &update(id.into_inner()))
4794                .expect_err("an update on a request this endpoint made is neither case");
4795            assert!(matches!(err, EndpointError::UnexpectedRequestUpdate(_)), "{which}: {err}");
4796            assert_session_failed(&mut ep, err);
4797        }
4798    }
4799
4800    /// A REQUEST_UPDATE whose own Request ID names a different request than
4801    /// the stream it arrived on was sent on a stream that is not its
4802    /// request's, which is the same violation.
4803    #[test]
4804    fn a_request_update_whose_id_disagrees_with_its_stream_closes_the_session() {
4805        let mut ep = active(Role::Client);
4806        let (a, _) = ep.subscribe(ns("live"), b"video".to_vec(), vec![]).unwrap();
4807        let (b, _) = ep.subscribe(ns("live"), b"audio".to_vec(), vec![]).unwrap();
4808        let err = ep.receive_request_update(a, &update(b.into_inner())).unwrap_err();
4809        assert!(matches!(err, EndpointError::UnexpectedRequestUpdate(_)), "{err}");
4810        assert_session_failed(&mut ep, err);
4811    }
4812
4813    /// Draft-21 Section 9.3: Track Properties "are populated in
4814    /// TRACK_STATUS_OK; they are empty in PUBLISH_OK, REQUEST_UPDATE_OK,
4815    /// SUBSCRIBE_NAMESPACE_OK and PUBLISH_NAMESPACE_OK. If an endpoint
4816    /// receives Track Properties in one of these messages it MUST close the
4817    /// session with a PROTOCOL_VIOLATION."
4818    ///
4819    /// Binding the message as `_msg` in `receive_request_ok`, so its Track
4820    /// Properties go unread, gives:
4821    ///
4822    /// ```text
4823    /// called `Result::unwrap_err()` on an `Ok` value: ()
4824    /// ```
4825    #[test]
4826    fn track_properties_on_a_request_ok_that_is_not_a_track_status_close_the_session() {
4827        let properties = vec![KeyValuePair { key: v(0x04), value: KvpValue::Varint(v(1000)) }];
4828
4829        let mut ep = active(Role::Client);
4830        let (id, _) = ep.subscribe_namespace(ns("live"), vec![]).unwrap();
4831        let err = ep
4832            .receive_request_ok(
4833                id,
4834                &RequestOk { parameters: vec![], track_properties: properties.clone() },
4835            )
4836            .unwrap_err();
4837        assert!(matches!(err, EndpointError::TrackPropertiesOnNonTrackStatus(_)), "{err}");
4838        assert_session_failed(&mut ep, err);
4839
4840        // TRACK_STATUS_OK is the shape that carries them, and still does.
4841        let mut ep = active(Role::Client);
4842        let (id, _) = ep.track_status(ns("live"), b"video".to_vec(), vec![]).unwrap();
4843        ep.receive_request_ok(id, &RequestOk { parameters: vec![], track_properties: properties })
4844            .unwrap();
4845        assert_eq!(ep.session_state(), SessionState::Active);
4846    }
4847
4848    /// Draft-21 Section 9.2: "A GOAWAY MAY also be sent on a request stream
4849    /// to initiate migration of that individual request." Table 5 gives GOAWAY
4850    /// the Stream value "Control, Request".
4851    ///
4852    /// Without the `GoAway` arm in `receive_response_on_stream` the message
4853    /// falls through to the catch-all:
4854    ///
4855    /// ```text
4856    /// called `Result::unwrap()` on an `Err` value: ResponseOnControlStream
4857    /// ```
4858    ///
4859    /// The session must survive it — only the one request is being moved.
4860    #[test]
4861    fn a_goaway_on_a_request_stream_migrates_that_request_and_not_the_session() {
4862        let mut ep = active(Role::Client);
4863        let (id, _) = ep.subscribe(ns("live"), b"video".to_vec(), vec![]).unwrap();
4864
4865        let goaway =
4866            GoAway { new_session_uri: b"https://elsewhere.example/moq".to_vec(), timeout: v(0) };
4867        ep.receive_response_on_stream(id, ControlMessage::GoAway(goaway)).unwrap();
4868
4869        assert_eq!(ep.session_state(), SessionState::Active);
4870        // Per-request migration leaves the session-wide URI unset; the control
4871        // stream is what sets that.
4872        assert_eq!(ep.goaway_uri(), None);
4873        // And a new request is still allowed.
4874        ep.subscribe(ns("live"), b"audio".to_vec(), vec![]).unwrap();
4875    }
4876
4877    /// Draft-21 Section 9.2: "If a server receives a GOAWAY with a non-zero
4878    /// New Session URI Length it MUST close the session with a
4879    /// PROTOCOL_VIOLATION."
4880    ///
4881    /// Without the role check the URI is stored and a server-role endpoint
4882    /// will follow a redirect it should have refused:
4883    ///
4884    /// ```text
4885    /// called `Result::unwrap_err()` on an `Ok` value: ()
4886    /// ```
4887    #[test]
4888    fn a_server_refuses_a_goaway_carrying_a_new_session_uri() {
4889        let mut ep = active(Role::Server);
4890        let goaway =
4891            GoAway { new_session_uri: b"https://elsewhere.example/moq".to_vec(), timeout: v(0) };
4892        let err = ep.receive_goaway(&goaway).unwrap_err();
4893        assert!(matches!(err, EndpointError::GoAwayUriAtServer), "{err}");
4894        assert_eq!(ep.goaway_uri(), None);
4895        assert_session_failed(&mut ep, err);
4896
4897        // An empty URI is the form a server may legitimately receive: it says
4898        // the peer is going away, not where to go.
4899        let mut ep = active(Role::Server);
4900        ep.receive_goaway(&GoAway { new_session_uri: vec![], timeout: v(0) }).unwrap();
4901        assert_eq!(ep.session_state(), SessionState::Draining);
4902
4903        // A client is the side that may be redirected.
4904        let mut ep = active(Role::Client);
4905        ep.receive_goaway(&goaway).unwrap();
4906        assert_eq!(ep.goaway_uri(), Some(&b"https://elsewhere.example/moq"[..]));
4907    }
4908
4909    /// Draft-21 Section 9.11 deleted the `Fetch Type` field, both variant
4910    /// structures and the Fetch Type registry, so a FETCH names a track the way
4911    /// a SUBSCRIBE does and the range travels in a parameter.
4912    ///
4913    /// Restoring draft-19's `+ 1` on the end object — which is what a port that
4914    /// kept the arithmetic would do — fails with:
4915    ///
4916    /// ```text
4917    /// assertion `left == right` failed
4918    ///   left: [10, 0, 0, 6]
4919    ///  right: [10, 0, 0, 5]
4920    /// ```
4921    #[test]
4922    fn a_fetch_carries_its_range_in_an_inclusive_location_filter() {
4923        let mut ep = active(Role::Client);
4924        let range = LocationFilter::range_to(10, 0, 0, 5).unwrap();
4925        let (_, msg) = ep.fetch_range(ns("live"), b"video".to_vec(), &range, vec![]).unwrap();
4926
4927        let ControlMessage::Fetch(fetch) = msg else { panic!("expected a FETCH") };
4928        assert_eq!(fetch.track_namespace, ns("live"));
4929        assert_eq!(fetch.track_name, b"video".to_vec());
4930        let filter = fetch
4931            .parameters
4932            .iter()
4933            .find(|p| p.key.into_inner() == 0x21)
4934            .expect("the range is a LOCATION_FILTER parameter");
4935        let KvpValue::Bytes(bytes) = &filter.value else { panic!("length-prefixed") };
4936        // The last object is 5, not 6: Sections 3.3.1 and 9.11 make the range
4937        // inclusive, and draft-19's "the last Object, plus 1" is gone.
4938        assert_eq!(bytes, &vec![10, 0, 0, 5]);
4939    }
4940
4941    /// Draft-21 Section 3.4, and what replaced the joining fetch: a
4942    /// subscription that carries FILL_PARAMETERS is answered with a
4943    /// unidirectional stream whose FETCH_HEADER carries **the subscription's**
4944    /// Request ID.
4945    ///
4946    /// Without the `note_fill_requested` call in `subscribe`, the stream is
4947    /// unattributable and the open is refused:
4948    ///
4949    /// ```text
4950    /// called `Result::unwrap()` on an `Err` value: UnrequestedFillStream(0)
4951    /// ```
4952    #[test]
4953    fn a_subscription_that_asks_for_a_fill_can_account_for_its_stream() {
4954        let mut ep = active(Role::Client);
4955        let fill_parameter = fill::FillParameters::inherited().parameter().unwrap();
4956        let (id, _) = ep.subscribe(ns("live"), b"video".to_vec(), vec![fill_parameter]).unwrap();
4957
4958        assert!(ep.fill_requested(id));
4959        ep.on_fill_stream_opened(id).unwrap();
4960        ep.on_fill_stream_opened(id).unwrap();
4961        // Section 3.4: "a subscription can have multiple fill fetch streams
4962        // open at once ... opening a new fill fetch stream does not implicitly
4963        // cancel any previously opened fill fetch streams."
4964        assert_eq!(ep.open_fill_streams(id), 2);
4965
4966        ep.on_fill_stream_ended(id);
4967        assert_eq!(ep.open_fill_streams(id), 1);
4968        // Section 9.9 counts every stream the publisher opened, ended or not.
4969        assert_eq!(ep.fill_streams_opened(id), 2);
4970
4971        // Section 3.4.1: neither ending touches the subscription.
4972        ep.on_fill_stream_ended(id);
4973        assert_eq!(ep.active_subscription_count(), 1);
4974    }
4975
4976    /// A subscription that asked for no fill has no fill fetch stream to
4977    /// attribute, and Section 9.20.16 is plain that the parameter is the whole
4978    /// of the request: "Its presence is what requests a fill fetch stream; a
4979    /// subscription with no FILL_PARAMETERS opens none."
4980    #[test]
4981    fn a_stream_for_a_subscription_that_asked_for_no_fill_is_refused() {
4982        let mut ep = active(Role::Client);
4983        let (id, _) = ep.subscribe(ns("live"), b"video".to_vec(), vec![]).unwrap();
4984        assert!(!ep.fill_requested(id));
4985        let err = ep.on_fill_stream_opened(id).unwrap_err();
4986        assert!(matches!(err, EndpointError::UnrequestedFillStream(_)), "{err}");
4987        // Not a session close: draft-21 names no code for it, and Section
4988        // 3.4.1 makes cancelling one stream something that "does not affect
4989        // the subscription".
4990        assert_eq!(err.session_error_code(), None);
4991        assert_eq!(ep.session_state(), SessionState::Active);
4992    }
4993
4994    /// Draft-21 Section 9.10: PUBLISH_STATE_NOTIFY "applies only to
4995    /// subscriptions, and is sent only by the publisher. An endpoint that
4996    /// receives a PUBLISH_STATE_NOTIFY for any other request type, or from the
4997    /// subscriber, MUST close the session with a PROTOCOL_VIOLATION."
4998    ///
4999    /// It is also unilateral, so it must not spend an update credit: routing it
5000    /// through `receive_request_update` instead would leave this endpoint owing
5001    /// an answer the section forbids it to send.
5002    #[test]
5003    fn a_state_notify_is_taken_on_a_subscription_and_refused_anywhere_else() {
5004        let notify = PublishStateNotify { parameters: vec![] };
5005
5006        // On this endpoint's own SUBSCRIBE the peer is the publisher.
5007        let mut ep = active(Role::Client);
5008        let (id, _) = ep.subscribe(ns("live"), b"video".to_vec(), vec![]).unwrap();
5009        ep.receive_publish_state_notify(id, &notify).unwrap();
5010        assert_eq!(ep.session_state(), SessionState::Active);
5011        // Nothing is owed in reply.
5012        assert!(!ep.has_unanswered_update(id));
5013
5014        // On a FETCH there is no subscription for it to be about.
5015        let mut ep = active(Role::Client);
5016        let (id, _) = ep.fetch(ns("live"), b"video".to_vec(), vec![]).unwrap();
5017        let err = ep.receive_publish_state_notify(id, &notify).unwrap_err();
5018        assert!(matches!(err, EndpointError::StateNotifyForNonSubscription(_)), "{err}");
5019        assert_eq!(err.session_error_code(), Some(SessionErrorCode::ProtocolViolation));
5020        assert_eq!(ep.session_state(), SessionState::Closed);
5021
5022        // On a SUBSCRIBE the peer sent, this endpoint is the publisher, so a
5023        // notify arriving on it came from the subscriber.
5024        let mut ep = active(Role::Server);
5025        let id = ep.receive_request_on_stream(&peer_subscribe(0, "live")).unwrap();
5026        let err = ep.receive_publish_state_notify(id, &notify).unwrap_err();
5027        assert!(matches!(err, EndpointError::StateNotifyFromSubscriber(_)), "{err}");
5028        assert_eq!(ep.session_state(), SessionState::Closed);
5029    }
5030}