Skip to main content

moqtap_client/draft14/
endpoint.rs

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