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