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