Skip to main content

moqtap_client/draft13/
endpoint.rs

1use std::collections::HashMap;
2use std::sync::{Arc, Mutex, MutexGuard};
3
4use crate::draft13::fetch::{FetchError, FetchState, FetchStateMachine};
5use crate::draft13::namespace::{
6    AnnounceState, AnnounceStateMachine, NamespaceError, SubscribeNamespaceState,
7    SubscribeNamespaceStateMachine,
8};
9use crate::draft13::publish::{
10    PublishError as PublishFlowError, PublishState, PublishStateMachine,
11};
12use crate::draft13::session::request_id::{RequestIdAllocator, RequestIdError, Role};
13use crate::draft13::session::setup::{self, SetupError};
14use crate::draft13::session::state::{SessionError, SessionState, SessionStateMachine};
15use crate::draft13::subscription::{
16    SubscriptionError, SubscriptionState, SubscriptionStateMachine,
17};
18use crate::draft13::track_status::{TrackStatusError, TrackStatusState, TrackStatusStateMachine};
19use crate::forwarding_preference::{ObjectForwardingPreference, TrackForwardingPreferences};
20use crate::malformed_tracks::{MalformedTrackCondition, MalformedTracks};
21use crate::track_locations::{
22    EndOfTrackPlacement, ObjectLocation, ObjectRole, TrackFault, TrackLocations, TrackObjects,
23};
24use moqtap_codec::draft13::error_codes::{
25    FetchErrorCode, SessionErrorCode, SubscribeNamespaceErrorCode,
26};
27use moqtap_codec::draft13::message::{
28    self, Announce, AnnounceCancel, AnnounceError, AnnounceOk, ClientSetup, ControlMessage, Fetch,
29    FetchCancel, FetchPayload, FetchType, GoAway, MaxRequestId, Publish, PublishError, PublishOk,
30    RequestsBlocked, ServerSetup, Subscribe, SubscribeDone, SubscribeError, SubscribeNamespace,
31    SubscribeNamespaceError, SubscribeNamespaceOk, SubscribeOk, SubscribeUpdate, TrackStatus,
32    TrackStatusError as TrackStatusErrorMsg, TrackStatusOk, Unannounce, Unsubscribe,
33    UnsubscribeNamespace,
34};
35use moqtap_codec::kvp::{KeyValuePair, KvpValue};
36use moqtap_codec::types::*;
37use moqtap_codec::varint::VarInt;
38
39/// Key identifying a namespace (used for Announce maps).
40type NamespaceKey = Vec<Vec<u8>>;
41
42/// Errors that can occur during draft-13 endpoint operations.
43#[derive(Debug, thiserror::Error)]
44pub enum EndpointError {
45    /// A GOAWAY carrying a New Session URI arrived at a server.
46    ///
47    /// Section 8.4: "If a server receives a GOAWAY with a non-zero New
48    /// Session URI Length it MUST terminate the session with a Protocol
49    /// Violation." Migration is something a server offers a client, never the
50    /// other way round.
51    #[error("GOAWAY carrying a New Session URI received at a server")]
52    GoAwayUriAtServer,
53    /// A session-level state machine error.
54    #[error("session error: {0}")]
55    Session(#[from] SessionError),
56    /// A request ID allocation or validation error.
57    #[error("request ID error: {0}")]
58    RequestId(#[from] RequestIdError),
59    /// A subscription state machine error.
60    #[error("subscription error: {0}")]
61    Subscription(#[from] SubscriptionError),
62    /// A fetch state machine error.
63    #[error("fetch error: {0}")]
64    Fetch(#[from] FetchError),
65    /// A namespace state machine error.
66    #[error("namespace error: {0}")]
67    Namespace(#[from] NamespaceError),
68    /// A track status state machine error.
69    #[error("track status error: {0}")]
70    TrackStatus(#[from] TrackStatusError),
71    /// A publish flow state machine error.
72    #[error("publish flow error: {0}")]
73    PublishFlow(#[from] PublishFlowError),
74    /// A setup negotiation error.
75    #[error("setup error: {0}")]
76    Setup(#[from] SetupError),
77    /// The request ID does not match any known state machine.
78    #[error("unknown request ID: {0}")]
79    UnknownRequest(u64),
80    /// A message that only a subscription can carry named a track status.
81    ///
82    /// Section 8.20 treats a TRACK_STATUS as a SUBSCRIBE "except it does not
83    /// create downstream subscription state", and says what follows from that
84    /// in the same breath: "the subscriber cannot send SUBSCRIBE_UPDATE or
85    /// UNSUBSCRIBE". Both messages are about a subscription, and this request
86    /// opened none for them to name.
87    ///
88    /// Separate from [`EndpointError::UnknownRequest`], which says the
89    /// identifier names nothing at all. This one says it names something, and
90    /// that what it names is the one request kind neither message applies to.
91    #[error("request {0} is a track status, which cannot be updated or unsubscribed")]
92    NotASubscription(u64),
93    /// The track namespace does not match any known state machine.
94    #[error("unknown namespace")]
95    UnknownNamespace,
96    /// The session is not in the Active state.
97    #[error("session not active")]
98    NotActive,
99    /// The session is draining and cannot accept new requests.
100    #[error("session is draining, no new requests allowed")]
101    Draining,
102    /// A filter that names a start location was asked for through a helper
103    /// that has no start location to give it.
104    #[error("this filter type needs a start location; use the range form of this call")]
105    FilterNeedsRange,
106    /// A second GOAWAY arrived on the control stream.
107    ///
108    /// The GOAWAY that says the peer is going away is one message, and the
109    /// draft answers a repeat of it with a session close rather than with an
110    /// error about the second message: there is no state a second one could
111    /// move that the first has not already moved.
112    #[error("a second GOAWAY arrived on the control stream")]
113    RepeatedGoAway,
114    /// The peer named a Track Alias it is already using for another track.
115    ///
116    /// Draft-13 states it twice, once per message. Section 8.8: "The same
117    /// Track Alias MUST NOT be used to refer to two different Tracks
118    /// simultaneously. If a subscriber receives a SUBSCRIBE_OK that uses the
119    /// same Track Alias as a different track with an active subscription, it
120    /// MUST close the session with error 'Duplicate Track Alias'." Section 8.13 is the same
121    /// sentence with PUBLISH in place of SUBSCRIBE_OK.
122    ///
123    /// The session is over: this endpoint's own state has moved to Closed and
124    /// the code the transport should close with is in
125    /// [`EndpointError::session_error_code`].
126    #[error("track alias {alias} already names request {established}'s track; request {offered} names a different one")]
127    DuplicateTrackAlias {
128        /// The alias both tracks are named by.
129        alias: u64,
130        /// The request whose live subscription holds the alias.
131        established: u64,
132        /// The request whose message arrived naming it for another track.
133        offered: u64,
134    },
135    /// This endpoint was asked to hand out a Track Alias that a live track of
136    /// its own already holds.
137    ///
138    /// Section 8.8 states the rule as a prohibition before it states what the
139    /// receiver does about one: "The same Track Alias MUST NOT be used to
140    /// refer to two different Tracks simultaneously."
141    ///
142    /// The message is refused instead of built, and nothing else moves: the
143    /// subscription the peer opened is still waiting for an answer, and the
144    /// session stays as it was. The alias never reaches the peer, so there is
145    /// nothing for the peer to close over.
146    #[error("track alias {alias} already names request {held}'s track")]
147    TrackAliasInUse {
148        /// The alias that is already spoken for.
149        alias: u64,
150        /// The request whose live flow holds it.
151        held: u64,
152    },
153    /// A track's objects were framed two different ways.
154    ///
155    /// Section 9: "Every Track has a single 'Object Forwarding Preference' and
156    /// the Original Publisher MUST NOT mix different forwarding preferences
157    /// within a single track (see Section 2.5)."
158    ///
159    /// The framing is the preference: an object on a subgroup stream has the
160    /// Subgroup preference and an object in a datagram has the Datagram one,
161    /// so the track's first object settles the property and this is every
162    /// later object measured against it.
163    ///
164    /// # Why this one ends no session
165    ///
166    /// Drafts 07 through 11 finish that paragraph with a close and a code.
167    /// This draft replaces the sentence with the cross-reference above, and
168    /// what it points at is a different answer rather than the same one worded
169    /// differently. Section 2.5 lists "An Object is received with a different
170    /// Forwarding Preference than previously observed from the same Track"
171    /// among the conditions that make a track malformed, and says of all of
172    /// them: "When a subscriber detects a Malformed Track, it MUST UNSUBSCRIBE
173    /// from the Track and SHOULD deliver an error to the application".
174    ///
175    /// So this error carries no code in [`EndpointError::session_error_code`],
176    /// and the connection's `close_for_data_stream` declines it. Ending the
177    /// session over it would be this crate inventing a consequence the draft
178    /// withdrew.
179    ///
180    /// This is half of that answer and not all of it. "SHOULD deliver an error
181    /// to the application" is what this error is. The unsubscribe the same
182    /// sentence requires is not done: those are control messages, and a data
183    /// path holding `&self` has no way to send one. It is also one condition
184    /// of eight in that section rather than a rule of its own, so the answer
185    /// belongs to the family and not to this arm. A caller that wants it has
186    /// the error to act on.
187    #[error(
188        "track alias {alias} carries objects framed as {established}, and one is \
189         framed as {offered}"
190    )]
191    MixedForwardingPreference {
192        /// The Track Alias the offending object named.
193        alias: u64,
194        /// The framing the track's earlier objects settled on.
195        established: ObjectForwardingPreference,
196        /// The framing the offending object used.
197        offered: ObjectForwardingPreference,
198    },
199    /// An object saying the track ended somewhere the track has already passed.
200    ///
201    /// Section 9.2.1.1 describes Object Status 0x4, end of Track, as one whose
202    /// "GroupID is either the largest group produced in this track and the
203    /// ObjectID is one greater than the largest object produced in that
204    /// group, or GroupID is one greater than the largest group produced in
205    /// this track and the ObjectID is zero", and states the consequence:
206    /// "An object with this status that has a Group ID less than any other
207    /// GroupID, or an ObjectID less than or equal to the largest in the
208    /// specified group, is a protocol error, and the receiver MUST terminate
209    /// the session."
210    ///
211    /// # What this draft merged
212    ///
213    /// Drafts 08 through 10 spell this against a status that means end of
214    /// Track *and Group*, and carry a second status beside it for the end of
215    /// the Track alone. This draft folded the two into 0x4 and kept the
216    /// looser of the two conditions, which is why the Group ID may equal the
217    /// largest group here and may not there.
218    #[error(
219        "the end-of-track object at group {group}, object {object} on track alias \
220         {alias} is out of place: {placement}"
221    )]
222    EndOfTrackOutOfPlace {
223        /// The Track Alias the offending object named.
224        alias: u64,
225        /// The Group ID it named.
226        group: u64,
227        /// The Object ID it named.
228        object: u64,
229        /// Which half of the condition it broke, and what it was measured
230        /// against.
231        placement: EndOfTrackPlacement,
232    },
233    /// An object arriving after the track's final object.
234    ///
235    /// Section 2.5 lists the condition: "An Object is received on a Track whose
236    /// Group and Object ID are larger than the final Object in the Track. The
237    /// final Object in a Track is the Object with Status END_OF_TRACK or the
238    /// last Object sent in a FETCH whose response indicated End of Track."
239    ///
240    /// **Larger is Section 1.3.1's comparison and not a reading of the words.**
241    /// That section puts one Location below another when "A.Group < B.Group ||
242    /// (A.Group == B.Group && A.Object < B.Object)", so an Object in a later
243    /// group is past the end whatever its own Object ID is.
244    ///
245    /// **A Malformed Track and not a session error**, which is the whole
246    /// difference between this arm and the one above it. They are the same
247    /// record's two answers about the same pair of numbers: that one asks
248    /// whether an end-of-track object is behind what the track has carried and
249    /// ends the session, this one asks whether an ordinary object is past where
250    /// the track ended and gives up one track.
251    ///
252    /// This is the error half of the answer Section 2.5 states once for its
253    /// whole list — "it MUST UNSUBSCRIBE from the Track and SHOULD deliver an
254    /// error to the application". The UNSUBSCRIBE is the connection's, for the
255    /// reason the mixed-framing arm gives.
256    #[error(
257        "the object at group {group}, object {object} on track alias {alias} arrived \
258         after the track's final object at group {final_group}, object {final_object}"
259    )]
260    ObjectPastFinalObject {
261        /// The Track Alias the offending object named.
262        alias: u64,
263        /// The Group ID it named.
264        group: u64,
265        /// The Object ID it named.
266        object: u64,
267        /// The Group ID of the object the track ended at.
268        final_group: u64,
269        /// The Object ID of the object the track ended at.
270        final_object: u64,
271    },
272    /// SUBSCRIBE_UPDATE named a Request ID this session has never carried a
273    /// request under.
274    ///
275    /// Section 8.10: "A publisher MUST terminate the session with a 'Protocol
276    /// Violation' if the SUBSCRIBE_UPDATE violates these rules or if the
277    /// subscriber specifies a request ID that has not existed within the
278    /// Session."
279    ///
280    /// A request that has **ended** is not this: it existed. That is why the
281    /// record of an inbound SUBSCRIBE outlives the subscription, and why an
282    /// update naming an ended one is refused by the flow rather than by the
283    /// session.
284    #[error("SUBSCRIBE_UPDATE names request {0}, which this session has never carried")]
285    UpdateForUnknownRequest(u64),
286
287    /// A Joining Fetch named a subscription this session cannot join.
288    ///
289    /// Section 8.16: "If a publisher receives a Joining Fetch with a Request ID that
290    /// does not correspond to an existing Subscribe in the same session, it
291    /// MUST respond with a Fetch Error with code Invalid Joining Request ID."
292    ///
293    /// A refusal and not a session close, so the session runs on and the error
294    /// names both identifiers: the fetch to refuse, and the subscription it
295    /// asked to join.
296    #[error("FETCH {fetch} joins request {joining}, which is no live subscription of the peer's")]
297    UnjoinableSubscription {
298        /// The fetch that named it.
299        fetch: u64,
300        /// The identifier it named.
301        joining: u64,
302    },
303
304    /// A Joining Fetch was refused under a code other than the one the same
305    /// sentence names for it.
306    ///
307    /// The reason travels with the refusal, so a subscriber told the wrong one
308    /// retries the wrong thing: it can rebuild a fetch whose range was refused,
309    /// and cannot rebuild one whose subscription is gone.
310    #[error("refusing FETCH {fetch} for the subscription it joins takes error code {required}")]
311    WrongJoiningRefusal {
312        /// The fetch being refused.
313        fetch: u64,
314        /// The code the draft names for that refusal.
315        required: u64,
316    },
317    /// A message about an announcement named a namespace the peer has not
318    /// announced.
319    ///
320    /// Section 8.27 says what a cancellation is for: the subscriber "will stop
321    /// sending new subscriptions for tracks within the provided Track
322    /// Namespace". What a withdrawal ends and a cancellation revokes is an
323    /// announcement the **peer** made, so the record they reach for is the one
324    /// this endpoint keeps of the peer's announcements.
325    ///
326    /// Separate from [`EndpointError::UnknownNamespace`], which is the same
327    /// miss on the announcements this endpoint made, so a caller can tell which
328    /// of the two maps came up empty.
329    #[error("the peer has made no live announcement for this namespace")]
330    UnknownPeerNamespace,
331    /// A message about a namespace subscription named a prefix the peer has
332    /// not subscribed to.
333    ///
334    /// Section 5.1: "An UNSUBSCRIBE_NAMESPACE withdraws a previous SUBSCRIBE_NAMESPACE."
335    ///
336    /// What a withdrawal ends is a namespace subscription the **peer** made,
337    /// so the record it reaches for is the one this endpoint keeps of the
338    /// peer's. A namespace subscription this endpoint made is withdrawn by
339    /// [`Endpoint::unsubscribe_namespace`], which is the same message travelling the other
340    /// way and answers with [`EndpointError::UnknownNamespace`].
341    #[error("the peer has made no live namespace subscription for this prefix")]
342    UnknownPeerNamespaceSubscription,
343    /// The peer subscribed to a namespace prefix overlapping one it is
344    /// already subscribed to.
345    ///
346    /// Section 8.28: "A subscriber cannot make overlapping namespace
347    /// subscriptions on a single session. Within a session, if a publisher
348    /// receives a SUBSCRIBE_NAMESPACE with a Track Namespace Prefix that is a
349    /// prefix of, suffix of, or equal to an active SUBSCRIBE_NAMESPACE, it
350    /// MUST respond with SUBSCRIBE_NAMESPACE_ERROR, with error code Namespace
351    /// Prefix Overlap."
352    ///
353    /// Taken when the message arrives, which is the moment the sentence
354    /// names, and read again when an answer is built: a request this endpoint
355    /// may not accept is one no later call can accept.
356    ///
357    /// The refusal itself is not this error. It is a message the peer is
358    /// owed, so the request is recorded like any other and refused through
359    /// the same call that refuses any other, under the code the sentence
360    /// names.
361    #[error(
362        "request {request} subscribes to a namespace prefix overlapping request {established}"
363    )]
364    PeerPrefixOverlap {
365        /// The request that arrived.
366        request: u64,
367        /// The namespace subscription it overlaps.
368        established: u64,
369    },
370    /// This endpoint was asked to subscribe to a namespace prefix overlapping
371    /// one it is already subscribed to.
372    ///
373    /// The first half of the same sentence, which is addressed to the
374    /// subscriber: "A subscriber cannot make overlapping namespace
375    /// subscriptions on a single session."
376    ///
377    /// The message is refused instead of built, and nothing else moves: no
378    /// Request ID is spent, no state machine is created and the session stays
379    /// as it was. The request never reaches the peer, so there is nothing for
380    /// the peer to refuse.
381    #[error("the namespace prefix overlaps request {established}, which this endpoint made")]
382    OwnPrefixOverlap {
383        /// The namespace subscription this endpoint already has.
384        established: u64,
385    },
386    /// A namespace subscription that overlaps another was refused under a
387    /// code other than the one the sentence names.
388    ///
389    /// The same shape as [`EndpointError::WrongJoiningRefusal`]: a rule that
390    /// names the code its refusal carries is not satisfied by a refusal under
391    /// any other, because the peer reads the code to learn what went wrong.
392    #[error("request {request} overlaps a namespace subscription and must be refused with code {required:#x}")]
393    WrongOverlapRefusal {
394        /// The request being refused.
395        request: u64,
396        /// The code the sentence names for it.
397        required: u64,
398    },
399}
400
401/// Whether two namespace prefixes overlap.
402///
403/// Section 8.28: "A subscriber cannot make overlapping namespace
404/// subscriptions on a single session. Within a session, if a publisher
405/// receives a SUBSCRIBE_NAMESPACE with a Track Namespace Prefix that is a
406/// prefix of, suffix of, or equal to an active SUBSCRIBE_NAMESPACE, it MUST
407/// respond with SUBSCRIBE_NAMESPACE_ERROR, with error code Namespace Prefix
408/// Overlap."
409///
410/// A namespace matches a namespace subscription when the subscription's
411/// prefix is a prefix of it, so two prefixes select overlapping sets of
412/// namespaces exactly when one of them is a prefix of the other. Equal
413/// prefixes are that case as well, and this draft names them.
414///
415/// "Suffix of" is read as the "or vice versa" drafts 07 through 11 write in
416/// the same place. Which suffix a prefix ends with decides nothing about the
417/// namespaces it matches, so read at its word the term would forbid pairs
418/// that overlap in nothing and permit pairs that overlap entirely.
419fn prefixes_overlap(a: &[Vec<u8>], b: &[Vec<u8>]) -> bool {
420    let shared = a.len().min(b.len());
421    a[..shared] == b[..shared]
422}
423
424impl EndpointError {
425    /// The code to close the session with, when draft-13 answers this error
426    /// with a close rather than leaving it to the one request it concerns.
427    ///
428    /// `None` means the error is recoverable: the caller may report it, give
429    /// up on the request it concerns, and keep the session running. `Some`
430    /// means the draft ends the session, and the endpoint has already moved
431    /// its own state to Closed - the code is what the transport should carry.
432    ///
433    /// The table grows one rule at a time, and a rule joins it with a gate
434    /// that drives the bytes at a real connection and reads the close code
435    /// back off the wire. An arm added without one asserts nothing: from
436    /// inside the process the session ends either way, and only the peer can
437    /// tell the difference.
438    pub fn session_error_code(&self) -> Option<SessionErrorCode> {
439        match self {
440            // Section 8.5 answers a ceiling that does not increase with a
441            // close, and names this code for it.
442            EndpointError::RequestId(RequestIdError::Decreased(..)) => {
443                Some(SessionErrorCode::ProtocolViolation)
444            }
445            // The same section answers a Request ID that reaches the ceiling
446            // this endpoint advertised, and names a different code for it.
447            EndpointError::RequestId(RequestIdError::ExceedsMax(..)) => {
448                Some(SessionErrorCode::TooManyRequests)
449            }
450            // Section 8.1 answers a Request ID that is not the peer's to
451            // spend with a close, and names Invalid Request ID for it. Both
452            // halves of that sentence arrive here: "not valid for the peer"
453            // is an ID out of this endpoint's own half of the space, and "not
454            // expected" is a new request carrying an ID other than the next
455            // one the peer's sequence calls for.
456            EndpointError::RequestId(
457                RequestIdError::WrongParity(..) | RequestIdError::OutOfSequence { .. },
458            ) => Some(SessionErrorCode::InvalidRequestId),
459            // Section 8.4 answers a GOAWAY that repeats one already
460            // received, and names this code in the same sentence.
461            EndpointError::RepeatedGoAway => Some(SessionErrorCode::ProtocolViolation),
462            // The same section answers a migration URI arriving at a server
463            // with a close, and names this code for it. Only a server may
464            // offer one, so a client that sends one is telling a server where
465            // to reconnect, which it has no standing to do.
466            EndpointError::GoAwayUriAtServer => Some(SessionErrorCode::ProtocolViolation),
467            // Sections 8.8 and 8.13 name this code in the sentence that
468            // states the rule, and name no other. A close carrying
469            // PROTOCOL_VIOLATION would tell the peer a different thing went
470            // wrong.
471            EndpointError::DuplicateTrackAlias { .. } => {
472                Some(SessionErrorCode::DuplicateTrackAlias)
473            }
474            // Section 8.10 answers an update naming a request the session has
475            // never carried with a close, and names this code for it.
476            EndpointError::UpdateForUnknownRequest(_) => Some(SessionErrorCode::ProtocolViolation),
477            // Section 9.2.1.1 answers an end-of-track object in the wrong place
478            // with "the receiver MUST terminate the session", and names no code
479            // in that sentence. The paragraph it closes names one for the
480            // status field's other failure — "Any other value SHOULD be treated
481            // as a protocol error and terminate the session with a Protocol
482            // Violation" — and it is the code this draft's decoder already
483            // gives the half of the same rule it can settle from one header.
484            EndpointError::EndOfTrackOutOfPlace { .. } => Some(SessionErrorCode::ProtocolViolation),
485            _ => None,
486        }
487    }
488}
489
490impl EndpointError {
491    /// The endpoint's report of a fault a track's record found.
492    ///
493    /// Written once because the two data paths reach it from different places:
494    /// a datagram through [`Endpoint::note_received_object`], and a subgroup
495    /// object through the stream that decoded it, which holds a record handle
496    /// and no endpoint. Two translations would be two chances for the same
497    /// fault to be reported as different rules.
498    pub(crate) fn for_track_fault(alias: u64, at: ObjectLocation, fault: TrackFault) -> Self {
499        match fault {
500            TrackFault::EndOfTrackOutOfPlace(placement) => EndpointError::EndOfTrackOutOfPlace {
501                alias,
502                group: at.group,
503                object: at.object,
504                placement,
505            },
506            TrackFault::PastFinalObject(end) => EndpointError::ObjectPastFinalObject {
507                alias,
508                group: at.group,
509                object: at.object,
510                final_group: end.group,
511                final_object: end.object,
512            },
513        }
514    }
515}
516
517/// Unified draft-13 MoQT endpoint wrapping session lifecycle, request ID
518/// allocation, and all per-flow state machines (subscriptions, fetches,
519/// announces, subscribe-namespaces, track statuses).
520pub struct Endpoint {
521    /// Which end of the session this is, which fixes the parity of the
522    /// request IDs it allocates.
523    role: Role,
524    session: SessionStateMachine,
525    request_ids: RequestIdAllocator,
526    /// Tracks the MAX_REQUEST_ID we have advertised to the peer.
527    advertised_max_id: u64,
528    subscriptions: HashMap<u64, Mutex<SubscriptionStateMachine>>,
529    fetches: HashMap<u64, FetchStateMachine>,
530    subscribe_namespaces: HashMap<u64, SubscribeNamespaceStateMachine>,
531    /// Namespace subscriptions the **peer** made, keyed by the Request ID it
532    /// opened each under.
533    ///
534    /// Kept apart from `subscribe_namespaces`, which holds the ones this endpoint made:
535    /// one Request ID names one request whichever end opened it, and the two
536    /// ends answer opposite halves of the flow.
537    inbound_subscribe_namespaces: HashMap<u64, InboundSubscribeNamespace>,
538    announces: HashMap<u64, AnnounceStateMachine>,
539    /// Announcements the **peer** made, keyed by the Request ID it
540    /// opened each under.
541    inbound_announces: HashMap<u64, InboundAnnounce>,
542    /// Maps namespace tuple -> request_id, so callers can UNANNOUNCE / cancel
543    /// by namespace without threading the id through every API.
544    announce_ids: HashMap<NamespaceKey, u64>,
545    /// Maps namespace prefix tuple -> request_id for subscribe-namespaces.
546    subscribe_namespace_ids: HashMap<NamespaceKey, u64>,
547    track_statuses: HashMap<u64, TrackStatusStateMachine>,
548    /// Track statuses the **peer** asked about, keyed by the Request ID it
549    /// asked under.
550    ///
551    /// Kept apart from `track_statuses`, which holds the ones this endpoint
552    /// asked about: the two are answered by opposite ends, and one Request ID
553    /// belongs to one request whichever end opened it.
554    inbound_track_statuses: HashMap<u64, InboundTrackStatus>,
555    /// The subscriptions the peer opened with PUBLISH, keyed by the request
556    /// id it opened them under.
557    ///
558    /// The record outlives the answer. Section 4.1 makes a PUBLISH the start
559    /// of a subscription rather than a question to be answered and forgotten:
560    /// "Once either of these sequences is successful, the subscription can be
561    /// updated by the subscriber using SUBSCRIBE_UPDATE, terminated by the
562    /// subscriber using UNSUBSCRIBE, or terminated by the publisher using
563    /// SUBSCRIBE_DONE." Everything after the answer needs the record - which
564    /// Track Alias is spoken for, and whether a message naming this request is
565    /// still about anything.
566    ///
567    /// The offers this endpoint makes itself are in `publishes` beside this,
568    /// which holds their state and not their message: the two are answered by
569    /// opposite ends.
570    inbound_publishes: HashMap<u64, InboundPublish>,
571    /// The subscriptions **this endpoint** opened with PUBLISH, keyed by the
572    /// Request ID it allocated for each.
573    ///
574    /// The state and nothing else, because the offer was built here and the
575    /// caller was handed it. That is the one asymmetry with `inbound_publishes`
576    /// and it is the same one every other pair of records here has.
577    publishes: HashMap<u64, PublishStateMachine>,
578    /// Subscriptions the **peer** opened with SUBSCRIBE, by Request ID.
579    ///
580    /// Section 8.10 puts SUBSCRIBE_UPDATE in the subscriber's hands - "A
581    /// subscriber sends a SUBSCRIBE_UPDATE to a publisher to modify an
582    /// existing subscription" - so an update that arrives names a
583    /// subscription in here and never one in `subscriptions`.
584    ///
585    /// The record holds the SUBSCRIBE itself and not only its state, because
586    /// the answer is built out of the request: the track a SUBSCRIBE_OK is
587    /// about is named nowhere else, and the alias it hands out has to be
588    /// judged against that name.
589    inbound_subscribes: HashMap<u64, InboundSubscribe>,
590    /// Every FETCH the peer has sent, from arrival to the end of the fetch.
591    ///
592    /// Separate from `fetches`, which holds the ones this endpoint made. The
593    /// record holds the FETCH itself and not only its state, because the
594    /// answer is built out of the request: a Joining Fetch has to be judged
595    /// against the subscription it names, and that name is nowhere else.
596    inbound_fetches: HashMap<u64, InboundFetch>,
597    negotiated_version: Option<VarInt>,
598    offered_versions: Vec<VarInt>,
599    goaway_uri: Option<Vec<u8>>,
600    /// The most recent `maximum_request_id` reported by the peer via a
601    /// `REQUESTS_BLOCKED` message.
602    peer_reported_max_request_id: Option<VarInt>,
603    /// What Full Track Name the peer has attached each Track Alias to, per
604    /// Request ID.
605    ///
606    /// Sections 8.8 and 8.13 forbid one alias naming two tracks at once, and the
607    /// "at once" is what makes this a table rather than a set: an alias the
608    /// peer used for a track whose subscription has ended is free again. The
609    /// table therefore records the binding and reads liveness back off the
610    /// subscription's own state machine, rather than keeping a second copy of
611    /// it that every path ending a subscription would have to remember to
612    /// prune.
613    /// What each track's objects have been framed as, so far.
614    ///
615    /// Behind a lock because this is the one endpoint fact a *data* stream
616    /// settles, and the data plane reaches the endpoint through `&Connection`:
617    /// a caller may hold one across tasks while it reads streams and datagrams,
618    /// so there is no `&mut` to reach the rest of this struct with.
619    forwarding_preferences: Mutex<TrackForwardingPreferences>,
620    /// The tracks this endpoint has found malformed and withdrawn from.
621    ///
622    /// Behind a lock for the same reason the record above it is: it is
623    /// written from the data plane, which holds a shared reference.
624    malformed: Mutex<MalformedTracks>,
625    /// How far each track's objects have reached, so far.
626    ///
627    /// Behind an `Arc` rather than beside the rest of this struct because the
628    /// objects that settle it are read off stream handles the caller owns, one
629    /// at a time, with no way back to the endpoint. Each such stream is handed a
630    /// clone of the handle, and every clone measures against this one record.
631    locations: Arc<Mutex<TrackLocations>>,
632    track_bindings: HashMap<u64, TrackBinding>,
633}
634
635/// A Track Alias the peer has attached to a Full Track Name, and the request
636/// whose lifetime the attachment follows.
637#[derive(Debug, Clone)]
638struct TrackBinding {
639    namespace: TrackNamespace,
640    name: Vec<u8>,
641    /// The alias, once the peer has named one.
642    ///
643    /// A SUBSCRIBE this endpoint sends names a track and waits for its alias,
644    /// so the binding exists with no alias in it from the moment the request
645    /// is made until its SUBSCRIBE_OK arrives. A PUBLISH carries both at once
646    /// and is never in that state.
647    alias: Option<u64>,
648    kind: BindingKind,
649}
650
651/// Which of the two sequences Section 4.1 names established the subscription
652/// that owns a binding, and so which record says whether it still has one.
653#[derive(Debug, Clone, Copy, PartialEq, Eq)]
654enum BindingKind {
655    /// This endpoint's SUBSCRIBE, established by the peer's SUBSCRIBE_OK.
656    Subscribe,
657    /// A PUBLISH, established by the PUBLISH_OK answering it - whichever end
658    /// sent which. Both records are consulted for one of these, and only one
659    /// of them can hold the id: Section 8.1 gives the two ends opposite halves
660    /// of the Request ID space.
661    Publish,
662    /// The peer's SUBSCRIBE, established by this endpoint's SUBSCRIBE_OK.
663    ///
664    /// The alias is this endpoint's to choose on that one, because Section 8.8
665    /// carries it in the answer rather than in the request.
666    PeerSubscribe,
667}
668
669/// A subscription the peer opened with PUBLISH.
670struct InboundPublish {
671    /// The message as it arrived, which is what the application answers from.
672    message: Publish,
673    /// How far the subscription it opened has got.
674    ///
675    /// Behind a lock for the reason [`Endpoint::subscription`] gives: an
676    /// offer the peer made is one of the two ways this endpoint receives a
677    /// track, so a withdrawal from the data plane has to reach it.
678    state: Mutex<PublishStateMachine>,
679}
680
681impl InboundPublish {
682    /// The flow this offer opened, locked for a read or a transition.
683    fn flow(&self) -> MutexGuard<'_, PublishStateMachine> {
684        self.state.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
685    }
686
687    /// How far that flow has got.
688    fn publish_state(&self) -> PublishState {
689        self.flow().state()
690    }
691}
692
693/// A subscription the peer opened with SUBSCRIBE.
694struct InboundSubscribe {
695    /// The message as it arrived, which is what the application answers from.
696    message: Subscribe,
697    /// How far the subscription it opened has got.
698    state: SubscriptionStateMachine,
699}
700/// A fetch the peer opened with FETCH.
701struct InboundFetch {
702    /// The message as it arrived, which is what the application answers from.
703    message: Fetch,
704    /// How far the fetch it opened has got.
705    state: FetchStateMachine,
706    /// The subscription a Joining Fetch named and this session had none live
707    /// for when the FETCH arrived, which is when the rule about it is read.
708    unjoinable: Option<u64>,
709}
710
711/// An announcement the peer made with ANNOUNCE.
712///
713/// Kept apart from the announcements this endpoint made because the two are
714/// answered by opposite ends: this one is waiting for an answer from here,
715/// and the other for one from the peer.
716struct InboundAnnounce {
717    /// The message as it arrived, which is what the application answers from.
718    message: Announce,
719    /// How far the announcement it makes has got.
720    state: AnnounceStateMachine,
721}
722/// A track status the peer asked for with TRACK_STATUS.
723///
724/// Section 8.20 treats the request as a SUBSCRIBE "except it does not create
725/// downstream subscription state", so it is recorded here and not among the
726/// subscriptions the peer has opened. Nothing that names a subscription can
727/// find it, which is the whole of what that exception asks for.
728struct InboundTrackStatus {
729    /// The message as it arrived, which is what the answer is built from.
730    message: TrackStatus,
731    /// How far the request it opened has got.
732    state: TrackStatusStateMachine,
733}
734/// A SUBSCRIBE_NAMESPACE the peer sent, and how far the namespace subscription it opens
735/// has got.
736///
737/// Kept apart from `subscribe_namespaces`, which holds the ones this endpoint made: the
738/// two are answered by opposite ends, and this one is waiting for an answer
739/// from here.
740struct InboundSubscribeNamespace {
741    /// The message as it arrived, which is what the answer is built from.
742    message: SubscribeNamespace,
743    /// How far the namespace subscription it opens has got.
744    state: SubscribeNamespaceStateMachine,
745    /// The namespace subscription this one overlapped when it arrived,
746    /// which is when the rule about it is read.
747    overlaps: Option<u64>,
748}
749impl Endpoint {
750    /// Create a new draft-13 endpoint for the given role.
751    pub fn new(role: Role) -> Self {
752        Self {
753            role,
754            session: SessionStateMachine::new(),
755            request_ids: RequestIdAllocator::new(role),
756            advertised_max_id: 0,
757            subscriptions: HashMap::new(),
758            fetches: HashMap::new(),
759            subscribe_namespaces: HashMap::new(),
760            inbound_subscribe_namespaces: HashMap::new(),
761            announces: HashMap::new(),
762            inbound_announces: HashMap::new(),
763            announce_ids: HashMap::new(),
764            subscribe_namespace_ids: HashMap::new(),
765            track_statuses: HashMap::new(),
766            inbound_track_statuses: HashMap::new(),
767            inbound_publishes: HashMap::new(),
768            publishes: HashMap::new(),
769            inbound_subscribes: HashMap::new(),
770            inbound_fetches: HashMap::new(),
771            negotiated_version: None,
772            offered_versions: Vec::new(),
773            goaway_uri: None,
774            peer_reported_max_request_id: None,
775            track_bindings: HashMap::new(),
776            forwarding_preferences: Mutex::new(TrackForwardingPreferences::new()),
777            malformed: Mutex::new(MalformedTracks::new()),
778            locations: Arc::new(Mutex::new(TrackLocations::new())),
779        }
780    }
781
782    /// The Track Alias the peer attached to `request_id`, once it has named
783    /// one.
784    ///
785    /// Answers for a subscription this endpoint asked for from the moment its
786    /// SUBSCRIBE_OK arrives, and for one the peer offered from the moment its
787    /// PUBLISH does. `None` before that, and for a Request ID this session has
788    /// no track for.
789    pub fn track_alias_for(&self, request_id: VarInt) -> Option<VarInt> {
790        let alias = self.track_bindings.get(&request_id.into_inner())?.alias?;
791        VarInt::from_u64(alias).ok()
792    }
793
794    /// The refusal Sections 8.8 and 8.13 require when `alias` already names a
795    /// different track whose subscription is still live, or `None` when it is
796    /// free.
797    ///
798    /// # Why the set is read rather than kept
799    ///
800    /// The draft says "an active subscription", and this endpoint's
801    /// subscription state machine has exactly that state: a subscription
802    /// reaches Active on SUBSCRIBE_OK and leaves it on the message that ends
803    /// the flow. Asking it is what makes an alias free again the moment its track's
804    /// subscription ends, with nothing to prune on the way out - and a path
805    /// that ended a subscription without telling this table would otherwise
806    /// leave the alias held forever and refuse the peer's next, conforming,
807    /// use of it.
808    ///
809    /// # Why the request's own binding is skipped
810    ///
811    /// A SUBSCRIBE_OK is judged before its own alias is written down, so the
812    /// skip is not what keeps it from finding itself. A PUBLISH is: it carries
813    /// its alias and its track in the one message, and a second PUBLISH under
814    /// a Request ID already bound is refused by the duplicate-Request-ID rule
815    /// before it reaches here.
816    fn conflicting_track_alias(
817        &self,
818        request_id: u64,
819        alias: u64,
820        namespace: &TrackNamespace,
821        name: &[u8],
822    ) -> Option<EndpointError> {
823        for (&id, binding) in &self.track_bindings {
824            if id == request_id || binding.alias != Some(alias) {
825                continue;
826            }
827            if binding.namespace == *namespace && binding.name == name {
828                continue;
829            }
830            if self.binding_is_established(id, binding.kind) {
831                return Some(EndpointError::DuplicateTrackAlias {
832                    alias,
833                    established: id,
834                    offered: request_id,
835                });
836            }
837        }
838        None
839    }
840
841    /// Whether the request that owns a binding still has a live subscription.
842    ///
843    /// The two kinds are answered by two different records because the two
844    /// sequences Section 4.1 names end in different places: a SUBSCRIBE this
845    /// endpoint made is live once its SUBSCRIBE_OK arrives, a PUBLISH the peer
846    /// made once this endpoint has answered PUBLISH_OK.
847    fn binding_is_established(&self, id: u64, kind: BindingKind) -> bool {
848        match kind {
849            BindingKind::Subscribe => {
850                self.subscription(id).is_some_and(|sm| sm.state() == SubscriptionState::Active)
851            }
852            BindingKind::Publish => {
853                self.inbound_publishes
854                    .get(&id)
855                    .is_some_and(|p| p.publish_state() == PublishState::Active)
856                    || self.publishes.get(&id).is_some_and(|sm| sm.state() == PublishState::Active)
857            }
858            BindingKind::PeerSubscribe => self
859                .inbound_subscribes
860                .get(&id)
861                .is_some_and(|s| s.state.state() == SubscriptionState::Active),
862        }
863    }
864
865    /// The request already using `alias` for a track other than (`namespace`,
866    /// `name`), or `None` when this endpoint may give the alias to that track.
867    ///
868    /// Separate from [`Self::conflicting_track_alias`] because the two answer
869    /// different questions about the same table. That one judges a message
870    /// that has arrived and ends the session over it; this one judges one that
871    /// has not been built and declines to build it.
872    fn alias_held_elsewhere(
873        &self,
874        alias: u64,
875        namespace: &TrackNamespace,
876        name: &[u8],
877    ) -> Option<EndpointError> {
878        self.track_bindings.iter().find_map(|(&id, binding)| {
879            let other_track = binding.namespace != *namespace || binding.name != name;
880            (binding.alias == Some(alias)
881                && other_track
882                && self.binding_is_in_use(id, binding.kind))
883            .then_some(EndpointError::TrackAliasInUse { alias, held: id })
884        })
885    }
886
887    /// The track a live binding has given `alias` to.
888    ///
889    /// Read rather than kept, for the reason the alias table beside it gives: a
890    /// binding whose request has ended holds nothing, and an alias that is free
891    /// again may name a different track next. That is exactly why the
892    /// forwarding-preference record below is keyed on the track this returns
893    /// and never on the alias itself.
894    fn track_for_alias(&self, alias: u64) -> Option<(&TrackNamespace, &[u8])> {
895        self.track_bindings.iter().find_map(|(&id, binding)| {
896            (binding.alias == Some(alias) && self.binding_is_in_use(id, binding.kind))
897                .then_some((&binding.namespace, binding.name.as_slice()))
898        })
899    }
900
901    /// The record a stream carrying `alias`'s objects measures them against.
902    ///
903    /// `None` for an alias no live binding names: an object for one breaks a
904    /// different rule, and measuring it against a track this endpoint never
905    /// asked for would answer that one with the wrong sentence.
906    pub fn track_objects(&self, alias: u64) -> Option<TrackObjects> {
907        let (namespace, name) = self.track_for_alias(alias)?;
908        Some(TrackObjects::new(
909            Arc::clone(&self.locations),
910            namespace.clone(),
911            name.to_vec(),
912            alias,
913        ))
914    }
915
916    /// Record or judge one object that arrived outside a subgroup stream, and
917    /// report the two rules its track's record answers: Section 9.2.1.1's
918    /// protocol error when an end-of-track object says the track ended
919    /// somewhere the track has already passed, and Section 2.5's Malformed
920    /// Track when an object arrives after the place an end-of-track object put
921    /// the end.
922    ///
923    /// `&self`, because the call site is the data plane's.
924    pub fn note_received_object(
925        &self,
926        alias: u64,
927        at: ObjectLocation,
928        role: ObjectRole,
929    ) -> Result<(), EndpointError> {
930        let Some(objects) = self.track_objects(alias) else { return Ok(()) };
931        objects
932            .note_with_final_object(at, role)
933            .map_err(|fault| EndpointError::for_track_fault(alias, at, fault))
934    }
935
936    /// Record how a track's object was framed, and report Section 9's "MUST NOT
937    /// mix" when it disagrees with what that track's earlier objects used.
938    ///
939    /// `&self`, because the call sites are the data plane's: a subgroup header
940    /// arriving, a datagram arriving, and the two writers that produce them.
941    ///
942    /// An alias no live binding names records nothing and reports nothing. An
943    /// object for such an alias breaks a different rule — the one about objects
944    /// nobody asked for — and answering that one here would answer it with the
945    /// wrong sentence.
946    pub fn note_object_forwarding_preference(
947        &self,
948        alias: u64,
949        seen: ObjectForwardingPreference,
950    ) -> Result<(), EndpointError> {
951        let Some((namespace, name)) = self.track_for_alias(alias) else { return Ok(()) };
952        self.forwarding_preferences
953            .lock()
954            .unwrap_or_else(|poisoned| poisoned.into_inner())
955            .observe(namespace, name, seen)
956            .map_err(|established| EndpointError::MixedForwardingPreference {
957                alias,
958                established,
959                offered: seen,
960            })
961    }
962
963    /// The subscription `id` opened, locked for a read or a transition.
964    ///
965    /// `&self`, which is the whole point of the lock. Section 2.5's answer to
966    /// a Malformed Track is "it MUST UNSUBSCRIBE from the Track", and the
967    /// conditions that make a track malformed are detected on the data plane,
968    /// where this endpoint is reached through a shared reference. A flow that
969    /// could only be moved through `&mut self` would leave that sentence
970    /// unanswerable from the only place it is ever read.
971    ///
972    /// Hold one guard at a time. Nothing here takes a second while a first is
973    /// live, and [`Self::withdraw_malformed_track`] collects the requests it
974    /// is going to move before it moves any of them for that reason.
975    fn subscription(&self, id: u64) -> Option<MutexGuard<'_, SubscriptionStateMachine>> {
976        self.subscriptions
977            .get(&id)
978            .map(|sm| sm.lock().unwrap_or_else(|poisoned| poisoned.into_inner()))
979    }
980
981    /// The condition a track was withdrawn for, or `None` for a track this
982    /// endpoint has found nothing wrong with.
983    ///
984    /// The withdrawal happens without the application asking for it, so this
985    /// is how an application that missed the error learns why a subscription
986    /// it never ended has ended.
987    ///
988    /// # Why this takes a track and not the alias the object carried
989    ///
990    /// An alias only means anything through a live binding, and the
991    /// withdrawal ends the binding it would have been resolved through. An
992    /// accessor taking an alias would therefore answer `None` from the
993    /// instant it had something to say, which is worse than not existing.
994    /// The record is keyed on the track, and so is this.
995    pub fn malformed_track(
996        &self,
997        namespace: &TrackNamespace,
998        name: &[u8],
999    ) -> Option<MalformedTrackCondition> {
1000        self.malformed
1001            .lock()
1002            .unwrap_or_else(|poisoned| poisoned.into_inner())
1003            .condition(namespace, name)
1004    }
1005
1006    /// Withdraw from a track a condition has just shown to be malformed, and
1007    /// give back the UNSUBSCRIBEs Section 2.5 asks for.
1008    ///
1009    /// "When a subscriber detects a Malformed Track, it MUST UNSUBSCRIBE from
1010    /// the Track and SHOULD deliver an error to the application." This is the
1011    /// first half. The second is the error the detecting path returns, which
1012    /// is why nothing here reports anything: a caller that gets an empty list
1013    /// back is not being told the track was fine.
1014    ///
1015    /// `&self`, because the call site is the data plane's.
1016    ///
1017    /// # What comes back, and what does not
1018    ///
1019    /// One UNSUBSCRIBE for each request through which this endpoint is
1020    /// *receiving* the track — a SUBSCRIBE it sent, and a PUBLISH the peer
1021    /// sent that it accepted, which Section 4.1 ends with the same message.
1022    /// A request that names the track the other way round is not one of
1023    /// those: a peer subscribing to this endpoint makes it the publisher, and
1024    /// a publisher does not unsubscribe from what it is serving.
1025    ///
1026    /// Empty for an alias no live binding names — there is no track to
1027    /// withdraw from — and for a track whose only requests are in a state
1028    /// that has no UNSUBSCRIBE in it. A subscription still waiting for its
1029    /// SUBSCRIBE_OK is the case that reaches that last one, and it holds no
1030    /// alias for an object to have arrived under.
1031    ///
1032    /// # What makes the answer happen once
1033    ///
1034    /// Not the record. The requests this withdraws end here, and a request
1035    /// that has ended has no second UNSUBSCRIBE in it, so a publisher that
1036    /// goes on mixing a track's framing is answered once however many objects
1037    /// it sends. The record is read afterwards, by an application asking why
1038    /// a track it never gave up was given up.
1039    ///
1040    /// Which leaves the case the two answers differ on: an application that
1041    /// subscribes to the same track again. That request has never been
1042    /// withdrawn from, and a publisher mixing its framing again has broken
1043    /// the sentence again, so it is withdrawn from too.
1044    pub fn withdraw_malformed_track(
1045        &self,
1046        alias: u64,
1047        condition: MalformedTrackCondition,
1048    ) -> Vec<ControlMessage> {
1049        let Some((namespace, name)) = self.track_for_alias(alias) else { return Vec::new() };
1050        let (namespace, name) = (namespace.clone(), name.to_vec());
1051        self.malformed
1052            .lock()
1053            .unwrap_or_else(|poisoned| poisoned.into_inner())
1054            .note(&namespace, &name, condition);
1055        // Collected before any of them is moved. `binding_is_in_use` takes a
1056        // subscription's own lock and the transitions below take it again, so
1057        // the scan finishes first and holds nothing while it runs.
1058        let mut ids: Vec<u64> = self
1059            .track_bindings
1060            .iter()
1061            .filter(|(&id, binding)| {
1062                binding.namespace == namespace
1063                    && binding.name == name
1064                    && self.binding_is_in_use(id, binding.kind)
1065            })
1066            .map(|(&id, _)| id)
1067            .collect();
1068        // A HashMap iterates in no order, and two subscriptions on one track
1069        // is a shape a peer can produce. Sorted so the wire is the same twice.
1070        ids.sort_unstable();
1071        ids.into_iter().filter_map(|id| self.withdraw_one(id)).collect()
1072    }
1073
1074    /// The UNSUBSCRIBE ending one request, or `None` when that request is not
1075    /// one this endpoint receives a track through or is not in a state that
1076    /// can be unsubscribed from.
1077    fn withdraw_one(&self, id: u64) -> Option<ControlMessage> {
1078        let request_id = VarInt::from_u64(id).ok()?;
1079        if let Some(mut sm) = self.subscription(id) {
1080            sm.on_unsubscribe().ok()?;
1081            return Some(ControlMessage::Unsubscribe(Unsubscribe { request_id }));
1082        }
1083        let entry = self.inbound_publishes.get(&id)?;
1084        entry.flow().on_unsubscribe_sent().ok()?;
1085        Some(ControlMessage::Unsubscribe(Unsubscribe { request_id }))
1086    }
1087
1088    /// Whether a binding's request has put its alias in play at all.
1089    ///
1090    /// Broader than [`Self::binding_is_established`], and the two sentences
1091    /// are why. What an endpoint must close over is qualified - "a different
1092    /// track with an active subscription" - and the prohibition Section 8.8
1093    /// opens with is not: "The same Track Alias MUST NOT be used to refer to
1094    /// two different Tracks simultaneously." Once an alias has been handed
1095    /// out, giving it to a second track is what that sentence forbids,
1096    /// answered or not.
1097    fn binding_is_in_use(&self, id: u64, kind: BindingKind) -> bool {
1098        match kind {
1099            BindingKind::Subscribe => {
1100                self.subscription(id).is_some_and(|sm| sm.state() != SubscriptionState::Done)
1101            }
1102            BindingKind::Publish => {
1103                self.inbound_publishes
1104                    .get(&id)
1105                    .is_some_and(|p| p.publish_state() != PublishState::Done)
1106                    || self.publishes.get(&id).is_some_and(|sm| sm.state() != PublishState::Done)
1107            }
1108            BindingKind::PeerSubscribe => self
1109                .inbound_subscribes
1110                .get(&id)
1111                .is_some_and(|s| s.state.state() != SubscriptionState::Done),
1112        }
1113    }
1114
1115    /// The conflict a SUBSCRIBE_OK's alias has with the tracks already bound.
1116    ///
1117    /// Separate from [`Self::conflicting_track_alias`] because the track a
1118    /// SUBSCRIBE_OK is about is not in the SUBSCRIBE_OK: it is the one this
1119    /// endpoint's own SUBSCRIBE asked for, which is why the request has to be
1120    /// looked up before the alias can be judged.
1121    fn conflicting_alias_for_subscribe_ok(&self, id: u64, alias: u64) -> Option<EndpointError> {
1122        let binding = self.track_bindings.get(&id)?;
1123        self.conflicting_track_alias(id, alias, &binding.namespace, &binding.name)
1124    }
1125
1126    // ── Accessors ──────────────────────────────────────────────
1127
1128    /// Returns the role (client or server) of this endpoint.
1129    pub fn role(&self) -> Role {
1130        self.role
1131    }
1132
1133    /// Returns the current session state.
1134    pub fn session_state(&self) -> SessionState {
1135        self.session.state()
1136    }
1137
1138    /// Returns the negotiated MoQT version, if setup is complete.
1139    pub fn negotiated_version(&self) -> Option<VarInt> {
1140        self.negotiated_version
1141    }
1142
1143    /// Returns the URI from a received GOAWAY message, if any.
1144    pub fn goaway_uri(&self) -> Option<&[u8]> {
1145        self.goaway_uri.as_deref()
1146    }
1147
1148    /// Returns whether this endpoint is blocked on request ID allocation.
1149    pub fn is_blocked(&self) -> bool {
1150        self.request_ids.is_blocked()
1151    }
1152
1153    /// Returns the number of active subscription state machines.
1154    pub fn active_subscription_count(&self) -> usize {
1155        self.subscriptions.len()
1156    }
1157
1158    /// Returns the number of active fetch state machines.
1159    pub fn active_fetch_count(&self) -> usize {
1160        self.fetches.len()
1161    }
1162
1163    /// Returns the number of active subscribe-namespace state machines.
1164    pub fn active_subscribe_namespace_count(&self) -> usize {
1165        self.subscribe_namespaces.len()
1166    }
1167
1168    /// Returns the number of active announce state machines.
1169    pub fn active_announce_count(&self) -> usize {
1170        self.announces.len()
1171    }
1172
1173    /// Returns the number of active track status state machines.
1174    pub fn active_track_status_count(&self) -> usize {
1175        self.track_statuses.len()
1176    }
1177
1178    // ── Session lifecycle ──────────────────────────────────────
1179
1180    /// Transition from Connecting to SetupExchange.
1181    pub fn connect(&mut self) -> Result<(), EndpointError> {
1182        self.session.on_connect()?;
1183        Ok(())
1184    }
1185
1186    /// Close the session (SetupExchange, Active or Draining -> Closed).
1187    pub fn close(&mut self) -> Result<(), EndpointError> {
1188        self.session.on_close()?;
1189        Ok(())
1190    }
1191
1192    // ── Client setup ───────────────────────────────────────────
1193
1194    /// Generate a CLIENT_SETUP message (client-side).
1195    pub fn send_client_setup(
1196        &mut self,
1197        versions: Vec<VarInt>,
1198        parameters: Vec<KeyValuePair>,
1199    ) -> Result<ControlMessage, EndpointError> {
1200        self.offered_versions = versions.clone();
1201        let msg = ClientSetup { supported_versions: versions, parameters };
1202        setup::validate_client_setup(&msg)?;
1203        // A MAX_REQUEST_ID parameter in our own CLIENT_SETUP is a grant to
1204        // the peer, so it is the first value of the ceiling we advertise.
1205        self.record_advertised_max(&msg.parameters);
1206        Ok(ControlMessage::ClientSetup(msg))
1207    }
1208
1209    /// Process a SERVER_SETUP message (client-side). Transitions to Active.
1210    /// If the server includes a MAX_REQUEST_ID parameter (key 0x02), the
1211    /// request ID allocator is initialized with that value.
1212    pub fn receive_server_setup(&mut self, msg: &ServerSetup) -> Result<(), EndpointError> {
1213        setup::validate_server_setup(msg)?;
1214        let version = setup::negotiate_version(&self.offered_versions, msg.selected_version)?;
1215        self.negotiated_version = Some(version);
1216        self.session.on_setup_complete()?;
1217        // Extract MAX_REQUEST_ID (key 0x02) from setup parameters if present
1218        for param in &msg.parameters {
1219            if param.key == VarInt::from_u64(0x02).unwrap() {
1220                if let KvpValue::Varint(v) = &param.value {
1221                    self.request_ids.update_max(v.into_inner())?;
1222                }
1223            }
1224        }
1225        Ok(())
1226    }
1227
1228    // ── Server setup ───────────────────────────────────────────
1229
1230    /// Process CLIENT_SETUP and generate SERVER_SETUP (server-side).
1231    pub fn receive_client_setup_and_respond(
1232        &mut self,
1233        client_setup: &ClientSetup,
1234        selected_version: VarInt,
1235    ) -> Result<ControlMessage, EndpointError> {
1236        setup::validate_client_setup(client_setup)?;
1237        // Section 8.3.2.2 puts no role restriction on MAX_REQUEST_ID, so a
1238        // CLIENT_SETUP may carry it and it grants this endpoint its budget.
1239        for param in &client_setup.parameters {
1240            if param.key == VarInt::from_u64(0x02).unwrap() {
1241                if let KvpValue::Varint(v) = &param.value {
1242                    self.request_ids.update_max(v.into_inner())?;
1243                }
1244            }
1245        }
1246        let version = setup::negotiate_version(&client_setup.supported_versions, selected_version)?;
1247        self.negotiated_version = Some(version);
1248        self.session.on_setup_complete()?;
1249        let msg = ServerSetup { selected_version: version, parameters: vec![] };
1250        Ok(ControlMessage::ServerSetup(msg))
1251    }
1252
1253    // ── MAX_REQUEST_ID ─────────────────────────────────────────
1254
1255    /// Take a Request ID from a new request the peer sent, holding it to all
1256    /// three halves of the rule.
1257    ///
1258    /// Section 8.1 closes the session with Invalid Request ID on a request ID
1259    /// "that is not valid for the peer", and Section 8.5 closes it with
1260    /// 'Too Many Requests' on one at or above the ceiling this endpoint
1261    /// advertised. The parity half belongs to the allocator; the ceiling half
1262    /// needs `advertised_max_id`, which is this endpoint's grant to the peer
1263    /// and not the peer's grant to it, so the two numbers are different and
1264    /// only one of them answers this question.
1265    ///
1266    /// The third is the sequence. The same sentence closes the session on "a
1267    /// new request with a Request ID that is not expected", and what is
1268    /// expected is fixed by the two lines above it: each endpoint starts at 0
1269    /// or 1 by role and steps by 2 per request. So a repeat and a skip are both
1270    /// refused, and neither is caught by parity or by the ceiling - a repeat has
1271    /// the right parity and sits below the ceiling by construction, having been
1272    /// accepted once already.
1273    ///
1274    /// Taking the ID is what advances the sequence, so this is for a **new**
1275    /// request only. A response, a cancellation or an update that names the
1276    /// request it modifies all carry an ID that has already been spent, and
1277    /// passing one here would refuse it.
1278    ///
1279    /// # The ceiling is a session rule, not a request rule
1280    ///
1281    /// Section 8.5: a Request ID "equal or larger than this" received by the
1282    /// endpoint that sent the MAX_REQUEST_ID in any request message - the list
1283    /// [`Self::receive_request`] carries - closes the session, and 'Too Many
1284    /// Requests' is the code. Which is also why the number measured against is
1285    /// the one **this** endpoint sent. An id that reaches the ceiling is not a
1286    /// request to refuse with an error message: the session is over, so this
1287    /// moves the endpoint's own state to Closed and leaves the code to
1288    /// [`EndpointError::session_error_code`].
1289    ///
1290    /// # Errors
1291    ///
1292    /// [`RequestIdError::WrongParity`] if the ID belongs to this endpoint's
1293    /// half of the space, [`RequestIdError::ExceedsMax`] if it is not
1294    /// below the advertised ceiling, or [`RequestIdError::OutOfSequence`] if it
1295    /// is not the one the peer's sequence called for. A ceiling that was never
1296    /// raised is 0, which refuses every ID, matching a default that reads "the
1297    /// peer MUST NOT send requests".
1298    ///
1299    /// All three end the session rather than the one request, so all three
1300    /// move this endpoint's own session to Closed on the way out and all three
1301    /// answer `Some` from [`EndpointError::session_error_code`]. Returning one
1302    /// of them without ending the session would leave a peer that broke the
1303    /// rule free to keep sending, and never told.
1304    pub fn validate_peer_request_id(&mut self, id: u64) -> Result<(), EndpointError> {
1305        if let Err(e) = self.request_ids.validate_peer_id(id) {
1306            return Err(self.fail_session(EndpointError::RequestId(e)));
1307        }
1308        if id >= self.advertised_max_id {
1309            return Err(self.fail_session(EndpointError::RequestId(RequestIdError::ExceedsMax(
1310                id,
1311                self.advertised_max_id,
1312            ))));
1313        }
1314        if let Err(e) = self.request_ids.record_peer_id(id) {
1315            return Err(self.fail_session(EndpointError::RequestId(e)));
1316        }
1317        Ok(())
1318    }
1319
1320    /// Record a MAX_REQUEST_ID parameter this endpoint is about to send as
1321    /// the ceiling it has advertised to the peer.
1322    fn record_advertised_max(&mut self, parameters: &[KeyValuePair]) {
1323        for param in parameters {
1324            if param.key == VarInt::from_u64(0x02).unwrap() {
1325                if let KvpValue::Varint(v) = &param.value {
1326                    self.advertised_max_id = v.into_inner();
1327                }
1328            }
1329        }
1330    }
1331
1332    /// Process an incoming MAX_REQUEST_ID message, ending the session if the
1333    /// ceiling it carries does not increase.
1334    /// Section 8.5: "The Maximum Request ID MUST only increase within a
1335    /// session, and receipt of a MAX_REQUEST_ID message with an equal or
1336    /// smaller Request ID value is a 'Protocol Violation'." Section 3.4 lists
1337    /// Protocol Violation among the codes for terminating the session - "The
1338    /// remote endpoint performed an action that was disallowed by the
1339    /// specification" - so naming it of a *receipt* is this draft saying the
1340    /// session ends, and with which code. Draft-16 Section 9.5 states the same
1341    /// rule with the verb in it: "it MUST close the session with a
1342    /// PROTOCOL_VIOLATION".
1343    ///
1344    /// # Errors
1345    ///
1346    /// [`RequestIdError::Decreased`] if the value does not increase, with the
1347    /// session already moved to Closed.
1348    pub fn receive_max_request_id(&mut self, msg: &MaxRequestId) -> Result<(), EndpointError> {
1349        if let Err(err) = self.request_ids.update_max(msg.request_id.into_inner()) {
1350            return Err(self.fail_session(err.into()));
1351        }
1352        Ok(())
1353    }
1354
1355    /// Generate a MAX_REQUEST_ID message (typically server-side).
1356    ///
1357    /// Section 8.5: "The Maximum Request ID MUST only increase within a
1358    /// session", and a peer that receives an equal or smaller value closes
1359    /// the session. The ceiling starts at 0 and 0 is not greater than 0, so
1360    /// the first value that may go on the wire is 1 and there is no opening
1361    /// case where a repeat is allowed.
1362    ///
1363    /// # Errors
1364    ///
1365    /// The decrease error if the value does not strictly increase.
1366    pub fn send_max_request_id(&mut self, max_id: VarInt) -> Result<ControlMessage, EndpointError> {
1367        let new_val = max_id.into_inner();
1368        if new_val <= self.advertised_max_id {
1369            return Err(EndpointError::RequestId(RequestIdError::Decreased(
1370                self.advertised_max_id,
1371                new_val,
1372            )));
1373        }
1374        self.advertised_max_id = new_val;
1375        Ok(ControlMessage::MaxRequestId(MaxRequestId { request_id: max_id }))
1376    }
1377
1378    // ── GoAway ─────────────────────────────────────────────────
1379
1380    /// Process an incoming GOAWAY message. Transitions to Draining.
1381    ///
1382    /// # Errors
1383    ///
1384    /// [`EndpointError::GoAwayUriAtServer`] if this endpoint is the server and
1385    /// the GOAWAY carries a New Session URI. The session is over: this
1386    /// endpoint's own state has moved to Closed and the code the transport
1387    /// should close with is in [`EndpointError::session_error_code`].
1388    ///
1389    /// [`EndpointError::RepeatedGoAway`] if a GOAWAY has already been
1390    /// received. The session is over: this endpoint's own state has moved to
1391    /// Closed and the code the transport should close with is in
1392    /// [`EndpointError::session_error_code`].
1393    pub fn receive_goaway(&mut self, msg: &GoAway) -> Result<(), EndpointError> {
1394        // Section 8.4: "If a server receives a GOAWAY with a non-zero New
1395        // Session URI Length it MUST terminate the session with a Protocol
1396        // Violation." Refused before the URI is stored rather than
1397        // after, so an application reading `goaway_uri` back can never be
1398        // handed somewhere a client chose to send it. The session ends with
1399        // it: the sentence names a close and a code, and an endpoint that
1400        // raised the error and carried on would keep serving a peer it had
1401        // just found in violation.
1402        if self.role == Role::Server && !msg.new_session_uri.is_empty() {
1403            return Err(self.fail_session(EndpointError::GoAwayUriAtServer));
1404        }
1405        // Section 8.4: "The endpoint MUST terminate the session with a
1406        // Protocol Violation (Section 3.4) if it receives multiple GOAWAY messages."
1407        // Draining is reached from nowhere else - `on_goaway` is its only
1408        // entry and this method is that method's only caller - so the session
1409        // state is the record of the first GOAWAY having arrived.
1410        if self.session.state() == SessionState::Draining {
1411            return Err(self.fail_session(EndpointError::RepeatedGoAway));
1412        }
1413        self.session.on_goaway()?;
1414        self.goaway_uri = Some(msg.new_session_uri.clone());
1415        Ok(())
1416    }
1417
1418    // ── Subscribe flow ─────────────────────────────────────────
1419
1420    fn require_active_or_err(&self) -> Result<(), EndpointError> {
1421        match self.session.state() {
1422            SessionState::Active => Ok(()),
1423            SessionState::Draining => Err(EndpointError::Draining),
1424            _ => Err(EndpointError::NotActive),
1425        }
1426    }
1427
1428    /// Record that the session is over because the peer broke a rule this
1429    /// draft answers with a session close, and hand the error back unchanged.
1430    ///
1431    /// The state move is what makes the violation stick: every request entry
1432    /// point goes through
1433    /// [`require_active_or_err`](Self::require_active_or_err), so a caller
1434    /// that ignores the returned error still cannot start anything new.
1435    /// Closing on the wire is the connection layer's job - see
1436    /// [`EndpointError::session_error_code`] for the code it should use.
1437    fn fail_session(&mut self, err: EndpointError) -> EndpointError {
1438        // `on_close` accepts SetupExchange, Active and Draining. A violation
1439        // seen in Connecting or Closed leaves the state machine alone: there
1440        // is no session to close, and the error itself is still the answer.
1441        //
1442        // SetupExchange is in that set because the Termination section says
1443        // "The Transport Session can be terminated at any point", and the
1444        // Setup exchange is a point. So a violation caught while the setup is
1445        // still in flight does close the session rather than being recorded
1446        // and forgotten, which is what this discarded result used to mean.
1447        let _ = self.session.on_close();
1448        err
1449    }
1450
1451    /// Send a SUBSCRIBE message. Allocates a request ID and creates a
1452    /// subscription state machine.
1453    ///
1454    /// `LargestObject` is a reasonable default. `AbsoluteStart` and
1455    /// `AbsoluteRange` name a start location, which this call has no way to
1456    /// supply, and are answered with [`EndpointError::FilterNeedsRange`] —
1457    /// use [`Self::subscribe_range`] for those. Without that refusal this
1458    /// call would hand back a message whose filter announces fields the
1459    /// message does not carry, which the encoder rejects.
1460    ///
1461    /// Draft-13 note: the track alias is no longer chosen by the subscriber
1462    /// — it is returned by the publisher in `SUBSCRIBE_OK`. Callers that
1463    /// need the alias should inspect `track_alias_for` after the OK arrives.
1464    pub fn subscribe(
1465        &mut self,
1466        track_namespace: TrackNamespace,
1467        track_name: Vec<u8>,
1468        subscriber_priority: u8,
1469        group_order: GroupOrder,
1470        filter_type: FilterType,
1471        parameters: Vec<KeyValuePair>,
1472    ) -> Result<(VarInt, ControlMessage), EndpointError> {
1473        if matches!(filter_type, FilterType::AbsoluteStart | FilterType::AbsoluteRange) {
1474            return Err(EndpointError::FilterNeedsRange);
1475        }
1476        self.subscribe_inner(
1477            track_namespace,
1478            track_name,
1479            subscriber_priority,
1480            group_order,
1481            filter_type,
1482            None,
1483            None,
1484            parameters,
1485        )
1486    }
1487
1488    /// Send a SUBSCRIBE for a range of the track, starting at a given
1489    /// location.
1490    ///
1491    /// The Filter Type is derived from the arguments rather than taken beside
1492    /// them: `end_group` present means AbsoluteRange and absent means
1493    /// AbsoluteStart. So the message cannot name a filter whose fields it does
1494    /// not carry.
1495    #[allow(clippy::too_many_arguments)]
1496    pub fn subscribe_range(
1497        &mut self,
1498        track_namespace: TrackNamespace,
1499        track_name: Vec<u8>,
1500        subscriber_priority: u8,
1501        group_order: GroupOrder,
1502        start_location: Location,
1503        end_group: Option<VarInt>,
1504        parameters: Vec<KeyValuePair>,
1505    ) -> Result<(VarInt, ControlMessage), EndpointError> {
1506        let filter_type = match end_group {
1507            Some(_) => FilterType::AbsoluteRange,
1508            None => FilterType::AbsoluteStart,
1509        };
1510        self.subscribe_inner(
1511            track_namespace,
1512            track_name,
1513            subscriber_priority,
1514            group_order,
1515            filter_type,
1516            Some((start_location.group, start_location.object)),
1517            end_group,
1518            parameters,
1519        )
1520    }
1521
1522    #[allow(clippy::too_many_arguments)]
1523    fn subscribe_inner(
1524        &mut self,
1525        track_namespace: TrackNamespace,
1526        track_name: Vec<u8>,
1527        subscriber_priority: u8,
1528        group_order: GroupOrder,
1529        filter_type: FilterType,
1530        start: Option<(VarInt, VarInt)>,
1531        end_group: Option<VarInt>,
1532        parameters: Vec<KeyValuePair>,
1533    ) -> Result<(VarInt, ControlMessage), EndpointError> {
1534        self.require_active_or_err()?;
1535        let req_id = self.request_ids.allocate()?;
1536
1537        let mut sm = SubscriptionStateMachine::new();
1538        sm.on_subscribe_sent()?;
1539        self.subscriptions.insert(req_id.into_inner(), Mutex::new(sm));
1540        // The track is recorded here because this is the only place it is
1541        // known: a SUBSCRIBE_OK names an alias and not the track it is for.
1542        self.track_bindings.insert(
1543            req_id.into_inner(),
1544            TrackBinding {
1545                namespace: track_namespace.clone(),
1546                name: track_name.clone(),
1547                alias: None,
1548                kind: BindingKind::Subscribe,
1549            },
1550        );
1551
1552        let (start_group, start_object) = match start {
1553            Some((group, object)) => (Some(group), Some(object)),
1554            None => (None, None),
1555        };
1556        let msg = ControlMessage::Subscribe(Subscribe {
1557            request_id: req_id,
1558            track_namespace,
1559            track_name,
1560            subscriber_priority,
1561            group_order,
1562            forward: Forward::Forward,
1563            filter_type,
1564            start_group,
1565            start_object,
1566            end_group,
1567            parameters,
1568        });
1569        Ok((req_id, msg))
1570    }
1571
1572    /// Process an incoming SUBSCRIBE_OK.
1573    ///
1574    /// Draft-13 carries the publisher-assigned `track_alias` on SUBSCRIBE_OK,
1575    /// which is recorded here so callers can retrieve it via
1576    /// [`Endpoint::track_alias_for`].
1577    pub fn receive_subscribe_ok(&mut self, msg: &SubscribeOk) -> Result<(), EndpointError> {
1578        let id = msg.request_id.into_inner();
1579        if !self.subscriptions.contains_key(&id) {
1580            return Err(EndpointError::UnknownRequest(id));
1581        }
1582        let alias = msg.track_alias.into_inner();
1583        // Judged before the transition, so that the subscription this message
1584        // is about is not yet live and cannot be found as its own conflict,
1585        // and so that a refused SUBSCRIBE_OK leaves no alias behind.
1586        if let Some(conflict) = self.conflicting_alias_for_subscribe_ok(id, alias) {
1587            return Err(self.fail_session(conflict));
1588        }
1589        let mut sm = self.subscription(id).expect("checked above");
1590        sm.on_subscribe_ok()?;
1591        drop(sm);
1592        if let Some(binding) = self.track_bindings.get_mut(&id) {
1593            binding.alias = Some(alias);
1594        }
1595        Ok(())
1596    }
1597
1598    /// Process an incoming SUBSCRIBE_ERROR.
1599    pub fn receive_subscribe_error(&mut self, msg: &SubscribeError) -> Result<(), EndpointError> {
1600        let id = msg.request_id.into_inner();
1601        let mut sm = self.subscription(id).ok_or(EndpointError::UnknownRequest(id))?;
1602        sm.on_subscribe_error()?;
1603        Ok(())
1604    }
1605
1606    /// Send an UNSUBSCRIBE message for an active subscription.
1607    ///
1608    /// Section 4.1 gives the subscriber this for a subscription that came
1609    /// either way round, so a request the peer opened with PUBLISH ends here
1610    /// too. The two kinds cannot collide: a Request ID belongs to whichever
1611    /// endpoint allocated it, and the two halves of the space have opposite
1612    /// least significant bits.
1613    pub fn unsubscribe(&mut self, request_id: VarInt) -> Result<ControlMessage, EndpointError> {
1614        let id = request_id.into_inner();
1615        if let Some(mut sm) = self.subscription(id) {
1616            sm.on_unsubscribe()?;
1617            return Ok(ControlMessage::Unsubscribe(Unsubscribe { request_id }));
1618        }
1619        let entry = self.inbound_publishes.get(&id).ok_or(EndpointError::UnknownRequest(id))?;
1620        entry.flow().on_unsubscribe_sent()?;
1621        Ok(ControlMessage::Unsubscribe(Unsubscribe { request_id }))
1622    }
1623
1624    /// Send a SUBSCRIBE_UPDATE narrowing a subscription this endpoint opened.
1625    ///
1626    /// Section 8.10 gives the message to the subscriber, which is what this
1627    /// endpoint is for every subscription in `subscriptions`. No identifier is
1628    /// spent: this draft's SUBSCRIBE_UPDATE has one Request ID field and it
1629    /// names the subscription being modified, which is why Section 8.1 leaves
1630    /// the message out of the list that steps the peer's sequence.
1631    ///
1632    /// The narrowing rules the same section states are the caller's to keep.
1633    ///
1634    /// # Errors
1635    ///
1636    /// [`EndpointError::UnknownRequest`] when this endpoint opened no
1637    /// subscription under that identifier, and the subscription flow's own
1638    /// `InvalidTransition` when the one it names has already ended.
1639    #[allow(clippy::too_many_arguments)]
1640    pub fn subscribe_update(
1641        &mut self,
1642        request_id: VarInt,
1643        start_group: VarInt,
1644        start_object: VarInt,
1645        end_group: VarInt,
1646        subscriber_priority: u8,
1647        forward: Forward,
1648        parameters: Vec<KeyValuePair>,
1649    ) -> Result<ControlMessage, EndpointError> {
1650        self.require_active_or_err()?;
1651        let id = request_id.into_inner();
1652        let mut sm = self.subscription(id).ok_or(EndpointError::UnknownRequest(id))?;
1653        sm.on_subscribe_update()?;
1654        drop(sm);
1655        Ok(ControlMessage::SubscribeUpdate(SubscribeUpdate {
1656            request_id,
1657            start_group,
1658            start_object,
1659            end_group,
1660            subscriber_priority,
1661            forward,
1662            parameters,
1663        }))
1664    }
1665
1666    /// Process an incoming SUBSCRIBE_UPDATE.
1667    ///
1668    /// Section 8.10: "A subscriber sends a SUBSCRIBE_UPDATE to a publisher to
1669    /// modify an existing subscription." One that arrives is therefore about a
1670    /// subscription the **peer** opened, which is why it is looked for among
1671    /// those and not among this endpoint's own.
1672    ///
1673    /// # Errors
1674    ///
1675    /// [`EndpointError::UpdateForUnknownRequest`] when the Request ID names no
1676    /// subscription the peer has opened in this session, with the session
1677    /// already moved to Closed, and the subscription flow's own
1678    /// `InvalidTransition` when it names one that has already ended.
1679    pub fn receive_subscribe_update(&mut self, msg: &SubscribeUpdate) -> Result<(), EndpointError> {
1680        let id = msg.request_id.into_inner();
1681        if let Some(sub) = self.inbound_subscribes.get_mut(&id) {
1682            sub.state.on_subscribe_update_received()?;
1683            return Ok(());
1684        }
1685        // The close is over an identifier "that has not existed within the
1686        // Session", which is wider than *no subscription the peer opened*:
1687        // every request this session has carried has existed. Only a
1688        // subscription the peer opened has a transition for an update, so for
1689        // the rest the message is accepted and the state left alone - which is
1690        // also what a PUBLISH-established subscription needs, since Section
1691        // 8.10 counts the parameters set in PUBLISH_OK among the ones an
1692        // update may change.
1693        // Section 8.20 takes the update away from this request kind outright:
1694        // "the subscriber cannot send SUBSCRIBE_UPDATE or UNSUBSCRIBE". A track status
1695        // the peer asked for and one this endpoint asked for are the same kind
1696        // of request and neither is a subscription, so both are refused. The
1697        // check comes before the set below because an identifier naming a track
1698        // status has existed, and being told so is what would let it through.
1699        if self.track_statuses.contains_key(&id) || self.inbound_track_statuses.contains_key(&id) {
1700            return Err(EndpointError::NotASubscription(id));
1701        }
1702        let existed = self.subscriptions.contains_key(&id)
1703            || self.inbound_publishes.contains_key(&id)
1704            || self.fetches.contains_key(&id)
1705            || self.subscribe_namespaces.contains_key(&id)
1706            || self.announces.contains_key(&id);
1707        if existed {
1708            return Ok(());
1709        }
1710        Err(self.fail_session(EndpointError::UpdateForUnknownRequest(id)))
1711    }
1712
1713    /// Process an incoming SUBSCRIBE_DONE (subscriber side - publisher
1714    /// finished).
1715    ///
1716    /// Section 4.1 gives the publisher this for a subscription that came
1717    /// either way round, so one the peer opened with PUBLISH ends here too.
1718    pub fn receive_subscribe_done(&mut self, msg: &SubscribeDone) -> Result<(), EndpointError> {
1719        let id = msg.request_id.into_inner();
1720        if let Some(mut sm) = self.subscription(id) {
1721            sm.on_subscribe_done()?;
1722            return Ok(());
1723        }
1724        let entry = self.inbound_publishes.get(&id).ok_or(EndpointError::UnknownRequest(id))?;
1725        entry.flow().on_subscribe_done_received()?;
1726        Ok(())
1727    }
1728
1729    // ── Answering a SUBSCRIBE the peer sent ────────────────────
1730
1731    /// Process an incoming SUBSCRIBE, recording the subscription it opens.
1732    ///
1733    /// The Request ID has already been checked by [`Self::receive_request`],
1734    /// which every request message passes through before its own handler.
1735    /// There is no Track Alias to judge here: Section 8.8 carries it in the
1736    /// SUBSCRIBE_OK, so this endpoint chooses it, and it is judged when the
1737    /// answer is built.
1738    ///
1739    /// # Errors
1740    ///
1741    /// The session error when the session is not established, and the
1742    /// subscription flow's own `InvalidTransition` for a second SUBSCRIBE
1743    /// under a Request ID already carrying one.
1744    pub fn receive_subscribe(&mut self, msg: &Subscribe) -> Result<(), EndpointError> {
1745        self.require_active_or_err()?;
1746        let id = msg.request_id.into_inner();
1747        let mut state = SubscriptionStateMachine::new();
1748        state.on_subscribe_received()?;
1749        self.inbound_subscribes.insert(id, InboundSubscribe { message: msg.clone(), state });
1750        Ok(())
1751    }
1752
1753    /// The SUBSCRIBE the peer sent under `request_id` and this endpoint has
1754    /// not answered yet.
1755    ///
1756    /// `None` once it has been answered, and for an identifier this session
1757    /// has no inbound subscription for. The record itself lives on for as long
1758    /// as the session does, which is what lets an update say whether the
1759    /// request it names has ever existed.
1760    pub fn pending_subscribe(&self, request_id: VarInt) -> Option<&Subscribe> {
1761        self.inbound_subscribes
1762            .get(&request_id.into_inner())
1763            .filter(|s| s.state.state() == SubscriptionState::Subscribing)
1764            .map(|s| &s.message)
1765    }
1766
1767    /// How many SUBSCRIBEs the peer has sent that are still waiting for an
1768    /// answer.
1769    pub fn pending_subscribe_count(&self) -> usize {
1770        self.inbound_subscribes
1771            .values()
1772            .filter(|s| s.state.state() == SubscriptionState::Subscribing)
1773            .count()
1774    }
1775
1776    /// Build the SUBSCRIBE_OK accepting a subscription the peer opened, giving
1777    /// its track a Track Alias.
1778    ///
1779    /// # Errors
1780    ///
1781    /// [`EndpointError::UnknownRequest`] if the peer opened no subscription
1782    /// under that identifier, [`EndpointError::TrackAliasInUse`] if a live
1783    /// track of this endpoint's already holds the alias, and
1784    /// [`EndpointError::Subscription`] if the request has already been
1785    /// answered.
1786    pub fn send_subscribe_ok(
1787        &mut self,
1788        request_id: VarInt,
1789        track_alias: VarInt,
1790        expires: VarInt,
1791        group_order: GroupOrder,
1792        parameters: Vec<KeyValuePair>,
1793    ) -> Result<ControlMessage, EndpointError> {
1794        let id = request_id.into_inner();
1795        let alias = track_alias.into_inner();
1796        let sub = self.inbound_subscribes.get(&id).ok_or(EndpointError::UnknownRequest(id))?;
1797        let namespace = sub.message.track_namespace.clone();
1798        let name = sub.message.track_name.clone();
1799        if let Some(refusal) = self.alias_held_elsewhere(alias, &namespace, &name) {
1800            return Err(refusal);
1801        }
1802        let sub = self.inbound_subscribes.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
1803        sub.state.on_subscribe_ok_sent()?;
1804        self.track_bindings.insert(
1805            id,
1806            TrackBinding { namespace, name, alias: Some(alias), kind: BindingKind::PeerSubscribe },
1807        );
1808        Ok(ControlMessage::SubscribeOk(SubscribeOk {
1809            request_id,
1810            track_alias,
1811            expires,
1812            group_order,
1813            content_exists: ContentExists::NoLargestLocation,
1814            largest_location: None,
1815            parameters,
1816        }))
1817    }
1818
1819    /// Build the SUBSCRIBE_ERROR rejecting a subscription the peer opened.
1820    ///
1821    /// No Track Alias goes back with it. This draft moved the field out of
1822    /// SUBSCRIBE_ERROR along with the retry the earlier ones offered, so a
1823    /// refusal here says only that the request failed.
1824    ///
1825    /// # Errors
1826    ///
1827    /// [`EndpointError::UnknownRequest`] if the peer opened no subscription
1828    /// under that identifier, and [`EndpointError::Subscription`] if it has
1829    /// already been answered.
1830    pub fn send_subscribe_error(
1831        &mut self,
1832        request_id: VarInt,
1833        error_code: VarInt,
1834        reason_phrase: Vec<u8>,
1835    ) -> Result<ControlMessage, EndpointError> {
1836        let id = request_id.into_inner();
1837        let sub = self.inbound_subscribes.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
1838        sub.state.on_subscribe_error_sent()?;
1839        Ok(ControlMessage::SubscribeError(SubscribeError { request_id, error_code, reason_phrase }))
1840    }
1841
1842    /// Build the SUBSCRIBE_DONE ending a subscription this endpoint publishes,
1843    /// whichever of the two sequences opened it.
1844    ///
1845    /// # Errors
1846    ///
1847    /// [`EndpointError::UnknownRequest`] if this session is carrying no such
1848    /// subscription under that identifier, [`EndpointError::Subscription`] if
1849    /// it is one the peer opened and this endpoint has not accepted or has
1850    /// already ended, and [`EndpointError::PublishFlow`] for the same of one
1851    /// this endpoint opened.
1852    pub fn send_subscribe_done(
1853        &mut self,
1854        request_id: VarInt,
1855        status_code: VarInt,
1856        stream_count: VarInt,
1857        reason_phrase: Vec<u8>,
1858    ) -> Result<ControlMessage, EndpointError> {
1859        let id = request_id.into_inner();
1860        // Section 4.1 gives this message to the publisher for a subscription
1861        // that came either way round - "Once either of these sequences is
1862        // successful, the subscription can be ... terminated by the publisher
1863        // using SUBSCRIBE_DONE" - so one this endpoint opened with PUBLISH
1864        // ends here too. Two records, one message.
1865        if let Some(state) = self.publishes.get_mut(&id) {
1866            state.on_subscribe_done_sent()?;
1867            return Ok(ControlMessage::SubscribeDone(SubscribeDone {
1868                request_id,
1869                status_code,
1870                stream_count,
1871                reason_phrase,
1872            }));
1873        }
1874        let sub = self.inbound_subscribes.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
1875        sub.state.on_subscribe_done_sent()?;
1876        Ok(ControlMessage::SubscribeDone(SubscribeDone {
1877            request_id,
1878            status_code,
1879            stream_count,
1880            reason_phrase,
1881        }))
1882    }
1883
1884    /// Process an incoming UNSUBSCRIBE, ending a subscription this endpoint
1885    /// publishes and freeing the Track Alias it held.
1886    ///
1887    /// Section 8.11: "A subscriber issues a UNSUBSCRIBE message to a publisher
1888    /// indicating it is no longer interested in receiving media for the
1889    /// specified track and requesting that the publisher stop sending Objects
1890    /// as soon as possible." The message travels from subscriber to publisher,
1891    /// so what it can end is whatever this endpoint publishes - and Section
1892    /// 4.1 gives that two sources, not one: "A subscription can be initiated
1893    /// by either a publisher or a subscriber."
1894    ///
1895    /// # Errors
1896    ///
1897    /// [`EndpointError::UnknownRequest`] if this endpoint publishes no
1898    /// subscription under that identifier, [`EndpointError::Subscription`] if
1899    /// it is one the peer opened that this endpoint never accepted or has
1900    /// already ended, and [`EndpointError::PublishFlow`] for the same of one
1901    /// this endpoint opened.
1902    pub fn receive_unsubscribe(&mut self, msg: &Unsubscribe) -> Result<(), EndpointError> {
1903        let id = msg.request_id.into_inner();
1904        // The other half of the same sentence. Section 8.20: "the subscriber
1905        // cannot send SUBSCRIBE_UPDATE or UNSUBSCRIBE". Refused by name rather than left
1906        // to the miss below, which would say the identifier names nothing when
1907        // it names a request this session is carrying.
1908        if self.track_statuses.contains_key(&id) || self.inbound_track_statuses.contains_key(&id) {
1909            return Err(EndpointError::NotASubscription(id));
1910        }
1911        // The offers this endpoint made itself. A PUBLISH the *peer* made is
1912        // not here and must not be: this endpoint is the subscriber of that
1913        // one, so an UNSUBSCRIBE naming it would be the publisher ending a
1914        // subscription the sentence above gives the subscriber, and the miss
1915        // below is the right answer for it.
1916        if let Some(state) = self.publishes.get_mut(&id) {
1917            state.on_unsubscribe_received()?;
1918            return Ok(());
1919        }
1920        let sub = self.inbound_subscribes.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
1921        sub.state.on_unsubscribe_received()?;
1922        Ok(())
1923    }
1924
1925    // ── Fetch flow ─────────────────────────────────────────────
1926
1927    /// Send a standalone FETCH message. Allocates a request ID and creates a
1928    /// fetch state machine.
1929    #[allow(clippy::too_many_arguments)]
1930    pub fn fetch(
1931        &mut self,
1932        track_namespace: TrackNamespace,
1933        track_name: Vec<u8>,
1934        subscriber_priority: u8,
1935        group_order: GroupOrder,
1936        start_group: VarInt,
1937        start_object: VarInt,
1938        end_group: VarInt,
1939        end_object: VarInt,
1940        parameters: Vec<KeyValuePair>,
1941    ) -> Result<(VarInt, ControlMessage), EndpointError> {
1942        self.require_active_or_err()?;
1943        let req_id = self.request_ids.allocate()?;
1944
1945        let mut sm = FetchStateMachine::new();
1946        sm.on_fetch_sent()?;
1947        self.fetches.insert(req_id.into_inner(), sm);
1948
1949        let msg = ControlMessage::Fetch(Fetch {
1950            request_id: req_id,
1951            subscriber_priority,
1952            group_order,
1953            fetch_type: FetchType::Standalone,
1954            fetch_payload: FetchPayload::Standalone {
1955                track_namespace,
1956                track_name,
1957                start_group,
1958                start_object,
1959                end_group,
1960                end_object,
1961            },
1962            parameters,
1963        });
1964        Ok((req_id, msg))
1965    }
1966
1967    /// Send a Relative Joining Fetch (Fetch Type 0x2), attaching a fetch to a
1968    /// subscription this session already holds. Allocates a Request ID.
1969    ///
1970    /// Section 8.16 calls it "A Fetch joined together with a Subscribe by
1971    /// specifying the Request ID of an active subscription and a relative
1972    /// starting offset", and has "A publisher receiving a Joining Fetch uses
1973    /// properties of the associated Subscribe to determine the Track
1974    /// Namespace, Track, Start Location, and End Location such that it is
1975    /// contiguous with the associated Subscribe." So a joining fetch names
1976    /// neither the namespace nor the name, and `joining_start` is read against
1977    /// the subscription rather than against the track: Section 8.16.1 has the
1978    /// publisher set "Fetch Start Location: {Subscribe Largest
1979    /// Location.Group - Joining Start, 0}", which makes it a count of groups
1980    /// back from the live edge.
1981    ///
1982    /// # Errors
1983    ///
1984    /// The session error when the session is not established, and the
1985    /// request-id error when this endpoint has no identifier left to spend. A
1986    /// Request ID naming no subscription is not refused here — Section 8.16
1987    /// answers that at the publisher, "it MUST respond with a Fetch Error with
1988    /// code Invalid Joining Request ID", and this endpoint is the subscriber.
1989    /// The code is 0x7 whichever of its two names is read: the FETCH_ERROR
1990    /// table in Section 8.18 lists it under the name above and then describes
1991    /// it one line down as Invalid Joining Subscribe ID.
1992    pub fn joining_fetch(
1993        &mut self,
1994        subscriber_priority: u8,
1995        group_order: GroupOrder,
1996        joining_request_id: VarInt,
1997        joining_start: VarInt,
1998        parameters: Vec<KeyValuePair>,
1999    ) -> Result<(VarInt, ControlMessage), EndpointError> {
2000        self.joining_fetch_of_type(
2001            FetchType::RelativeJoining,
2002            subscriber_priority,
2003            group_order,
2004            joining_request_id,
2005            joining_start,
2006            parameters,
2007        )
2008    }
2009
2010    /// Send an Absolute Joining Fetch (Fetch Type 0x3).
2011    ///
2012    /// Section 8.16.2 is the whole of the difference: "Identical to the
2013    /// Relative Joining fetch except that Fetch Start Location.Group is the
2014    /// Joining Start value." So `joining_start` is the group to begin at
2015    /// rather than a count of groups back, which is what an application that
2016    /// knows the group it wants actually has. Asking for the same range
2017    /// relatively would need the Largest Location, and a subscriber that has
2018    /// not yet been told one cannot compute the offset.
2019    ///
2020    /// # Errors
2021    ///
2022    /// As [`Endpoint::joining_fetch`].
2023    pub fn absolute_joining_fetch(
2024        &mut self,
2025        subscriber_priority: u8,
2026        group_order: GroupOrder,
2027        joining_request_id: VarInt,
2028        joining_start: VarInt,
2029        parameters: Vec<KeyValuePair>,
2030    ) -> Result<(VarInt, ControlMessage), EndpointError> {
2031        self.joining_fetch_of_type(
2032            FetchType::AbsoluteJoining,
2033            subscriber_priority,
2034            group_order,
2035            joining_request_id,
2036            joining_start,
2037            parameters,
2038        )
2039    }
2040
2041    /// What both calls above are, and why neither of them takes the type.
2042    ///
2043    /// Section 8.16 admits three Fetch Types and this payload carries two of
2044    /// them. A type taken as an argument here would leave a third value a
2045    /// caller could pass and this call would have to answer for; naming the
2046    /// two rules it out instead, so there is no answer left to get wrong.
2047    fn joining_fetch_of_type(
2048        &mut self,
2049        fetch_type: FetchType,
2050        subscriber_priority: u8,
2051        group_order: GroupOrder,
2052        joining_request_id: VarInt,
2053        joining_start: VarInt,
2054        parameters: Vec<KeyValuePair>,
2055    ) -> Result<(VarInt, ControlMessage), EndpointError> {
2056        self.require_active_or_err()?;
2057        let req_id = self.request_ids.allocate()?;
2058
2059        let mut sm = FetchStateMachine::new();
2060        sm.on_fetch_sent()?;
2061        self.fetches.insert(req_id.into_inner(), sm);
2062
2063        let msg = ControlMessage::Fetch(Fetch {
2064            request_id: req_id,
2065            subscriber_priority,
2066            group_order,
2067            fetch_type,
2068            fetch_payload: FetchPayload::Joining { joining_request_id, joining_start },
2069            parameters,
2070        });
2071        Ok((req_id, msg))
2072    }
2073
2074    /// Process an incoming FETCH_OK.
2075    pub fn receive_fetch_ok(&mut self, msg: &message::FetchOk) -> Result<(), EndpointError> {
2076        let id = msg.request_id.into_inner();
2077        let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2078        sm.on_fetch_ok()?;
2079        Ok(())
2080    }
2081
2082    /// Process an incoming FETCH_ERROR.
2083    pub fn receive_fetch_error(&mut self, msg: &message::FetchError) -> Result<(), EndpointError> {
2084        let id = msg.request_id.into_inner();
2085        let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2086        sm.on_fetch_error()?;
2087        Ok(())
2088    }
2089
2090    /// Send a FETCH_CANCEL message.
2091    pub fn fetch_cancel(&mut self, request_id: VarInt) -> Result<ControlMessage, EndpointError> {
2092        let id = request_id.into_inner();
2093        let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2094        sm.on_fetch_cancel()?;
2095        Ok(ControlMessage::FetchCancel(FetchCancel { request_id }))
2096    }
2097
2098    /// Notify that a fetch data stream received FIN.
2099    ///
2100    /// It may arrive before the FETCH_OK or FETCH_ERROR answering the
2101    /// request, which leaves the fetch in `FetchState::Unanswered` until the
2102    /// answer lands.
2103    pub fn on_fetch_stream_fin(&mut self, request_id: VarInt) -> Result<(), EndpointError> {
2104        let id = request_id.into_inner();
2105        let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2106        sm.on_stream_fin()?;
2107        Ok(())
2108    }
2109
2110    /// Notify that a fetch data stream was reset.
2111    ///
2112    /// As with a FIN, it may arrive before the answer to the request.
2113    pub fn on_fetch_stream_reset(&mut self, request_id: VarInt) -> Result<(), EndpointError> {
2114        let id = request_id.into_inner();
2115        let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2116        sm.on_stream_reset()?;
2117        Ok(())
2118    }
2119
2120    // ── Answering a FETCH the peer sent ────────────────────────
2121
2122    /// Process an incoming FETCH, recording the fetch it opens.
2123    ///
2124    /// The Request ID has already been checked by [`Self::receive_request`],
2125    /// which every request message passes through before its own handler.
2126    ///
2127    /// A Joining Fetch is recorded like any other. Section 8.16 answers one
2128    /// naming a subscription this session cannot join with a refusal and not
2129    /// a session close, and a refusal is a message this endpoint has to
2130    /// build, so the request it refuses has to be on record first.
2131    ///
2132    /// # Errors
2133    ///
2134    /// The session error when the session is not established, and the fetch
2135    /// flow's own `InvalidTransition` for a second FETCH under an identifier
2136    /// already carrying one.
2137    pub fn receive_fetch(&mut self, msg: &Fetch) -> Result<(), EndpointError> {
2138        self.require_active_or_err()?;
2139        let id = msg.request_id.into_inner();
2140        let unjoinable = self.joining_subscription_missing(msg);
2141        let mut state = FetchStateMachine::new();
2142        state.on_fetch_received()?;
2143        self.inbound_fetches.insert(id, InboundFetch { message: msg.clone(), state, unjoinable });
2144        Ok(())
2145    }
2146
2147    /// The FETCH the peer sent under `request_id` and this endpoint has not
2148    /// answered yet.
2149    ///
2150    /// `None` once it has been answered, and for an identifier this session
2151    /// has no inbound fetch for. The record itself lives on past the answer,
2152    /// because the fetch is not over until its data stream is.
2153    pub fn pending_fetch(&self, request_id: VarInt) -> Option<&Fetch> {
2154        self.inbound_fetches
2155            .get(&request_id.into_inner())
2156            .filter(|f| matches!(f.state.state(), FetchState::Pending | FetchState::Unanswered))
2157            .map(|f| &f.message)
2158    }
2159
2160    /// How many FETCHes the peer has sent that are still waiting for an
2161    /// answer.
2162    pub fn pending_fetch_count(&self) -> usize {
2163        self.inbound_fetches
2164            .values()
2165            .filter(|f| matches!(f.state.state(), FetchState::Pending | FetchState::Unanswered))
2166            .count()
2167    }
2168
2169    /// The identifier an arriving Joining Fetch names, when this session has
2170    /// no subscription it may join.
2171    ///
2172    /// Section 8.16:
2173    /// "If a publisher receives a Joining Fetch with a Request ID that
2174    /// does not correspond to an existing Subscribe in the same session, it
2175    /// MUST respond with a Fetch Error with code Invalid Joining Request ID."
2176    ///
2177    /// The verdict is taken as the FETCH arrives, because that is the moment
2178    /// the sentence names, and it is kept. A subscription that ends between
2179    /// the FETCH and its answer does not turn a fetch that could be joined
2180    /// into one that could not.
2181    ///
2182    /// A standalone fetch names none and answers `None`, and so does a joining
2183    /// one whose subscription is live. The subscription is one the peer opened,
2184    /// because the peer is the end that fetches and this endpoint is the one
2185    /// answering.
2186    /// "Existing" is read as "has not ended". Draft-16 Section 9.16.2 states
2187    /// the same rule with the states named - "in the Established or Pending
2188    /// (subscriber) states" - which is the same set read the same way.
2189    fn joining_subscription_missing(&self, msg: &Fetch) -> Option<u64> {
2190        let message::FetchPayload::Joining { joining_request_id: joined, .. } = &msg.fetch_payload
2191        else {
2192            return None;
2193        };
2194        let joined = joined.into_inner();
2195        let live = self
2196            .inbound_subscribes
2197            .get(&joined)
2198            .is_some_and(|s| s.state.state() != SubscriptionState::Done);
2199        if live {
2200            None
2201        } else {
2202            Some(joined)
2203        }
2204    }
2205
2206    /// Build the FETCH_OK accepting a fetch the peer opened.
2207    ///
2208    /// # Errors
2209    ///
2210    /// [`EndpointError::UnknownRequest`] if the peer opened no fetch under
2211    /// that identifier, [`EndpointError::UnjoinableSubscription`] for a
2212    /// Joining Fetch naming a subscription this session cannot join, and the
2213    /// fetch flow's own `InvalidTransition` for a second answer: Section 4.1
2214    /// says the publisher "MUST send exactly one FETCH_OK or FETCH_ERROR in
2215    /// response to a FETCH".
2216    pub fn send_fetch_ok(
2217        &mut self,
2218        request_id: VarInt,
2219        group_order: GroupOrder,
2220        end_of_track: u8,
2221        end_location: Location,
2222        parameters: Vec<KeyValuePair>,
2223    ) -> Result<ControlMessage, EndpointError> {
2224        let id = request_id.into_inner();
2225        let unjoinable =
2226            self.inbound_fetches.get(&id).ok_or(EndpointError::UnknownRequest(id))?.unjoinable;
2227        if let Some(joining) = unjoinable {
2228            return Err(EndpointError::UnjoinableSubscription { fetch: id, joining });
2229        }
2230        let fetch = self.inbound_fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2231        fetch.state.on_fetch_ok_sent()?;
2232        Ok(ControlMessage::FetchOk(message::FetchOk {
2233            request_id,
2234            group_order,
2235            end_of_track,
2236            end_location,
2237            parameters,
2238        }))
2239    }
2240
2241    /// Build the FETCH_ERROR refusing a fetch the peer opened.
2242    ///
2243    /// Section 8.16 names the code a Joining Fetch naming an unjoinable
2244    /// subscription is refused with, so that refusal cannot go out under any
2245    /// other: a subscriber told the wrong reason retries the wrong thing.
2246    ///
2247    /// # Errors
2248    ///
2249    /// [`EndpointError::UnknownRequest`] if the peer opened no fetch under
2250    /// that identifier, and the fetch flow's own `InvalidTransition` if it has
2251    /// already been answered.
2252    pub fn send_fetch_error(
2253        &mut self,
2254        request_id: VarInt,
2255        error_code: VarInt,
2256        reason_phrase: Vec<u8>,
2257    ) -> Result<ControlMessage, EndpointError> {
2258        let id = request_id.into_inner();
2259        let fetch = self.inbound_fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2260        if fetch.unjoinable.is_some() {
2261            let required = FetchErrorCode::InvalidJoiningRequestId as u64;
2262            if error_code.into_inner() != required {
2263                return Err(EndpointError::WrongJoiningRefusal { fetch: id, required });
2264            }
2265        }
2266        fetch.state.on_fetch_error_sent()?;
2267        Ok(ControlMessage::FetchError(message::FetchError {
2268            request_id,
2269            error_code,
2270            reason_phrase,
2271        }))
2272    }
2273
2274    /// Process an incoming FETCH_CANCEL, ending the fetch the peer opened.
2275    ///
2276    /// Section 8.19: the subscriber sends it to stop a fetch it no longer
2277    /// wants, so the record this endpoint serves the fetch from is the one it
2278    /// ends.
2279    ///
2280    /// # Errors
2281    ///
2282    /// [`EndpointError::UnknownRequest`] if the peer opened no fetch under
2283    /// that identifier, and the fetch flow's own `InvalidTransition` for a fetch
2284    /// that has already ended.
2285    pub fn receive_fetch_cancel(&mut self, msg: &FetchCancel) -> Result<(), EndpointError> {
2286        let id = msg.request_id.into_inner();
2287        let fetch = self.inbound_fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2288        fetch.state.on_fetch_cancel_received()?;
2289        Ok(())
2290    }
2291
2292    /// Note that this endpoint finished the data stream serving a fetch the
2293    /// peer opened.
2294    ///
2295    /// A fetch is over when its answer and its data stream have both settled,
2296    /// and this is the second of those for the end that serves it.
2297    ///
2298    /// # Errors
2299    ///
2300    /// [`EndpointError::UnknownRequest`] if the peer opened no fetch under
2301    /// that identifier, and the fetch flow's own `InvalidTransition` from a state
2302    /// the stream cannot close from.
2303    pub fn on_peer_fetch_stream_fin(&mut self, request_id: VarInt) -> Result<(), EndpointError> {
2304        let id = request_id.into_inner();
2305        let fetch = self.inbound_fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2306        fetch.state.on_stream_fin_sent()?;
2307        Ok(())
2308    }
2309
2310    // ── Subscribe Namespace flow ──────────────────────────────
2311
2312    /// Send a SUBSCRIBE_NAMESPACE message. Returns the allocated request ID
2313    /// alongside the control message so the caller can correlate replies.
2314    ///
2315    /// Section 8.28 addresses the first half of the overlap rule to this end
2316    /// of the session: "A subscriber cannot make overlapping namespace
2317    /// subscriptions on a single session." So a prefix overlapping one this
2318    /// endpoint has already asked about is refused here rather than built and
2319    /// sent for the peer to refuse.
2320    ///
2321    /// # Errors
2322    ///
2323    /// The session error when the session is not established,
2324    /// [`EndpointError::OwnPrefixOverlap`] when the prefix overlaps one this
2325    /// endpoint is already subscribed to, and the Request ID allocator's own
2326    /// error when the peer has granted no room for another request.
2327    pub fn subscribe_namespace(
2328        &mut self,
2329        track_namespace_prefix: TrackNamespace,
2330        parameters: Vec<KeyValuePair>,
2331    ) -> Result<(VarInt, ControlMessage), EndpointError> {
2332        self.require_active_or_err()?;
2333        // The subscriber's half of the rule, refused before the message
2334        // exists. A publisher that follows this draft answers it with a
2335        // refusal, so building it spends a Request ID on a namespace
2336        // subscription that is not going to open.
2337        if let Some(established) = self.own_prefix_overlap(&track_namespace_prefix) {
2338            return Err(EndpointError::OwnPrefixOverlap { established });
2339        }
2340        let req_id = self.request_ids.allocate()?;
2341        let key = track_namespace_prefix.0.clone();
2342        let mut sm = SubscribeNamespaceStateMachine::new();
2343        sm.on_subscribe_namespace_sent()?;
2344        self.subscribe_namespaces.insert(req_id.into_inner(), sm);
2345        self.subscribe_namespace_ids.insert(key, req_id.into_inner());
2346        Ok((
2347            req_id,
2348            ControlMessage::SubscribeNamespace(SubscribeNamespace {
2349                request_id: req_id,
2350                track_namespace_prefix,
2351                parameters,
2352            }),
2353        ))
2354    }
2355
2356    /// Process an incoming SUBSCRIBE_NAMESPACE_OK.
2357    pub fn receive_subscribe_namespace_ok(
2358        &mut self,
2359        msg: &SubscribeNamespaceOk,
2360    ) -> Result<(), EndpointError> {
2361        let id = msg.request_id.into_inner();
2362        let sm = self.subscribe_namespaces.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2363        sm.on_subscribe_namespace_ok()?;
2364        Ok(())
2365    }
2366
2367    /// Process an incoming SUBSCRIBE_NAMESPACE_ERROR.
2368    pub fn receive_subscribe_namespace_error(
2369        &mut self,
2370        msg: &SubscribeNamespaceError,
2371    ) -> Result<(), EndpointError> {
2372        let id = msg.request_id.into_inner();
2373        let sm = self.subscribe_namespaces.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2374        sm.on_subscribe_namespace_error()?;
2375        Ok(())
2376    }
2377
2378    /// Send an UNSUBSCRIBE_NAMESPACE message.
2379    pub fn unsubscribe_namespace(
2380        &mut self,
2381        track_namespace_prefix: TrackNamespace,
2382    ) -> Result<ControlMessage, EndpointError> {
2383        let id = *self
2384            .subscribe_namespace_ids
2385            .get(&track_namespace_prefix.0)
2386            .ok_or(EndpointError::UnknownNamespace)?;
2387        let sm = self.subscribe_namespaces.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2388        sm.on_unsubscribe_namespace()?;
2389        Ok(ControlMessage::UnsubscribeNamespace(UnsubscribeNamespace { track_namespace_prefix }))
2390    }
2391
2392    // ── Answering a SUBSCRIBE_NAMESPACE the peer sent ──────────
2393
2394    /// Process an incoming SUBSCRIBE_NAMESPACE, recording the namespace
2395    /// subscription it opens.
2396    ///
2397    /// Section 8.28: "The subscriber sends the SUBSCRIBE_NAMESPACE control
2398    /// message to a publisher to request the current set of matching
2399    /// announcements and established subscriptions, as well as future updates
2400    /// to the set."
2401    ///
2402    /// The set it asks for is this endpoint's to decide, and deciding needs
2403    /// both the request and somewhere to answer from. The record holds the
2404    /// message and not only its state, because every message that answers
2405    /// this request or ends it names the prefix the request carried, and
2406    /// nothing else here has it.
2407    ///
2408    /// # Errors
2409    ///
2410    /// The session error when the session is not established.
2411    pub fn receive_subscribe_namespace(
2412        &mut self,
2413        msg: &SubscribeNamespace,
2414    ) -> Result<(), EndpointError> {
2415        self.require_active_or_err()?;
2416        let overlaps = self.peer_prefix_overlap(&msg.track_namespace_prefix);
2417        let mut state = SubscribeNamespaceStateMachine::new();
2418        state.on_subscribe_namespace_received()?;
2419        self.inbound_subscribe_namespaces.insert(
2420            msg.request_id.into_inner(),
2421            InboundSubscribeNamespace { message: msg.clone(), state, overlaps },
2422        );
2423        Ok(())
2424    }
2425
2426    /// The earliest namespace subscription the peer has made whose prefix
2427    /// overlaps `prefix`, and `None` when there is none.
2428    ///
2429    /// Only ones that have not ended count: the sentence weighs the arriving
2430    /// prefix against "an active SUBSCRIBE_NAMESPACE", so one the peer has
2431    /// withdrawn and one this endpoint refused are both past. Drafts 07
2432    /// through 11 say "an earlier" instead and count those too.
2433    ///
2434    /// One that has arrived and has not been answered does count. It is not
2435    /// active yet, but this endpoint is the one about to make it so, and
2436    /// accepting both would leave the session holding exactly the pair the
2437    /// sentence exists to prevent.
2438    ///
2439    /// Namespace subscriptions this endpoint made are a separate set and are
2440    /// not consulted. This endpoint is the subscriber for those, so a prefix
2441    /// it asked about says nothing about what the peer may ask about.
2442    ///
2443    /// The lowest Request ID wins when more than one overlaps, so the answer
2444    /// does not depend on the order a map happens to iterate in. Identifiers
2445    /// are handed out in increasing order, so the lowest of them is the
2446    /// earliest request.
2447    fn peer_prefix_overlap(&self, prefix: &TrackNamespace) -> Option<u64> {
2448        self.inbound_subscribe_namespaces
2449            .iter()
2450            .filter(|(_, s)| s.state.state() != SubscribeNamespaceState::Done)
2451            .filter_map(|(&id, s)| {
2452                prefixes_overlap(&s.message.track_namespace_prefix.0, &prefix.0).then_some(id)
2453            })
2454            .min()
2455    }
2456
2457    /// The earliest namespace subscription **this endpoint** has made whose
2458    /// prefix overlaps `prefix`, and `None` when there is none.
2459    ///
2460    /// The subscriber's half of the same sentence reads over this endpoint's
2461    /// own requests, and takes the same view of which of them are past as
2462    /// [`Self::peer_prefix_overlap`] takes of the peer's.
2463    fn own_prefix_overlap(&self, prefix: &TrackNamespace) -> Option<u64> {
2464        self.subscribe_namespace_ids
2465            .iter()
2466            .filter_map(|(key, &id)| prefixes_overlap(key, &prefix.0).then_some(id))
2467            .filter(|id| {
2468                self.subscribe_namespaces
2469                    .get(id)
2470                    .is_some_and(|sm| sm.state() != SubscribeNamespaceState::Done)
2471            })
2472            .min()
2473    }
2474
2475    /// The SUBSCRIBE_NAMESPACE the peer sent under `request_id` and this
2476    /// endpoint has not answered yet.
2477    ///
2478    /// `None` once it has been answered, and for an identifier the peer has
2479    /// subscribed to nothing under. The record itself lives on past the
2480    /// answer, because a namespace subscription that was accepted is not over
2481    /// until it is withdrawn.
2482    pub fn pending_subscribe_namespace(&self, request_id: VarInt) -> Option<&SubscribeNamespace> {
2483        self.inbound_subscribe_namespaces
2484            .get(&request_id.into_inner())
2485            .filter(|s| s.state.state() == SubscribeNamespaceState::Pending)
2486            .map(|s| &s.message)
2487    }
2488
2489    /// How many namespace subscriptions the peer has made that are still
2490    /// waiting for an answer.
2491    pub fn pending_subscribe_namespace_count(&self) -> usize {
2492        self.inbound_subscribe_namespaces
2493            .values()
2494            .filter(|s| s.state.state() == SubscribeNamespaceState::Pending)
2495            .count()
2496    }
2497
2498    /// The identifier of a live namespace subscription the peer made for
2499    /// `prefix`.
2500    ///
2501    /// Section 8.31 names a Track Namespace Prefix where the
2502    /// SUBSCRIBE_NAMESPACE it ends named a Request ID, so one record has to
2503    /// be reachable both ways. It is stored under the identifier, which is
2504    /// unique, and found by prefix with a scan of the same map. A second map
2505    /// from prefix to identifier would be quicker and could fall out of step
2506    /// with the first; there is nothing here for it to disagree with.
2507    ///
2508    /// One that has ended is skipped, so a prefix subscribed again after
2509    /// being withdrawn finds the live one.
2510    fn inbound_subscribe_namespace_id(&self, prefix: &TrackNamespace) -> Option<u64> {
2511        self.inbound_subscribe_namespaces
2512            .iter()
2513            .find(|(_, s)| {
2514                s.message.track_namespace_prefix == *prefix
2515                    && s.state.state() != SubscribeNamespaceState::Done
2516            })
2517            .map(|(id, _)| *id)
2518    }
2519
2520    /// Build the SUBSCRIBE_NAMESPACE_OK accepting a namespace subscription
2521    /// the peer made.
2522    ///
2523    /// Section 5.1: "A publisher MUST send exactly one SUBSCRIBE_NAMESPACE_OK
2524    /// or SUBSCRIBE_NAMESPACE_ERROR in response to a SUBSCRIBE_NAMESPACE."
2525    ///
2526    /// One answer and no second one: the flow moves on the first, and a
2527    /// second call finds a record that has left Pending.
2528    ///
2529    /// # Errors
2530    ///
2531    /// [`EndpointError::UnknownRequest`] if the peer has subscribed to
2532    /// nothing under that identifier, [`EndpointError::PeerPrefixOverlap`] if
2533    /// the prefix it asked about overlaps one the peer is already subscribed
2534    /// to, and the namespace flow's own `InvalidTransition` for a request
2535    /// already answered.
2536    pub fn send_subscribe_namespace_ok(
2537        &mut self,
2538        request_id: VarInt,
2539    ) -> Result<ControlMessage, EndpointError> {
2540        let id = request_id.into_inner();
2541        let sub = self
2542            .inbound_subscribe_namespaces
2543            .get_mut(&id)
2544            .ok_or(EndpointError::UnknownRequest(id))?;
2545        // The MUST names one answer for this request, and it is not this one.
2546        if let Some(established) = sub.overlaps {
2547            return Err(EndpointError::PeerPrefixOverlap { request: id, established });
2548        }
2549        sub.state.on_subscribe_namespace_ok_sent()?;
2550        Ok(ControlMessage::SubscribeNamespaceOk(SubscribeNamespaceOk { request_id }))
2551    }
2552
2553    /// Build the SUBSCRIBE_NAMESPACE_ERROR refusing a namespace subscription
2554    /// the peer made.
2555    ///
2556    /// The other half of the same sentence: one message back, whichever of
2557    /// the two it is.
2558    ///
2559    /// # Errors
2560    ///
2561    /// [`EndpointError::UnknownRequest`] if the peer has subscribed to
2562    /// nothing under that identifier, [`EndpointError::WrongOverlapRefusal`]
2563    /// if the request overlaps another and the code named is not the one the
2564    /// draft assigns to that refusal, and the namespace flow's own
2565    /// `InvalidTransition` for a request already answered.
2566    pub fn send_subscribe_namespace_error(
2567        &mut self,
2568        request_id: VarInt,
2569        error_code: VarInt,
2570        reason_phrase: Vec<u8>,
2571    ) -> Result<ControlMessage, EndpointError> {
2572        let id = request_id.into_inner();
2573        let sub = self
2574            .inbound_subscribe_namespaces
2575            .get_mut(&id)
2576            .ok_or(EndpointError::UnknownRequest(id))?;
2577        if sub.overlaps.is_some() {
2578            let required = SubscribeNamespaceErrorCode::NamespacePrefixOverlap as u64;
2579            if error_code.into_inner() != required {
2580                return Err(EndpointError::WrongOverlapRefusal { request: id, required });
2581            }
2582        }
2583        sub.state.on_subscribe_namespace_error_sent()?;
2584        Ok(ControlMessage::SubscribeNamespaceError(SubscribeNamespaceError {
2585            request_id,
2586            error_code,
2587            reason_phrase,
2588        }))
2589    }
2590
2591    /// Process an incoming UNSUBSCRIBE_NAMESPACE, ending the namespace
2592    /// subscription the peer made.
2593    ///
2594    /// Section 5.1: "An UNSUBSCRIBE_NAMESPACE withdraws a previous
2595    /// SUBSCRIBE_NAMESPACE."
2596    ///
2597    /// The subscription it ends is the peer's, so the record it reads is the
2598    /// one this endpoint keeps of what the peer subscribed to. One this
2599    /// endpoint made is withdrawn by [`Endpoint::unsubscribe_namespace`],
2600    /// which is the same message travelling the other way.
2601    ///
2602    /// # Errors
2603    ///
2604    /// [`EndpointError::UnknownPeerNamespaceSubscription`] if the peer has no
2605    /// live namespace subscription for that prefix, and the namespace flow's
2606    /// own `InvalidTransition` for one this endpoint never accepted.
2607    pub fn receive_unsubscribe_namespace(
2608        &mut self,
2609        msg: &UnsubscribeNamespace,
2610    ) -> Result<(), EndpointError> {
2611        let id = self
2612            .inbound_subscribe_namespace_id(&msg.track_namespace_prefix)
2613            .ok_or(EndpointError::UnknownPeerNamespaceSubscription)?;
2614        let sub = self
2615            .inbound_subscribe_namespaces
2616            .get_mut(&id)
2617            .ok_or(EndpointError::UnknownRequest(id))?;
2618        sub.state.on_unsubscribe_namespace_received()?;
2619        Ok(())
2620    }
2621
2622    // ── Announce flow ──────────────────────────────────────────
2623
2624    /// Send an ANNOUNCE message. Returns the allocated request ID alongside
2625    /// the control message.
2626    pub fn announce(
2627        &mut self,
2628        track_namespace: TrackNamespace,
2629        parameters: Vec<KeyValuePair>,
2630    ) -> Result<(VarInt, ControlMessage), EndpointError> {
2631        self.require_active_or_err()?;
2632        let req_id = self.request_ids.allocate()?;
2633        let key = track_namespace.0.clone();
2634        let mut sm = AnnounceStateMachine::new();
2635        sm.on_announce_sent()?;
2636        self.announces.insert(req_id.into_inner(), sm);
2637        self.announce_ids.insert(key, req_id.into_inner());
2638        Ok((
2639            req_id,
2640            ControlMessage::Announce(Announce { request_id: req_id, track_namespace, parameters }),
2641        ))
2642    }
2643
2644    /// Process an incoming ANNOUNCE_OK.
2645    pub fn receive_announce_ok(&mut self, msg: &AnnounceOk) -> Result<(), EndpointError> {
2646        let id = msg.request_id.into_inner();
2647        let sm = self.announces.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2648        sm.on_announce_ok()?;
2649        Ok(())
2650    }
2651
2652    /// Process an incoming ANNOUNCE_ERROR.
2653    pub fn receive_announce_error(&mut self, msg: &AnnounceError) -> Result<(), EndpointError> {
2654        let id = msg.request_id.into_inner();
2655        let sm = self.announces.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2656        sm.on_announce_error()?;
2657        Ok(())
2658    }
2659
2660    /// Process an incoming ANNOUNCE_CANCEL.
2661    pub fn receive_announce_cancel(&mut self, msg: &AnnounceCancel) -> Result<(), EndpointError> {
2662        let id = *self
2663            .announce_ids
2664            .get(&msg.track_namespace.0)
2665            .ok_or(EndpointError::UnknownNamespace)?;
2666        let sm = self.announces.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2667        sm.on_announce_cancel()?;
2668        Ok(())
2669    }
2670
2671    /// Send an UNANNOUNCE message (publisher withdrawing).
2672    pub fn unannounce(
2673        &mut self,
2674        track_namespace: TrackNamespace,
2675    ) -> Result<ControlMessage, EndpointError> {
2676        let id =
2677            *self.announce_ids.get(&track_namespace.0).ok_or(EndpointError::UnknownNamespace)?;
2678        let sm = self.announces.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2679        sm.on_unannounce()?;
2680        Ok(ControlMessage::Unannounce(Unannounce { track_namespace }))
2681    }
2682
2683    // ── Answering an ANNOUNCE the peer sent ────────────────────
2684
2685    /// Process an incoming ANNOUNCE, recording the announcement it makes.
2686    ///
2687    /// Section 8.23: "The publisher sends the ANNOUNCE control message to
2688    /// advertise that it has tracks available within the announced Track
2689    /// Namespace. The receiver verifies the publisher is authorized to publish
2690    /// tracks under this namespace."
2691    ///
2692    /// Verifying is the application's to do, and it needs both the message to
2693    /// verify and somewhere to answer from. The Request ID has already been
2694    /// checked by [`Self::receive_request`], which every request message passes
2695    /// through before its own handler, so a repeat of one the peer has already
2696    /// spent never reaches here.
2697    ///
2698    /// # Errors
2699    ///
2700    /// The session error when the session is not established.
2701    pub fn receive_announce(&mut self, msg: &Announce) -> Result<(), EndpointError> {
2702        self.require_active_or_err()?;
2703        let id = msg.request_id.into_inner();
2704        let mut state = AnnounceStateMachine::new();
2705        state.on_announce_received()?;
2706        self.inbound_announces.insert(id, InboundAnnounce { message: msg.clone(), state });
2707        Ok(())
2708    }
2709
2710    /// The ANNOUNCE the peer sent under `request_id` and this endpoint has not
2711    /// answered yet.
2712    ///
2713    /// `None` once it has been answered, and for an identifier the peer has
2714    /// announced nothing under. The record itself lives on past the answer,
2715    /// because an announcement that was accepted is not over until it is
2716    /// withdrawn or cancelled.
2717    pub fn pending_announce(&self, request_id: VarInt) -> Option<&Announce> {
2718        self.inbound_announces
2719            .get(&request_id.into_inner())
2720            .filter(|a| a.state.state() == AnnounceState::Pending)
2721            .map(|a| &a.message)
2722    }
2723
2724    /// How many announcements the peer has made that are still waiting for an
2725    /// answer.
2726    pub fn pending_announce_count(&self) -> usize {
2727        self.inbound_announces
2728            .values()
2729            .filter(|a| a.state.state() == AnnounceState::Pending)
2730            .count()
2731    }
2732
2733    /// The identifier of a live announcement the peer made for `namespace`.
2734    ///
2735    /// Section 8.26: "The publisher sends the UNANNOUNCE control message to
2736    /// indicate its intent to stop serving new subscriptions for tracks within
2737    /// the provided Track Namespace." and Section 8.27 says what a cancellation
2738    /// is for: the subscriber "will stop sending new subscriptions for tracks
2739    /// within the provided Track Namespace". Both name a namespace where the
2740    /// ANNOUNCE they are about named a Request ID, so one record has to be
2741    /// reachable both ways.
2742    ///
2743    /// It is stored under the identifier, which is unique, and found by
2744    /// namespace with a scan of the same map. A second map from namespace to
2745    /// identifier would be quicker and could fall out of step with the first;
2746    /// there is nothing here for it to disagree with.
2747    ///
2748    /// An announcement that has ended is skipped, so a namespace announced
2749    /// again after being withdrawn finds the live one. Which of two live
2750    /// announcements for the same namespace is found is not decided here,
2751    /// because no sentence in the draft makes a second one for a namespace
2752    /// already announced an error.
2753    fn inbound_announce_id(&self, namespace: &TrackNamespace) -> Option<u64> {
2754        self.inbound_announces
2755            .iter()
2756            .find(|(_, a)| {
2757                a.message.track_namespace == *namespace && a.state.state() != AnnounceState::Done
2758            })
2759            .map(|(id, _)| *id)
2760    }
2761
2762    /// Build the ANNOUNCE_OK accepting an announcement the peer made.
2763    ///
2764    /// Section 5.2: "A subscriber MUST send exactly one ANNOUNCE_OK or
2765    /// ANNOUNCE_ERROR in response to an ANNOUNCE. The publisher SHOULD close
2766    /// the session with a protocol error if it receives more than one."
2767    ///
2768    /// One answer and no second one: the flow moves on the first, and a second
2769    /// call finds a record that has left Pending.
2770    ///
2771    /// # Errors
2772    ///
2773    /// [`EndpointError::UnknownRequest`] if the peer has announced nothing
2774    /// under that identifier, and the namespace flow's own `InvalidTransition`
2775    /// for an announcement already answered.
2776    pub fn send_announce_ok(
2777        &mut self,
2778        request_id: VarInt,
2779    ) -> Result<ControlMessage, EndpointError> {
2780        let id = request_id.into_inner();
2781        let ann = self.inbound_announces.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2782        ann.state.on_announce_ok_sent()?;
2783        Ok(ControlMessage::AnnounceOk(AnnounceOk { request_id }))
2784    }
2785
2786    /// Build the ANNOUNCE_ERROR refusing an announcement the peer made.
2787    ///
2788    /// The same sentence in Section 5.2 answers both ways: one message back and
2789    /// no second one, whichever of the two it is.
2790    ///
2791    /// # Errors
2792    ///
2793    /// [`EndpointError::UnknownRequest`] if the peer has announced nothing
2794    /// under that identifier, and the namespace flow's own `InvalidTransition`
2795    /// for an announcement already answered.
2796    pub fn send_announce_error(
2797        &mut self,
2798        request_id: VarInt,
2799        error_code: VarInt,
2800        reason_phrase: Vec<u8>,
2801    ) -> Result<ControlMessage, EndpointError> {
2802        let id = request_id.into_inner();
2803        let ann = self.inbound_announces.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2804        ann.state.on_announce_error_sent()?;
2805        Ok(ControlMessage::AnnounceError(AnnounceError { request_id, error_code, reason_phrase }))
2806    }
2807
2808    /// Process an incoming UNANNOUNCE, ending the announcement the peer made.
2809    ///
2810    /// Section 8.26: "The publisher sends the UNANNOUNCE control message to
2811    /// indicate its intent to stop serving new subscriptions for tracks within
2812    /// the provided Track Namespace."
2813    ///
2814    /// The announcement it ends is the peer's, so the record it reads is the
2815    /// one this endpoint keeps of what the peer announced. An announcement this
2816    /// endpoint made is withdrawn by [`Self::unannounce`], which is the same
2817    /// message travelling the other way.
2818    ///
2819    /// # Errors
2820    ///
2821    /// [`EndpointError::UnknownPeerNamespace`] if the peer has no live
2822    /// announcement for that namespace, and the namespace flow's own
2823    /// `InvalidTransition` for one this endpoint never accepted.
2824    pub fn receive_unannounce(&mut self, msg: &Unannounce) -> Result<(), EndpointError> {
2825        let id = self
2826            .inbound_announce_id(&msg.track_namespace)
2827            .ok_or(EndpointError::UnknownPeerNamespace)?;
2828        let ann = self.inbound_announces.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2829        ann.state.on_unannounce_received()?;
2830        Ok(())
2831    }
2832
2833    /// Build the ANNOUNCE_CANCEL revoking an acceptance.
2834    ///
2835    /// Section 7.3 names what a cancellation revokes: a namespace "it
2836    /// previously responded ANNOUNCE_OK to". Section 8.27 says what it does:
2837    /// the subscriber "will stop sending new subscriptions for tracks within
2838    /// the provided Track Namespace".
2839    ///
2840    /// Previously responded ANNOUNCE_OK to is a state, and it is Active: an
2841    /// announcement reaches it by being accepted and no other way. One still
2842    /// waiting for an answer, one refused and one already ended are all refused
2843    /// here rather than sent.
2844    ///
2845    /// The announcement is the peer's. An announcement this endpoint made is
2846    /// not cancelled by its own publisher; the peer cancels it, and that
2847    /// arrives at [`Self::receive_announce_cancel`].
2848    ///
2849    /// # Errors
2850    ///
2851    /// [`EndpointError::UnknownPeerNamespace`] if the peer has no live
2852    /// announcement for that namespace, and the namespace flow's own
2853    /// `InvalidTransition` for one this endpoint never accepted.
2854    pub fn announce_cancel(
2855        &mut self,
2856        track_namespace: TrackNamespace,
2857        error_code: VarInt,
2858        reason_phrase: Vec<u8>,
2859    ) -> Result<ControlMessage, EndpointError> {
2860        let id = self
2861            .inbound_announce_id(&track_namespace)
2862            .ok_or(EndpointError::UnknownPeerNamespace)?;
2863        let ann = self.inbound_announces.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2864        ann.state.on_announce_cancel_sent()?;
2865        Ok(ControlMessage::AnnounceCancel(AnnounceCancel {
2866            track_namespace,
2867            error_code,
2868            reason_phrase,
2869        }))
2870    }
2871
2872    // ── Track Status flow ──────────────────────────────────────
2873
2874    /// Send a TRACK_STATUS message asking after a track, and record it.
2875    ///
2876    /// Named for the message this draft carries. TRACK_STATUS_REQUEST was the
2877    /// earlier drafts' name for the request and is not a message here, so a
2878    /// method carrying that name would say the wrong thing about which of the
2879    /// two shapes goes on the wire.
2880    ///
2881    /// Returns the allocated request ID alongside the control message.
2882    #[allow(clippy::too_many_arguments)]
2883    pub fn track_status(
2884        &mut self,
2885        track_namespace: TrackNamespace,
2886        track_name: Vec<u8>,
2887        subscriber_priority: u8,
2888        group_order: GroupOrder,
2889        forward: Forward,
2890        filter_type: FilterType,
2891        parameters: Vec<KeyValuePair>,
2892    ) -> Result<(VarInt, ControlMessage), EndpointError> {
2893        // TRACK_STATUS is the SUBSCRIBE message on draft-13, filter-dependent
2894        // fields included, and this call carries none of them.
2895        if matches!(filter_type, FilterType::AbsoluteStart | FilterType::AbsoluteRange) {
2896            return Err(EndpointError::FilterNeedsRange);
2897        }
2898        self.require_active_or_err()?;
2899        let req_id = self.request_ids.allocate()?;
2900        let mut sm = TrackStatusStateMachine::new();
2901        sm.on_track_status_sent()?;
2902        self.track_statuses.insert(req_id.into_inner(), sm);
2903        Ok((
2904            req_id,
2905            ControlMessage::TrackStatus(TrackStatus {
2906                request_id: req_id,
2907                track_namespace,
2908                track_name,
2909                subscriber_priority,
2910                group_order,
2911                forward,
2912                filter_type,
2913                start_group: None,
2914                start_object: None,
2915                end_group: None,
2916                parameters,
2917            }),
2918        ))
2919    }
2920
2921    /// Process an incoming TRACK_STATUS_OK reply.
2922    ///
2923    /// Section 8.21 gives this draft a pair of answers where the earlier ones
2924    /// had a single TRACK_STATUS, so the acceptance and the refusal walk
2925    /// separate edges: a machine told only that an answer arrived cannot say
2926    /// which of the two it was.
2927    pub fn receive_track_status_ok(&mut self, msg: &TrackStatusOk) -> Result<(), EndpointError> {
2928        let id = msg.request_id.into_inner();
2929        let sm = self.track_statuses.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2930        sm.on_track_status_ok()?;
2931        Ok(())
2932    }
2933
2934    /// Process an incoming TRACK_STATUS_ERROR reply.
2935    ///
2936    /// Section 8.22: "The publisher sends a TRACK_STATUS_ERROR control message
2937    /// in response to a failed TRACK_STATUS message."
2938    pub fn receive_track_status_error(
2939        &mut self,
2940        msg: &TrackStatusErrorMsg,
2941    ) -> Result<(), EndpointError> {
2942        let id = msg.request_id.into_inner();
2943        let sm = self.track_statuses.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2944        sm.on_track_status_error()?;
2945        Ok(())
2946    }
2947
2948    // ── Answering a TRACK_STATUS the peer sent ─────────────────
2949
2950    /// Process an incoming TRACK_STATUS, recording what the peer asked about.
2951    ///
2952    /// Section 8.20: the receiver of one "treats it identically as if it had
2953    /// received a SUBSCRIBE message, except it does not create downstream
2954    /// subscription state or send any Objects". The exception is why this does
2955    /// not reach for the subscriptions the peer has opened: nothing that names
2956    /// a subscription is to find this request, and the surest way to hold to
2957    /// that is for it never to be one.
2958    ///
2959    /// # Errors
2960    ///
2961    /// The session error when the session is not established.
2962    pub fn receive_track_status(&mut self, msg: &TrackStatus) -> Result<(), EndpointError> {
2963        self.require_active_or_err()?;
2964        let id = msg.request_id.into_inner();
2965        let mut state = TrackStatusStateMachine::new();
2966        state.on_track_status_received()?;
2967        self.inbound_track_statuses.insert(id, InboundTrackStatus { message: msg.clone(), state });
2968        Ok(())
2969    }
2970
2971    /// The TRACK_STATUS the peer sent under `request_id` and this endpoint has
2972    /// not answered yet.
2973    ///
2974    /// `None` once it has been answered, and for an identifier this session has
2975    /// carried no track status under.
2976    pub fn pending_track_status(&self, request_id: VarInt) -> Option<&TrackStatus> {
2977        self.inbound_track_statuses
2978            .get(&request_id.into_inner())
2979            .filter(|t| t.state.state() == TrackStatusState::Pending)
2980            .map(|t| &t.message)
2981    }
2982
2983    /// How many track statuses the peer has asked about that are still waiting
2984    /// for an answer.
2985    pub fn pending_track_status_count(&self) -> usize {
2986        self.inbound_track_statuses
2987            .values()
2988            .filter(|t| t.state.state() == TrackStatusState::Pending)
2989            .count()
2990    }
2991
2992    /// Build the TRACK_STATUS_OK accepting a track status the peer asked for.
2993    ///
2994    /// Section 8.21: "The publisher sends a TRACK_STATUS_OK control message in
2995    /// response to a successful TRACK_STATUS message", populating it "exactly
2996    /// as it would have populated a SUBSCRIBE_OK, setting Track Alias to 0".
2997    ///
2998    /// The alias is not a parameter for that reason: the one value the draft
2999    /// allows is the one this builds, so no caller can put another on the wire.
3000    /// The sentence after it is what keeps the alias out of the table this
3001    /// endpoint judges aliases against - "It is not considered an error if
3002    /// Track Alias 0 is already in use by an active subscription" - so nothing
3003    /// here consults that table and nothing here adds to it. An alias that
3004    /// names no track cannot collide with one that does.
3005    ///
3006    /// # Errors
3007    ///
3008    /// [`EndpointError::UnknownRequest`] if the peer has asked nothing under
3009    /// that identifier, and the flow's own `InvalidTransition` for a request
3010    /// already answered.
3011    pub fn send_track_status_ok(
3012        &mut self,
3013        request_id: VarInt,
3014        expires: VarInt,
3015        group_order: GroupOrder,
3016        parameters: Vec<KeyValuePair>,
3017    ) -> Result<ControlMessage, EndpointError> {
3018        let id = request_id.into_inner();
3019        let req =
3020            self.inbound_track_statuses.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
3021        req.state.on_track_status_ok_sent()?;
3022        Ok(ControlMessage::TrackStatusOk(TrackStatusOk {
3023            request_id,
3024            track_alias: VarInt::from_u64(0).expect("0 fits in VarInt"),
3025            expires,
3026            group_order,
3027            content_exists: ContentExists::NoLargestLocation,
3028            largest_location: None,
3029            parameters,
3030        }))
3031    }
3032
3033    /// Build the TRACK_STATUS_ERROR refusing a track status the peer asked for.
3034    ///
3035    /// Section 8.22: "The publisher sends a TRACK_STATUS_ERROR control message
3036    /// in response to a failed TRACK_STATUS message."
3037    ///
3038    /// # Errors
3039    ///
3040    /// [`EndpointError::UnknownRequest`] if the peer has asked nothing under
3041    /// that identifier, and the flow's own `InvalidTransition` for a request
3042    /// already answered.
3043    pub fn send_track_status_error(
3044        &mut self,
3045        request_id: VarInt,
3046        error_code: VarInt,
3047        reason_phrase: Vec<u8>,
3048    ) -> Result<ControlMessage, EndpointError> {
3049        let id = request_id.into_inner();
3050        let req =
3051            self.inbound_track_statuses.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
3052        req.state.on_track_status_error_sent()?;
3053        Ok(ControlMessage::TrackStatusError(TrackStatusErrorMsg {
3054            request_id,
3055            error_code,
3056            reason_phrase,
3057        }))
3058    }
3059
3060    // ── Requests blocked ───────────────────────────────────────
3061
3062    /// Process an incoming REQUESTS_BLOCKED message.
3063    ///
3064    /// The peer explicitly reports that a new request id would exceed our
3065    /// advertised maximum. The endpoint records the peer's reported maximum;
3066    /// acting on it (issuing a new `MAX_REQUEST_ID`) is up to the caller.
3067    pub fn receive_requests_blocked(&mut self, msg: &RequestsBlocked) -> Result<(), EndpointError> {
3068        self.peer_reported_max_request_id = Some(msg.maximum_request_id);
3069        Ok(())
3070    }
3071
3072    /// The maximum request id that the peer most recently reported in a
3073    /// `REQUESTS_BLOCKED` message, if any.
3074    pub fn peer_reported_max_request_id(&self) -> Option<VarInt> {
3075        self.peer_reported_max_request_id
3076    }
3077
3078    // ── Publish flow (draft-13) ────────────────────────────────
3079
3080    /// Offer the peer a subscription to a track this endpoint publishes.
3081    /// Allocates a Request ID.
3082    ///
3083    /// Section 4.1: "A subscription can be initiated by either a publisher or
3084    /// a subscriber. A publisher initiates a subscription to a track by
3085    /// sending the PUBLISH message. The subscriber either accepts or rejects
3086    /// the subscription using PUBLISH_OK or PUBLISH_ERROR."
3087    ///
3088    /// There is no `content_exists` parameter because it is not a choice.
3089    /// Section 8.13 makes it a flag for whether the field after it is there at
3090    /// all - "1 if an object has been published on this track, 0 if not. If 0,
3091    /// then the Largest Group ID and Largest Object ID fields will not be
3092    /// present" - so it is derived from `largest_location`, and the pair
3093    /// cannot be built disagreeing.
3094    ///
3095    /// `forward` is the subscription's initial Forward State, which Section
3096    /// 4.1 gives to whichever end opened it: "The initiator of the
3097    /// subscription sets the initial Forward State in either PUBLISH or
3098    /// SUBSCRIBE." Section 8.13 says what the peer may then assume of it: "1
3099    /// indicates the publisher will start transmitting objects immediately,
3100    /// even before PUBLISH_OK."
3101    ///
3102    /// # Errors
3103    ///
3104    /// [`EndpointError::TrackAliasInUse`] when some live request of this
3105    /// session already holds `track_alias` for a different track, which
3106    /// Section 8.13 forbids outright - "The same Track Alias MUST NOT be used
3107    /// to refer to two different Tracks simultaneously" - and which the
3108    /// subscriber answers by closing the session. Judged before the Request ID
3109    /// is allocated, so a refused offer spends nothing. Also the session error
3110    /// when the session is not established, and the request-id error when this
3111    /// endpoint has no identifier left to spend.
3112    #[allow(clippy::too_many_arguments)]
3113    pub fn publish(
3114        &mut self,
3115        track_namespace: TrackNamespace,
3116        track_name: Vec<u8>,
3117        track_alias: VarInt,
3118        group_order: GroupOrder,
3119        largest_location: Option<Location>,
3120        forward: Forward,
3121        parameters: Vec<KeyValuePair>,
3122    ) -> Result<(VarInt, ControlMessage), EndpointError> {
3123        self.require_active_or_err()?;
3124        let alias = track_alias.into_inner();
3125        if let Some(refusal) = self.alias_held_elsewhere(alias, &track_namespace, &track_name) {
3126            return Err(refusal);
3127        }
3128        let req_id = self.request_ids.allocate()?;
3129        let id = req_id.into_inner();
3130        let mut state = PublishStateMachine::new();
3131        state.on_publish_sent()?;
3132        self.publishes.insert(id, state);
3133        // A PUBLISH names its track and its alias in the one message, so the
3134        // binding is complete the moment the offer is built, and the same
3135        // split applies as on the arriving side: it counts against the next
3136        // offer this endpoint builds from here on, because that sentence is
3137        // unqualified, and against an arriving PUBLISH or SUBSCRIBE_OK only
3138        // once the peer's PUBLISH_OK has made the subscription active.
3139        self.track_bindings.insert(
3140            id,
3141            TrackBinding {
3142                namespace: track_namespace.clone(),
3143                name: track_name.clone(),
3144                alias: Some(alias),
3145                kind: BindingKind::Publish,
3146            },
3147        );
3148        let content_exists = if largest_location.is_some() {
3149            ContentExists::HasLargestLocation
3150        } else {
3151            ContentExists::NoLargestLocation
3152        };
3153        let msg = ControlMessage::Publish(Publish {
3154            request_id: req_id,
3155            track_namespace,
3156            track_name,
3157            track_alias,
3158            group_order,
3159            content_exists,
3160            largest_location,
3161            forward,
3162            parameters,
3163        });
3164        Ok((req_id, msg))
3165    }
3166
3167    /// Process an incoming PUBLISH message, which opens a subscription this
3168    /// endpoint is the subscriber of.
3169    ///
3170    /// The message is kept so the application can answer it with
3171    /// [`Endpoint::send_publish_ok`] or [`Endpoint::send_publish_error`] under
3172    /// the same request id, and the record it is kept in outlives that answer:
3173    /// Section 4.1 gives the subscription that follows an UNSUBSCRIBE and a
3174    /// SUBSCRIBE_DONE, and both name this request.
3175    ///
3176    /// # Errors
3177    ///
3178    /// [`EndpointError::DuplicateTrackAlias`] when the Track Alias offered is
3179    /// one a different live track already holds, which ends the session, and
3180    /// the publish flow's own `InvalidTransition` for a second PUBLISH under a
3181    /// request id already carrying one.
3182    pub fn receive_publish(&mut self, msg: &Publish) -> Result<(), EndpointError> {
3183        self.require_active_or_err()?;
3184        // Section 8.13 answers a PUBLISH naming an alias another live track
3185        // already holds with a session close, in the same words Section 8.8
3186        // uses for a SUBSCRIBE_OK.
3187        let id = msg.request_id.into_inner();
3188        let alias = msg.track_alias.into_inner();
3189        if let Some(conflict) =
3190            self.conflicting_track_alias(id, alias, &msg.track_namespace, &msg.track_name)
3191        {
3192            return Err(self.fail_session(conflict));
3193        }
3194        let mut state = PublishStateMachine::new();
3195        state.on_publish_received()?;
3196        self.inbound_publishes
3197            .insert(id, InboundPublish { message: msg.clone(), state: Mutex::new(state) });
3198        // A PUBLISH names its track and its alias in the one message, so the
3199        // binding is complete on arrival. What it counts against from here
3200        // depends on which of the two comparisons is asking. An offer this
3201        // endpoint is about to build is refused now, because the prohibition
3202        // is not qualified; an arriving message closes the session only once
3203        // this endpoint has answered PUBLISH_OK, because the half that closes
3204        // is qualified by "a different track with an active subscription".
3205        self.track_bindings.insert(
3206            id,
3207            TrackBinding {
3208                namespace: msg.track_namespace.clone(),
3209                name: msg.track_name.clone(),
3210                alias: Some(alias),
3211                kind: BindingKind::Publish,
3212            },
3213        );
3214        Ok(())
3215    }
3216
3217    /// The inbound PUBLISH for a request id that is still waiting to be
3218    /// answered.
3219    ///
3220    /// Stops answering once the PUBLISH has been answered, which is the whole
3221    /// of what "pending" means: the record itself lives on for as long as the
3222    /// subscription does.
3223    pub fn pending_publish(&self, request_id: VarInt) -> Option<&Publish> {
3224        self.inbound_publishes
3225            .get(&request_id.into_inner())
3226            .filter(|p| p.publish_state() == PublishState::Publishing)
3227            .map(|p| &p.message)
3228    }
3229
3230    /// Number of PUBLISH requests received but not yet responded to.
3231    pub fn pending_publish_count(&self) -> usize {
3232        self.inbound_publishes
3233            .values()
3234            .filter(|p| p.publish_state() == PublishState::Publishing)
3235            .count()
3236    }
3237
3238    /// Generate a PUBLISH_OK response for a previously received PUBLISH,
3239    /// which establishes the subscription it opened.
3240    ///
3241    /// # Errors
3242    ///
3243    /// [`EndpointError::UnknownRequest`] when no PUBLISH arrived under that
3244    /// id, and the publish flow's own `InvalidTransition` for a second answer:
3245    /// Section 4.1 says "A subscriber MUST send exactly one PUBLISH_OK or
3246    /// PUBLISH_ERROR in response to a PUBLISH."
3247    #[allow(clippy::too_many_arguments)]
3248    pub fn send_publish_ok(
3249        &mut self,
3250        request_id: VarInt,
3251        forward: Forward,
3252        subscriber_priority: u8,
3253        group_order: GroupOrder,
3254        filter_type: FilterType,
3255        start_group: Option<VarInt>,
3256        start_object: Option<VarInt>,
3257        end_group: Option<VarInt>,
3258    ) -> Result<ControlMessage, EndpointError> {
3259        let id = request_id.into_inner();
3260        let entry = self.inbound_publishes.get(&id).ok_or(EndpointError::UnknownRequest(id))?;
3261        entry.flow().on_publish_ok_sent()?;
3262        Ok(ControlMessage::PublishOk(PublishOk {
3263            request_id,
3264            forward,
3265            subscriber_priority,
3266            group_order,
3267            filter_type,
3268            start_group,
3269            start_object,
3270            end_group,
3271            parameters: vec![],
3272        }))
3273    }
3274
3275    /// Generate a PUBLISH_ERROR response for a previously received PUBLISH,
3276    /// which ends the subscription it opened before it was established.
3277    ///
3278    /// # Errors
3279    ///
3280    /// [`EndpointError::UnknownRequest`] when no PUBLISH arrived under that
3281    /// id, and the publish flow's own `InvalidTransition` for a second answer:
3282    /// Section 4.1 says "A subscriber MUST send exactly one PUBLISH_OK or
3283    /// PUBLISH_ERROR in response to a PUBLISH."
3284    pub fn send_publish_error(
3285        &mut self,
3286        request_id: VarInt,
3287        error_code: VarInt,
3288        reason_phrase: Vec<u8>,
3289    ) -> Result<ControlMessage, EndpointError> {
3290        let id = request_id.into_inner();
3291        let entry = self.inbound_publishes.get(&id).ok_or(EndpointError::UnknownRequest(id))?;
3292        entry.flow().on_publish_error_sent()?;
3293        Ok(ControlMessage::PublishError(PublishError { request_id, error_code, reason_phrase }))
3294    }
3295
3296    /// Process an incoming PUBLISH_OK, which establishes the subscription this
3297    /// endpoint offered under that Request ID.
3298    ///
3299    /// # Errors
3300    ///
3301    /// [`EndpointError::UnknownRequest`] when this endpoint has offered
3302    /// nothing under that identifier, and the publish flow's own
3303    /// `InvalidTransition` for an offer that has been answered already:
3304    /// Section 4.1 says "A subscriber MUST send exactly one PUBLISH_OK or
3305    /// PUBLISH_ERROR in response to a PUBLISH. The peer SHOULD close the
3306    /// session with a protocol error if it receives more than one." The verb
3307    /// there is SHOULD, so the second answer is reported rather than acted on,
3308    /// and the caller decides.
3309    pub fn receive_publish_ok(&mut self, msg: &PublishOk) -> Result<(), EndpointError> {
3310        let id = msg.request_id.into_inner();
3311        let state = self.publishes.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
3312        state.on_publish_ok()?;
3313        Ok(())
3314    }
3315
3316    /// Process an incoming PUBLISH_ERROR, which ends the subscription this
3317    /// endpoint offered under that Request ID before it was established.
3318    ///
3319    /// Section 4.1: "Objects MUST NOT be sent for requests that end with an
3320    /// error." The Track Alias the offer named is free again from here,
3321    /// because a binding reads liveness off this record rather than keeping a
3322    /// second copy of it.
3323    ///
3324    /// # Errors
3325    ///
3326    /// As [`Self::receive_publish_ok`].
3327    pub fn receive_publish_error(&mut self, msg: &PublishError) -> Result<(), EndpointError> {
3328        let id = msg.request_id.into_inner();
3329        let state = self.publishes.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
3330        state.on_publish_error()?;
3331        Ok(())
3332    }
3333
3334    /// Hold a Request ID a peer allocated to the rules Section 8.1 states
3335    /// about it.
3336    ///
3337    /// "The client's Request ID starts at 0 and are even and the server's
3338    /// Request ID starts at 1 and are odd. The Request ID increments by 2 ...
3339    /// If an endpoint receives a Request ID that is not valid for the peer, it
3340    /// MUST close the session with Invalid Request ID." Parity says which end
3341    /// may have chosen it; the ceiling this endpoint advertised says how far
3342    /// the peer may go.
3343    ///
3344    /// This is the call site [`Self::validate_peer_request_id`] did not have.
3345    /// The rule was implemented and then applied to nothing, so a peer could
3346    /// open requests with ids from this endpoint's own half of the space, or
3347    /// past the ceiling it had advertised, and neither was noticed.
3348    ///
3349    /// A message that is a response rather than a request carries the id of a
3350    /// request this endpoint made, so it is not checked here - it is checked by
3351    /// finding the state machine it names. SUBSCRIBE_UPDATE is the same case on
3352    /// this draft: its one Request ID field names the subscription it modifies
3353    /// rather than opening a request of its own, which is why Section 8.1 does
3354    /// not list it among the messages that step the sequence.
3355    ///
3356    /// # The list below is the rule, not a convenience
3357    ///
3358    /// Every message named here spends one of the peer's Request IDs, and
3359    /// nothing else does. That makes the list load-bearing in a way it was not
3360    /// before the sequence was tracked: a request left out of it spends an ID
3361    /// this endpoint never counts, so the peer's **next** request looks like a
3362    /// skip and a conforming session is closed over it. PUBLISH is here and
3363    /// not in Section 8.1's enumeration, which predates the message; its own
3364    /// Request ID field refers the reader to that section like every other
3365    /// request's does.
3366    ///
3367    /// # Errors
3368    ///
3369    /// The request-id errors, for a wrong parity, an id at or above the
3370    /// advertised ceiling, or one that is not the next in the peer's sequence.
3371    pub fn receive_request(&mut self, msg: &ControlMessage) -> Result<(), EndpointError> {
3372        let request_id = match msg {
3373            ControlMessage::Subscribe(m) => m.request_id,
3374            ControlMessage::Fetch(m) => m.request_id,
3375            ControlMessage::Announce(m) => m.request_id,
3376            ControlMessage::SubscribeNamespace(m) => m.request_id,
3377            ControlMessage::TrackStatus(m) => m.request_id,
3378            ControlMessage::Publish(m) => m.request_id,
3379            _ => return Ok(()),
3380        };
3381        self.validate_peer_request_id(request_id.into_inner())
3382    }
3383
3384    // ── Unified message dispatch ───────────────────────────────
3385
3386    /// Dispatch an incoming control message to the appropriate handler.
3387    pub fn receive_message(&mut self, msg: ControlMessage) -> Result<(), EndpointError> {
3388        self.receive_request(&msg)?;
3389        match msg {
3390            ControlMessage::GoAway(ref m) => self.receive_goaway(m),
3391            ControlMessage::MaxRequestId(ref m) => self.receive_max_request_id(m),
3392            ControlMessage::RequestsBlocked(ref m) => self.receive_requests_blocked(m),
3393            ControlMessage::SubscribeOk(ref m) => self.receive_subscribe_ok(m),
3394            ControlMessage::SubscribeError(ref m) => self.receive_subscribe_error(m),
3395            ControlMessage::SubscribeUpdate(ref m) => self.receive_subscribe_update(m),
3396            ControlMessage::SubscribeDone(ref m) => self.receive_subscribe_done(m),
3397            ControlMessage::FetchOk(ref m) => self.receive_fetch_ok(m),
3398            ControlMessage::FetchError(ref m) => self.receive_fetch_error(m),
3399            ControlMessage::SubscribeNamespaceOk(ref m) => self.receive_subscribe_namespace_ok(m),
3400            ControlMessage::SubscribeNamespaceError(ref m) => {
3401                self.receive_subscribe_namespace_error(m)
3402            }
3403            ControlMessage::AnnounceOk(ref m) => self.receive_announce_ok(m),
3404            ControlMessage::AnnounceError(ref m) => self.receive_announce_error(m),
3405            ControlMessage::AnnounceCancel(ref m) => self.receive_announce_cancel(m),
3406            ControlMessage::TrackStatusOk(ref m) => self.receive_track_status_ok(m),
3407            ControlMessage::TrackStatusError(ref m) => self.receive_track_status_error(m),
3408            ControlMessage::TrackStatus(ref m) => self.receive_track_status(m),
3409            ControlMessage::Subscribe(ref m) => self.receive_subscribe(m),
3410            ControlMessage::Unsubscribe(ref m) => self.receive_unsubscribe(m),
3411            ControlMessage::Fetch(ref m) => self.receive_fetch(m),
3412            ControlMessage::FetchCancel(ref m) => self.receive_fetch_cancel(m),
3413            ControlMessage::Publish(ref m) => self.receive_publish(m),
3414            ControlMessage::PublishOk(ref m) => self.receive_publish_ok(m),
3415            ControlMessage::PublishError(ref m) => self.receive_publish_error(m),
3416            ControlMessage::Announce(ref m) => self.receive_announce(m),
3417            ControlMessage::Unannounce(ref m) => self.receive_unannounce(m),
3418            ControlMessage::SubscribeNamespace(ref m) => self.receive_subscribe_namespace(m),
3419            ControlMessage::UnsubscribeNamespace(ref m) => self.receive_unsubscribe_namespace(m),
3420            _ => Ok(()),
3421        }
3422    }
3423}