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