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 Sections 10.18 and 10.19, of SUBSCRIBE_NAMESPACE and
176    /// SUBSCRIBE_TRACKS alike: "The publisher will respond with REQUEST_OK or
177    /// REQUEST_ERROR on the response half of the stream. If the subscriber
178    /// receives any message other than a REQUEST_OK or a REQUEST_ERROR as the
179    /// first message on the response half of the stream, then it MUST close the
180    /// 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.malformed
1056            .lock()
1057            .unwrap_or_else(|poisoned| poisoned.into_inner())
1058            .note(&namespace, &name, condition);
1059        let mut ids: Vec<u64> = self
1060            .track_bindings
1061            .iter()
1062            .filter(|(&id, binding)| {
1063                binding.namespace == namespace
1064                    && binding.name == name
1065                    && self.binding_is_in_use(id, binding.kind)
1066                    && self.receives_through(id, binding.kind)
1067            })
1068            .map(|(&id, _)| id)
1069            .chain(self.fetch_tracks.iter().filter_map(|(&id, track)| {
1070                (track.namespace == namespace
1071                    && track.name == name
1072                    && self.fetches.get(&id).is_some_and(|sm| sm.state() != FetchState::Done))
1073                .then_some(id)
1074            }))
1075            .collect();
1076        ids.sort_unstable();
1077        ids.into_iter().filter_map(|id| VarInt::from_u64(id).ok()).collect()
1078    }
1079
1080    /// The conflict a SUBSCRIBE_OK's alias has with the tracks already bound.
1081    ///
1082    /// Separate from [`Self::conflicting_track_alias`] because the track a
1083    /// SUBSCRIBE_OK is about is not in the SUBSCRIBE_OK: it is the one this
1084    /// endpoint's own SUBSCRIBE asked for, which is why the request has to be
1085    /// looked up before the alias can be judged.
1086    fn conflicting_alias_for_subscribe_ok(&self, id: u64, alias: u64) -> Option<EndpointError> {
1087        let binding = self.track_bindings.get(&id)?;
1088        self.conflicting_track_alias(id, alias, &binding.namespace, &binding.name)
1089    }
1090
1091    pub fn role(&self) -> Role {
1092        self.role
1093    }
1094
1095    /// Returns the role of the peer, which is the other one.
1096    pub fn peer_role(&self) -> Role {
1097        match self.role {
1098            Role::Client => Role::Server,
1099            Role::Server => Role::Client,
1100        }
1101    }
1102
1103    /// Hold a refused update's ending to the status the draft names, and
1104    /// retire the obligation once that ending is written.
1105    ///
1106    /// Both routes to a PUBLISH_DONE come through here. A subscription this
1107    /// endpoint accepted is ended by a response on the peer's stream; a PUBLISH
1108    /// this endpoint sent is ended on its own, which does not go through the
1109    /// response path at all. The rule is the same either way, so it is stated
1110    /// once and called twice rather than written where each route happened to
1111    /// need it.
1112    fn require_update_failure_status(
1113        &mut self,
1114        id: u64,
1115        status_code: VarInt,
1116    ) -> Result<(), EndpointError> {
1117        if !self.owed_update_failures.contains(&id) {
1118            return Ok(());
1119        }
1120        let required = PublishDoneStatusCode::UpdateFailed as u64;
1121        if status_code.into_inner() != required {
1122            return Err(EndpointError::WrongUpdateFailureStatus { request: id, required });
1123        }
1124        self.owed_update_failures.remove(&id);
1125        Ok(())
1126    }
1127
1128    /// Whether the peer has sent a REQUEST_UPDATE on this request that has not
1129    /// been answered yet.
1130    ///
1131    /// Draft-18 Section 10.9: "A subscriber can also send REQUEST_UPDATE to
1132    /// modify parameters of a subscription established with PUBLISH", and the
1133    /// receiver of one "MUST respond with exactly one REQUEST_OK or
1134    /// REQUEST_ERROR message indicating if the update was successful".
1135    ///
1136    /// Asked by the connection layer, which otherwise writes nothing on a
1137    /// stream this endpoint opened. An update the peer sent on a PUBLISH is
1138    /// the one thing on such a stream that this endpoint has to answer, and
1139    /// this is what tells it apart from a response to its own request.
1140    pub fn has_unanswered_update(&self, request_id: VarInt) -> bool {
1141        self.unanswered_peer_updates.get(&request_id.into_inner()).is_some_and(|&n| n > 0)
1142    }
1143
1144    /// Whether the request `id` names a subscription this endpoint publishes.
1145    ///
1146    /// Draft-18 Section 10.9.1 gives a refused REQUEST_UPDATE three different
1147    /// consequences and picks between them by what was being updated: "When a
1148    /// REQUEST_UPDATE is unsuccessful, the publisher MUST also terminate the
1149    /// subscription by sending a PUBLISH_DONE with error code UPDATE_FAILED.
1150    /// When a REQUEST_UPDATE fails for a FETCH, the publisher MUST reset the
1151    /// FETCH data stream. When a REQUEST_UPDATE fails for a SUBSCRIBE_NAMESPACE
1152    /// or PUBLISH_NAMESPACE, the responder MUST close the bidi stream."
1153    ///
1154    /// Only the first of the three is a message, and only the first is owed
1155    /// here. Recording it for the other two is what would hold their streams
1156    /// open past the close the third sentence requires.
1157    ///
1158    /// Two requests leave this endpoint publishing: a SUBSCRIBE the peer sent,
1159    /// and a PUBLISH this endpoint sent. Either can carry an update from the
1160    /// other side, and either is ended by a PUBLISH_DONE written from here. A
1161    /// peer's PUBLISH is neither, because the peer is the publisher on it, and
1162    /// it is told apart by having arrived rather than been sent.
1163    fn publishes_a_subscription(&self, id: u64) -> bool {
1164        matches!(self.inbound_requests.get(&id), Some(ControlMessage::Subscribe(_)))
1165            || (self.publishes.contains_key(&id) && !self.inbound_requests.contains_key(&id))
1166    }
1167
1168    /// Whether this request's refused update still owes the peer the
1169    /// PUBLISH_DONE that ends it.
1170    ///
1171    /// Asked by the connection layer, which owns the stream that termination
1172    /// has to be written on and therefore has to know not to close it. A
1173    /// REQUEST_ERROR answering the request itself ends the exchange and takes
1174    /// the stream with it; one answering an update does not, and nothing in
1175    /// the message tells the two apart.
1176    pub fn owes_update_failure(&self, request_id: VarInt) -> bool {
1177        self.owed_update_failures.contains(&request_id.into_inner())
1178    }
1179
1180    pub fn session_state(&self) -> SessionState {
1181        self.session.state()
1182    }
1183
1184    pub fn goaway_uri(&self) -> Option<&[u8]> {
1185        self.goaway_uri.as_deref()
1186    }
1187
1188    pub fn active_subscription_count(&self) -> usize {
1189        self.subscriptions.len()
1190    }
1191
1192    pub fn active_fetch_count(&self) -> usize {
1193        self.fetches.len()
1194    }
1195
1196    pub fn active_subscribe_namespace_count(&self) -> usize {
1197        self.subscribe_namespaces.len()
1198    }
1199
1200    pub fn active_subscribe_tracks_count(&self) -> usize {
1201        self.subscribe_tracks.len()
1202    }
1203
1204    pub fn active_publish_namespace_count(&self) -> usize {
1205        self.publish_namespaces.len()
1206    }
1207
1208    pub fn active_track_status_count(&self) -> usize {
1209        self.track_statuses.len()
1210    }
1211
1212    pub fn active_publish_count(&self) -> usize {
1213        self.publishes.len()
1214    }
1215
1216    /// How many Request IDs the peer has spent on this session.
1217    ///
1218    /// Nothing here removes an entry, so this only grows. A responder that
1219    /// wants a ceiling on peer-created state has to impose one itself — see
1220    /// the note on [`receive_request_on_stream`](Self::receive_request_on_stream).
1221    pub fn peer_request_count(&self) -> usize {
1222        self.peer_request_ids.len()
1223    }
1224
1225    // -- Session lifecycle ------------------------------------------
1226
1227    pub fn connect(&mut self) -> Result<(), EndpointError> {
1228        self.session.on_connect()?;
1229        Ok(())
1230    }
1231
1232    pub fn close(&mut self) -> Result<(), EndpointError> {
1233        self.session.on_close()?;
1234        Ok(())
1235    }
1236
1237    // -- Unified SETUP ----------------------------------------------
1238
1239    /// Generate a SETUP message. Both client and server use the same message
1240    /// type; only the role (and the order of send/receive) distinguishes them.
1241    pub fn send_setup(
1242        &mut self,
1243        options: Vec<KeyValuePair>,
1244    ) -> Result<ControlMessage, EndpointError> {
1245        let msg = Setup { options };
1246        setup::validate_setup(&msg, self.role)?;
1247        Ok(ControlMessage::Setup(msg))
1248    }
1249
1250    /// Process an incoming SETUP message. Transitions the session to Active.
1251    pub fn receive_setup(&mut self, msg: &Setup) -> Result<(), EndpointError> {
1252        setup::validate_setup(msg, self.peer_role())?;
1253        self.session.on_setup_complete()?;
1254        Ok(())
1255    }
1256
1257    // -- GoAway -----------------------------------------------------
1258
1259    pub fn receive_goaway(&mut self, msg: &GoAway) -> Result<(), EndpointError> {
1260        // Draft-18 Section 10.4: "If a server receives a GOAWAY with a non-zero
1261        // New Session URI Length it MUST close the session with a
1262        // PROTOCOL_VIOLATION." Migration is something a server offers a client,
1263        // never the other way round, so the URI is refused here rather than
1264        // stored in `goaway_uri` and later followed — an application reading it
1265        // back would reconnect to somewhere a client chose.
1266        if self.role == Role::Server && !msg.new_session_uri.is_empty() {
1267            return Err(self.fail_session(EndpointError::GoAwayUriAtServer));
1268        }
1269        // Draft-18 Section 10.4: "The endpoint MUST close the session with a
1270        // PROTOCOL_VIOLATION (Section 3.5) if it receives more than one GOAWAY on the
1271        // control stream or on a single request stream." Draining is reached
1272        // from nowhere else - `on_goaway` is its only entry and this method is
1273        // that method's only caller - so the session state is the record of
1274        // the first GOAWAY having arrived. This is the control-stream half;
1275        // the per-stream half is in `receive_goaway_on_request_stream`.
1276        if self.session.state() == SessionState::Draining {
1277            return Err(self.fail_session(EndpointError::RepeatedGoAway));
1278        }
1279        self.session.on_goaway()?;
1280        self.goaway_uri = Some(msg.new_session_uri.clone());
1281        Ok(())
1282    }
1283
1284    /// Process a GOAWAY that arrived on one request stream rather than on the
1285    /// control stream.
1286    ///
1287    /// Draft-18 Table 5 gives GOAWAY the Stream value "Control, Request", and
1288    /// Section 10.4 says why: "A GOAWAY MAY also be sent on a request stream to
1289    /// initiate migration of that individual request. Upon receiving a GOAWAY on
1290    /// a request stream, the endpoint SHOULD re-issue that specific request on a
1291    /// session at the specified URI (or the current session if no URI is
1292    /// provided), and close the old request stream using the appropriate
1293    /// mechanism (e.g. FIN, stream reset, or PUBLISH_DONE)."
1294    ///
1295    /// The session keeps running — only this one request is being moved — so the
1296    /// session state machine is not touched and nothing drains. Routing this
1297    /// form to [`receive_goaway`](Self::receive_goaway) instead would take the
1298    /// whole session to Draining over something the draft scopes to a single
1299    /// request.
1300    ///
1301    /// The server-side URI rule of the same section still applies here: a server
1302    /// may not be handed a migration URI on a request stream either.
1303    ///
1304    /// Re-issuing the request and closing the old stream are the caller's, since
1305    /// only the caller knows what the request was; this reports that the GOAWAY
1306    /// named a request that exists.
1307    ///
1308    /// # Errors
1309    ///
1310    /// [`EndpointError::RepeatedGoAwayOnRequestStream`] if this request's
1311    /// stream has already carried one. The session is over: this endpoint's own
1312    /// state has moved to Closed and the code the transport should close with
1313    /// is in [`EndpointError::session_error_code`].
1314    pub fn receive_goaway_on_request_stream(
1315        &mut self,
1316        request_id: VarInt,
1317        msg: &GoAway,
1318    ) -> Result<(), EndpointError> {
1319        if self.role == Role::Server && !msg.new_session_uri.is_empty() {
1320            return Err(self.fail_session(EndpointError::GoAwayUriAtServer));
1321        }
1322        let id = request_id.into_inner();
1323        if self.subscriptions.contains_key(&id)
1324            || self.publishes.contains_key(&id)
1325            || self.fetches.contains_key(&id)
1326            || self.subscribe_namespaces.contains_key(&id)
1327            || self.subscribe_tracks.contains_key(&id)
1328            || self.publish_namespaces.contains_key(&id)
1329            || self.track_statuses.contains_key(&id)
1330        {
1331            // Section 10.4 counts per stream, so this is a separate first
1332            // GOAWAY on every request and a repeat only on the one that has
1333            // already carried one. The set is fed only by GOAWAYs accepted
1334            // here, and never pruned: a Request ID is spent once, so an entry
1335            // can never come to describe a different request.
1336            if self.goaway_request_streams.insert(id) {
1337                Ok(())
1338            } else {
1339                Err(self.fail_session(EndpointError::RepeatedGoAwayOnRequestStream(id)))
1340            }
1341        } else {
1342            Err(EndpointError::UnknownRequest(id))
1343        }
1344    }
1345
1346    /// Record that the session is over because the peer broke a rule the draft
1347    /// answers with a session close, and hand the error back unchanged.
1348    ///
1349    /// The state move is what makes the violation stick: every request entry
1350    /// point goes through [`require_active_or_err`](Self::require_active_or_err),
1351    /// so a caller that ignores the returned error still cannot start anything
1352    /// new. The close on the wire is the connection layer's job — see
1353    /// [`EndpointError::session_error_code`] for the code it should use.
1354    fn fail_session(&mut self, err: EndpointError) -> EndpointError {
1355        // `on_close` accepts SetupExchange, Active and Draining. A violation
1356        // seen in Connecting or Closed leaves the state machine alone: there
1357        // is no session to close, and the error itself is still the answer.
1358        //
1359        // SetupExchange is in that set because the Termination section says
1360        // "The Transport Session can be terminated at any point", and the
1361        // Setup exchange is a point. So a violation caught while the setup is
1362        // still in flight does close the session, and the discarded result is
1363        // safe because that is one of the states `on_close` accepts.
1364        let _ = self.session.on_close();
1365        err
1366    }
1367
1368    fn require_active_or_err(&self) -> Result<(), EndpointError> {
1369        match self.session.state() {
1370            SessionState::Active => Ok(()),
1371            SessionState::Draining => Err(EndpointError::Draining),
1372            _ => Err(EndpointError::NotActive),
1373        }
1374    }
1375
1376    // -- Subscribe flow ---------------------------------------------
1377
1378    pub fn subscribe(
1379        &mut self,
1380        track_namespace: TrackNamespace,
1381        track_name: Vec<u8>,
1382        parameters: Vec<KeyValuePair>,
1383    ) -> Result<(VarInt, ControlMessage), EndpointError> {
1384        self.require_active_or_err()?;
1385        let req_id = self.request_ids.allocate()?;
1386
1387        let mut sm = SubscriptionStateMachine::new();
1388        sm.on_subscribe_sent()?;
1389        self.subscriptions.insert(req_id.into_inner(), sm);
1390        // The track is recorded here because this is the only place it is
1391        // known: a SUBSCRIBE_OK names an alias and not the track it is for.
1392        self.track_bindings.insert(
1393            req_id.into_inner(),
1394            TrackBinding {
1395                namespace: track_namespace.clone(),
1396                name: track_name.clone(),
1397                alias: None,
1398                kind: BindingKind::Subscribe,
1399            },
1400        );
1401
1402        let msg = ControlMessage::Subscribe(Subscribe {
1403            request_id: req_id,
1404            track_namespace,
1405            track_name,
1406            parameters,
1407        });
1408        Ok((req_id, msg))
1409    }
1410
1411    /// Process an incoming SUBSCRIBE_OK. Draft-18: no request_id on wire; the
1412    /// caller supplies the `request_id` of the bidi stream on which the
1413    /// response arrived.
1414    pub fn receive_subscribe_ok(
1415        &mut self,
1416        request_id: VarInt,
1417        msg: &SubscribeOk,
1418    ) -> Result<(), EndpointError> {
1419        let id = request_id.into_inner();
1420        if !self.subscriptions.contains_key(&id) {
1421            return Err(EndpointError::UnknownRequest(id));
1422        }
1423        let alias = msg.track_alias.into_inner();
1424        // Judged before the transition, so that the subscription this message
1425        // is about is not yet Established and cannot be found as its own
1426        // conflict, and so that a refused SUBSCRIBE_OK leaves no alias behind.
1427        if let Some(conflict) = self.conflicting_alias_for_subscribe_ok(id, alias) {
1428            return Err(self.fail_session(conflict));
1429        }
1430        let sm = self.subscriptions.get_mut(&id).expect("checked above");
1431        sm.on_subscribe_ok()?;
1432        if let Some(binding) = self.track_bindings.get_mut(&id) {
1433            binding.alias = Some(alias);
1434        }
1435        Ok(())
1436    }
1437
1438    /// Process a REQUEST_UPDATE that arrived on the bidi request stream
1439    /// identified by `request_id`.
1440    ///
1441    /// The stream names the request being modified, not the message. Draft-18
1442    /// Section 10.9: "The sender of a request (SUBSCRIBE, PUBLISH, FETCH,
1443    /// PUBLISH_NAMESPACE, SUBSCRIBE_NAMESPACE, SUBSCRIBE_TRACKS) can later send
1444    /// a REQUEST_UPDATE on the same bidi stream as the request to modify it."
1445    /// Section 10.1 lists REQUEST_UPDATE among the messages that consume a
1446    /// Request ID of their own, so the id in the message body is the update's,
1447    /// not the target's — reading it as the target means every conforming
1448    /// peer's update names a request that by construction does not exist.
1449    ///
1450    /// Every request kind in that list is accepted, not only subscriptions: the
1451    /// draft names six senders and only one of them is a SUBSCRIBE.
1452    /// `SubscriptionStateMachine` is the only one of the machines with an update
1453    /// edge, so a subscription walks it and the rest are acknowledged without a
1454    /// state move.
1455    ///
1456    /// One parameter is read rather than acknowledged. Section 10.2.14 puts
1457    /// TRACK_NAMESPACE_PREFIX on an update for a namespace subscription, and it
1458    /// is the only thing an update on any draft can change about a request that
1459    /// this endpoint has to judge before answering.
1460    pub fn receive_request_update(
1461        &mut self,
1462        request_id: VarInt,
1463        msg: &RequestUpdate,
1464    ) -> Result<(), EndpointError> {
1465        let id = request_id.into_inner();
1466        // Section 10.14 takes the update away from this request kind outright:
1467        // "the subscriber cannot send REQUEST_UPDATE". Refused before the set below is
1468        // consulted, because an identifier naming a track status does name a
1469        // request, which is exactly what would otherwise let it through.
1470        if self.track_statuses.contains_key(&id) {
1471            return Err(EndpointError::NotASubscription(id));
1472        }
1473        let updatable = if let Some(sm) = self.subscriptions.get_mut(&id) {
1474            sm.on_subscribe_update()?;
1475            true
1476        } else {
1477            self.publishes.contains_key(&id)
1478                || self.fetches.contains_key(&id)
1479                || self.subscribe_namespaces.contains_key(&id)
1480                // Draft-18 added SUBSCRIBE_TRACKS and put it in Section 10.9's
1481                // list of the requests an update may modify. It gets its own map
1482                // for the reason the outbound path gives it one, so it needs its
1483                // own probe here.
1484                || self.subscribe_tracks.contains_key(&id)
1485                || self.publish_namespaces.contains_key(&id)
1486        };
1487        if !updatable {
1488            return Err(EndpointError::UnknownRequest(id));
1489        }
1490        *self.unanswered_peer_updates.entry(id).or_insert(0) += 1;
1491
1492        // Section 10.9.2 gives an update one thing to move that no draft before
1493        // this one lets a request change: "A subscriber can update the Track
1494        // Namespace Prefix of an established SUBSCRIBE_NAMESPACE or
1495        // SUBSCRIBE_TRACKS by including the TRACK_NAMESPACE_PREFIX parameter
1496        // (Section 10.2.14) in a REQUEST_UPDATE."
1497        //
1498        // The two kinds keep their own ground -- "The overlap restriction
1499        // applies independently per type" -- so which set the new prefix is
1500        // weighed against is decided by what the peer opened the stream with.
1501        //
1502        // A request of any other kind has no prefix for the parameter to move,
1503        // and nothing in this draft says what to do with the parameter when it
1504        // turns up on one, so it is left alone rather than guessed at.
1505        if let Some(prefix) = updated_prefix(&msg.parameters) {
1506            let namespace = matches!(
1507                self.inbound_requests.get(&id),
1508                Some(ControlMessage::SubscribeNamespace(_))
1509            );
1510            let tracks =
1511                matches!(self.inbound_requests.get(&id), Some(ControlMessage::SubscribeTracks(_)));
1512            if namespace || tracks {
1513                let collides = if tracks {
1514                    self.peer_tracks_overlap(&prefix, Some(id))
1515                } else {
1516                    self.peer_namespace_overlap(&prefix, Some(id))
1517                };
1518                match collides {
1519                    Some(established) => {
1520                        self.overlapping_prefix_updates.insert(id, established);
1521                    }
1522                    // A later update clears an earlier one's verdict along with
1523                    // its prefix, which is what a receiver "applying only the
1524                    // cumulative result" is entitled to do.
1525                    None => {
1526                        self.overlapping_prefix_updates.remove(&id);
1527                    }
1528                }
1529                self.updated_namespace_prefixes.insert(id, prefix);
1530            }
1531        }
1532        Ok(())
1533    }
1534
1535    pub fn receive_publish_done(
1536        &mut self,
1537        request_id: VarInt,
1538        _msg: &PublishDone,
1539    ) -> Result<(), EndpointError> {
1540        let id = request_id.into_inner();
1541        let sm = self.subscriptions.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
1542        sm.on_publish_done()?;
1543        Ok(())
1544    }
1545
1546    // -- Fetch flow -------------------------------------------------
1547
1548    /// `parameters` are the request's own, as they are on every other request
1549    /// this endpoint makes. FETCH carried an empty list on all five of these
1550    /// drafts while SUBSCRIBE, TRACK_STATUS, PUBLISH_NAMESPACE and PUBLISH
1551    /// took the caller's, which made it the only one an application could not
1552    /// attach an authorization token to.
1553    #[allow(clippy::too_many_arguments)]
1554    pub fn fetch(
1555        &mut self,
1556        track_namespace: TrackNamespace,
1557        track_name: Vec<u8>,
1558        start_group: VarInt,
1559        start_object: VarInt,
1560        end_group: VarInt,
1561        end_object: VarInt,
1562        parameters: Vec<KeyValuePair>,
1563    ) -> Result<(VarInt, ControlMessage), EndpointError> {
1564        self.require_active_or_err()?;
1565        let req_id = self.request_ids.allocate()?;
1566
1567        let mut sm = FetchStateMachine::new();
1568        sm.on_fetch_sent()?;
1569        self.fetches.insert(req_id.into_inner(), sm);
1570        // Before the two of them are moved into the message below: a fetch
1571        // that has left no track behind is one no withdrawal can name.
1572        self.fetch_tracks.insert(
1573            req_id.into_inner(),
1574            FetchTrack { namespace: track_namespace.clone(), name: track_name.clone() },
1575        );
1576
1577        let msg = ControlMessage::Fetch(Fetch {
1578            request_id: req_id,
1579            fetch_type: FetchType::Standalone,
1580            fetch_payload: FetchPayload::Standalone {
1581                track_namespace,
1582                track_name,
1583                start_group,
1584                start_object,
1585                end_group,
1586                end_object,
1587            },
1588            parameters,
1589        });
1590        Ok((req_id, msg))
1591    }
1592
1593    /// Send a Relative Joining Fetch (Fetch Type 0x2). Allocates a request ID.
1594    ///
1595    /// `joining_start` is an offset rather than a group number: draft-18
1596    /// Section 10.12.2.1 has the publisher set the Start Location to
1597    /// "{Joining Location.Group - Joining Start, 0}". To name the starting group
1598    /// outright, use [`absolute_joining_fetch`](Self::absolute_joining_fetch).
1599    ///
1600    /// `parameters` are the request's own, as they are on every other request
1601    /// this endpoint makes.
1602    pub fn joining_fetch(
1603        &mut self,
1604        joining_request_id: VarInt,
1605        joining_start: VarInt,
1606        parameters: Vec<KeyValuePair>,
1607    ) -> Result<(VarInt, ControlMessage), EndpointError> {
1608        self.joining_fetch_of_type(
1609            FetchType::RelativeJoining,
1610            joining_request_id,
1611            joining_start,
1612            parameters,
1613        )
1614    }
1615
1616    /// Send an Absolute Joining Fetch (Fetch Type 0x3). Allocates a request ID.
1617    ///
1618    /// Draft-18 Section 10.12.2.1: "For an Absolute Joining Fetch, the
1619    /// publisher sets the Start Location to {Joining Start, 0}." So
1620    /// `joining_start` is the group to begin at rather than a count back from
1621    /// one, and Section 10.12.2 leaves the choice with the subscriber: "The
1622    /// subscriber can set the Start Location to an absolute Location or a
1623    /// Location relative to the Largest group." Only the relative form could be sent
1624    /// before, so an application that knew which group it wanted had to
1625    /// express it as an offset from a largest group it may never have been
1626    /// told.
1627    pub fn absolute_joining_fetch(
1628        &mut self,
1629        joining_request_id: VarInt,
1630        joining_start: VarInt,
1631        parameters: Vec<KeyValuePair>,
1632    ) -> Result<(VarInt, ControlMessage), EndpointError> {
1633        self.joining_fetch_of_type(
1634            FetchType::AbsoluteJoining,
1635            joining_request_id,
1636            joining_start,
1637            parameters,
1638        )
1639    }
1640
1641    /// The two above, which differ in the Fetch Type and in what the publisher
1642    /// then reads Joining Start as. Nothing else about the message changes.
1643    fn joining_fetch_of_type(
1644        &mut self,
1645        fetch_type: FetchType,
1646        joining_request_id: VarInt,
1647        joining_start: VarInt,
1648        parameters: Vec<KeyValuePair>,
1649    ) -> Result<(VarInt, ControlMessage), EndpointError> {
1650        self.require_active_or_err()?;
1651        let req_id = self.request_ids.allocate()?;
1652
1653        let mut sm = FetchStateMachine::new();
1654        sm.on_fetch_sent()?;
1655        self.fetches.insert(req_id.into_inner(), sm);
1656        // Resolved through the join, once, here. A Joining Fetch names no
1657        // track; the publisher takes the Track Namespace and Track Name from
1658        // the subscription it names, so that is the track. A Request ID this
1659        // session holds no track for leaves the fetch out of the record
1660        // entirely rather than putting a guess in it.
1661        if let Some(binding) = self.track_bindings.get(&joining_request_id.into_inner()) {
1662            let track =
1663                FetchTrack { namespace: binding.namespace.clone(), name: binding.name.clone() };
1664            self.fetch_tracks.insert(req_id.into_inner(), track);
1665        }
1666
1667        let msg = ControlMessage::Fetch(Fetch {
1668            request_id: req_id,
1669            fetch_type,
1670            fetch_payload: FetchPayload::Joining { joining_request_id, joining_start },
1671            parameters,
1672        });
1673        Ok((req_id, msg))
1674    }
1675
1676    pub fn receive_fetch_ok(
1677        &mut self,
1678        request_id: VarInt,
1679        _msg: &message::FetchOk,
1680    ) -> Result<(), EndpointError> {
1681        let id = request_id.into_inner();
1682        let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
1683        sm.on_fetch_ok()?;
1684        Ok(())
1685    }
1686
1687    pub fn on_fetch_stream_fin(&mut self, request_id: VarInt) -> Result<(), EndpointError> {
1688        let id = request_id.into_inner();
1689        let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
1690        sm.on_stream_fin()?;
1691        Ok(())
1692    }
1693
1694    pub fn on_fetch_stream_reset(&mut self, request_id: VarInt) -> Result<(), EndpointError> {
1695        let id = request_id.into_inner();
1696        let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
1697        sm.on_stream_reset()?;
1698        Ok(())
1699    }
1700
1701    // -- Subscribe Namespace flow -----------------------------------
1702
1703    pub fn subscribe_namespace(
1704        &mut self,
1705        namespace_prefix: TrackNamespace,
1706        parameters: Vec<KeyValuePair>,
1707    ) -> Result<(VarInt, ControlMessage), EndpointError> {
1708        self.require_active_or_err()?;
1709        let req_id = self.request_ids.allocate()?;
1710
1711        let mut sm = SubscribeNamespaceStateMachine::new();
1712        sm.on_subscribe_namespace_sent()?;
1713        self.subscribe_namespaces.insert(req_id.into_inner(), sm);
1714
1715        let msg = ControlMessage::SubscribeNamespace(SubscribeNamespace {
1716            request_id: req_id,
1717            namespace_prefix,
1718            parameters,
1719        });
1720        Ok((req_id, msg))
1721    }
1722
1723    // -- Subscribe Tracks flow (new in draft-18) --------------------
1724
1725    pub fn subscribe_tracks(
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        // Reuse the SubscribeNamespace state machine — the lifecycle is the
1734        // same (request → ok/error → done) and adding a parallel state
1735        // machine purely to disambiguate would be churn.
1736        let mut sm = SubscribeNamespaceStateMachine::new();
1737        sm.on_subscribe_namespace_sent()?;
1738        self.subscribe_tracks.insert(req_id.into_inner(), sm);
1739
1740        let msg = ControlMessage::SubscribeTracks(SubscribeTracks {
1741            request_id: req_id,
1742            namespace_prefix,
1743            parameters,
1744        });
1745        Ok((req_id, msg))
1746    }
1747
1748    // -- Publish Namespace flow -------------------------------------
1749
1750    pub fn publish_namespace(
1751        &mut self,
1752        track_namespace: TrackNamespace,
1753        parameters: Vec<KeyValuePair>,
1754    ) -> Result<(VarInt, ControlMessage), EndpointError> {
1755        self.require_active_or_err()?;
1756        let req_id = self.request_ids.allocate()?;
1757
1758        let mut sm = PublishNamespaceStateMachine::new();
1759        sm.on_publish_namespace_sent()?;
1760        self.publish_namespaces.insert(req_id.into_inner(), sm);
1761
1762        let msg = ControlMessage::PublishNamespace(PublishNamespace {
1763            request_id: req_id,
1764            track_namespace,
1765            parameters,
1766        });
1767        Ok((req_id, msg))
1768    }
1769
1770    // -- Track Status flow ------------------------------------------
1771
1772    pub fn track_status(
1773        &mut self,
1774        track_namespace: TrackNamespace,
1775        track_name: Vec<u8>,
1776        parameters: Vec<KeyValuePair>,
1777    ) -> Result<(VarInt, ControlMessage), EndpointError> {
1778        self.require_active_or_err()?;
1779        let req_id = self.request_ids.allocate()?;
1780        let mut sm = TrackStatusStateMachine::new();
1781        sm.on_track_status_sent()?;
1782        self.track_statuses.insert(req_id.into_inner(), sm);
1783
1784        let msg = ControlMessage::TrackStatus(message::TrackStatus {
1785            request_id: req_id,
1786            track_namespace,
1787            track_name,
1788            parameters,
1789        });
1790        Ok((req_id, msg))
1791    }
1792
1793    // -- Publish flow (publisher side) ------------------------------
1794
1795    pub fn publish(
1796        &mut self,
1797        track_namespace: TrackNamespace,
1798        track_name: Vec<u8>,
1799        track_alias: VarInt,
1800        parameters: Vec<KeyValuePair>,
1801        track_properties: Vec<KeyValuePair>,
1802    ) -> Result<(VarInt, ControlMessage), EndpointError> {
1803        self.require_active_or_err()?;
1804        // Section 11.1: "The same Track Alias MUST NOT be used by a publisher to refer to
1805        // two different Tracks simultaneously in the same session." Refused before the
1806        // Request ID is allocated, so a refusal spends nothing.
1807        let alias = track_alias.into_inner();
1808        if let Some(refusal) = self.alias_held_elsewhere(alias, &track_namespace, &track_name) {
1809            return Err(refusal);
1810        }
1811        let req_id = self.request_ids.allocate()?;
1812        let mut sm = PublishStateMachine::new();
1813        sm.on_publish_sent()?;
1814        self.publishes.insert(req_id.into_inner(), sm);
1815        // The alias and the track travel together in a PUBLISH, so the binding
1816        // is complete the moment the message is built.
1817        self.track_bindings.insert(
1818            req_id.into_inner(),
1819            TrackBinding {
1820                namespace: track_namespace.clone(),
1821                name: track_name.clone(),
1822                alias: Some(alias),
1823                kind: BindingKind::Publish,
1824            },
1825        );
1826
1827        let msg = ControlMessage::Publish(Publish {
1828            request_id: req_id,
1829            track_namespace,
1830            track_name,
1831            track_alias,
1832            parameters,
1833            track_properties,
1834        });
1835        Ok((req_id, msg))
1836    }
1837
1838    pub fn send_publish_done(
1839        &mut self,
1840        request_id: VarInt,
1841        status_code: VarInt,
1842        stream_count: VarInt,
1843        reason_phrase: Vec<u8>,
1844    ) -> Result<ControlMessage, EndpointError> {
1845        let id = request_id.into_inner();
1846        // Before the state machine moves, so an ending under the wrong status
1847        // leaves the publication where it was. A PUBLISH this endpoint sent is
1848        // ended here rather than through the response path, and a refused
1849        // update on it owes the same ending as one on a peer's SUBSCRIBE.
1850        self.require_update_failure_status(id, status_code)?;
1851        let sm = self.publishes.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
1852        sm.on_publish_done_sent()?;
1853        Ok(ControlMessage::PublishDone(PublishDone { status_code, stream_count, reason_phrase }))
1854    }
1855
1856    // -- Consolidated responses (per-bidi-stream routing) -----------
1857
1858    /// Process an incoming REQUEST_OK on the bidi stream identified by
1859    /// `request_id`. Draft-18: PUBLISH_OK is now a REQUEST_OK alias, so this
1860    /// handler also resolves outstanding PUBLISH requests.
1861    pub fn receive_request_ok(
1862        &mut self,
1863        request_id: VarInt,
1864        msg: &RequestOk,
1865    ) -> Result<(), EndpointError> {
1866        let id = request_id.into_inner();
1867        // Draft-18 Section 10.5: Track Properties "are populated in
1868        // TRACK_STATUS_OK; they are empty in PUBLISH_OK, REQUEST_UPDATE_OK,
1869        // SUBSCRIBE_NAMESPACE_OK and PUBLISH_NAMESPACE_OK. If an endpoint
1870        // receives Track Properties in one of these messages it MUST close the
1871        // session with a PROTOCOL_VIOLATION." The codec cannot make that check:
1872        // REQUEST_OK is one wire form and only the request stream says which of
1873        // the five shapes it is. This is the layer that knows, because finding
1874        // the request id in one of the maps below is what names the shape.
1875        if !msg.track_properties.is_empty() && !self.track_statuses.contains_key(&id) {
1876            return Err(self.fail_session(EndpointError::TrackPropertiesOnNonTrackStatus(id)));
1877        }
1878        if let Some(sm) = self.publishes.get_mut(&id) {
1879            sm.on_publish_ok()?;
1880            return Ok(());
1881        }
1882        if let Some(sm) = self.subscribe_namespaces.get_mut(&id) {
1883            sm.on_subscribe_namespace_ok()?;
1884            return Ok(());
1885        }
1886        if let Some(sm) = self.subscribe_tracks.get_mut(&id) {
1887            sm.on_subscribe_namespace_ok()?;
1888            return Ok(());
1889        }
1890        if let Some(sm) = self.publish_namespaces.get_mut(&id) {
1891            sm.on_publish_namespace_ok()?;
1892            return Ok(());
1893        }
1894        if let Some(sm) = self.track_statuses.get_mut(&id) {
1895            sm.on_track_status_ok()?;
1896            return Ok(());
1897        }
1898        Err(EndpointError::UnknownRequest(id))
1899    }
1900
1901    /// Process an incoming REQUEST_ERROR on the bidi stream identified by
1902    /// `request_id`.
1903    pub fn receive_request_error(
1904        &mut self,
1905        request_id: VarInt,
1906        msg: &RequestError,
1907    ) -> Result<(), EndpointError> {
1908        let id = request_id.into_inner();
1909        if let Some(redirect) = &msg.redirect {
1910            // Draft-18 Section 10.6.1: "If a server receives a Redirect with a
1911            // non-zero Connect URI Length it MUST close the session with a
1912            // PROTOCOL_VIOLATION." Left unchecked, a Redirect sends a server
1913            // chasing a URI a client picked.
1914            if self.role == Role::Server && !redirect.connect_uri.is_empty() {
1915                return Err(self.fail_session(EndpointError::RedirectUriAtServer));
1916            }
1917            // Same section: "Track Name is not meaningful for namespace-scoped
1918            // requests (SUBSCRIBE_NAMESPACE, PUBLISH_NAMESPACE) and MUST be
1919            // empty; an endpoint that receives a non-empty Track Name in a
1920            // Redirect for a namespace-scoped request MUST close the session
1921            // with a PROTOCOL_VIOLATION." Which request this answers is known
1922            // only here, from the map the stream's id is in. Draft-18 names
1923            // those two and not SUBSCRIBE_TRACKS, so SUBSCRIBE_TRACKS is not
1924            // held to the rule on this draft.
1925            let namespace_scoped = self.subscribe_namespaces.contains_key(&id)
1926                || self.publish_namespaces.contains_key(&id);
1927            if namespace_scoped && !redirect.track_name.is_empty() {
1928                return Err(
1929                    self.fail_session(EndpointError::RedirectTrackNameOnNamespaceRequest(id))
1930                );
1931            }
1932        }
1933        if let Some(sm) = self.subscriptions.get_mut(&id) {
1934            sm.on_subscribe_error()?;
1935            return Ok(());
1936        }
1937        if let Some(sm) = self.fetches.get_mut(&id) {
1938            sm.on_fetch_error()?;
1939            return Ok(());
1940        }
1941        if let Some(sm) = self.publishes.get_mut(&id) {
1942            sm.on_publish_error()?;
1943            return Ok(());
1944        }
1945        if let Some(sm) = self.subscribe_namespaces.get_mut(&id) {
1946            sm.on_subscribe_namespace_error()?;
1947            return Ok(());
1948        }
1949        if let Some(sm) = self.subscribe_tracks.get_mut(&id) {
1950            sm.on_subscribe_namespace_error()?;
1951            return Ok(());
1952        }
1953        if let Some(sm) = self.publish_namespaces.get_mut(&id) {
1954            sm.on_publish_namespace_error()?;
1955            return Ok(());
1956        }
1957        if let Some(sm) = self.track_statuses.get_mut(&id) {
1958            sm.on_track_status_error()?;
1959            return Ok(());
1960        }
1961        Err(EndpointError::UnknownRequest(id))
1962    }
1963
1964    /// Record that a request was cancelled at its stream.
1965    ///
1966    /// This draft withdraws a request by terminating the bidirectional stream
1967    /// it was made on rather than by sending a message. Section 3.3.2: "Once
1968    /// a request stream has been opened, the request MAY be cancelled by either
1969    /// endpoint. Senders cancel requests if the response is no longer of
1970    /// interest; Receivers cancel requests if they are unable to or choose not
1971    /// to respond."
1972    ///
1973    /// Both of those reach here. A request the peer opened and one this
1974    /// endpoint opened share a map and cannot collide, because their Request
1975    /// IDs have opposite least significant bits, so one method records a cancel
1976    /// from whichever side performed it.
1977    ///
1978    /// The request moves to its end state, which is what makes the record worth
1979    /// keeping: a response arriving afterwards is refused rather than applied to
1980    /// a request that is over. The Request ID is not released — nothing here
1981    /// reuses one — and the per-request bookkeeping keyed by it is left alone,
1982    /// since a cancelled request's stream carries nothing more.
1983    ///
1984    /// Nothing is written on the wire. The reset that goes with this is
1985    /// [`Connection::cancel_request_stream`], which calls this first and
1986    /// terminates the stream only if it returns `Ok`.
1987    ///
1988    /// # Errors
1989    ///
1990    /// [`EndpointError::UnknownRequest`] when no request of any kind carries
1991    /// that id, and the kind's own `InvalidTransition` when the request has not
1992    /// been written yet.
1993    ///
1994    /// [`Connection::cancel_request_stream`]: crate::draft18::connection::Connection::cancel_request_stream
1995    pub fn cancel_request(&mut self, request_id: VarInt) -> Result<(), EndpointError> {
1996        let id = request_id.into_inner();
1997        if let Some(sm) = self.subscriptions.get_mut(&id) {
1998            sm.on_request_cancelled()?;
1999            return Ok(());
2000        }
2001        if let Some(sm) = self.fetches.get_mut(&id) {
2002            sm.on_request_cancelled()?;
2003            return Ok(());
2004        }
2005        if let Some(sm) = self.publishes.get_mut(&id) {
2006            sm.on_request_cancelled()?;
2007            return Ok(());
2008        }
2009        if let Some(sm) = self.subscribe_namespaces.get_mut(&id) {
2010            sm.on_request_cancelled()?;
2011            return Ok(());
2012        }
2013        if let Some(sm) = self.subscribe_tracks.get_mut(&id) {
2014            sm.on_request_cancelled()?;
2015            return Ok(());
2016        }
2017        if let Some(sm) = self.publish_namespaces.get_mut(&id) {
2018            sm.on_request_cancelled()?;
2019            return Ok(());
2020        }
2021        if let Some(sm) = self.track_statuses.get_mut(&id) {
2022            sm.on_request_cancelled()?;
2023            return Ok(());
2024        }
2025        Err(EndpointError::UnknownRequest(id))
2026    }
2027
2028    // -- PublishBlocked / Namespace announcements -------------------
2029
2030    pub fn receive_namespace(&mut self, _msg: &message::Namespace) -> Result<(), EndpointError> {
2031        Ok(())
2032    }
2033
2034    pub fn receive_namespace_done(
2035        &mut self,
2036        _msg: &message::NamespaceDone,
2037    ) -> Result<(), EndpointError> {
2038        Ok(())
2039    }
2040
2041    pub fn receive_publish_blocked(&mut self, _msg: &PublishBlocked) -> Result<(), EndpointError> {
2042        Ok(())
2043    }
2044
2045    // -- Unified message dispatch -----------------------------------
2046
2047    /// Dispatch a message that arrived on the control stream.
2048    ///
2049    /// Draft-18 Table 5 gives every message a Stream value, and only two of
2050    /// them name the control stream: SETUP is "Control", GOAWAY is "Control,
2051    /// Request". Everything else is "Request", so this method's job is to take
2052    /// those two and refuse the messages that identify a request they have no
2053    /// stream to name.
2054    ///
2055    /// Four are refused for that reason. REQUEST_UPDATE modifies the request
2056    /// its stream carries (Section 10.9). NAMESPACE and NAMESPACE_DONE report
2057    /// namespaces on the SUBSCRIBE_NAMESPACE request stream that asked for them
2058    /// (Sections 10.16 and 10.17), and PUBLISH_BLOCKED names a track that
2059    /// cannot be published on the SUBSCRIBE_TRACKS stream that asked for it
2060    /// (Section 10.20). All four route through
2061    /// [`receive_response_on_stream`](Self::receive_response_on_stream), which
2062    /// has the request ID they need.
2063    pub fn receive_message(&mut self, msg: ControlMessage) -> Result<(), EndpointError> {
2064        match msg {
2065            ControlMessage::Setup(ref m) => self.receive_setup(m),
2066            ControlMessage::GoAway(ref m) => self.receive_goaway(m),
2067            // Refused, and the session left running rather than going through
2068            // `fail_session`, which is the answer for a rule the draft closes
2069            // over — and draft-18 closes over none of them; see
2070            // `session_error_code`. A control message carries its own
2071            // length, so the next boundary on the stream is known and a
2072            // refused message costs the session nothing; that is the answer
2073            // `ResponseOnControlStream` below has always given.
2074            ControlMessage::RequestUpdate(_) => Err(EndpointError::RequestUpdateOnControlStream),
2075            ControlMessage::Namespace(_) => {
2076                Err(EndpointError::RequestMessageOnControlStream("NAMESPACE"))
2077            }
2078            ControlMessage::NamespaceDone(_) => {
2079                Err(EndpointError::RequestMessageOnControlStream("NAMESPACE_DONE"))
2080            }
2081            ControlMessage::PublishBlocked(_) => {
2082                Err(EndpointError::RequestMessageOnControlStream("PUBLISH_BLOCKED"))
2083            }
2084            ControlMessage::SubscribeOk(_)
2085            | ControlMessage::PublishDone(_)
2086            | ControlMessage::FetchOk(_)
2087            | ControlMessage::RequestOk(_)
2088            | ControlMessage::RequestError(_) => Err(EndpointError::ResponseOnControlStream),
2089            _ => Ok(()),
2090        }
2091    }
2092
2093    /// Hold the response half of a namespace-scoped request to the rule that
2094    /// its first message answers the request.
2095    ///
2096    /// Sections 10.18 and 10.19 state it once each, for SUBSCRIBE_NAMESPACE
2097    /// and for SUBSCRIBE_TRACKS: "The publisher will respond with REQUEST_OK or
2098    /// REQUEST_ERROR on the response half of the stream. If the subscriber
2099    /// receives any message other than a REQUEST_OK or a REQUEST_ERROR as the
2100    /// first message on the response half of the stream, then it MUST close the
2101    /// session with a PROTOCOL_VIOLATION." Draft-18's own change log records it
2102    /// as new work rather than as a clarification, so drafts 17 and earlier are
2103    /// deliberately not held to it: they say nothing about which message comes
2104    /// first, and refusing one there would close a session over traffic those
2105    /// drafts permit.
2106    ///
2107    /// # Why only these two requests
2108    ///
2109    /// They are the two whose response half carries more than an answer.
2110    /// SUBSCRIBE_NAMESPACE goes on to carry NAMESPACE and NAMESPACE_DONE and
2111    /// SUBSCRIBE_TRACKS goes on to carry PUBLISH_BLOCKED, and those are exactly the
2112    /// messages that could arrive before the answer and be taken for it. A
2113    /// SUBSCRIBE has SUBSCRIBE_OK as its own first message and no such
2114    /// ambiguity, which is why the rule is written where it is.
2115    ///
2116    /// # What "first" is read from
2117    ///
2118    /// The request's own state machine. `Pending` means the request went out
2119    /// and nothing has come back, so it is the same question asked of the state
2120    /// rather than of a second counter that could disagree with it. A request
2121    /// this endpoint did not open, or one already answered, is not this rule's
2122    /// subject and passes through.
2123    ///
2124    /// # Errors
2125    ///
2126    /// [`EndpointError::ResponseBeforeTheFirstResponse`], which answers
2127    /// `Some(ProtocolViolation)`, and the session is failed before it returns.
2128    fn require_the_first_response_first(
2129        &mut self,
2130        id: u64,
2131        msg: &ControlMessage,
2132    ) -> Result<(), EndpointError> {
2133        let awaiting = [self.subscribe_namespaces.get(&id), self.subscribe_tracks.get(&id)]
2134            .into_iter()
2135            .flatten()
2136            .any(|sm| sm.state() == SubscribeNamespaceState::Pending);
2137        if !awaiting {
2138            return Ok(());
2139        }
2140        if matches!(msg, ControlMessage::RequestOk(_) | ControlMessage::RequestError(_)) {
2141            return Ok(());
2142        }
2143        let ty = msg.message_type();
2144        Err(self.fail_session(EndpointError::ResponseBeforeTheFirstResponse(id, ty)))
2145    }
2146
2147    /// Dispatch a message that arrived on the bidi request stream identified
2148    /// by `request_id`.
2149    ///
2150    /// Beyond the five responses this also takes the messages draft-18 Table 5
2151    /// places on a request stream without their being answers to it.
2152    ///
2153    /// NAMESPACE (0x8) and NAMESPACE_DONE (0xE) arrive on the
2154    /// SUBSCRIBE_NAMESPACE request stream that asked for the namespaces they
2155    /// report — Section 10.16 says NAMESPACE "is sent on the response stream of
2156    /// a SUBSCRIBE_NAMESPACE request" — and PUBLISH_BLOCKED (0xF) on the
2157    /// SUBSCRIBE_TRACKS stream that asked for the track it says cannot be
2158    /// published: "All PUBLISH_BLOCKED messages are in response to a
2159    /// SUBSCRIBE_TRACKS" (Section 10.20). Table 5 marks all three "Request", so
2160    /// this is where they land; the control stream refuses them.
2161    ///
2162    /// Without these three arms a peer that follows the draft renders
2163    /// SUBSCRIBE_NAMESPACE useless: every namespace it reports would fall
2164    /// through to [`EndpointError::ResponseOnControlStream`], on the one stream
2165    /// the draft says to report it on.
2166    pub fn receive_response_on_stream(
2167        &mut self,
2168        request_id: VarInt,
2169        msg: ControlMessage,
2170    ) -> Result<(), EndpointError> {
2171        self.require_the_first_response_first(request_id.into_inner(), &msg)?;
2172        match msg {
2173            ControlMessage::SubscribeOk(ref m) => self.receive_subscribe_ok(request_id, m),
2174            ControlMessage::PublishDone(ref m) => self.receive_publish_done(request_id, m),
2175            ControlMessage::FetchOk(ref m) => self.receive_fetch_ok(request_id, m),
2176            ControlMessage::RequestOk(ref m) => self.receive_request_ok(request_id, m),
2177            ControlMessage::RequestError(ref m) => self.receive_request_error(request_id, m),
2178            // Section 10.9 puts a REQUEST_UPDATE on the stream of the request it
2179            // modifies, which is this one.
2180            ControlMessage::RequestUpdate(ref m) => self.receive_request_update(request_id, m),
2181            // Table 5 marks GOAWAY (0x10) "Control, Request". Section 10.4's
2182            // per-request migration form arrives here, and must not be routed
2183            // to the session-wide handler.
2184            ControlMessage::GoAway(ref m) => self.receive_goaway_on_request_stream(request_id, m),
2185            ControlMessage::Namespace(ref m) => self.receive_namespace(m),
2186            ControlMessage::NamespaceDone(ref m) => self.receive_namespace_done(m),
2187            ControlMessage::PublishBlocked(ref m) => self.receive_publish_blocked(m),
2188            _ => Err(EndpointError::ResponseOnControlStream),
2189        }
2190    }
2191
2192    // -- Responder side: requests the peer opened a stream with -----
2193
2194    /// Refuse a bidirectional stream the peer opened with a message type that
2195    /// does not begin a request, and end the session.
2196    ///
2197    /// Draft-18 Section 3.3: "Bidirectional streams MUST NOT begin with any
2198    /// other message type unless negotiated. If they do, the peer MUST close
2199    /// the Session with a PROTOCOL_VIOLATION." The returned error answers
2200    /// `Some(ProtocolViolation)` from
2201    /// [`EndpointError::session_error_code`], which is what tells the
2202    /// connection layer to put the close on the wire.
2203    pub fn refuse_non_request(&mut self, ty: MessageType) -> EndpointError {
2204        self.fail_session(EndpointError::NotARequest(ty))
2205    }
2206
2207    /// Register a request the **peer** opened a bidirectional stream with, and
2208    /// hand back the Request ID it carries.
2209    ///
2210    /// The mirror of [`receive_response_on_stream`](Self::receive_response_on_stream):
2211    /// that one is fed what comes back on a stream this endpoint opened, this
2212    /// one is fed the first message on a stream the peer opened.
2213    ///
2214    /// # What it enforces, and with which code
2215    ///
2216    /// Draft-18 Section 10.1: "If an endpoint receives a Request ID where the
2217    /// least significant bit is incorrect for the sender, or a duplicate
2218    /// Request ID, it MUST close the session with INVALID_REQUEST_ID." Both
2219    /// halves are checked here and both are returned as errors that answer
2220    /// `Some(InvalidRequestId)` from
2221    /// [`EndpointError::session_error_code`]. A message that opens no request
2222    /// stream at all is a different rule with a different code — see
2223    /// [`refuse_non_request`](Self::refuse_non_request).
2224    ///
2225    /// # Seven kinds, not six
2226    ///
2227    /// Draft-18 added SUBSCRIBE_TRACKS (0x51) to the set Section 3.3 lets a
2228    /// bidirectional stream begin with, and renumbered SUBSCRIBE_NAMESPACE from
2229    /// 0x11 to 0x50. Both share the namespace-subscription lifecycle, so they
2230    /// share a state machine type while keeping separate maps: Section 10.19
2231    /// gives them "independent overlap spaces", so a prefix registered by one
2232    /// is not registered by the other.
2233    ///
2234    /// # Why there are no separate inbound maps
2235    ///
2236    /// The peer's ids and this endpoint's ids have opposite least significant
2237    /// bits, so they cannot collide. A peer's request goes into the same
2238    /// `HashMap` its outbound twin would, keyed the same way, and only the
2239    /// transition names differ — `on_subscribe_received` where the outbound
2240    /// path calls `on_subscribe_sent`.
2241    ///
2242    /// # Peer-controlled growth
2243    ///
2244    /// Every accepted request adds an entry that nothing removes, and the peer
2245    /// chooses how many to open. [`peer_request_count`](Self::peer_request_count)
2246    /// is what a responder can watch to impose its own ceiling; this method
2247    /// imposes none.
2248    pub fn receive_request_on_stream(
2249        &mut self,
2250        msg: &ControlMessage,
2251    ) -> Result<VarInt, EndpointError> {
2252        let request_id = match msg {
2253            ControlMessage::Subscribe(m) => m.request_id,
2254            ControlMessage::Publish(m) => m.request_id,
2255            ControlMessage::Fetch(m) => m.request_id,
2256            ControlMessage::PublishNamespace(m) => m.request_id,
2257            ControlMessage::SubscribeNamespace(m) => m.request_id,
2258            ControlMessage::SubscribeTracks(m) => m.request_id,
2259            ControlMessage::TrackStatus(m) => m.request_id,
2260            other => return Err(self.refuse_non_request(other.message_type())),
2261        };
2262        self.require_active_or_err()?;
2263
2264        let id = request_id.into_inner();
2265        if let Err(e) = self.request_ids.validate_peer_id(id) {
2266            return Err(self.fail_session(EndpointError::RequestId(e)));
2267        }
2268        // `insert` answers false when the id was already present, which is
2269        // exactly the duplicate the section names. Doing the check and the
2270        // record in one step means no path can record an id it did not check.
2271        if !self.peer_request_ids.insert(id) {
2272            return Err(self.fail_session(EndpointError::DuplicateRequestId(id)));
2273        }
2274
2275        match msg {
2276            ControlMessage::Subscribe(_) => {
2277                let mut sm = SubscriptionStateMachine::new();
2278                sm.on_subscribe_received()?;
2279                self.subscriptions.insert(id, sm);
2280            }
2281            ControlMessage::Publish(m) => {
2282                let alias = m.track_alias.into_inner();
2283                if let Some(conflict) =
2284                    self.conflicting_track_alias(id, alias, &m.track_namespace, &m.track_name)
2285                {
2286                    return Err(self.fail_session(conflict));
2287                }
2288                let mut sm = PublishStateMachine::new();
2289                sm.on_publish_received()?;
2290                self.publishes.insert(id, sm);
2291                // A PUBLISH names its track and its alias in the one message,
2292                // so the binding is complete on arrival. It counts against the
2293                // next one only once this endpoint has answered PUBLISH_OK,
2294                // which is what moves its state machine to Active.
2295                self.track_bindings.insert(
2296                    id,
2297                    TrackBinding {
2298                        namespace: m.track_namespace.clone(),
2299                        name: m.track_name.clone(),
2300                        alias: Some(alias),
2301                        kind: BindingKind::Publish,
2302                    },
2303                );
2304            }
2305            ControlMessage::Fetch(m) => {
2306                if let Some(joining) = self.joining_subscription_missing(m) {
2307                    self.unjoinable_fetches.insert(id, joining);
2308                }
2309                let mut sm = FetchStateMachine::new();
2310                sm.on_fetch_received()?;
2311                self.fetches.insert(id, sm);
2312            }
2313            ControlMessage::PublishNamespace(_) => {
2314                let mut sm = PublishNamespaceStateMachine::new();
2315                sm.on_publish_namespace_received()?;
2316                self.publish_namespaces.insert(id, sm);
2317            }
2318            ControlMessage::SubscribeNamespace(m) => {
2319                if let Some(established) = self.peer_namespace_overlap(&m.namespace_prefix, None) {
2320                    self.overlapping_namespace_subscriptions.insert(id, established);
2321                }
2322                let mut sm = SubscribeNamespaceStateMachine::new();
2323                sm.on_subscribe_namespace_received()?;
2324                self.subscribe_namespaces.insert(id, sm);
2325            }
2326            ControlMessage::SubscribeTracks(m) => {
2327                // Its own space: a SUBSCRIBE_TRACKS is weighed against the
2328                // SUBSCRIBE_TRACKS the peer has open and against nothing else,
2329                // so one prefix may carry one of each.
2330                if let Some(established) = self.peer_tracks_overlap(&m.namespace_prefix, None) {
2331                    self.overlapping_namespace_subscriptions.insert(id, established);
2332                }
2333                let mut sm = SubscribeNamespaceStateMachine::new();
2334                sm.on_subscribe_namespace_received()?;
2335                self.subscribe_tracks.insert(id, sm);
2336            }
2337            ControlMessage::TrackStatus(_) => {
2338                let mut sm = TrackStatusStateMachine::new();
2339                sm.on_track_status_received()?;
2340                self.track_statuses.insert(id, sm);
2341            }
2342            // Unreachable: the match above returned for every other variant.
2343            other => return Err(self.refuse_non_request(other.message_type())),
2344        }
2345        // The request as it arrived, recorded last so that nothing above it
2346        // can leave one behind for a request it went on to refuse.
2347        self.inbound_requests.insert(id, msg.clone());
2348
2349        Ok(request_id)
2350    }
2351
2352    // -- What the peer asked for ------------------------------------
2353
2354    /// The SUBSCRIBE the peer sent under `request_id` and this endpoint has
2355    /// not answered yet.
2356    ///
2357    /// `None` once it has been answered, for an identifier this session has
2358    /// carried no SUBSCRIBE under, and for one this endpoint spent on a
2359    /// request of its own -- whose state is in the same map, but which never
2360    /// arrived here. The record itself lives on past the answer, because the
2361    /// subscription it opened runs on after it, for as long as the request
2362    /// stream does.
2363    pub fn pending_subscribe(&self, request_id: VarInt) -> Option<&Subscribe> {
2364        let id = request_id.into_inner();
2365        let unanswered = self
2366            .subscriptions
2367            .get(&id)
2368            .is_some_and(|sm| sm.state() == SubscriptionState::Subscribing);
2369        match self.inbound_requests.get(&id) {
2370            Some(ControlMessage::Subscribe(msg)) if unanswered => Some(msg),
2371            _ => None,
2372        }
2373    }
2374
2375    /// How many SUBSCRIBEs the peer has sent that are still waiting for an
2376    /// answer.
2377    pub fn pending_subscribe_count(&self) -> usize {
2378        self.inbound_requests
2379            .iter()
2380            .filter(|(id, msg)| {
2381                matches!(msg, ControlMessage::Subscribe(_))
2382                    && self
2383                        .subscriptions
2384                        .get(*id)
2385                        .is_some_and(|sm| sm.state() == SubscriptionState::Subscribing)
2386            })
2387            .count()
2388    }
2389
2390    /// The PUBLISH the peer sent under `request_id` and this endpoint has not
2391    /// answered yet.
2392    ///
2393    /// `None` once it has been answered, for an identifier this session has
2394    /// carried no PUBLISH under, and for one this endpoint spent on a request
2395    /// of its own -- whose state is in the same map, but which never arrived
2396    /// here. The record itself lives on past the answer, because the
2397    /// subscription the offer opened runs on after it, for as long as the
2398    /// request stream does.
2399    pub fn pending_publish(&self, request_id: VarInt) -> Option<&message::Publish> {
2400        let id = request_id.into_inner();
2401        let unanswered =
2402            self.publishes.get(&id).is_some_and(|sm| sm.state() == PublishState::Publishing);
2403        match self.inbound_requests.get(&id) {
2404            Some(ControlMessage::Publish(msg)) if unanswered => Some(msg),
2405            _ => None,
2406        }
2407    }
2408
2409    /// How many offers the peer has sent that are still waiting for an
2410    /// answer.
2411    pub fn pending_publish_count(&self) -> usize {
2412        self.inbound_requests
2413            .iter()
2414            .filter(|(id, msg)| {
2415                matches!(msg, ControlMessage::Publish(_))
2416                    && self
2417                        .publishes
2418                        .get(*id)
2419                        .is_some_and(|sm| sm.state() == PublishState::Publishing)
2420            })
2421            .count()
2422    }
2423
2424    /// The FETCH the peer sent under `request_id` and this endpoint has not
2425    /// answered yet.
2426    ///
2427    /// `None` once it has been answered, for an identifier this session has
2428    /// carried no FETCH under, and for one this endpoint spent on a request
2429    /// of its own -- whose state is in the same map, but which never arrived
2430    /// here. The record itself lives on past the answer, because the fetch is
2431    /// not over until its response stream is.
2432    pub fn pending_fetch(&self, request_id: VarInt) -> Option<&Fetch> {
2433        let id = request_id.into_inner();
2434        let unanswered = self
2435            .fetches
2436            .get(&id)
2437            .is_some_and(|sm| matches!(sm.state(), FetchState::Pending | FetchState::Unanswered));
2438        match self.inbound_requests.get(&id) {
2439            Some(ControlMessage::Fetch(msg)) if unanswered => Some(msg),
2440            _ => None,
2441        }
2442    }
2443
2444    /// How many FETCHes the peer has sent that are still waiting for an
2445    /// answer.
2446    pub fn pending_fetch_count(&self) -> usize {
2447        self.inbound_requests
2448            .iter()
2449            .filter(|(id, msg)| {
2450                matches!(msg, ControlMessage::Fetch(_))
2451                    && self.fetches.get(*id).is_some_and(|sm| {
2452                        matches!(sm.state(), FetchState::Pending | FetchState::Unanswered)
2453                    })
2454            })
2455            .count()
2456    }
2457
2458    /// The PUBLISH_NAMESPACE the peer sent under `request_id` and this
2459    /// endpoint has not answered yet.
2460    ///
2461    /// `None` once it has been answered, for an identifier this session has
2462    /// carried no PUBLISH_NAMESPACE under, and for one this endpoint spent on
2463    /// a request of its own -- whose state is in the same map, but which
2464    /// never arrived here. The record itself lives on past the answer,
2465    /// because an announcement that was accepted stands until it is
2466    /// withdrawn.
2467    pub fn pending_publish_namespace(&self, request_id: VarInt) -> Option<&PublishNamespace> {
2468        let id = request_id.into_inner();
2469        let unanswered = self
2470            .publish_namespaces
2471            .get(&id)
2472            .is_some_and(|sm| sm.state() == PublishNamespaceState::Pending);
2473        match self.inbound_requests.get(&id) {
2474            Some(ControlMessage::PublishNamespace(msg)) if unanswered => Some(msg),
2475            _ => None,
2476        }
2477    }
2478
2479    /// How many announcements the peer has sent that are still waiting for an
2480    /// answer.
2481    pub fn pending_publish_namespace_count(&self) -> usize {
2482        self.inbound_requests
2483            .iter()
2484            .filter(|(id, msg)| {
2485                matches!(msg, ControlMessage::PublishNamespace(_))
2486                    && self
2487                        .publish_namespaces
2488                        .get(*id)
2489                        .is_some_and(|sm| sm.state() == PublishNamespaceState::Pending)
2490            })
2491            .count()
2492    }
2493
2494    /// The earliest namespace subscription the peer has made whose prefix
2495    /// overlaps `prefix`, and `None` when there is none.
2496    ///
2497    /// Only ones that have not ended count: the sentence weighs the arriving
2498    /// prefix against an "established" one, so one the peer has withdrawn and
2499    /// one this endpoint refused are both past. Drafts 07 through 11 say "an
2500    /// earlier" instead and count those too.
2501    ///
2502    /// One that has arrived and has not been answered does count. It is not
2503    /// established yet, but this endpoint is the one about to establish it,
2504    /// and accepting both would leave the session holding exactly the pair
2505    /// the sentence exists to prevent.
2506    ///
2507    /// The record of what the peer sent is what tells the two directions
2508    /// apart. The state machines live in one map per kind whichever end
2509    /// opened the request, so a prefix this endpoint asked about would be
2510    /// indistinguishable there; only requests that arrived are written into
2511    /// `inbound_requests`.
2512    ///
2513    /// The lowest Request ID wins when more than one overlaps, so the answer
2514    /// does not depend on the order a map happens to iterate in.
2515    ///
2516    /// `except` is the subscription a REQUEST_UPDATE is moving, which Section
2517    /// 10.2.14 weighs against "another active subscription of the same type"
2518    /// and therefore not against the prefix it is leaving behind.
2519    fn peer_namespace_overlap(&self, prefix: &TrackNamespace, except: Option<u64>) -> Option<u64> {
2520        self.inbound_requests
2521            .iter()
2522            .filter(|(&id, _)| Some(id) != except)
2523            .filter_map(|(&id, msg)| match msg {
2524                ControlMessage::SubscribeNamespace(m) => Some((id, &m.namespace_prefix)),
2525                _ => None,
2526            })
2527            .filter(|(id, _)| {
2528                self.subscribe_namespaces
2529                    .get(id)
2530                    .is_some_and(|sm| sm.state() != SubscribeNamespaceState::Done)
2531            })
2532            .filter_map(|(id, p)| prefixes_overlap(&p.0, &prefix.0).then_some(id))
2533            .min()
2534    }
2535
2536    /// The earliest track subscription the peer has made whose prefix
2537    /// overlaps `prefix`, and `None` when there is none.
2538    ///
2539    /// Only ones that have not ended count: the sentence weighs the arriving
2540    /// prefix against an "established" one, so one the peer has withdrawn and
2541    /// one this endpoint refused are both past. Drafts 07 through 11 say "an
2542    /// earlier" instead and count those too.
2543    ///
2544    /// One that has arrived and has not been answered does count. It is not
2545    /// established yet, but this endpoint is the one about to establish it,
2546    /// and accepting both would leave the session holding exactly the pair
2547    /// the sentence exists to prevent.
2548    ///
2549    /// The record of what the peer sent is what tells the two directions
2550    /// apart. The state machines live in one map per kind whichever end
2551    /// opened the request, so a prefix this endpoint asked about would be
2552    /// indistinguishable there; only requests that arrived are written into
2553    /// `inbound_requests`.
2554    ///
2555    /// The lowest Request ID wins when more than one overlaps, so the answer
2556    /// does not depend on the order a map happens to iterate in.
2557    ///
2558    /// `except` is the subscription a REQUEST_UPDATE is moving, which Section
2559    /// 10.2.14 weighs against "another active subscription of the same type"
2560    /// and therefore not against the prefix it is leaving behind.
2561    fn peer_tracks_overlap(&self, prefix: &TrackNamespace, except: Option<u64>) -> Option<u64> {
2562        self.inbound_requests
2563            .iter()
2564            .filter(|(&id, _)| Some(id) != except)
2565            .filter_map(|(&id, msg)| match msg {
2566                ControlMessage::SubscribeTracks(m) => Some((id, &m.namespace_prefix)),
2567                _ => None,
2568            })
2569            .filter(|(id, _)| {
2570                self.subscribe_tracks
2571                    .get(id)
2572                    .is_some_and(|sm| sm.state() != SubscribeNamespaceState::Done)
2573            })
2574            .filter_map(|(id, p)| prefixes_overlap(&p.0, &prefix.0).then_some(id))
2575            .min()
2576    }
2577
2578    /// The SUBSCRIBE_NAMESPACE the peer sent under `request_id` and this
2579    /// endpoint has not answered yet.
2580    ///
2581    /// `None` once it has been answered, for an identifier this session has
2582    /// carried no SUBSCRIBE_NAMESPACE under, and for one this endpoint spent
2583    /// on a request of its own -- whose state is in the same map, but which
2584    /// never arrived here. The record itself lives on past the answer,
2585    /// because a namespace subscription lasts as long as its stream does.
2586    pub fn pending_subscribe_namespace(&self, request_id: VarInt) -> Option<&SubscribeNamespace> {
2587        let id = request_id.into_inner();
2588        let unanswered = self
2589            .subscribe_namespaces
2590            .get(&id)
2591            .is_some_and(|sm| sm.state() == SubscribeNamespaceState::Pending);
2592        match self.inbound_requests.get(&id) {
2593            Some(ControlMessage::SubscribeNamespace(msg)) if unanswered => Some(msg),
2594            _ => None,
2595        }
2596    }
2597
2598    /// How many namespace subscriptions the peer has sent that are still
2599    /// waiting for an answer.
2600    pub fn pending_subscribe_namespace_count(&self) -> usize {
2601        self.inbound_requests
2602            .iter()
2603            .filter(|(id, msg)| {
2604                matches!(msg, ControlMessage::SubscribeNamespace(_))
2605                    && self
2606                        .subscribe_namespaces
2607                        .get(*id)
2608                        .is_some_and(|sm| sm.state() == SubscribeNamespaceState::Pending)
2609            })
2610            .count()
2611    }
2612
2613    /// The SUBSCRIBE_TRACKS the peer sent under `request_id` and this
2614    /// endpoint has not answered yet.
2615    ///
2616    /// `None` once it has been answered, for an identifier this session has
2617    /// carried no SUBSCRIBE_TRACKS under, and for one this endpoint spent on
2618    /// a request of its own -- whose state is in the same map, but which
2619    /// never arrived here. The record itself lives on past the answer,
2620    /// because the subscription it opened lasts as long as its stream does.
2621    pub fn pending_subscribe_tracks(&self, request_id: VarInt) -> Option<&SubscribeTracks> {
2622        let id = request_id.into_inner();
2623        let unanswered = self
2624            .subscribe_tracks
2625            .get(&id)
2626            .is_some_and(|sm| sm.state() == SubscribeNamespaceState::Pending);
2627        match self.inbound_requests.get(&id) {
2628            Some(ControlMessage::SubscribeTracks(msg)) if unanswered => Some(msg),
2629            _ => None,
2630        }
2631    }
2632
2633    /// How many track subscriptions the peer has sent that are still waiting
2634    /// for an answer.
2635    pub fn pending_subscribe_tracks_count(&self) -> usize {
2636        self.inbound_requests
2637            .iter()
2638            .filter(|(id, msg)| {
2639                matches!(msg, ControlMessage::SubscribeTracks(_))
2640                    && self
2641                        .subscribe_tracks
2642                        .get(*id)
2643                        .is_some_and(|sm| sm.state() == SubscribeNamespaceState::Pending)
2644            })
2645            .count()
2646    }
2647
2648    /// The TRACK_STATUS the peer sent under `request_id` and this endpoint
2649    /// has not answered yet.
2650    ///
2651    /// `None` once it has been answered, for an identifier this session has
2652    /// carried no TRACK_STATUS under, and for one this endpoint spent on a
2653    /// request of its own -- whose state is in the same map, but which never
2654    /// arrived here. The record itself lives on past the answer, because an
2655    /// update may still name it once it has been answered.
2656    pub fn pending_track_status(&self, request_id: VarInt) -> Option<&message::TrackStatus> {
2657        let id = request_id.into_inner();
2658        let unanswered =
2659            self.track_statuses.get(&id).is_some_and(|sm| sm.state() == TrackStatusState::Pending);
2660        match self.inbound_requests.get(&id) {
2661            Some(ControlMessage::TrackStatus(msg)) if unanswered => Some(msg),
2662            _ => None,
2663        }
2664    }
2665
2666    /// How many track statuses the peer has sent that are still waiting for
2667    /// an answer.
2668    pub fn pending_track_status_count(&self) -> usize {
2669        self.inbound_requests
2670            .iter()
2671            .filter(|(id, msg)| {
2672                matches!(msg, ControlMessage::TrackStatus(_))
2673                    && self
2674                        .track_statuses
2675                        .get(*id)
2676                        .is_some_and(|sm| sm.state() == TrackStatusState::Pending)
2677            })
2678            .count()
2679    }
2680
2681    /// Drive the state machine for a message this endpoint is about to write
2682    /// on a request stream the peer opened.
2683    ///
2684    /// The mirror of [`receive_response_on_stream`](Self::receive_response_on_stream),
2685    /// and the reason the transitions are named `*_sent` rather than reusing
2686    /// the received-side ones: the state edges coincide, so a mis-dispatch
2687    /// would otherwise succeed silently instead of naming the wrong event in
2688    /// an `InvalidTransition`.
2689    ///
2690    /// # More than the five responses
2691    ///
2692    /// Draft-18's Table 5 marks NAMESPACE (0x8), NAMESPACE_DONE (0xE) and
2693    /// PUBLISH_BLOCKED (0xF) "Request", and each is written by the *responder*
2694    /// on the stream of the request that asked for it: Section 10.16 says
2695    /// NAMESPACE "is sent on the response stream of a SUBSCRIBE_NAMESPACE
2696    /// request", Section 10.17 says the same of NAMESPACE_DONE, and Section
2697    /// 10.20 says "All PUBLISH_BLOCKED messages are in response to a
2698    /// SUBSCRIBE_TRACKS". None of the three ends its request, so each is a
2699    /// self-transition on an Active namespace subscription; sending one before
2700    /// the REQUEST_OK that made it Active is refused.
2701    ///
2702    /// Draft-18 folded PUBLISH_OK into REQUEST_OK (Section 10.5), so REQUEST_OK
2703    /// is also what answers a peer's PUBLISH here, where draft-17 had a message
2704    /// of its own for it.
2705    ///
2706    /// The caller writes `msg` only after this returns `Ok`. What it cannot
2707    /// undo is the opposite order: a write that fails afterwards leaves the
2708    /// state machine one step ahead of the wire, the same asymmetry the
2709    /// outbound request path already carries.
2710    /// Whether this response answers a REQUEST_UPDATE rather than the request
2711    /// that opened the stream.
2712    ///
2713    /// Section 10.9 gives an update the same two answers a request has: "The
2714    /// receiver of a REQUEST_UPDATE MUST respond with exactly one REQUEST_OK or
2715    /// REQUEST_ERROR message indicating if the update was successful." Nothing
2716    /// in either message says which of the two it is answering, so the question
2717    /// is settled twice over.
2718    ///
2719    /// A SUBSCRIBE is answered with SUBSCRIBE_OK and a FETCH with FETCH_OK, so a
2720    /// REQUEST_OK on one of those streams has no other message it could be
2721    /// answering. Draft-17 has a third, PUBLISH, which this draft folded into
2722    /// REQUEST_OK.
2723    ///
2724    /// Everywhere else it is ordering: the first REQUEST_OK or REQUEST_ERROR on
2725    /// a stream answers the request that opened it, and the ones after it answer
2726    /// updates. Both endpoints have to resolve it the same way and neither has
2727    /// anything else to resolve it with.
2728    fn answers_an_update(&self, id: u64, msg: &ControlMessage) -> bool {
2729        if !matches!(msg, ControlMessage::RequestOk(_) | ControlMessage::RequestError(_)) {
2730            return false;
2731        }
2732        // A request this endpoint made is answered by the peer, so a response
2733        // written here can only be answering an update the peer sent on it.
2734        // Section 10.9 names the one request that works that way: "A subscriber
2735        // can also send REQUEST_UPDATE to modify parameters of a subscription
2736        // established with PUBLISH."
2737        if self.publishes.contains_key(&id) && !self.inbound_requests.contains_key(&id) {
2738            return true;
2739        }
2740        if matches!(msg, ControlMessage::RequestOk(_))
2741            && (self.subscriptions.contains_key(&id) || self.fetches.contains_key(&id))
2742        {
2743            return true;
2744        }
2745        self.answered_peer_requests.contains(&id)
2746    }
2747
2748    /// Record a response as the answer to an outstanding update.
2749    ///
2750    /// A REQUEST_OK answers exactly one: "The receiver MUST still send a
2751    /// REQUEST_OK for each successful update". A REQUEST_ERROR may answer every
2752    /// update still waiting, because Section 10.9.1 permits the receiver to
2753    /// coalesce them — "If the coalesced REQUEST_UPDATE results in
2754    /// REQUEST_ERROR, only a single REQUEST_ERROR will be sent and the sender of
2755    /// the REQUEST_UPDATEs will not always be able to determine which caused an
2756    /// error." Draft-17 has no such paragraph and answers one update per
2757    /// message in both directions.
2758    ///
2759    /// No state machine moves. An update changes a request's parameters and not
2760    /// its lifecycle, so a subscription that was Active before its update was
2761    /// answered is Active after it, whichever answer went out.
2762    ///
2763    /// # Errors
2764    ///
2765    /// [`EndpointError::NoUpdateToAnswer`], and nothing is written.
2766    ///
2767    /// [`EndpointError::PeerPrefixOverlap`] and
2768    /// [`EndpointError::WrongOverlapRefusal`] when the update asked to move a
2769    /// namespace subscription onto a prefix that overlaps another of its kind
2770    /// and the answer is not the REQUEST_ERROR the sentence names. The same
2771    /// two the request itself is refused with, because it is the same rule
2772    /// under the same code; which of the two moments raised it is told apart
2773    /// by which call returned.
2774    fn answer_an_update(&mut self, id: u64, msg: &ControlMessage) -> Result<(), EndpointError> {
2775        let unanswered = self.unanswered_peer_updates.get(&id).copied().unwrap_or(0);
2776        if unanswered == 0 {
2777            return Err(EndpointError::NoUpdateToAnswer(id));
2778        }
2779        // The update's half of the overlap rule, and the reason it needs a
2780        // verdict of its own: an update is answered by the same two messages
2781        // on the same stream as the request, so the moment one of them is
2782        // about to be written is the one place both the answer and the code
2783        // the sentence names are known. Section 10.2.14: "If the new prefix
2784        // would share a common prefix with another active subscription of the
2785        // same type in the same session, the receiver MUST respond with
2786        // REQUEST_ERROR with error code PREFIX_OVERLAP."
2787        if let Some(&established) = self.overlapping_prefix_updates.get(&id) {
2788            let ControlMessage::RequestError(err) = msg else {
2789                return Err(EndpointError::PeerPrefixOverlap { request: id, established });
2790            };
2791            let required = RequestErrorCode::PrefixOverlap as u64;
2792            if err.error_code.into_inner() != required {
2793                return Err(EndpointError::WrongOverlapRefusal { request: id, required });
2794            }
2795        }
2796        let answered = if matches!(msg, ControlMessage::RequestError(_)) { unanswered } else { 1 };
2797        self.unanswered_peer_updates.insert(id, unanswered - answered);
2798        // The prefix moves on the acceptance and not before: "If the update is
2799        // accepted, NAMESPACE and NAMESPACE_DONE messages following the
2800        // REQUEST_OK will contain Track Namespace suffixes relative to the
2801        // updated prefix." A REQUEST_ERROR drops it and the subscription goes
2802        // on selecting what it selected before the update was sent.
2803        //
2804        // Nothing else is recomputed. A subscription this endpoint has already
2805        // refused keeps that verdict when ground it wanted comes free, because
2806        // the sentence that refused it names the moment it arrived and that
2807        // moment has passed.
2808        let accepted = matches!(msg, ControlMessage::RequestOk(_));
2809        if let Some(prefix) = self.updated_namespace_prefixes.remove(&id) {
2810            if accepted {
2811                match self.inbound_requests.get_mut(&id) {
2812                    Some(ControlMessage::SubscribeNamespace(m)) => m.namespace_prefix = prefix,
2813                    Some(ControlMessage::SubscribeTracks(m)) => m.namespace_prefix = prefix,
2814                    // Unreachable: nothing else is ever written above.
2815                    _ => {}
2816                }
2817            }
2818        }
2819        if !accepted {
2820            self.overlapping_prefix_updates.remove(&id);
2821        }
2822        Ok(())
2823    }
2824
2825    /// The identifier an arriving Joining Fetch names, when this session has
2826    /// no subscription it may join.
2827    ///
2828    /// Section 10.12.2:
2829    /// "If a publisher receives a Joining Fetch with a Request ID that
2830    /// does not correspond to a subscription in the same session in the
2831    /// Established or Pending (subscriber) states, it MUST return a
2832    /// REQUEST_ERROR with error code INVALID_JOINING_REQUEST_ID."
2833    /// A standalone fetch names none and answers `None`, and so does a joining
2834    /// one whose subscription is live. Either message can establish the one it
2835    /// joins: Section 5.1 says the Largest Location a Joining FETCH works from
2836    /// is the one saved "communicated in SUBSCRIBE_OK, PUBLISH or
2837    /// REQUEST_UPDATE_OK that changes the Forward State from 0 to 1".
2838    fn joining_subscription_missing(&self, msg: &Fetch) -> Option<u64> {
2839        let message::FetchPayload::Joining { joining_request_id: joined, .. } = &msg.fetch_payload
2840        else {
2841            return None;
2842        };
2843        let joined = joined.into_inner();
2844        let live =
2845            self.subscriptions.get(&joined).is_some_and(|s| s.state() != SubscriptionState::Done)
2846                || self.publishes.get(&joined).is_some_and(|p| p.state() != PublishState::Done);
2847        if live {
2848            None
2849        } else {
2850            Some(joined)
2851        }
2852    }
2853
2854    /// Drive the state machines for a response this endpoint is about to write
2855    /// on a request stream the peer opened.
2856    ///
2857    /// The caller writes the message only after this returns `Ok`, so a
2858    /// response the endpoint refuses never reaches the wire.
2859    ///
2860    /// # Errors
2861    ///
2862    /// [`EndpointError::UnknownRequest`] when no request of the answering kind
2863    /// carries that identifier, [`EndpointError::NotAResponse`] when the
2864    /// message is not one, [`EndpointError::UnjoinableSubscription`] and
2865    /// [`EndpointError::WrongJoiningRefusal`] for a Joining Fetch that named no
2866    /// live subscription, [`EndpointError::PeerPrefixOverlap`] and
2867    /// [`EndpointError::WrongOverlapRefusal`] for a namespace subscription that
2868    /// overlapped one already open or that a REQUEST_UPDATE asked to move onto
2869    /// an overlapping prefix, and each flow's own
2870    /// `InvalidTransition` for a request already answered.
2871    pub fn send_response_on_stream(
2872        &mut self,
2873        request_id: VarInt,
2874        msg: &ControlMessage,
2875    ) -> Result<(), EndpointError> {
2876        let id = request_id.into_inner();
2877        // An answer to an update reaches none of the arms below: the request it
2878        // belongs to has its own lifecycle and an update does not move it.
2879        // A refused update leaves this endpoint owing the peer a termination,
2880        // and the draft names the status that termination must carry. So the
2881        // request's ending is what the debt governs: a PUBLISH_DONE under any
2882        // other status is refused, and everything else the caller may still
2883        // have to write for this request - the answer to a second update it
2884        // has not answered yet - is left alone, because the sentence orders
2885        // nothing.
2886        if let ControlMessage::PublishDone(done) = msg {
2887            self.require_update_failure_status(id, done.status_code)?;
2888        }
2889        if self.answers_an_update(id, msg) {
2890            self.answer_an_update(id, msg)?;
2891            // Refusing an update is half of what the draft asks for, and which
2892            // half it is depends on what was being updated. A subscription is
2893            // owed the PUBLISH_DONE that ends it, recorded here so that the
2894            // next ending written for this request has to be that one. A
2895            // namespace request, a fetch and a track status are owed no
2896            // message at all, and recording one for them would keep a stream
2897            // open that has nothing left to carry.
2898            if matches!(msg, ControlMessage::RequestError(_)) && self.publishes_a_subscription(id) {
2899                self.owed_update_failures.insert(id);
2900            }
2901            return Ok(());
2902        }
2903        // A namespace subscription that overlapped one already open when it
2904        // arrived has one answer available to it, and this is where both the
2905        // answer and its code are known. An update's answer returned above, so
2906        // nothing here judges one.
2907        if let Some(&established) = self.overlapping_namespace_subscriptions.get(&id) {
2908            let ControlMessage::RequestError(err) = msg else {
2909                return Err(EndpointError::PeerPrefixOverlap { request: id, established });
2910            };
2911            let required = RequestErrorCode::PrefixOverlap as u64;
2912            if err.error_code.into_inner() != required {
2913                return Err(EndpointError::WrongOverlapRefusal { request: id, required });
2914            }
2915        }
2916        match msg {
2917            ControlMessage::SubscribeOk(_) => {
2918                let sm =
2919                    self.subscriptions.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2920                sm.on_subscribe_ok_sent()?;
2921            }
2922            ControlMessage::FetchOk(_) => {
2923                if let Some(&joining) = self.unjoinable_fetches.get(&id) {
2924                    return Err(EndpointError::UnjoinableSubscription { fetch: id, joining });
2925                }
2926                let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2927                sm.on_fetch_ok_sent()?;
2928            }
2929            ControlMessage::PublishDone(_) => {
2930                let sm =
2931                    self.subscriptions.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2932                sm.on_publish_done_sent()?;
2933            }
2934            // REQUEST_OK answers the five kinds with no response message of
2935            // their own, PUBLISH among them on this draft. The probe order does
2936            // not matter: the maps are keyed by Request ID and one id belongs
2937            // to one request.
2938            ControlMessage::RequestOk(_) => {
2939                if let Some(sm) = self.publishes.get_mut(&id) {
2940                    sm.on_publish_ok_sent()?;
2941                } else if let Some(sm) = self.subscribe_namespaces.get_mut(&id) {
2942                    sm.on_subscribe_namespace_ok_sent()?;
2943                } else if let Some(sm) = self.subscribe_tracks.get_mut(&id) {
2944                    sm.on_subscribe_namespace_ok_sent()?;
2945                } else if let Some(sm) = self.publish_namespaces.get_mut(&id) {
2946                    sm.on_publish_namespace_ok_sent()?;
2947                } else if let Some(sm) = self.track_statuses.get_mut(&id) {
2948                    sm.on_track_status_ok_sent()?;
2949                } else {
2950                    return Err(EndpointError::UnknownRequest(id));
2951                }
2952            }
2953            ControlMessage::RequestError(err) => {
2954                if let Some(sm) = self.subscriptions.get_mut(&id) {
2955                    sm.on_subscribe_error_sent()?;
2956                } else if self.fetches.contains_key(&id) {
2957                    if self.unjoinable_fetches.contains_key(&id) {
2958                        let required = RequestErrorCode::InvalidJoiningRequestId as u64;
2959                        if err.error_code.into_inner() != required {
2960                            return Err(EndpointError::WrongJoiningRefusal { fetch: id, required });
2961                        }
2962                    }
2963                    let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2964                    sm.on_fetch_error_sent()?;
2965                } else if let Some(sm) = self.publishes.get_mut(&id) {
2966                    sm.on_publish_error_sent()?;
2967                } else if let Some(sm) = self.subscribe_namespaces.get_mut(&id) {
2968                    sm.on_subscribe_namespace_error_sent()?;
2969                } else if let Some(sm) = self.subscribe_tracks.get_mut(&id) {
2970                    sm.on_subscribe_namespace_error_sent()?;
2971                } else if let Some(sm) = self.publish_namespaces.get_mut(&id) {
2972                    sm.on_publish_namespace_error_sent()?;
2973                } else if let Some(sm) = self.track_statuses.get_mut(&id) {
2974                    sm.on_track_status_error_sent()?;
2975                } else {
2976                    return Err(EndpointError::UnknownRequest(id));
2977                }
2978            }
2979            ControlMessage::Namespace(_) => {
2980                let sm = self
2981                    .subscribe_namespaces
2982                    .get_mut(&id)
2983                    .ok_or(EndpointError::UnknownRequest(id))?;
2984                sm.on_namespace_sent()?;
2985            }
2986            ControlMessage::NamespaceDone(_) => {
2987                let sm = self
2988                    .subscribe_namespaces
2989                    .get_mut(&id)
2990                    .ok_or(EndpointError::UnknownRequest(id))?;
2991                sm.on_namespace_done_sent()?;
2992            }
2993            ControlMessage::PublishBlocked(_) => {
2994                let sm =
2995                    self.subscribe_tracks.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2996                sm.on_publish_blocked_sent()?;
2997            }
2998            other => return Err(EndpointError::NotAResponse(other.message_type())),
2999        }
3000        // Reached only by a response that answered the request itself, since an
3001        // update's answer returned above. From here on, a REQUEST_OK or
3002        // REQUEST_ERROR on this stream can only be answering an update.
3003        if matches!(
3004            msg,
3005            ControlMessage::SubscribeOk(_)
3006                | ControlMessage::FetchOk(_)
3007                | ControlMessage::RequestOk(_)
3008                | ControlMessage::RequestError(_)
3009        ) {
3010            self.answered_peer_requests.insert(id);
3011        }
3012        Ok(())
3013    }
3014
3015    /// Dispatch a message that arrived on a request stream the **peer** opened,
3016    /// after the request that opened it.
3017    ///
3018    /// Nothing that arrives here is a response: this endpoint is the responder
3019    /// on such a stream, so a SUBSCRIBE_OK or REQUEST_ERROR turning up is the
3020    /// peer answering its own request, and it is refused with
3021    /// [`EndpointError::UnexpectedOnPeerRequestStream`]. The same goes for
3022    /// NAMESPACE, NAMESPACE_DONE and PUBLISH_BLOCKED, which Table 5 puts on a
3023    /// request stream but which only ever travel from the responder to the
3024    /// requester — see [`send_response_on_stream`](Self::send_response_on_stream).
3025    ///
3026    /// Two messages are expected instead.
3027    ///
3028    /// REQUEST_UPDATE, because draft-18 Section 10.9 puts it on the request's
3029    /// own stream: "The sender of a request (SUBSCRIBE, PUBLISH, FETCH,
3030    /// PUBLISH_NAMESPACE, SUBSCRIBE_NAMESPACE, SUBSCRIBE_TRACKS) can later send
3031    /// a REQUEST_UPDATE on the same bidi stream as the request to modify it."
3032    /// The request it modifies is therefore the stream's, which is why
3033    /// `request_id` here comes from the stream and the message's own Request ID
3034    /// field is not consulted — Section 10.1 says REQUEST_UPDATE consumes a
3035    /// Request ID of its own, so that field does not name the request being
3036    /// updated. Only a subscription carries a transition for an update; for the
3037    /// other kinds draft-18 defines no state change, so the message is accepted
3038    /// and the state left alone.
3039    ///
3040    /// PUBLISH_DONE, because a peer that sent PUBLISH is the publisher and ends
3041    /// the publication it opened.
3042    pub fn receive_on_peer_request_stream(
3043        &mut self,
3044        request_id: VarInt,
3045        msg: ControlMessage,
3046    ) -> Result<(), EndpointError> {
3047        let id = request_id.into_inner();
3048        match msg {
3049            // Through the same method the requester side uses, so the rule
3050            // about which requests may be updated is stated once. An id the
3051            // peer never opened is in none of the maps that method probes, so
3052            // this arm needs no check of its own.
3053            ControlMessage::RequestUpdate(ref m) => self.receive_request_update(request_id, m),
3054            // Section 10.4 puts no direction on it: "A GOAWAY MAY also be
3055            // sent on a request stream to initiate migration of that individual
3056            // request." A request the peer opened is a request stream, so a
3057            // GOAWAY on one migrates it the same way.
3058            ControlMessage::GoAway(ref m) => self.receive_goaway_on_request_stream(request_id, m),
3059            ControlMessage::PublishDone(_) => {
3060                let sm = self.publishes.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
3061                sm.on_publish_done_received()?;
3062                Ok(())
3063            }
3064            other => Err(EndpointError::UnexpectedOnPeerRequestStream(other.message_type())),
3065        }
3066    }
3067}
3068
3069// -- Responder-side state machine transitions -----------------------
3070//
3071// Each one is the same edge as an existing requester-side transition — the
3072// graph does not change with the direction — but under a name that says which
3073// way the message went, so a mis-dispatch names the responder event in the
3074// `InvalidTransition` it produces instead of quietly succeeding.
3075//
3076// They are written against the public surface of the machines rather than
3077// against the state field, so the rejected-state error has to be rebuilt to
3078// carry the responder event name.
3079
3080impl SubscriptionStateMachine {
3081    /// Idle -> Subscribing (SUBSCRIBE received from the peer).
3082    pub fn on_subscribe_received(&mut self) -> Result<(), SubscriptionError> {
3083        self.on_subscribe_sent().map_err(|_| SubscriptionError::InvalidTransition {
3084            from: self.state(),
3085            event: "on_subscribe_received".to_string(),
3086        })
3087    }
3088
3089    /// Subscribing -> Active (SUBSCRIBE_OK written on the peer's stream).
3090    pub fn on_subscribe_ok_sent(&mut self) -> Result<(), SubscriptionError> {
3091        self.on_subscribe_ok().map_err(|_| SubscriptionError::InvalidTransition {
3092            from: self.state(),
3093            event: "on_subscribe_ok_sent".to_string(),
3094        })
3095    }
3096
3097    /// Subscribing -> Done (REQUEST_ERROR written on the peer's stream).
3098    pub fn on_subscribe_error_sent(&mut self) -> Result<(), SubscriptionError> {
3099        self.on_subscribe_error().map_err(|_| SubscriptionError::InvalidTransition {
3100            from: self.state(),
3101            event: "on_subscribe_error_sent".to_string(),
3102        })
3103    }
3104
3105    /// Active -> Done (PUBLISH_DONE written on the peer's stream).
3106    pub fn on_publish_done_sent(&mut self) -> Result<(), SubscriptionError> {
3107        self.on_publish_done().map_err(|_| SubscriptionError::InvalidTransition {
3108            from: self.state(),
3109            event: "on_publish_done_sent".to_string(),
3110        })
3111    }
3112}
3113
3114impl FetchStateMachine {
3115    /// Idle -> Pending (FETCH received from the peer).
3116    pub fn on_fetch_received(&mut self) -> Result<(), FetchError> {
3117        self.on_fetch_sent().map_err(|_| FetchError::InvalidTransition {
3118            from: self.state(),
3119            event: "on_fetch_received".to_string(),
3120        })
3121    }
3122
3123    /// Pending -> Receiving, Unanswered -> Done (FETCH_OK written on the
3124    /// peer's stream).
3125    ///
3126    /// The state is named for the requester's view; for a responder the same
3127    /// node means the objects are being served rather than received. It is the
3128    /// same node in the graph, with the same edges, so it keeps its name.
3129    pub fn on_fetch_ok_sent(&mut self) -> Result<(), FetchError> {
3130        self.on_fetch_ok().map_err(|_| FetchError::InvalidTransition {
3131            from: self.state(),
3132            event: "on_fetch_ok_sent".to_string(),
3133        })
3134    }
3135
3136    /// Pending | Unanswered -> Done (REQUEST_ERROR written on the peer's
3137    /// stream).
3138    pub fn on_fetch_error_sent(&mut self) -> Result<(), FetchError> {
3139        self.on_fetch_error().map_err(|_| FetchError::InvalidTransition {
3140            from: self.state(),
3141            event: "on_fetch_error_sent".to_string(),
3142        })
3143    }
3144
3145    /// Receiving -> Done, Pending -> Unanswered (this endpoint finished the
3146    /// fetch data stream).
3147    pub fn on_stream_fin_sent(&mut self) -> Result<(), FetchError> {
3148        self.on_stream_fin().map_err(|_| FetchError::InvalidTransition {
3149            from: self.state(),
3150            event: "on_stream_fin_sent".to_string(),
3151        })
3152    }
3153}
3154
3155impl PublishStateMachine {
3156    /// Idle -> Publishing (PUBLISH received from the peer).
3157    pub fn on_publish_received(&mut self) -> Result<(), PublishFlowError> {
3158        self.on_publish_sent().map_err(|_| PublishFlowError::InvalidTransition {
3159            from: self.state(),
3160            event: "on_publish_received".to_string(),
3161        })
3162    }
3163
3164    /// Publishing -> Active (REQUEST_OK written on the peer's stream).
3165    ///
3166    /// Draft-18 folded PUBLISH_OK into REQUEST_OK (Section 10.5), so the
3167    /// message this edge is taken on is a REQUEST_OK here where draft-17 had a
3168    /// PUBLISH_OK of its own.
3169    pub fn on_publish_ok_sent(&mut self) -> Result<(), PublishFlowError> {
3170        self.on_publish_ok().map_err(|_| PublishFlowError::InvalidTransition {
3171            from: self.state(),
3172            event: "on_publish_ok_sent".to_string(),
3173        })
3174    }
3175
3176    /// Publishing -> Done (REQUEST_ERROR written on the peer's stream).
3177    pub fn on_publish_error_sent(&mut self) -> Result<(), PublishFlowError> {
3178        self.on_publish_error().map_err(|_| PublishFlowError::InvalidTransition {
3179            from: self.state(),
3180            event: "on_publish_error_sent".to_string(),
3181        })
3182    }
3183
3184    /// Active -> Done (PUBLISH_DONE received from the publishing peer).
3185    pub fn on_publish_done_received(&mut self) -> Result<(), PublishFlowError> {
3186        self.on_publish_done_sent().map_err(|_| PublishFlowError::InvalidTransition {
3187            from: self.state(),
3188            event: "on_publish_done_received".to_string(),
3189        })
3190    }
3191}
3192
3193impl PublishNamespaceStateMachine {
3194    /// Idle -> Pending (PUBLISH_NAMESPACE received from the peer).
3195    pub fn on_publish_namespace_received(&mut self) -> Result<(), NamespaceError> {
3196        self.on_publish_namespace_sent().map_err(|_| NamespaceError::InvalidTransition {
3197            from: format!("{:?}", self.state()),
3198            event: "on_publish_namespace_received".to_string(),
3199        })
3200    }
3201
3202    /// Pending -> Active (REQUEST_OK written on the peer's stream).
3203    pub fn on_publish_namespace_ok_sent(&mut self) -> Result<(), NamespaceError> {
3204        self.on_publish_namespace_ok().map_err(|_| NamespaceError::InvalidTransition {
3205            from: format!("{:?}", self.state()),
3206            event: "on_publish_namespace_ok_sent".to_string(),
3207        })
3208    }
3209
3210    /// Pending -> Done (REQUEST_ERROR written on the peer's stream).
3211    pub fn on_publish_namespace_error_sent(&mut self) -> Result<(), NamespaceError> {
3212        self.on_publish_namespace_error().map_err(|_| NamespaceError::InvalidTransition {
3213            from: format!("{:?}", self.state()),
3214            event: "on_publish_namespace_error_sent".to_string(),
3215        })
3216    }
3217}
3218
3219impl SubscribeNamespaceStateMachine {
3220    /// Idle -> Pending (SUBSCRIBE_NAMESPACE or SUBSCRIBE_TRACKS received from
3221    /// the peer).
3222    pub fn on_subscribe_namespace_received(&mut self) -> Result<(), NamespaceError> {
3223        self.on_subscribe_namespace_sent().map_err(|_| NamespaceError::InvalidTransition {
3224            from: format!("{:?}", self.state()),
3225            event: "on_subscribe_namespace_received".to_string(),
3226        })
3227    }
3228
3229    /// Pending -> Active (REQUEST_OK written on the peer's stream).
3230    pub fn on_subscribe_namespace_ok_sent(&mut self) -> Result<(), NamespaceError> {
3231        self.on_subscribe_namespace_ok().map_err(|_| NamespaceError::InvalidTransition {
3232            from: format!("{:?}", self.state()),
3233            event: "on_subscribe_namespace_ok_sent".to_string(),
3234        })
3235    }
3236
3237    /// Pending -> Done (REQUEST_ERROR written on the peer's stream).
3238    pub fn on_subscribe_namespace_error_sent(&mut self) -> Result<(), NamespaceError> {
3239        self.on_subscribe_namespace_error().map_err(|_| NamespaceError::InvalidTransition {
3240            from: format!("{:?}", self.state()),
3241            event: "on_subscribe_namespace_error_sent".to_string(),
3242        })
3243    }
3244
3245    /// Active -> Active (NAMESPACE written on the peer's SUBSCRIBE_NAMESPACE
3246    /// stream).
3247    ///
3248    /// Draft-18 Section 10.16 puts NAMESPACE "on the response stream of a
3249    /// SUBSCRIBE_NAMESPACE request", and Table 5 marks it "Request" rather than
3250    /// "Control". Reporting a namespace neither completes the subscription nor
3251    /// ends it, so the edge is a self-loop; what it does rule out is announcing
3252    /// a namespace on a subscription that has not been accepted yet.
3253    pub fn on_namespace_sent(&mut self) -> Result<(), NamespaceError> {
3254        self.stay_active("on_namespace_sent")
3255    }
3256
3257    /// Active -> Active (NAMESPACE_DONE written on the peer's
3258    /// SUBSCRIBE_NAMESPACE stream).
3259    ///
3260    /// Section 10.17: NAMESPACE_DONE says the publisher will stop serving new
3261    /// subscriptions for one namespace, not that the namespace subscription
3262    /// itself is over — the peer keeps hearing about the others — so this is a
3263    /// self-loop like [`on_namespace_sent`](Self::on_namespace_sent).
3264    pub fn on_namespace_done_sent(&mut self) -> Result<(), NamespaceError> {
3265        self.stay_active("on_namespace_done_sent")
3266    }
3267
3268    /// Active -> Active (PUBLISH_BLOCKED written on the peer's SUBSCRIBE_TRACKS
3269    /// stream).
3270    ///
3271    /// Section 10.20: "All PUBLISH_BLOCKED messages are in response to a
3272    /// SUBSCRIBE_TRACKS", reporting one track the publisher cannot send a
3273    /// PUBLISH for. The rest of the subscription is unaffected, so this too is
3274    /// a self-loop.
3275    pub fn on_publish_blocked_sent(&mut self) -> Result<(), NamespaceError> {
3276        self.stay_active("on_publish_blocked_sent")
3277    }
3278
3279    /// The shared body of the three self-loops above: accepted only in
3280    /// [`SubscribeNamespaceState::Active`], and moving nothing.
3281    ///
3282    /// Written as a state check rather than as a delegation because no existing
3283    /// transition on this machine walks Active to Active, so there is nothing
3284    /// to delegate to.
3285    fn stay_active(&mut self, event: &str) -> Result<(), NamespaceError> {
3286        if self.state() == SubscribeNamespaceState::Active {
3287            Ok(())
3288        } else {
3289            Err(NamespaceError::InvalidTransition {
3290                from: format!("{:?}", self.state()),
3291                event: event.to_string(),
3292            })
3293        }
3294    }
3295}
3296
3297impl TrackStatusStateMachine {
3298    /// Idle -> Pending (TRACK_STATUS received from the peer).
3299    pub fn on_track_status_received(&mut self) -> Result<(), TrackStatusError> {
3300        self.on_track_status_sent().map_err(|_| TrackStatusError::InvalidTransition {
3301            from: self.state(),
3302            event: "on_track_status_received".to_string(),
3303        })
3304    }
3305
3306    /// Pending -> Done (REQUEST_OK written on the peer's stream).
3307    pub fn on_track_status_ok_sent(&mut self) -> Result<(), TrackStatusError> {
3308        self.on_track_status_ok().map_err(|_| TrackStatusError::InvalidTransition {
3309            from: self.state(),
3310            event: "on_track_status_ok_sent".to_string(),
3311        })
3312    }
3313
3314    /// Pending -> Done (REQUEST_ERROR written on the peer's stream).
3315    pub fn on_track_status_error_sent(&mut self) -> Result<(), TrackStatusError> {
3316        self.on_track_status_error().map_err(|_| TrackStatusError::InvalidTransition {
3317            from: self.state(),
3318            event: "on_track_status_error_sent".to_string(),
3319        })
3320    }
3321}
3322
3323#[cfg(test)]
3324mod responder_tests {
3325    use super::*;
3326    use crate::draft18::publish::PublishState;
3327    use crate::draft18::subscription::SubscriptionState;
3328    use crate::draft18::track_status::TrackStatusState;
3329
3330    fn active_client() -> Endpoint {
3331        let mut ep = Endpoint::new(Role::Client);
3332        ep.connect().unwrap();
3333        let _ = ep.send_setup(vec![]).unwrap();
3334        ep.receive_setup(&Setup { options: vec![] }).unwrap();
3335        ep
3336    }
3337
3338    fn v(n: u64) -> VarInt {
3339        VarInt::from_u64(n).unwrap()
3340    }
3341
3342    fn ns() -> TrackNamespace {
3343        TrackNamespace(vec![b"ns".to_vec()])
3344    }
3345
3346    fn peer_subscribe(id: u64) -> ControlMessage {
3347        ControlMessage::Subscribe(Subscribe {
3348            request_id: v(id),
3349            track_namespace: ns(),
3350            track_name: b"t".to_vec(),
3351            parameters: vec![],
3352        })
3353    }
3354
3355    fn peer_publish(id: u64) -> ControlMessage {
3356        ControlMessage::Publish(Publish {
3357            request_id: v(id),
3358            track_namespace: ns(),
3359            track_name: b"t".to_vec(),
3360            track_alias: v(7),
3361            parameters: vec![],
3362            track_properties: vec![],
3363        })
3364    }
3365
3366    fn peer_subscribe_namespace(id: u64) -> ControlMessage {
3367        ControlMessage::SubscribeNamespace(SubscribeNamespace {
3368            request_id: v(id),
3369            namespace_prefix: ns(),
3370            parameters: vec![],
3371        })
3372    }
3373
3374    fn peer_subscribe_tracks(id: u64) -> ControlMessage {
3375        ControlMessage::SubscribeTracks(SubscribeTracks {
3376            request_id: v(id),
3377            namespace_prefix: ns(),
3378            parameters: vec![],
3379        })
3380    }
3381
3382    fn request_ok() -> ControlMessage {
3383        ControlMessage::RequestOk(RequestOk { parameters: vec![], track_properties: vec![] })
3384    }
3385
3386    /// The peer's requests and this endpoint's share one map per kind, and the
3387    /// opposite Request ID parity is what keeps them apart. Both directions
3388    /// are registered here and both are still there afterwards, which is the
3389    /// consequence a collision would destroy.
3390    #[test]
3391    fn a_peers_request_lives_beside_our_own_in_the_same_map() {
3392        let mut ep = active_client();
3393        let (ours, _) = ep.subscribe(ns(), b"t".to_vec(), vec![]).unwrap();
3394        assert_eq!(ours.into_inner(), 0, "a client allocates even Request IDs");
3395
3396        let theirs = ep.receive_request_on_stream(&peer_subscribe(1)).unwrap();
3397        assert_eq!(theirs.into_inner(), 1);
3398        assert_eq!(
3399            ep.active_subscription_count(),
3400            2,
3401            "the peer's subscription displaced ours in the map",
3402        );
3403        assert_eq!(ep.peer_request_count(), 1);
3404    }
3405
3406    /// Draft-18 Section 10.1: a Request ID whose least significant bit is wrong
3407    /// for the sender MUST close the session with INVALID_REQUEST_ID. The
3408    /// close is observable twice over — the endpoint stops accepting requests,
3409    /// and the code the connection layer will put on the wire is the one the
3410    /// section names.
3411    ///
3412    /// Deleting the `validate_peer_id` call from `receive_request_on_stream`
3413    /// fails with:
3414    ///
3415    /// ```text
3416    /// called `Result::unwrap_err()` on an `Ok` value: VarInt(2)
3417    /// ```
3418    #[test]
3419    fn a_peer_id_with_our_own_parity_closes_the_session() {
3420        let mut ep = active_client();
3421        // 2 is even, so it is an id this client allocates, not one the server
3422        // may send.
3423        let err = ep.receive_request_on_stream(&peer_subscribe(2)).unwrap_err();
3424        assert_eq!(err.session_error_code(), Some(SessionErrorCode::InvalidRequestId));
3425        assert_eq!(err.to_string(), "request ID error: request ID 2 has wrong parity for Server");
3426        assert_eq!(ep.session_state(), SessionState::Closed);
3427        assert_eq!(ep.active_subscription_count(), 0, "a refused request was registered anyway");
3428        assert!(matches!(
3429            ep.receive_request_on_stream(&peer_subscribe(1)),
3430            Err(EndpointError::NotActive),
3431        ));
3432    }
3433
3434    /// The other half of the same sentence: a duplicate Request ID is also
3435    /// INVALID_REQUEST_ID. The id is remembered even though the first request
3436    /// is still open, which is why the second is caught.
3437    #[test]
3438    fn a_repeated_peer_request_id_closes_the_session() {
3439        let mut ep = active_client();
3440        ep.receive_request_on_stream(&peer_subscribe(1)).unwrap();
3441        let err = ep.receive_request_on_stream(&peer_publish(1)).unwrap_err();
3442        assert_eq!(err.session_error_code(), Some(SessionErrorCode::InvalidRequestId));
3443        assert_eq!(err.to_string(), "request 1 was already used by the peer");
3444        assert_eq!(ep.session_state(), SessionState::Closed);
3445        assert_eq!(ep.active_publish_count(), 0, "the duplicate was registered anyway");
3446    }
3447
3448    /// Draft-18 Section 3.3 gives a different code for a different rule: a
3449    /// bidirectional stream that begins with the wrong message type is a
3450    /// PROTOCOL_VIOLATION, not an INVALID_REQUEST_ID.
3451    ///
3452    /// Mapping `NotARequest` to `InvalidRequestId` in `session_error_code`
3453    /// fails with:
3454    ///
3455    /// ```text
3456    /// assertion `left == right` failed
3457    ///   left: Some(InvalidRequestId)
3458    ///  right: Some(ProtocolViolation)
3459    /// ```
3460    #[test]
3461    fn a_stream_that_opens_no_request_closes_the_session_with_protocol_violation() {
3462        let mut ep = active_client();
3463        let not_a_request = ControlMessage::GoAway(GoAway {
3464            new_session_uri: Vec::new(),
3465            timeout: v(0),
3466            request_id: None,
3467        });
3468        let err = ep.receive_request_on_stream(&not_a_request).unwrap_err();
3469        assert_eq!(err.session_error_code(), Some(SessionErrorCode::ProtocolViolation));
3470        assert_eq!(err.to_string(), "GoAway does not begin a request stream");
3471        assert_eq!(ep.session_state(), SessionState::Closed);
3472    }
3473
3474    /// A peer's SUBSCRIBE runs the same graph our own does, in the other
3475    /// direction: received, then answered, then ended with PUBLISH_DONE by
3476    /// this endpoint rather than by the peer.
3477    #[test]
3478    fn answering_a_peers_subscribe_walks_the_subscription_to_done() {
3479        let mut ep = active_client();
3480        let id = ep.receive_request_on_stream(&peer_subscribe(1)).unwrap();
3481
3482        let ok = ControlMessage::SubscribeOk(SubscribeOk {
3483            track_alias: v(7),
3484            parameters: vec![],
3485            track_properties: vec![],
3486        });
3487        ep.send_response_on_stream(id, &ok).unwrap();
3488        assert_eq!(ep.subscriptions[&1].state(), SubscriptionState::Active);
3489
3490        let done = ControlMessage::PublishDone(PublishDone {
3491            status_code: v(0),
3492            stream_count: v(0),
3493            reason_phrase: Vec::new(),
3494        });
3495        ep.send_response_on_stream(id, &done).unwrap();
3496        assert_eq!(ep.subscriptions[&1].state(), SubscriptionState::Done);
3497    }
3498
3499    /// The responder transitions are separate from the requester ones so a
3500    /// mis-dispatch names the responder event rather than succeeding quietly.
3501    #[test]
3502    fn a_responder_transition_out_of_order_names_the_responder_event() {
3503        let mut ep = active_client();
3504        let id = ep.receive_request_on_stream(&peer_subscribe(1)).unwrap();
3505        let done = ControlMessage::PublishDone(PublishDone {
3506            status_code: v(0),
3507            stream_count: v(0),
3508            reason_phrase: Vec::new(),
3509        });
3510        // PUBLISH_DONE before SUBSCRIBE_OK: the subscription is not Active.
3511        let err = ep.send_response_on_stream(id, &done).unwrap_err();
3512        assert_eq!(
3513            err.to_string(),
3514            "subscription error: invalid transition from Subscribing on event on_publish_done_sent",
3515        );
3516    }
3517
3518    /// Draft-18 Section 10.5 folded PUBLISH_OK into REQUEST_OK, so a peer's
3519    /// PUBLISH is accepted with the same message that accepts a TRACK_STATUS.
3520    /// Draft-17 had PUBLISH_OK as a message of its own here, which is the port
3521    /// this most easily gets wrong: a REQUEST_OK arm that does not probe
3522    /// `publishes` leaves a peer's PUBLISH unanswerable.
3523    ///
3524    /// Removing the `publishes` probe from the `RequestOk` arm of
3525    /// `send_response_on_stream` fails with:
3526    ///
3527    /// ```text
3528    /// called `Result::unwrap()` on an `Err` value: UnknownRequest(1)
3529    /// ```
3530    #[test]
3531    fn a_peers_publish_is_accepted_with_request_ok_on_this_draft() {
3532        let mut ep = active_client();
3533        let id = ep.receive_request_on_stream(&peer_publish(1)).unwrap();
3534        ep.send_response_on_stream(id, &request_ok()).unwrap();
3535        assert_eq!(ep.publishes[&1].state(), PublishState::Active);
3536    }
3537
3538    /// A peer that sends PUBLISH is the publisher, so PUBLISH_DONE comes back
3539    /// from it on the same stream. That is the one direction the requester
3540    /// path never has to handle, because there we are the publisher.
3541    #[test]
3542    fn a_peers_publish_is_ended_by_the_peer() {
3543        let mut ep = active_client();
3544        let id = ep.receive_request_on_stream(&peer_publish(1)).unwrap();
3545        ep.send_response_on_stream(id, &request_ok()).unwrap();
3546        assert_eq!(ep.publishes[&1].state(), PublishState::Active);
3547
3548        let done = ControlMessage::PublishDone(PublishDone {
3549            status_code: v(0),
3550            stream_count: v(0),
3551            reason_phrase: Vec::new(),
3552        });
3553        ep.receive_on_peer_request_stream(id, done.clone()).unwrap();
3554        assert_eq!(ep.publishes[&1].state(), PublishState::Done);
3555        assert!(
3556            ep.receive_on_peer_request_stream(id, done).is_err(),
3557            "a second PUBLISH_DONE was accepted on a publication already Done",
3558        );
3559    }
3560
3561    /// SUBSCRIBE_TRACKS (0x51) is the seventh request kind, new in draft-18. It
3562    /// gets a map of its own rather than sharing SUBSCRIBE_NAMESPACE's, because
3563    /// Section 10.19 gives the two "independent overlap spaces"; a peer that
3564    /// sends both must end up with two registrations, not one.
3565    #[test]
3566    fn the_seventh_request_kind_is_registered_apart_from_subscribe_namespace() {
3567        let mut ep = active_client();
3568        let sn = ep.receive_request_on_stream(&peer_subscribe_namespace(1)).unwrap();
3569        let st = ep.receive_request_on_stream(&peer_subscribe_tracks(3)).unwrap();
3570        assert_eq!(ep.active_subscribe_namespace_count(), 1);
3571        assert_eq!(ep.active_subscribe_tracks_count(), 1);
3572
3573        ep.send_response_on_stream(sn, &request_ok()).unwrap();
3574        ep.send_response_on_stream(st, &request_ok()).unwrap();
3575        assert_eq!(ep.subscribe_namespaces[&1].state(), SubscribeNamespaceState::Active);
3576        assert_eq!(ep.subscribe_tracks[&3].state(), SubscribeNamespaceState::Active);
3577    }
3578
3579    /// The three messages draft-18 Table 5 places on a request stream that are
3580    /// not responses: NAMESPACE (0x8), NAMESPACE_DONE (0xE) and PUBLISH_BLOCKED
3581    /// (0xF), on the **receive** side.
3582    ///
3583    /// Table 5's Stream column reads "Request" for all three, the same value it
3584    /// gives REQUEST_UPDATE. Only SETUP is "Control" on its own; GOAWAY is
3585    /// "Control, Request". The placement had all three exactly inverted — the
3586    /// control stream accepted them and returned `Ok`, and the request stream
3587    /// fell through to the catch-all and refused them — so a conforming peer
3588    /// sending NAMESPACE on the SUBSCRIBE_NAMESPACE stream that asked for it had
3589    /// its announcement dropped, which is what made SUBSCRIBE_NAMESPACE unusable
3590    /// end to end on this draft.
3591    ///
3592    /// Restoring the control-stream arms (`Namespace(ref m) =>
3593    /// self.receive_namespace(m)` and its two siblings) fails this test at the
3594    /// second half with:
3595    ///
3596    /// ```text
3597    /// NAMESPACE must be refused on the control stream: Ok(())
3598    /// ```
3599    ///
3600    /// and removing the request-stream arms fails it at the first half with:
3601    ///
3602    /// ```text
3603    /// NAMESPACE belongs on a request stream: ResponseOnControlStream
3604    /// ```
3605    #[test]
3606    fn namespace_and_publish_blocked_arrive_on_a_request_stream() {
3607        /// A message name paired with a way to build a fresh one, since each
3608        /// case needs two copies and `ControlMessage` is consumed by both
3609        /// dispatchers.
3610        type Case = (&'static str, fn() -> ControlMessage);
3611
3612        let cases: [Case; 3] = [
3613            ("NAMESPACE", || {
3614                ControlMessage::Namespace(message::Namespace { namespace_suffix: ns() })
3615            }),
3616            ("NAMESPACE_DONE", || {
3617                ControlMessage::NamespaceDone(message::NamespaceDone { namespace_suffix: ns() })
3618            }),
3619            ("PUBLISH_BLOCKED", || {
3620                ControlMessage::PublishBlocked(PublishBlocked {
3621                    namespace_suffix: ns(),
3622                    track_name: b"video".to_vec(),
3623                })
3624            }),
3625        ];
3626
3627        for (name, build) in cases {
3628            let mut ep = active_client();
3629            let id = ep.subscribe_namespace(ns(), vec![]).unwrap().0;
3630
3631            // Sections 10.18 and 10.19 make REQUEST_OK or REQUEST_ERROR the
3632            // first message on this stream, so the answer comes before the
3633            // messages that follow it. Sending the NAMESPACE first is a
3634            // different rule's violation and would answer this test's question
3635            // with that rule's error.
3636            ep.receive_response_on_stream(
3637                id,
3638                ControlMessage::RequestOk(RequestOk {
3639                    parameters: vec![],
3640                    track_properties: vec![],
3641                }),
3642            )
3643            .expect("REQUEST_OK answers the namespace subscription");
3644
3645            // Where Table 5 puts it.
3646            ep.receive_response_on_stream(id, build())
3647                .unwrap_or_else(|e| panic!("{name} belongs on a request stream: {e:?}"));
3648
3649            // Where it does not. Refused, and the session left running.
3650            let err = match ep.receive_message(build()) {
3651                Err(e) => e,
3652                Ok(()) => panic!("{name} must be refused on the control stream: Ok(())"),
3653            };
3654            assert!(
3655                matches!(err, EndpointError::RequestMessageOnControlStream(m) if m == name),
3656                "{name} on the control stream gave {err}"
3657            );
3658            // And refused *without* a close. Section 3.3's opener sentence
3659            // does not reach a message arriving on the control stream — that
3660            // sentence is about what a bidirectional stream may *begin* with —
3661            // and no sentence in draft-18 closes a session over a message
3662            // being in the wrong place. A library that closes a session over
3663            // its own model of the protocol hands every consumer an accusation
3664            // the draft will not support.
3665            assert_eq!(
3666                err.session_error_code(),
3667                None,
3668                "no sentence in draft-18 answers {err} with a close"
3669            );
3670            assert_eq!(ep.session_state(), SessionState::Active);
3671            ep.subscribe(ns(), b"t".to_vec(), vec![])
3672                .expect("the session survives a message it could not place");
3673        }
3674    }
3675
3676    /// Draft-18's Table 5 marks NAMESPACE (0x8) and NAMESPACE_DONE (0xE)
3677    /// "Request", and Section 10.16 says NAMESPACE "is sent on the response
3678    /// stream of a SUBSCRIBE_NAMESPACE request". So a responder writes them on
3679    /// the peer's SUBSCRIBE_NAMESPACE stream, and only once that subscription
3680    /// has been accepted — before the REQUEST_OK there is nothing to report on.
3681    ///
3682    /// Neither ends the subscription: it is still Active afterwards, which is
3683    /// what lets a second namespace follow.
3684    ///
3685    /// Letting `on_namespace_sent` accept any state fails with:
3686    ///
3687    /// ```text
3688    /// a NAMESPACE was accepted on a namespace subscription that was never answered
3689    /// ```
3690    #[test]
3691    fn namespaces_are_reported_on_the_subscribe_namespace_request_stream() {
3692        let mut ep = active_client();
3693        let id = ep.receive_request_on_stream(&peer_subscribe_namespace(1)).unwrap();
3694        let namespace = ControlMessage::Namespace(message::Namespace { namespace_suffix: ns() });
3695
3696        assert!(
3697            ep.send_response_on_stream(id, &namespace).is_err(),
3698            "a NAMESPACE was accepted on a namespace subscription that was never answered",
3699        );
3700
3701        ep.send_response_on_stream(id, &request_ok()).unwrap();
3702        ep.send_response_on_stream(id, &namespace).unwrap();
3703        ep.send_response_on_stream(id, &namespace).unwrap();
3704        let done = ControlMessage::NamespaceDone(message::NamespaceDone { namespace_suffix: ns() });
3705        ep.send_response_on_stream(id, &done).unwrap();
3706        assert_eq!(
3707            ep.subscribe_namespaces[&1].state(),
3708            SubscribeNamespaceState::Active,
3709            "reporting a namespace ended the namespace subscription",
3710        );
3711    }
3712
3713    /// Table 5 marks PUBLISH_BLOCKED (0xF) "Request" too, and Section 10.20
3714    /// names the stream it belongs on: "All PUBLISH_BLOCKED messages are in
3715    /// response to a SUBSCRIBE_TRACKS". A SUBSCRIBE_NAMESPACE stream is
3716    /// therefore the wrong one, and that is checked here rather than assumed —
3717    /// the two requests are separate maps precisely so this can be told apart.
3718    #[test]
3719    fn publish_blocked_belongs_to_a_subscribe_tracks_stream_only() {
3720        let mut ep = active_client();
3721        let namespaces = ep.receive_request_on_stream(&peer_subscribe_namespace(1)).unwrap();
3722        let tracks = ep.receive_request_on_stream(&peer_subscribe_tracks(3)).unwrap();
3723        ep.send_response_on_stream(namespaces, &request_ok()).unwrap();
3724        ep.send_response_on_stream(tracks, &request_ok()).unwrap();
3725
3726        let blocked = ControlMessage::PublishBlocked(PublishBlocked {
3727            namespace_suffix: ns(),
3728            track_name: b"t".to_vec(),
3729        });
3730        assert!(
3731            matches!(
3732                ep.send_response_on_stream(namespaces, &blocked),
3733                Err(EndpointError::UnknownRequest(1)),
3734            ),
3735            "PUBLISH_BLOCKED was accepted on a SUBSCRIBE_NAMESPACE stream",
3736        );
3737        ep.send_response_on_stream(tracks, &blocked).unwrap();
3738        assert_eq!(ep.subscribe_tracks[&3].state(), SubscribeNamespaceState::Active);
3739    }
3740
3741    /// This endpoint is the responder on a stream the peer opened, so a
3742    /// response arriving there is the peer answering itself. Routing it to the
3743    /// response dispatcher would look up a request we never made; refusing it
3744    /// is what the origin marker buys.
3745    #[test]
3746    fn a_response_on_a_peer_opened_stream_is_refused() {
3747        let mut ep = active_client();
3748        let id = ep.receive_request_on_stream(&peer_subscribe(1)).unwrap();
3749        let ok = ControlMessage::SubscribeOk(SubscribeOk {
3750            track_alias: v(7),
3751            parameters: vec![],
3752            track_properties: vec![],
3753        });
3754        let err = ep.receive_on_peer_request_stream(id, ok).unwrap_err();
3755        assert_eq!(
3756            err.to_string(),
3757            "SubscribeOk may not follow a request on a stream the peer opened",
3758        );
3759    }
3760
3761    /// Draft-18 Section 10.9 puts REQUEST_UPDATE on the request's own stream,
3762    /// so the request it modifies is the stream's and not whatever its own
3763    /// Request ID field says — that field names an id of its own, since
3764    /// Section 10.1 has REQUEST_UPDATE consume one.
3765    #[test]
3766    fn a_peers_request_update_is_correlated_by_the_stream() {
3767        let mut ep = active_client();
3768        let id = ep.receive_request_on_stream(&peer_subscribe(1)).unwrap();
3769        let ok = ControlMessage::SubscribeOk(SubscribeOk {
3770            track_alias: v(7),
3771            parameters: vec![],
3772            track_properties: vec![],
3773        });
3774        ep.send_response_on_stream(id, &ok).unwrap();
3775
3776        // The Request ID field carries 5, an id no request of ours or theirs
3777        // has opened. The update still lands on the stream's subscription.
3778        let update =
3779            ControlMessage::RequestUpdate(RequestUpdate { request_id: v(5), parameters: vec![] });
3780        ep.receive_on_peer_request_stream(id, update).unwrap();
3781        assert_eq!(ep.subscriptions[&1].state(), SubscriptionState::Active);
3782    }
3783
3784    /// A peer's TRACK_STATUS is answered with REQUEST_OK and is then over —
3785    /// unlike every other kind, nothing follows it on the stream, which is why
3786    /// its state machine has no Active node.
3787    #[test]
3788    fn a_peers_track_status_is_done_once_it_is_answered() {
3789        let mut ep = active_client();
3790        let id = ep
3791            .receive_request_on_stream(&ControlMessage::TrackStatus(message::TrackStatus {
3792                request_id: v(1),
3793                track_namespace: ns(),
3794                track_name: b"t".to_vec(),
3795                parameters: vec![],
3796            }))
3797            .unwrap();
3798        assert_eq!(ep.track_statuses[&1].state(), TrackStatusState::Pending);
3799        ep.send_response_on_stream(id, &request_ok()).unwrap();
3800        assert_eq!(ep.track_statuses[&1].state(), TrackStatusState::Done);
3801    }
3802}