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