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