Skip to main content

moqtap_client/draft19/
endpoint.rs

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