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