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