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