Skip to main content

moqtap_client/draft07/
endpoint.rs

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