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