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