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