Skip to main content

moqtap_client/draft11/
endpoint.rs

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