Skip to main content

moqtap_client/draft08/
endpoint.rs

1use std::collections::HashMap;
2use std::sync::{Arc, Mutex};
3
4use crate::draft08::fetch::{FetchError, FetchState, FetchStateMachine};
5use crate::draft08::namespace::{
6    AnnounceState, AnnounceStateMachine, NamespaceError, SubscribeAnnouncesState,
7    SubscribeAnnouncesStateMachine,
8};
9use crate::draft08::session::setup::{self, SetupError};
10use crate::draft08::session::state::{SessionError, SessionState, SessionStateMachine};
11use crate::draft08::session::subscribe_id::{SubscribeIdAllocator, SubscribeIdError};
12use crate::draft08::subscription::{
13    SubscriptionError, SubscriptionState, SubscriptionStateMachine,
14};
15use crate::draft08::track_status::{TrackStatusError, TrackStatusState, TrackStatusStateMachine};
16use crate::forwarding_preference::{ObjectForwardingPreference, TrackForwardingPreferences};
17use crate::track_locations::{
18    EndOfTrackPlacement, ObjectLocation, ObjectRole, TrackLocations, TrackObjects,
19};
20use moqtap_codec::draft08::error_codes::{SessionErrorCode, SubscribeErrorCode};
21use moqtap_codec::draft08::message::{
22    self, Announce, AnnounceCancel, AnnounceError, AnnounceOk, ClientSetup, ControlMessage, Fetch,
23    FetchCancel, FetchType, GoAway, MaxSubscribeId, ServerSetup, Subscribe, SubscribeAnnounces,
24    SubscribeAnnouncesError, SubscribeAnnouncesOk, SubscribeDone, SubscribeError, SubscribeOk,
25    SubscribeUpdate, SubscribesBlocked, TrackStatus, TrackStatusRequest, Unannounce, Unsubscribe,
26    UnsubscribeAnnounces,
27};
28use moqtap_codec::kvp::KeyValuePair;
29use moqtap_codec::types::*;
30use moqtap_codec::varint::VarInt;
31
32/// Key identifying a namespace (used for Announce / SubscribeAnnounces maps).
33type NamespaceKey = Vec<Vec<u8>>;
34
35/// Key identifying a track (namespace + track name).
36type TrackKey = (Vec<Vec<u8>>, Vec<u8>);
37
38/// Which side of the session this endpoint is.
39///
40/// This draft has no Request ID parity and no ROLE parameter, so the only rule
41/// that turns on the answer is the one in Section 7.3 about which side may
42/// send a GOAWAY that carries a New Session URI. It lives here rather than
43/// beside the Subscribe ID allocator for that reason: a Subscribe ID on this
44/// draft is a session-wide counter and does not depend on who allocates it.
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum Role {
47    /// The endpoint that opened the session.
48    Client,
49    /// The endpoint that accepted it.
50    Server,
51}
52
53/// Errors that can occur during draft-08 endpoint operations.
54#[derive(Debug, thiserror::Error)]
55pub enum EndpointError {
56    /// A GOAWAY carrying a New Session URI arrived at a server.
57    ///
58    /// Section 7.3: "If a server receives a GOAWAY with a non-zero New
59    /// Session URI Length it MUST terminate the session with a Protocol
60    /// Violation." Migration is something a server offers a client, never the
61    /// other way round.
62    #[error("GOAWAY carrying a New Session URI received at a server")]
63    GoAwayUriAtServer,
64    /// A session-level state machine error.
65    #[error("session error: {0}")]
66    Session(#[from] SessionError),
67    /// A subscribe ID allocation or validation error.
68    #[error("subscribe ID error: {0}")]
69    SubscribeId(#[from] SubscribeIdError),
70    /// This endpoint was asked to advertise a Maximum Subscribe ID that does
71    /// not increase, and refused. Nothing was written.
72    ///
73    /// The send-side mirror of the rule a peer breaks by sending one — Section 7.20:
74    /// "The Maximum Subscribe Id MUST only increase within a session". No closing
75    /// mark, because the draft's sentence does not close there: it runs on
76    /// into the receipt half, which is the peer's side of this rule and not
77    /// this one's. Its own
78    /// variant, and not the received one, because the two are opposite
79    /// findings that would otherwise arrive as the same value: the received one
80    /// is a peer in violation and this one is a caller of this library asking
81    /// for a message that would put this endpoint in violation, and
82    /// [`EndpointError::session_error_code`] answers `Some` for it either way.
83    ///
84    /// Not fatal. The message is refused instead of built, the ceiling stays
85    /// where it was, and nothing reaches the peer to object to.
86    #[error(
87        "the Maximum Subscribe ID already advertised is {advertised}, so {offered} would not increase it"
88    )]
89    MaxSubscribeIdWouldNotIncrease {
90        /// The ceiling this endpoint has already advertised.
91        advertised: u64,
92        /// The value it was asked to advertise instead.
93        offered: u64,
94    },
95    /// A subscription state machine error.
96    #[error("subscription error: {0}")]
97    Subscription(#[from] SubscriptionError),
98    /// A fetch state machine error.
99    #[error("fetch error: {0}")]
100    Fetch(#[from] FetchError),
101    /// A namespace state machine error.
102    #[error("namespace error: {0}")]
103    Namespace(#[from] NamespaceError),
104    /// A track status state machine error.
105    #[error("track status error: {0}")]
106    TrackStatus(#[from] TrackStatusError),
107    /// A setup negotiation error.
108    #[error("setup error: {0}")]
109    Setup(#[from] SetupError),
110    /// The subscribe ID does not match any known state machine.
111    #[error("unknown subscribe ID: {0}")]
112    UnknownSubscribe(u64),
113    /// The track namespace does not match any known state machine.
114    #[error("unknown namespace")]
115    UnknownNamespace,
116    /// The (namespace, track) pair does not match any known track status request.
117    #[error("unknown track status request")]
118    UnknownTrackStatus,
119    /// A message about a track status named a track the peer has not asked
120    /// about.
121    ///
122    /// Section 7.12 makes the request the subscriber's: "A potential subscriber
123    /// sends a 'TRACK_STATUS_REQUEST' message on the control stream to obtain
124    /// information about the current status of a given track." What an answer
125    /// answers is therefore a request the **peer** made, so the record it
126    /// reaches for is the one this endpoint keeps of what the peer has asked
127    /// about.
128    ///
129    /// Separate from [`EndpointError::UnknownTrackStatus`], which is the same
130    /// miss on the requests this endpoint made, so a caller can tell which of
131    /// the two maps came up empty.
132    #[error("the peer has asked for no status of this track")]
133    UnknownPeerTrackStatus,
134    /// The session is not in the Active state.
135    #[error("session not active")]
136    NotActive,
137    /// The session is draining and cannot accept new requests.
138    #[error("session is draining, no new requests allowed")]
139    Draining,
140    /// A filter that names a start location was asked for through a helper
141    /// that has no start location to give it.
142    #[error("this filter type needs a start location; use the range form of this call")]
143    FilterNeedsRange,
144    /// A setup parameter's value could not be read as the type its key implies.
145    #[error("setup parameter {0:#x} has a malformed value")]
146    MalformedSetupParameter(
147        /// Key of the offending parameter.
148        u64,
149    ),
150    /// A Subscribe ID the peer chose did not increase on the last one it used.
151    #[error("peer subscribe ID {0} does not increase on {1}")]
152    PeerSubscribeIdNotIncreasing(
153        /// The Subscribe ID that arrived.
154        u64,
155        /// The highest Subscribe ID the peer had used before it.
156        u64,
157    ),
158    /// A second GOAWAY arrived on the control stream.
159    ///
160    /// The GOAWAY that says the peer is going away is one message, and the
161    /// draft answers a repeat of it with a session close rather than with an
162    /// error about the second message: there is no state a second one could
163    /// move that the first has not already moved.
164    #[error("a second GOAWAY arrived on the control stream")]
165    RepeatedGoAway,
166
167    /// A Track Alias names two tracks at once.
168    ///
169    /// Section 7.4, on the Track Alias the subscriber chooses in SUBSCRIBE:
170    /// "If the Track Alias is already being used for a different track, the
171    /// publisher MUST close the session with a Duplicate Track Alias error".
172    /// Section 7.16 states the other end of the same rule, on the alias a
173    /// SUBSCRIBE_ERROR may offer to retry with: "If this Track Alias is
174    /// already in use, the subscriber MUST close the connection with a
175    /// Duplicate Track Alias error".
176    ///
177    /// The session is over: this endpoint's own state has moved to Closed and
178    /// the code the transport should close with is in
179    /// [`EndpointError::session_error_code`].
180    #[error(
181        "track alias {alias} already names the track of {established_side} subscribe \
182         {established}; {offered_side} subscribe {offered} names a different one"
183    )]
184    DuplicateTrackAlias {
185        /// The alias both tracks are named by.
186        alias: u64,
187        /// Which end opened the subscription that holds the alias.
188        established_side: SubscribeSide,
189        /// That subscription's identifier, in its own end's sequence.
190        established: u64,
191        /// Which end opened the subscription naming it for another track.
192        offered_side: SubscribeSide,
193        /// That subscription's identifier, in its own end's sequence.
194        offered: u64,
195    },
196    /// This endpoint was asked to give a Track Alias to a second track.
197    ///
198    /// The same rule as [`EndpointError::DuplicateTrackAlias`] read at the end
199    /// that chooses the alias. Section 3.5 describes the code as "The
200    /// endpoint attempted to use a Track Alias that was already in use", and
201    /// Section 7.4 says what the receiving publisher does about it, so a
202    /// SUBSCRIBE built this way is one the peer must answer by ending the
203    /// session.
204    ///
205    /// The message is refused instead, and nothing else moves: no Subscribe ID
206    /// is spent, no subscription is created, and the session stays as it was.
207    /// The alias never reaches the peer, so there is nothing for the peer to
208    /// close over.
209    #[error("track alias {alias} already names the track of {side} subscribe {held}")]
210    TrackAliasInUse {
211        /// The alias that is already spoken for.
212        alias: u64,
213        /// Which end opened the subscription holding it.
214        side: SubscribeSide,
215        /// That subscription's identifier, in its own end's sequence.
216        held: u64,
217    },
218    /// A track's objects were framed two different ways.
219    ///
220    /// Section 8: "Every Track has a single 'Object Forwarding Preference' and
221    /// the Original Publisher MUST NOT mix different forwarding preferences
222    /// within a single track. If a subscriber receives different forwarding
223    /// preferences for a track, it SHOULD close the session with an error of
224    /// 'Protocol Violation'."
225    ///
226    /// The framing is the preference: an object on a subgroup stream has the
227    /// Subgroup preference and an object in a datagram has the Datagram one,
228    /// so the track's first object settles the property and this is every
229    /// later object measured against it.
230    #[error(
231        "track alias {alias} carries objects framed as {established}, and one is \
232         framed as {offered}"
233    )]
234    MixedForwardingPreference {
235        /// The Track Alias the offending object named.
236        alias: u64,
237        /// The framing the track's earlier objects settled on.
238        established: ObjectForwardingPreference,
239        /// The framing the offending object used.
240        offered: ObjectForwardingPreference,
241    },
242    /// An object saying the track ended somewhere the track has already passed.
243    ///
244    /// Section 8.1.1.1 describes Object Status 0x4, end of Track and
245    /// Group, as one whose "GroupID is the largest group produced in this
246    /// track and the ObjectId is one greater than the largest object
247    /// produced in that group", and states the consequence: "An object with
248    /// this status that has a Group ID less than any other Group ID, or an
249    /// Object ID less than or equal to the largest in the group, is a
250    /// protocol error, and the receiver MUST terminate the session."
251    ///
252    /// Status 0x5, end of Track, is one notch stricter in the same
253    /// paragraph: "An object with this status that has a Group ID less than
254    /// or equal to any other Group ID, or an Object ID other than zero, is a
255    /// protocol error, and the receiver MUST terminate the session." Its
256    /// Object-ID half needs no record and the codec refuses it on the header;
257    /// its Group ID half is this.
258    #[error(
259        "the end-of-track object at group {group}, object {object} on track alias \
260         {alias} is out of place: {placement}"
261    )]
262    EndOfTrackOutOfPlace {
263        /// The Track Alias the offending object named.
264        alias: u64,
265        /// The Group ID it named.
266        group: u64,
267        /// The Object ID it named.
268        object: u64,
269        /// Which half of the condition it broke, and what it was measured
270        /// against.
271        placement: EndOfTrackPlacement,
272    },
273    /// A SUBSCRIBE_UPDATE named an identifier no subscription the peer opened
274    /// has ever been given.
275    ///
276    /// Section 7.5: "A publisher SHOULD close the Session as a 'Protocol
277    /// Violation' if the SUBSCRIBE_UPDATE violates either rule or if the
278    /// subscriber specifies a Subscribe ID that has not existed within the Session."
279    ///
280    /// **SHOULD**, so this is reported and the session is left running. From
281    /// draft-12 the same sentence says MUST, and there the session ends. An
282    /// endpoint that wants the close on these drafts has everything it needs
283    /// to make it: the error names the identifier that was not found.
284    ///
285    /// A subscription that has **ended** is not this: it existed. That is why
286    /// the record of an inbound SUBSCRIBE outlives the subscription, and why
287    /// an update naming an ended one is refused by the flow rather than by
288    /// this error.
289    #[error("SUBSCRIBE_UPDATE names subscribe {0}, which no subscription the peer opened has had")]
290    UpdateForUnknownSubscribe(u64),
291
292    /// A Joining Fetch named a subscription this session cannot join.
293    ///
294    /// Section 7.7: "If a publisher receives a Joining Fetch with a Subscribe ID
295    /// that does not correspond to an existing Subscribe, it MUST respond with
296    /// a Fetch Error."
297    ///
298    /// A refusal and not a session close, so the session runs on and the error
299    /// names both identifiers: the fetch to refuse, and the subscription it
300    /// asked to join.
301    #[error(
302        "FETCH {fetch} joins subscribe {joining}, which is no live subscription of the peer's"
303    )]
304    UnjoinableSubscription {
305        /// The fetch that named it.
306        fetch: u64,
307        /// The identifier it named.
308        joining: u64,
309    },
310    /// A message about an announcement named a namespace the peer has not
311    /// announced.
312    ///
313    /// Section 7.11 says what a cancellation is for: the subscriber "will stop
314    /// sending new subscriptions for tracks within the provided Track
315    /// Namespace". What a withdrawal ends and a cancellation revokes is an
316    /// announcement the **peer** made, so the record they reach for is the one
317    /// this endpoint keeps of the peer's announcements.
318    ///
319    /// Separate from [`EndpointError::UnknownNamespace`], which is the same
320    /// miss on the announcements this endpoint made, so a caller can tell which
321    /// of the two maps came up empty.
322    #[error("the peer has made no live announcement for this namespace")]
323    UnknownPeerNamespace,
324    /// A message about a namespace subscription named a prefix the peer has
325    /// not subscribed to.
326    ///
327    /// Section 7.14: "A subscriber issues a UNSUBSCRIBE_ANNOUNCES message to a publisher indicating it is no longer interested in ANNOUNCE and UNANNOUNCE messages for the specified track namespace prefix."
328    ///
329    /// What a withdrawal ends is a namespace subscription the **peer** made,
330    /// so the record it reaches for is the one this endpoint keeps of the
331    /// peer's. A namespace subscription this endpoint made is withdrawn by
332    /// [`Endpoint::unsubscribe_announces`], which is the same message travelling the other
333    /// way and answers with [`EndpointError::UnknownNamespace`].
334    #[error("the peer has made no live namespace subscription for this prefix")]
335    UnknownPeerNamespaceSubscription,
336    /// The peer subscribed to a namespace prefix overlapping one it is
337    /// already subscribed to.
338    ///
339    /// Section 7.13: "A subscriber cannot make overlapping namespace
340    /// subscriptions on a single session. Within a session, if a publisher
341    /// receives a SUBSCRIBE_ANNOUNCES with a Track Namespace Prefix that is a
342    /// prefix of an earlier SUBSCRIBE_ANNOUNCES or vice versa, it MUST
343    /// respond with SUBSCRIBE_ANNOUNCES_ERROR, with error code
344    /// SUBSCRIBE_ANNOUNCES_OVERLAP."
345    ///
346    /// The request is refused where it arrives and nothing is written down for
347    /// it, which is the only outcome this draft can express. SUBSCRIBE_ANNOUNCES
348    /// carries no Request ID here, so the acceptance, the refusal and the
349    /// withdrawal all name a Track Namespace Prefix and nothing else. Two
350    /// namespace subscriptions under one prefix would therefore have answers
351    /// that cannot be told apart, and an equal prefix is the first case the
352    /// sentence above names.
353    ///
354    /// The code the sentence gives the refusal, SUBSCRIBE_ANNOUNCES_OVERLAP, is
355    /// named in prose and appears in no registry this draft defines, so there
356    /// is no number for this crate to put on the wire. A caller that wants to
357    /// send the refusal builds it from the message it has just been handed.
358    #[error("the namespace prefix the peer subscribed to overlaps one it already has")]
359    PeerPrefixOverlap,
360    /// This endpoint was asked to subscribe to a namespace prefix overlapping
361    /// one it is already subscribed to.
362    ///
363    /// The first half of the same sentence, which is addressed to the
364    /// subscriber: "A subscriber cannot make overlapping namespace
365    /// subscriptions on a single session."
366    ///
367    /// The message is refused instead of built, and nothing else moves: no
368    /// state machine is created and the session stays as it was. The request
369    /// never reaches the peer, so there is nothing for the peer to refuse.
370    ///
371    /// A subscription that has been withdrawn still counts, because the
372    /// publisher's half of the sentence weighs a new prefix against "an
373    /// earlier SUBSCRIBE_ANNOUNCES" rather than against a live one. Drafts
374    /// from 12 on say "active" instead, and there a withdrawn one stops
375    /// counting.
376    #[error("the namespace prefix overlaps one this endpoint is already subscribed to")]
377    OwnPrefixOverlap,
378}
379
380/// Whether two namespace prefixes overlap.
381///
382/// Section 7.13: "A subscriber cannot make overlapping namespace
383/// subscriptions on a single session. Within a session, if a publisher
384/// receives a SUBSCRIBE_ANNOUNCES with a Track Namespace Prefix that is a
385/// prefix of an earlier SUBSCRIBE_ANNOUNCES or vice versa, it MUST respond
386/// with SUBSCRIBE_ANNOUNCES_ERROR, with error code
387/// SUBSCRIBE_ANNOUNCES_OVERLAP."
388///
389/// A namespace matches a namespace subscription when the subscription's
390/// prefix is a prefix of it, so two prefixes select overlapping sets of
391/// namespaces exactly when one of them is a prefix of the other. Equal
392/// prefixes are that case as well: every prefix is a prefix of itself, and
393/// two equal ones select the same set.
394fn prefixes_overlap(a: &[Vec<u8>], b: &[Vec<u8>]) -> bool {
395    let shared = a.len().min(b.len());
396    a[..shared] == b[..shared]
397}
398
399/// Which end of the session opened a subscription.
400///
401/// It takes this and an identifier together to name one on this draft. Each
402/// end allocates Subscribe IDs from zero, nothing in the draft separates the
403/// two sequences, and this endpoint keeps the peer's apart from its own - so
404/// the peer's subscribe 3 and this endpoint's subscribe 3 are two
405/// subscriptions, not one.
406#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
407pub enum SubscribeSide {
408    /// A SUBSCRIBE this endpoint sent, carrying the alias it chose.
409    Ours,
410    /// A SUBSCRIBE the peer sent, carrying the alias the peer chose.
411    Peers,
412}
413
414impl std::fmt::Display for SubscribeSide {
415    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
416        match self {
417            SubscribeSide::Ours => f.write_str("our"),
418            SubscribeSide::Peers => f.write_str("the peer's"),
419        }
420    }
421}
422
423/// A SUBSCRIBE the peer sent, and how far the subscription it opened has got.
424struct InboundSubscribe {
425    /// The message as it arrived, which is what the application answers from.
426    message: Subscribe,
427    /// The subscription's state, driven from the publisher's end.
428    state: SubscriptionStateMachine,
429}
430/// A FETCH the peer sent, and how far the fetch it opened has got.
431struct InboundFetch {
432    /// The message as it arrived, which is what the application answers from.
433    message: Fetch,
434    /// The fetch's state, driven from the end that serves it.
435    state: FetchStateMachine,
436    /// The subscription a Joining Fetch named and this session had none live
437    /// for when the FETCH arrived, which is when the rule about it is read.
438    unjoinable: Option<u64>,
439}
440
441/// A Track Alias attached to a Full Track Name, and nothing else: the
442/// subscription whose lifetime the attachment follows is the map's key.
443///
444/// SUBSCRIBE carries the alias and the track in the one message, whichever end
445/// sends it, so a binding is complete from the moment it is made. That stops
446/// being true at draft-12, where the alias arrives in the answer instead.
447#[derive(Debug, Clone)]
448struct TrackBinding {
449    namespace: TrackNamespace,
450    name: Vec<u8>,
451    alias: u64,
452}
453
454impl EndpointError {
455    /// Whose doing this is — the peer's, or this endpoint's, or a variant that
456    /// cannot say.
457    ///
458    /// The companion of [`EndpointError::session_error_code`], which answers
459    /// *what the draft requires be done about it*. Neither answers the other's
460    /// question and the pair is what a caller needs: a code without a side
461    /// names nobody, and a side without a code is not grounds to publish
462    /// anything.
463    ///
464    /// Exhaustive, with no wildcard arm, so a variant added to this draft's
465    /// `EndpointError` is a compile error here rather than a silent arrival on
466    /// the wrong side of the answer. See
467    /// [`EndpointFault`](crate::above_codec_rules::EndpointFault) for the three
468    /// answers and for the collision that made the third one necessary.
469    pub fn fault(&self) -> crate::above_codec_rules::EndpointFault {
470        use crate::above_codec_rules::{AboveCodecRule as Rule, EndpointFault as Fault};
471
472        match self {
473            // Raised on both a receive path and a send path, so the
474            // variant cannot say which end is at fault. The state machines
475            // render as `invalid transition from X on event Y` whichever end
476            // asked for the transition, and the unknown-request errors name
477            // an id that may be one the peer sent or one a caller here made
478            // up.
479            EndpointError::Session(..)
480            | EndpointError::Subscription(..)
481            | EndpointError::Fetch(..)
482            | EndpointError::Namespace(..)
483            | EndpointError::TrackStatus(..)
484            | EndpointError::Setup(..)
485            | EndpointError::UnknownSubscribe(..)
486            | EndpointError::UnknownNamespace
487            | EndpointError::UnknownPeerNamespace
488            | EndpointError::UnknownPeerNamespaceSubscription => Fault::EitherEnd,
489
490            // Raised on the way out. Nothing reached the wire, so none of
491            // these is evidence about a peer — including the ones a peer
492            // caused, where what failed is this side's attempt to accept
493            // something the draft says to refuse.
494            EndpointError::MaxSubscribeIdWouldNotIncrease { .. }
495            | EndpointError::UnknownPeerTrackStatus
496            | EndpointError::NotActive
497            | EndpointError::Draining
498            | EndpointError::FilterNeedsRange
499            | EndpointError::TrackAliasInUse { .. }
500            | EndpointError::UnjoinableSubscription { .. }
501            | EndpointError::OwnPrefixOverlap => Fault::ThisEndpoint,
502
503            // Raised reading what the peer sent.
504            EndpointError::DuplicateTrackAlias { .. } => Fault::Peer(Rule::DuplicateTrackAlias),
505            EndpointError::EndOfTrackOutOfPlace { .. } => Fault::Peer(Rule::EndOfTrackOutOfPlace),
506            EndpointError::GoAwayUriAtServer => Fault::Peer(Rule::GoAwayAtServer),
507            EndpointError::UnknownTrackStatus => Fault::Peer(Rule::MessageNamesAnUnknownRequest),
508            EndpointError::MixedForwardingPreference { .. } => {
509                Fault::Peer(Rule::MixedForwardingPreference)
510            }
511            EndpointError::PeerPrefixOverlap => Fault::Peer(Rule::NamespacePrefixOverlap),
512            EndpointError::RepeatedGoAway => Fault::Peer(Rule::RepeatedGoAway),
513            EndpointError::PeerSubscribeIdNotIncreasing(..) => {
514                Fault::Peer(Rule::RequestIdOutOfSequence)
515            }
516            EndpointError::UpdateForUnknownSubscribe(..) => {
517                Fault::Peer(Rule::RequestUpdateForTheWrongRequest)
518            }
519            EndpointError::MalformedSetupParameter(..) => Fault::Peer(Rule::SetupParameterValue),
520
521            // The ceiling rules, which are the peer's whenever they are read
522            // off the wire. The mirror — this endpoint asked to advertise a
523            // ceiling that does not increase — is
524            // `MaxSubscribeIdWouldNotIncrease` above, which is a variant of its
525            // own so that the two never arrive as one value.
526            EndpointError::SubscribeId(e) => match e {
527                SubscribeIdError::Decreased(..) => Fault::Peer(Rule::MaxRequestIdDecreased),
528                SubscribeIdError::ExceedsMax(..) => Fault::Peer(Rule::RequestIdCeiling),
529                // This endpoint has spent the budget the peer granted it.
530                SubscribeIdError::Blocked => Fault::ThisEndpoint,
531            },
532        }
533    }
534
535    /// The code to close the session with, when draft-08 answers this error
536    /// with a close rather than leaving it to the one request it concerns.
537    ///
538    /// `None` means the error is recoverable: the caller may report it, give
539    /// up on the request it concerns, and keep the session running. `Some`
540    /// means the draft ends the session, and the endpoint has already moved
541    /// its own state to Closed - the code is what the transport should carry.
542    ///
543    /// The table grows one rule at a time, and a rule joins it with a gate
544    /// that drives the bytes at a real connection and reads the close code
545    /// back off the wire. An arm added without one asserts nothing: from
546    /// inside the process the session ends either way, and only the peer can
547    /// tell the difference.
548    pub fn session_error_code(&self) -> Option<SessionErrorCode> {
549        match self {
550            // Section 7.20 answers a ceiling that does not increase with a
551            // close, and names this code for it.
552            EndpointError::SubscribeId(SubscribeIdError::Decreased(..)) => {
553                Some(SessionErrorCode::ProtocolViolation)
554            }
555            // The same section answers a Subscribe ID that reaches the ceiling
556            // this endpoint advertised, and names a different code for it.
557            EndpointError::SubscribeId(SubscribeIdError::ExceedsMax(..)) => {
558                Some(SessionErrorCode::TooManySubscribes)
559            }
560            // Section 7.3 answers a GOAWAY that repeats one already
561            // received, and names this code in the same sentence.
562            EndpointError::RepeatedGoAway => Some(SessionErrorCode::ProtocolViolation),
563            // The same section answers a migration URI arriving at a server
564            // with a close, and names this code for it. Only a server may
565            // offer one, so a client that sends one is telling a server where
566            // to reconnect, which it has no standing to do.
567            EndpointError::GoAwayUriAtServer => Some(SessionErrorCode::ProtocolViolation),
568            // Section 7.4 answers a SUBSCRIBE whose Track Alias already
569            // names a different track with a session close, and Section 7.16
570            // answers the retry alias a SUBSCRIBE_ERROR offers the same way.
571            // Section 3.5 names this code for both.
572            EndpointError::DuplicateTrackAlias { .. } => {
573                Some(SessionErrorCode::DuplicateTrackAlias)
574            }
575            // Section 8 answers a track whose objects mix forwarding
576            // preferences, and names this code in the same sentence: "it SHOULD
577            // close the session with an error of 'Protocol Violation'".
578            //
579            // SHOULD, so the close is the caller's to make. The code lives here
580            // and `Connection::close_for_data_stream` is what carries it, the
581            // same opt-in every other rule broken on a data stream takes.
582            EndpointError::MixedForwardingPreference { .. } => {
583                Some(SessionErrorCode::ProtocolViolation)
584            }
585            // Section 8.1.1.1 answers an end-of-track object in the wrong place
586            // with "the receiver MUST terminate the session", and names no code
587            // in that sentence. The paragraph it closes names one for the
588            // status field's other failure — "Any other value SHOULD be treated
589            // as a protocol error and terminate the session with a Protocol
590            // Violation" — and it is the code this draft's decoder already
591            // gives the half of the same rule it can settle from one header.
592            EndpointError::EndOfTrackOutOfPlace { .. } => Some(SessionErrorCode::ProtocolViolation),
593            _ => None,
594        }
595    }
596}
597
598/// Unified draft-08 MoQT endpoint wrapping session lifecycle, subscribe ID
599/// allocation, and all per-flow state machines (subscriptions, fetches,
600/// announces, subscribe-announces, track statuses).
601pub struct Endpoint {
602    /// Which side of the session this is. Read only by the GOAWAY rule.
603    role: Role,
604    session: SessionStateMachine,
605    subscribe_ids: SubscribeIdAllocator,
606    /// Tracks the MAX_SUBSCRIBE_ID we have advertised to the peer.
607    advertised_max_id: u64,
608    /// The highest Subscribe ID the peer has used, once it has used one.
609    peer_highest_subscribe_id: Option<u64>,
610    subscriptions: HashMap<u64, SubscriptionStateMachine>,
611    /// Subscriptions the peer opened with SUBSCRIBE, each from the moment its
612    /// message arrived to the end of the flow.
613    ///
614    /// Separate from `subscriptions`, which holds the ones this endpoint
615    /// opened, because the identifiers do not separate themselves: see
616    /// [`SubscribeSide`].
617    inbound_subscribes: HashMap<u64, InboundSubscribe>,
618    /// Every FETCH the peer has sent, from arrival to the end of the fetch.
619    ///
620    /// Separate from `fetches`, which holds the ones this endpoint made,
621    /// because the identifiers do not separate themselves: both ends allocate
622    /// from zero, so one number can name a fetch at each end at once.
623    inbound_fetches: HashMap<u64, InboundFetch>,
624    /// Every Track Alias in use in this session, and the track each one names.
625    ///
626    /// "Already being used" is what makes this a table rather than a set: an
627    /// alias whose subscription has ended is free again. The table records the
628    /// binding and reads liveness back off the subscription's own state
629    /// machine, rather than keeping a second copy of it that every path ending
630    /// a subscription would have to remember to prune.
631    /// What each track's objects have been framed as, so far.
632    ///
633    /// Behind a lock because this is the one endpoint fact a *data* stream
634    /// settles, and the data plane reaches the endpoint through `&Connection`:
635    /// a caller may hold one across tasks while it reads streams and datagrams,
636    /// so there is no `&mut` to reach the rest of this struct with.
637    forwarding_preferences: Mutex<TrackForwardingPreferences>,
638    /// How far each track's objects have reached, so far.
639    ///
640    /// Behind an `Arc` rather than beside the rest of this struct because the
641    /// objects that settle it are read off stream handles the caller owns, one
642    /// at a time, with no way back to the endpoint. Each such stream is handed a
643    /// clone of the handle, and every clone measures against this one record.
644    locations: Arc<Mutex<TrackLocations>>,
645    track_bindings: HashMap<(SubscribeSide, u64), TrackBinding>,
646    fetches: HashMap<u64, FetchStateMachine>,
647    subscribe_announces: HashMap<NamespaceKey, SubscribeAnnouncesStateMachine>,
648    /// Namespace subscriptions the **peer** made, keyed by the prefix each
649    /// one names.
650    ///
651    /// The prefix is the whole of a request's name on this draft: the
652    /// acceptance, the refusal and the withdrawal all carry a Track Namespace
653    /// Prefix and nothing else, so one prefix has one record here. A second
654    /// SUBSCRIBE_ANNOUNCES under a prefix already subscribed replaces it, which is
655    /// a case Section 4.1 forbids rather than one this map decides.
656    inbound_subscribe_announces: HashMap<NamespaceKey, InboundSubscribeAnnounces>,
657    announces: HashMap<NamespaceKey, AnnounceStateMachine>,
658    /// Announcements the **peer** made, keyed by the namespace each
659    /// names, which is all this draft's ANNOUNCE carries to name it by.
660    inbound_announces: HashMap<NamespaceKey, InboundAnnounce>,
661    track_statuses: HashMap<TrackKey, TrackStatusStateMachine>,
662    /// Track statuses the **peer** asked about, keyed by the track each names.
663    ///
664    /// Kept apart from `track_statuses`, which holds the ones this endpoint
665    /// asked about: the two are answered by opposite ends. This draft's
666    /// TRACK_STATUS_REQUEST carries no identifier of its own, so the track it
667    /// names is the only thing an answer can be matched to, which is the key
668    /// the outbound map is under for the same reason.
669    inbound_track_statuses: HashMap<TrackKey, InboundTrackStatus>,
670    negotiated_version: Option<VarInt>,
671    offered_versions: Vec<VarInt>,
672    goaway_uri: Option<Vec<u8>>,
673    /// The most recent `maximum_subscribe_id` reported by the peer via a
674    /// `SUBSCRIBES_BLOCKED` message (draft-08 only).
675    peer_reported_max_subscribe_id: Option<VarInt>,
676}
677
678impl Default for Endpoint {
679    fn default() -> Self {
680        Self::new(Role::Client)
681    }
682}
683
684/// An announcement the peer made with ANNOUNCE.
685///
686/// Kept apart from the announcements this endpoint made because the two are
687/// answered by opposite ends: this one is waiting for an answer from here,
688/// and the other for one from the peer.
689struct InboundAnnounce {
690    /// The message as it arrived, which is what the application answers from.
691    message: Announce,
692    /// How far the announcement it makes has got.
693    state: AnnounceStateMachine,
694}
695/// A track status the peer asked for with TRACK_STATUS_REQUEST.
696///
697/// Kept apart from the ones this endpoint asked for because the two are
698/// answered by opposite ends: this one is waiting for an answer from here,
699/// and the other for one from the peer.
700struct InboundTrackStatus {
701    /// The message as it arrived, which is what the answer is built from.
702    message: TrackStatusRequest,
703    /// How far the request it opened has got.
704    state: TrackStatusStateMachine,
705}
706/// A SUBSCRIBE_ANNOUNCES the peer sent, and how far the namespace subscription it opens
707/// has got.
708///
709/// Kept apart from `subscribe_announces`, which holds the ones this endpoint made: the
710/// two are answered by opposite ends, and this one is waiting for an answer
711/// from here.
712struct InboundSubscribeAnnounces {
713    /// The message as it arrived, which is what the answer is built from.
714    message: SubscribeAnnounces,
715    /// How far the namespace subscription it opens has got.
716    state: SubscribeAnnouncesStateMachine,
717}
718impl Endpoint {
719    /// Create a new draft-08 endpoint for the given role.
720    pub fn new(role: Role) -> Self {
721        Self {
722            role,
723            session: SessionStateMachine::new(),
724            subscribe_ids: SubscribeIdAllocator::new(),
725            advertised_max_id: 0,
726            peer_highest_subscribe_id: None,
727            subscriptions: HashMap::new(),
728            inbound_subscribes: HashMap::new(),
729            inbound_fetches: HashMap::new(),
730            track_bindings: HashMap::new(),
731            forwarding_preferences: Mutex::new(TrackForwardingPreferences::new()),
732            locations: Arc::new(Mutex::new(TrackLocations::new())),
733            fetches: HashMap::new(),
734            subscribe_announces: HashMap::new(),
735            inbound_subscribe_announces: HashMap::new(),
736            announces: HashMap::new(),
737            inbound_announces: HashMap::new(),
738            track_statuses: HashMap::new(),
739            inbound_track_statuses: HashMap::new(),
740            negotiated_version: None,
741            offered_versions: Vec::new(),
742            goaway_uri: None,
743            peer_reported_max_subscribe_id: None,
744        }
745    }
746
747    // ── Track aliases ──────────────────────────────────────────
748
749    /// The subscription already using `alias` for a track other than
750    /// (`namespace`, `name`), or `None` when the alias is free for that track.
751    ///
752    /// # Why the set is read rather than kept
753    ///
754    /// Section 7.4 says "already being used", and a subscription that has
755    /// ended is not using anything. Asking each binding's own state machine is
756    /// what makes an alias free again the instant its track's subscription
757    /// ends, with nothing to prune on the way out - and a path that ended a
758    /// subscription without telling this table would otherwise hold the alias
759    /// forever and refuse the peer's next, conforming, use of it.
760    ///
761    /// # Why a binding for the same track is not a conflict
762    ///
763    /// The rule is about a Track Alias naming two tracks, not about naming one
764    /// track twice. A second subscription to the track an alias already names
765    /// breaks nothing this section states.
766    fn alias_holder(
767        &self,
768        alias: u64,
769        namespace: &TrackNamespace,
770        name: &[u8],
771    ) -> Option<(SubscribeSide, u64)> {
772        self.track_bindings.iter().find_map(|(&key, binding)| {
773            let other_track = binding.namespace != *namespace || binding.name != name;
774            (binding.alias == alias && other_track && self.binding_is_live(key)).then_some(key)
775        })
776    }
777
778    /// The lowest Track Alias no live binding has given to a track.
779    ///
780    /// Drafts 07 through 11 make the **subscriber** choose the Track Alias, and
781    /// require it to name one track per session; draft-12 moved the field to
782    /// SUBSCRIBE_OK and made the choice the publisher's. A client spanning both
783    /// eras therefore has to supply a value on these drafts and cannot on the
784    /// later ones, so the value is read off the endpoint rather than asked of
785    /// the caller: that is what lets
786    /// [`crate::dispatch::AnyConnection::subscribe`] carry one signature across
787    /// all the drafts instead of an argument that does nothing on nine of
788    /// them.
789    ///
790    /// Read rather than kept, for the same reason the alias table beside it
791    /// gives: a binding whose request has ended holds nothing, so an alias
792    /// falls free when its subscription does and may name a different track
793    /// next. A caller that mixes this with aliases of its own choosing stays
794    /// correct by construction, because both read this one table — and
795    /// `subscribe` refuses a duplicate before the alias reaches the wire
796    /// either way.
797    pub fn next_free_track_alias(&self) -> VarInt {
798        let taken: std::collections::BTreeSet<u64> = self
799            .track_bindings
800            .iter()
801            .filter(|(&key, _)| self.binding_is_live(key))
802            .map(|(_, binding)| binding.alias)
803            .collect();
804        let mut candidate = 0;
805        while taken.contains(&candidate) {
806            candidate += 1;
807        }
808        VarInt::from_u64_moqt(candidate)
809    }
810
811    /// The track a live binding has given `alias` to.
812    ///
813    /// Read rather than kept, for the reason the alias table beside it gives: a
814    /// binding whose request has ended holds nothing, and an alias that is free
815    /// again may name a different track next. That is exactly why the
816    /// forwarding-preference record below is keyed on the track this returns
817    /// and never on the alias itself.
818    fn track_for_alias(&self, alias: u64) -> Option<(&TrackNamespace, &[u8])> {
819        self.track_bindings.iter().find_map(|(&key, binding)| {
820            (binding.alias == alias && self.binding_is_live(key))
821                .then_some((&binding.namespace, binding.name.as_slice()))
822        })
823    }
824
825    /// The record a stream carrying `alias`'s objects measures them against.
826    ///
827    /// `None` for an alias no live binding names: an object for one breaks a
828    /// different rule, and measuring it against a track this endpoint never
829    /// asked for would answer that one with the wrong sentence.
830    pub fn track_objects(&self, alias: u64) -> Option<TrackObjects> {
831        let (namespace, name) = self.track_for_alias(alias)?;
832        Some(TrackObjects::new(
833            Arc::clone(&self.locations),
834            namespace.clone(),
835            name.to_vec(),
836            alias,
837        ))
838    }
839
840    /// Record or judge one object that arrived outside a subgroup stream, and
841    /// report Section 8.1.1.1's protocol error when it says the track ended
842    /// somewhere the track has already passed.
843    ///
844    /// `&self`, because the call site is the data plane's.
845    pub fn note_received_object(
846        &self,
847        alias: u64,
848        at: ObjectLocation,
849        role: ObjectRole,
850    ) -> Result<(), EndpointError> {
851        let Some(objects) = self.track_objects(alias) else { return Ok(()) };
852        objects.note(at, role).map_err(|placement| EndpointError::EndOfTrackOutOfPlace {
853            alias,
854            group: at.group,
855            object: at.object,
856            placement,
857        })
858    }
859
860    /// Record how a track's object was framed, and report Section 8's "MUST NOT
861    /// mix" when it disagrees with what that track's earlier objects used.
862    ///
863    /// `&self`, because the call sites are the data plane's: a subgroup header
864    /// arriving, a datagram arriving, and the two writers that produce them.
865    ///
866    /// An alias no live binding names records nothing and reports nothing. An
867    /// object for such an alias breaks a different rule — the one about objects
868    /// nobody asked for — and answering that one here would answer it with the
869    /// wrong sentence.
870    pub fn note_object_forwarding_preference(
871        &self,
872        alias: u64,
873        seen: ObjectForwardingPreference,
874    ) -> Result<(), EndpointError> {
875        let Some((namespace, name)) = self.track_for_alias(alias) else { return Ok(()) };
876        self.forwarding_preferences
877            .lock()
878            .unwrap_or_else(|poisoned| poisoned.into_inner())
879            .observe(namespace, name, seen)
880            .map_err(|established| EndpointError::MixedForwardingPreference {
881                alias,
882                established,
883                offered: seen,
884            })
885    }
886
887    /// Whether the subscription that owns a binding is still standing.
888    /// Subscribing counts as well as Active, which is what separates this draft
889    /// from draft-12 Section 8.8 onwards: there the sentence is "a different
890    /// track with an active subscription" and the alias arrives in the answer,
891    /// so only an answered request holds one. Here the alias is in the
892    /// SUBSCRIBE itself, and the sentence puts no qualifier on "already being
893    /// used" - so it is in use from the moment that message is sent or
894    /// received, and stays in use until the subscription ends.
895    fn binding_is_live(&self, key: (SubscribeSide, u64)) -> bool {
896        let state = match key.0 {
897            SubscribeSide::Ours => self.subscriptions.get(&key.1).map(|sm| sm.state()),
898            SubscribeSide::Peers => self.inbound_subscribes.get(&key.1).map(|s| s.state.state()),
899        };
900        matches!(state, Some(SubscriptionState::Subscribing | SubscriptionState::Active))
901    }
902
903    /// The close Section 7.4 requires of an arriving SUBSCRIBE whose Track
904    /// Alias is spoken for, or `None` when it is free.
905    fn conflicting_track_alias(
906        &self,
907        side: SubscribeSide,
908        id: u64,
909        alias: u64,
910        namespace: &TrackNamespace,
911        name: &[u8],
912    ) -> Option<EndpointError> {
913        let (established_side, established) = self.alias_holder(alias, namespace, name)?;
914        Some(EndpointError::DuplicateTrackAlias {
915            alias,
916            established_side,
917            established,
918            offered_side: side,
919            offered: id,
920        })
921    }
922
923    /// The close Section 7.16 requires of a SUBSCRIBE_ERROR offering a Track
924    /// Alias to retry with, or `None` when the offer can be taken up.
925    ///
926    /// The track is not in the SUBSCRIBE_ERROR: it is the one this endpoint's
927    /// own SUBSCRIBE asked for, so the request has to be looked up before the
928    /// alias offered for it can be judged. An offer of an alias this endpoint
929    /// already holds for that same track is the retry succeeding, not a
930    /// conflict.
931    fn conflicting_retry_alias(&self, id: u64, alias: u64) -> Option<EndpointError> {
932        let binding = self.track_bindings.get(&(SubscribeSide::Ours, id))?;
933        let (established_side, established) =
934            self.alias_holder(alias, &binding.namespace, &binding.name)?;
935        Some(EndpointError::DuplicateTrackAlias {
936            alias,
937            established_side,
938            established,
939            offered_side: SubscribeSide::Ours,
940            offered: id,
941        })
942    }
943
944    // ── Accessors ──────────────────────────────────────────────
945
946    /// Returns which side of the session this endpoint is.
947    pub fn role(&self) -> Role {
948        self.role
949    }
950
951    /// Returns the current session state.
952    pub fn session_state(&self) -> SessionState {
953        self.session.state()
954    }
955
956    /// Returns the negotiated MoQT version, if setup is complete.
957    pub fn negotiated_version(&self) -> Option<VarInt> {
958        self.negotiated_version
959    }
960
961    /// Returns the URI from a received GOAWAY message, if any.
962    pub fn goaway_uri(&self) -> Option<&[u8]> {
963        self.goaway_uri.as_deref()
964    }
965
966    /// Returns whether this endpoint is blocked on subscribe ID allocation.
967    pub fn is_blocked(&self) -> bool {
968        self.subscribe_ids.is_blocked()
969    }
970
971    /// Returns the number of active subscription state machines.
972    pub fn active_subscription_count(&self) -> usize {
973        self.subscriptions.len()
974    }
975
976    /// Returns the number of active fetch state machines.
977    pub fn active_fetch_count(&self) -> usize {
978        self.fetches.len()
979    }
980
981    /// Returns the number of active subscribe-announces state machines.
982    pub fn active_subscribe_announces_count(&self) -> usize {
983        self.subscribe_announces.len()
984    }
985
986    /// Returns the number of active announce state machines.
987    pub fn active_announce_count(&self) -> usize {
988        self.announces.len()
989    }
990
991    /// Returns the number of active track status state machines.
992    pub fn active_track_status_count(&self) -> usize {
993        self.track_statuses.len()
994    }
995
996    // ── Session lifecycle ──────────────────────────────────────
997
998    /// Transition from Connecting to SetupExchange.
999    pub fn connect(&mut self) -> Result<(), EndpointError> {
1000        self.session.on_connect()?;
1001        Ok(())
1002    }
1003
1004    /// Close the session (SetupExchange, Active or Draining -> Closed).
1005    pub fn close(&mut self) -> Result<(), EndpointError> {
1006        self.session.on_close()?;
1007        Ok(())
1008    }
1009
1010    // ── Client setup ───────────────────────────────────────────
1011
1012    /// Generate a CLIENT_SETUP message (client-side).
1013    pub fn send_client_setup(
1014        &mut self,
1015        versions: Vec<VarInt>,
1016        parameters: Vec<KeyValuePair>,
1017    ) -> Result<ControlMessage, EndpointError> {
1018        self.offered_versions = versions.clone();
1019        let msg = ClientSetup { supported_versions: versions, parameters };
1020        setup::validate_client_setup(&msg)?;
1021        self.record_advertised_max(&msg.parameters);
1022        Ok(ControlMessage::ClientSetup(msg))
1023    }
1024
1025    /// Process a SERVER_SETUP message (client-side). Transitions to Active.
1026    /// If the server includes a MAX_SUBSCRIBE_ID parameter (key 0x02), the
1027    /// subscribe ID allocator is initialized with that value.
1028    pub fn receive_server_setup(&mut self, msg: &ServerSetup) -> Result<(), EndpointError> {
1029        setup::validate_server_setup(msg)?;
1030        let version = setup::negotiate_version(&self.offered_versions, msg.selected_version)?;
1031        self.negotiated_version = Some(version);
1032        self.session.on_setup_complete()?;
1033        self.read_granted_max(&msg.parameters)?;
1034        Ok(())
1035    }
1036
1037    // ── Server setup ───────────────────────────────────────────
1038
1039    /// Process CLIENT_SETUP and generate SERVER_SETUP (server-side).
1040    pub fn receive_client_setup_and_respond(
1041        &mut self,
1042        client_setup: &ClientSetup,
1043        selected_version: VarInt,
1044    ) -> Result<ControlMessage, EndpointError> {
1045        self.receive_client_setup_and_respond_with(client_setup, selected_version, Vec::new())
1046    }
1047
1048    /// Process CLIENT_SETUP and generate SERVER_SETUP carrying `parameters`.
1049    ///
1050    /// The form that can answer with a MAX_SUBSCRIBE_ID. Section 7.2.2.2
1051    /// describes the parameter as communicating "an initial value for the
1052    /// Maximum Subscribe ID to the receiving subscriber. The default value is
1053    /// 0, so if not specified, the peer MUST NOT create subscriptions" - so a
1054    /// server that never sends it has told the client it may not subscribe,
1055    /// and every SUBSCRIBE the client tries is answered Blocked until a
1056    /// MAX_SUBSCRIBE_ID message arrives.
1057    ///
1058    /// A MAX_SUBSCRIBE_ID among `parameters` is recorded as the ceiling this
1059    /// endpoint has advertised, which is the number a peer's Subscribe IDs are
1060    /// measured against.
1061    ///
1062    /// # Errors
1063    ///
1064    /// The setup errors, and a malformed MAX_SUBSCRIBE_ID in the CLIENT_SETUP.
1065    pub fn receive_client_setup_and_respond_with(
1066        &mut self,
1067        client_setup: &ClientSetup,
1068        selected_version: VarInt,
1069        parameters: Vec<KeyValuePair>,
1070    ) -> Result<ControlMessage, EndpointError> {
1071        setup::validate_client_setup(client_setup)?;
1072        // Section 7.2.2.2 puts no role restriction on MAX_SUBSCRIBE_ID, so a
1073        // CLIENT_SETUP may carry it and it grants this endpoint its budget.
1074        self.read_granted_max(&client_setup.parameters)?;
1075        let version = setup::negotiate_version(&client_setup.supported_versions, selected_version)?;
1076        self.negotiated_version = Some(version);
1077        self.session.on_setup_complete()?;
1078        self.record_advertised_max(&parameters);
1079        let msg = ServerSetup { selected_version: version, parameters };
1080        Ok(ControlMessage::ServerSetup(msg))
1081    }
1082
1083    /// Take the budget a peer's setup parameters grant this endpoint.
1084    ///
1085    /// An explicit 0 is the same as the parameter's absence - Section
1086    /// 7.2.2.2 gives it a default of 0 - so it is not put through the
1087    /// only-increase rule, which belongs to the MAX_SUBSCRIBE_ID message.
1088    fn read_granted_max(&mut self, parameters: &[KeyValuePair]) -> Result<(), EndpointError> {
1089        for param in parameters {
1090            if param.key == VarInt::from_u64(0x02).unwrap() {
1091                let max = setup::setup_varint(&param.value)
1092                    .ok_or(EndpointError::MalformedSetupParameter(0x02))?;
1093                if max > 0 {
1094                    self.subscribe_ids.update_max(max)?;
1095                }
1096            }
1097        }
1098        Ok(())
1099    }
1100
1101    /// Record a MAX_SUBSCRIBE_ID parameter this endpoint is about to send as
1102    /// the ceiling it has advertised to the peer.
1103    ///
1104    /// The peer's Subscribe IDs are bound by this number, and this endpoint's
1105    /// own by the one the peer advertised. The two are different values and
1106    /// measuring against the wrong one accepts ids a conforming peer would
1107    /// never send and refuses ids it may.
1108    fn record_advertised_max(&mut self, parameters: &[KeyValuePair]) {
1109        for param in parameters {
1110            if param.key == VarInt::from_u64(0x02).unwrap() {
1111                if let Some(max) = setup::setup_varint(&param.value) {
1112                    self.advertised_max_id = max;
1113                }
1114            }
1115        }
1116    }
1117
1118    /// Hold a Subscribe ID the peer chose to the rules Section 7.4
1119    /// states about it.
1120    ///
1121    /// "Subscribe ID is a variable length integer that MUST be unique and
1122    /// monotonically increasing within a session and MUST be less than the
1123    /// session's Maximum Subscribe ID", and Section 7.7 repeats the
1124    /// first half for FETCH - so the two share one sequence and are checked
1125    /// together here.
1126    ///
1127    /// The ceiling is the one **this** endpoint advertised, not the one the
1128    /// peer granted us: those are different numbers, and either may be the
1129    /// larger. Strictly increasing gives uniqueness as well, so one high-water
1130    /// mark answers both halves of the sentence.
1131    ///
1132    /// # The ceiling is a session rule, not a request rule
1133    ///
1134    /// Section 7.20: "If a Subscribe ID equal or larger than this is received
1135    /// by the publisher that sent the MAX_SUBSCRIBE_ID, the publisher MUST
1136    /// close the session with an error of 'Too Many Subscribes'." Which is
1137    /// also why the number measured against is the one **this** endpoint
1138    /// sent. An id that reaches the ceiling is not a SUBSCRIBE to refuse with
1139    /// a SUBSCRIBE_ERROR: the session is over, so this moves the endpoint's
1140    /// own state to Closed and leaves the code to
1141    /// [`EndpointError::session_error_code`].
1142    ///
1143    /// # Errors
1144    ///
1145    /// [`SubscribeIdError::ExceedsMax`] if the id reaches the advertised
1146    /// ceiling, and [`EndpointError::PeerSubscribeIdNotIncreasing`] if it does
1147    /// not increase on the last one the peer used.
1148    pub fn validate_peer_subscribe_id(&mut self, id: u64) -> Result<(), EndpointError> {
1149        if id >= self.advertised_max_id {
1150            return Err(self.fail_session(EndpointError::SubscribeId(
1151                SubscribeIdError::ExceedsMax(id, self.advertised_max_id),
1152            )));
1153        }
1154        if let Some(highest) = self.peer_highest_subscribe_id {
1155            if id <= highest {
1156                return Err(EndpointError::PeerSubscribeIdNotIncreasing(id, highest));
1157            }
1158        }
1159        self.peer_highest_subscribe_id = Some(id);
1160        Ok(())
1161    }
1162
1163    /// Process an incoming SUBSCRIBE, checking the Subscribe ID the peer chose.
1164    ///
1165    /// # Errors
1166    ///
1167    /// Whatever [`Self::validate_peer_subscribe_id`] answers.
1168    pub fn receive_subscribe(&mut self, msg: &Subscribe) -> Result<(), EndpointError> {
1169        let id = msg.subscribe_id.into_inner();
1170        self.validate_peer_subscribe_id(id)?;
1171        // Section 7.4: "If the Track Alias is already being used for a
1172        // different track, the publisher MUST close the session with a
1173        // Duplicate Track Alias error". This endpoint is the publisher of a
1174        // SUBSCRIBE that arrives, so this is where that close is raised.
1175        // Judged before anything is written down, so a refused SUBSCRIBE
1176        // leaves no binding behind.
1177        let alias = msg.track_alias.into_inner();
1178        if let Some(conflict) = self.conflicting_track_alias(
1179            SubscribeSide::Peers,
1180            id,
1181            alias,
1182            &msg.track_namespace,
1183            &msg.track_name,
1184        ) {
1185            return Err(self.fail_session(conflict));
1186        }
1187        let mut state = SubscriptionStateMachine::new();
1188        state.on_subscribe_received()?;
1189        self.inbound_subscribes.insert(id, InboundSubscribe { message: msg.clone(), state });
1190        self.track_bindings.insert(
1191            (SubscribeSide::Peers, id),
1192            TrackBinding {
1193                namespace: msg.track_namespace.clone(),
1194                name: msg.track_name.clone(),
1195                alias,
1196            },
1197        );
1198        Ok(())
1199    }
1200
1201    // ── MAX_SUBSCRIBE_ID ───────────────────────────────────────
1202
1203    /// Process an incoming MAX_SUBSCRIBE_ID message, ending the session if the
1204    /// ceiling it carries does not increase.
1205    /// Section 7.20: "The Maximum Subscribe Id MUST only increase within a
1206    /// session, and receipt of a MAX_SUBSCRIBE_ID message with an equal or
1207    /// smaller Subscribe ID value is a 'Protocol Violation'." Section 3.5 lists
1208    /// Protocol Violation among the codes for terminating the session - "The
1209    /// remote endpoint performed an action that was disallowed by the
1210    /// specification" - so naming it of a *receipt* is this draft saying the
1211    /// session ends, and with which code. Draft-16 Section 9.5 states the same
1212    /// rule with the verb in it: "it MUST close the session with a
1213    /// PROTOCOL_VIOLATION".
1214    ///
1215    /// # Errors
1216    ///
1217    /// [`SubscribeIdError::Decreased`] if the value does not increase, with
1218    /// the session already moved to Closed.
1219    pub fn receive_max_subscribe_id(&mut self, msg: &MaxSubscribeId) -> Result<(), EndpointError> {
1220        if let Err(err) = self.subscribe_ids.update_max(msg.subscribe_id.into_inner()) {
1221            return Err(self.fail_session(err.into()));
1222        }
1223        Ok(())
1224    }
1225
1226    /// Generate a MAX_SUBSCRIBE_ID message (typically server-side).
1227    ///
1228    /// Section 7.20: "The Maximum Subscribe ID MUST only increase within a
1229    /// session", and a peer that receives an equal or smaller value closes
1230    /// the session. The ceiling starts at 0 and 0 is not greater than 0, so
1231    /// the first value that may go on the wire is 1 and there is no opening
1232    /// case where a repeat is allowed.
1233    ///
1234    /// # Errors
1235    ///
1236    /// [`EndpointError::MaxSubscribeIdWouldNotIncrease`] if the value does not
1237    /// strictly increase. Its own variant rather than the one a *received*
1238    /// ceiling that did not increase raises, so that a refusal to write is
1239    /// never read back as a peer in violation.
1240    pub fn send_max_subscribe_id(
1241        &mut self,
1242        max_id: VarInt,
1243    ) -> Result<ControlMessage, EndpointError> {
1244        let new_val = max_id.into_inner();
1245        if new_val <= self.advertised_max_id {
1246            return Err(EndpointError::MaxSubscribeIdWouldNotIncrease {
1247                advertised: self.advertised_max_id,
1248                offered: new_val,
1249            });
1250        }
1251        self.advertised_max_id = new_val;
1252        Ok(ControlMessage::MaxSubscribeId(MaxSubscribeId { subscribe_id: max_id }))
1253    }
1254
1255    // ── GoAway ─────────────────────────────────────────────────
1256
1257    /// Process an incoming GOAWAY message. Transitions to Draining.
1258    ///
1259    /// # Errors
1260    ///
1261    /// [`EndpointError::GoAwayUriAtServer`] if this endpoint is the server and
1262    /// the GOAWAY carries a New Session URI. The session is over: this
1263    /// endpoint's own state has moved to Closed and the code the transport
1264    /// should close with is in [`EndpointError::session_error_code`].
1265    ///
1266    /// [`EndpointError::RepeatedGoAway`] if a GOAWAY has already been
1267    /// received. The session is over: this endpoint's own state has moved to
1268    /// Closed and the code the transport should close with is in
1269    /// [`EndpointError::session_error_code`].
1270    pub fn receive_goaway(&mut self, msg: &GoAway) -> Result<(), EndpointError> {
1271        // Section 7.3: "If a server receives a GOAWAY with a non-zero New
1272        // Session URI Length it MUST terminate the session with a Protocol
1273        // Violation." Refused before the URI is stored rather than
1274        // after, so an application reading `goaway_uri` back can never be
1275        // handed somewhere a client chose to send it. The session ends with
1276        // it: the sentence names a close and a code, and an endpoint that
1277        // raised the error and carried on would keep serving a peer it had
1278        // just found in violation.
1279        if self.role == Role::Server && !msg.new_session_uri.is_empty() {
1280            return Err(self.fail_session(EndpointError::GoAwayUriAtServer));
1281        }
1282        // Section 7.3: "The endpoint MUST terminate the session with a
1283        // Protocol Violation (Section 3.5) if it receives multiple GOAWAY messages."
1284        // Draining is reached from nowhere else - `on_goaway` is its only
1285        // entry and this method is that method's only caller - so the session
1286        // state is the record of the first GOAWAY having arrived.
1287        if self.session.state() == SessionState::Draining {
1288            return Err(self.fail_session(EndpointError::RepeatedGoAway));
1289        }
1290        self.session.on_goaway()?;
1291        self.goaway_uri = Some(msg.new_session_uri.clone());
1292        Ok(())
1293    }
1294
1295    // ── Subscribe flow ─────────────────────────────────────────
1296
1297    fn require_active_or_err(&self) -> Result<(), EndpointError> {
1298        match self.session.state() {
1299            SessionState::Active => Ok(()),
1300            SessionState::Draining => Err(EndpointError::Draining),
1301            _ => Err(EndpointError::NotActive),
1302        }
1303    }
1304
1305    /// Record that the session is over because the peer broke a rule this
1306    /// draft answers with a session close, and hand the error back unchanged.
1307    ///
1308    /// The state move is what makes the violation stick: every request entry
1309    /// point goes through
1310    /// [`require_active_or_err`](Self::require_active_or_err), so a caller
1311    /// that ignores the returned error still cannot start anything new.
1312    /// Closing on the wire is the connection layer's job - see
1313    /// [`EndpointError::session_error_code`] for the code it should use.
1314    fn fail_session(&mut self, err: EndpointError) -> EndpointError {
1315        // `on_close` accepts SetupExchange, Active and Draining. A violation
1316        // seen in Connecting or Closed leaves the state machine alone: there
1317        // is no session to close, and the error itself is still the answer.
1318        //
1319        // SetupExchange is in that set because the Termination section says
1320        // "The Transport Session can be terminated at any point", and the
1321        // Setup exchange is a point. So a violation caught while the setup is
1322        // still in flight does close the session, and the discarded result is
1323        // safe because that is one of the states `on_close` accepts.
1324        let _ = self.session.on_close();
1325        err
1326    }
1327
1328    /// Send a SUBSCRIBE message. Allocates an ID and creates a subscription
1329    /// state machine.
1330    ///
1331    /// `AbsoluteStart` and `AbsoluteRange` name a start location, which this
1332    /// call has no way to supply, and are answered with
1333    /// [`EndpointError::FilterNeedsRange`] - use [`Self::subscribe_range`] for
1334    /// those. Without the refusal this call would hand back a message whose
1335    /// filter announces fields the message does not carry, and the frame that
1336    /// goes on the wire is short by exactly those fields.
1337    pub fn subscribe(
1338        &mut self,
1339        track_alias: VarInt,
1340        track_namespace: TrackNamespace,
1341        track_name: Vec<u8>,
1342        subscriber_priority: u8,
1343        group_order: GroupOrder,
1344        filter_type: FilterType,
1345    ) -> Result<(VarInt, ControlMessage), EndpointError> {
1346        if matches!(filter_type, FilterType::AbsoluteStart | FilterType::AbsoluteRange) {
1347            return Err(EndpointError::FilterNeedsRange);
1348        }
1349        self.subscribe_inner(
1350            track_alias,
1351            track_namespace,
1352            track_name,
1353            subscriber_priority,
1354            group_order,
1355            filter_type,
1356            None,
1357            None,
1358        )
1359    }
1360
1361    /// Send a SUBSCRIBE for a range of the track, starting at a given
1362    /// location.
1363    ///
1364    /// The Filter Type is derived from the arguments rather than taken beside
1365    /// them, so the message cannot name a filter whose fields it does not
1366    /// carry.
1367    #[allow(clippy::too_many_arguments)]
1368    pub fn subscribe_range(
1369        &mut self,
1370        track_alias: VarInt,
1371        track_namespace: TrackNamespace,
1372        track_name: Vec<u8>,
1373        subscriber_priority: u8,
1374        group_order: GroupOrder,
1375        start_location: Location,
1376        end_group: Option<VarInt>,
1377    ) -> Result<(VarInt, ControlMessage), EndpointError> {
1378        let filter_type = match end_group {
1379            Some(_) => FilterType::AbsoluteRange,
1380            None => FilterType::AbsoluteStart,
1381        };
1382        self.subscribe_inner(
1383            track_alias,
1384            track_namespace,
1385            track_name,
1386            subscriber_priority,
1387            group_order,
1388            filter_type,
1389            Some(start_location),
1390            end_group,
1391        )
1392    }
1393
1394    #[allow(clippy::too_many_arguments)]
1395    fn subscribe_inner(
1396        &mut self,
1397        track_alias: VarInt,
1398        track_namespace: TrackNamespace,
1399        track_name: Vec<u8>,
1400        subscriber_priority: u8,
1401        group_order: GroupOrder,
1402        filter_type: FilterType,
1403        start_location: Option<Location>,
1404        end_group: Option<VarInt>,
1405    ) -> Result<(VarInt, ControlMessage), EndpointError> {
1406        self.require_active_or_err()?;
1407        // The alias travels in the SUBSCRIBE, so this is the last point at
1408        // which giving it to a second track can still be taken back. Refused
1409        // before the Subscribe ID is allocated, so a refusal spends nothing.
1410        let alias = track_alias.into_inner();
1411        if let Some((side, held)) = self.alias_holder(alias, &track_namespace, &track_name) {
1412            return Err(EndpointError::TrackAliasInUse { alias, side, held });
1413        }
1414        let sub_id = self.subscribe_ids.allocate()?;
1415
1416        let mut sm = SubscriptionStateMachine::new();
1417        sm.on_subscribe_sent()?;
1418        self.subscriptions.insert(sub_id.into_inner(), sm);
1419        self.track_bindings.insert(
1420            (SubscribeSide::Ours, sub_id.into_inner()),
1421            TrackBinding { namespace: track_namespace.clone(), name: track_name.clone(), alias },
1422        );
1423
1424        let msg = ControlMessage::Subscribe(Subscribe {
1425            subscribe_id: sub_id,
1426            track_alias,
1427            track_namespace,
1428            track_name,
1429            subscriber_priority,
1430            group_order,
1431            filter_type,
1432            start_location,
1433            end_group,
1434            parameters: vec![],
1435        });
1436        Ok((sub_id, msg))
1437    }
1438
1439    /// Process an incoming SUBSCRIBE_OK.
1440    pub fn receive_subscribe_ok(&mut self, msg: &SubscribeOk) -> Result<(), EndpointError> {
1441        let id = msg.subscribe_id.into_inner();
1442        let sm = self.subscriptions.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1443        sm.on_subscribe_ok()?;
1444        Ok(())
1445    }
1446
1447    /// Process an incoming SUBSCRIBE_ERROR.
1448    pub fn receive_subscribe_error(&mut self, msg: &SubscribeError) -> Result<(), EndpointError> {
1449        let id = msg.subscribe_id.into_inner();
1450        // Section 7.16 gives SUBSCRIBE_ERROR a Track Alias field with one
1451        // meaning: an alias to retry the SUBSCRIBE with. Judged before the
1452        // subscription is ended, because the request it names is what says
1453        // which track the offered alias would be for.
1454        if SubscribeErrorCode::from_u64(msg.error_code.into_inner())
1455            == Some(SubscribeErrorCode::RetryTrackAlias)
1456        {
1457            if let Some(conflict) = self.conflicting_retry_alias(id, msg.track_alias.into_inner()) {
1458                return Err(self.fail_session(conflict));
1459            }
1460        }
1461        let sm = self.subscriptions.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1462        sm.on_subscribe_error()?;
1463        Ok(())
1464    }
1465
1466    /// Send an UNSUBSCRIBE message for an active subscription.
1467    pub fn unsubscribe(&mut self, subscribe_id: VarInt) -> Result<ControlMessage, EndpointError> {
1468        let id = subscribe_id.into_inner();
1469        let sm = self.subscriptions.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1470        sm.on_unsubscribe()?;
1471        Ok(ControlMessage::Unsubscribe(Unsubscribe { subscribe_id }))
1472    }
1473
1474    /// Send a SUBSCRIBE_UPDATE narrowing a subscription this endpoint opened.
1475    ///
1476    /// Section 7.5 gives the message to the subscriber, which is what this
1477    /// endpoint is for every subscription in `subscriptions`. No identifier is
1478    /// spent: the update's one identifier field names the subscription being
1479    /// modified rather than opening a request of its own.
1480    ///
1481    /// The narrowing rules the same section states are the caller's to keep.
1482    ///
1483    /// # Errors
1484    ///
1485    /// [`EndpointError::UnknownSubscribe`] when this endpoint opened no
1486    /// subscription under that identifier, and the subscription flow's own
1487    /// `InvalidTransition` when the one it names has already ended.
1488    pub fn subscribe_update(
1489        &mut self,
1490        subscribe_id: VarInt,
1491        start_group: VarInt,
1492        start_object: VarInt,
1493        end_group: VarInt,
1494        subscriber_priority: u8,
1495        parameters: Vec<KeyValuePair>,
1496    ) -> Result<ControlMessage, EndpointError> {
1497        self.require_active_or_err()?;
1498        let id = subscribe_id.into_inner();
1499        let sm = self.subscriptions.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1500        sm.on_subscribe_update()?;
1501        Ok(ControlMessage::SubscribeUpdate(SubscribeUpdate {
1502            subscribe_id,
1503            start_group,
1504            start_object,
1505            end_group,
1506            subscriber_priority,
1507            parameters,
1508        }))
1509    }
1510
1511    /// Process an incoming SUBSCRIBE_UPDATE.
1512    ///
1513    /// Section 7.5: "A subscriber issues a SUBSCRIBE_UPDATE to a publisher to
1514    /// request a change to an existing subscription." One that arrives is
1515    /// therefore about a subscription the **peer** opened, which is why it is
1516    /// looked for among those and not among this endpoint's own.
1517    ///
1518    /// # Errors
1519    ///
1520    /// [`EndpointError::UpdateForUnknownSubscribe`] when the identifier names no
1521    /// subscription the peer has opened in this session, and the subscription
1522    /// flow's own `InvalidTransition` when it names one that has already
1523    /// ended. Neither ends the session: Section 7.5 says SHOULD.
1524    pub fn receive_subscribe_update(&mut self, msg: &SubscribeUpdate) -> Result<(), EndpointError> {
1525        let id = msg.subscribe_id.into_inner();
1526        let sub = self
1527            .inbound_subscribes
1528            .get_mut(&id)
1529            .ok_or(EndpointError::UpdateForUnknownSubscribe(id))?;
1530        sub.state.on_subscribe_update_received()?;
1531        Ok(())
1532    }
1533
1534    /// Process an incoming SUBSCRIBE_DONE (subscriber side — publisher finished).
1535    pub fn receive_subscribe_done(&mut self, msg: &SubscribeDone) -> Result<(), EndpointError> {
1536        let id = msg.subscribe_id.into_inner();
1537        let sm = self.subscriptions.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1538        sm.on_subscribe_done()?;
1539        Ok(())
1540    }
1541
1542    // ── Answering a SUBSCRIBE the peer sent ────────────────────
1543
1544    /// The SUBSCRIBE the peer sent under `subscribe_id` and this endpoint has
1545    /// not answered yet.
1546    ///
1547    /// `None` once it has been answered, and for an identifier this session
1548    /// has no inbound subscription for.
1549    pub fn pending_subscribe(&self, subscribe_id: VarInt) -> Option<&Subscribe> {
1550        self.inbound_subscribes
1551            .get(&subscribe_id.into_inner())
1552            .filter(|s| s.state.state() == SubscriptionState::Subscribing)
1553            .map(|s| &s.message)
1554    }
1555
1556    /// How many SUBSCRIBEs the peer has sent that are still waiting for an
1557    /// answer.
1558    pub fn pending_subscribe_count(&self) -> usize {
1559        self.inbound_subscribes
1560            .values()
1561            .filter(|s| s.state.state() == SubscriptionState::Subscribing)
1562            .count()
1563    }
1564
1565    /// Build the SUBSCRIBE_OK accepting a subscription the peer opened.
1566    ///
1567    /// # Errors
1568    ///
1569    /// [`EndpointError::UnknownSubscribe`] if the peer opened no subscription
1570    /// under that identifier, and [`EndpointError::Subscription`] if it has
1571    /// already been answered.
1572    pub fn send_subscribe_ok(
1573        &mut self,
1574        subscribe_id: VarInt,
1575        expires: VarInt,
1576        group_order: GroupOrder,
1577        parameters: Vec<KeyValuePair>,
1578    ) -> Result<ControlMessage, EndpointError> {
1579        let id = subscribe_id.into_inner();
1580        let sub =
1581            self.inbound_subscribes.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1582        sub.state.on_subscribe_ok_sent()?;
1583        Ok(ControlMessage::SubscribeOk(SubscribeOk {
1584            subscribe_id,
1585            expires,
1586            group_order,
1587            content_exists: ContentExists::NoLargestLocation,
1588            largest_group_id: None,
1589            largest_object_id: None,
1590            parameters,
1591        }))
1592    }
1593
1594    /// Build the SUBSCRIBE_ERROR rejecting a subscription the peer opened.
1595    ///
1596    /// The Track Alias goes back out with the refusal because Section 7.16
1597    /// gives the field a use: an alias to retry with, when the code is 'Retry
1598    /// Track Alias'. Under any other code the peer reads nothing from it.
1599    ///
1600    /// # Errors
1601    ///
1602    /// [`EndpointError::UnknownSubscribe`] if the peer opened no subscription
1603    /// under that identifier, and [`EndpointError::Subscription`] if it has
1604    /// already been answered.
1605    pub fn send_subscribe_error(
1606        &mut self,
1607        subscribe_id: VarInt,
1608        error_code: VarInt,
1609        reason_phrase: Vec<u8>,
1610        track_alias: VarInt,
1611    ) -> Result<ControlMessage, EndpointError> {
1612        let id = subscribe_id.into_inner();
1613        let sub =
1614            self.inbound_subscribes.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1615        sub.state.on_subscribe_error_sent()?;
1616        Ok(ControlMessage::SubscribeError(SubscribeError {
1617            subscribe_id,
1618            error_code,
1619            reason_phrase,
1620            track_alias,
1621        }))
1622    }
1623
1624    /// Build the SUBSCRIBE_DONE ending a subscription this endpoint accepted.
1625    ///
1626    /// # Errors
1627    ///
1628    /// [`EndpointError::UnknownSubscribe`] if the peer opened no subscription
1629    /// under that identifier, and [`EndpointError::Subscription`] if it is not
1630    /// one this endpoint accepted and has not already ended.
1631    pub fn send_subscribe_done(
1632        &mut self,
1633        subscribe_id: VarInt,
1634        status_code: VarInt,
1635        reason_phrase: Vec<u8>,
1636    ) -> Result<ControlMessage, EndpointError> {
1637        let id = subscribe_id.into_inner();
1638        let sub =
1639            self.inbound_subscribes.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1640        sub.state.on_subscribe_done_sent()?;
1641        Ok(ControlMessage::SubscribeDone(SubscribeDone {
1642            subscribe_id,
1643            status_code,
1644            stream_count: VarInt::from_u64(0).unwrap(),
1645            reason_phrase,
1646        }))
1647    }
1648
1649    /// Process an incoming UNSUBSCRIBE, ending the subscription the peer
1650    /// opened and freeing the Track Alias it held.
1651    ///
1652    /// # Errors
1653    ///
1654    /// [`EndpointError::UnknownSubscribe`] if the peer opened no subscription
1655    /// under that identifier, and [`EndpointError::Subscription`] if it is not
1656    /// one this endpoint accepted and has not already ended.
1657    pub fn receive_unsubscribe(&mut self, msg: &Unsubscribe) -> Result<(), EndpointError> {
1658        let id = msg.subscribe_id.into_inner();
1659        let sub =
1660            self.inbound_subscribes.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1661        sub.state.on_unsubscribe_received()?;
1662        Ok(())
1663    }
1664
1665    // ── Fetch flow ─────────────────────────────────────────────
1666
1667    /// Send a FETCH message. Allocates a subscribe ID and creates a fetch state machine.
1668    #[allow(clippy::too_many_arguments)]
1669    pub fn fetch(
1670        &mut self,
1671        track_namespace: TrackNamespace,
1672        track_name: Vec<u8>,
1673        subscriber_priority: u8,
1674        group_order: GroupOrder,
1675        start_group: VarInt,
1676        start_object: VarInt,
1677        end_group: VarInt,
1678        end_object: VarInt,
1679    ) -> Result<(VarInt, ControlMessage), EndpointError> {
1680        self.require_active_or_err()?;
1681        let sub_id = self.subscribe_ids.allocate()?;
1682
1683        let mut sm = FetchStateMachine::new();
1684        sm.on_fetch_sent()?;
1685        self.fetches.insert(sub_id.into_inner(), sm);
1686
1687        let msg = ControlMessage::Fetch(Fetch {
1688            subscribe_id: sub_id,
1689            subscriber_priority,
1690            group_order,
1691            fetch_type: FetchType::Standalone,
1692            track_namespace: Some(track_namespace),
1693            track_name: Some(track_name),
1694            start_group: Some(start_group),
1695            start_object: Some(start_object),
1696            end_group: Some(end_group),
1697            end_object: Some(end_object),
1698            joining_subscribe_id: None,
1699            preceding_group_offset: None,
1700            parameters: vec![],
1701        });
1702        Ok((sub_id, msg))
1703    }
1704
1705    /// Send a joining FETCH message that attaches to an existing subscription.
1706    /// Allocates a new subscribe ID for the fetch and tracks it in its own
1707    /// fetch state machine.
1708    pub fn joining_fetch(
1709        &mut self,
1710        subscriber_priority: u8,
1711        group_order: GroupOrder,
1712        joining_subscribe_id: VarInt,
1713        preceding_group_offset: VarInt,
1714    ) -> Result<(VarInt, ControlMessage), EndpointError> {
1715        self.require_active_or_err()?;
1716        let sub_id = self.subscribe_ids.allocate()?;
1717
1718        let mut sm = FetchStateMachine::new();
1719        sm.on_fetch_sent()?;
1720        self.fetches.insert(sub_id.into_inner(), sm);
1721
1722        let msg = ControlMessage::Fetch(Fetch {
1723            subscribe_id: sub_id,
1724            subscriber_priority,
1725            group_order,
1726            fetch_type: FetchType::Joining,
1727            track_namespace: None,
1728            track_name: None,
1729            start_group: None,
1730            start_object: None,
1731            end_group: None,
1732            end_object: None,
1733            joining_subscribe_id: Some(joining_subscribe_id),
1734            preceding_group_offset: Some(preceding_group_offset),
1735            parameters: vec![],
1736        });
1737        Ok((sub_id, msg))
1738    }
1739
1740    /// Process an incoming FETCH_OK.
1741    pub fn receive_fetch_ok(&mut self, msg: &message::FetchOk) -> Result<(), EndpointError> {
1742        let id = msg.subscribe_id.into_inner();
1743        let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1744        sm.on_fetch_ok()?;
1745        Ok(())
1746    }
1747
1748    /// Process an incoming FETCH_ERROR.
1749    pub fn receive_fetch_error(&mut self, msg: &message::FetchError) -> Result<(), EndpointError> {
1750        let id = msg.subscribe_id.into_inner();
1751        let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1752        sm.on_fetch_error()?;
1753        Ok(())
1754    }
1755
1756    /// Send a FETCH_CANCEL message.
1757    pub fn fetch_cancel(&mut self, subscribe_id: VarInt) -> Result<ControlMessage, EndpointError> {
1758        let id = subscribe_id.into_inner();
1759        let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1760        sm.on_fetch_cancel()?;
1761        Ok(ControlMessage::FetchCancel(FetchCancel { subscribe_id }))
1762    }
1763
1764    /// Notify that a fetch data stream received FIN.
1765    ///
1766    /// It may arrive before the FETCH_OK or FETCH_ERROR answering the
1767    /// request, which leaves the fetch in `FetchState::Unanswered` until the
1768    /// answer lands.
1769    pub fn on_fetch_stream_fin(&mut self, subscribe_id: VarInt) -> Result<(), EndpointError> {
1770        let id = subscribe_id.into_inner();
1771        let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1772        sm.on_stream_fin()?;
1773        Ok(())
1774    }
1775
1776    /// Notify that a fetch data stream was reset.
1777    ///
1778    /// As with a FIN, it may arrive before the answer to the request.
1779    pub fn on_fetch_stream_reset(&mut self, subscribe_id: VarInt) -> Result<(), EndpointError> {
1780        let id = subscribe_id.into_inner();
1781        let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1782        sm.on_stream_reset()?;
1783        Ok(())
1784    }
1785
1786    // ── Answering a FETCH the peer sent ────────────────────────
1787
1788    /// Process an incoming FETCH, recording the fetch it opens.
1789    ///
1790    /// The Subscribe ID is checked here, in the same sequence a SUBSCRIBE
1791    /// draws from: Section 7.7 gives it the same "unique and monotonically
1792    /// increasing within a session" requirement.
1793    ///
1794    /// A Joining Fetch is recorded like any other. Section 7.7 answers one
1795    /// naming a subscription this session cannot join with a refusal and not
1796    /// a session close, and a refusal is a message this endpoint has to
1797    /// build, so the request it refuses has to be on record first.
1798    ///
1799    /// # Errors
1800    ///
1801    /// Whatever [`Self::validate_peer_subscribe_id`] answers, and the fetch
1802    /// flow's own `InvalidTransition` for a second FETCH under an identifier
1803    /// already carrying one.
1804    pub fn receive_fetch(&mut self, msg: &Fetch) -> Result<(), EndpointError> {
1805        self.validate_peer_subscribe_id(msg.subscribe_id.into_inner())?;
1806        let id = msg.subscribe_id.into_inner();
1807        let unjoinable = self.joining_subscription_missing(msg);
1808        let mut state = FetchStateMachine::new();
1809        state.on_fetch_received()?;
1810        self.inbound_fetches.insert(id, InboundFetch { message: msg.clone(), state, unjoinable });
1811        Ok(())
1812    }
1813
1814    /// The FETCH the peer sent under `subscribe_id` and this endpoint has not
1815    /// answered yet.
1816    ///
1817    /// `None` once it has been answered, and for an identifier this session
1818    /// has no inbound fetch for. The record itself lives on past the answer,
1819    /// because the fetch is not over until its data stream is.
1820    pub fn pending_fetch(&self, subscribe_id: VarInt) -> Option<&Fetch> {
1821        self.inbound_fetches
1822            .get(&subscribe_id.into_inner())
1823            .filter(|f| matches!(f.state.state(), FetchState::Pending | FetchState::Unanswered))
1824            .map(|f| &f.message)
1825    }
1826
1827    /// How many FETCHes the peer has sent that are still waiting for an
1828    /// answer.
1829    pub fn pending_fetch_count(&self) -> usize {
1830        self.inbound_fetches
1831            .values()
1832            .filter(|f| matches!(f.state.state(), FetchState::Pending | FetchState::Unanswered))
1833            .count()
1834    }
1835
1836    /// The identifier an arriving Joining Fetch names, when this session has
1837    /// no subscription it may join.
1838    ///
1839    /// Section 7.7:
1840    /// "If a publisher receives a Joining Fetch with a Subscribe ID
1841    /// that does not correspond to an existing Subscribe, it MUST respond with
1842    /// a Fetch Error."
1843    ///
1844    /// The verdict is taken as the FETCH arrives, because that is the moment
1845    /// the sentence names, and it is kept. A subscription that ends between
1846    /// the FETCH and its answer does not turn a fetch that could be joined
1847    /// into one that could not.
1848    ///
1849    /// A standalone fetch names none and answers `None`, and so does a joining
1850    /// one whose subscription is live. The subscription is one the peer opened,
1851    /// because the peer is the end that fetches and this endpoint is the one
1852    /// answering.
1853    /// "Existing" is read as "has not ended". Draft-16 Section 9.16.2 states
1854    /// the same rule with the states named - "in the Established or Pending
1855    /// (subscriber) states" - which is the same set read the same way.
1856    fn joining_subscription_missing(&self, msg: &Fetch) -> Option<u64> {
1857        let joined = msg.joining_subscribe_id?.into_inner();
1858        let live = self
1859            .inbound_subscribes
1860            .get(&joined)
1861            .is_some_and(|s| s.state.state() != SubscriptionState::Done);
1862        if live {
1863            None
1864        } else {
1865            Some(joined)
1866        }
1867    }
1868
1869    /// Build the FETCH_OK accepting a fetch the peer opened.
1870    ///
1871    /// # Errors
1872    ///
1873    /// [`EndpointError::UnknownSubscribe`] if the peer opened no fetch under
1874    /// that identifier, [`EndpointError::UnjoinableSubscription`] for a
1875    /// Joining Fetch naming a subscription this session cannot join, and the
1876    /// fetch flow's own `InvalidTransition` for a second answer: Section 4.3
1877    /// says the publisher "MUST send exactly one FETCH_OK or FETCH_ERROR in
1878    /// response to a FETCH".
1879    pub fn send_fetch_ok(
1880        &mut self,
1881        subscribe_id: VarInt,
1882        group_order: GroupOrder,
1883        end_of_track: u8,
1884        largest_group_id: VarInt,
1885        largest_object_id: VarInt,
1886        parameters: Vec<KeyValuePair>,
1887    ) -> Result<ControlMessage, EndpointError> {
1888        let id = subscribe_id.into_inner();
1889        let unjoinable =
1890            self.inbound_fetches.get(&id).ok_or(EndpointError::UnknownSubscribe(id))?.unjoinable;
1891        if let Some(joining) = unjoinable {
1892            return Err(EndpointError::UnjoinableSubscription { fetch: id, joining });
1893        }
1894        let fetch = self.inbound_fetches.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1895        fetch.state.on_fetch_ok_sent()?;
1896        Ok(ControlMessage::FetchOk(message::FetchOk {
1897            subscribe_id,
1898            group_order,
1899            end_of_track,
1900            largest_group_id,
1901            largest_object_id,
1902            parameters,
1903        }))
1904    }
1905
1906    /// Build the FETCH_ERROR refusing a fetch the peer opened.
1907    ///
1908    /// # Errors
1909    ///
1910    /// [`EndpointError::UnknownSubscribe`] if the peer opened no fetch under
1911    /// that identifier, and the fetch flow's own `InvalidTransition` if it has
1912    /// already been answered.
1913    pub fn send_fetch_error(
1914        &mut self,
1915        subscribe_id: VarInt,
1916        error_code: VarInt,
1917        reason_phrase: Vec<u8>,
1918    ) -> Result<ControlMessage, EndpointError> {
1919        let id = subscribe_id.into_inner();
1920        let fetch = self.inbound_fetches.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1921        fetch.state.on_fetch_error_sent()?;
1922        Ok(ControlMessage::FetchError(message::FetchError {
1923            subscribe_id,
1924            error_code,
1925            reason_phrase,
1926        }))
1927    }
1928
1929    /// Process an incoming FETCH_CANCEL, ending the fetch the peer opened.
1930    ///
1931    /// Section 7.8: the subscriber sends it to stop a fetch it no longer
1932    /// wants, so the record this endpoint serves the fetch from is the one it
1933    /// ends.
1934    ///
1935    /// # Errors
1936    ///
1937    /// [`EndpointError::UnknownSubscribe`] if the peer opened no fetch under
1938    /// that identifier, and the fetch flow's own `InvalidTransition` for a fetch
1939    /// that has already ended.
1940    pub fn receive_fetch_cancel(&mut self, msg: &FetchCancel) -> Result<(), EndpointError> {
1941        let id = msg.subscribe_id.into_inner();
1942        let fetch = self.inbound_fetches.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1943        fetch.state.on_fetch_cancel_received()?;
1944        Ok(())
1945    }
1946
1947    /// Note that this endpoint finished the data stream serving a fetch the
1948    /// peer opened.
1949    ///
1950    /// A fetch is over when its answer and its data stream have both settled,
1951    /// and this is the second of those for the end that serves it.
1952    ///
1953    /// # Errors
1954    ///
1955    /// [`EndpointError::UnknownSubscribe`] if the peer opened no fetch under
1956    /// that identifier, and the fetch flow's own `InvalidTransition` from a state
1957    /// the stream cannot close from.
1958    pub fn on_peer_fetch_stream_fin(&mut self, subscribe_id: VarInt) -> Result<(), EndpointError> {
1959        let id = subscribe_id.into_inner();
1960        let fetch = self.inbound_fetches.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1961        fetch.state.on_stream_fin_sent()?;
1962        Ok(())
1963    }
1964
1965    // ── Subscribe Announces flow ───────────────────────────────
1966
1967    /// Send a SUBSCRIBE_ANNOUNCES message.
1968    ///
1969    /// Section 7.13: "A subscriber cannot make overlapping namespace
1970    /// subscriptions on a single session."
1971    ///
1972    /// # Errors
1973    ///
1974    /// The session error when the session is not established, and
1975    /// [`EndpointError::OwnPrefixOverlap`] when the prefix overlaps one this
1976    /// endpoint has already subscribed to.
1977    pub fn subscribe_announces(
1978        &mut self,
1979        track_namespace_prefix: TrackNamespace,
1980    ) -> Result<ControlMessage, EndpointError> {
1981        self.require_active_or_err()?;
1982        let key = track_namespace_prefix.0.clone();
1983        // The subscriber's half of the rule, refused before the message
1984        // exists. A publisher that follows this draft would answer it with
1985        // SUBSCRIBE_ANNOUNCES_ERROR, so building it wastes a round trip and
1986        // leaves this endpoint holding a namespace subscription that is not
1987        // going to open.
1988        if self.subscribe_announces.keys().any(|k| prefixes_overlap(k, &key)) {
1989            return Err(EndpointError::OwnPrefixOverlap);
1990        }
1991        let mut sm = SubscribeAnnouncesStateMachine::new();
1992        sm.on_subscribe_announces_sent()?;
1993        self.subscribe_announces.insert(key, sm);
1994        Ok(ControlMessage::SubscribeAnnounces(SubscribeAnnounces {
1995            track_namespace_prefix,
1996            parameters: vec![],
1997        }))
1998    }
1999
2000    /// Process an incoming SUBSCRIBE_ANNOUNCES_OK.
2001    pub fn receive_subscribe_announces_ok(
2002        &mut self,
2003        msg: &SubscribeAnnouncesOk,
2004    ) -> Result<(), EndpointError> {
2005        let sm = self
2006            .subscribe_announces
2007            .get_mut(&msg.track_namespace_prefix.0)
2008            .ok_or(EndpointError::UnknownNamespace)?;
2009        sm.on_subscribe_announces_ok()?;
2010        Ok(())
2011    }
2012
2013    /// Process an incoming SUBSCRIBE_ANNOUNCES_ERROR.
2014    pub fn receive_subscribe_announces_error(
2015        &mut self,
2016        msg: &SubscribeAnnouncesError,
2017    ) -> Result<(), EndpointError> {
2018        let sm = self
2019            .subscribe_announces
2020            .get_mut(&msg.track_namespace_prefix.0)
2021            .ok_or(EndpointError::UnknownNamespace)?;
2022        sm.on_subscribe_announces_error()?;
2023        Ok(())
2024    }
2025
2026    /// Send an UNSUBSCRIBE_ANNOUNCES message.
2027    pub fn unsubscribe_announces(
2028        &mut self,
2029        track_namespace_prefix: TrackNamespace,
2030    ) -> Result<ControlMessage, EndpointError> {
2031        let sm = self
2032            .subscribe_announces
2033            .get_mut(&track_namespace_prefix.0)
2034            .ok_or(EndpointError::UnknownNamespace)?;
2035        sm.on_unsubscribe_announces()?;
2036        Ok(ControlMessage::UnsubscribeAnnounces(UnsubscribeAnnounces { track_namespace_prefix }))
2037    }
2038
2039    // ── Answering a SUBSCRIBE_ANNOUNCES the peer sent ──────────
2040
2041    /// Process an incoming SUBSCRIBE_ANNOUNCES, recording the namespace
2042    /// subscription it opens.
2043    ///
2044    /// Section 7.13: "The subscriber sends the SUBSCRIBE_ANNOUNCES control
2045    /// message to a publisher to request the current set of matching
2046    /// announcements, as well as future updates to the set."
2047    ///
2048    /// The set it asks for is this endpoint's to decide, and deciding needs
2049    /// both the request and somewhere to answer from. The record holds the
2050    /// message and not only its state, because every message that answers
2051    /// this request or ends it names the prefix the request carried, and
2052    /// nothing else here has it.
2053    ///
2054    /// # Errors
2055    ///
2056    /// The session error when the session is not established, and
2057    /// [`EndpointError::PeerPrefixOverlap`] when the prefix overlaps one the
2058    /// peer has already subscribed to.
2059    pub fn receive_subscribe_announces(
2060        &mut self,
2061        msg: &SubscribeAnnounces,
2062    ) -> Result<(), EndpointError> {
2063        self.require_active_or_err()?;
2064        // Judged on arrival, because that is the moment the sentence names:
2065        // "if a publisher receives a SUBSCRIBE_ANNOUNCES ... it MUST respond
2066        // with SUBSCRIBE_ANNOUNCES_ERROR". Nothing is recorded for a request
2067        // this endpoint may not accept, so no later call can accept it.
2068        if self.peer_prefix_overlap(&msg.track_namespace_prefix) {
2069            return Err(EndpointError::PeerPrefixOverlap);
2070        }
2071        let mut state = SubscribeAnnouncesStateMachine::new();
2072        state.on_subscribe_announces_received()?;
2073        self.inbound_subscribe_announces.insert(
2074            msg.track_namespace_prefix.0.clone(),
2075            InboundSubscribeAnnounces { message: msg.clone(), state },
2076        );
2077        Ok(())
2078    }
2079
2080    /// Whether `prefix` overlaps a namespace subscription the peer has already
2081    /// made on this session.
2082    ///
2083    /// Every one of them counts, including a subscription the peer has since
2084    /// withdrawn: the sentence weighs the arriving prefix against "an earlier
2085    /// SUBSCRIBE_ANNOUNCES", and one that has ended was still earlier.
2086    ///
2087    /// Namespace subscriptions this endpoint made are a separate set and are
2088    /// not consulted. This endpoint is the subscriber for those, so a prefix
2089    /// it asked about says nothing about what the peer may ask about.
2090    fn peer_prefix_overlap(&self, prefix: &TrackNamespace) -> bool {
2091        self.inbound_subscribe_announces.keys().any(|k| prefixes_overlap(k, &prefix.0))
2092    }
2093
2094    /// The SUBSCRIBE_ANNOUNCES the peer sent for `prefix` and this endpoint
2095    /// has not answered yet.
2096    ///
2097    /// `None` once it has been answered, and for a prefix the peer has
2098    /// subscribed to nothing under. The record itself lives on past the
2099    /// answer, because a namespace subscription that was accepted is not over
2100    /// until it is withdrawn.
2101    pub fn pending_subscribe_announces(
2102        &self,
2103        prefix: &TrackNamespace,
2104    ) -> Option<&SubscribeAnnounces> {
2105        self.inbound_subscribe_announces
2106            .get(&prefix.0)
2107            .filter(|s| s.state.state() == SubscribeAnnouncesState::Pending)
2108            .map(|s| &s.message)
2109    }
2110
2111    /// How many namespace subscriptions the peer has made that are still
2112    /// waiting for an answer.
2113    pub fn pending_subscribe_announces_count(&self) -> usize {
2114        self.inbound_subscribe_announces
2115            .values()
2116            .filter(|s| s.state.state() == SubscribeAnnouncesState::Pending)
2117            .count()
2118    }
2119
2120    /// Build the SUBSCRIBE_ANNOUNCES_OK accepting a namespace subscription
2121    /// the peer made.
2122    ///
2123    /// Section 4.1: "A publisher MUST send exactly one SUBSCRIBE_ANNOUNCES_OK
2124    /// or SUBSCRIBE_ANNOUNCES_ERROR in response to a SUBSCRIBE_ANNOUNCES."
2125    ///
2126    /// One answer and no second one: the flow moves on the first, and a
2127    /// second call finds a record that has left Pending.
2128    ///
2129    /// # Errors
2130    ///
2131    /// [`EndpointError::UnknownPeerNamespaceSubscription`] if the peer has
2132    /// subscribed to nothing under that prefix, and the namespace flow's own
2133    /// `InvalidTransition` for a request already answered.
2134    pub fn send_subscribe_announces_ok(
2135        &mut self,
2136        track_namespace_prefix: TrackNamespace,
2137    ) -> Result<ControlMessage, EndpointError> {
2138        let sub = self
2139            .inbound_subscribe_announces
2140            .get_mut(&track_namespace_prefix.0)
2141            .ok_or(EndpointError::UnknownPeerNamespaceSubscription)?;
2142        sub.state.on_subscribe_announces_ok_sent()?;
2143        Ok(ControlMessage::SubscribeAnnouncesOk(SubscribeAnnouncesOk { track_namespace_prefix }))
2144    }
2145
2146    /// Build the SUBSCRIBE_ANNOUNCES_ERROR refusing a namespace subscription
2147    /// the peer made.
2148    ///
2149    /// The other half of the same sentence: one message back, whichever of
2150    /// the two it is.
2151    ///
2152    /// # Errors
2153    ///
2154    /// [`EndpointError::UnknownPeerNamespaceSubscription`] if the peer has
2155    /// subscribed to nothing under that prefix, and the namespace flow's own
2156    /// `InvalidTransition` for a request already answered.
2157    pub fn send_subscribe_announces_error(
2158        &mut self,
2159        track_namespace_prefix: TrackNamespace,
2160        error_code: VarInt,
2161        reason_phrase: Vec<u8>,
2162    ) -> Result<ControlMessage, EndpointError> {
2163        let sub = self
2164            .inbound_subscribe_announces
2165            .get_mut(&track_namespace_prefix.0)
2166            .ok_or(EndpointError::UnknownPeerNamespaceSubscription)?;
2167        sub.state.on_subscribe_announces_error_sent()?;
2168        Ok(ControlMessage::SubscribeAnnouncesError(SubscribeAnnouncesError {
2169            track_namespace_prefix,
2170            error_code,
2171            reason_phrase,
2172        }))
2173    }
2174
2175    /// Process an incoming UNSUBSCRIBE_ANNOUNCES, ending the namespace
2176    /// subscription the peer made.
2177    ///
2178    /// Section 7.14: "A subscriber issues a UNSUBSCRIBE_ANNOUNCES message to
2179    /// a publisher indicating it is no longer interested in ANNOUNCE and
2180    /// UNANNOUNCE messages for the specified track namespace prefix."
2181    ///
2182    /// The subscription it ends is the peer's, so the record it reads is the
2183    /// one this endpoint keeps of what the peer subscribed to. One this
2184    /// endpoint made is withdrawn by [`Endpoint::unsubscribe_announces`],
2185    /// which is the same message travelling the other way.
2186    ///
2187    /// # Errors
2188    ///
2189    /// [`EndpointError::UnknownPeerNamespaceSubscription`] if the peer has no
2190    /// live namespace subscription for that prefix, and the namespace flow's
2191    /// own `InvalidTransition` for one this endpoint never accepted.
2192    pub fn receive_unsubscribe_announces(
2193        &mut self,
2194        msg: &UnsubscribeAnnounces,
2195    ) -> Result<(), EndpointError> {
2196        let sub = self
2197            .inbound_subscribe_announces
2198            .get_mut(&msg.track_namespace_prefix.0)
2199            .ok_or(EndpointError::UnknownPeerNamespaceSubscription)?;
2200        sub.state.on_unsubscribe_announces_received()?;
2201        Ok(())
2202    }
2203
2204    // ── Announce flow ──────────────────────────────────────────
2205
2206    /// Send an ANNOUNCE message.
2207    pub fn announce(
2208        &mut self,
2209        track_namespace: TrackNamespace,
2210    ) -> Result<ControlMessage, EndpointError> {
2211        self.require_active_or_err()?;
2212        let key = track_namespace.0.clone();
2213        let mut sm = AnnounceStateMachine::new();
2214        sm.on_announce_sent()?;
2215        self.announces.insert(key, sm);
2216        Ok(ControlMessage::Announce(Announce { track_namespace, parameters: vec![] }))
2217    }
2218
2219    /// Process an incoming ANNOUNCE_OK.
2220    pub fn receive_announce_ok(&mut self, msg: &AnnounceOk) -> Result<(), EndpointError> {
2221        let sm = self
2222            .announces
2223            .get_mut(&msg.track_namespace.0)
2224            .ok_or(EndpointError::UnknownNamespace)?;
2225        sm.on_announce_ok()?;
2226        Ok(())
2227    }
2228
2229    /// Process an incoming ANNOUNCE_ERROR.
2230    pub fn receive_announce_error(&mut self, msg: &AnnounceError) -> Result<(), EndpointError> {
2231        let sm = self
2232            .announces
2233            .get_mut(&msg.track_namespace.0)
2234            .ok_or(EndpointError::UnknownNamespace)?;
2235        sm.on_announce_error()?;
2236        Ok(())
2237    }
2238
2239    /// Process an incoming ANNOUNCE_CANCEL.
2240    pub fn receive_announce_cancel(&mut self, msg: &AnnounceCancel) -> Result<(), EndpointError> {
2241        let sm = self
2242            .announces
2243            .get_mut(&msg.track_namespace.0)
2244            .ok_or(EndpointError::UnknownNamespace)?;
2245        sm.on_announce_cancel()?;
2246        Ok(())
2247    }
2248
2249    /// Send an UNANNOUNCE message (publisher withdrawing).
2250    pub fn unannounce(
2251        &mut self,
2252        track_namespace: TrackNamespace,
2253    ) -> Result<ControlMessage, EndpointError> {
2254        let sm =
2255            self.announces.get_mut(&track_namespace.0).ok_or(EndpointError::UnknownNamespace)?;
2256        sm.on_unannounce()?;
2257        Ok(ControlMessage::Unannounce(Unannounce { track_namespace }))
2258    }
2259
2260    // ── Answering an ANNOUNCE the peer sent ────────────────────
2261
2262    /// Process an incoming ANNOUNCE, recording the announcement it makes.
2263    ///
2264    /// Section 7.22: "The publisher sends the ANNOUNCE control message to
2265    /// advertise where the receiver can route SUBSCRIBEs for tracks within the
2266    /// announced Track Namespace. The receiver verifies the publisher is
2267    /// authorized to publish tracks under this namespace."
2268    ///
2269    /// Verifying is the application's to do, and it needs both the message to
2270    /// verify and somewhere to answer from. This draft's ANNOUNCE carries no
2271    /// Request ID, so the namespace it names is what the record is filed under,
2272    /// and a second one naming a namespace already held replaces it. No
2273    /// sentence makes a repeat an error, and the newest advertisement is the
2274    /// one an answer has to be built from.
2275    ///
2276    /// # Errors
2277    ///
2278    /// The session error when the session is not established.
2279    pub fn receive_announce(&mut self, msg: &Announce) -> Result<(), EndpointError> {
2280        self.require_active_or_err()?;
2281        let key = msg.track_namespace.0.clone();
2282        let mut state = AnnounceStateMachine::new();
2283        state.on_announce_received()?;
2284        self.inbound_announces.insert(key, InboundAnnounce { message: msg.clone(), state });
2285        Ok(())
2286    }
2287
2288    /// The ANNOUNCE the peer sent for `track_namespace` and this endpoint has
2289    /// not answered yet.
2290    ///
2291    /// `None` once it has been answered, and for a namespace the peer has
2292    /// announced nothing under. The record itself lives on past the answer,
2293    /// because an announcement that was accepted is not over until it is
2294    /// withdrawn or cancelled.
2295    pub fn pending_announce(&self, track_namespace: &TrackNamespace) -> Option<&Announce> {
2296        self.inbound_announces
2297            .get(&track_namespace.0)
2298            .filter(|a| a.state.state() == AnnounceState::Pending)
2299            .map(|a| &a.message)
2300    }
2301
2302    /// How many announcements the peer has made that are still waiting for an
2303    /// answer.
2304    pub fn pending_announce_count(&self) -> usize {
2305        self.inbound_announces
2306            .values()
2307            .filter(|a| a.state.state() == AnnounceState::Pending)
2308            .count()
2309    }
2310
2311    /// Build the ANNOUNCE_OK accepting an announcement the peer made.
2312    ///
2313    /// Section 4.2: "A subscriber MUST send exactly one ANNOUNCE_OK or
2314    /// ANNOUNCE_ERROR in response to an ANNOUNCE. The publisher SHOULD close
2315    /// the session with a protocol error if it receives more than one."
2316    ///
2317    /// One answer and no second one: the flow moves on the first, and a second
2318    /// call finds a record that has left Pending.
2319    ///
2320    /// # Errors
2321    ///
2322    /// [`EndpointError::UnknownPeerNamespace`] if the peer has announced
2323    /// nothing under that namespace, and the namespace flow's own
2324    /// `InvalidTransition` for an announcement already answered.
2325    pub fn send_announce_ok(
2326        &mut self,
2327        track_namespace: TrackNamespace,
2328    ) -> Result<ControlMessage, EndpointError> {
2329        let ann = self
2330            .inbound_announces
2331            .get_mut(&track_namespace.0)
2332            .ok_or(EndpointError::UnknownPeerNamespace)?;
2333        ann.state.on_announce_ok_sent()?;
2334        Ok(ControlMessage::AnnounceOk(AnnounceOk { track_namespace }))
2335    }
2336
2337    /// Build the ANNOUNCE_ERROR refusing an announcement the peer made.
2338    ///
2339    /// The same sentence in Section 4.2 answers both ways: one message back and
2340    /// no second one, whichever of the two it is.
2341    ///
2342    /// # Errors
2343    ///
2344    /// [`EndpointError::UnknownPeerNamespace`] if the peer has announced
2345    /// nothing under that namespace, and the namespace flow's own
2346    /// `InvalidTransition` for an announcement already answered.
2347    pub fn send_announce_error(
2348        &mut self,
2349        track_namespace: TrackNamespace,
2350        error_code: VarInt,
2351        reason_phrase: Vec<u8>,
2352    ) -> Result<ControlMessage, EndpointError> {
2353        let ann = self
2354            .inbound_announces
2355            .get_mut(&track_namespace.0)
2356            .ok_or(EndpointError::UnknownPeerNamespace)?;
2357        ann.state.on_announce_error_sent()?;
2358        Ok(ControlMessage::AnnounceError(AnnounceError {
2359            track_namespace,
2360            error_code,
2361            reason_phrase,
2362        }))
2363    }
2364
2365    /// Process an incoming UNANNOUNCE, ending the announcement the peer made.
2366    ///
2367    /// Section 7.23: "The publisher sends the UNANNOUNCE control message to
2368    /// indicate its intent to stop serving new subscriptions for tracks within
2369    /// the provided Track Namespace."
2370    ///
2371    /// The announcement it ends is the peer's, so the record it reads is the
2372    /// one this endpoint keeps of what the peer announced. An announcement this
2373    /// endpoint made is withdrawn by [`Self::unannounce`], which is the same
2374    /// message travelling the other way.
2375    ///
2376    /// # Errors
2377    ///
2378    /// [`EndpointError::UnknownPeerNamespace`] if the peer has no live
2379    /// announcement for that namespace, and the namespace flow's own
2380    /// `InvalidTransition` for one this endpoint never accepted.
2381    pub fn receive_unannounce(&mut self, msg: &Unannounce) -> Result<(), EndpointError> {
2382        let ann = self
2383            .inbound_announces
2384            .get_mut(&msg.track_namespace.0)
2385            .ok_or(EndpointError::UnknownPeerNamespace)?;
2386        ann.state.on_unannounce_received()?;
2387        Ok(())
2388    }
2389
2390    /// Build the ANNOUNCE_CANCEL revoking an acceptance.
2391    ///
2392    /// Section 6.2 names what a cancellation revokes: a namespace "it
2393    /// previously responded ANNOUNCE_OK to". Section 7.11 says what it does:
2394    /// the subscriber "will stop sending new subscriptions for tracks within
2395    /// the provided Track Namespace".
2396    ///
2397    /// Previously responded ANNOUNCE_OK to is a state, and it is Active: an
2398    /// announcement reaches it by being accepted and no other way. One still
2399    /// waiting for an answer, one refused and one already ended are all refused
2400    /// here rather than sent.
2401    ///
2402    /// The announcement is the peer's. An announcement this endpoint made is
2403    /// not cancelled by its own publisher; the peer cancels it, and that
2404    /// arrives at [`Self::receive_announce_cancel`].
2405    ///
2406    /// # Errors
2407    ///
2408    /// [`EndpointError::UnknownPeerNamespace`] if the peer has no live
2409    /// announcement for that namespace, and the namespace flow's own
2410    /// `InvalidTransition` for one this endpoint never accepted.
2411    pub fn announce_cancel(
2412        &mut self,
2413        track_namespace: TrackNamespace,
2414        error_code: VarInt,
2415        reason_phrase: Vec<u8>,
2416    ) -> Result<ControlMessage, EndpointError> {
2417        let ann = self
2418            .inbound_announces
2419            .get_mut(&track_namespace.0)
2420            .ok_or(EndpointError::UnknownPeerNamespace)?;
2421        ann.state.on_announce_cancel_sent()?;
2422        Ok(ControlMessage::AnnounceCancel(AnnounceCancel {
2423            track_namespace,
2424            error_code,
2425            reason_phrase,
2426        }))
2427    }
2428
2429    // ── Track Status flow ──────────────────────────────────────
2430
2431    /// Send a TRACK_STATUS_REQUEST message.
2432    pub fn track_status_request(
2433        &mut self,
2434        track_namespace: TrackNamespace,
2435        track_name: Vec<u8>,
2436    ) -> Result<ControlMessage, EndpointError> {
2437        self.require_active_or_err()?;
2438        let key = (track_namespace.0.clone(), track_name.clone());
2439        let mut sm = TrackStatusStateMachine::new();
2440        sm.on_track_status_request_sent()?;
2441        self.track_statuses.insert(key, sm);
2442        Ok(ControlMessage::TrackStatusRequest(TrackStatusRequest { track_namespace, track_name }))
2443    }
2444
2445    /// Process an incoming TRACK_STATUS reply.
2446    pub fn receive_track_status(&mut self, msg: &TrackStatus) -> Result<(), EndpointError> {
2447        let key = (msg.track_namespace.0.clone(), msg.track_name.clone());
2448        let sm = self.track_statuses.get_mut(&key).ok_or(EndpointError::UnknownTrackStatus)?;
2449        sm.on_track_status()?;
2450        Ok(())
2451    }
2452
2453    // ── Answering a TRACK_STATUS_REQUEST the peer sent ─────────
2454
2455    /// Process an incoming TRACK_STATUS_REQUEST, recording what the peer asked
2456    /// about.
2457    ///
2458    /// Section 7.12: "A potential subscriber sends a 'TRACK_STATUS_REQUEST'
2459    /// message on the control stream to obtain information about the current
2460    /// status of a given track."
2461    ///
2462    /// Answering is the application's to do, and it needs both the request and
2463    /// somewhere to answer from. This draft's request carries no Request ID, so
2464    /// the track it names is what the record is filed under, and a second
2465    /// request for a track already asked about replaces it. No sentence makes a
2466    /// repeat an error, and the newest request is the one an answer has to be
2467    /// built from.
2468    ///
2469    /// # Errors
2470    ///
2471    /// The session error when the session is not established.
2472    pub fn receive_track_status_request(
2473        &mut self,
2474        msg: &TrackStatusRequest,
2475    ) -> Result<(), EndpointError> {
2476        self.require_active_or_err()?;
2477        let key = (msg.track_namespace.0.clone(), msg.track_name.clone());
2478        let mut state = TrackStatusStateMachine::new();
2479        state.on_track_status_request_received()?;
2480        self.inbound_track_statuses.insert(key, InboundTrackStatus { message: msg.clone(), state });
2481        Ok(())
2482    }
2483
2484    /// The TRACK_STATUS_REQUEST the peer sent about this track and this
2485    /// endpoint has not answered yet.
2486    ///
2487    /// `None` once it has been answered, and for a track the peer has asked
2488    /// nothing about.
2489    pub fn pending_track_status_request(
2490        &self,
2491        track_namespace: &TrackNamespace,
2492        track_name: &[u8],
2493    ) -> Option<&TrackStatusRequest> {
2494        self.inbound_track_statuses
2495            .get(&(track_namespace.0.clone(), track_name.to_vec()))
2496            .filter(|t| t.state.state() == TrackStatusState::Pending)
2497            .map(|t| &t.message)
2498    }
2499
2500    /// How many track statuses the peer has asked about that are still waiting
2501    /// for an answer.
2502    pub fn pending_track_status_request_count(&self) -> usize {
2503        self.inbound_track_statuses
2504            .values()
2505            .filter(|t| t.state.state() == TrackStatusState::Pending)
2506            .count()
2507    }
2508
2509    /// Build the TRACK_STATUS answering a request the peer sent.
2510    ///
2511    /// Section 7.12 leaves the answering end no discretion about whether to
2512    /// answer: "A TRACK_STATUS message MUST be sent in response to each
2513    /// TRACK_STATUS_REQUEST." What it bounds is how many, and that half is what
2514    /// the record carries: the request leaves `Pending` on the first answer, so
2515    /// a second call finds nothing left to answer.
2516    ///
2517    /// Section 7.24 says which track the answer is about, and this draft has
2518    /// no identifier to say it with, so the caller names the track and the
2519    /// message repeats it.
2520    ///
2521    /// # Errors
2522    ///
2523    /// [`EndpointError::UnknownPeerTrackStatus`] if the peer has asked nothing
2524    /// about that track, and the flow's own `InvalidTransition` for a request
2525    /// already answered.
2526    pub fn send_track_status(
2527        &mut self,
2528        track_namespace: TrackNamespace,
2529        track_name: Vec<u8>,
2530        status_code: VarInt,
2531        last_group_id: VarInt,
2532        last_object_id: VarInt,
2533    ) -> Result<ControlMessage, EndpointError> {
2534        let key = (track_namespace.0.clone(), track_name.clone());
2535        let req = self
2536            .inbound_track_statuses
2537            .get_mut(&key)
2538            .ok_or(EndpointError::UnknownPeerTrackStatus)?;
2539        req.state.on_track_status_sent()?;
2540        Ok(ControlMessage::TrackStatus(TrackStatus {
2541            track_namespace,
2542            track_name,
2543            status_code,
2544            last_group_id,
2545            last_object_id,
2546        }))
2547    }
2548
2549    // ── Subscribes blocked (draft-08 new) ──────────────────────
2550
2551    /// Process an incoming SUBSCRIBES_BLOCKED.
2552    ///
2553    /// Draft-08 adds this message so the peer can explicitly report that a
2554    /// new subscribe id would exceed our advertised maximum. The endpoint
2555    /// records the peer's reported maximum; acting on it (issuing a new
2556    /// `MAX_SUBSCRIBE_ID`) is up to the caller.
2557    pub fn receive_subscribes_blocked(
2558        &mut self,
2559        msg: &SubscribesBlocked,
2560    ) -> Result<(), EndpointError> {
2561        self.peer_reported_max_subscribe_id = Some(msg.maximum_subscribe_id);
2562        Ok(())
2563    }
2564
2565    /// The maximum subscribe id that the peer most recently reported in a
2566    /// `SUBSCRIBES_BLOCKED` message, if any.
2567    pub fn peer_reported_max_subscribe_id(&self) -> Option<VarInt> {
2568        self.peer_reported_max_subscribe_id
2569    }
2570
2571    // ── Unified message dispatch ───────────────────────────────
2572
2573    /// Dispatch an incoming control message to the appropriate handler.
2574    pub fn receive_message(&mut self, msg: ControlMessage) -> Result<(), EndpointError> {
2575        match msg {
2576            ControlMessage::GoAway(ref m) => self.receive_goaway(m),
2577            ControlMessage::MaxSubscribeId(ref m) => self.receive_max_subscribe_id(m),
2578            ControlMessage::SubscribesBlocked(ref m) => self.receive_subscribes_blocked(m),
2579            ControlMessage::SubscribeOk(ref m) => self.receive_subscribe_ok(m),
2580            ControlMessage::SubscribeError(ref m) => self.receive_subscribe_error(m),
2581            ControlMessage::SubscribeUpdate(ref m) => self.receive_subscribe_update(m),
2582            ControlMessage::SubscribeDone(ref m) => self.receive_subscribe_done(m),
2583            ControlMessage::FetchOk(ref m) => self.receive_fetch_ok(m),
2584            ControlMessage::FetchError(ref m) => self.receive_fetch_error(m),
2585            ControlMessage::SubscribeAnnouncesOk(ref m) => self.receive_subscribe_announces_ok(m),
2586            ControlMessage::SubscribeAnnouncesError(ref m) => {
2587                self.receive_subscribe_announces_error(m)
2588            }
2589            ControlMessage::AnnounceOk(ref m) => self.receive_announce_ok(m),
2590            ControlMessage::AnnounceError(ref m) => self.receive_announce_error(m),
2591            ControlMessage::AnnounceCancel(ref m) => self.receive_announce_cancel(m),
2592            ControlMessage::TrackStatus(ref m) => self.receive_track_status(m),
2593            ControlMessage::TrackStatusRequest(ref m) => self.receive_track_status_request(m),
2594            ControlMessage::Subscribe(ref m) => self.receive_subscribe(m),
2595            ControlMessage::Fetch(ref m) => self.receive_fetch(m),
2596            ControlMessage::FetchCancel(ref m) => self.receive_fetch_cancel(m),
2597            ControlMessage::Unsubscribe(ref m) => self.receive_unsubscribe(m),
2598            ControlMessage::Announce(ref m) => self.receive_announce(m),
2599            ControlMessage::Unannounce(ref m) => self.receive_unannounce(m),
2600            ControlMessage::SubscribeAnnounces(ref m) => self.receive_subscribe_announces(m),
2601            ControlMessage::UnsubscribeAnnounces(ref m) => self.receive_unsubscribe_announces(m),
2602            _ => Ok(()),
2603        }
2604    }
2605}