Skip to main content

moqtap_client/draft07/
endpoint.rs

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