Skip to main content

moqtap_client/draft10/
endpoint.rs

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