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