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