Skip to main content

moqtap_client/draft14/
endpoint.rs

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