Skip to main content

moqtap_client/draft15/
endpoint.rs

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