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