Skip to main content

moqtap_client/draft20/
endpoint.rs

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