Skip to main content

moqtap_client/draft13/
endpoint.rs

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