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