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