Skip to main content

moqtap_client/draft17/
endpoint.rs

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