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