Skip to main content

moqtap_client/draft18/
endpoint.rs

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