Skip to main content

moqtap_client/draft12/
endpoint.rs

1use std::collections::HashMap;
2use std::sync::{Arc, Mutex, MutexGuard};
3
4use crate::draft12::fetch::{FetchError, FetchState, FetchStateMachine};
5use crate::draft12::namespace::{
6    AnnounceState, AnnounceStateMachine, NamespaceError, SubscribeAnnouncesState,
7    SubscribeAnnouncesStateMachine,
8};
9use crate::draft12::publish::{
10    PublishError as PublishFlowError, PublishState, PublishStateMachine,
11};
12use crate::draft12::session::request_id::{RequestIdAllocator, RequestIdError, Role};
13use crate::draft12::session::setup::{self, SetupError};
14use crate::draft12::session::state::{SessionError, SessionState, SessionStateMachine};
15use crate::draft12::subscription::{
16    SubscriptionError, SubscriptionState, SubscriptionStateMachine,
17};
18use crate::draft12::track_status::{TrackStatusError, TrackStatusState, TrackStatusStateMachine};
19use crate::forwarding_preference::{ObjectForwardingPreference, TrackForwardingPreferences};
20use crate::malformed_tracks::{MalformedTrackCondition, MalformedTracks};
21use crate::track_locations::{
22    EndOfTrackPlacement, ObjectLocation, ObjectRole, TrackFault, TrackLocations, TrackObjects,
23};
24use moqtap_codec::draft12::error_codes::{
25    FetchErrorCode, SessionErrorCode, SubscribeAnnouncesErrorCode,
26};
27use moqtap_codec::draft12::message::{
28    self, Announce, AnnounceCancel, AnnounceError, AnnounceOk, ClientSetup, ControlMessage, Fetch,
29    FetchCancel, FetchPayload, FetchType, GoAway, MaxRequestId, Publish, PublishError, PublishOk,
30    RequestsBlocked, ServerSetup, Subscribe, SubscribeAnnounces, SubscribeAnnouncesError,
31    SubscribeAnnouncesOk, SubscribeDone, SubscribeError, SubscribeOk, SubscribeUpdate, TrackStatus,
32    TrackStatusRequest, Unannounce, Unsubscribe, UnsubscribeAnnounces,
33};
34use moqtap_codec::kvp::{KeyValuePair, KvpValue};
35use moqtap_codec::types::*;
36use moqtap_codec::varint::VarInt;
37
38/// Key identifying a namespace (used for Announce maps).
39type NamespaceKey = Vec<Vec<u8>>;
40
41/// Errors that can occur during draft-12 endpoint operations.
42#[derive(Debug, thiserror::Error)]
43pub enum EndpointError {
44    /// A GOAWAY carrying a New Session URI arrived at a server.
45    ///
46    /// Section 8.4: "If a server receives a GOAWAY with a non-zero New
47    /// Session URI Length it MUST terminate the session with a Protocol
48    /// Violation." Migration is something a server offers a client, never the
49    /// other way round.
50    #[error("GOAWAY carrying a New Session URI received at a server")]
51    GoAwayUriAtServer,
52    /// A session-level state machine error.
53    #[error("session error: {0}")]
54    Session(#[from] SessionError),
55    /// A request ID allocation or validation error.
56    #[error("request ID error: {0}")]
57    RequestId(#[from] RequestIdError),
58    /// This endpoint was asked to advertise a Maximum Request ID that does
59    /// not increase, and refused. Nothing was written.
60    ///
61    /// The send-side mirror of the rule a peer breaks by sending one — Section 8.5:
62    /// "The Maximum Request ID MUST only increase within a session". No closing
63    /// mark, because the draft's sentence does not close there: it runs on
64    /// into the receipt half, which is the peer's side of this rule and not
65    /// this one's. Its own
66    /// variant, and not the received one, because the two are opposite
67    /// findings that would otherwise arrive as the same value: the received one
68    /// is a peer in violation and this one is a caller of this library asking
69    /// for a message that would put this endpoint in violation, and
70    /// [`EndpointError::session_error_code`] answers `Some` for it either way.
71    ///
72    /// Not fatal. The message is refused instead of built, the ceiling stays
73    /// where it was, and nothing reaches the peer to object to.
74    #[error(
75        "the Maximum Request ID already advertised is {advertised}, so {offered} would not increase it"
76    )]
77    MaxRequestIdWouldNotIncrease {
78        /// The ceiling this endpoint has already advertised.
79        advertised: u64,
80        /// The value it was asked to advertise instead.
81        offered: u64,
82    },
83    /// A subscription state machine error.
84    #[error("subscription error: {0}")]
85    Subscription(#[from] SubscriptionError),
86    /// A fetch state machine error.
87    #[error("fetch error: {0}")]
88    Fetch(#[from] FetchError),
89    /// A namespace state machine error.
90    #[error("namespace error: {0}")]
91    Namespace(#[from] NamespaceError),
92    /// A track status state machine error.
93    #[error("track status error: {0}")]
94    TrackStatus(#[from] TrackStatusError),
95    /// A publish flow state machine error.
96    #[error("publish flow error: {0}")]
97    PublishFlow(#[from] PublishFlowError),
98    /// A setup negotiation error.
99    #[error("setup error: {0}")]
100    Setup(#[from] SetupError),
101    /// The request ID does not match any known state machine.
102    #[error("unknown request ID: {0}")]
103    UnknownRequest(u64),
104    /// The track namespace does not match any known state machine.
105    #[error("unknown namespace")]
106    UnknownNamespace,
107    /// The session is not in the Active state.
108    #[error("session not active")]
109    NotActive,
110    /// The session is draining and cannot accept new requests.
111    #[error("session is draining, no new requests allowed")]
112    Draining,
113    /// A filter that names a start location was asked for through a helper
114    /// that has no start location to give it.
115    #[error("this filter type needs a start location; use the range form of this call")]
116    FilterNeedsRange,
117    /// A second GOAWAY arrived on the control stream.
118    ///
119    /// The GOAWAY that says the peer is going away is one message, and the
120    /// draft answers a repeat of it with a session close rather than with an
121    /// error about the second message: there is no state a second one could
122    /// move that the first has not already moved.
123    #[error("a second GOAWAY arrived on the control stream")]
124    RepeatedGoAway,
125    /// The peer named a Track Alias it is already using for another track.
126    ///
127    /// Draft-12 states it twice, once per message. Section 8.8: "The same
128    /// Track Alias MUST NOT be used to refer to two different Tracks
129    /// simultaneously. If a subscriber receives a SUBSCRIBE_OK that uses the
130    /// same Track Alias as a different track with an active subscription, it
131    /// MUST close the session with error 'Duplicate Track Alias'." Section 8.13 is the same
132    /// sentence with PUBLISH in place of SUBSCRIBE_OK.
133    ///
134    /// The session is over: this endpoint's own state has moved to Closed and
135    /// the code the transport should close with is in
136    /// [`EndpointError::session_error_code`].
137    #[error("track alias {alias} already names request {established}'s track; request {offered} names a different one")]
138    DuplicateTrackAlias {
139        /// The alias both tracks are named by.
140        alias: u64,
141        /// The request whose live subscription holds the alias.
142        established: u64,
143        /// The request whose message arrived naming it for another track.
144        offered: u64,
145    },
146    /// This endpoint was asked to hand out a Track Alias that a live track of
147    /// its own already holds.
148    ///
149    /// Section 8.8 states the rule as a prohibition before it states what the
150    /// receiver does about one: "The same Track Alias MUST NOT be used to
151    /// refer to two different Tracks simultaneously."
152    ///
153    /// The message is refused instead of built, and nothing else moves: the
154    /// subscription the peer opened is still waiting for an answer, and the
155    /// session stays as it was. The alias never reaches the peer, so there is
156    /// nothing for the peer to close over.
157    #[error("track alias {alias} already names request {held}'s track")]
158    TrackAliasInUse {
159        /// The alias that is already spoken for.
160        alias: u64,
161        /// The request whose live flow holds it.
162        held: u64,
163    },
164    /// A track's objects were framed two different ways.
165    ///
166    /// Section 9: "Every Track has a single 'Object Forwarding Preference' and
167    /// the Original Publisher MUST NOT mix different forwarding preferences
168    /// within a single track (see Section 2.5)."
169    ///
170    /// The framing is the preference: an object on a subgroup stream has the
171    /// Subgroup preference and an object in a datagram has the Datagram one,
172    /// so the track's first object settles the property and this is every
173    /// later object measured against it.
174    ///
175    /// # Why this one ends no session
176    ///
177    /// Drafts 07 through 11 finish that paragraph with a close and a code.
178    /// This draft replaces the sentence with the cross-reference above, and
179    /// what it points at is a different answer rather than the same one worded
180    /// differently. Section 2.5 lists "An Object is received with a different
181    /// Forwarding Preference than previously observed from the same Track"
182    /// among the conditions that make a track malformed, and says of all of
183    /// them: "When a subscriber detects a Malformed Track, it MUST UNSUBSCRIBE
184    /// from the Track and SHOULD deliver an error to the application".
185    ///
186    /// So this error carries no code in [`EndpointError::session_error_code`],
187    /// and the connection's `close_for_data_stream` declines it. Ending the
188    /// session over it would be this crate inventing a consequence the draft
189    /// withdrew.
190    ///
191    /// This is half of that answer and not all of it. "SHOULD deliver an error
192    /// to the application" is what this error is. The unsubscribe the same
193    /// sentence requires is not done: those are control messages, and a data
194    /// path holding `&self` has no way to send one. It is also one condition
195    /// of eight in that section rather than a rule of its own, so the answer
196    /// belongs to the family and not to this arm. A caller that wants it has
197    /// the error to act on.
198    #[error(
199        "track alias {alias} carries objects framed as {established}, and one is \
200         framed as {offered}"
201    )]
202    MixedForwardingPreference {
203        /// The Track Alias the offending object named.
204        alias: u64,
205        /// The framing the track's earlier objects settled on.
206        established: ObjectForwardingPreference,
207        /// The framing the offending object used.
208        offered: ObjectForwardingPreference,
209    },
210    /// An object saying the track ended somewhere the track has already passed.
211    ///
212    /// Section 9.2.1.1 describes Object Status 0x4, end of Track, as one whose
213    /// "GroupID is either the largest group produced in this track and the
214    /// ObjectID is one greater than the largest object produced in that
215    /// group, or GroupID is one greater than the largest group produced in
216    /// this track and the ObjectID is zero", and states the consequence:
217    /// "An object with this status that has a Group ID less than any other
218    /// GroupID, or an ObjectID less than or equal to the largest in the
219    /// specified group, is a protocol error, and the receiver MUST terminate
220    /// the session."
221    ///
222    /// # What this draft merged
223    ///
224    /// Drafts 08 through 10 spell this against a status that means end of
225    /// Track *and Group*, and carry a second status beside it for the end of
226    /// the Track alone. This draft folded the two into 0x4 and kept the
227    /// looser of the two conditions, which is why the Group ID may equal the
228    /// largest group here and may not there.
229    #[error(
230        "the end-of-track object at group {group}, object {object} on track alias \
231         {alias} is out of place: {placement}"
232    )]
233    EndOfTrackOutOfPlace {
234        /// The Track Alias the offending object named.
235        alias: u64,
236        /// The Group ID it named.
237        group: u64,
238        /// The Object ID it named.
239        object: u64,
240        /// Which half of the condition it broke, and what it was measured
241        /// against.
242        placement: EndOfTrackPlacement,
243    },
244    /// An object arriving after the track's final object.
245    ///
246    /// Section 2.5 lists the condition: "An Object is received on a Track whose
247    /// Group and Object ID are larger than the final Object in the Track. The
248    /// final Object in a Track is the Object with Status END_OF_TRACK or the
249    /// last Object sent in a FETCH whose response indicated End of Track."
250    ///
251    /// **Larger is Section 1.3.1's comparison and not a reading of the words.**
252    /// That section puts one Location below another when "A.Group < B.Group ||
253    /// (A.Group == B.Group && A.Object < B.Object)", so an Object in a later
254    /// group is past the end whatever its own Object ID is.
255    ///
256    /// **A Malformed Track and not a session error**, which is the whole
257    /// difference between this arm and the one above it. They are the same
258    /// record's two answers about the same pair of numbers: that one asks
259    /// whether an end-of-track object is behind what the track has carried and
260    /// ends the session, this one asks whether an ordinary object is past where
261    /// the track ended and gives up one track.
262    ///
263    /// This is the error half of the answer Section 2.5 states once for its
264    /// whole list — "it MUST UNSUBSCRIBE from the Track and SHOULD deliver an
265    /// error to the application". The UNSUBSCRIBE is the connection's, for the
266    /// reason the mixed-framing arm gives.
267    #[error(
268        "the object at group {group}, object {object} on track alias {alias} arrived \
269         after the track's final object at group {final_group}, object {final_object}"
270    )]
271    ObjectPastFinalObject {
272        /// The Track Alias the offending object named.
273        alias: u64,
274        /// The Group ID it named.
275        group: u64,
276        /// The Object ID it named.
277        object: u64,
278        /// The Group ID of the object the track ended at.
279        final_group: u64,
280        /// The Object ID of the object the track ended at.
281        final_object: u64,
282    },
283    /// SUBSCRIBE_UPDATE named a Request ID this session has never carried a
284    /// request under.
285    ///
286    /// Section 8.10: "A publisher MUST terminate the session with a 'Protocol
287    /// Violation' if the SUBSCRIBE_UPDATE violates these rules or if the
288    /// subscriber specifies a request ID that has not existed within the
289    /// Session."
290    ///
291    /// A request that has **ended** is not this: it existed. That is why the
292    /// record of an inbound SUBSCRIBE outlives the subscription, and why an
293    /// update naming an ended one is refused by the flow rather than by the
294    /// session.
295    #[error("SUBSCRIBE_UPDATE names request {0}, which this session has never carried")]
296    UpdateForUnknownRequest(u64),
297
298    /// A Joining Fetch named a subscription this session cannot join.
299    ///
300    /// Section 8.16: "If a publisher receives a Joining Fetch with a Request ID that
301    /// does not correspond to an existing Subscribe in the same session, it
302    /// MUST respond with a Fetch Error with code Invalid Joining Request ID."
303    ///
304    /// A refusal and not a session close, so the session runs on and the error
305    /// names both identifiers: the fetch to refuse, and the subscription it
306    /// asked to join.
307    #[error("FETCH {fetch} joins request {joining}, which is no live subscription of the peer's")]
308    UnjoinableSubscription {
309        /// The fetch that named it.
310        fetch: u64,
311        /// The identifier it named.
312        joining: u64,
313    },
314
315    /// A Joining Fetch was refused under a code other than the one the same
316    /// sentence names for it.
317    ///
318    /// The reason travels with the refusal, so a subscriber told the wrong one
319    /// retries the wrong thing: it can rebuild a fetch whose range was refused,
320    /// and cannot rebuild one whose subscription is gone.
321    #[error("refusing FETCH {fetch} for the subscription it joins takes error code {required}")]
322    WrongJoiningRefusal {
323        /// The fetch being refused.
324        fetch: u64,
325        /// The code the draft names for that refusal.
326        required: u64,
327    },
328    /// A message about an announcement named a namespace the peer has not
329    /// announced.
330    ///
331    /// Section 8.26 says what a cancellation is for: the subscriber "will stop
332    /// sending new subscriptions for tracks within the provided Track
333    /// Namespace". What a withdrawal ends and a cancellation revokes is an
334    /// announcement the **peer** made, so the record they reach for is the one
335    /// this endpoint keeps of the peer's announcements.
336    ///
337    /// Separate from [`EndpointError::UnknownNamespace`], which is the same
338    /// miss on the announcements this endpoint made, so a caller can tell which
339    /// of the two maps came up empty.
340    #[error("the peer has made no live announcement for this namespace")]
341    UnknownPeerNamespace,
342    /// A message about a namespace subscription named a prefix the peer has
343    /// not subscribed to.
344    ///
345    /// Section 5.1: "An UNSUBSCRIBE_ANNOUNCES withdraws a previous SUBSCRIBE_ANNOUNCES."
346    ///
347    /// What a withdrawal ends is a namespace subscription the **peer** made,
348    /// so the record it reaches for is the one this endpoint keeps of the
349    /// peer's. A namespace subscription this endpoint made is withdrawn by
350    /// [`Endpoint::unsubscribe_announces`], which is the same message travelling the other
351    /// way and answers with [`EndpointError::UnknownNamespace`].
352    #[error("the peer has made no live namespace subscription for this prefix")]
353    UnknownPeerNamespaceSubscription,
354    /// The peer subscribed to a namespace prefix overlapping one it is
355    /// already subscribed to.
356    ///
357    /// Section 8.27: "A subscriber cannot make overlapping namespace
358    /// subscriptions on a single session. Within a session, if a publisher
359    /// receives a SUBSCRIBE_ANNOUNCES with a Track Namespace Prefix that is a
360    /// prefix of, suffix of, or equal to an active SUBSCRIBE_ANNOUNCES, it
361    /// MUST respond with SUBSCRIBE_ANNOUNCES_ERROR, with error code Namespace
362    /// Prefix Overlap."
363    ///
364    /// Taken when the message arrives, which is the moment the sentence
365    /// names, and read again when an answer is built: a request this endpoint
366    /// may not accept is one no later call can accept.
367    ///
368    /// The refusal itself is not this error. It is a message the peer is
369    /// owed, so the request is recorded like any other and refused through
370    /// the same call that refuses any other, under the code the sentence
371    /// names.
372    #[error(
373        "request {request} subscribes to a namespace prefix overlapping request {established}"
374    )]
375    PeerPrefixOverlap {
376        /// The request that arrived.
377        request: u64,
378        /// The namespace subscription it overlaps.
379        established: u64,
380    },
381    /// This endpoint was asked to subscribe to a namespace prefix overlapping
382    /// one it is already subscribed to.
383    ///
384    /// The first half of the same sentence, which is addressed to the
385    /// subscriber: "A subscriber cannot make overlapping namespace
386    /// subscriptions on a single session."
387    ///
388    /// The message is refused instead of built, and nothing else moves: no
389    /// Request ID is spent, no state machine is created and the session stays
390    /// as it was. The request never reaches the peer, so there is nothing for
391    /// the peer to refuse.
392    #[error("the namespace prefix overlaps request {established}, which this endpoint made")]
393    OwnPrefixOverlap {
394        /// The namespace subscription this endpoint already has.
395        established: u64,
396    },
397    /// A namespace subscription that overlaps another was refused under a
398    /// code other than the one the sentence names.
399    ///
400    /// The same shape as [`EndpointError::WrongJoiningRefusal`]: a rule that
401    /// names the code its refusal carries is not satisfied by a refusal under
402    /// any other, because the peer reads the code to learn what went wrong.
403    #[error("request {request} overlaps a namespace subscription and must be refused with code {required:#x}")]
404    WrongOverlapRefusal {
405        /// The request being refused.
406        request: u64,
407        /// The code the sentence names for it.
408        required: u64,
409    },
410}
411
412/// Whether two namespace prefixes overlap.
413///
414/// Section 8.27: "A subscriber cannot make overlapping namespace
415/// subscriptions on a single session. Within a session, if a publisher
416/// receives a SUBSCRIBE_ANNOUNCES with a Track Namespace Prefix that is a
417/// prefix of, suffix of, or equal to an active SUBSCRIBE_ANNOUNCES, it MUST
418/// respond with SUBSCRIBE_ANNOUNCES_ERROR, with error code Namespace Prefix
419/// Overlap."
420///
421/// A namespace matches a namespace subscription when the subscription's
422/// prefix is a prefix of it, so two prefixes select overlapping sets of
423/// namespaces exactly when one of them is a prefix of the other. Equal
424/// prefixes are that case as well, and this draft names them.
425///
426/// "Suffix of" is read as the "or vice versa" drafts 07 through 11 write in
427/// the same place. Which suffix a prefix ends with decides nothing about the
428/// namespaces it matches, so read at its word the term would forbid pairs
429/// that overlap in nothing and permit pairs that overlap entirely.
430fn prefixes_overlap(a: &[Vec<u8>], b: &[Vec<u8>]) -> bool {
431    let shared = a.len().min(b.len());
432    a[..shared] == b[..shared]
433}
434
435impl EndpointError {
436    /// Whose doing this is — the peer's, or this endpoint's, or a variant that
437    /// cannot say.
438    ///
439    /// The companion of [`EndpointError::session_error_code`], which answers
440    /// *what the draft requires be done about it*. Neither answers the other's
441    /// question and the pair is what a caller needs: a code without a side
442    /// names nobody, and a side without a code is not grounds to publish
443    /// anything.
444    ///
445    /// Exhaustive, with no wildcard arm, so a variant added to this draft's
446    /// `EndpointError` is a compile error here rather than a silent arrival on
447    /// the wrong side of the answer. See
448    /// [`EndpointFault`](crate::above_codec_rules::EndpointFault) for the three
449    /// answers and for the collision that made the third one necessary.
450    pub fn fault(&self) -> crate::above_codec_rules::EndpointFault {
451        use crate::above_codec_rules::{AboveCodecRule as Rule, EndpointFault as Fault};
452
453        match self {
454            // Raised on both a receive path and a send path, so the
455            // variant cannot say which end is at fault. The state machines
456            // render as `invalid transition from X on event Y` whichever end
457            // asked for the transition, and the unknown-request errors name
458            // an id that may be one the peer sent or one a caller here made
459            // up.
460            EndpointError::Session(..)
461            | EndpointError::Subscription(..)
462            | EndpointError::Fetch(..)
463            | EndpointError::Namespace(..)
464            | EndpointError::TrackStatus(..)
465            | EndpointError::PublishFlow(..)
466            | EndpointError::Setup(..)
467            | EndpointError::UnknownRequest(..)
468            | EndpointError::UnknownNamespace
469            | EndpointError::UnknownPeerNamespace
470            | EndpointError::UnknownPeerNamespaceSubscription => Fault::EitherEnd,
471
472            // Raised on the way out. Nothing reached the wire, so none of
473            // these is evidence about a peer — including the ones a peer
474            // caused, where what failed is this side's attempt to accept
475            // something the draft says to refuse.
476            EndpointError::MaxRequestIdWouldNotIncrease { .. }
477            | EndpointError::NotActive
478            | EndpointError::Draining
479            | EndpointError::FilterNeedsRange
480            | EndpointError::TrackAliasInUse { .. }
481            | EndpointError::UnjoinableSubscription { .. }
482            | EndpointError::WrongJoiningRefusal { .. }
483            | EndpointError::PeerPrefixOverlap { .. }
484            | EndpointError::OwnPrefixOverlap { .. }
485            | EndpointError::WrongOverlapRefusal { .. } => Fault::ThisEndpoint,
486
487            // Raised reading what the peer sent.
488            EndpointError::DuplicateTrackAlias { .. } => Fault::Peer(Rule::DuplicateTrackAlias),
489            EndpointError::EndOfTrackOutOfPlace { .. } => Fault::Peer(Rule::EndOfTrackOutOfPlace),
490            EndpointError::GoAwayUriAtServer => Fault::Peer(Rule::GoAwayAtServer),
491            EndpointError::MixedForwardingPreference { .. } => {
492                Fault::Peer(Rule::MixedForwardingPreference)
493            }
494            EndpointError::ObjectPastFinalObject { .. } => Fault::Peer(Rule::ObjectPastFinalObject),
495            EndpointError::RepeatedGoAway => Fault::Peer(Rule::RepeatedGoAway),
496            EndpointError::UpdateForUnknownRequest(..) => {
497                Fault::Peer(Rule::RequestUpdateForTheWrongRequest)
498            }
499
500            // The Request ID rules, which are the peer's whenever they are
501            // read off the wire. The mirror — this endpoint asked to advertise
502            // a ceiling that does not increase — is
503            // `MaxRequestIdWouldNotIncrease` above, which is a variant of its
504            // own so that the two never arrive as one value.
505            EndpointError::RequestId(e) => match e {
506                RequestIdError::Decreased(..) => Fault::Peer(Rule::MaxRequestIdDecreased),
507                RequestIdError::ExceedsMax(..) => Fault::Peer(Rule::RequestIdCeiling),
508                RequestIdError::WrongParity(..) => Fault::Peer(Rule::RequestIdParity),
509                RequestIdError::OutOfSequence { .. } => Fault::Peer(Rule::RequestIdOutOfSequence),
510                // This endpoint has spent the budget the peer granted it.
511                RequestIdError::Blocked => Fault::ThisEndpoint,
512            },
513        }
514    }
515
516    /// The code to close the session with, when draft-12 answers this error
517    /// with a close rather than leaving it to the one request it concerns.
518    ///
519    /// `None` means the error is recoverable: the caller may report it, give
520    /// up on the request it concerns, and keep the session running. `Some`
521    /// means the draft ends the session, and the endpoint has already moved
522    /// its own state to Closed - the code is what the transport should carry.
523    ///
524    /// The table grows one rule at a time, and a rule joins it with a gate
525    /// that drives the bytes at a real connection and reads the close code
526    /// back off the wire. An arm added without one asserts nothing: from
527    /// inside the process the session ends either way, and only the peer can
528    /// tell the difference.
529    pub fn session_error_code(&self) -> Option<SessionErrorCode> {
530        match self {
531            // Section 8.5 answers a ceiling that does not increase with a
532            // close, and names this code for it.
533            EndpointError::RequestId(RequestIdError::Decreased(..)) => {
534                Some(SessionErrorCode::ProtocolViolation)
535            }
536            // The same section answers a Request ID that reaches the ceiling
537            // this endpoint advertised, and names a different code for it.
538            EndpointError::RequestId(RequestIdError::ExceedsMax(..)) => {
539                Some(SessionErrorCode::TooManyRequests)
540            }
541            // Section 8.1 answers a Request ID that is not the peer's to
542            // spend with a close, and names Invalid Request ID for it. Both
543            // halves of that sentence arrive here: "not valid for the peer"
544            // is an ID out of this endpoint's own half of the space, and "not
545            // expected" is a new request carrying an ID other than the next
546            // one the peer's sequence calls for.
547            EndpointError::RequestId(
548                RequestIdError::WrongParity(..) | RequestIdError::OutOfSequence { .. },
549            ) => Some(SessionErrorCode::InvalidRequestId),
550            // Section 8.4 answers a GOAWAY that repeats one already
551            // received, and names this code in the same sentence.
552            EndpointError::RepeatedGoAway => Some(SessionErrorCode::ProtocolViolation),
553            // The same section answers a migration URI arriving at a server
554            // with a close, and names this code for it. Only a server may
555            // offer one, so a client that sends one is telling a server where
556            // to reconnect, which it has no standing to do.
557            EndpointError::GoAwayUriAtServer => Some(SessionErrorCode::ProtocolViolation),
558            // Section 8.8 and Section 8.13 name this code in the sentence that
559            // states the rule, and name no other. A close carrying
560            // PROTOCOL_VIOLATION would tell the peer a different thing went
561            // wrong.
562            EndpointError::DuplicateTrackAlias { .. } => {
563                Some(SessionErrorCode::DuplicateTrackAlias)
564            }
565            // Section 8.10 answers an update naming a request the session has
566            // never carried with a close, and names this code for it.
567            EndpointError::UpdateForUnknownRequest(_) => Some(SessionErrorCode::ProtocolViolation),
568            // Section 9.2.1.1 answers an end-of-track object in the wrong place
569            // with "the receiver MUST terminate the session", and names no code
570            // in that sentence. The paragraph it closes names one for the
571            // status field's other failure — "Any other value SHOULD be treated
572            // as a protocol error and terminate the session with a Protocol
573            // Violation" — and it is the code this draft's decoder already
574            // gives the half of the same rule it can settle from one header.
575            EndpointError::EndOfTrackOutOfPlace { .. } => Some(SessionErrorCode::ProtocolViolation),
576            _ => None,
577        }
578    }
579}
580
581impl EndpointError {
582    /// The endpoint's report of a fault a track's record found.
583    ///
584    /// Written once because the two data paths reach it from different places:
585    /// a datagram through [`Endpoint::note_received_object`], and a subgroup
586    /// object through the stream that decoded it, which holds a record handle
587    /// and no endpoint. Two translations would be two chances for the same
588    /// fault to be reported as different rules.
589    pub(crate) fn for_track_fault(alias: u64, at: ObjectLocation, fault: TrackFault) -> Self {
590        match fault {
591            TrackFault::EndOfTrackOutOfPlace(placement) => EndpointError::EndOfTrackOutOfPlace {
592                alias,
593                group: at.group,
594                object: at.object,
595                placement,
596            },
597            TrackFault::PastFinalObject(end) => EndpointError::ObjectPastFinalObject {
598                alias,
599                group: at.group,
600                object: at.object,
601                final_group: end.group,
602                final_object: end.object,
603            },
604        }
605    }
606}
607
608/// Unified draft-12 MoQT endpoint wrapping session lifecycle, request ID
609/// allocation, and all per-flow state machines (subscriptions, fetches,
610/// announces, subscribe-announces, track statuses).
611pub struct Endpoint {
612    /// Which end of the session this is, which fixes the parity of the
613    /// request IDs it allocates.
614    role: Role,
615    session: SessionStateMachine,
616    request_ids: RequestIdAllocator,
617    /// Tracks the MAX_REQUEST_ID we have advertised to the peer.
618    advertised_max_id: u64,
619    subscriptions: HashMap<u64, Mutex<SubscriptionStateMachine>>,
620    fetches: HashMap<u64, FetchStateMachine>,
621    /// The track each fetch this endpoint made asked for, keyed by its
622    /// Request ID, for a FETCH stream found malformed: the stream names no
623    /// alias.
624    fetch_tracks: HashMap<u64, FetchTrack>,
625    subscribe_announces: HashMap<u64, SubscribeAnnouncesStateMachine>,
626    /// Namespace subscriptions the **peer** made, keyed by the Request ID it
627    /// opened each under.
628    ///
629    /// Kept apart from `subscribe_announces`, which holds the ones this endpoint made:
630    /// one Request ID names one request whichever end opened it, and the two
631    /// ends answer opposite halves of the flow.
632    inbound_subscribe_announces: HashMap<u64, InboundSubscribeAnnounces>,
633    announces: HashMap<u64, AnnounceStateMachine>,
634    /// Announcements the **peer** made, keyed by the Request ID it
635    /// opened each under.
636    inbound_announces: HashMap<u64, InboundAnnounce>,
637    /// Maps namespace tuple -> request_id, so callers can UNANNOUNCE / cancel
638    /// by namespace without threading the id through every API.
639    announce_ids: HashMap<NamespaceKey, u64>,
640    /// Maps namespace prefix tuple -> request_id for subscribe-announces.
641    subscribe_announces_ids: HashMap<NamespaceKey, u64>,
642    track_statuses: HashMap<u64, TrackStatusStateMachine>,
643    /// Track statuses the **peer** asked about, keyed by the Request ID it
644    /// asked under.
645    ///
646    /// Kept apart from `track_statuses`, which holds the ones this endpoint
647    /// asked about: the two are answered by opposite ends, and one Request ID
648    /// belongs to one request whichever end opened it.
649    inbound_track_statuses: HashMap<u64, InboundTrackStatus>,
650    /// The subscriptions the peer opened with PUBLISH, keyed by the request
651    /// id it opened them under.
652    ///
653    /// The record outlives the answer. Section 4.1 makes a PUBLISH the start
654    /// of a subscription rather than a question to be answered and forgotten:
655    /// "Once either of these sequences is successful, the subscription can be
656    /// updated by the subscriber using SUBSCRIBE_UPDATE, terminated by the
657    /// subscriber using UNSUBSCRIBE, or terminated by the publisher using
658    /// SUBSCRIBE_DONE." Everything after the answer needs the record - which
659    /// Track Alias is spoken for, and whether a message naming this request is
660    /// still about anything.
661    ///
662    /// The offers this endpoint makes itself are in `publishes` beside this,
663    /// which holds their state and not their message: the two are answered by
664    /// opposite ends.
665    inbound_publishes: HashMap<u64, InboundPublish>,
666    /// The subscriptions **this endpoint** opened with PUBLISH, keyed by the
667    /// Request ID it allocated for each.
668    ///
669    /// The state and nothing else, because the offer was built here and the
670    /// caller was handed it. That is the one asymmetry with `inbound_publishes`
671    /// and it is the same one every other pair of records here has.
672    publishes: HashMap<u64, PublishStateMachine>,
673    /// Subscriptions the **peer** opened with SUBSCRIBE, by Request ID.
674    ///
675    /// Section 8.10 puts SUBSCRIBE_UPDATE in the subscriber's hands - "A
676    /// subscriber sends a SUBSCRIBE_UPDATE to a publisher to modify an
677    /// existing subscription" - so an update that arrives names a
678    /// subscription in here and never one in `subscriptions`.
679    ///
680    /// The record holds the SUBSCRIBE itself and not only its state, because
681    /// the answer is built out of the request: the track a SUBSCRIBE_OK is
682    /// about is named nowhere else, and the alias it hands out has to be
683    /// judged against that name.
684    inbound_subscribes: HashMap<u64, InboundSubscribe>,
685    /// Every FETCH the peer has sent, from arrival to the end of the fetch.
686    ///
687    /// Separate from `fetches`, which holds the ones this endpoint made. The
688    /// record holds the FETCH itself and not only its state, because the
689    /// answer is built out of the request: a Joining Fetch has to be judged
690    /// against the subscription it names, and that name is nowhere else.
691    inbound_fetches: HashMap<u64, InboundFetch>,
692    negotiated_version: Option<VarInt>,
693    offered_versions: Vec<VarInt>,
694    goaway_uri: Option<Vec<u8>>,
695    /// The most recent `maximum_request_id` reported by the peer via a
696    /// `REQUESTS_BLOCKED` message.
697    peer_reported_max_request_id: Option<VarInt>,
698    /// What each track's objects have been framed as, so far.
699    ///
700    /// Behind a lock because this is the one endpoint fact a *data* stream
701    /// settles, and the data plane reaches the endpoint through `&Connection`:
702    /// a caller may hold one across tasks while it reads streams and datagrams,
703    /// so there is no `&mut` to reach the rest of this struct with.
704    forwarding_preferences: Mutex<TrackForwardingPreferences>,
705    /// The tracks this endpoint has found malformed and withdrawn from.
706    ///
707    /// Behind a lock for the same reason the record above it is: it is
708    /// written from the data plane, which holds a shared reference.
709    malformed: Mutex<MalformedTracks>,
710    /// How far each track's objects have reached, so far.
711    ///
712    /// Behind an `Arc` rather than beside the rest of this struct because the
713    /// objects that settle it are read off stream handles the caller owns, one
714    /// at a time, with no way back to the endpoint. Each such stream is handed a
715    /// clone of the handle, and every clone measures against this one record.
716    locations: Arc<Mutex<TrackLocations>>,
717    /// What Full Track Name the peer has attached each Track Alias to, per
718    /// Request ID.
719    ///
720    /// Section 8.8 and Section 8.13 forbid one alias naming two tracks at once,
721    /// and the "at once" is what makes this a table rather than a set: an alias
722    /// the peer used for a track whose subscription has ended is free again.
723    /// The table therefore records the binding and reads liveness back off the
724    /// subscription's own state machine, rather than keeping a second copy of
725    /// it that every path ending a subscription would have to remember to
726    /// prune.
727    track_bindings: HashMap<u64, TrackBinding>,
728}
729
730/// The track a fetch this endpoint made asked for: the one a standalone
731/// FETCH names, or the one the subscription a Joining Fetch joins is for.
732#[derive(Debug, Clone)]
733struct FetchTrack {
734    namespace: TrackNamespace,
735    name: Vec<u8>,
736}
737
738/// A Track Alias the peer has attached to a Full Track Name, and the request
739/// whose lifetime the attachment follows.
740#[derive(Debug, Clone)]
741struct TrackBinding {
742    namespace: TrackNamespace,
743    name: Vec<u8>,
744    /// The alias, once the peer has named one.
745    ///
746    /// A SUBSCRIBE this endpoint sends names a track and waits for its alias,
747    /// so the binding exists with no alias in it from the moment the request
748    /// is made until its SUBSCRIBE_OK arrives. A PUBLISH carries both at once
749    /// and is never in that state.
750    alias: Option<u64>,
751    kind: BindingKind,
752}
753
754/// Which of the two sequences Section 4.1 names established the subscription
755/// that owns a binding, and so which record says whether it still has one.
756#[derive(Debug, Clone, Copy, PartialEq, Eq)]
757enum BindingKind {
758    /// This endpoint's SUBSCRIBE, established by the peer's SUBSCRIBE_OK.
759    Subscribe,
760    /// A PUBLISH, established by the PUBLISH_OK answering it - whichever end
761    /// sent which. Both records are consulted for one of these, and only one
762    /// of them can hold the id: Section 8.1 gives the two ends opposite halves
763    /// of the Request ID space.
764    Publish,
765    /// The peer's SUBSCRIBE, established by this endpoint's SUBSCRIBE_OK.
766    ///
767    /// The alias is this endpoint's to choose on that one, because Section 8.8
768    /// carries it in the answer rather than in the request.
769    PeerSubscribe,
770}
771
772/// A subscription the peer opened with PUBLISH.
773struct InboundPublish {
774    /// The message as it arrived, which is what the application answers from.
775    message: Publish,
776    /// How far the subscription it opened has got.
777    ///
778    /// Behind a lock for the reason [`Endpoint::subscription`] gives: an
779    /// offer the peer made is one of the two ways this endpoint receives a
780    /// track, so a withdrawal from the data plane has to reach it.
781    state: Mutex<PublishStateMachine>,
782}
783
784impl InboundPublish {
785    /// The flow this offer opened, locked for a read or a transition.
786    fn flow(&self) -> MutexGuard<'_, PublishStateMachine> {
787        self.state.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
788    }
789
790    /// How far that flow has got.
791    fn publish_state(&self) -> PublishState {
792        self.flow().state()
793    }
794}
795
796/// A subscription the peer opened with SUBSCRIBE.
797struct InboundSubscribe {
798    /// The message as it arrived, which is what the application answers from.
799    message: Subscribe,
800    /// How far the subscription it opened has got.
801    state: SubscriptionStateMachine,
802}
803/// A fetch the peer opened with FETCH.
804struct InboundFetch {
805    /// The message as it arrived, which is what the application answers from.
806    message: Fetch,
807    /// How far the fetch it opened has got.
808    state: FetchStateMachine,
809    /// The subscription a Joining Fetch named and this session had none live
810    /// for when the FETCH arrived, which is when the rule about it is read.
811    unjoinable: Option<u64>,
812}
813
814/// An announcement the peer made with ANNOUNCE.
815///
816/// Kept apart from the announcements this endpoint made because the two are
817/// answered by opposite ends: this one is waiting for an answer from here,
818/// and the other for one from the peer.
819struct InboundAnnounce {
820    /// The message as it arrived, which is what the application answers from.
821    message: Announce,
822    /// How far the announcement it makes has got.
823    state: AnnounceStateMachine,
824}
825/// A track status the peer asked for with TRACK_STATUS_REQUEST.
826///
827/// Kept apart from the ones this endpoint asked for because the two are
828/// answered by opposite ends: this one is waiting for an answer from here,
829/// and the other for one from the peer.
830struct InboundTrackStatus {
831    /// The message as it arrived, which is what the answer is built from.
832    message: TrackStatusRequest,
833    /// How far the request it opened has got.
834    state: TrackStatusStateMachine,
835}
836/// A SUBSCRIBE_ANNOUNCES the peer sent, and how far the namespace subscription it opens
837/// has got.
838///
839/// Kept apart from `subscribe_announces`, which holds the ones this endpoint made: the
840/// two are answered by opposite ends, and this one is waiting for an answer
841/// from here.
842struct InboundSubscribeAnnounces {
843    /// The message as it arrived, which is what the answer is built from.
844    message: SubscribeAnnounces,
845    /// How far the namespace subscription it opens has got.
846    state: SubscribeAnnouncesStateMachine,
847    /// The namespace subscription this one overlapped when it arrived,
848    /// which is when the rule about it is read.
849    overlaps: Option<u64>,
850}
851impl Endpoint {
852    /// Create a new draft-12 endpoint for the given role.
853    pub fn new(role: Role) -> Self {
854        Self {
855            role,
856            session: SessionStateMachine::new(),
857            request_ids: RequestIdAllocator::new(role),
858            advertised_max_id: 0,
859            subscriptions: HashMap::new(),
860            fetches: HashMap::new(),
861            fetch_tracks: HashMap::new(),
862            subscribe_announces: HashMap::new(),
863            inbound_subscribe_announces: HashMap::new(),
864            announces: HashMap::new(),
865            inbound_announces: HashMap::new(),
866            announce_ids: HashMap::new(),
867            subscribe_announces_ids: HashMap::new(),
868            track_statuses: HashMap::new(),
869            inbound_track_statuses: HashMap::new(),
870            inbound_publishes: HashMap::new(),
871            publishes: HashMap::new(),
872            inbound_subscribes: HashMap::new(),
873            inbound_fetches: HashMap::new(),
874            negotiated_version: None,
875            offered_versions: Vec::new(),
876            goaway_uri: None,
877            peer_reported_max_request_id: None,
878            track_bindings: HashMap::new(),
879            forwarding_preferences: Mutex::new(TrackForwardingPreferences::new()),
880            malformed: Mutex::new(MalformedTracks::new()),
881            locations: Arc::new(Mutex::new(TrackLocations::new())),
882        }
883    }
884
885    /// The Track Alias the peer attached to `request_id`, once it has named
886    /// one.
887    ///
888    /// Answers for a subscription this endpoint asked for from the moment its
889    /// SUBSCRIBE_OK arrives, and for one the peer offered from the moment its
890    /// PUBLISH does. `None` before that, and for a Request ID this session has
891    /// no track for.
892    pub fn track_alias_for(&self, request_id: VarInt) -> Option<VarInt> {
893        let alias = self.track_bindings.get(&request_id.into_inner())?.alias?;
894        VarInt::from_u64(alias).ok()
895    }
896
897    /// The refusal Section 8.8 and Section 8.13 require when `alias` already
898    /// names a different track whose subscription is still live, or `None` when
899    /// it is free.
900    ///
901    /// # Why the set is read rather than kept
902    ///
903    /// The draft says "an active subscription", and this endpoint's
904    /// subscription state machine has exactly that state: a subscription
905    /// reaches Active on SUBSCRIBE_OK and leaves it on the message that ends
906    /// the flow. Asking it is what makes an alias free again the moment its track's
907    /// subscription ends, with nothing to prune on the way out - and a path
908    /// that ended a subscription without telling this table would otherwise
909    /// leave the alias held forever and refuse the peer's next, conforming,
910    /// use of it.
911    ///
912    /// # Why the request's own binding is skipped
913    ///
914    /// A SUBSCRIBE_OK is judged before its own alias is written down, so the
915    /// skip is not what keeps it from finding itself. A PUBLISH is: it carries
916    /// its alias and its track in the one message, and a second PUBLISH under
917    /// a Request ID already bound is refused by the duplicate-Request-ID rule
918    /// before it reaches here.
919    fn conflicting_track_alias(
920        &self,
921        request_id: u64,
922        alias: u64,
923        namespace: &TrackNamespace,
924        name: &[u8],
925    ) -> Option<EndpointError> {
926        for (&id, binding) in &self.track_bindings {
927            if id == request_id || binding.alias != Some(alias) {
928                continue;
929            }
930            if binding.namespace == *namespace && binding.name == name {
931                continue;
932            }
933            if self.binding_is_established(id, binding.kind) {
934                return Some(EndpointError::DuplicateTrackAlias {
935                    alias,
936                    established: id,
937                    offered: request_id,
938                });
939            }
940        }
941        None
942    }
943
944    /// Whether the request that owns a binding still has a live subscription.
945    ///
946    /// The two kinds are answered by two different records because the two
947    /// sequences Section 4.1 names end in different places: a SUBSCRIBE this
948    /// endpoint made is live once its SUBSCRIBE_OK arrives, a PUBLISH the peer
949    /// made once this endpoint has answered PUBLISH_OK.
950    fn binding_is_established(&self, id: u64, kind: BindingKind) -> bool {
951        match kind {
952            BindingKind::Subscribe => {
953                self.subscription(id).is_some_and(|sm| sm.state() == SubscriptionState::Active)
954            }
955            BindingKind::Publish => {
956                self.inbound_publishes
957                    .get(&id)
958                    .is_some_and(|p| p.publish_state() == PublishState::Active)
959                    || self.publishes.get(&id).is_some_and(|sm| sm.state() == PublishState::Active)
960            }
961            BindingKind::PeerSubscribe => self
962                .inbound_subscribes
963                .get(&id)
964                .is_some_and(|s| s.state.state() == SubscriptionState::Active),
965        }
966    }
967
968    /// The request already using `alias` for a track other than (`namespace`,
969    /// `name`), or `None` when this endpoint may give the alias to that track.
970    ///
971    /// Separate from [`Self::conflicting_track_alias`] because the two answer
972    /// different questions about the same table. That one judges a message
973    /// that has arrived and ends the session over it; this one judges one that
974    /// has not been built and declines to build it.
975    fn alias_held_elsewhere(
976        &self,
977        alias: u64,
978        namespace: &TrackNamespace,
979        name: &[u8],
980    ) -> Option<EndpointError> {
981        self.track_bindings.iter().find_map(|(&id, binding)| {
982            let other_track = binding.namespace != *namespace || binding.name != name;
983            (binding.alias == Some(alias)
984                && other_track
985                && self.binding_is_in_use(id, binding.kind))
986            .then_some(EndpointError::TrackAliasInUse { alias, held: id })
987        })
988    }
989
990    /// The track a live binding has given `alias` to.
991    ///
992    /// Read rather than kept, for the reason the alias table beside it gives: a
993    /// binding whose request has ended holds nothing, and an alias that is free
994    /// again may name a different track next. That is exactly why the
995    /// forwarding-preference record below is keyed on the track this returns
996    /// and never on the alias itself.
997    fn track_for_alias(&self, alias: u64) -> Option<(&TrackNamespace, &[u8])> {
998        self.track_bindings.iter().find_map(|(&id, binding)| {
999            (binding.alias == Some(alias) && self.binding_is_in_use(id, binding.kind))
1000                .then_some((&binding.namespace, binding.name.as_slice()))
1001        })
1002    }
1003
1004    /// The record a stream carrying `alias`'s objects measures them against.
1005    ///
1006    /// `None` for an alias no live binding names: an object for one breaks a
1007    /// different rule, and measuring it against a track this endpoint never
1008    /// asked for would answer that one with the wrong sentence.
1009    pub fn track_objects(&self, alias: u64) -> Option<TrackObjects> {
1010        let (namespace, name) = self.track_for_alias(alias)?;
1011        Some(TrackObjects::new(
1012            Arc::clone(&self.locations),
1013            namespace.clone(),
1014            name.to_vec(),
1015            alias,
1016        ))
1017    }
1018
1019    /// Record or judge one object that arrived outside a subgroup stream, and
1020    /// report the two rules its track's record answers: Section 9.2.1.1's
1021    /// protocol error when an end-of-track object says the track ended
1022    /// somewhere the track has already passed, and Section 2.5's Malformed
1023    /// Track when an object arrives after the place an end-of-track object put
1024    /// the end.
1025    ///
1026    /// `&self`, because the call site is the data plane's.
1027    pub fn note_received_object(
1028        &self,
1029        alias: u64,
1030        at: ObjectLocation,
1031        role: ObjectRole,
1032    ) -> Result<(), EndpointError> {
1033        let Some(objects) = self.track_objects(alias) else { return Ok(()) };
1034        objects
1035            .note_with_final_object(at, role)
1036            .map_err(|fault| EndpointError::for_track_fault(alias, at, fault))
1037    }
1038
1039    /// Record how a track's object was framed, and report Section 9's "MUST NOT
1040    /// mix" when it disagrees with what that track's earlier objects used.
1041    ///
1042    /// `&self`, because the call sites are the data plane's: a subgroup header
1043    /// arriving, a datagram arriving, and the two writers that produce them.
1044    ///
1045    /// An alias no live binding names records nothing and reports nothing. An
1046    /// object for such an alias breaks a different rule — the one about objects
1047    /// nobody asked for — and answering that one here would answer it with the
1048    /// wrong sentence.
1049    pub fn note_object_forwarding_preference(
1050        &self,
1051        alias: u64,
1052        seen: ObjectForwardingPreference,
1053    ) -> Result<(), EndpointError> {
1054        let Some((namespace, name)) = self.track_for_alias(alias) else { return Ok(()) };
1055        self.forwarding_preferences
1056            .lock()
1057            .unwrap_or_else(|poisoned| poisoned.into_inner())
1058            .observe(namespace, name, seen)
1059            .map_err(|established| EndpointError::MixedForwardingPreference {
1060                alias,
1061                established,
1062                offered: seen,
1063            })
1064    }
1065
1066    /// The subscription `id` opened, locked for a read or a transition.
1067    ///
1068    /// `&self`, which is the whole point of the lock. Section 2.5's answer to
1069    /// a Malformed Track is "it MUST UNSUBSCRIBE from the Track", and the
1070    /// conditions that make a track malformed are detected on the data plane,
1071    /// where this endpoint is reached through a shared reference. A flow that
1072    /// could only be moved through `&mut self` would leave that sentence
1073    /// unanswerable from the only place it is ever read.
1074    ///
1075    /// Hold one guard at a time. Nothing here takes a second while a first is
1076    /// live, and [`Self::withdraw_malformed_track`] collects the requests it
1077    /// is going to move before it moves any of them for that reason.
1078    fn subscription(&self, id: u64) -> Option<MutexGuard<'_, SubscriptionStateMachine>> {
1079        self.subscriptions
1080            .get(&id)
1081            .map(|sm| sm.lock().unwrap_or_else(|poisoned| poisoned.into_inner()))
1082    }
1083
1084    /// The condition a track was withdrawn for, or `None` for a track this
1085    /// endpoint has found nothing wrong with.
1086    ///
1087    /// The withdrawal happens without the application asking for it, so this
1088    /// is how an application that missed the error learns why a subscription
1089    /// it never ended has ended.
1090    ///
1091    /// # Why this takes a track and not the alias the object carried
1092    ///
1093    /// An alias only means anything through a live binding, and the
1094    /// withdrawal ends the binding it would have been resolved through. An
1095    /// accessor taking an alias would therefore answer `None` from the
1096    /// instant it had something to say, which is worse than not existing.
1097    /// The record is keyed on the track, and so is this.
1098    pub fn malformed_track(
1099        &self,
1100        namespace: &TrackNamespace,
1101        name: &[u8],
1102    ) -> Option<MalformedTrackCondition> {
1103        self.malformed
1104            .lock()
1105            .unwrap_or_else(|poisoned| poisoned.into_inner())
1106            .condition(namespace, name)
1107    }
1108
1109    /// Withdraw from a track a condition has just shown to be malformed, and
1110    /// give back the UNSUBSCRIBEs Section 2.5 asks for.
1111    ///
1112    /// "When a subscriber detects a Malformed Track, it MUST UNSUBSCRIBE from
1113    /// the Track and SHOULD deliver an error to the application." This is the
1114    /// first half. The second is the error the detecting path returns, which
1115    /// is why nothing here reports anything: a caller that gets an empty list
1116    /// back is not being told the track was fine.
1117    ///
1118    /// `&self`, because the call site is the data plane's.
1119    ///
1120    /// # What comes back, and what does not
1121    ///
1122    /// One UNSUBSCRIBE for each request through which this endpoint is
1123    /// *receiving* the track — a SUBSCRIBE it sent, and a PUBLISH the peer
1124    /// sent that it accepted, which Section 4.1 ends with the same message.
1125    /// A request that names the track the other way round is not one of
1126    /// those: a peer subscribing to this endpoint makes it the publisher, and
1127    /// a publisher does not unsubscribe from what it is serving.
1128    ///
1129    /// Empty for an alias no live binding names — there is no track to
1130    /// withdraw from — and for a track whose only requests are in a state
1131    /// that has no UNSUBSCRIBE in it. A subscription still waiting for its
1132    /// SUBSCRIBE_OK is the case that reaches that last one, and it holds no
1133    /// alias for an object to have arrived under.
1134    ///
1135    /// # What makes the answer happen once
1136    ///
1137    /// Not the record. The requests this withdraws end here, and a request
1138    /// that has ended has no second UNSUBSCRIBE in it, so a publisher that
1139    /// goes on mixing a track's framing is answered once however many objects
1140    /// it sends. The record is read afterwards, by an application asking why
1141    /// a track it never gave up was given up.
1142    ///
1143    /// Which leaves the case the two answers differ on: an application that
1144    /// subscribes to the same track again. That request has never been
1145    /// withdrawn from, and a publisher mixing its framing again has broken
1146    /// the sentence again, so it is withdrawn from too.
1147    pub fn withdraw_malformed_track(
1148        &self,
1149        alias: u64,
1150        condition: MalformedTrackCondition,
1151    ) -> Vec<ControlMessage> {
1152        let Some((namespace, name)) = self.track_for_alias(alias) else { return Vec::new() };
1153        let (namespace, name) = (namespace.clone(), name.to_vec());
1154        self.withdraw_malformed_track_named(namespace, name, condition)
1155    }
1156
1157    /// [`Self::withdraw_malformed_track`] for the track the FETCH `request_id`
1158    /// asked for, which a FETCH stream found malformed: the stream names no
1159    /// alias. Empty for a Request ID this endpoint sent no FETCH under.
1160    pub fn withdraw_malformed_fetch_track(
1161        &self,
1162        request_id: u64,
1163        condition: MalformedTrackCondition,
1164    ) -> Vec<ControlMessage> {
1165        let Some(track) = self.fetch_tracks.get(&request_id) else { return Vec::new() };
1166        let (namespace, name) = (track.namespace.clone(), track.name.clone());
1167        self.withdraw_malformed_track_named(namespace, name, condition)
1168    }
1169
1170    fn withdraw_malformed_track_named(
1171        &self,
1172        namespace: TrackNamespace,
1173        name: Vec<u8>,
1174        condition: MalformedTrackCondition,
1175    ) -> Vec<ControlMessage> {
1176        self.malformed
1177            .lock()
1178            .unwrap_or_else(|poisoned| poisoned.into_inner())
1179            .note(&namespace, &name, condition);
1180        // Collected before any of them is moved. `binding_is_in_use` takes a
1181        // subscription's own lock and the transitions below take it again, so
1182        // the scan finishes first and holds nothing while it runs.
1183        let mut ids: Vec<u64> = self
1184            .track_bindings
1185            .iter()
1186            .filter(|(&id, binding)| {
1187                binding.namespace == namespace
1188                    && binding.name == name
1189                    && self.binding_is_in_use(id, binding.kind)
1190            })
1191            .map(|(&id, _)| id)
1192            .collect();
1193        // A HashMap iterates in no order, and two subscriptions on one track
1194        // is a shape a peer can produce. Sorted so the wire is the same twice.
1195        ids.sort_unstable();
1196        ids.into_iter().filter_map(|id| self.withdraw_one(id)).collect()
1197    }
1198
1199    /// The UNSUBSCRIBE ending one request, or `None` when that request is not
1200    /// one this endpoint receives a track through or is not in a state that
1201    /// can be unsubscribed from.
1202    fn withdraw_one(&self, id: u64) -> Option<ControlMessage> {
1203        let request_id = VarInt::from_u64(id).ok()?;
1204        if let Some(mut sm) = self.subscription(id) {
1205            sm.on_unsubscribe().ok()?;
1206            return Some(ControlMessage::Unsubscribe(Unsubscribe { request_id }));
1207        }
1208        let entry = self.inbound_publishes.get(&id)?;
1209        entry.flow().on_unsubscribe_sent().ok()?;
1210        Some(ControlMessage::Unsubscribe(Unsubscribe { request_id }))
1211    }
1212
1213    /// Whether a binding's request has put its alias in play at all.
1214    ///
1215    /// Broader than [`Self::binding_is_established`], and the two sentences
1216    /// are why. What an endpoint must close over is qualified - "a different
1217    /// track with an active subscription" - and the prohibition Section 8.8
1218    /// opens with is not: "The same Track Alias MUST NOT be used to refer to
1219    /// two different Tracks simultaneously." Once an alias has been handed
1220    /// out, giving it to a second track is what that sentence forbids,
1221    /// answered or not.
1222    fn binding_is_in_use(&self, id: u64, kind: BindingKind) -> bool {
1223        match kind {
1224            BindingKind::Subscribe => {
1225                self.subscription(id).is_some_and(|sm| sm.state() != SubscriptionState::Done)
1226            }
1227            BindingKind::Publish => {
1228                self.inbound_publishes
1229                    .get(&id)
1230                    .is_some_and(|p| p.publish_state() != PublishState::Done)
1231                    || self.publishes.get(&id).is_some_and(|sm| sm.state() != PublishState::Done)
1232            }
1233            BindingKind::PeerSubscribe => self
1234                .inbound_subscribes
1235                .get(&id)
1236                .is_some_and(|s| s.state.state() != SubscriptionState::Done),
1237        }
1238    }
1239
1240    /// The conflict a SUBSCRIBE_OK's alias has with the tracks already bound.
1241    ///
1242    /// Separate from [`Self::conflicting_track_alias`] because the track a
1243    /// SUBSCRIBE_OK is about is not in the SUBSCRIBE_OK: it is the one this
1244    /// endpoint's own SUBSCRIBE asked for, which is why the request has to be
1245    /// looked up before the alias can be judged.
1246    fn conflicting_alias_for_subscribe_ok(&self, id: u64, alias: u64) -> Option<EndpointError> {
1247        let binding = self.track_bindings.get(&id)?;
1248        self.conflicting_track_alias(id, alias, &binding.namespace, &binding.name)
1249    }
1250
1251    // ── Accessors ──────────────────────────────────────────────
1252
1253    /// Returns the role (client or server) of this endpoint.
1254    pub fn role(&self) -> Role {
1255        self.role
1256    }
1257
1258    /// Returns the current session state.
1259    pub fn session_state(&self) -> SessionState {
1260        self.session.state()
1261    }
1262
1263    /// Returns the negotiated MoQT version, if setup is complete.
1264    pub fn negotiated_version(&self) -> Option<VarInt> {
1265        self.negotiated_version
1266    }
1267
1268    /// Returns the URI from a received GOAWAY message, if any.
1269    pub fn goaway_uri(&self) -> Option<&[u8]> {
1270        self.goaway_uri.as_deref()
1271    }
1272
1273    /// Returns whether this endpoint is blocked on request ID allocation.
1274    pub fn is_blocked(&self) -> bool {
1275        self.request_ids.is_blocked()
1276    }
1277
1278    /// Returns the number of active subscription state machines.
1279    pub fn active_subscription_count(&self) -> usize {
1280        self.subscriptions.len()
1281    }
1282
1283    /// Returns the number of active fetch state machines.
1284    pub fn active_fetch_count(&self) -> usize {
1285        self.fetches.len()
1286    }
1287
1288    /// Returns the number of active subscribe-announces state machines.
1289    pub fn active_subscribe_announces_count(&self) -> usize {
1290        self.subscribe_announces.len()
1291    }
1292
1293    /// Returns the number of active announce state machines.
1294    pub fn active_announce_count(&self) -> usize {
1295        self.announces.len()
1296    }
1297
1298    /// Returns the number of active track status state machines.
1299    pub fn active_track_status_count(&self) -> usize {
1300        self.track_statuses.len()
1301    }
1302
1303    // ── Session lifecycle ──────────────────────────────────────
1304
1305    /// Transition from Connecting to SetupExchange.
1306    pub fn connect(&mut self) -> Result<(), EndpointError> {
1307        self.session.on_connect()?;
1308        Ok(())
1309    }
1310
1311    /// Close the session (SetupExchange, Active or Draining -> Closed).
1312    pub fn close(&mut self) -> Result<(), EndpointError> {
1313        self.session.on_close()?;
1314        Ok(())
1315    }
1316
1317    // ── Client setup ───────────────────────────────────────────
1318
1319    /// Generate a CLIENT_SETUP message (client-side).
1320    pub fn send_client_setup(
1321        &mut self,
1322        versions: Vec<VarInt>,
1323        parameters: Vec<KeyValuePair>,
1324    ) -> Result<ControlMessage, EndpointError> {
1325        self.offered_versions = versions.clone();
1326        let msg = ClientSetup { supported_versions: versions, parameters };
1327        setup::validate_client_setup(&msg)?;
1328        // A MAX_REQUEST_ID parameter in our own CLIENT_SETUP is a grant to
1329        // the peer, so it is the first value of the ceiling we advertise.
1330        self.record_advertised_max(&msg.parameters);
1331        Ok(ControlMessage::ClientSetup(msg))
1332    }
1333
1334    /// Process a SERVER_SETUP message (client-side). Transitions to Active.
1335    /// If the server includes a MAX_REQUEST_ID parameter (key 0x02), the
1336    /// request ID allocator is initialized with that value.
1337    pub fn receive_server_setup(&mut self, msg: &ServerSetup) -> Result<(), EndpointError> {
1338        setup::validate_server_setup(msg)?;
1339        let version = setup::negotiate_version(&self.offered_versions, msg.selected_version)?;
1340        self.negotiated_version = Some(version);
1341        self.session.on_setup_complete()?;
1342        // Extract MAX_REQUEST_ID (key 0x02) from setup parameters if present
1343        for param in &msg.parameters {
1344            if param.key == VarInt::from_u64(0x02).unwrap() {
1345                if let KvpValue::Varint(v) = &param.value {
1346                    self.request_ids.update_max(v.into_inner())?;
1347                }
1348            }
1349        }
1350        Ok(())
1351    }
1352
1353    // ── Server setup ───────────────────────────────────────────
1354
1355    /// Process CLIENT_SETUP and generate SERVER_SETUP (server-side).
1356    pub fn receive_client_setup_and_respond(
1357        &mut self,
1358        client_setup: &ClientSetup,
1359        selected_version: VarInt,
1360    ) -> Result<ControlMessage, EndpointError> {
1361        setup::validate_client_setup(client_setup)?;
1362        // Section 8.3.2.2 puts no role restriction on MAX_REQUEST_ID, so a
1363        // CLIENT_SETUP may carry it and it grants this endpoint its budget.
1364        for param in &client_setup.parameters {
1365            if param.key == VarInt::from_u64(0x02).unwrap() {
1366                if let KvpValue::Varint(v) = &param.value {
1367                    self.request_ids.update_max(v.into_inner())?;
1368                }
1369            }
1370        }
1371        let version = setup::negotiate_version(&client_setup.supported_versions, selected_version)?;
1372        self.negotiated_version = Some(version);
1373        self.session.on_setup_complete()?;
1374        let msg = ServerSetup { selected_version: version, parameters: vec![] };
1375        Ok(ControlMessage::ServerSetup(msg))
1376    }
1377
1378    // ── MAX_REQUEST_ID ─────────────────────────────────────────
1379
1380    /// Take a Request ID from a new request the peer sent, holding it to all
1381    /// three halves of the rule.
1382    ///
1383    /// Section 8.1 closes the session with Invalid Request ID on a request ID
1384    /// "that is not valid for the peer", and Section 8.5 closes it with
1385    /// 'Too Many Requests' on one at or above the ceiling this endpoint
1386    /// advertised. The parity half belongs to the allocator; the ceiling half
1387    /// needs `advertised_max_id`, which is this endpoint's grant to the peer
1388    /// and not the peer's grant to it, so the two numbers are different and
1389    /// only one of them answers this question.
1390    ///
1391    /// The third is the sequence. The same sentence closes the session on "a
1392    /// new request with a Request ID that is not expected", and what is
1393    /// expected is fixed by the two lines above it: each endpoint starts at 0
1394    /// or 1 by role and steps by 2 per request. So a repeat and a skip are both
1395    /// refused, and neither is caught by parity or by the ceiling - a repeat has
1396    /// the right parity and sits below the ceiling by construction, having been
1397    /// accepted once already.
1398    ///
1399    /// Taking the ID is what advances the sequence, so this is for a **new**
1400    /// request only. A response, a cancellation or an update that names the
1401    /// request it modifies all carry an ID that has already been spent, and
1402    /// passing one here would refuse it.
1403    ///
1404    /// # The ceiling is a session rule, not a request rule
1405    ///
1406    /// Section 8.5: a Request ID "equal to or larger than this" received by the
1407    /// endpoint that sent the MAX_REQUEST_ID in any request message - the list
1408    /// [`Self::receive_request`] carries - closes the session, and 'Too Many
1409    /// Requests' is the code. Which is also why the number measured against is
1410    /// the one **this** endpoint sent. An id that reaches the ceiling is not a
1411    /// request to refuse with an error message: the session is over, so this
1412    /// moves the endpoint's own state to Closed and leaves the code to
1413    /// [`EndpointError::session_error_code`].
1414    ///
1415    /// # Errors
1416    ///
1417    /// [`RequestIdError::WrongParity`] if the ID belongs to this endpoint's
1418    /// half of the space, [`RequestIdError::ExceedsMax`] if it is not
1419    /// below the advertised ceiling, or [`RequestIdError::OutOfSequence`] if it
1420    /// is not the one the peer's sequence called for. A ceiling that was never
1421    /// raised is 0, which refuses every ID, matching a default that reads "the
1422    /// peer MUST NOT send requests".
1423    ///
1424    /// All three end the session rather than the one request, so all three
1425    /// move this endpoint's own session to Closed on the way out and all three
1426    /// answer `Some` from [`EndpointError::session_error_code`]. Returning one
1427    /// of them without ending the session would leave a peer that broke the
1428    /// rule free to keep sending, and never told.
1429    pub fn validate_peer_request_id(&mut self, id: u64) -> Result<(), EndpointError> {
1430        if let Err(e) = self.request_ids.validate_peer_id(id) {
1431            return Err(self.fail_session(EndpointError::RequestId(e)));
1432        }
1433        if id >= self.advertised_max_id {
1434            return Err(self.fail_session(EndpointError::RequestId(RequestIdError::ExceedsMax(
1435                id,
1436                self.advertised_max_id,
1437            ))));
1438        }
1439        if let Err(e) = self.request_ids.record_peer_id(id) {
1440            return Err(self.fail_session(EndpointError::RequestId(e)));
1441        }
1442        Ok(())
1443    }
1444
1445    /// Record a MAX_REQUEST_ID parameter this endpoint is about to send as
1446    /// the ceiling it has advertised to the peer.
1447    fn record_advertised_max(&mut self, parameters: &[KeyValuePair]) {
1448        for param in parameters {
1449            if param.key == VarInt::from_u64(0x02).unwrap() {
1450                if let KvpValue::Varint(v) = &param.value {
1451                    self.advertised_max_id = v.into_inner();
1452                }
1453            }
1454        }
1455    }
1456
1457    /// Process an incoming MAX_REQUEST_ID message, ending the session if the
1458    /// ceiling it carries does not increase.
1459    /// Section 8.5: "The Maximum Request ID MUST only increase within a
1460    /// session, and receipt of a MAX_REQUEST_ID message with an equal or
1461    /// smaller Request ID value is a 'Protocol Violation'." Section 3.4 lists
1462    /// Protocol Violation among the codes for terminating the session - "The
1463    /// remote endpoint performed an action that was disallowed by the
1464    /// specification" - so naming it of a *receipt* is this draft saying the
1465    /// session ends, and with which code. Draft-16 Section 9.5 states the same
1466    /// rule with the verb in it: "it MUST close the session with a
1467    /// PROTOCOL_VIOLATION".
1468    ///
1469    /// # Errors
1470    ///
1471    /// [`RequestIdError::Decreased`] if the value does not increase, with the
1472    /// session already moved to Closed.
1473    pub fn receive_max_request_id(&mut self, msg: &MaxRequestId) -> Result<(), EndpointError> {
1474        if let Err(err) = self.request_ids.update_max(msg.request_id.into_inner()) {
1475            return Err(self.fail_session(err.into()));
1476        }
1477        Ok(())
1478    }
1479
1480    /// Generate a MAX_REQUEST_ID message (typically server-side).
1481    ///
1482    /// Section 8.5: "The Maximum Request ID MUST only increase within a
1483    /// session", and a peer that receives an equal or smaller value closes
1484    /// the session. The ceiling starts at 0 and 0 is not greater than 0, so
1485    /// the first value that may go on the wire is 1 and there is no opening
1486    /// case where a repeat is allowed.
1487    ///
1488    /// # Errors
1489    ///
1490    /// [`EndpointError::MaxRequestIdWouldNotIncrease`] if the value does not
1491    /// strictly increase. Its own variant rather than the one a *received*
1492    /// ceiling that did not increase raises, so that a refusal to write is
1493    /// never read back as a peer in violation.
1494    pub fn send_max_request_id(&mut self, max_id: VarInt) -> Result<ControlMessage, EndpointError> {
1495        let new_val = max_id.into_inner();
1496        if new_val <= self.advertised_max_id {
1497            return Err(EndpointError::MaxRequestIdWouldNotIncrease {
1498                advertised: self.advertised_max_id,
1499                offered: new_val,
1500            });
1501        }
1502        self.advertised_max_id = new_val;
1503        Ok(ControlMessage::MaxRequestId(MaxRequestId { request_id: max_id }))
1504    }
1505
1506    // ── GoAway ─────────────────────────────────────────────────
1507
1508    /// Process an incoming GOAWAY message. Transitions to Draining.
1509    ///
1510    /// # Errors
1511    ///
1512    /// [`EndpointError::GoAwayUriAtServer`] if this endpoint is the server and
1513    /// the GOAWAY carries a New Session URI. The session is over: this
1514    /// endpoint's own state has moved to Closed and the code the transport
1515    /// should close with is in [`EndpointError::session_error_code`].
1516    ///
1517    /// [`EndpointError::RepeatedGoAway`] if a GOAWAY has already been
1518    /// received. The session is over: this endpoint's own state has moved to
1519    /// Closed and the code the transport should close with is in
1520    /// [`EndpointError::session_error_code`].
1521    pub fn receive_goaway(&mut self, msg: &GoAway) -> Result<(), EndpointError> {
1522        // Section 8.4: "If a server receives a GOAWAY with a non-zero New
1523        // Session URI Length it MUST terminate the session with a Protocol
1524        // Violation." Refused before the URI is stored rather than
1525        // after, so an application reading `goaway_uri` back can never be
1526        // handed somewhere a client chose to send it. The session ends with
1527        // it: the sentence names a close and a code, and an endpoint that
1528        // raised the error and carried on would keep serving a peer it had
1529        // just found in violation.
1530        if self.role == Role::Server && !msg.new_session_uri.is_empty() {
1531            return Err(self.fail_session(EndpointError::GoAwayUriAtServer));
1532        }
1533        // Section 8.4: "The endpoint MUST terminate the session with a
1534        // Protocol Violation (Section 3.4) if it receives multiple GOAWAY messages."
1535        // Draining is reached from nowhere else - `on_goaway` is its only
1536        // entry and this method is that method's only caller - so the session
1537        // state is the record of the first GOAWAY having arrived.
1538        if self.session.state() == SessionState::Draining {
1539            return Err(self.fail_session(EndpointError::RepeatedGoAway));
1540        }
1541        self.session.on_goaway()?;
1542        self.goaway_uri = Some(msg.new_session_uri.clone());
1543        Ok(())
1544    }
1545
1546    // ── Subscribe flow ─────────────────────────────────────────
1547
1548    fn require_active_or_err(&self) -> Result<(), EndpointError> {
1549        match self.session.state() {
1550            SessionState::Active => Ok(()),
1551            SessionState::Draining => Err(EndpointError::Draining),
1552            _ => Err(EndpointError::NotActive),
1553        }
1554    }
1555
1556    /// Record that the session is over because the peer broke a rule this
1557    /// draft answers with a session close, and hand the error back unchanged.
1558    ///
1559    /// The state move is what makes the violation stick: every request entry
1560    /// point goes through
1561    /// [`require_active_or_err`](Self::require_active_or_err), so a caller
1562    /// that ignores the returned error still cannot start anything new.
1563    /// Closing on the wire is the connection layer's job - see
1564    /// [`EndpointError::session_error_code`] for the code it should use.
1565    fn fail_session(&mut self, err: EndpointError) -> EndpointError {
1566        // `on_close` accepts SetupExchange, Active and Draining. A violation
1567        // seen in Connecting or Closed leaves the state machine alone: there
1568        // is no session to close, and the error itself is still the answer.
1569        //
1570        // SetupExchange is in that set because the Termination section says
1571        // "The Transport Session can be terminated at any point", and the
1572        // Setup exchange is a point. So a violation caught while the setup is
1573        // still in flight does close the session, and the discarded result is
1574        // safe because that is one of the states `on_close` accepts.
1575        let _ = self.session.on_close();
1576        err
1577    }
1578
1579    /// Send a SUBSCRIBE message. Allocates an ID and creates a subscription
1580    /// state machine.
1581    ///
1582    /// `AbsoluteStart` and `AbsoluteRange` name a start location, which this
1583    /// call has no way to supply, and are answered with
1584    /// [`EndpointError::FilterNeedsRange`] - use [`Self::subscribe_range`] for
1585    /// those. Without the refusal this call would hand back a message whose
1586    /// filter announces fields the message does not carry, and the frame that
1587    /// goes on the wire is short by exactly those fields.
1588    pub fn subscribe(
1589        &mut self,
1590        track_namespace: TrackNamespace,
1591        track_name: Vec<u8>,
1592        subscriber_priority: u8,
1593        group_order: GroupOrder,
1594        filter_type: VarInt,
1595        parameters: Vec<KeyValuePair>,
1596    ) -> Result<(VarInt, ControlMessage), EndpointError> {
1597        if matches!(filter_type.into_inner(), 3 | 4) {
1598            return Err(EndpointError::FilterNeedsRange);
1599        }
1600        self.subscribe_inner(
1601            track_namespace,
1602            track_name,
1603            subscriber_priority,
1604            group_order,
1605            filter_type,
1606            None,
1607            None,
1608            parameters,
1609        )
1610    }
1611
1612    /// Send a SUBSCRIBE for a range of the track, starting at a given
1613    /// location.
1614    ///
1615    /// The Filter Type is derived from the arguments rather than taken beside
1616    /// them, so the message cannot name a filter whose fields it does not
1617    /// carry.
1618    #[allow(clippy::too_many_arguments)]
1619    pub fn subscribe_range(
1620        &mut self,
1621        track_namespace: TrackNamespace,
1622        track_name: Vec<u8>,
1623        subscriber_priority: u8,
1624        group_order: GroupOrder,
1625        start_location: Location,
1626        end_group: Option<VarInt>,
1627        parameters: Vec<KeyValuePair>,
1628    ) -> Result<(VarInt, ControlMessage), EndpointError> {
1629        // 0x3 is AbsoluteStart and 0x4 is AbsoluteRange. The Filter Type is a
1630        // bare varint on this draft, so there is no enum to name them by.
1631        let filter_type = VarInt::from_u64(if end_group.is_some() { 4 } else { 3 }).unwrap();
1632        self.subscribe_inner(
1633            track_namespace,
1634            track_name,
1635            subscriber_priority,
1636            group_order,
1637            filter_type,
1638            Some(start_location),
1639            end_group,
1640            parameters,
1641        )
1642    }
1643
1644    #[allow(clippy::too_many_arguments)]
1645    fn subscribe_inner(
1646        &mut self,
1647        track_namespace: TrackNamespace,
1648        track_name: Vec<u8>,
1649        subscriber_priority: u8,
1650        group_order: GroupOrder,
1651        filter_type: VarInt,
1652        start_location: Option<Location>,
1653        end_group: Option<VarInt>,
1654        parameters: Vec<KeyValuePair>,
1655    ) -> Result<(VarInt, ControlMessage), EndpointError> {
1656        self.require_active_or_err()?;
1657        let req_id = self.request_ids.allocate()?;
1658
1659        let mut sm = SubscriptionStateMachine::new();
1660        sm.on_subscribe_sent()?;
1661        self.subscriptions.insert(req_id.into_inner(), Mutex::new(sm));
1662        // The track is recorded here because this is the only place it is
1663        // known: a SUBSCRIBE_OK names an alias and not the track it is for.
1664        self.track_bindings.insert(
1665            req_id.into_inner(),
1666            TrackBinding {
1667                namespace: track_namespace.clone(),
1668                name: track_name.clone(),
1669                alias: None,
1670                kind: BindingKind::Subscribe,
1671            },
1672        );
1673
1674        let (start_group, start_object) = match start_location {
1675            Some(start) => (Some(start.group), Some(start.object)),
1676            None => (None, None),
1677        };
1678        let msg = ControlMessage::Subscribe(Subscribe {
1679            request_id: req_id,
1680            track_namespace,
1681            track_name,
1682            subscriber_priority,
1683            group_order,
1684            forward: Forward::Forward,
1685            filter_type,
1686            start_group,
1687            start_object,
1688            end_group,
1689            parameters,
1690        });
1691        Ok((req_id, msg))
1692    }
1693
1694    /// Process an incoming SUBSCRIBE_OK.
1695    ///
1696    /// Draft-12 carries the publisher-assigned `track_alias` on SUBSCRIBE_OK,
1697    /// which is recorded here so callers can retrieve it via
1698    /// [`Endpoint::track_alias_for`].
1699    pub fn receive_subscribe_ok(&mut self, msg: &SubscribeOk) -> Result<(), EndpointError> {
1700        let id = msg.request_id.into_inner();
1701        if !self.subscriptions.contains_key(&id) {
1702            return Err(EndpointError::UnknownRequest(id));
1703        }
1704        let alias = msg.track_alias.into_inner();
1705        // Judged before the transition, so that the subscription this message
1706        // is about is not yet live and cannot be found as its own conflict,
1707        // and so that a refused SUBSCRIBE_OK leaves no alias behind.
1708        if let Some(conflict) = self.conflicting_alias_for_subscribe_ok(id, alias) {
1709            return Err(self.fail_session(conflict));
1710        }
1711        let mut sm = self.subscription(id).expect("checked above");
1712        sm.on_subscribe_ok()?;
1713        drop(sm);
1714        if let Some(binding) = self.track_bindings.get_mut(&id) {
1715            binding.alias = Some(alias);
1716        }
1717        Ok(())
1718    }
1719
1720    /// Process an incoming SUBSCRIBE_ERROR.
1721    pub fn receive_subscribe_error(&mut self, msg: &SubscribeError) -> Result<(), EndpointError> {
1722        let id = msg.request_id.into_inner();
1723        let mut sm = self.subscription(id).ok_or(EndpointError::UnknownRequest(id))?;
1724        sm.on_subscribe_error()?;
1725        Ok(())
1726    }
1727
1728    /// Send an UNSUBSCRIBE message for an active subscription.
1729    ///
1730    /// Section 4.1 gives the subscriber this for a subscription that came
1731    /// either way round, so a request the peer opened with PUBLISH ends here
1732    /// too. The two kinds cannot collide: a Request ID belongs to whichever
1733    /// endpoint allocated it, and the two halves of the space have opposite
1734    /// least significant bits.
1735    pub fn unsubscribe(&mut self, request_id: VarInt) -> Result<ControlMessage, EndpointError> {
1736        let id = request_id.into_inner();
1737        if let Some(mut sm) = self.subscription(id) {
1738            sm.on_unsubscribe()?;
1739            return Ok(ControlMessage::Unsubscribe(Unsubscribe { request_id }));
1740        }
1741        let entry = self.inbound_publishes.get(&id).ok_or(EndpointError::UnknownRequest(id))?;
1742        entry.flow().on_unsubscribe_sent()?;
1743        Ok(ControlMessage::Unsubscribe(Unsubscribe { request_id }))
1744    }
1745
1746    /// Send a SUBSCRIBE_UPDATE narrowing a subscription this endpoint opened.
1747    ///
1748    /// Section 8.10 gives the message to the subscriber, which is what this
1749    /// endpoint is for every subscription in `subscriptions`. No identifier is
1750    /// spent: this draft's SUBSCRIBE_UPDATE has one Request ID field and it
1751    /// names the subscription being modified, which is why Section 8.1 leaves
1752    /// the message out of the list that steps the peer's sequence.
1753    ///
1754    /// The narrowing rules the same section states are the caller's to keep.
1755    ///
1756    /// # Errors
1757    ///
1758    /// [`EndpointError::UnknownRequest`] when this endpoint opened no
1759    /// subscription under that identifier, and the subscription flow's own
1760    /// `InvalidTransition` when the one it names has already ended.
1761    #[allow(clippy::too_many_arguments)]
1762    pub fn subscribe_update(
1763        &mut self,
1764        request_id: VarInt,
1765        start_group: VarInt,
1766        start_object: VarInt,
1767        end_group: VarInt,
1768        subscriber_priority: u8,
1769        forward: Forward,
1770        parameters: Vec<KeyValuePair>,
1771    ) -> Result<ControlMessage, EndpointError> {
1772        self.require_active_or_err()?;
1773        let id = request_id.into_inner();
1774        let mut sm = self.subscription(id).ok_or(EndpointError::UnknownRequest(id))?;
1775        sm.on_subscribe_update()?;
1776        drop(sm);
1777        Ok(ControlMessage::SubscribeUpdate(SubscribeUpdate {
1778            request_id,
1779            start_group,
1780            start_object,
1781            end_group,
1782            subscriber_priority,
1783            forward,
1784            parameters,
1785        }))
1786    }
1787
1788    /// Process an incoming SUBSCRIBE_UPDATE.
1789    ///
1790    /// Section 8.10: "A subscriber sends a SUBSCRIBE_UPDATE to a publisher to
1791    /// modify an existing subscription." One that arrives is therefore about a
1792    /// subscription the **peer** opened, which is why it is looked for among
1793    /// those and not among this endpoint's own.
1794    ///
1795    /// # Errors
1796    ///
1797    /// [`EndpointError::UpdateForUnknownRequest`] when the Request ID names no
1798    /// subscription the peer has opened in this session, with the session
1799    /// already moved to Closed, and the subscription flow's own
1800    /// `InvalidTransition` when it names one that has already ended.
1801    pub fn receive_subscribe_update(&mut self, msg: &SubscribeUpdate) -> Result<(), EndpointError> {
1802        let id = msg.request_id.into_inner();
1803        if let Some(sub) = self.inbound_subscribes.get_mut(&id) {
1804            sub.state.on_subscribe_update_received()?;
1805            return Ok(());
1806        }
1807        // The close is over an identifier "that has not existed within the
1808        // Session", which is wider than *no subscription the peer opened*:
1809        // every request this session has carried has existed. Only a
1810        // subscription the peer opened has a transition for an update, so for
1811        // the rest the message is accepted and the state left alone - which is
1812        // also what a PUBLISH-established subscription needs: Section 4.1
1813        // says that once PUBLISH and PUBLISH_OK succeed "the subscription can
1814        // be updated by the subscriber using SUBSCRIBE_UPDATE". That holds
1815        // whichever side published: the peer is the subscriber of every
1816        // PUBLISH this endpoint made, so an update naming one of those is the
1817        // subscriber's to send.
1818        let existed = self.subscriptions.contains_key(&id)
1819            || self.inbound_publishes.contains_key(&id)
1820            || self.publishes.contains_key(&id)
1821            || self.fetches.contains_key(&id)
1822            || self.subscribe_announces.contains_key(&id)
1823            || self.announces.contains_key(&id)
1824            || self.track_statuses.contains_key(&id);
1825        if existed {
1826            return Ok(());
1827        }
1828        Err(self.fail_session(EndpointError::UpdateForUnknownRequest(id)))
1829    }
1830
1831    /// Process an incoming SUBSCRIBE_DONE (subscriber side - publisher
1832    /// finished).
1833    ///
1834    /// Section 4.1 gives the publisher this for a subscription that came
1835    /// either way round, so one the peer opened with PUBLISH ends here too.
1836    pub fn receive_subscribe_done(&mut self, msg: &SubscribeDone) -> Result<(), EndpointError> {
1837        let id = msg.request_id.into_inner();
1838        if let Some(mut sm) = self.subscription(id) {
1839            sm.on_subscribe_done()?;
1840            return Ok(());
1841        }
1842        let entry = self.inbound_publishes.get(&id).ok_or(EndpointError::UnknownRequest(id))?;
1843        entry.flow().on_subscribe_done_received()?;
1844        Ok(())
1845    }
1846
1847    // ── Answering a SUBSCRIBE the peer sent ────────────────────
1848
1849    /// Process an incoming SUBSCRIBE, recording the subscription it opens.
1850    ///
1851    /// The Request ID has already been checked by [`Self::receive_request`],
1852    /// which every request message passes through before its own handler.
1853    /// There is no Track Alias to judge here: Section 8.8 carries it in the
1854    /// SUBSCRIBE_OK, so this endpoint chooses it, and it is judged when the
1855    /// answer is built.
1856    ///
1857    /// # Errors
1858    ///
1859    /// The session error when the session is not established, and the
1860    /// subscription flow's own `InvalidTransition` for a second SUBSCRIBE
1861    /// under a Request ID already carrying one.
1862    pub fn receive_subscribe(&mut self, msg: &Subscribe) -> Result<(), EndpointError> {
1863        self.require_active_or_err()?;
1864        let id = msg.request_id.into_inner();
1865        let mut state = SubscriptionStateMachine::new();
1866        state.on_subscribe_received()?;
1867        self.inbound_subscribes.insert(id, InboundSubscribe { message: msg.clone(), state });
1868        Ok(())
1869    }
1870
1871    /// The SUBSCRIBE the peer sent under `request_id` and this endpoint has
1872    /// not answered yet.
1873    ///
1874    /// `None` once it has been answered, and for an identifier this session
1875    /// has no inbound subscription for. The record itself lives on for as long
1876    /// as the session does, which is what lets an update say whether the
1877    /// request it names has ever existed.
1878    pub fn pending_subscribe(&self, request_id: VarInt) -> Option<&Subscribe> {
1879        self.inbound_subscribes
1880            .get(&request_id.into_inner())
1881            .filter(|s| s.state.state() == SubscriptionState::Subscribing)
1882            .map(|s| &s.message)
1883    }
1884
1885    /// How many SUBSCRIBEs the peer has sent that are still waiting for an
1886    /// answer.
1887    pub fn pending_subscribe_count(&self) -> usize {
1888        self.inbound_subscribes
1889            .values()
1890            .filter(|s| s.state.state() == SubscriptionState::Subscribing)
1891            .count()
1892    }
1893
1894    /// Build the SUBSCRIBE_OK accepting a subscription the peer opened, giving
1895    /// its track a Track Alias.
1896    ///
1897    /// # Errors
1898    ///
1899    /// [`EndpointError::UnknownRequest`] if the peer opened no subscription
1900    /// under that identifier, [`EndpointError::TrackAliasInUse`] if a live
1901    /// track of this endpoint's already holds the alias, and
1902    /// [`EndpointError::Subscription`] if the request has already been
1903    /// answered.
1904    pub fn send_subscribe_ok(
1905        &mut self,
1906        request_id: VarInt,
1907        track_alias: VarInt,
1908        expires: VarInt,
1909        group_order: GroupOrder,
1910        parameters: Vec<KeyValuePair>,
1911    ) -> Result<ControlMessage, EndpointError> {
1912        let id = request_id.into_inner();
1913        let alias = track_alias.into_inner();
1914        let sub = self.inbound_subscribes.get(&id).ok_or(EndpointError::UnknownRequest(id))?;
1915        let namespace = sub.message.track_namespace.clone();
1916        let name = sub.message.track_name.clone();
1917        if let Some(refusal) = self.alias_held_elsewhere(alias, &namespace, &name) {
1918            return Err(refusal);
1919        }
1920        let sub = self.inbound_subscribes.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
1921        sub.state.on_subscribe_ok_sent()?;
1922        self.track_bindings.insert(
1923            id,
1924            TrackBinding { namespace, name, alias: Some(alias), kind: BindingKind::PeerSubscribe },
1925        );
1926        Ok(ControlMessage::SubscribeOk(SubscribeOk {
1927            request_id,
1928            track_alias,
1929            expires,
1930            group_order,
1931            content_exists: ContentExists::NoLargestLocation,
1932            largest_location: None,
1933            parameters,
1934        }))
1935    }
1936
1937    /// Build the SUBSCRIBE_ERROR rejecting a subscription the peer opened.
1938    ///
1939    /// No Track Alias goes back with it. This draft moved the field out of
1940    /// SUBSCRIBE_ERROR along with the retry the earlier ones offered, so a
1941    /// refusal here says only that the request failed.
1942    ///
1943    /// # Errors
1944    ///
1945    /// [`EndpointError::UnknownRequest`] if the peer opened no subscription
1946    /// under that identifier, and [`EndpointError::Subscription`] if it has
1947    /// already been answered.
1948    pub fn send_subscribe_error(
1949        &mut self,
1950        request_id: VarInt,
1951        error_code: VarInt,
1952        reason_phrase: Vec<u8>,
1953    ) -> Result<ControlMessage, EndpointError> {
1954        let id = request_id.into_inner();
1955        let sub = self.inbound_subscribes.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
1956        sub.state.on_subscribe_error_sent()?;
1957        Ok(ControlMessage::SubscribeError(SubscribeError { request_id, error_code, reason_phrase }))
1958    }
1959
1960    /// Build the SUBSCRIBE_DONE ending a subscription this endpoint publishes,
1961    /// whichever of the two sequences opened it.
1962    ///
1963    /// # Errors
1964    ///
1965    /// [`EndpointError::UnknownRequest`] if this session is carrying no such
1966    /// subscription under that identifier, [`EndpointError::Subscription`] if
1967    /// it is one the peer opened and this endpoint has not accepted or has
1968    /// already ended, and [`EndpointError::PublishFlow`] for the same of one
1969    /// this endpoint opened.
1970    pub fn send_subscribe_done(
1971        &mut self,
1972        request_id: VarInt,
1973        status_code: VarInt,
1974        stream_count: VarInt,
1975        reason_phrase: Vec<u8>,
1976    ) -> Result<ControlMessage, EndpointError> {
1977        let id = request_id.into_inner();
1978        // Section 4.1 gives this message to the publisher for a subscription
1979        // that came either way round - "Once either of these sequences is
1980        // successful, the subscription can be ... terminated by the publisher
1981        // using SUBSCRIBE_DONE" - so one this endpoint opened with PUBLISH
1982        // ends here too. Two records, one message.
1983        if let Some(state) = self.publishes.get_mut(&id) {
1984            state.on_subscribe_done_sent()?;
1985            return Ok(ControlMessage::SubscribeDone(SubscribeDone {
1986                request_id,
1987                status_code,
1988                stream_count,
1989                reason_phrase,
1990            }));
1991        }
1992        let sub = self.inbound_subscribes.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
1993        sub.state.on_subscribe_done_sent()?;
1994        Ok(ControlMessage::SubscribeDone(SubscribeDone {
1995            request_id,
1996            status_code,
1997            stream_count,
1998            reason_phrase,
1999        }))
2000    }
2001
2002    /// Process an incoming UNSUBSCRIBE, ending a subscription this endpoint
2003    /// publishes and freeing the Track Alias it held.
2004    ///
2005    /// Section 8.11: "A subscriber issues a UNSUBSCRIBE message to a publisher
2006    /// indicating it is no longer interested in receiving media for the
2007    /// specified track and requesting that the publisher stop sending Objects
2008    /// as soon as possible." The message travels from subscriber to publisher,
2009    /// so what it can end is whatever this endpoint publishes - and Section
2010    /// 4.1 gives that two sources, not one: "A subscription can be initiated
2011    /// by either a publisher or a subscriber."
2012    ///
2013    /// # Errors
2014    ///
2015    /// [`EndpointError::UnknownRequest`] if this endpoint publishes no
2016    /// subscription under that identifier, [`EndpointError::Subscription`] if
2017    /// it is one the peer opened that this endpoint never accepted or has
2018    /// already ended, and [`EndpointError::PublishFlow`] for the same of one
2019    /// this endpoint opened.
2020    pub fn receive_unsubscribe(&mut self, msg: &Unsubscribe) -> Result<(), EndpointError> {
2021        let id = msg.request_id.into_inner();
2022        // The offers this endpoint made itself. A PUBLISH the *peer* made is
2023        // not here and must not be: this endpoint is the subscriber of that
2024        // one, so an UNSUBSCRIBE naming it would be the publisher ending a
2025        // subscription the sentence above gives the subscriber, and the miss
2026        // below is the right answer for it.
2027        if let Some(state) = self.publishes.get_mut(&id) {
2028            state.on_unsubscribe_received()?;
2029            return Ok(());
2030        }
2031        let sub = self.inbound_subscribes.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2032        sub.state.on_unsubscribe_received()?;
2033        Ok(())
2034    }
2035
2036    // ── Fetch flow ─────────────────────────────────────────────
2037
2038    /// Send a standalone FETCH message. Allocates a request ID and creates a
2039    /// fetch state machine.
2040    #[allow(clippy::too_many_arguments)]
2041    pub fn fetch(
2042        &mut self,
2043        track_namespace: TrackNamespace,
2044        track_name: Vec<u8>,
2045        subscriber_priority: u8,
2046        group_order: GroupOrder,
2047        start_group: VarInt,
2048        start_object: VarInt,
2049        end_group: VarInt,
2050        end_object: VarInt,
2051        parameters: Vec<KeyValuePair>,
2052    ) -> Result<(VarInt, ControlMessage), EndpointError> {
2053        self.require_active_or_err()?;
2054        let req_id = self.request_ids.allocate()?;
2055
2056        let mut sm = FetchStateMachine::new();
2057        sm.on_fetch_sent()?;
2058        self.fetches.insert(req_id.into_inner(), sm);
2059        // Recorded before the two of them are moved into the message.
2060        self.fetch_tracks.insert(
2061            req_id.into_inner(),
2062            FetchTrack { namespace: track_namespace.clone(), name: track_name.clone() },
2063        );
2064
2065        let msg = ControlMessage::Fetch(Fetch {
2066            request_id: req_id,
2067            subscriber_priority,
2068            group_order,
2069            fetch_type: FetchType::Standalone,
2070            fetch_payload: FetchPayload::Standalone {
2071                track_namespace,
2072                track_name,
2073                start_group,
2074                start_object,
2075                end_group,
2076                end_object,
2077            },
2078            parameters,
2079        });
2080        Ok((req_id, msg))
2081    }
2082
2083    /// Send a Relative Joining Fetch (Fetch Type 0x2), attaching a fetch to a
2084    /// subscription this session already holds. Allocates a Request ID.
2085    ///
2086    /// Section 8.16 calls it "A Fetch joined together with a Subscribe by
2087    /// specifying the Request ID of an active subscription and a relative
2088    /// starting offset", and has "A publisher receiving a Joining Fetch uses
2089    /// properties of the associated Subscribe to determine the Track
2090    /// Namespace, Track, Start Location, and End Location such that it is
2091    /// contiguous with the associated Subscribe." So a joining fetch names
2092    /// neither the namespace nor the name, and `joining_start` is read against
2093    /// the subscription rather than against the track: Section 8.16.1 has the
2094    /// publisher set "Fetch Start Location: {Subscribe Largest
2095    /// Location.Group - Joining Start, 0}", which makes it a count of groups
2096    /// back from the live edge.
2097    ///
2098    /// # Errors
2099    ///
2100    /// The session error when the session is not established, and the
2101    /// request-id error when this endpoint has no identifier left to spend. A
2102    /// Request ID naming no subscription is not refused here — Section 8.16
2103    /// answers that at the publisher, "it MUST respond with a Fetch Error with
2104    /// code Invalid Joining Request ID", and this endpoint is the subscriber.
2105    /// The code is 0x7 whichever of its two names is read: the FETCH_ERROR
2106    /// table in Section 8.18 lists it under the name above and then describes
2107    /// it one line down as Invalid Joining Subscribe ID.
2108    pub fn joining_fetch(
2109        &mut self,
2110        subscriber_priority: u8,
2111        group_order: GroupOrder,
2112        joining_request_id: VarInt,
2113        joining_start: VarInt,
2114        parameters: Vec<KeyValuePair>,
2115    ) -> Result<(VarInt, ControlMessage), EndpointError> {
2116        self.joining_fetch_of_type(
2117            FetchType::RelativeJoining,
2118            subscriber_priority,
2119            group_order,
2120            joining_request_id,
2121            joining_start,
2122            parameters,
2123        )
2124    }
2125
2126    /// Send an Absolute Joining Fetch (Fetch Type 0x3).
2127    ///
2128    /// Section 8.16.2 is the whole of the difference: "Identical to the
2129    /// Relative Joining fetch except that Fetch Start Location.Group is the
2130    /// Joining Start value." So `joining_start` is the group to begin at
2131    /// rather than a count of groups back, which is what an application that
2132    /// knows the group it wants actually has. Asking for the same range
2133    /// relatively would need the Largest Location, and a subscriber that has
2134    /// not yet been told one cannot compute the offset.
2135    ///
2136    /// # Errors
2137    ///
2138    /// As [`Endpoint::joining_fetch`].
2139    pub fn absolute_joining_fetch(
2140        &mut self,
2141        subscriber_priority: u8,
2142        group_order: GroupOrder,
2143        joining_request_id: VarInt,
2144        joining_start: VarInt,
2145        parameters: Vec<KeyValuePair>,
2146    ) -> Result<(VarInt, ControlMessage), EndpointError> {
2147        self.joining_fetch_of_type(
2148            FetchType::AbsoluteJoining,
2149            subscriber_priority,
2150            group_order,
2151            joining_request_id,
2152            joining_start,
2153            parameters,
2154        )
2155    }
2156
2157    /// What both calls above are, and why neither of them takes the type.
2158    ///
2159    /// Section 8.16 admits three Fetch Types and this payload carries two of
2160    /// them. A type taken as an argument here would leave a third value a
2161    /// caller could pass and this call would have to answer for; naming the
2162    /// two rules it out instead, so there is no answer left to get wrong.
2163    fn joining_fetch_of_type(
2164        &mut self,
2165        fetch_type: FetchType,
2166        subscriber_priority: u8,
2167        group_order: GroupOrder,
2168        joining_request_id: VarInt,
2169        joining_start: VarInt,
2170        parameters: Vec<KeyValuePair>,
2171    ) -> Result<(VarInt, ControlMessage), EndpointError> {
2172        self.require_active_or_err()?;
2173        let req_id = self.request_ids.allocate()?;
2174
2175        let mut sm = FetchStateMachine::new();
2176        sm.on_fetch_sent()?;
2177        self.fetches.insert(req_id.into_inner(), sm);
2178        // draft-12 Section 8.16: "A publisher receiving a Joining Fetch uses
2179        // properties of the associated Subscribe to determine the Track
2180        // Namespace, Track". A Request ID this session holds no track for
2181        // leaves the fetch out of the record.
2182        if let Some(binding) = self.track_bindings.get(&joining_request_id.into_inner()) {
2183            let track =
2184                FetchTrack { namespace: binding.namespace.clone(), name: binding.name.clone() };
2185            self.fetch_tracks.insert(req_id.into_inner(), track);
2186        }
2187
2188        let msg = ControlMessage::Fetch(Fetch {
2189            request_id: req_id,
2190            subscriber_priority,
2191            group_order,
2192            fetch_type,
2193            fetch_payload: FetchPayload::Joining { joining_request_id, joining_start },
2194            parameters,
2195        });
2196        Ok((req_id, msg))
2197    }
2198
2199    /// Process an incoming FETCH_OK.
2200    pub fn receive_fetch_ok(&mut self, msg: &message::FetchOk) -> Result<(), EndpointError> {
2201        let id = msg.request_id.into_inner();
2202        let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2203        sm.on_fetch_ok()?;
2204        Ok(())
2205    }
2206
2207    /// Process an incoming FETCH_ERROR.
2208    pub fn receive_fetch_error(&mut self, msg: &message::FetchError) -> Result<(), EndpointError> {
2209        let id = msg.request_id.into_inner();
2210        let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2211        sm.on_fetch_error()?;
2212        Ok(())
2213    }
2214
2215    /// Send a FETCH_CANCEL message.
2216    pub fn fetch_cancel(&mut self, request_id: VarInt) -> Result<ControlMessage, EndpointError> {
2217        let id = request_id.into_inner();
2218        let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2219        sm.on_fetch_cancel()?;
2220        Ok(ControlMessage::FetchCancel(FetchCancel { request_id }))
2221    }
2222
2223    /// Notify that a fetch data stream received FIN.
2224    ///
2225    /// It may arrive before the FETCH_OK or FETCH_ERROR answering the
2226    /// request, which leaves the fetch in `FetchState::Unanswered` until the
2227    /// answer lands.
2228    pub fn on_fetch_stream_fin(&mut self, request_id: VarInt) -> Result<(), EndpointError> {
2229        let id = request_id.into_inner();
2230        let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2231        sm.on_stream_fin()?;
2232        Ok(())
2233    }
2234
2235    /// Notify that a fetch data stream was reset.
2236    ///
2237    /// As with a FIN, it may arrive before the answer to the request.
2238    pub fn on_fetch_stream_reset(&mut self, request_id: VarInt) -> Result<(), EndpointError> {
2239        let id = request_id.into_inner();
2240        let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2241        sm.on_stream_reset()?;
2242        Ok(())
2243    }
2244
2245    // ── Answering a FETCH the peer sent ────────────────────────
2246
2247    /// Process an incoming FETCH, recording the fetch it opens.
2248    ///
2249    /// The Request ID has already been checked by [`Self::receive_request`],
2250    /// which every request message passes through before its own handler.
2251    ///
2252    /// A Joining Fetch is recorded like any other. Section 8.16 answers one
2253    /// naming a subscription this session cannot join with a refusal and not
2254    /// a session close, and a refusal is a message this endpoint has to
2255    /// build, so the request it refuses has to be on record first.
2256    ///
2257    /// # Errors
2258    ///
2259    /// The session error when the session is not established, and the fetch
2260    /// flow's own `InvalidTransition` for a second FETCH under an identifier
2261    /// already carrying one.
2262    pub fn receive_fetch(&mut self, msg: &Fetch) -> Result<(), EndpointError> {
2263        self.require_active_or_err()?;
2264        let id = msg.request_id.into_inner();
2265        let unjoinable = self.joining_subscription_missing(msg);
2266        let mut state = FetchStateMachine::new();
2267        state.on_fetch_received()?;
2268        self.inbound_fetches.insert(id, InboundFetch { message: msg.clone(), state, unjoinable });
2269        Ok(())
2270    }
2271
2272    /// The FETCH the peer sent under `request_id` and this endpoint has not
2273    /// answered yet.
2274    ///
2275    /// `None` once it has been answered, and for an identifier this session
2276    /// has no inbound fetch for. The record itself lives on past the answer,
2277    /// because the fetch is not over until its data stream is.
2278    pub fn pending_fetch(&self, request_id: VarInt) -> Option<&Fetch> {
2279        self.inbound_fetches
2280            .get(&request_id.into_inner())
2281            .filter(|f| matches!(f.state.state(), FetchState::Pending | FetchState::Unanswered))
2282            .map(|f| &f.message)
2283    }
2284
2285    /// How many FETCHes the peer has sent that are still waiting for an
2286    /// answer.
2287    pub fn pending_fetch_count(&self) -> usize {
2288        self.inbound_fetches
2289            .values()
2290            .filter(|f| matches!(f.state.state(), FetchState::Pending | FetchState::Unanswered))
2291            .count()
2292    }
2293
2294    /// The identifier an arriving Joining Fetch names, when this session has
2295    /// no subscription it may join.
2296    ///
2297    /// Section 8.16:
2298    /// "If a publisher receives a Joining Fetch with a Request ID that
2299    /// does not correspond to an existing Subscribe in the same session, it
2300    /// MUST respond with a Fetch Error with code Invalid Joining Request ID."
2301    ///
2302    /// The verdict is taken as the FETCH arrives, because that is the moment
2303    /// the sentence names, and it is kept. A subscription that ends between
2304    /// the FETCH and its answer does not turn a fetch that could be joined
2305    /// into one that could not.
2306    ///
2307    /// A standalone fetch names none and answers `None`, and so does a joining
2308    /// one whose subscription is live. The subscription is one the peer opened,
2309    /// because the peer is the end that fetches and this endpoint is the one
2310    /// answering.
2311    /// "Existing" is read as "has not ended". Draft-16 Section 9.16.2 states
2312    /// the same rule with the states named - "in the Established or Pending
2313    /// (subscriber) states" - which is the same set read the same way.
2314    fn joining_subscription_missing(&self, msg: &Fetch) -> Option<u64> {
2315        let message::FetchPayload::Joining { joining_request_id: joined, .. } = &msg.fetch_payload
2316        else {
2317            return None;
2318        };
2319        let joined = joined.into_inner();
2320        let live = self
2321            .inbound_subscribes
2322            .get(&joined)
2323            .is_some_and(|s| s.state.state() != SubscriptionState::Done);
2324        if live {
2325            None
2326        } else {
2327            Some(joined)
2328        }
2329    }
2330
2331    /// Build the FETCH_OK accepting a fetch the peer opened.
2332    ///
2333    /// # Errors
2334    ///
2335    /// [`EndpointError::UnknownRequest`] if the peer opened no fetch under
2336    /// that identifier, [`EndpointError::UnjoinableSubscription`] for a
2337    /// Joining Fetch naming a subscription this session cannot join, and the
2338    /// fetch flow's own `InvalidTransition` for a second answer: Section 4.1
2339    /// says the publisher "MUST send exactly one FETCH_OK or FETCH_ERROR in
2340    /// response to a FETCH".
2341    pub fn send_fetch_ok(
2342        &mut self,
2343        request_id: VarInt,
2344        group_order: GroupOrder,
2345        end_of_track: u8,
2346        end_location: Location,
2347        parameters: Vec<KeyValuePair>,
2348    ) -> Result<ControlMessage, EndpointError> {
2349        let id = request_id.into_inner();
2350        let unjoinable =
2351            self.inbound_fetches.get(&id).ok_or(EndpointError::UnknownRequest(id))?.unjoinable;
2352        if let Some(joining) = unjoinable {
2353            return Err(EndpointError::UnjoinableSubscription { fetch: id, joining });
2354        }
2355        let fetch = self.inbound_fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2356        fetch.state.on_fetch_ok_sent()?;
2357        Ok(ControlMessage::FetchOk(message::FetchOk {
2358            request_id,
2359            group_order,
2360            end_of_track,
2361            end_location,
2362            parameters,
2363        }))
2364    }
2365
2366    /// Build the FETCH_ERROR refusing a fetch the peer opened.
2367    ///
2368    /// Section 8.16 names the code a Joining Fetch naming an unjoinable
2369    /// subscription is refused with, so that refusal cannot go out under any
2370    /// other: a subscriber told the wrong reason retries the wrong thing.
2371    ///
2372    /// # Errors
2373    ///
2374    /// [`EndpointError::UnknownRequest`] if the peer opened no fetch under
2375    /// that identifier, and the fetch flow's own `InvalidTransition` if it has
2376    /// already been answered.
2377    pub fn send_fetch_error(
2378        &mut self,
2379        request_id: VarInt,
2380        error_code: VarInt,
2381        reason_phrase: Vec<u8>,
2382    ) -> Result<ControlMessage, EndpointError> {
2383        let id = request_id.into_inner();
2384        let fetch = self.inbound_fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2385        if fetch.unjoinable.is_some() {
2386            let required = FetchErrorCode::InvalidJoiningRequestId as u64;
2387            if error_code.into_inner() != required {
2388                return Err(EndpointError::WrongJoiningRefusal { fetch: id, required });
2389            }
2390        }
2391        fetch.state.on_fetch_error_sent()?;
2392        Ok(ControlMessage::FetchError(message::FetchError {
2393            request_id,
2394            error_code,
2395            reason_phrase,
2396        }))
2397    }
2398
2399    /// Process an incoming FETCH_CANCEL, ending the fetch the peer opened.
2400    ///
2401    /// Section 8.19: the subscriber sends it to stop a fetch it no longer
2402    /// wants, so the record this endpoint serves the fetch from is the one it
2403    /// ends.
2404    ///
2405    /// # Errors
2406    ///
2407    /// [`EndpointError::UnknownRequest`] if the peer opened no fetch under
2408    /// that identifier, and the fetch flow's own `InvalidTransition` for a fetch
2409    /// that has already ended.
2410    pub fn receive_fetch_cancel(&mut self, msg: &FetchCancel) -> Result<(), EndpointError> {
2411        let id = msg.request_id.into_inner();
2412        let fetch = self.inbound_fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2413        fetch.state.on_fetch_cancel_received()?;
2414        Ok(())
2415    }
2416
2417    /// Note that this endpoint finished the data stream serving a fetch the
2418    /// peer opened.
2419    ///
2420    /// A fetch is over when its answer and its data stream have both settled,
2421    /// and this is the second of those for the end that serves it.
2422    ///
2423    /// # Errors
2424    ///
2425    /// [`EndpointError::UnknownRequest`] if the peer opened no fetch under
2426    /// that identifier, and the fetch flow's own `InvalidTransition` from a state
2427    /// the stream cannot close from.
2428    pub fn on_peer_fetch_stream_fin(&mut self, request_id: VarInt) -> Result<(), EndpointError> {
2429        let id = request_id.into_inner();
2430        let fetch = self.inbound_fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2431        fetch.state.on_stream_fin_sent()?;
2432        Ok(())
2433    }
2434
2435    // ── Subscribe Announces flow ───────────────────────────────
2436
2437    /// Send a SUBSCRIBE_ANNOUNCES message. Returns the allocated request ID
2438    /// alongside the control message so the caller can correlate replies.
2439    ///
2440    /// Section 8.27 addresses the first half of the overlap rule to this end
2441    /// of the session: "A subscriber cannot make overlapping namespace
2442    /// subscriptions on a single session." So a prefix overlapping one this
2443    /// endpoint has already asked about is refused here rather than built and
2444    /// sent for the peer to refuse.
2445    ///
2446    /// # Errors
2447    ///
2448    /// The session error when the session is not established,
2449    /// [`EndpointError::OwnPrefixOverlap`] when the prefix overlaps one this
2450    /// endpoint is already subscribed to, and the Request ID allocator's own
2451    /// error when the peer has granted no room for another request.
2452    pub fn subscribe_announces(
2453        &mut self,
2454        track_namespace_prefix: TrackNamespace,
2455        parameters: Vec<KeyValuePair>,
2456    ) -> Result<(VarInt, ControlMessage), EndpointError> {
2457        self.require_active_or_err()?;
2458        // The subscriber's half of the rule, refused before the message
2459        // exists. A publisher that follows this draft answers it with a
2460        // refusal, so building it spends a Request ID on a namespace
2461        // subscription that is not going to open.
2462        if let Some(established) = self.own_prefix_overlap(&track_namespace_prefix) {
2463            return Err(EndpointError::OwnPrefixOverlap { established });
2464        }
2465        let req_id = self.request_ids.allocate()?;
2466        let key = track_namespace_prefix.0.clone();
2467        let mut sm = SubscribeAnnouncesStateMachine::new();
2468        sm.on_subscribe_announces_sent()?;
2469        self.subscribe_announces.insert(req_id.into_inner(), sm);
2470        self.subscribe_announces_ids.insert(key, req_id.into_inner());
2471        Ok((
2472            req_id,
2473            ControlMessage::SubscribeAnnounces(SubscribeAnnounces {
2474                request_id: req_id,
2475                track_namespace_prefix,
2476                parameters,
2477            }),
2478        ))
2479    }
2480
2481    /// Process an incoming SUBSCRIBE_ANNOUNCES_OK.
2482    pub fn receive_subscribe_announces_ok(
2483        &mut self,
2484        msg: &SubscribeAnnouncesOk,
2485    ) -> Result<(), EndpointError> {
2486        let id = msg.request_id.into_inner();
2487        let sm = self.subscribe_announces.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2488        sm.on_subscribe_announces_ok()?;
2489        Ok(())
2490    }
2491
2492    /// Process an incoming SUBSCRIBE_ANNOUNCES_ERROR.
2493    pub fn receive_subscribe_announces_error(
2494        &mut self,
2495        msg: &SubscribeAnnouncesError,
2496    ) -> Result<(), EndpointError> {
2497        let id = msg.request_id.into_inner();
2498        let sm = self.subscribe_announces.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2499        sm.on_subscribe_announces_error()?;
2500        Ok(())
2501    }
2502
2503    /// Send an UNSUBSCRIBE_ANNOUNCES message.
2504    pub fn unsubscribe_announces(
2505        &mut self,
2506        track_namespace_prefix: TrackNamespace,
2507    ) -> Result<ControlMessage, EndpointError> {
2508        let id = *self
2509            .subscribe_announces_ids
2510            .get(&track_namespace_prefix.0)
2511            .ok_or(EndpointError::UnknownNamespace)?;
2512        let sm = self.subscribe_announces.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2513        sm.on_unsubscribe_announces()?;
2514        Ok(ControlMessage::UnsubscribeAnnounces(UnsubscribeAnnounces { track_namespace_prefix }))
2515    }
2516
2517    // ── Answering a SUBSCRIBE_ANNOUNCES the peer sent ──────────
2518
2519    /// Process an incoming SUBSCRIBE_ANNOUNCES, recording the namespace
2520    /// subscription it opens.
2521    ///
2522    /// Section 8.27: "The subscriber sends the SUBSCRIBE_ANNOUNCES control
2523    /// message to a publisher to request the current set of matching
2524    /// announcements and established subscriptions, as well as future updates
2525    /// to the set."
2526    ///
2527    /// The set it asks for is this endpoint's to decide, and deciding needs
2528    /// both the request and somewhere to answer from. The record holds the
2529    /// message and not only its state, because every message that answers
2530    /// this request or ends it names the prefix the request carried, and
2531    /// nothing else here has it.
2532    ///
2533    /// # Errors
2534    ///
2535    /// The session error when the session is not established.
2536    pub fn receive_subscribe_announces(
2537        &mut self,
2538        msg: &SubscribeAnnounces,
2539    ) -> Result<(), EndpointError> {
2540        self.require_active_or_err()?;
2541        let overlaps = self.peer_prefix_overlap(&msg.track_namespace_prefix);
2542        let mut state = SubscribeAnnouncesStateMachine::new();
2543        state.on_subscribe_announces_received()?;
2544        self.inbound_subscribe_announces.insert(
2545            msg.request_id.into_inner(),
2546            InboundSubscribeAnnounces { message: msg.clone(), state, overlaps },
2547        );
2548        Ok(())
2549    }
2550
2551    /// The earliest namespace subscription the peer has made whose prefix
2552    /// overlaps `prefix`, and `None` when there is none.
2553    ///
2554    /// Only ones that have not ended count: the sentence weighs the arriving
2555    /// prefix against "an active SUBSCRIBE_ANNOUNCES", so one the peer has
2556    /// withdrawn and one this endpoint refused are both past. Drafts 07
2557    /// through 11 say "an earlier" instead and count those too.
2558    ///
2559    /// One that has arrived and has not been answered does count. It is not
2560    /// active yet, but this endpoint is the one about to make it so, and
2561    /// accepting both would leave the session holding exactly the pair the
2562    /// sentence exists to prevent.
2563    ///
2564    /// Namespace subscriptions this endpoint made are a separate set and are
2565    /// not consulted. This endpoint is the subscriber for those, so a prefix
2566    /// it asked about says nothing about what the peer may ask about.
2567    ///
2568    /// The lowest Request ID wins when more than one overlaps, so the answer
2569    /// does not depend on the order a map happens to iterate in. Identifiers
2570    /// are handed out in increasing order, so the lowest of them is the
2571    /// earliest request.
2572    fn peer_prefix_overlap(&self, prefix: &TrackNamespace) -> Option<u64> {
2573        self.inbound_subscribe_announces
2574            .iter()
2575            .filter(|(_, s)| s.state.state() != SubscribeAnnouncesState::Done)
2576            .filter_map(|(&id, s)| {
2577                prefixes_overlap(&s.message.track_namespace_prefix.0, &prefix.0).then_some(id)
2578            })
2579            .min()
2580    }
2581
2582    /// The earliest namespace subscription **this endpoint** has made whose
2583    /// prefix overlaps `prefix`, and `None` when there is none.
2584    ///
2585    /// The subscriber's half of the same sentence reads over this endpoint's
2586    /// own requests, and takes the same view of which of them are past as
2587    /// [`Self::peer_prefix_overlap`] takes of the peer's.
2588    fn own_prefix_overlap(&self, prefix: &TrackNamespace) -> Option<u64> {
2589        self.subscribe_announces_ids
2590            .iter()
2591            .filter_map(|(key, &id)| prefixes_overlap(key, &prefix.0).then_some(id))
2592            .filter(|id| {
2593                self.subscribe_announces
2594                    .get(id)
2595                    .is_some_and(|sm| sm.state() != SubscribeAnnouncesState::Done)
2596            })
2597            .min()
2598    }
2599
2600    /// The SUBSCRIBE_ANNOUNCES the peer sent under `request_id` and this
2601    /// endpoint has not answered yet.
2602    ///
2603    /// `None` once it has been answered, and for an identifier the peer has
2604    /// subscribed to nothing under. The record itself lives on past the
2605    /// answer, because a namespace subscription that was accepted is not over
2606    /// until it is withdrawn.
2607    pub fn pending_subscribe_announces(&self, request_id: VarInt) -> Option<&SubscribeAnnounces> {
2608        self.inbound_subscribe_announces
2609            .get(&request_id.into_inner())
2610            .filter(|s| s.state.state() == SubscribeAnnouncesState::Pending)
2611            .map(|s| &s.message)
2612    }
2613
2614    /// How many namespace subscriptions the peer has made that are still
2615    /// waiting for an answer.
2616    pub fn pending_subscribe_announces_count(&self) -> usize {
2617        self.inbound_subscribe_announces
2618            .values()
2619            .filter(|s| s.state.state() == SubscribeAnnouncesState::Pending)
2620            .count()
2621    }
2622
2623    /// The identifier of a live namespace subscription the peer made for
2624    /// `prefix`.
2625    ///
2626    /// Section 8.30 names a Track Namespace Prefix where the
2627    /// SUBSCRIBE_ANNOUNCES it ends named a Request ID, so one record has to
2628    /// be reachable both ways. It is stored under the identifier, which is
2629    /// unique, and found by prefix with a scan of the same map. A second map
2630    /// from prefix to identifier would be quicker and could fall out of step
2631    /// with the first; there is nothing here for it to disagree with.
2632    ///
2633    /// One that has ended is skipped, so a prefix subscribed again after
2634    /// being withdrawn finds the live one.
2635    fn inbound_subscribe_announces_id(&self, prefix: &TrackNamespace) -> Option<u64> {
2636        self.inbound_subscribe_announces
2637            .iter()
2638            .find(|(_, s)| {
2639                s.message.track_namespace_prefix == *prefix
2640                    && s.state.state() != SubscribeAnnouncesState::Done
2641            })
2642            .map(|(id, _)| *id)
2643    }
2644
2645    /// Build the SUBSCRIBE_ANNOUNCES_OK accepting a namespace subscription
2646    /// the peer made.
2647    ///
2648    /// Section 5.1: "A publisher MUST send exactly one SUBSCRIBE_ANNOUNCES_OK
2649    /// or SUBSCRIBE_ANNOUNCES_ERROR in response to a SUBSCRIBE_ANNOUNCES."
2650    ///
2651    /// One answer and no second one: the flow moves on the first, and a
2652    /// second call finds a record that has left Pending.
2653    ///
2654    /// # Errors
2655    ///
2656    /// [`EndpointError::UnknownRequest`] if the peer has subscribed to
2657    /// nothing under that identifier, [`EndpointError::PeerPrefixOverlap`] if
2658    /// the prefix it asked about overlaps one the peer is already subscribed
2659    /// to, and the namespace flow's own `InvalidTransition` for a request
2660    /// already answered.
2661    pub fn send_subscribe_announces_ok(
2662        &mut self,
2663        request_id: VarInt,
2664    ) -> Result<ControlMessage, EndpointError> {
2665        let id = request_id.into_inner();
2666        let sub = self
2667            .inbound_subscribe_announces
2668            .get_mut(&id)
2669            .ok_or(EndpointError::UnknownRequest(id))?;
2670        // The MUST names one answer for this request, and it is not this one.
2671        if let Some(established) = sub.overlaps {
2672            return Err(EndpointError::PeerPrefixOverlap { request: id, established });
2673        }
2674        sub.state.on_subscribe_announces_ok_sent()?;
2675        Ok(ControlMessage::SubscribeAnnouncesOk(SubscribeAnnouncesOk { request_id }))
2676    }
2677
2678    /// Build the SUBSCRIBE_ANNOUNCES_ERROR refusing a namespace subscription
2679    /// the peer made.
2680    ///
2681    /// The other half of the same sentence: one message back, whichever of
2682    /// the two it is.
2683    ///
2684    /// # Errors
2685    ///
2686    /// [`EndpointError::UnknownRequest`] if the peer has subscribed to
2687    /// nothing under that identifier, [`EndpointError::WrongOverlapRefusal`]
2688    /// if the request overlaps another and the code named is not the one the
2689    /// draft assigns to that refusal, and the namespace flow's own
2690    /// `InvalidTransition` for a request already answered.
2691    pub fn send_subscribe_announces_error(
2692        &mut self,
2693        request_id: VarInt,
2694        error_code: VarInt,
2695        reason_phrase: Vec<u8>,
2696    ) -> Result<ControlMessage, EndpointError> {
2697        let id = request_id.into_inner();
2698        let sub = self
2699            .inbound_subscribe_announces
2700            .get_mut(&id)
2701            .ok_or(EndpointError::UnknownRequest(id))?;
2702        if sub.overlaps.is_some() {
2703            let required = SubscribeAnnouncesErrorCode::NamespacePrefixOverlap as u64;
2704            if error_code.into_inner() != required {
2705                return Err(EndpointError::WrongOverlapRefusal { request: id, required });
2706            }
2707        }
2708        sub.state.on_subscribe_announces_error_sent()?;
2709        Ok(ControlMessage::SubscribeAnnouncesError(SubscribeAnnouncesError {
2710            request_id,
2711            error_code,
2712            reason_phrase,
2713        }))
2714    }
2715
2716    /// Process an incoming UNSUBSCRIBE_ANNOUNCES, ending the namespace
2717    /// subscription the peer made.
2718    ///
2719    /// Section 5.1: "An UNSUBSCRIBE_ANNOUNCES withdraws a previous
2720    /// SUBSCRIBE_ANNOUNCES."
2721    ///
2722    /// The subscription it ends is the peer's, so the record it reads is the
2723    /// one this endpoint keeps of what the peer subscribed to. One this
2724    /// endpoint made is withdrawn by [`Endpoint::unsubscribe_announces`],
2725    /// which is the same message travelling the other way.
2726    ///
2727    /// # Errors
2728    ///
2729    /// [`EndpointError::UnknownPeerNamespaceSubscription`] if the peer has no
2730    /// live namespace subscription for that prefix, and the namespace flow's
2731    /// own `InvalidTransition` for one this endpoint never accepted.
2732    pub fn receive_unsubscribe_announces(
2733        &mut self,
2734        msg: &UnsubscribeAnnounces,
2735    ) -> Result<(), EndpointError> {
2736        let id = self
2737            .inbound_subscribe_announces_id(&msg.track_namespace_prefix)
2738            .ok_or(EndpointError::UnknownPeerNamespaceSubscription)?;
2739        let sub = self
2740            .inbound_subscribe_announces
2741            .get_mut(&id)
2742            .ok_or(EndpointError::UnknownRequest(id))?;
2743        sub.state.on_unsubscribe_announces_received()?;
2744        Ok(())
2745    }
2746
2747    // ── Announce flow ──────────────────────────────────────────
2748
2749    /// Send an ANNOUNCE message. Returns the allocated request ID alongside
2750    /// the control message.
2751    pub fn announce(
2752        &mut self,
2753        track_namespace: TrackNamespace,
2754        parameters: Vec<KeyValuePair>,
2755    ) -> Result<(VarInt, ControlMessage), EndpointError> {
2756        self.require_active_or_err()?;
2757        let req_id = self.request_ids.allocate()?;
2758        let key = track_namespace.0.clone();
2759        let mut sm = AnnounceStateMachine::new();
2760        sm.on_announce_sent()?;
2761        self.announces.insert(req_id.into_inner(), sm);
2762        self.announce_ids.insert(key, req_id.into_inner());
2763        Ok((
2764            req_id,
2765            ControlMessage::Announce(Announce { request_id: req_id, track_namespace, parameters }),
2766        ))
2767    }
2768
2769    /// Process an incoming ANNOUNCE_OK.
2770    pub fn receive_announce_ok(&mut self, msg: &AnnounceOk) -> Result<(), EndpointError> {
2771        let id = msg.request_id.into_inner();
2772        let sm = self.announces.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2773        sm.on_announce_ok()?;
2774        Ok(())
2775    }
2776
2777    /// Process an incoming ANNOUNCE_ERROR.
2778    pub fn receive_announce_error(&mut self, msg: &AnnounceError) -> Result<(), EndpointError> {
2779        let id = msg.request_id.into_inner();
2780        let sm = self.announces.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2781        sm.on_announce_error()?;
2782        Ok(())
2783    }
2784
2785    /// Process an incoming ANNOUNCE_CANCEL.
2786    pub fn receive_announce_cancel(&mut self, msg: &AnnounceCancel) -> Result<(), EndpointError> {
2787        let id = *self
2788            .announce_ids
2789            .get(&msg.track_namespace.0)
2790            .ok_or(EndpointError::UnknownNamespace)?;
2791        let sm = self.announces.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2792        // A cancellation that crossed this endpoint's own withdrawal: the
2793        // announcement is already over and the peer could not have known, so
2794        // there is nothing to end and nothing it did wrong.
2795        if sm.state() == AnnounceState::Done {
2796            return Ok(());
2797        }
2798        sm.on_announce_cancel()?;
2799        Ok(())
2800    }
2801
2802    /// Send an UNANNOUNCE message (publisher withdrawing).
2803    pub fn unannounce(
2804        &mut self,
2805        track_namespace: TrackNamespace,
2806    ) -> Result<ControlMessage, EndpointError> {
2807        let id =
2808            *self.announce_ids.get(&track_namespace.0).ok_or(EndpointError::UnknownNamespace)?;
2809        let sm = self.announces.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2810        sm.on_unannounce()?;
2811        Ok(ControlMessage::Unannounce(Unannounce { track_namespace }))
2812    }
2813
2814    // ── Answering an ANNOUNCE the peer sent ────────────────────
2815
2816    /// Process an incoming ANNOUNCE, recording the announcement it makes.
2817    ///
2818    /// Section 8.22: "The publisher sends the ANNOUNCE control message to
2819    /// advertise that it has tracks available within the announced Track
2820    /// Namespace. The receiver verifies the publisher is authorized to publish
2821    /// tracks under this namespace."
2822    ///
2823    /// Verifying is the application's to do, and it needs both the message to
2824    /// verify and somewhere to answer from. The Request ID has already been
2825    /// checked by [`Self::receive_request`], which every request message passes
2826    /// through before its own handler, so a repeat of one the peer has already
2827    /// spent never reaches here.
2828    ///
2829    /// # Errors
2830    ///
2831    /// The session error when the session is not established.
2832    pub fn receive_announce(&mut self, msg: &Announce) -> Result<(), EndpointError> {
2833        self.require_active_or_err()?;
2834        let id = msg.request_id.into_inner();
2835        let mut state = AnnounceStateMachine::new();
2836        state.on_announce_received()?;
2837        self.inbound_announces.insert(id, InboundAnnounce { message: msg.clone(), state });
2838        Ok(())
2839    }
2840
2841    /// The ANNOUNCE the peer sent under `request_id` and this endpoint has not
2842    /// answered yet.
2843    ///
2844    /// `None` once it has been answered, and for an identifier the peer has
2845    /// announced nothing under. The record itself lives on past the answer,
2846    /// because an announcement that was accepted is not over until it is
2847    /// withdrawn or cancelled.
2848    pub fn pending_announce(&self, request_id: VarInt) -> Option<&Announce> {
2849        self.inbound_announces
2850            .get(&request_id.into_inner())
2851            .filter(|a| a.state.state() == AnnounceState::Pending)
2852            .map(|a| &a.message)
2853    }
2854
2855    /// How many announcements the peer has made that are still waiting for an
2856    /// answer.
2857    pub fn pending_announce_count(&self) -> usize {
2858        self.inbound_announces
2859            .values()
2860            .filter(|a| a.state.state() == AnnounceState::Pending)
2861            .count()
2862    }
2863
2864    /// The identifier of a live announcement the peer made for `namespace`.
2865    ///
2866    /// Section 8.25: "The publisher sends the UNANNOUNCE control message to
2867    /// indicate its intent to stop serving new subscriptions for tracks within
2868    /// the provided Track Namespace." and Section 8.26 says what a cancellation
2869    /// is for: the subscriber "will stop sending new subscriptions for tracks
2870    /// within the provided Track Namespace". Both name a namespace where the
2871    /// ANNOUNCE they are about named a Request ID, so one record has to be
2872    /// reachable both ways.
2873    ///
2874    /// It is stored under the identifier, which is unique, and found by
2875    /// namespace with a scan of the same map. A second map from namespace to
2876    /// identifier would be quicker and could fall out of step with the first;
2877    /// there is nothing here for it to disagree with.
2878    ///
2879    /// An announcement that has ended is skipped, so a namespace announced
2880    /// again after being withdrawn finds the live one. Which of two live
2881    /// announcements for the same namespace is found is not decided here,
2882    /// because no sentence in the draft makes a second one for a namespace
2883    /// already announced an error.
2884    fn inbound_announce_id(&self, namespace: &TrackNamespace) -> Option<u64> {
2885        self.inbound_announces
2886            .iter()
2887            .find(|(_, a)| {
2888                a.message.track_namespace == *namespace && a.state.state() != AnnounceState::Done
2889            })
2890            .map(|(id, _)| *id)
2891    }
2892
2893    /// Build the ANNOUNCE_OK accepting an announcement the peer made.
2894    ///
2895    /// Section 5.2: "A subscriber MUST send exactly one ANNOUNCE_OK or
2896    /// ANNOUNCE_ERROR in response to an ANNOUNCE. The publisher SHOULD close
2897    /// the session with a protocol error if it receives more than one."
2898    ///
2899    /// One answer and no second one: the flow moves on the first, and a second
2900    /// call finds a record that has left Pending.
2901    ///
2902    /// # Errors
2903    ///
2904    /// [`EndpointError::UnknownRequest`] if the peer has announced nothing
2905    /// under that identifier, and the namespace flow's own `InvalidTransition`
2906    /// for an announcement already answered.
2907    pub fn send_announce_ok(
2908        &mut self,
2909        request_id: VarInt,
2910    ) -> Result<ControlMessage, EndpointError> {
2911        let id = request_id.into_inner();
2912        let ann = self.inbound_announces.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2913        ann.state.on_announce_ok_sent()?;
2914        Ok(ControlMessage::AnnounceOk(AnnounceOk { request_id }))
2915    }
2916
2917    /// Build the ANNOUNCE_ERROR refusing an announcement the peer made.
2918    ///
2919    /// The same sentence in Section 5.2 answers both ways: one message back and
2920    /// no second one, whichever of the two it is.
2921    ///
2922    /// # Errors
2923    ///
2924    /// [`EndpointError::UnknownRequest`] if the peer has announced nothing
2925    /// under that identifier, and the namespace flow's own `InvalidTransition`
2926    /// for an announcement already answered.
2927    pub fn send_announce_error(
2928        &mut self,
2929        request_id: VarInt,
2930        error_code: VarInt,
2931        reason_phrase: Vec<u8>,
2932    ) -> Result<ControlMessage, EndpointError> {
2933        let id = request_id.into_inner();
2934        let ann = self.inbound_announces.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2935        ann.state.on_announce_error_sent()?;
2936        Ok(ControlMessage::AnnounceError(AnnounceError { request_id, error_code, reason_phrase }))
2937    }
2938
2939    /// Process an incoming UNANNOUNCE, ending the announcement the peer made.
2940    ///
2941    /// Section 8.25: "The publisher sends the UNANNOUNCE control message to
2942    /// indicate its intent to stop serving new subscriptions for tracks within
2943    /// the provided Track Namespace."
2944    ///
2945    /// The announcement it ends is the peer's, so the record it reads is the
2946    /// one this endpoint keeps of what the peer announced. An announcement this
2947    /// endpoint made is withdrawn by [`Self::unannounce`], which is the same
2948    /// message travelling the other way.
2949    ///
2950    /// # Errors
2951    ///
2952    /// [`EndpointError::UnknownPeerNamespace`] if the peer has no live
2953    /// announcement for that namespace, and the namespace flow's own
2954    /// `InvalidTransition` for one this endpoint never accepted.
2955    pub fn receive_unannounce(&mut self, msg: &Unannounce) -> Result<(), EndpointError> {
2956        let id = self
2957            .inbound_announce_id(&msg.track_namespace)
2958            .ok_or(EndpointError::UnknownPeerNamespace)?;
2959        let ann = self.inbound_announces.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2960        ann.state.on_unannounce_received()?;
2961        Ok(())
2962    }
2963
2964    /// Build the ANNOUNCE_CANCEL revoking an acceptance.
2965    ///
2966    /// Section 7.3 names what a cancellation revokes: a namespace "it
2967    /// previously responded ANNOUNCE_OK to". Section 8.26 says what it does:
2968    /// the subscriber "will stop sending new subscriptions for tracks within
2969    /// the provided Track Namespace".
2970    ///
2971    /// Previously responded ANNOUNCE_OK to is a state, and it is Active: an
2972    /// announcement reaches it by being accepted and no other way. One still
2973    /// waiting for an answer, one refused and one already ended are all refused
2974    /// here rather than sent.
2975    ///
2976    /// The announcement is the peer's. An announcement this endpoint made is
2977    /// not cancelled by its own publisher; the peer cancels it, and that
2978    /// arrives at [`Self::receive_announce_cancel`].
2979    ///
2980    /// # Errors
2981    ///
2982    /// [`EndpointError::UnknownPeerNamespace`] if the peer has no live
2983    /// announcement for that namespace, and the namespace flow's own
2984    /// `InvalidTransition` for one this endpoint never accepted.
2985    pub fn announce_cancel(
2986        &mut self,
2987        track_namespace: TrackNamespace,
2988        error_code: VarInt,
2989        reason_phrase: Vec<u8>,
2990    ) -> Result<ControlMessage, EndpointError> {
2991        let id = self
2992            .inbound_announce_id(&track_namespace)
2993            .ok_or(EndpointError::UnknownPeerNamespace)?;
2994        let ann = self.inbound_announces.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2995        ann.state.on_announce_cancel_sent()?;
2996        Ok(ControlMessage::AnnounceCancel(AnnounceCancel {
2997            track_namespace,
2998            error_code,
2999            reason_phrase,
3000        }))
3001    }
3002
3003    // ── Track Status flow ──────────────────────────────────────
3004
3005    /// Send a TRACK_STATUS_REQUEST message. Returns the allocated request ID
3006    /// alongside the control message so the caller can correlate replies.
3007    pub fn track_status_request(
3008        &mut self,
3009        track_namespace: TrackNamespace,
3010        track_name: Vec<u8>,
3011        parameters: Vec<KeyValuePair>,
3012    ) -> Result<(VarInt, ControlMessage), EndpointError> {
3013        self.require_active_or_err()?;
3014        let req_id = self.request_ids.allocate()?;
3015        let mut sm = TrackStatusStateMachine::new();
3016        sm.on_track_status_request_sent()?;
3017        self.track_statuses.insert(req_id.into_inner(), sm);
3018        Ok((
3019            req_id,
3020            ControlMessage::TrackStatusRequest(TrackStatusRequest {
3021                request_id: req_id,
3022                track_namespace,
3023                track_name,
3024                parameters,
3025            }),
3026        ))
3027    }
3028
3029    /// Process an incoming TRACK_STATUS reply.
3030    pub fn receive_track_status(&mut self, msg: &TrackStatus) -> Result<(), EndpointError> {
3031        let id = msg.request_id.into_inner();
3032        let sm = self.track_statuses.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
3033        sm.on_track_status()?;
3034        Ok(())
3035    }
3036
3037    // ── Answering a TRACK_STATUS_REQUEST the peer sent ─────────
3038
3039    /// Process an incoming TRACK_STATUS_REQUEST, recording what the peer asked
3040    /// about.
3041    ///
3042    /// Section 8.20: "A potential subscriber sends a 'TRACK_STATUS_REQUEST'
3043    /// message on the control stream to obtain information about the current
3044    /// status of a given track."
3045    ///
3046    /// Answering is the application's to do, and it needs both the request and
3047    /// somewhere to answer from. This draft gave the request a Request ID, so
3048    /// that is what the record is filed under and what the answer names.
3049    ///
3050    /// # Errors
3051    ///
3052    /// The session error when the session is not established.
3053    pub fn receive_track_status_request(
3054        &mut self,
3055        msg: &TrackStatusRequest,
3056    ) -> Result<(), EndpointError> {
3057        self.require_active_or_err()?;
3058        let id = msg.request_id.into_inner();
3059        let mut state = TrackStatusStateMachine::new();
3060        state.on_track_status_request_received()?;
3061        self.inbound_track_statuses.insert(id, InboundTrackStatus { message: msg.clone(), state });
3062        Ok(())
3063    }
3064
3065    /// The TRACK_STATUS_REQUEST the peer sent under `request_id` and this
3066    /// endpoint has not answered yet.
3067    ///
3068    /// `None` once it has been answered, and for an identifier this session has
3069    /// carried no track status request under.
3070    pub fn pending_track_status_request(&self, request_id: VarInt) -> Option<&TrackStatusRequest> {
3071        self.inbound_track_statuses
3072            .get(&request_id.into_inner())
3073            .filter(|t| t.state.state() == TrackStatusState::Pending)
3074            .map(|t| &t.message)
3075    }
3076
3077    /// How many track statuses the peer has asked about that are still waiting
3078    /// for an answer.
3079    pub fn pending_track_status_request_count(&self) -> usize {
3080        self.inbound_track_statuses
3081            .values()
3082            .filter(|t| t.state.state() == TrackStatusState::Pending)
3083            .count()
3084    }
3085
3086    /// Build the TRACK_STATUS answering a request the peer sent.
3087    ///
3088    /// Section 8.20 leaves the answering end no discretion about whether to
3089    /// answer: "A TRACK_STATUS message MUST be sent in response to each
3090    /// TRACK_STATUS_REQUEST." What it bounds is how many, and that half is what
3091    /// the record carries: the request leaves `Pending` on the first answer, so
3092    /// a second call finds nothing left to answer.
3093    ///
3094    /// Section 8.21 says the identifier this message carries is "The Request ID
3095    /// of the TRACK_STATUS_REQUEST this message is replying to", which is why
3096    /// the answer is asked for by the identifier the request arrived under and
3097    /// not by the track it named.
3098    ///
3099    /// # Errors
3100    ///
3101    /// [`EndpointError::UnknownRequest`] if the peer has asked nothing under
3102    /// that identifier, and the flow's own `InvalidTransition` for a request
3103    /// already answered.
3104    pub fn send_track_status(
3105        &mut self,
3106        request_id: VarInt,
3107        status_code: VarInt,
3108        largest_location: Location,
3109        parameters: Vec<KeyValuePair>,
3110    ) -> Result<ControlMessage, EndpointError> {
3111        let id = request_id.into_inner();
3112        let req =
3113            self.inbound_track_statuses.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
3114        req.state.on_track_status_sent()?;
3115        Ok(ControlMessage::TrackStatus(TrackStatus {
3116            request_id,
3117            status_code,
3118            largest_location,
3119            parameters,
3120        }))
3121    }
3122
3123    // ── Requests blocked ───────────────────────────────────────
3124
3125    /// Process an incoming REQUESTS_BLOCKED message.
3126    ///
3127    /// Draft-12 renames draft-12's SUBSCRIBES_BLOCKED to REQUESTS_BLOCKED so
3128    /// the peer can explicitly report that a new request id would exceed our
3129    /// advertised maximum. The endpoint records the peer's reported maximum;
3130    /// acting on it (issuing a new `MAX_REQUEST_ID`) is up to the caller.
3131    pub fn receive_requests_blocked(&mut self, msg: &RequestsBlocked) -> Result<(), EndpointError> {
3132        self.peer_reported_max_request_id = Some(msg.maximum_request_id);
3133        Ok(())
3134    }
3135
3136    /// The maximum request id that the peer most recently reported in a
3137    /// `REQUESTS_BLOCKED` message, if any.
3138    pub fn peer_reported_max_request_id(&self) -> Option<VarInt> {
3139        self.peer_reported_max_request_id
3140    }
3141
3142    // ── Publish flow (draft-12) ────────────────────────────────
3143
3144    /// Offer the peer a subscription to a track this endpoint publishes.
3145    /// Allocates a Request ID.
3146    ///
3147    /// Section 4.1: "A subscription can be initiated by either a publisher or
3148    /// a subscriber. A publisher initiates a subscription to a track by
3149    /// sending the PUBLISH message. The subscriber either accepts or rejects
3150    /// the subscription using PUBLISH_OK or PUBLISH_ERROR."
3151    ///
3152    /// There is no `content_exists` parameter because it is not a choice.
3153    /// Section 8.13 makes it a flag for whether the field after it is there at
3154    /// all - "1 if an object has been published on this track, 0 if not. If 0,
3155    /// then the Largest Group ID and Largest Object ID fields will not be
3156    /// present" - so it is derived from `largest_location`, and the pair
3157    /// cannot be built disagreeing.
3158    ///
3159    /// `forward` is the subscription's initial Forward State, which Section
3160    /// 4.1 gives to whichever end opened it: "The initiator of the
3161    /// subscription sets the initial Forward State in either PUBLISH or
3162    /// SUBSCRIBE." Section 8.13 says what the peer may then assume of it: "1
3163    /// indicates the publisher will start transmitting objects immediately,
3164    /// even before PUBLISH_OK."
3165    ///
3166    /// # Errors
3167    ///
3168    /// [`EndpointError::TrackAliasInUse`] when some live request of this
3169    /// session already holds `track_alias` for a different track, which
3170    /// Section 8.13 forbids outright - "The same Track Alias MUST NOT be used
3171    /// to refer to two different Tracks simultaneously" - and which the
3172    /// subscriber answers by closing the session. Judged before the Request ID
3173    /// is allocated, so a refused offer spends nothing. Also the session error
3174    /// when the session is not established, and the request-id error when this
3175    /// endpoint has no identifier left to spend.
3176    #[allow(clippy::too_many_arguments)]
3177    pub fn publish(
3178        &mut self,
3179        track_namespace: TrackNamespace,
3180        track_name: Vec<u8>,
3181        track_alias: VarInt,
3182        group_order: GroupOrder,
3183        largest_location: Option<Location>,
3184        forward: Forward,
3185        parameters: Vec<KeyValuePair>,
3186    ) -> Result<(VarInt, ControlMessage), EndpointError> {
3187        self.require_active_or_err()?;
3188        let alias = track_alias.into_inner();
3189        if let Some(refusal) = self.alias_held_elsewhere(alias, &track_namespace, &track_name) {
3190            return Err(refusal);
3191        }
3192        let req_id = self.request_ids.allocate()?;
3193        let id = req_id.into_inner();
3194        let mut state = PublishStateMachine::new();
3195        state.on_publish_sent()?;
3196        self.publishes.insert(id, state);
3197        // A PUBLISH names its track and its alias in the one message, so the
3198        // binding is complete the moment the offer is built, and the same
3199        // split applies as on the arriving side: it counts against the next
3200        // offer this endpoint builds from here on, because that sentence is
3201        // unqualified, and against an arriving PUBLISH or SUBSCRIBE_OK only
3202        // once the peer's PUBLISH_OK has made the subscription active.
3203        self.track_bindings.insert(
3204            id,
3205            TrackBinding {
3206                namespace: track_namespace.clone(),
3207                name: track_name.clone(),
3208                alias: Some(alias),
3209                kind: BindingKind::Publish,
3210            },
3211        );
3212        let content_exists = if largest_location.is_some() {
3213            ContentExists::HasLargestLocation
3214        } else {
3215            ContentExists::NoLargestLocation
3216        };
3217        let msg = ControlMessage::Publish(Publish {
3218            request_id: req_id,
3219            track_namespace,
3220            track_name,
3221            track_alias,
3222            group_order,
3223            content_exists,
3224            largest_location,
3225            forward,
3226            parameters,
3227        });
3228        Ok((req_id, msg))
3229    }
3230
3231    /// Process an incoming PUBLISH message, which opens a subscription this
3232    /// endpoint is the subscriber of.
3233    ///
3234    /// The message is kept so the application can answer it with
3235    /// [`Endpoint::send_publish_ok`] or [`Endpoint::send_publish_error`] under
3236    /// the same request id, and the record it is kept in outlives that answer:
3237    /// Section 4.1 gives the subscription that follows an UNSUBSCRIBE and a
3238    /// SUBSCRIBE_DONE, and both name this request.
3239    ///
3240    /// # Errors
3241    ///
3242    /// [`EndpointError::DuplicateTrackAlias`] when the Track Alias offered is
3243    /// one a different live track already holds, which ends the session, and
3244    /// the publish flow's own `InvalidTransition` for a second PUBLISH under a
3245    /// request id already carrying one.
3246    pub fn receive_publish(&mut self, msg: &Publish) -> Result<(), EndpointError> {
3247        self.require_active_or_err()?;
3248        // Section 8.13 answers a PUBLISH naming an alias another live track
3249        // already holds with a session close, in the same words Section 8.8
3250        // uses for a SUBSCRIBE_OK.
3251        let id = msg.request_id.into_inner();
3252        let alias = msg.track_alias.into_inner();
3253        if let Some(conflict) =
3254            self.conflicting_track_alias(id, alias, &msg.track_namespace, &msg.track_name)
3255        {
3256            return Err(self.fail_session(conflict));
3257        }
3258        let mut state = PublishStateMachine::new();
3259        state.on_publish_received()?;
3260        self.inbound_publishes
3261            .insert(id, InboundPublish { message: msg.clone(), state: Mutex::new(state) });
3262        // A PUBLISH names its track and its alias in the one message, so the
3263        // binding is complete on arrival. What it counts against from here
3264        // depends on which of the two comparisons is asking. An offer this
3265        // endpoint is about to build is refused now, because the prohibition
3266        // is not qualified; an arriving message closes the session only once
3267        // this endpoint has answered PUBLISH_OK, because the half that closes
3268        // is qualified by "a different track with an active subscription".
3269        self.track_bindings.insert(
3270            id,
3271            TrackBinding {
3272                namespace: msg.track_namespace.clone(),
3273                name: msg.track_name.clone(),
3274                alias: Some(alias),
3275                kind: BindingKind::Publish,
3276            },
3277        );
3278        Ok(())
3279    }
3280
3281    /// The inbound PUBLISH for a request id that is still waiting to be
3282    /// answered.
3283    ///
3284    /// Stops answering once the PUBLISH has been answered, which is the whole
3285    /// of what "pending" means: the record itself lives on for as long as the
3286    /// subscription does.
3287    pub fn pending_publish(&self, request_id: VarInt) -> Option<&Publish> {
3288        self.inbound_publishes
3289            .get(&request_id.into_inner())
3290            .filter(|p| p.publish_state() == PublishState::Publishing)
3291            .map(|p| &p.message)
3292    }
3293
3294    /// Number of PUBLISH requests received but not yet responded to.
3295    pub fn pending_publish_count(&self) -> usize {
3296        self.inbound_publishes
3297            .values()
3298            .filter(|p| p.publish_state() == PublishState::Publishing)
3299            .count()
3300    }
3301
3302    /// Generate a PUBLISH_OK response for a previously received PUBLISH,
3303    /// which establishes the subscription it opened.
3304    ///
3305    /// # Errors
3306    ///
3307    /// [`EndpointError::UnknownRequest`] when no PUBLISH arrived under that
3308    /// id, and the publish flow's own `InvalidTransition` for a second answer:
3309    /// Section 4.1 says "A subscriber MUST send exactly one PUBLISH_OK or
3310    /// PUBLISH_ERROR in response to a PUBLISH."
3311    #[allow(clippy::too_many_arguments)]
3312    pub fn send_publish_ok(
3313        &mut self,
3314        request_id: VarInt,
3315        forward: Forward,
3316        subscriber_priority: u8,
3317        group_order: GroupOrder,
3318        filter_type: VarInt,
3319        start_group: Option<VarInt>,
3320        start_object: Option<VarInt>,
3321        end_group: Option<VarInt>,
3322    ) -> Result<ControlMessage, EndpointError> {
3323        let id = request_id.into_inner();
3324        let entry = self.inbound_publishes.get(&id).ok_or(EndpointError::UnknownRequest(id))?;
3325        entry.flow().on_publish_ok_sent()?;
3326        Ok(ControlMessage::PublishOk(PublishOk {
3327            request_id,
3328            forward,
3329            subscriber_priority,
3330            group_order,
3331            filter_type,
3332            start_group,
3333            start_object,
3334            end_group,
3335            parameters: vec![],
3336        }))
3337    }
3338
3339    /// Generate a PUBLISH_ERROR response for a previously received PUBLISH,
3340    /// which ends the subscription it opened before it was established.
3341    ///
3342    /// # Errors
3343    ///
3344    /// [`EndpointError::UnknownRequest`] when no PUBLISH arrived under that
3345    /// id, and the publish flow's own `InvalidTransition` for a second answer:
3346    /// Section 4.1 says "A subscriber MUST send exactly one PUBLISH_OK or
3347    /// PUBLISH_ERROR in response to a PUBLISH."
3348    pub fn send_publish_error(
3349        &mut self,
3350        request_id: VarInt,
3351        error_code: VarInt,
3352        reason_phrase: Vec<u8>,
3353    ) -> Result<ControlMessage, EndpointError> {
3354        let id = request_id.into_inner();
3355        let entry = self.inbound_publishes.get(&id).ok_or(EndpointError::UnknownRequest(id))?;
3356        entry.flow().on_publish_error_sent()?;
3357        Ok(ControlMessage::PublishError(PublishError { request_id, error_code, reason_phrase }))
3358    }
3359
3360    /// Process an incoming PUBLISH_OK, which establishes the subscription this
3361    /// endpoint offered under that Request ID.
3362    ///
3363    /// # Errors
3364    ///
3365    /// [`EndpointError::UnknownRequest`] when this endpoint has offered
3366    /// nothing under that identifier, and the publish flow's own
3367    /// `InvalidTransition` for an offer that has been answered already:
3368    /// Section 4.1 says "A subscriber MUST send exactly one PUBLISH_OK or
3369    /// PUBLISH_ERROR in response to a PUBLISH. The peer SHOULD close the
3370    /// session with a protocol error if it receives more than one." The verb
3371    /// there is SHOULD, so the second answer is reported rather than acted on,
3372    /// and the caller decides.
3373    pub fn receive_publish_ok(&mut self, msg: &PublishOk) -> Result<(), EndpointError> {
3374        let id = msg.request_id.into_inner();
3375        let state = self.publishes.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
3376        state.on_publish_ok()?;
3377        Ok(())
3378    }
3379
3380    /// Process an incoming PUBLISH_ERROR, which ends the subscription this
3381    /// endpoint offered under that Request ID before it was established.
3382    ///
3383    /// Section 4.1: "Objects MUST NOT be sent for requests that end with an
3384    /// error." The Track Alias the offer named is free again from here,
3385    /// because a binding reads liveness off this record rather than keeping a
3386    /// second copy of it.
3387    ///
3388    /// # Errors
3389    ///
3390    /// As [`Self::receive_publish_ok`].
3391    pub fn receive_publish_error(&mut self, msg: &PublishError) -> Result<(), EndpointError> {
3392        let id = msg.request_id.into_inner();
3393        let state = self.publishes.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
3394        state.on_publish_error()?;
3395        Ok(())
3396    }
3397
3398    /// Hold a Request ID a peer allocated to the rules Section 8.1 states
3399    /// about it.
3400    ///
3401    /// "The client's Request ID starts at 0 and are even and the server's
3402    /// Request ID starts at 1 and are odd. The Request ID increments by 2 ...
3403    /// If an endpoint receives a Request ID that is not valid for the peer, it
3404    /// MUST close the session with Invalid Request ID." Parity says which end
3405    /// may have chosen it; the ceiling this endpoint advertised says how far
3406    /// the peer may go.
3407    ///
3408    /// This is where [`Self::validate_peer_request_id`] is applied to arriving
3409    /// traffic, and the only place inside the endpoint that applies it. A
3410    /// request message the list below omits is one whose Request ID is checked
3411    /// for nothing: not its parity, not the ceiling this endpoint advertised,
3412    /// not its place in the peer's sequence.
3413    ///
3414    /// A message that is a response rather than a request carries the id of a
3415    /// request this endpoint made, so it is not checked here - it is checked by
3416    /// finding the state machine it names. SUBSCRIBE_UPDATE is the same case on
3417    /// this draft: its one Request ID field names the subscription it modifies
3418    /// rather than opening a request of its own, which is why Section 8.1 does
3419    /// not list it among the messages that step the sequence.
3420    ///
3421    /// # The list below is the rule, not a convenience
3422    ///
3423    /// Every message named here spends one of the peer's Request IDs, and
3424    /// nothing else does. That is what makes the list load-bearing, because
3425    /// the sequence is tracked: a request left out of it spends an ID
3426    /// this endpoint never counts, so the peer's **next** request looks like a
3427    /// skip and a conforming session is closed over it. PUBLISH is here and
3428    /// not in Section 8.1's enumeration, which predates the message; its own
3429    /// Request ID field refers the reader to that section like every other
3430    /// request's does.
3431    ///
3432    /// # Errors
3433    ///
3434    /// The request-id errors, for a wrong parity, an id at or above the
3435    /// advertised ceiling, or one that is not the next in the peer's sequence.
3436    pub fn receive_request(&mut self, msg: &ControlMessage) -> Result<(), EndpointError> {
3437        let request_id = match msg {
3438            ControlMessage::Subscribe(m) => m.request_id,
3439            ControlMessage::Fetch(m) => m.request_id,
3440            ControlMessage::Announce(m) => m.request_id,
3441            ControlMessage::SubscribeAnnounces(m) => m.request_id,
3442            ControlMessage::TrackStatusRequest(m) => m.request_id,
3443            ControlMessage::Publish(m) => m.request_id,
3444            _ => return Ok(()),
3445        };
3446        self.validate_peer_request_id(request_id.into_inner())
3447    }
3448
3449    // ── Unified message dispatch ───────────────────────────────
3450
3451    /// Dispatch an incoming control message to the appropriate handler.
3452    pub fn receive_message(&mut self, msg: ControlMessage) -> Result<(), EndpointError> {
3453        self.receive_request(&msg)?;
3454        match msg {
3455            ControlMessage::GoAway(ref m) => self.receive_goaway(m),
3456            ControlMessage::MaxRequestId(ref m) => self.receive_max_request_id(m),
3457            ControlMessage::RequestsBlocked(ref m) => self.receive_requests_blocked(m),
3458            ControlMessage::SubscribeOk(ref m) => self.receive_subscribe_ok(m),
3459            ControlMessage::SubscribeError(ref m) => self.receive_subscribe_error(m),
3460            ControlMessage::SubscribeUpdate(ref m) => self.receive_subscribe_update(m),
3461            ControlMessage::SubscribeDone(ref m) => self.receive_subscribe_done(m),
3462            ControlMessage::FetchOk(ref m) => self.receive_fetch_ok(m),
3463            ControlMessage::FetchError(ref m) => self.receive_fetch_error(m),
3464            ControlMessage::SubscribeAnnouncesOk(ref m) => self.receive_subscribe_announces_ok(m),
3465            ControlMessage::SubscribeAnnouncesError(ref m) => {
3466                self.receive_subscribe_announces_error(m)
3467            }
3468            ControlMessage::AnnounceOk(ref m) => self.receive_announce_ok(m),
3469            ControlMessage::AnnounceError(ref m) => self.receive_announce_error(m),
3470            ControlMessage::AnnounceCancel(ref m) => self.receive_announce_cancel(m),
3471            ControlMessage::TrackStatus(ref m) => self.receive_track_status(m),
3472            ControlMessage::TrackStatusRequest(ref m) => self.receive_track_status_request(m),
3473            ControlMessage::Subscribe(ref m) => self.receive_subscribe(m),
3474            ControlMessage::Unsubscribe(ref m) => self.receive_unsubscribe(m),
3475            ControlMessage::Fetch(ref m) => self.receive_fetch(m),
3476            ControlMessage::FetchCancel(ref m) => self.receive_fetch_cancel(m),
3477            ControlMessage::Publish(ref m) => self.receive_publish(m),
3478            ControlMessage::PublishOk(ref m) => self.receive_publish_ok(m),
3479            ControlMessage::PublishError(ref m) => self.receive_publish_error(m),
3480            ControlMessage::Announce(ref m) => self.receive_announce(m),
3481            ControlMessage::Unannounce(ref m) => self.receive_unannounce(m),
3482            ControlMessage::SubscribeAnnounces(ref m) => self.receive_subscribe_announces(m),
3483            ControlMessage::UnsubscribeAnnounces(ref m) => self.receive_unsubscribe_announces(m),
3484            _ => Ok(()),
3485        }
3486    }
3487}