moqtap_client/draft19/endpoint.rs
1#![allow(missing_docs)]
2//! Draft-19 MoQT endpoint.
3//!
4//! Major changes from draft-18:
5//!
6//! * `GoAway` drops the `request_id` field entirely; the control-stream and
7//! request-stream forms are now identical on the wire.
8//! * `PublishBlocked` renamed to `PublishSkipped` (still type 0x0F, wire
9//! identical).
10//! * `GROUP_ORDER` (parameter 0x22) moves from PUBLISH_OK to SUBSCRIBE_TRACKS.
11//! * New Range Filter parameters (SUBGROUP_FILTER 0x25, OBJECTID_FILTER 0x26,
12//! PRIORITY_FILTER 0x27, OBJECT_PROPERTY_FILTER 0x28, TRACK_PROPERTY_FILTER
13//! 0x29) may appear on SUBSCRIBE / FETCH / SUBSCRIBE_TRACKS / REQUEST_UPDATE.
14//! * `RequestError` adds CONFLICTING_FILTERS (0x35) and INVALID_FILTER (0x36);
15//! DUPLICATE_SUBSCRIPTION (0x19) is removed.
16
17use std::collections::{HashMap, HashSet};
18use std::sync::{Arc, Mutex};
19
20use crate::draft19::fetch::{FetchError, FetchState, FetchStateMachine};
21use crate::draft19::namespace::{
22 NamespaceError, PublishNamespaceState, PublishNamespaceStateMachine, SubscribeNamespaceState,
23 SubscribeNamespaceStateMachine,
24};
25use crate::draft19::publish::{
26 PublishError as PublishFlowError, PublishState, PublishStateMachine,
27};
28use crate::draft19::session::request_id::{RequestIdAllocator, RequestIdError, Role};
29use crate::draft19::session::setup::{self, SetupError};
30
31use moqtap_codec::kvp::KvpValue;
32use moqtap_codec::range_filter::{self, RangeFilterError};
33use moqtap_codec::varint::Moqt18;
34
35/// The MAX_REQUEST_UPDATES Setup Option, Section 10.3.1.7.
36const MAX_REQUEST_UPDATES: u64 = 0x08;
37
38/// The MAX_FILTER_RANGES Setup Option, Section 10.3.1.6.
39const MAX_FILTER_RANGES: u64 = 0x06;
40
41/// The Message Parameters a request carries, or nothing for a message that is
42/// not a request.
43///
44/// Every one of the seven request kinds carries a parameter list, so the empty
45/// arm is only reached by a caller that is not looking at a request. Section
46/// 5.1.3 names five message types a Range Filter may appear in, and this does
47/// not narrow to them: a filter parameter arriving where the section does not
48/// list it is a scope question, and scope is answered by the parameter registry
49/// rather than here. What matters for the ceiling is that a filter this endpoint
50/// has no budget for is not accepted, wherever it turned up.
51fn request_parameters(msg: &ControlMessage) -> &[KeyValuePair] {
52 match msg {
53 ControlMessage::Subscribe(m) => &m.parameters,
54 ControlMessage::Publish(m) => &m.parameters,
55 ControlMessage::Fetch(m) => &m.parameters,
56 ControlMessage::PublishNamespace(m) => &m.parameters,
57 ControlMessage::SubscribeNamespace(m) => &m.parameters,
58 ControlMessage::SubscribeTracks(m) => &m.parameters,
59 ControlMessage::TrackStatus(m) => &m.parameters,
60 _ => &[],
61 }
62}
63
64/// The Range Filter parameters in `parameters`, dropping the removals.
65///
66/// A Range Filter whose value is empty is Section 5.1.3's removal form — "Length
67/// can be 0 to remove a filter parameter" — so it names a filter rather than
68/// being one, and carrying it forward would leave a filter of no ranges in force
69/// for the life of the request.
70fn range_filter_parameters(parameters: &[KeyValuePair]) -> Vec<KeyValuePair> {
71 parameters
72 .iter()
73 .filter(|p| range_filter::is_range_filter(p.key.into_inner()))
74 .filter(|p| !matches!(&p.value, KvpValue::Bytes(bytes) if bytes.is_empty()))
75 .cloned()
76 .collect()
77}
78
79/// Apply a REQUEST_UPDATE's filter parameters to the set already in force.
80///
81/// Replacement is by Parameter Type and takes the whole type with it: "non-zero
82/// to replace that entire filter parameter including all sets and Property
83/// Types". A type the update does not mention is left alone, which is the other
84/// half of the same sentence — "If a filter parameter is omitted from
85/// REQUEST_UPDATE, the value is unchanged" — and is why this merges rather than
86/// measuring the update on its own.
87fn apply_filter_update(in_force: &mut Vec<KeyValuePair>, update: &[KeyValuePair]) {
88 let mentioned: Vec<u64> = update
89 .iter()
90 .map(|p| p.key.into_inner())
91 .filter(|key| range_filter::is_range_filter(*key))
92 .collect();
93 in_force.retain(|p| !mentioned.contains(&p.key.into_inner()));
94 in_force.extend(range_filter_parameters(update));
95}
96
97/// Read a Setup Option's value as a variable-length integer, or 0 if it is
98/// absent.
99///
100/// Zero is also the default every numeric option in Section 10.3.1 takes when
101/// it is not sent, so an absent option and an explicit zero mean the same thing
102/// and do not need to be told apart.
103fn setup_varint(options: &[KeyValuePair], key: u64) -> u64 {
104 let key = VarInt::from_u64(key).expect("option key fits a varint");
105 options
106 .iter()
107 .find(|o| o.key == key)
108 .and_then(|o| match &o.value {
109 KvpValue::Varint(v) => Some(v.into_inner()),
110 KvpValue::Bytes(bytes) => {
111 let mut cursor = &bytes[..];
112 let parsed = VarInt::decode_moqt::<Moqt18>(&mut cursor).ok()?;
113 cursor.is_empty().then(|| parsed.into_inner())
114 }
115 })
116 .unwrap_or(0)
117}
118use crate::draft19::session::state::{SessionError, SessionState, SessionStateMachine};
119use crate::draft19::subscription::{
120 SubscriptionError, SubscriptionState, SubscriptionStateMachine,
121};
122use crate::draft19::track_status::{TrackStatusError, TrackStatusState, TrackStatusStateMachine};
123use crate::malformed_tracks::{MalformedTrackCondition, MalformedTracks};
124use crate::track_locations::{ObjectLocation, ObjectRole, TrackLocations, TrackObjects};
125use moqtap_codec::draft19::error_codes::{
126 PublishDoneStatusCode, RequestErrorCode, SessionErrorCode,
127};
128use moqtap_codec::draft19::message::{
129 self, ControlMessage, Fetch, FetchPayload, FetchType, GoAway, MessageType, Publish,
130 PublishDone, PublishNamespace, PublishSkipped, RequestError, RequestOk, RequestUpdate, Setup,
131 Subscribe, SubscribeNamespace, SubscribeOk, SubscribeTracks,
132};
133use moqtap_codec::kvp::KeyValuePair;
134use moqtap_codec::types::*;
135use moqtap_codec::varint::VarInt;
136
137/// Why a peer's request must be answered with REQUEST_ERROR and INVALID_FILTER.
138///
139/// Draft-19 gives the Range Filters four rules and answers every one of them
140/// the same way — "MUST be rejected with REQUEST_ERROR with error code
141/// INVALID_FILTER" — so this says which rule was broken rather than which code
142/// to send. [`FilterRejection::request_error_code`] is the code, and it is the
143/// same for all four.
144///
145/// None of these is a session close, which is the whole reason the type exists.
146/// A rejection is a reply, and a reply names the Request ID of the request it
147/// answers, so the endpoint has to have taken the request in order to refuse it.
148/// The rejection is recorded against that id and spent by the REQUEST_ERROR that
149/// answers it.
150#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
151pub enum FilterRejection {
152 /// The peer sent a Range Filter and this endpoint advertised no budget.
153 ///
154 /// Section 10.3.1.6: "The default value is 0, so if not specified, the peer
155 /// MUST NOT send any such filter parameters." Zero is the default and it
156 /// means none allowed — the opposite of what zero means in the option
157 /// beside it, MAX_REQUEST_UPDATES, where it means no limit. The two sit in
158 /// consecutive subsections and read opposite ways.
159 #[error("this endpoint advertised no MAX_FILTER_RANGES, so no range filter is allowed")]
160 NoBudgetAdvertised,
161 /// More Ranges than MAX_FILTER_RANGES allows, counted across every filter.
162 #[error("{ranges} ranges across the request's filters, where {limit} were advertised")]
163 TooManyRanges {
164 /// The total the request carried.
165 ranges: usize,
166 /// The total this endpoint advertised.
167 limit: u64,
168 },
169 /// Two filters with the same Parameter Type, SetID and Property Type.
170 ///
171 /// Repeats of a type are otherwise expected, so the key is the whole triple:
172 /// "The Track Property filter parameter MAY appear multiple times in a
173 /// SUBSCRIBE_TRACKS message"
174 #[error("two filters share parameter type {0:#x}, set {1} and property type {2:?}")]
175 RepeatedFilter(u64, u8, Option<u64>),
176 /// A filter this codec could not read, or one that broke a rule of its own.
177 #[error("a range filter is not usable: {0}")]
178 Unreadable(#[from] RangeFilterError),
179}
180
181impl FilterRejection {
182 /// The REQUEST_ERROR code every one of these is answered with.
183 pub fn request_error_code(&self) -> RequestErrorCode {
184 RequestErrorCode::InvalidFilter
185 }
186}
187
188/// Errors that can occur during endpoint operations.
189#[derive(Debug, thiserror::Error)]
190pub enum EndpointError {
191 #[error("session error: {0}")]
192 Session(#[from] SessionError),
193 #[error("request ID error: {0}")]
194 RequestId(#[from] RequestIdError),
195 #[error("subscription error: {0}")]
196 Subscription(#[from] SubscriptionError),
197 #[error("fetch error: {0}")]
198 Fetch(#[from] FetchError),
199 #[error("namespace error: {0}")]
200 Namespace(#[from] NamespaceError),
201 #[error("track status error: {0}")]
202 TrackStatus(#[from] TrackStatusError),
203 #[error("publish flow error: {0}")]
204 PublishFlow(#[from] PublishFlowError),
205 #[error("setup error: {0}")]
206 Setup(#[from] SetupError),
207 #[error("unknown request ID: {0}")]
208 UnknownRequest(u64),
209 #[error(
210 "response message received on control stream; draft-19 responses belong on bidi request streams"
211 )]
212 ResponseOnControlStream,
213 /// A REQUEST_UPDATE arrived on the control stream.
214 ///
215 /// Draft-19 Table 5 gives REQUEST_UPDATE the Stream value "Request", and
216 /// Section 10.9 requires it on the same bidi stream as the request it
217 /// modifies. One on the control stream modifies nothing, which makes it a
218 /// case Section 10.9 says MUST close the session.
219 #[error(
220 "REQUEST_UPDATE received on the control stream; it belongs on its request's own stream"
221 )]
222 RequestUpdateOnControlStream,
223 /// A NAMESPACE, NAMESPACE_DONE or PUBLISH_SKIPPED arrived on the control
224 /// stream.
225 ///
226 /// Draft-19 Table 5 gives all three the Stream value "Request": NAMESPACE
227 /// (0x8, Section 10.16) and NAMESPACE_DONE (0xE, Section 10.17) belong on
228 /// the SUBSCRIBE_NAMESPACE request stream whose namespace they report, and
229 /// PUBLISH_SKIPPED (0xF, Section 10.20) on the SUBSCRIBE_TRACKS stream
230 /// whose namespace it names a skipped track in — "All PUBLISH_SKIPPED
231 /// messages are in response to a SUBSCRIBE_TRACKS". Only SETUP is
232 /// "Control" alone; GOAWAY is the one message the
233 /// table lists as "Control, Request". One of these three on the control
234 /// stream names no request, so nothing can be done with it.
235 #[error("{0} received on the control stream; draft-19 Table 5 places it on a request stream")]
236 RequestMessageOnControlStream(&'static str),
237 /// A REQUEST_UPDATE named a request that cannot be updated, or none.
238 ///
239 /// Draft-19 Section 10.9: "An endpoint that receives a REQUEST_UPDATE
240 /// other than in the two cases above MUST close the session with a
241 /// PROTOCOL_VIOLATION." TRACK_STATUS is called out in Section 10.14 as one
242 /// such case: "the subscriber cannot send REQUEST_UPDATE."
243 #[error("REQUEST_UPDATE for request {0}, which is not an updatable outstanding request")]
244 UnexpectedRequestUpdate(u64),
245 /// Track Properties on a REQUEST_OK answering something other than a
246 /// TRACK_STATUS.
247 ///
248 /// Draft-19 Section 10.5: they "are empty in PUBLISH_OK,
249 /// REQUEST_UPDATE_OK, SUBSCRIBE_NAMESPACE_OK and PUBLISH_NAMESPACE_OK. If
250 /// an endpoint receives Track Properties in one of these messages it MUST
251 /// close the session with a PROTOCOL_VIOLATION."
252 #[error(
253 "track properties on the REQUEST_OK answering request {0}, which is not a TRACK_STATUS"
254 )]
255 TrackPropertiesOnNonTrackStatus(u64),
256 /// A server received a Redirect naming a Connect URI.
257 ///
258 /// Draft-19 Section 10.6.1: "If a server receives a Redirect with a
259 /// non-zero Connect URI Length it MUST close the session with a
260 /// PROTOCOL_VIOLATION." As with GOAWAY, only a client is redirected.
261 #[error("Redirect carrying a Connect URI received at a server")]
262 RedirectUriAtServer,
263 /// A Redirect answering a namespace-scoped request carried a Track Name.
264 ///
265 /// Draft-19 Section 10.6.1: "Track Name is not meaningful for
266 /// namespace-scoped requests (SUBSCRIBE_NAMESPACE, PUBLISH_NAMESPACE,
267 /// SUBSCRIBE_TRACKS) and MUST be empty; an endpoint that receives a
268 /// non-empty Track Name in a Redirect for a namespace-scoped request MUST
269 /// close the session with a PROTOCOL_VIOLATION." Draft-18 names the same
270 /// rule with SUBSCRIBE_TRACKS left out of the list.
271 #[error("Redirect for namespace-scoped request {0} carries a track name")]
272 RedirectTrackNameOnNamespaceRequest(u64),
273 /// A server received a GOAWAY carrying a New Session URI.
274 ///
275 /// Draft-19 Section 10.4: "If a server receives a GOAWAY with a non-zero
276 /// New Session URI Length it MUST close the session with a
277 /// PROTOCOL_VIOLATION." Only a client can be redirected.
278 #[error("GOAWAY carrying a New Session URI received at a server")]
279 GoAwayUriAtServer,
280 /// The peer reused a Request ID it had already spent.
281 ///
282 /// Draft-19 Section 10.1: "If an endpoint receives a Request ID where the
283 /// least significant bit is incorrect for the sender, or a duplicate
284 /// Request ID, it MUST close the session with INVALID_REQUEST_ID."
285 #[error("request {0} was already used by the peer")]
286 DuplicateRequestId(u64),
287 /// A bidirectional stream the peer opened began with a message that does
288 /// not open a request stream.
289 ///
290 /// Draft-19 Section 3.3: "Bidirectional streams MUST NOT begin with any
291 /// other message type unless negotiated. If they do, the peer MUST close
292 /// the Session with a PROTOCOL_VIOLATION."
293 #[error("{0:?} does not begin a request stream")]
294 NotARequest(MessageType),
295 /// A message that this endpoint may not write on a request stream the peer
296 /// opened was handed to the responder path. Nothing was written and no
297 /// state moved.
298 #[error("{0:?} is not a message a responder writes on a peer's request stream")]
299 NotAResponse(MessageType),
300 /// A message arrived on a request stream the peer opened that may not
301 /// follow a request there.
302 ///
303 /// This endpoint is the responder on such a stream, so a response arriving
304 /// on it is the peer answering its own request.
305 #[error("{0:?} may not follow a request on a stream the peer opened")]
306 UnexpectedOnPeerRequestStream(MessageType),
307 /// A namespace-scoped request's response half opened with something other
308 /// than REQUEST_OK or REQUEST_ERROR.
309 ///
310 /// Draft-19 Section 10.18 and draft-19 Section 10.19, of
311 /// SUBSCRIBE_NAMESPACE and SUBSCRIBE_TRACKS alike: "The publisher will
312 /// respond with REQUEST_OK or REQUEST_ERROR on the response half of the
313 /// stream. If the subscriber receives any message other than a REQUEST_OK
314 /// or a REQUEST_ERROR as the first message on the response half of the
315 /// stream, then it MUST close the session with a PROTOCOL_VIOLATION."
316 #[error("request {0} answered with {1:?} before its REQUEST_OK or REQUEST_ERROR")]
317 ResponseBeforeTheFirstResponse(u64, MessageType),
318 /// Track Properties were put on a REQUEST_OK answering something other
319 /// than a TRACK_STATUS, on the way out. Nothing was written.
320 ///
321 /// The send-side mirror of
322 /// [`TrackPropertiesOnNonTrackStatus`](Self::TrackPropertiesOnNonTrackStatus):
323 /// Section 10.5 answers receiving them with a session close, so writing
324 /// them would hand a conforming peer a reason to close this session. This
325 /// one is not fatal — nothing reached the wire, so there is nothing for the
326 /// peer to object to.
327 #[error("request {0} is not a TRACK_STATUS; only its response carries track properties")]
328 TrackPropertiesOnOutgoingRequestOk(u64),
329 /// A REQUEST_UPDATE arrived on a stream that had already used up the
330 /// concurrency this endpoint advertised.
331 ///
332 /// Draft-19 Section 10.3.1.7: "If an endpoint receives a REQUEST_UPDATE on
333 /// a stream that already has MAX_REQUEST_UPDATES outstanding
334 /// REQUEST_UPDATEs, it MUST close the session with
335 /// TOO_MANY_REQUEST_UPDATES."
336 #[error("request {0} already has the {1} outstanding REQUEST_UPDATEs it was allowed")]
337 TooManyRequestUpdates(u64, u64),
338 /// A REQUEST_OK was offered for a request whose Range Filters this endpoint
339 /// is required to reject.
340 ///
341 /// Not fatal, and deliberately not raised where the filter arrives. Every
342 /// Range Filter rule in Section 5.1.3 is answered with a REQUEST_ERROR, and
343 /// a REQUEST_ERROR names the Request ID of the request it answers — so the
344 /// request has to be taken before it can be refused. What this stops is the
345 /// other answer: accepting the request the draft says to reject leaves the
346 /// subscriber with a subscription whose filters this endpoint never agreed
347 /// to apply, and a publisher that then forwards by its own reading of them.
348 #[error("request {0} must be answered with REQUEST_ERROR: {1}")]
349 FilterMustBeRejected(u64, FilterRejection),
350 /// A REQUEST_OK or REQUEST_ERROR was offered as the answer to a
351 /// REQUEST_UPDATE on a stream with no update waiting for one.
352 ///
353 /// Section 10.9 requires "exactly one REQUEST_OK or REQUEST_ERROR message
354 /// indicating if the update was successful", so an answer with nothing to
355 /// answer is one the peer will read as belonging to an update it never
356 /// sent. Not fatal: nothing was written.
357 ///
358 /// A SUBSCRIBE is answered with SUBSCRIBE_OK and a FETCH with FETCH_OK, so
359 /// on those two streams a REQUEST_OK can be nothing but an update's answer
360 /// and this is what a mistimed one produces. On the five kinds REQUEST_OK
361 /// answers itself, the first one is the request's and only the ones after
362 /// it can reach here.
363 #[error("request {0} has no REQUEST_UPDATE waiting for an answer")]
364 NoUpdateToAnswer(u64),
365 /// An update was refused and the subscription it belongs to was then ended
366 /// under some status other than the one that names why.
367 ///
368 /// Section 10.9.1: "When a REQUEST_UPDATE is unsuccessful, the publisher MUST
369 /// also terminate the subscription by sending a PUBLISH_DONE with error
370 /// code UPDATE_FAILED." The REQUEST_ERROR is half of what that sentence
371 /// asks for and the termination is the other half, so this endpoint holds
372 /// the request to it: whatever else the caller writes first, the
373 /// termination it does write says so.
374 #[error("request {request}'s update was refused, so its PUBLISH_DONE must carry {required}")]
375 WrongUpdateFailureStatus {
376 /// The request whose update was refused.
377 request: u64,
378 /// The status code the termination must carry.
379 required: u64,
380 },
381 #[error("session not active")]
382 NotActive,
383 #[error("session is draining, no new requests allowed")]
384 Draining,
385 /// A second GOAWAY arrived on the control stream.
386 ///
387 /// The GOAWAY that says the peer is going away is one message, and the
388 /// draft answers a repeat of it with a session close rather than with an
389 /// error about the second message: there is no state a second one could
390 /// move that the first has not already moved.
391 #[error("a second GOAWAY arrived on the control stream")]
392 RepeatedGoAway,
393 /// A second GOAWAY arrived on one request's stream.
394 ///
395 /// The count is per stream rather than per session: this draft lets a
396 /// GOAWAY migrate a single request, so one on each of two request streams
397 /// is two first GOAWAYs and not a repeat.
398 #[error("a second GOAWAY arrived on request {0}'s stream")]
399 RepeatedGoAwayOnRequestStream(
400 /// The Request ID of the stream that carried both.
401 u64,
402 ),
403 /// The peer named a Track Alias it is already using for another track.
404 ///
405 /// Draft-19 Section 11.1: "The same Track Alias MUST NOT be used by a
406 /// publisher to refer to two different Tracks simultaneously in the same
407 /// session. If a subscriber receives a PUBLISH or SUBSCRIBE_OK that uses
408 /// the same Track Alias as a different Track with an Established
409 /// subscription, it MUST close the session with error
410 /// DUPLICATE_TRACK_ALIAS."
411 ///
412 /// The session is over: this endpoint's own state has moved to Closed and
413 /// the code the transport should close with is in
414 /// [`EndpointError::session_error_code`].
415 #[error("track alias {alias} already names request {established}'s track; request {offered} names a different one")]
416 DuplicateTrackAlias {
417 /// The alias both tracks are named by.
418 alias: u64,
419 /// The request whose Established subscription holds the alias.
420 established: u64,
421 /// The request whose message arrived naming it for another track.
422 offered: u64,
423 },
424 /// This endpoint was asked to give a Track Alias to a second track.
425 ///
426 /// The same rule as [`EndpointError::DuplicateTrackAlias`] read at the end
427 /// that chooses the alias. Section 11.1 states it as a prohibition on the
428 /// publisher before it states what the subscriber does about one: "The
429 /// same Track Alias MUST NOT be used by a publisher to refer to two different Tracks
430 /// simultaneously in the same session."
431 ///
432 /// The message is refused instead of built, and nothing else moves: no
433 /// Request ID is spent, no publish flow is created, and the session stays
434 /// as it was. The alias never reaches the peer, so there is nothing for
435 /// the peer to close over.
436 #[error("track alias {alias} already names request {held}'s track")]
437 TrackAliasInUse {
438 /// The alias that is already spoken for.
439 alias: u64,
440 /// The request whose live flow holds it.
441 held: u64,
442 },
443 /// An object arriving after the track's final object.
444 ///
445 /// Section 2.4.2 lists the condition: "An Object is received whose
446 /// Group and Object ID are larger
447 /// than the final Object in the Track.
448 /// The final Object in a Track is the Object with Status END_OF_TRACK or
449 /// the last Object sent in a FETCH whose response indicated End of Track."
450 ///
451 /// **Larger is Section 1.4.2's comparison and not a reading of the
452 /// words.** That section puts one Location below another when "A.Group <
453 /// B.Group || (A.Group == B.Group && A.Object < B.Object)", so an Object in
454 /// a later group is past the end whatever its own Object ID is.
455 ///
456 /// **A Malformed Track and not a session error**, and on this draft not a
457 /// message either. Section 2.4.2 answers its whole list at once with "it
458 /// MUST cancel any corresponding subscription or fetches for that Track
459 /// from that publisher", where cancelling a request is the transport
460 /// operation Section 3.3.3 describes. This is the error half; the
461 /// requests to cancel are named by
462 /// [`Endpoint::requests_for_malformed_track`].
463 #[error(
464 "the object at group {group}, object {object} on track alias {alias} arrived \
465 after the track's final object at group {final_group}, object {final_object}"
466 )]
467 ObjectPastFinalObject {
468 /// The Track Alias the offending object named.
469 alias: u64,
470 /// The Group ID it named.
471 group: u64,
472 /// The Object ID it named.
473 object: u64,
474 /// The Group ID of the object the track ended at.
475 final_group: u64,
476 /// The Object ID of the object the track ended at.
477 final_object: u64,
478 },
479
480 /// A Joining Fetch named a subscription this session cannot join.
481 ///
482 /// Section 10.12.2:
483 /// "If a publisher receives a Joining Fetch with a Request ID that
484 /// does not correspond to a subscription in the same session in the
485 /// Established or Pending (subscriber) states, it MUST return a
486 /// REQUEST_ERROR with error code INVALID_JOINING_REQUEST_ID."
487 ///
488 /// A refusal and not a session close, so the session runs on and the error
489 /// names both identifiers: the fetch to refuse, and the subscription it
490 /// asked to join.
491 #[error("FETCH {fetch} joins request {joining}, which is no live subscription of the peer's")]
492 UnjoinableSubscription {
493 /// The fetch that named it.
494 fetch: u64,
495 /// The identifier it named.
496 joining: u64,
497 },
498
499 /// A Joining Fetch was refused under a code other than the one the same
500 /// sentence names for it.
501 ///
502 /// The reason travels with the refusal, so a subscriber told the wrong one
503 /// retries the wrong thing: it can rebuild a fetch whose range was refused,
504 /// and cannot rebuild one whose subscription is gone.
505 #[error("refusing FETCH {fetch} for the subscription it joins takes error code {required}")]
506 WrongJoiningRefusal {
507 /// The fetch being refused.
508 fetch: u64,
509 /// The code the draft names for that refusal.
510 required: u64,
511 },
512 /// The peer subscribed to a namespace prefix overlapping one it is
513 /// already subscribed to.
514 ///
515 /// Section 10.18: "Within a session, if a publisher receives a
516 /// SUBSCRIBE_NAMESPACE with a Track Namespace Prefix that shares a common
517 /// prefix with an established SUBSCRIBE_NAMESPACE, it MUST respond with
518 /// REQUEST_ERROR with error code PREFIX_OVERLAP."
519 ///
520 /// Section 10.19: "Within a session, if a publisher receives a
521 /// SUBSCRIBE_TRACKS with a Track Namespace Prefix that shares a common
522 /// prefix with an established SUBSCRIBE_TRACKS, it MUST respond with
523 /// REQUEST_ERROR with error code PREFIX_OVERLAP."
524 ///
525 /// Section 10.6.2: "SUBSCRIBE_NAMESPACE and SUBSCRIBE_TRACKS have
526 /// independent overlap spaces, so a SUBSCRIBE_NAMESPACE and a
527 /// SUBSCRIBE_TRACKS may share the same prefix."
528 ///
529 /// Taken when the message arrives, which is the moment the sentence
530 /// names, and read again when an answer is built: a request this endpoint
531 /// may not accept is one no later call can accept.
532 ///
533 /// The refusal itself is not this error. It is a message the peer is
534 /// owed, so the request is recorded like any other and refused through
535 /// the same call that refuses any other, under the code the sentence
536 /// names.
537 #[error(
538 "request {request} subscribes to a namespace prefix overlapping request {established}"
539 )]
540 PeerPrefixOverlap {
541 /// The request that arrived.
542 request: u64,
543 /// The namespace subscription it overlaps.
544 established: u64,
545 },
546 /// A namespace subscription that overlaps another was refused under a
547 /// code other than the one the sentence names.
548 ///
549 /// The same shape as [`EndpointError::WrongJoiningRefusal`]: a rule that
550 /// names the code its refusal carries is not satisfied by a refusal under
551 /// any other, because the peer reads the code to learn what went wrong.
552 #[error("request {request} overlaps a namespace subscription and must be refused with code {required:#x}")]
553 WrongOverlapRefusal {
554 /// The request being refused.
555 request: u64,
556 /// The code the sentence names for it.
557 required: u64,
558 },
559}
560
561/// Whether two namespace prefixes overlap.
562///
563/// Section 10.18: "Within a session, if a publisher receives a
564/// SUBSCRIBE_NAMESPACE with a Track Namespace Prefix that shares a common
565/// prefix with an established SUBSCRIBE_NAMESPACE, it MUST respond with
566/// REQUEST_ERROR with error code PREFIX_OVERLAP."
567///
568/// Section 10.19: "Within a session, if a publisher receives a
569/// SUBSCRIBE_TRACKS with a Track Namespace Prefix that shares a common prefix
570/// with an established SUBSCRIBE_TRACKS, it MUST respond with REQUEST_ERROR
571/// with error code PREFIX_OVERLAP."
572///
573/// Section 10.6.2: "SUBSCRIBE_NAMESPACE and SUBSCRIBE_TRACKS have independent
574/// overlap spaces, so a SUBSCRIBE_NAMESPACE and a SUBSCRIBE_TRACKS may share
575/// the same prefix."
576///
577/// A namespace matches a namespace subscription when the subscription's
578/// prefix is a prefix of it, so two prefixes select overlapping sets of
579/// namespaces exactly when one of them is a prefix of the other. Equal
580/// prefixes are that case as well: every prefix is a prefix of itself, and
581/// two equal ones select the same set.
582///
583/// "Shares a common prefix with" is read as the relation drafts 07 through 14
584/// spell out at greater length. Taken at its word it would forbid every
585/// second namespace subscription in a session, since any two prefixes share
586/// the empty one, and nothing else in this draft supports that.
587fn prefixes_overlap(a: &[Vec<u8>], b: &[Vec<u8>]) -> bool {
588 let shared = a.len().min(b.len());
589 a[..shared] == b[..shared]
590}
591
592/// Parameter Type of TRACK_NAMESPACE_PREFIX.
593const TRACK_NAMESPACE_PREFIX: u64 = 0x34;
594
595/// The Track Namespace Prefix a REQUEST_UPDATE asks a namespace subscription
596/// to move to, and `None` when it asks for no such move.
597///
598/// Section 10.2.19: "The TRACK_NAMESPACE_PREFIX parameter (Parameter Type
599/// 0x34) uses the Track Namespace encoding described in Section 2.4.1. It MAY
600/// appear in REQUEST_UPDATE for a SUBSCRIBE_NAMESPACE or SUBSCRIBE_TRACKS
601/// request. It updates the Track Namespace Prefix for that subscription."
602///
603/// A prefix of no fields is a value rather than a removal. Section 2.4.1 puts
604/// a Track Namespace at "between 0 and 32 Track Namespace Fields", an empty
605/// prefix selects every namespace, and Section 10.9 leaves no other reading
606/// open: "There is no mechanism to remove a parameter from a request."
607///
608/// The last one wins when a message carries the parameter twice. That is the
609/// rule Section 10.9 gives for two messages -- "Parameter values from later
610/// REQUEST_UPDATE messages override values from earlier ones" -- applied
611/// inside one, which is the only reading under which a repeat means anything.
612///
613/// A value that is not a whole Track Namespace answers `None`, so an update
614/// carrying one leaves the prefix where it was. The decoder that built the
615/// message has already refused a malformed one; this is the arm that keeps a
616/// hand-built message from moving a subscription to half a prefix.
617fn updated_prefix(parameters: &[KeyValuePair]) -> Option<TrackNamespace> {
618 parameters.iter().rfind(|p| p.key.into_inner() == TRACK_NAMESPACE_PREFIX).and_then(|p| match &p
619 .value
620 {
621 KvpValue::Bytes(bytes) => {
622 let mut cursor = &bytes[..];
623 let prefix = TrackNamespace::decode_allow_empty_moqt::<Moqt18>(&mut cursor).ok()?;
624 cursor.is_empty().then_some(prefix)
625 }
626 KvpValue::Varint(_) => None,
627 })
628}
629
630impl EndpointError {
631 /// Whose doing this is — the peer's, or this endpoint's, or a variant that
632 /// cannot say.
633 ///
634 /// The companion of [`EndpointError::session_error_code`], which answers
635 /// *what the draft requires be done about it*. Neither answers the other's
636 /// question and the pair is what a caller needs: a code without a side
637 /// names nobody, and a side without a code is not grounds to publish
638 /// anything.
639 ///
640 /// Exhaustive, with no wildcard arm, so a variant added to this draft's
641 /// `EndpointError` is a compile error here rather than a silent arrival on
642 /// the wrong side of the answer. See
643 /// [`EndpointFault`](crate::above_codec_rules::EndpointFault) for the three
644 /// answers and for the collision that made the third one necessary.
645 pub fn fault(&self) -> crate::above_codec_rules::EndpointFault {
646 use crate::above_codec_rules::{AboveCodecRule as Rule, EndpointFault as Fault};
647
648 match self {
649 // Raised on both a receive path and a send path, so the
650 // variant cannot say which end is at fault. The state machines
651 // render as `invalid transition from X on event Y` whichever end
652 // asked for the transition, and the unknown-request errors name
653 // an id that may be one the peer sent or one a caller here made
654 // up.
655 EndpointError::Session(..)
656 | EndpointError::Subscription(..)
657 | EndpointError::Fetch(..)
658 | EndpointError::Namespace(..)
659 | EndpointError::TrackStatus(..)
660 | EndpointError::PublishFlow(..)
661 | EndpointError::Setup(..)
662 | EndpointError::UnknownRequest(..) => Fault::EitherEnd,
663
664 // Raised on the way out. Nothing reached the wire, so none of
665 // these is evidence about a peer — including the ones a peer
666 // caused, where what failed is this side's attempt to accept
667 // something the draft says to refuse.
668 EndpointError::NotAResponse(..)
669 | EndpointError::TrackPropertiesOnOutgoingRequestOk(..)
670 | EndpointError::FilterMustBeRejected(..)
671 | EndpointError::NoUpdateToAnswer(..)
672 | EndpointError::WrongUpdateFailureStatus { .. }
673 | EndpointError::NotActive
674 | EndpointError::Draining
675 | EndpointError::TrackAliasInUse { .. }
676 | EndpointError::UnjoinableSubscription { .. }
677 | EndpointError::WrongJoiningRefusal { .. }
678 | EndpointError::PeerPrefixOverlap { .. }
679 | EndpointError::WrongOverlapRefusal { .. } => Fault::ThisEndpoint,
680
681 // Raised reading what the peer sent.
682 EndpointError::NotARequest(..) => Fault::Peer(Rule::BidiStreamOpener),
683 EndpointError::DuplicateTrackAlias { .. } => Fault::Peer(Rule::DuplicateTrackAlias),
684 EndpointError::GoAwayUriAtServer => Fault::Peer(Rule::GoAwayAtServer),
685 EndpointError::ResponseOnControlStream
686 | EndpointError::RequestMessageOnControlStream(..)
687 | EndpointError::UnexpectedOnPeerRequestStream(..) => {
688 Fault::Peer(Rule::MessageOnTheWrongStream)
689 }
690 EndpointError::ObjectPastFinalObject { .. } => Fault::Peer(Rule::ObjectPastFinalObject),
691 EndpointError::RedirectTrackNameOnNamespaceRequest(..) => {
692 Fault::Peer(Rule::RedirectTrackNameOnNamespaceRequest)
693 }
694 EndpointError::RedirectUriAtServer => Fault::Peer(Rule::RedirectUriAtServer),
695 EndpointError::RepeatedGoAway | EndpointError::RepeatedGoAwayOnRequestStream(..) => {
696 Fault::Peer(Rule::RepeatedGoAway)
697 }
698 EndpointError::DuplicateRequestId(..) => Fault::Peer(Rule::RequestIdOutOfSequence),
699 // A REQUEST_UPDATE on the control stream is this rule and not
700 // `MessageOnTheWrongStream`. Section 10.9 states the rule as two
701 // permitted cases and closes over everything else — "The sender of
702 // a request (SUBSCRIBE, PUBLISH, FETCH, PUBLISH_NAMESPACE,
703 // SUBSCRIBE_NAMESPACE, SUBSCRIBE_TRACKS) can later send a
704 // REQUEST_UPDATE on the same bidi stream as the request to modify
705 // it. A subscriber can also send REQUEST_UPDATE to modify
706 // parameters of a subscription established with PUBLISH." — and
707 // the first case is *on the same bidi stream as the request*. One
708 // that arrives on the control stream is in neither case, so it is
709 // squarely inside "other than in the two cases above", which is the
710 // sentence this rule cites. Filed under the wrong-stream rule it
711 // would carry a close no draft states; filed here, it carries the
712 // one draft-19 and draft-20 do.
713 EndpointError::RequestUpdateOnControlStream
714 | EndpointError::UnexpectedRequestUpdate(..) => {
715 Fault::Peer(Rule::RequestUpdateForTheWrongRequest)
716 }
717 EndpointError::ResponseBeforeTheFirstResponse(..) => {
718 Fault::Peer(Rule::ResponseBeforeItsFirstResponse)
719 }
720 EndpointError::TooManyRequestUpdates(..) => Fault::Peer(Rule::TooManyRequestUpdates),
721 EndpointError::TrackPropertiesOnNonTrackStatus(..) => {
722 Fault::Peer(Rule::TrackPropertiesOnNonTrackStatus)
723 }
724
725 // The Request ID rules, which are the peer's: every one of them is
726 // read off an id the peer put on the wire. This draft carries no
727 // MAX_REQUEST_ID, so neither ceiling arm can fire — the allocator
728 // opens at `u64::MAX` and nothing ever lowers it - and they are
729 // answered rather than left out so that a draft restoring the
730 // message does not restore a hole with it.
731 EndpointError::RequestId(e) => match e {
732 RequestIdError::Decreased(..) => Fault::Peer(Rule::MaxRequestIdDecreased),
733 RequestIdError::ExceedsMax(..) => Fault::Peer(Rule::RequestIdCeiling),
734 RequestIdError::WrongParity(..) => Fault::Peer(Rule::RequestIdParity),
735 // This endpoint has spent the budget the peer granted it.
736 RequestIdError::Blocked => Fault::ThisEndpoint,
737 },
738 }
739 }
740
741 /// The code to close the session with, when draft-19 says this error is
742 /// fatal to the session rather than to one request.
743 ///
744 /// `None` means the error is recoverable: the caller may report it and
745 /// keep the session running. `Some` means the draft requires a close, and
746 /// the endpoint has already moved its own session state to
747 /// [`SessionState::Closed`] — the code is what the transport should carry.
748 ///
749 /// The two codes are not interchangeable. Section 3.3 gives
750 /// PROTOCOL_VIOLATION for a bidirectional stream that begins with the wrong
751 /// message type; Section 10.1 gives INVALID_REQUEST_ID for a Request ID
752 /// with the wrong least significant bit or a duplicate one. A peer checking
753 /// close codes can tell the two apart, so this must too.
754 pub fn session_error_code(&self) -> Option<SessionErrorCode> {
755 match self {
756 EndpointError::RequestUpdateOnControlStream
757 | EndpointError::UnexpectedRequestUpdate(_)
758 | EndpointError::TrackPropertiesOnNonTrackStatus(_)
759 | EndpointError::RedirectUriAtServer
760 | EndpointError::RedirectTrackNameOnNamespaceRequest(_)
761 | EndpointError::ResponseBeforeTheFirstResponse(..)
762 | EndpointError::NotARequest(_)
763 | EndpointError::GoAwayUriAtServer
764 | EndpointError::RepeatedGoAway
765 | EndpointError::RepeatedGoAwayOnRequestStream(_) => {
766 Some(SessionErrorCode::ProtocolViolation)
767 }
768 EndpointError::DuplicateRequestId(_)
769 | EndpointError::RequestId(RequestIdError::WrongParity(..)) => {
770 Some(SessionErrorCode::InvalidRequestId)
771 }
772 EndpointError::TooManyRequestUpdates(..) => {
773 Some(SessionErrorCode::TooManyRequestUpdates)
774 }
775 // Section 11.1 names this code in the sentence that states the
776 // rule, and names no other. A close carrying PROTOCOL_VIOLATION
777 // would tell the peer a different thing went wrong.
778 EndpointError::DuplicateTrackAlias { .. } => {
779 Some(SessionErrorCode::DuplicateTrackAlias)
780 }
781 // Stated rather than left to the arm below, because the neighbour
782 // above makes the opposite choice about a rule of the same shape.
783 // MAX_REQUEST_UPDATES is a ceiling the draft answers with a session
784 // close; MAX_FILTER_RANGES is a ceiling it answers with a
785 // REQUEST_ERROR. Two consecutive subsections, two different answers,
786 // and nothing about either sentence signals which.
787 EndpointError::FilterMustBeRejected(..) => None,
788 _ => None,
789 }
790 }
791}
792
793pub struct Endpoint {
794 role: Role,
795 session: SessionStateMachine,
796 request_ids: RequestIdAllocator,
797 subscriptions: HashMap<u64, SubscriptionStateMachine>,
798 fetches: HashMap<u64, FetchStateMachine>,
799 /// Every Joining Fetch the peer sent naming a subscription this session
800 /// had none live for, and the identifier each one named.
801 ///
802 /// Judged as the FETCH arrives, because that is the moment the rule about
803 /// it names, and kept for as long as the fetch is: a subscription that
804 /// ends between the FETCH and its answer does not turn a fetch that could
805 /// be joined into one that could not.
806 unjoinable_fetches: HashMap<u64, u64>,
807 subscribe_namespaces: HashMap<u64, SubscribeNamespaceStateMachine>,
808 subscribe_tracks: HashMap<u64, SubscribeNamespaceStateMachine>,
809 publish_namespaces: HashMap<u64, PublishNamespaceStateMachine>,
810 track_statuses: HashMap<u64, TrackStatusStateMachine>,
811 publishes: HashMap<u64, PublishStateMachine>,
812 goaway_uri: Option<Vec<u8>>,
813 /// The peer's requests whose stream has already carried a GOAWAY.
814 ///
815 /// Section 10.4 makes the second GOAWAY on one request stream a session
816 /// close while leaving a first one on every other stream legal, so the
817 /// count cannot live on the session. Held apart from the per-kind request
818 /// maps because a GOAWAY says nothing about which kind of request it
819 /// migrates.
820 goaway_request_streams: HashSet<u64>,
821 /// Every Request ID the peer has spent, whether or not the request it
822 /// opened is still live.
823 ///
824 /// Draft-19 Section 10.1 makes a duplicate Request ID a session close, and
825 /// "duplicate" is about the id ever having been used, not about the
826 /// request still being open. Deriving it from the per-kind maps instead
827 /// would answer wrongly the moment those maps are ever pruned, so the rule
828 /// is stated once, here, and this set is never pruned.
829 peer_request_ids: HashSet<u64>,
830 /// Every request the **peer** opened a stream with, as it arrived, keyed
831 /// by the Request ID it carries.
832 ///
833 ///
834 /// One map for all seven kinds, because one entry point takes all seven:
835 /// [`receive_request_on_stream`](Self::receive_request_on_stream) is the
836 /// only thing that writes here, so what is in here is a request, and it
837 /// is the peer's, by construction. How far each one has got stays in the
838 /// per-kind map beside this endpoint's own requests, which is where every
839 /// later message on the stream reads it and where the parity of a Request
840 /// ID keeps the two ends apart.
841 ///
842 /// What a map of state machines cannot hold is the request. Section 5.1 has
843 /// the subscriber "either accepts or rejects the subscription", and what is
844 /// being accepted or rejected -- the track, the namespace prefix, the
845 /// parameters -- is named in the request and nowhere else once the message
846 /// has been dropped.
847 inbound_requests: HashMap<u64, ControlMessage>,
848 /// The request each namespace subscription of the peer's overlapped when
849 /// it arrived, keyed by the Request ID of the one that arrived.
850 ///
851 /// The prefix is judged where the sentence says it is judged, on receipt,
852 /// and the verdict is read again when the answer is written. An entry
853 /// means this endpoint owes that request a REQUEST_ERROR and may send it
854 /// nothing else.
855 overlapping_namespace_subscriptions: HashMap<u64, u64>,
856 /// The Track Namespace Prefix a REQUEST_UPDATE asks one of the peer's
857 /// namespace subscriptions to move to, held until that update is
858 /// answered.
859 ///
860 /// Section 10.9.2 ties the move to the acceptance: "If the update is
861 /// accepted, NAMESPACE and NAMESPACE_DONE messages following the
862 /// REQUEST_OK will contain Track Namespace suffixes relative to the
863 /// updated prefix." Until then the subscription still selects what the
864 /// peer opened it with, so a later request is weighed against the prefix
865 /// in `inbound_requests` and not against this one.
866 updated_namespace_prefixes: HashMap<u64, TrackNamespace>,
867 /// The subscription an unanswered prefix update would collide with, when
868 /// it would, keyed by the Request ID of the one being moved.
869 ///
870 /// Separate from `overlapping_namespace_subscriptions` because one
871 /// Request ID can be carrying both verdicts at once: the request that
872 /// opened the stream has one and an update on it has another, and they
873 /// are settled by different messages on that same stream.
874 overlapping_prefix_updates: HashMap<u64, u64>,
875 /// The MAX_REQUEST_UPDATES this endpoint put in its own SETUP.
876 ///
877 /// The peer's value is a different number and belongs to the sending side,
878 /// which this type has no path for: draft-19 gives the endpoint no
879 /// REQUEST_UPDATE builder, so there is nothing here to hold back.
880 ///
881 /// Zero means no limit rather than none allowed, which is the opposite of
882 /// how MAX_REQUEST_ID reads. Section 10.3.1.7 says so outright - "A value
883 /// of 0 means the endpoint does not limit REQUEST_UPDATE concurrency. If
884 /// not present, the default value is 0" - so an endpoint that never sends
885 /// the option is not limiting anything, and a check that read zero as a
886 /// ceiling would refuse the first update of every session.
887 advertised_max_request_updates: u64,
888 /// Per request stream, how many REQUEST_UPDATEs the peer has sent that this
889 /// endpoint has not yet answered.
890 ///
891 /// "Outstanding" is Section 10.3.1.7's word and it is per stream, not per
892 /// session: "Each REQUEST_OK or REQUEST_ERROR response restores one credit
893 /// on that stream."
894 outstanding_peer_updates: HashMap<u64, u64>,
895 /// Per request stream, how many REQUEST_UPDATEs are still waiting for the
896 /// answer Section 10.9 requires.
897 ///
898 /// A second count rather than a reading of the one above, because the two
899 /// sentences count different things and disagree on exactly one response.
900 /// Section 10.3.1.7 says "Each REQUEST_OK or REQUEST_ERROR response
901 /// restores one credit on that stream" — every response, including the
902 /// REQUEST_OK that answers a SUBSCRIBE_NAMESPACE rather than an update. That
903 /// is the sender's accounting rule as much as the receiver's, and an
904 /// endpoint that credited more carefully than the peer does would close a
905 /// conforming session, so the credit above stays literal.
906 ///
907 /// This one is about which response answers which message, and there the
908 /// request's own REQUEST_OK answers the request. Keeping them apart is what
909 /// lets a response be recognised as an update's answer without changing
910 /// what the peer is allowed to send.
911 unanswered_peer_updates: HashMap<u64, u64>,
912 /// The requests whose refused update has not been followed by the
913 /// PUBLISH_DONE that ends them.
914 ///
915 /// Emptied as each is written. A request is in here for exactly as long as
916 /// this endpoint owes the peer the second half of a refusal.
917 owed_update_failures: HashSet<u64>,
918 /// The peer's requests whose own response has already been written.
919 ///
920 /// Section 10.9 gives an update the same two answers a request has, and on
921 /// the five kinds REQUEST_OK answers, the message that answers the request
922 /// and the message that answers an update are the same message. Nothing on
923 /// the wire tells them apart, so both endpoints resolve it by order: the
924 /// first response on a stream answers the request that opened it and the
925 /// ones after it answer updates. This set is that order, recorded.
926 answered_peer_requests: HashSet<u64>,
927 /// The MAX_FILTER_RANGES this endpoint put in its own SETUP.
928 ///
929 /// Section 10.3.1.6: "The MAX_FILTER_RANGES option (Type 0x06) limits the
930 /// peer's total number of Ranges (Start/End pairs) allowed concurrently in
931 /// all Range filter parameters for a given subscription or fetch. The
932 /// default value is 0, so if not specified, the peer MUST NOT send any such
933 /// filter parameters."
934 ///
935 /// So zero is none allowed, and it is the default. The option in the
936 /// subsection after this one, MAX_REQUEST_UPDATES, reads its zero the other
937 /// way — no limit — and the two are otherwise the same shape. Reading either
938 /// with the other's rule is a working implementation that is wrong in one
939 /// direction or the other for every session.
940 advertised_max_filter_ranges: u64,
941 /// Requests whose Range Filters this endpoint owes the peer a REQUEST_ERROR
942 /// about.
943 ///
944 /// Keyed by Request ID, because that is what the reply names. Entries are
945 /// spent by the REQUEST_ERROR that answers them, and a REQUEST_OK offered
946 /// for one is refused: taking the request in and then accepting it would
947 /// leave the peer with a subscription whose filters were never agreed.
948 peer_filter_rejections: HashMap<u64, FilterRejection>,
949 /// The Range Filter parameters currently in force on each of the peer's
950 /// requests.
951 ///
952 /// Section 5.1.3 makes the ceiling a property of the request rather than of
953 /// the message that carried it — "the total number of Ranges allowed
954 /// concurrently in all Range filter parameters for a given subscription or
955 /// fetch" — and lets a REQUEST_UPDATE rewrite the set: "Length can be 0 to
956 /// remove a filter parameter or non-zero to replace that entire filter
957 /// parameter including all sets and Property Types. If a filter parameter is
958 /// omitted from REQUEST_UPDATE, the value is unchanged."
959 ///
960 /// So the count that matters is of what is in force after the update, and
961 /// the parameters an update leaves alone are part of it. Held as the
962 /// parameters rather than as decoded filters because replacement is by
963 /// Parameter Type, which is the key of the pair.
964 peer_request_filters: HashMap<u64, Vec<KeyValuePair>>,
965 /// What Full Track Name the peer has attached each Track Alias to, per
966 /// Request ID.
967 ///
968 /// Section 11.1 forbids one alias naming two tracks at once, and the "at
969 /// once" is what makes this a table rather than a set: an alias the peer
970 /// used for a track whose subscription has ended is free again. The table
971 /// therefore records the binding and reads liveness back off the request's
972 /// own state machine, rather than keeping a second copy of it that every
973 /// path ending a subscription would have to remember to prune.
974 track_bindings: HashMap<u64, TrackBinding>,
975 /// The track each fetch this endpoint made is for.
976 ///
977 /// Not in `track_bindings`, because that table exists to answer questions
978 /// about Track Aliases and a fetch has none: its objects arrive on a stream
979 /// that opens by naming the Request ID. A Joining Fetch names no track
980 /// either and takes the joined subscription's, resolved as the fetch is
981 /// made rather than at the withdrawal - one fills a buffer behind the live
982 /// edge and outlives the subscription it joined, so a lookup through the
983 /// join would come up empty exactly while there was still a fetch to
984 /// cancel.
985 fetch_tracks: HashMap<u64, FetchTrack>,
986 /// Which tracks this endpoint has given up on, and what for.
987 ///
988 /// Behind a lock because the note is taken on the data plane, where this
989 /// endpoint is reached through `&self`.
990 malformed: Mutex<MalformedTracks>,
991 /// How far each track's objects have reached, and where a track ended.
992 ///
993 /// An `Arc` because a subgroup stream measures its objects against one
994 /// track for as long as it runs, and the handle it holds outlives any
995 /// single call into this endpoint.
996 locations: Arc<Mutex<TrackLocations>>,
997}
998
999/// The track one fetch is for, which the fetch's own state machine does not
1000/// hold.
1001#[derive(Debug, Clone)]
1002struct FetchTrack {
1003 namespace: TrackNamespace,
1004 name: Vec<u8>,
1005}
1006
1007/// A Track Alias the peer has attached to a Full Track Name, and the request
1008/// whose lifetime the attachment follows.
1009#[derive(Debug, Clone)]
1010struct TrackBinding {
1011 namespace: TrackNamespace,
1012 name: Vec<u8>,
1013 /// The alias, once the peer has named one.
1014 ///
1015 /// A SUBSCRIBE this endpoint sends names a track and waits for its alias,
1016 /// so the binding exists with no alias in it from the moment the request
1017 /// is made until its SUBSCRIBE_OK arrives. A PUBLISH carries both at once
1018 /// and is never in that state.
1019 alias: Option<u64>,
1020 kind: BindingKind,
1021}
1022
1023/// Which of the two sequences Section 5.1 names established the subscription
1024/// that owns a binding, and so which state machine says whether it still has
1025/// one.
1026#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1027enum BindingKind {
1028 /// This endpoint's SUBSCRIBE, established by the peer's SUBSCRIBE_OK.
1029 Subscribe,
1030 /// A PUBLISH, established by the PUBLISH_OK answering it - whichever end
1031 /// sent which. Both are held in the same map, which Request ID parity
1032 /// keeps from colliding.
1033 Publish,
1034}
1035
1036impl Endpoint {
1037 pub fn new(role: Role) -> Self {
1038 Self {
1039 role,
1040 session: SessionStateMachine::new(),
1041 request_ids: RequestIdAllocator::new(role),
1042 subscriptions: HashMap::new(),
1043 fetches: HashMap::new(),
1044 unjoinable_fetches: HashMap::new(),
1045 subscribe_namespaces: HashMap::new(),
1046 subscribe_tracks: HashMap::new(),
1047 publish_namespaces: HashMap::new(),
1048 track_statuses: HashMap::new(),
1049 publishes: HashMap::new(),
1050 goaway_uri: None,
1051 goaway_request_streams: HashSet::new(),
1052 peer_request_ids: HashSet::new(),
1053 inbound_requests: HashMap::new(),
1054 overlapping_namespace_subscriptions: HashMap::new(),
1055 updated_namespace_prefixes: HashMap::new(),
1056 overlapping_prefix_updates: HashMap::new(),
1057 advertised_max_request_updates: 0,
1058 outstanding_peer_updates: HashMap::new(),
1059 unanswered_peer_updates: HashMap::new(),
1060 owed_update_failures: HashSet::new(),
1061 answered_peer_requests: HashSet::new(),
1062 advertised_max_filter_ranges: 0,
1063 peer_filter_rejections: HashMap::new(),
1064 peer_request_filters: HashMap::new(),
1065 track_bindings: HashMap::new(),
1066 fetch_tracks: HashMap::new(),
1067 malformed: Mutex::new(MalformedTracks::new()),
1068 locations: Arc::new(Mutex::new(TrackLocations::new())),
1069 }
1070 }
1071
1072 /// The Track Alias the peer attached to `request_id`, once it has named
1073 /// one.
1074 ///
1075 /// Answers for a subscription this endpoint asked for from the moment its
1076 /// SUBSCRIBE_OK arrives, and for one the peer offered from the moment its
1077 /// PUBLISH does. `None` before that, and for a Request ID this session has
1078 /// no track for.
1079 pub fn track_alias_for(&self, request_id: VarInt) -> Option<VarInt> {
1080 let alias = self.track_bindings.get(&request_id.into_inner())?.alias?;
1081 VarInt::from_u64(alias).ok()
1082 }
1083
1084 /// The refusal Section 11.1 requires when `alias` already names a
1085 /// different track that still has an Established subscription, or `None`
1086 /// when it is free.
1087 ///
1088 /// # Why the set is read rather than kept
1089 ///
1090 /// "Established" is a subscription state Section 5.1 defines, and both
1091 /// state machines here already hold it: a subscription reaches it on
1092 /// SUBSCRIBE_OK and a publish on PUBLISH_OK, and each leaves it on the
1093 /// message that ends the flow. Asking them is what makes an alias free
1094 /// again the moment its track's subscription ends, with nothing to prune
1095 /// on the way out - and a path that ended a subscription without telling
1096 /// this table would otherwise leave the alias held forever and refuse the
1097 /// peer's next, conforming, use of it.
1098 ///
1099 /// # Why the request's own binding is skipped
1100 ///
1101 /// A SUBSCRIBE_OK is judged before its own alias is written down, so the
1102 /// skip is not what keeps it from finding itself. A PUBLISH is not: a
1103 /// second PUBLISH under a Request ID already bound is refused by the
1104 /// duplicate-Request-ID rule before it reaches here, and comparing a
1105 /// request against its own binding would answer the wrong rule if that one
1106 /// ever moved.
1107 fn conflicting_track_alias(
1108 &self,
1109 request_id: u64,
1110 alias: u64,
1111 namespace: &TrackNamespace,
1112 name: &[u8],
1113 ) -> Option<EndpointError> {
1114 for (&id, binding) in &self.track_bindings {
1115 if id == request_id || binding.alias != Some(alias) {
1116 continue;
1117 }
1118 if binding.namespace == *namespace && binding.name == name {
1119 continue;
1120 }
1121 if self.binding_is_established(id, binding.kind) {
1122 return Some(EndpointError::DuplicateTrackAlias {
1123 alias,
1124 established: id,
1125 offered: request_id,
1126 });
1127 }
1128 }
1129 None
1130 }
1131
1132 /// The request already using `alias` for a track other than (`namespace`,
1133 /// `name`), or `None` when this endpoint may give the alias to that track.
1134 ///
1135 /// Separate from [`Self::conflicting_track_alias`] because the two answer
1136 /// different questions about the same table. That one judges a message
1137 /// that has arrived and ends the session over it; this one judges one that
1138 /// has not been built and declines to build it.
1139 fn alias_held_elsewhere(
1140 &self,
1141 alias: u64,
1142 namespace: &TrackNamespace,
1143 name: &[u8],
1144 ) -> Option<EndpointError> {
1145 self.track_bindings.iter().find_map(|(&id, binding)| {
1146 let other_track = binding.namespace != *namespace || binding.name != name;
1147 (binding.alias == Some(alias)
1148 && other_track
1149 && self.binding_is_in_use(id, binding.kind))
1150 .then_some(EndpointError::TrackAliasInUse { alias, held: id })
1151 })
1152 }
1153
1154 /// Whether a binding's request has put its alias in play at all. Broader
1155 /// than [`Self::binding_is_established`], and the two sentences are why.
1156 /// What a subscriber must close over is qualified - "the same Track Alias
1157 /// as a different Track with an Established subscription" - and the
1158 /// prohibition on the publisher is not: "The same Track Alias MUST NOT be
1159 /// used by a publisher to refer to two different Tracks simultaneously in
1160 /// the same session." Once a PUBLISH carrying an alias has been sent,
1161 /// giving that alias to a second track is what that sentence forbids,
1162 /// answered or not.
1163 fn binding_is_in_use(&self, id: u64, kind: BindingKind) -> bool {
1164 match kind {
1165 BindingKind::Subscribe => {
1166 self.subscriptions.get(&id).is_some_and(|sm| sm.state() != SubscriptionState::Done)
1167 }
1168 BindingKind::Publish => {
1169 self.publishes.get(&id).is_some_and(|sm| sm.state() != PublishState::Done)
1170 }
1171 }
1172 }
1173
1174 /// Whether the request that owns a binding still has an Established
1175 /// subscription.
1176 fn binding_is_established(&self, id: u64, kind: BindingKind) -> bool {
1177 match kind {
1178 BindingKind::Subscribe => self
1179 .subscriptions
1180 .get(&id)
1181 .is_some_and(|sm| sm.state() == SubscriptionState::Active),
1182 BindingKind::Publish => {
1183 self.publishes.get(&id).is_some_and(|sm| sm.state() == PublishState::Active)
1184 }
1185 }
1186 }
1187
1188 /// The track a live binding has given `alias` to.
1189 ///
1190 /// Read rather than kept: a binding whose request has ended holds nothing,
1191 /// and an alias that is free again may name a different track next.
1192 fn track_for_alias(&self, alias: u64) -> Option<(&TrackNamespace, &[u8])> {
1193 self.track_bindings.iter().find_map(|(&id, binding)| {
1194 (binding.alias == Some(alias) && self.binding_is_in_use(id, binding.kind))
1195 .then_some((&binding.namespace, binding.name.as_slice()))
1196 })
1197 }
1198
1199 /// Whether this endpoint *receives* the track a request names.
1200 ///
1201 /// The sentence to be answered is a subscriber's - "cancel any
1202 /// corresponding subscription or fetches for that Track from that
1203 /// publisher" - so a request that makes this endpoint the publisher is not
1204 /// one of them. A SUBSCRIBE in the table is always this endpoint's own,
1205 /// because a SUBSCRIBE the peer sends makes this endpoint the publisher and
1206 /// leaves no binding. A PUBLISH is in the table either way round, and
1207 /// Request ID parity is what separates them: the offer the peer made is the
1208 /// one this endpoint receives a track through.
1209 fn receives_through(&self, id: u64, kind: BindingKind) -> bool {
1210 match kind {
1211 BindingKind::Subscribe => true,
1212 BindingKind::Publish => self.request_ids.validate_peer_id(id).is_ok(),
1213 }
1214 }
1215
1216 /// The record a stream carrying `alias`'s objects measures them against.
1217 ///
1218 /// `None` for an alias no live binding names: an object for one breaks a
1219 /// different rule, and measuring it against a track this endpoint never
1220 /// asked for would answer that one with the wrong sentence.
1221 pub fn track_objects(&self, alias: u64) -> Option<TrackObjects> {
1222 let (namespace, name) = self.track_for_alias(alias)?;
1223 Some(TrackObjects::new(
1224 Arc::clone(&self.locations),
1225 namespace.clone(),
1226 name.to_vec(),
1227 alias,
1228 ))
1229 }
1230
1231 /// Record or judge one object that arrived outside a subgroup stream, and
1232 /// report Section 2.4.2's Malformed Track when it arrived after the place
1233 /// an end-of-track object put the end.
1234 ///
1235 /// One rule and not two. The placement rule drafts 08 through 13 state
1236 /// about an end-of-track object is not in this draft, so an object that
1237 /// ends a track here is judged against nothing and only settles where the
1238 /// track stopped.
1239 ///
1240 /// `&self`, because the call site is the data plane's.
1241 pub fn note_received_object(
1242 &self,
1243 alias: u64,
1244 at: ObjectLocation,
1245 role: ObjectRole,
1246 ) -> Result<(), EndpointError> {
1247 let Some(objects) = self.track_objects(alias) else { return Ok(()) };
1248 objects.note_past_final(at, role).map_err(|end| EndpointError::ObjectPastFinalObject {
1249 alias,
1250 group: at.group,
1251 object: at.object,
1252 final_group: end.group,
1253 final_object: end.object,
1254 })
1255 }
1256
1257 /// The condition a track was withdrawn for, or `None` for a track this
1258 /// endpoint has found nothing wrong with.
1259 ///
1260 /// # Why this takes a track and not the alias the object carried
1261 ///
1262 /// An alias only means anything through a live binding, and the withdrawal
1263 /// ends the binding it would have been resolved through. An accessor taking
1264 /// an alias would therefore answer `None` from the instant it had something
1265 /// to say. The record is keyed on the track, and so is this.
1266 pub fn malformed_track(
1267 &self,
1268 namespace: &TrackNamespace,
1269 name: &[u8],
1270 ) -> Option<MalformedTrackCondition> {
1271 self.malformed
1272 .lock()
1273 .unwrap_or_else(|poisoned| poisoned.into_inner())
1274 .condition(namespace, name)
1275 }
1276
1277 /// Note a track as malformed and name every request through which this
1278 /// endpoint is receiving it, so the caller can cancel them.
1279 ///
1280 /// **This is the whole of what this crate can do here, and the reason is
1281 /// structural rather than a shortfall.** Section 2.4.2 asks a subscriber
1282 /// that detects a Malformed Track to "cancel any corresponding subscription
1283 /// or fetches for that Track from that publisher", and on this draft
1284 /// cancelling a request means resetting the request's own bidirectional
1285 /// stream - Section 3.3.3. Every request lives at the front of a
1286 /// stream of its own, and `Connection::recv_on_request_stream` hands that
1287 /// stream to the caller as a `RequestStream`. So the stream this answer
1288 /// operates on is not the endpoint's to touch, and no amount of state here
1289 /// changes that. What the endpoint can do is say which requests they are.
1290 ///
1291 /// # Nothing here ends a request
1292 ///
1293 /// Deliberately, and it is what keeps the answer to one. The flows are read
1294 /// and not moved, so a caller that passes each id to
1295 /// `Connection::cancel_request_stream` ends the request there - and the
1296 /// binding stops being in use, so a second object past the end finds no
1297 /// track for the alias and names nothing. The request ending is still what
1298 /// closes the loop, exactly as it is on the drafts that answer with a
1299 /// message; what changed is which side ends it. A caller that ignores the
1300 /// list gets named the same requests again, which is honest: the condition
1301 /// really did fire again.
1302 ///
1303 /// # What is named, and what is not
1304 ///
1305 /// Requests through which this endpoint *receives* the track: a SUBSCRIBE
1306 /// this endpoint sent, a PUBLISH the peer sent, and a fetch this endpoint
1307 /// made. A request that makes this endpoint the publisher is not one of
1308 /// them. Empty for an alias no live binding names,
1309 /// and empty for a track whose only requests are ones this endpoint
1310 /// publishes.
1311 ///
1312 /// Sorted, because a `HashMap` iterates in no order and two requests for
1313 /// one track is a shape a peer can produce.
1314 pub fn requests_for_malformed_track(
1315 &self,
1316 alias: u64,
1317 condition: MalformedTrackCondition,
1318 ) -> Vec<VarInt> {
1319 let Some((namespace, name)) = self.track_for_alias(alias) else { return Vec::new() };
1320 let (namespace, name) = (namespace.clone(), name.to_vec());
1321 self.requests_for_malformed_track_named(namespace, name, condition)
1322 }
1323
1324 /// [`Self::requests_for_malformed_track`] for the track the FETCH
1325 /// `request_id` asked for, which a FETCH stream found malformed: the stream
1326 /// names no alias. Empty for a Request ID this endpoint sent no FETCH under.
1327 pub fn requests_for_malformed_fetch_track(
1328 &self,
1329 request_id: u64,
1330 condition: MalformedTrackCondition,
1331 ) -> Vec<VarInt> {
1332 let Some(track) = self.fetch_tracks.get(&request_id) else { return Vec::new() };
1333 let (namespace, name) = (track.namespace.clone(), track.name.clone());
1334 self.requests_for_malformed_track_named(namespace, name, condition)
1335 }
1336
1337 fn requests_for_malformed_track_named(
1338 &self,
1339 namespace: TrackNamespace,
1340 name: Vec<u8>,
1341 condition: MalformedTrackCondition,
1342 ) -> Vec<VarInt> {
1343 self.malformed
1344 .lock()
1345 .unwrap_or_else(|poisoned| poisoned.into_inner())
1346 .note(&namespace, &name, condition);
1347 let mut ids: Vec<u64> = self
1348 .track_bindings
1349 .iter()
1350 .filter(|(&id, binding)| {
1351 binding.namespace == namespace
1352 && binding.name == name
1353 && self.binding_is_in_use(id, binding.kind)
1354 && self.receives_through(id, binding.kind)
1355 })
1356 .map(|(&id, _)| id)
1357 .chain(self.fetch_tracks.iter().filter_map(|(&id, track)| {
1358 (track.namespace == namespace
1359 && track.name == name
1360 && self.fetches.get(&id).is_some_and(|sm| sm.state() != FetchState::Done))
1361 .then_some(id)
1362 }))
1363 .collect();
1364 ids.sort_unstable();
1365 ids.into_iter().filter_map(|id| VarInt::from_u64(id).ok()).collect()
1366 }
1367
1368 /// The conflict a SUBSCRIBE_OK's alias has with the tracks already bound.
1369 ///
1370 /// Separate from [`Self::conflicting_track_alias`] because the track a
1371 /// SUBSCRIBE_OK is about is not in the SUBSCRIBE_OK: it is the one this
1372 /// endpoint's own SUBSCRIBE asked for, which is why the request has to be
1373 /// looked up before the alias can be judged.
1374 fn conflicting_alias_for_subscribe_ok(&self, id: u64, alias: u64) -> Option<EndpointError> {
1375 let binding = self.track_bindings.get(&id)?;
1376 self.conflicting_track_alias(id, alias, &binding.namespace, &binding.name)
1377 }
1378
1379 pub fn role(&self) -> Role {
1380 self.role
1381 }
1382
1383 /// Returns the role of the peer, which is the other one.
1384 pub fn peer_role(&self) -> Role {
1385 match self.role {
1386 Role::Client => Role::Server,
1387 Role::Server => Role::Client,
1388 }
1389 }
1390
1391 /// Hold a refused update's ending to the status the draft names, and
1392 /// retire the obligation once that ending is written.
1393 ///
1394 /// Both routes to a PUBLISH_DONE come through here. A subscription this
1395 /// endpoint accepted is ended by a response on the peer's stream; a PUBLISH
1396 /// this endpoint sent is ended on its own, which does not go through the
1397 /// response path at all. The rule is the same either way, so it is stated
1398 /// once and called twice rather than written where each route happened to
1399 /// need it.
1400 fn require_update_failure_status(
1401 &mut self,
1402 id: u64,
1403 status_code: VarInt,
1404 ) -> Result<(), EndpointError> {
1405 if !self.owed_update_failures.contains(&id) {
1406 return Ok(());
1407 }
1408 let required = PublishDoneStatusCode::UpdateFailed as u64;
1409 if status_code.into_inner() != required {
1410 return Err(EndpointError::WrongUpdateFailureStatus { request: id, required });
1411 }
1412 self.owed_update_failures.remove(&id);
1413 Ok(())
1414 }
1415
1416 /// Whether the peer has sent a REQUEST_UPDATE on this request that has not
1417 /// been answered yet.
1418 ///
1419 /// Draft-19 Section 10.9: "A subscriber can also send REQUEST_UPDATE to
1420 /// modify parameters of a subscription established with PUBLISH", and the
1421 /// receiver of one "MUST respond with exactly one REQUEST_OK or
1422 /// REQUEST_ERROR message indicating if the update was successful".
1423 ///
1424 /// Asked by the connection layer, which otherwise writes nothing on a
1425 /// stream this endpoint opened. An update the peer sent on a PUBLISH is
1426 /// the one thing on such a stream that this endpoint has to answer, and
1427 /// this is what tells it apart from a response to its own request.
1428 pub fn has_unanswered_update(&self, request_id: VarInt) -> bool {
1429 self.unanswered_peer_updates.get(&request_id.into_inner()).is_some_and(|&n| n > 0)
1430 }
1431
1432 /// Whether the request `id` names a subscription this endpoint publishes.
1433 ///
1434 /// Draft-19 Section 10.9.1 gives a refused REQUEST_UPDATE three different
1435 /// consequences and picks between them by what was being updated: "When a
1436 /// REQUEST_UPDATE is unsuccessful, the publisher MUST also terminate the
1437 /// subscription by sending a PUBLISH_DONE with error code UPDATE_FAILED.
1438 /// When a REQUEST_UPDATE fails for a FETCH, the publisher MUST reset the
1439 /// FETCH data stream. When a REQUEST_UPDATE fails for a SUBSCRIBE_NAMESPACE,
1440 /// SUBSCRIBE_TRACKS or PUBLISH_NAMESPACE, the responder MUST close the bidi
1441 /// stream (see Section 3.3.2)."
1442 ///
1443 /// Only the first of the three is a message, and only the first is owed
1444 /// here. Recording it for the other two is what would hold their streams
1445 /// open past the close the third sentence requires.
1446 ///
1447 /// Two requests leave this endpoint publishing: a SUBSCRIBE the peer sent,
1448 /// and a PUBLISH this endpoint sent. Either can carry an update from the
1449 /// other side, and either is ended by a PUBLISH_DONE written from here. A
1450 /// peer's PUBLISH is neither, because the peer is the publisher on it, and
1451 /// it is told apart by having arrived rather than been sent.
1452 fn publishes_a_subscription(&self, id: u64) -> bool {
1453 matches!(self.inbound_requests.get(&id), Some(ControlMessage::Subscribe(_)))
1454 || (self.publishes.contains_key(&id) && !self.inbound_requests.contains_key(&id))
1455 }
1456
1457 /// Whether this request's refused update still owes the peer the
1458 /// PUBLISH_DONE that ends it.
1459 ///
1460 /// Asked by the connection layer, which owns the stream that termination
1461 /// has to be written on and therefore has to know not to close it. A
1462 /// REQUEST_ERROR answering the request itself ends the exchange and takes
1463 /// the stream with it; one answering an update does not, and nothing in
1464 /// the message tells the two apart.
1465 pub fn owes_update_failure(&self, request_id: VarInt) -> bool {
1466 self.owed_update_failures.contains(&request_id.into_inner())
1467 }
1468
1469 pub fn session_state(&self) -> SessionState {
1470 self.session.state()
1471 }
1472
1473 pub fn goaway_uri(&self) -> Option<&[u8]> {
1474 self.goaway_uri.as_deref()
1475 }
1476
1477 pub fn active_subscription_count(&self) -> usize {
1478 self.subscriptions.len()
1479 }
1480
1481 pub fn active_fetch_count(&self) -> usize {
1482 self.fetches.len()
1483 }
1484
1485 pub fn active_subscribe_namespace_count(&self) -> usize {
1486 self.subscribe_namespaces.len()
1487 }
1488
1489 pub fn active_subscribe_tracks_count(&self) -> usize {
1490 self.subscribe_tracks.len()
1491 }
1492
1493 pub fn active_publish_namespace_count(&self) -> usize {
1494 self.publish_namespaces.len()
1495 }
1496
1497 pub fn active_track_status_count(&self) -> usize {
1498 self.track_statuses.len()
1499 }
1500
1501 pub fn active_publish_count(&self) -> usize {
1502 self.publishes.len()
1503 }
1504
1505 /// How many Request IDs the peer has spent on this session.
1506 ///
1507 /// Nothing here removes an entry, so this only grows. A responder that
1508 /// wants a ceiling on peer-created state has to impose one itself — see
1509 /// the note on [`receive_request_on_stream`](Self::receive_request_on_stream).
1510 pub fn peer_request_count(&self) -> usize {
1511 self.peer_request_ids.len()
1512 }
1513
1514 // -- Session lifecycle ------------------------------------------
1515
1516 pub fn connect(&mut self) -> Result<(), EndpointError> {
1517 self.session.on_connect()?;
1518 Ok(())
1519 }
1520
1521 pub fn close(&mut self) -> Result<(), EndpointError> {
1522 self.session.on_close()?;
1523 Ok(())
1524 }
1525
1526 // -- Unified SETUP ----------------------------------------------
1527
1528 /// Generate a SETUP message. Both client and server use the same message
1529 /// type; only the role (and the order of send/receive) distinguishes them.
1530 pub fn send_setup(
1531 &mut self,
1532 options: Vec<KeyValuePair>,
1533 ) -> Result<ControlMessage, EndpointError> {
1534 let msg = Setup { options };
1535 setup::validate_setup(&msg, self.role)?;
1536 self.advertised_max_request_updates = setup_varint(&msg.options, MAX_REQUEST_UPDATES);
1537 self.advertised_max_filter_ranges = setup_varint(&msg.options, MAX_FILTER_RANGES);
1538 Ok(ControlMessage::Setup(msg))
1539 }
1540
1541 /// Process an incoming SETUP message. Transitions the session to Active.
1542 pub fn receive_setup(&mut self, msg: &Setup) -> Result<(), EndpointError> {
1543 setup::validate_setup(msg, self.peer_role())?;
1544 self.session.on_setup_complete()?;
1545 Ok(())
1546 }
1547
1548 // -- GoAway -----------------------------------------------------
1549
1550 pub fn receive_goaway(&mut self, msg: &GoAway) -> Result<(), EndpointError> {
1551 // Draft-19 Section 10.4: "If a server receives a GOAWAY with a
1552 // non-zero New Session URI Length it MUST close the session with a
1553 // PROTOCOL_VIOLATION." Migration is something a server offers a
1554 // client, never the other way round, so the URI is refused here rather
1555 // than stored and later followed.
1556 if self.role == Role::Server && !msg.new_session_uri.is_empty() {
1557 return Err(self.fail_session(EndpointError::GoAwayUriAtServer));
1558 }
1559 // Draft-19 Section 10.4: "The endpoint MUST close the session with a
1560 // PROTOCOL_VIOLATION (Section 3.5) if it receives more than one GOAWAY on the
1561 // control stream or on a single request stream." Draining is reached
1562 // from nowhere else - `on_goaway` is its only entry and this method is
1563 // that method's only caller - so the session state is the record of
1564 // the first GOAWAY having arrived. This is the control-stream half;
1565 // the per-stream half is in `receive_goaway_on_request_stream`.
1566 if self.session.state() == SessionState::Draining {
1567 return Err(self.fail_session(EndpointError::RepeatedGoAway));
1568 }
1569 self.session.on_goaway()?;
1570 self.goaway_uri = Some(msg.new_session_uri.clone());
1571 Ok(())
1572 }
1573
1574 /// Record that the session is over because the peer broke a rule the draft
1575 /// answers with a session close, and hand the error back unchanged.
1576 ///
1577 /// The state move is what makes the violation stick: every request entry
1578 /// point goes through [`require_active_or_err`](Self::require_active_or_err),
1579 /// so a caller that ignores the returned error still cannot start anything
1580 /// new. The close on the wire is the connection layer's job — see
1581 /// [`EndpointError::session_error_code`] for the code it should use.
1582 fn fail_session(&mut self, err: EndpointError) -> EndpointError {
1583 // `on_close` accepts SetupExchange, Active and Draining. A violation
1584 // seen in Connecting or Closed leaves the state machine alone: there
1585 // is no session to close, and the error itself is still the answer.
1586 //
1587 // SetupExchange is in that set because the Termination section says
1588 // "The Transport Session can be terminated at any point", and the
1589 // Setup exchange is a point. So a violation caught while the setup is
1590 // still in flight does close the session, and the discarded result is
1591 // safe because that is one of the states `on_close` accepts.
1592 let _ = self.session.on_close();
1593 err
1594 }
1595
1596 fn require_active_or_err(&self) -> Result<(), EndpointError> {
1597 match self.session.state() {
1598 SessionState::Active => Ok(()),
1599 SessionState::Draining => Err(EndpointError::Draining),
1600 _ => Err(EndpointError::NotActive),
1601 }
1602 }
1603
1604 // -- Subscribe flow ---------------------------------------------
1605
1606 pub fn subscribe(
1607 &mut self,
1608 track_namespace: TrackNamespace,
1609 track_name: Vec<u8>,
1610 parameters: Vec<KeyValuePair>,
1611 ) -> Result<(VarInt, ControlMessage), EndpointError> {
1612 self.require_active_or_err()?;
1613 let req_id = self.request_ids.allocate()?;
1614
1615 let mut sm = SubscriptionStateMachine::new();
1616 sm.on_subscribe_sent()?;
1617 self.subscriptions.insert(req_id.into_inner(), sm);
1618 // The track is recorded here because this is the only place it is
1619 // known: a SUBSCRIBE_OK names an alias and not the track it is for.
1620 self.track_bindings.insert(
1621 req_id.into_inner(),
1622 TrackBinding {
1623 namespace: track_namespace.clone(),
1624 name: track_name.clone(),
1625 alias: None,
1626 kind: BindingKind::Subscribe,
1627 },
1628 );
1629
1630 let msg = ControlMessage::Subscribe(Subscribe {
1631 request_id: req_id,
1632 track_namespace,
1633 track_name,
1634 parameters,
1635 });
1636 Ok((req_id, msg))
1637 }
1638
1639 /// Process an incoming SUBSCRIBE_OK. Draft-19: no request_id on wire; the
1640 /// caller supplies the `request_id` of the bidi stream on which the
1641 /// response arrived.
1642 pub fn receive_subscribe_ok(
1643 &mut self,
1644 request_id: VarInt,
1645 msg: &SubscribeOk,
1646 ) -> Result<(), EndpointError> {
1647 let id = request_id.into_inner();
1648 if !self.subscriptions.contains_key(&id) {
1649 return Err(EndpointError::UnknownRequest(id));
1650 }
1651 let alias = msg.track_alias.into_inner();
1652 // Judged before the transition, so that the subscription this message
1653 // is about is not yet Established and cannot be found as its own
1654 // conflict, and so that a refused SUBSCRIBE_OK leaves no alias behind.
1655 if let Some(conflict) = self.conflicting_alias_for_subscribe_ok(id, alias) {
1656 return Err(self.fail_session(conflict));
1657 }
1658 let sm = self.subscriptions.get_mut(&id).expect("checked above");
1659 sm.on_subscribe_ok()?;
1660 if let Some(binding) = self.track_bindings.get_mut(&id) {
1661 binding.alias = Some(alias);
1662 }
1663 Ok(())
1664 }
1665
1666 /// Take one of the REQUEST_UPDATE credits this endpoint advertised for
1667 /// `id`'s stream.
1668 ///
1669 /// Section 10.3.1.7 puts the limit on the number *outstanding*, so the
1670 /// count is a running balance rather than a total: it goes up on each
1671 /// REQUEST_UPDATE received and down on each REQUEST_OK or REQUEST_ERROR
1672 /// this endpoint writes back, and a peer that keeps pace never reaches the
1673 /// ceiling however many updates it sends.
1674 ///
1675 /// # Errors
1676 ///
1677 /// [`EndpointError::TooManyRequestUpdates`], which answers
1678 /// `Some(TooManyRequestUpdates)` - its own close code, not the general
1679 /// PROTOCOL_VIOLATION - and the session is failed before it returns.
1680 fn spend_update_credit(&mut self, id: u64) -> Result<(), EndpointError> {
1681 let limit = self.advertised_max_request_updates;
1682 if limit == 0 {
1683 return Ok(());
1684 }
1685 let outstanding = self.outstanding_peer_updates.entry(id).or_insert(0);
1686 if *outstanding >= limit {
1687 return Err(self.fail_session(EndpointError::TooManyRequestUpdates(id, limit)));
1688 }
1689 *outstanding += 1;
1690 Ok(())
1691 }
1692
1693 /// Give back the credit a REQUEST_OK or REQUEST_ERROR restores.
1694 ///
1695 /// Called for every response this endpoint writes, whether or not the
1696 /// stream ever carried an update: a stream with no outstanding updates has
1697 /// nothing to restore and the saturating subtraction says so, which is
1698 /// cheaper than deciding first whether the response is answering an update
1699 /// or the original request.
1700 fn restore_update_credit(&mut self, id: u64) {
1701 if let Some(outstanding) = self.outstanding_peer_updates.get_mut(&id) {
1702 *outstanding = outstanding.saturating_sub(1);
1703 }
1704 }
1705
1706 /// Process a REQUEST_UPDATE that arrived on the bidi request stream
1707 /// identified by `request_id`.
1708 ///
1709 /// Draft-19 Section 10.9: "The sender of a request (SUBSCRIBE, PUBLISH,
1710 /// FETCH, PUBLISH_NAMESPACE, SUBSCRIBE_NAMESPACE, SUBSCRIBE_TRACKS) can
1711 /// later send a REQUEST_UPDATE on the same bidi stream as the request to
1712 /// modify it. A subscriber can also send REQUEST_UPDATE to modify
1713 /// parameters of a subscription established with PUBLISH." Anything else
1714 /// "MUST close the session with a PROTOCOL_VIOLATION", which is what
1715 /// [`EndpointError::UnexpectedRequestUpdate`] carries — TRACK_STATUS most
1716 /// of all, since Section 10.14 says outright that "the subscriber cannot
1717 /// send REQUEST_UPDATE".
1718 ///
1719 /// The message carries a Request ID of its own and the stream carries one
1720 /// too. They name the same request when the peer is conforming; a
1721 /// disagreement means the update was sent on a stream that is not its
1722 /// request's, which is the same violation, so it is refused rather than
1723 /// silently resolved to one of the two.
1724 ///
1725 /// Only SUBSCRIBE-established subscriptions have a state-machine event for
1726 /// this. That is not an omission: an update changes a request's parameters
1727 /// and not its lifecycle, so for the other five kinds the update is a
1728 /// self-transition with nothing to record.
1729 pub fn receive_request_update(
1730 &mut self,
1731 request_id: VarInt,
1732 msg: &RequestUpdate,
1733 ) -> Result<(), EndpointError> {
1734 let id = request_id.into_inner();
1735 if msg.request_id.into_inner() != id {
1736 return Err(self.fail_session(EndpointError::UnexpectedRequestUpdate(
1737 msg.request_id.into_inner(),
1738 )));
1739 }
1740 self.spend_update_credit(id)?;
1741 // The two cases are about who is sending, and only one of them is
1742 // about which request. Case one is the peer updating a request the
1743 // peer made, whatever its kind; case two is a subscriber updating a
1744 // subscription this endpoint established with PUBLISH. A SUBSCRIBE, a
1745 // FETCH or a namespace request this endpoint made is in neither, and
1746 // an update on one is a violation rather than a request to apply.
1747 //
1748 // They are not symmetrical about timing either. Case one allows an
1749 // update "later" and says nothing about the answer, so a request of
1750 // the peer's may be updated before this endpoint has answered it.
1751 // Case two rests on the subscription existing, and Section 5.1 says
1752 // when it does: "Once either of these sequences is successful, the
1753 // subscription moves to the Established state and can be updated by
1754 // the subscriber using REQUEST_UPDATE." A PUBLISH still waiting for
1755 // its answer is Pending, and an update on one is outside both cases.
1756 //
1757 // Checked before the state machine below moves, so a session that is
1758 // closing does not leave a subscription updated on the way out.
1759 let one_of_the_two_cases = self.inbound_requests.contains_key(&id)
1760 || self.binding_is_established(id, BindingKind::Publish);
1761 if !one_of_the_two_cases {
1762 return Err(self.fail_session(EndpointError::UnexpectedRequestUpdate(id)));
1763 }
1764 let updatable = if let Some(sm) = self.subscriptions.get_mut(&id) {
1765 sm.on_subscribe_update()?;
1766 true
1767 } else {
1768 self.publishes.contains_key(&id)
1769 || self.fetches.contains_key(&id)
1770 || self.subscribe_namespaces.contains_key(&id)
1771 || self.subscribe_tracks.contains_key(&id)
1772 || self.publish_namespaces.contains_key(&id)
1773 };
1774 if !updatable {
1775 return Err(self.fail_session(EndpointError::UnexpectedRequestUpdate(id)));
1776 }
1777 *self.unanswered_peer_updates.entry(id).or_insert(0) += 1;
1778
1779 // The ceiling is on what the request carries after the update, so the
1780 // update is merged into the set in force and the whole set measured.
1781 // Recorded rather than refused for the reason the request itself is:
1782 // the answer is a REQUEST_ERROR, which names a Request ID.
1783 let mut in_force = self.peer_request_filters.remove(&id).unwrap_or_default();
1784 apply_filter_update(&mut in_force, &msg.parameters);
1785 if let Some(rejection) = self.filter_verdict(&in_force) {
1786 self.peer_filter_rejections.insert(id, rejection);
1787 }
1788 self.peer_request_filters.insert(id, in_force);
1789
1790 // Section 10.9.2 gives an update one thing to move that no draft before
1791 // draft-18 lets a request change: "A subscriber can update the Track
1792 // Namespace Prefix of an established SUBSCRIBE_NAMESPACE or
1793 // SUBSCRIBE_TRACKS by including the TRACK_NAMESPACE_PREFIX parameter
1794 // (Section 10.2.19) in a REQUEST_UPDATE."
1795 //
1796 // The two kinds keep their own ground -- "The overlap restriction
1797 // applies independently per type" -- so which set the new prefix is
1798 // weighed against is decided by what the peer opened the stream with.
1799 //
1800 // A request of any other kind has no prefix for the parameter to move,
1801 // and nothing in this draft says what to do with the parameter when it
1802 // turns up on one, so it is left alone rather than guessed at.
1803 if let Some(prefix) = updated_prefix(&msg.parameters) {
1804 let namespace = matches!(
1805 self.inbound_requests.get(&id),
1806 Some(ControlMessage::SubscribeNamespace(_))
1807 );
1808 let tracks =
1809 matches!(self.inbound_requests.get(&id), Some(ControlMessage::SubscribeTracks(_)));
1810 if namespace || tracks {
1811 let collides = if tracks {
1812 self.peer_tracks_overlap(&prefix, Some(id))
1813 } else {
1814 self.peer_namespace_overlap(&prefix, Some(id))
1815 };
1816 match collides {
1817 Some(established) => {
1818 self.overlapping_prefix_updates.insert(id, established);
1819 }
1820 // A later update clears an earlier one's verdict along with
1821 // its prefix, which is what a receiver "applying only the
1822 // cumulative result" is entitled to do.
1823 None => {
1824 self.overlapping_prefix_updates.remove(&id);
1825 }
1826 }
1827 self.updated_namespace_prefixes.insert(id, prefix);
1828 }
1829 }
1830 Ok(())
1831 }
1832
1833 pub fn receive_publish_done(
1834 &mut self,
1835 request_id: VarInt,
1836 _msg: &PublishDone,
1837 ) -> Result<(), EndpointError> {
1838 let id = request_id.into_inner();
1839 let sm = self.subscriptions.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
1840 sm.on_publish_done()?;
1841 Ok(())
1842 }
1843
1844 // -- Fetch flow -------------------------------------------------
1845
1846 /// `parameters` are the request's own, as they are on every other request
1847 /// this endpoint makes. FETCH carried an empty list on all five of these
1848 /// drafts while SUBSCRIBE, TRACK_STATUS, PUBLISH_NAMESPACE and PUBLISH
1849 /// took the caller's, which made it the only one an application could not
1850 /// attach an authorization token to.
1851 #[allow(clippy::too_many_arguments)]
1852 pub fn fetch(
1853 &mut self,
1854 track_namespace: TrackNamespace,
1855 track_name: Vec<u8>,
1856 start_group: VarInt,
1857 start_object: VarInt,
1858 end_group: VarInt,
1859 end_object: VarInt,
1860 parameters: Vec<KeyValuePair>,
1861 ) -> Result<(VarInt, ControlMessage), EndpointError> {
1862 self.require_active_or_err()?;
1863 let req_id = self.request_ids.allocate()?;
1864
1865 let mut sm = FetchStateMachine::new();
1866 sm.on_fetch_sent()?;
1867 self.fetches.insert(req_id.into_inner(), sm);
1868 // Before the two of them are moved into the message below: a fetch
1869 // that has left no track behind is one no withdrawal can name.
1870 self.fetch_tracks.insert(
1871 req_id.into_inner(),
1872 FetchTrack { namespace: track_namespace.clone(), name: track_name.clone() },
1873 );
1874
1875 let msg = ControlMessage::Fetch(Fetch {
1876 request_id: req_id,
1877 fetch_type: FetchType::Standalone,
1878 fetch_payload: FetchPayload::Standalone {
1879 track_namespace,
1880 track_name,
1881 start_group,
1882 start_object,
1883 end_group,
1884 end_object,
1885 },
1886 parameters,
1887 });
1888 Ok((req_id, msg))
1889 }
1890
1891 /// Send a Relative Joining Fetch (Fetch Type 0x2).
1892 ///
1893 /// `joining_start` counts groups back from the subscription's Largest
1894 /// Group. To name the group directly, use
1895 /// [`absolute_joining_fetch`](Self::absolute_joining_fetch).
1896 ///
1897 /// `parameters` are the request's own, as they are on every other request
1898 /// this endpoint makes.
1899 pub fn joining_fetch(
1900 &mut self,
1901 joining_request_id: VarInt,
1902 joining_start: VarInt,
1903 parameters: Vec<KeyValuePair>,
1904 ) -> Result<(VarInt, ControlMessage), EndpointError> {
1905 self.joining_fetch_of_type(
1906 FetchType::RelativeJoining,
1907 joining_request_id,
1908 joining_start,
1909 parameters,
1910 )
1911 }
1912
1913 /// Send an Absolute Joining Fetch (Fetch Type 0x3).
1914 ///
1915 /// Draft-19 Section 10.12.2.1: "For an Absolute Joining Fetch, the
1916 /// publisher sets the Start Location to {Joining Start, 0}." So
1917 /// `joining_start` is the group to begin at, not an offset — which is what
1918 /// an application that knows the group it wants actually has. Expressing
1919 /// it as a relative fetch would need the Largest Group, which the
1920 /// subscriber may not know.
1921 pub fn absolute_joining_fetch(
1922 &mut self,
1923 joining_request_id: VarInt,
1924 joining_start: VarInt,
1925 parameters: Vec<KeyValuePair>,
1926 ) -> Result<(VarInt, ControlMessage), EndpointError> {
1927 self.joining_fetch_of_type(
1928 FetchType::AbsoluteJoining,
1929 joining_request_id,
1930 joining_start,
1931 parameters,
1932 )
1933 }
1934
1935 fn joining_fetch_of_type(
1936 &mut self,
1937 fetch_type: FetchType,
1938 joining_request_id: VarInt,
1939 joining_start: VarInt,
1940 parameters: Vec<KeyValuePair>,
1941 ) -> Result<(VarInt, ControlMessage), EndpointError> {
1942 self.require_active_or_err()?;
1943 let req_id = self.request_ids.allocate()?;
1944
1945 let mut sm = FetchStateMachine::new();
1946 sm.on_fetch_sent()?;
1947 self.fetches.insert(req_id.into_inner(), sm);
1948 // Resolved through the join, once, here. A Joining Fetch names no
1949 // track; the publisher takes the Track Namespace and Track Name from
1950 // the subscription it names, so that is the track. A Request ID this
1951 // session holds no track for leaves the fetch out of the record
1952 // entirely rather than putting a guess in it.
1953 if let Some(binding) = self.track_bindings.get(&joining_request_id.into_inner()) {
1954 let track =
1955 FetchTrack { namespace: binding.namespace.clone(), name: binding.name.clone() };
1956 self.fetch_tracks.insert(req_id.into_inner(), track);
1957 }
1958
1959 let msg = ControlMessage::Fetch(Fetch {
1960 request_id: req_id,
1961 fetch_type,
1962 fetch_payload: FetchPayload::Joining { joining_request_id, joining_start },
1963 parameters,
1964 });
1965 Ok((req_id, msg))
1966 }
1967
1968 pub fn receive_fetch_ok(
1969 &mut self,
1970 request_id: VarInt,
1971 _msg: &message::FetchOk,
1972 ) -> Result<(), EndpointError> {
1973 let id = request_id.into_inner();
1974 let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
1975 sm.on_fetch_ok()?;
1976 Ok(())
1977 }
1978
1979 pub fn on_fetch_stream_fin(&mut self, request_id: VarInt) -> Result<(), EndpointError> {
1980 let id = request_id.into_inner();
1981 let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
1982 sm.on_stream_fin()?;
1983 Ok(())
1984 }
1985
1986 pub fn on_fetch_stream_reset(&mut self, request_id: VarInt) -> Result<(), EndpointError> {
1987 let id = request_id.into_inner();
1988 let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
1989 sm.on_stream_reset()?;
1990 Ok(())
1991 }
1992
1993 // -- Subscribe Namespace flow -----------------------------------
1994
1995 pub fn subscribe_namespace(
1996 &mut self,
1997 namespace_prefix: TrackNamespace,
1998 parameters: Vec<KeyValuePair>,
1999 ) -> Result<(VarInt, ControlMessage), EndpointError> {
2000 self.require_active_or_err()?;
2001 let req_id = self.request_ids.allocate()?;
2002
2003 let mut sm = SubscribeNamespaceStateMachine::new();
2004 sm.on_subscribe_namespace_sent()?;
2005 self.subscribe_namespaces.insert(req_id.into_inner(), sm);
2006
2007 let msg = ControlMessage::SubscribeNamespace(SubscribeNamespace {
2008 request_id: req_id,
2009 namespace_prefix,
2010 parameters,
2011 });
2012 Ok((req_id, msg))
2013 }
2014
2015 // -- Subscribe Tracks flow (new in draft-18) --------------------
2016
2017 pub fn subscribe_tracks(
2018 &mut self,
2019 namespace_prefix: TrackNamespace,
2020 parameters: Vec<KeyValuePair>,
2021 ) -> Result<(VarInt, ControlMessage), EndpointError> {
2022 self.require_active_or_err()?;
2023 let req_id = self.request_ids.allocate()?;
2024
2025 // Reuse the SubscribeNamespace state machine — the lifecycle is the
2026 // same (request → ok/error → done) and adding a parallel state
2027 // machine purely to disambiguate would be churn.
2028 let mut sm = SubscribeNamespaceStateMachine::new();
2029 sm.on_subscribe_namespace_sent()?;
2030 self.subscribe_tracks.insert(req_id.into_inner(), sm);
2031
2032 let msg = ControlMessage::SubscribeTracks(SubscribeTracks {
2033 request_id: req_id,
2034 namespace_prefix,
2035 parameters,
2036 });
2037 Ok((req_id, msg))
2038 }
2039
2040 // -- Publish Namespace flow -------------------------------------
2041
2042 pub fn publish_namespace(
2043 &mut self,
2044 track_namespace: TrackNamespace,
2045 parameters: Vec<KeyValuePair>,
2046 ) -> Result<(VarInt, ControlMessage), EndpointError> {
2047 self.require_active_or_err()?;
2048 let req_id = self.request_ids.allocate()?;
2049
2050 let mut sm = PublishNamespaceStateMachine::new();
2051 sm.on_publish_namespace_sent()?;
2052 self.publish_namespaces.insert(req_id.into_inner(), sm);
2053
2054 let msg = ControlMessage::PublishNamespace(PublishNamespace {
2055 request_id: req_id,
2056 track_namespace,
2057 parameters,
2058 });
2059 Ok((req_id, msg))
2060 }
2061
2062 // -- Track Status flow ------------------------------------------
2063
2064 pub fn track_status(
2065 &mut self,
2066 track_namespace: TrackNamespace,
2067 track_name: Vec<u8>,
2068 parameters: Vec<KeyValuePair>,
2069 ) -> Result<(VarInt, ControlMessage), EndpointError> {
2070 self.require_active_or_err()?;
2071 let req_id = self.request_ids.allocate()?;
2072 let mut sm = TrackStatusStateMachine::new();
2073 sm.on_track_status_sent()?;
2074 self.track_statuses.insert(req_id.into_inner(), sm);
2075
2076 let msg = ControlMessage::TrackStatus(message::TrackStatus {
2077 request_id: req_id,
2078 track_namespace,
2079 track_name,
2080 parameters,
2081 });
2082 Ok((req_id, msg))
2083 }
2084
2085 // -- Publish flow (publisher side) ------------------------------
2086
2087 pub fn publish(
2088 &mut self,
2089 track_namespace: TrackNamespace,
2090 track_name: Vec<u8>,
2091 track_alias: VarInt,
2092 parameters: Vec<KeyValuePair>,
2093 track_properties: Vec<KeyValuePair>,
2094 ) -> Result<(VarInt, ControlMessage), EndpointError> {
2095 self.require_active_or_err()?;
2096 // Section 11.1: "The same Track Alias MUST NOT be used by a publisher to refer to
2097 // two different Tracks simultaneously in the same session." Refused before the
2098 // Request ID is allocated, so a refusal spends nothing.
2099 let alias = track_alias.into_inner();
2100 if let Some(refusal) = self.alias_held_elsewhere(alias, &track_namespace, &track_name) {
2101 return Err(refusal);
2102 }
2103 let req_id = self.request_ids.allocate()?;
2104 let mut sm = PublishStateMachine::new();
2105 sm.on_publish_sent()?;
2106 self.publishes.insert(req_id.into_inner(), sm);
2107 // The alias and the track travel together in a PUBLISH, so the binding
2108 // is complete the moment the message is built.
2109 self.track_bindings.insert(
2110 req_id.into_inner(),
2111 TrackBinding {
2112 namespace: track_namespace.clone(),
2113 name: track_name.clone(),
2114 alias: Some(alias),
2115 kind: BindingKind::Publish,
2116 },
2117 );
2118
2119 let msg = ControlMessage::Publish(Publish {
2120 request_id: req_id,
2121 track_namespace,
2122 track_name,
2123 track_alias,
2124 parameters,
2125 track_properties,
2126 });
2127 Ok((req_id, msg))
2128 }
2129
2130 pub fn send_publish_done(
2131 &mut self,
2132 request_id: VarInt,
2133 status_code: VarInt,
2134 stream_count: VarInt,
2135 reason_phrase: Vec<u8>,
2136 ) -> Result<ControlMessage, EndpointError> {
2137 let id = request_id.into_inner();
2138 // Before the state machine moves, so an ending under the wrong status
2139 // leaves the publication where it was. A PUBLISH this endpoint sent is
2140 // ended here rather than through the response path, and a refused
2141 // update on it owes the same ending as one on a peer's SUBSCRIBE.
2142 self.require_update_failure_status(id, status_code)?;
2143 let sm = self.publishes.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2144 sm.on_publish_done_sent()?;
2145 Ok(ControlMessage::PublishDone(PublishDone { status_code, stream_count, reason_phrase }))
2146 }
2147
2148 // -- Consolidated responses (per-bidi-stream routing) -----------
2149
2150 /// Process an incoming REQUEST_OK on the bidi stream identified by
2151 /// `request_id`. Draft-19: PUBLISH_OK is a REQUEST_OK alias, so this
2152 /// handler also resolves outstanding PUBLISH requests.
2153 ///
2154 /// # Track Properties are refused except on TRACK_STATUS_OK
2155 ///
2156 /// Draft-19 Section 10.5: Track Properties "are populated in
2157 /// TRACK_STATUS_OK; they are empty in PUBLISH_OK, REQUEST_UPDATE_OK,
2158 /// SUBSCRIBE_NAMESPACE_OK and PUBLISH_NAMESPACE_OK. If an endpoint
2159 /// receives Track Properties in one of these messages it MUST close the
2160 /// session with a PROTOCOL_VIOLATION." The codec cannot make that check —
2161 /// REQUEST_OK is one wire form and only the request stream says which of
2162 /// the five shapes it is. This is the layer that knows, because finding
2163 /// the request id in one of the maps below is what names the shape.
2164 pub fn receive_request_ok(
2165 &mut self,
2166 request_id: VarInt,
2167 msg: &RequestOk,
2168 ) -> Result<(), EndpointError> {
2169 let id = request_id.into_inner();
2170 if !msg.track_properties.is_empty() && !self.track_statuses.contains_key(&id) {
2171 return Err(self.fail_session(EndpointError::TrackPropertiesOnNonTrackStatus(id)));
2172 }
2173 if let Some(sm) = self.publishes.get_mut(&id) {
2174 sm.on_publish_ok()?;
2175 return Ok(());
2176 }
2177 if let Some(sm) = self.subscribe_namespaces.get_mut(&id) {
2178 sm.on_subscribe_namespace_ok()?;
2179 return Ok(());
2180 }
2181 if let Some(sm) = self.subscribe_tracks.get_mut(&id) {
2182 sm.on_subscribe_namespace_ok()?;
2183 return Ok(());
2184 }
2185 if let Some(sm) = self.publish_namespaces.get_mut(&id) {
2186 sm.on_publish_namespace_ok()?;
2187 return Ok(());
2188 }
2189 if let Some(sm) = self.track_statuses.get_mut(&id) {
2190 sm.on_track_status_ok()?;
2191 return Ok(());
2192 }
2193 Err(EndpointError::UnknownRequest(id))
2194 }
2195
2196 /// Process an incoming REQUEST_ERROR on the bidi stream identified by
2197 /// `request_id`.
2198 pub fn receive_request_error(
2199 &mut self,
2200 request_id: VarInt,
2201 msg: &RequestError,
2202 ) -> Result<(), EndpointError> {
2203 let id = request_id.into_inner();
2204 if let Some(redirect) = &msg.redirect {
2205 // Draft-19 Section 10.6.1: "If a server receives a Redirect with a
2206 // non-zero Connect URI Length it MUST close the session with a
2207 // PROTOCOL_VIOLATION." Left unchecked, a Redirect sends a server
2208 // chasing a URI a client picked.
2209 if self.role == Role::Server && !redirect.connect_uri.is_empty() {
2210 return Err(self.fail_session(EndpointError::RedirectUriAtServer));
2211 }
2212 // Same section: "Track Name is not meaningful for namespace-scoped
2213 // requests (SUBSCRIBE_NAMESPACE, PUBLISH_NAMESPACE,
2214 // SUBSCRIBE_TRACKS) and MUST be empty; an endpoint that receives a
2215 // non-empty Track Name in a Redirect for a namespace-scoped request
2216 // MUST close the session with a PROTOCOL_VIOLATION." Which request
2217 // this answers is known only here, from the map the stream's id is
2218 // in.
2219 let namespace_scoped = self.subscribe_namespaces.contains_key(&id)
2220 || self.subscribe_tracks.contains_key(&id)
2221 || self.publish_namespaces.contains_key(&id);
2222 if namespace_scoped && !redirect.track_name.is_empty() {
2223 return Err(
2224 self.fail_session(EndpointError::RedirectTrackNameOnNamespaceRequest(id))
2225 );
2226 }
2227 }
2228 if let Some(sm) = self.subscriptions.get_mut(&id) {
2229 sm.on_subscribe_error()?;
2230 return Ok(());
2231 }
2232 if let Some(sm) = self.fetches.get_mut(&id) {
2233 sm.on_fetch_error()?;
2234 return Ok(());
2235 }
2236 if let Some(sm) = self.publishes.get_mut(&id) {
2237 sm.on_publish_error()?;
2238 return Ok(());
2239 }
2240 if let Some(sm) = self.subscribe_namespaces.get_mut(&id) {
2241 sm.on_subscribe_namespace_error()?;
2242 return Ok(());
2243 }
2244 if let Some(sm) = self.subscribe_tracks.get_mut(&id) {
2245 sm.on_subscribe_namespace_error()?;
2246 return Ok(());
2247 }
2248 if let Some(sm) = self.publish_namespaces.get_mut(&id) {
2249 sm.on_publish_namespace_error()?;
2250 return Ok(());
2251 }
2252 if let Some(sm) = self.track_statuses.get_mut(&id) {
2253 sm.on_track_status_error()?;
2254 return Ok(());
2255 }
2256 Err(EndpointError::UnknownRequest(id))
2257 }
2258
2259 /// Record that a request was cancelled at its stream.
2260 ///
2261 /// This draft withdraws a request by terminating the bidirectional stream
2262 /// it was made on rather than by sending a message. Section 3.3.3: "Once
2263 /// a request stream has been opened, the request MAY be cancelled by either
2264 /// endpoint. Senders cancel requests if the response is no longer of
2265 /// interest; Receivers cancel requests if they are unable to or choose not
2266 /// to respond."
2267 ///
2268 /// Both of those reach here. A request the peer opened and one this
2269 /// endpoint opened share a map and cannot collide, because their Request
2270 /// IDs have opposite least significant bits, so one method records a cancel
2271 /// from whichever side performed it.
2272 ///
2273 /// The request moves to its end state, which is what makes the record worth
2274 /// keeping: a response arriving afterwards is refused rather than applied to
2275 /// a request that is over. The Request ID is not released — nothing here
2276 /// reuses one — and the per-request bookkeeping keyed by it is left alone,
2277 /// since a cancelled request's stream carries nothing more.
2278 ///
2279 /// Nothing is written on the wire. The reset that goes with this is
2280 /// [`Connection::cancel_request_stream`], which calls this first and
2281 /// terminates the stream only if it returns `Ok`.
2282 ///
2283 /// # Errors
2284 ///
2285 /// [`EndpointError::UnknownRequest`] when no request of any kind carries
2286 /// that id, and the kind's own `InvalidTransition` when the request has not
2287 /// been written yet.
2288 ///
2289 /// [`Connection::cancel_request_stream`]: crate::draft19::connection::Connection::cancel_request_stream
2290 pub fn cancel_request(&mut self, request_id: VarInt) -> Result<(), EndpointError> {
2291 let id = request_id.into_inner();
2292 if let Some(sm) = self.subscriptions.get_mut(&id) {
2293 sm.on_request_cancelled()?;
2294 return Ok(());
2295 }
2296 if let Some(sm) = self.fetches.get_mut(&id) {
2297 sm.on_request_cancelled()?;
2298 return Ok(());
2299 }
2300 if let Some(sm) = self.publishes.get_mut(&id) {
2301 sm.on_request_cancelled()?;
2302 return Ok(());
2303 }
2304 if let Some(sm) = self.subscribe_namespaces.get_mut(&id) {
2305 sm.on_request_cancelled()?;
2306 return Ok(());
2307 }
2308 if let Some(sm) = self.subscribe_tracks.get_mut(&id) {
2309 sm.on_request_cancelled()?;
2310 return Ok(());
2311 }
2312 if let Some(sm) = self.publish_namespaces.get_mut(&id) {
2313 sm.on_request_cancelled()?;
2314 return Ok(());
2315 }
2316 if let Some(sm) = self.track_statuses.get_mut(&id) {
2317 sm.on_request_cancelled()?;
2318 return Ok(());
2319 }
2320 Err(EndpointError::UnknownRequest(id))
2321 }
2322
2323 // -- PublishSkipped / Namespace announcements -------------------
2324
2325 pub fn receive_namespace(&mut self, _msg: &message::Namespace) -> Result<(), EndpointError> {
2326 Ok(())
2327 }
2328
2329 pub fn receive_namespace_done(
2330 &mut self,
2331 _msg: &message::NamespaceDone,
2332 ) -> Result<(), EndpointError> {
2333 Ok(())
2334 }
2335
2336 pub fn receive_publish_skipped(&mut self, _msg: &PublishSkipped) -> Result<(), EndpointError> {
2337 Ok(())
2338 }
2339
2340 // -- Unified message dispatch -----------------------------------
2341
2342 /// Dispatch a message that arrived on the control stream.
2343 ///
2344 /// Draft-19 Table 5 gives every message a Stream value, and only two of
2345 /// them name the control stream: SETUP is "Control", GOAWAY is "Control,
2346 /// Request". Everything else is "Request", so this method's job is to take
2347 /// those two and refuse the messages that identify a request they have no
2348 /// stream to name.
2349 ///
2350 /// Four are refused for that reason. REQUEST_UPDATE modifies the request
2351 /// its stream carries (Section 10.9). NAMESPACE and NAMESPACE_DONE report
2352 /// namespaces on the SUBSCRIBE_NAMESPACE request stream that asked for them
2353 /// (Section 10.16 and Section 10.17), and PUBLISH_SKIPPED names a track
2354 /// that will not be published on the SUBSCRIBE_TRACKS stream that asked for
2355 /// it (Section 10.20). All four route through
2356 /// [`receive_response_on_stream`](Self::receive_response_on_stream), which
2357 /// has the request ID they need.
2358 pub fn receive_message(&mut self, msg: ControlMessage) -> Result<(), EndpointError> {
2359 match msg {
2360 ControlMessage::Setup(ref m) => self.receive_setup(m),
2361 ControlMessage::GoAway(ref m) => self.receive_goaway(m),
2362 ControlMessage::RequestUpdate(_) => {
2363 Err(self.fail_session(EndpointError::RequestUpdateOnControlStream))
2364 }
2365 // Refused, and the session left running. No draft states a close
2366 // for a message arriving on a stream it does not belong on — see
2367 // `session_error_code`, which answers `None` for them. A
2368 // control message carries its own length, so the next boundary is
2369 // known and a refused message costs the session nothing; that is
2370 // the answer `ResponseOnControlStream` below has always given.
2371 //
2372 // REQUEST_UPDATE above is the one that is not like these: Section
2373 // 10.9 does close over one that arrives outside the two cases it
2374 // names, so it keeps both the close and `fail_session`.
2375 ControlMessage::Namespace(_) => {
2376 Err(EndpointError::RequestMessageOnControlStream("NAMESPACE"))
2377 }
2378 ControlMessage::NamespaceDone(_) => {
2379 Err(EndpointError::RequestMessageOnControlStream("NAMESPACE_DONE"))
2380 }
2381 ControlMessage::PublishSkipped(_) => {
2382 Err(EndpointError::RequestMessageOnControlStream("PUBLISH_SKIPPED"))
2383 }
2384 ControlMessage::SubscribeOk(_)
2385 | ControlMessage::PublishDone(_)
2386 | ControlMessage::FetchOk(_)
2387 | ControlMessage::RequestOk(_)
2388 | ControlMessage::RequestError(_) => Err(EndpointError::ResponseOnControlStream),
2389 _ => Ok(()),
2390 }
2391 }
2392
2393 /// Hold the response half of a namespace-scoped request to the rule that
2394 /// its first message answers the request.
2395 ///
2396 /// Section 10.18 and Section 10.19 state it once each, for
2397 /// SUBSCRIBE_NAMESPACE and for SUBSCRIBE_TRACKS: "The publisher will
2398 /// respond with REQUEST_OK or REQUEST_ERROR on the response half of the
2399 /// stream. If the subscriber receives any message other than a REQUEST_OK
2400 /// or a REQUEST_ERROR as the first message on the response half of the
2401 /// stream, then it MUST close the session with a PROTOCOL_VIOLATION."
2402 /// Draft-18's own change log records it as new work rather than as a
2403 /// clarification, so drafts 17 and earlier are deliberately not held to it:
2404 /// they say nothing about which message comes first, and refusing one there
2405 /// would close a session over traffic those drafts permit.
2406 ///
2407 /// # Why only these two requests
2408 ///
2409 /// They are the two whose response half carries more than an answer.
2410 /// SUBSCRIBE_NAMESPACE goes on to carry NAMESPACE and NAMESPACE_DONE and
2411 /// SUBSCRIBE_TRACKS goes on to carry PUBLISH_SKIPPED, and those are exactly the
2412 /// messages that could arrive before the answer and be taken for it. A
2413 /// SUBSCRIBE has SUBSCRIBE_OK as its own first message and no such
2414 /// ambiguity, which is why the rule is written where it is.
2415 ///
2416 /// # What "first" is read from
2417 ///
2418 /// The request's own state machine. `Pending` means the request went out
2419 /// and nothing has come back, so it is the same question asked of the state
2420 /// rather than of a second counter that could disagree with it. A request
2421 /// this endpoint did not open, or one already answered, is not this rule's
2422 /// subject and passes through.
2423 ///
2424 /// # Errors
2425 ///
2426 /// [`EndpointError::ResponseBeforeTheFirstResponse`], which answers
2427 /// `Some(ProtocolViolation)`, and the session is failed before it returns.
2428 fn require_the_first_response_first(
2429 &mut self,
2430 id: u64,
2431 msg: &ControlMessage,
2432 ) -> Result<(), EndpointError> {
2433 let awaiting = [self.subscribe_namespaces.get(&id), self.subscribe_tracks.get(&id)]
2434 .into_iter()
2435 .flatten()
2436 .any(|sm| sm.state() == SubscribeNamespaceState::Pending);
2437 if !awaiting {
2438 return Ok(());
2439 }
2440 if matches!(msg, ControlMessage::RequestOk(_) | ControlMessage::RequestError(_)) {
2441 return Ok(());
2442 }
2443 let ty = msg.message_type();
2444 Err(self.fail_session(EndpointError::ResponseBeforeTheFirstResponse(id, ty)))
2445 }
2446
2447 /// Dispatch a message that arrived on the bidi request stream identified
2448 /// by `request_id`.
2449 ///
2450 /// Beyond the five responses this also takes the messages draft-19 Table 5
2451 /// places on a request stream without their being answers to it.
2452 ///
2453 /// REQUEST_UPDATE modifies the request the stream carries (Section 10.9).
2454 /// GOAWAY is listed "Control, Request" because "A GOAWAY MAY also be sent
2455 /// on a request stream to initiate migration of that individual request"
2456 /// (Section 10.4); draft-19 is the draft where its receive path matters,
2457 /// since it removed GOAWAY's Request ID and left one wire form for both
2458 /// places.
2459 ///
2460 /// NAMESPACE (0x8) and NAMESPACE_DONE (0xE) arrive on the
2461 /// SUBSCRIBE_NAMESPACE request stream that asked for the namespaces they
2462 /// report, and PUBLISH_SKIPPED (0xF) on the SUBSCRIBE_TRACKS stream that
2463 /// asked for the track it says will not be published. Table 5 marks all
2464 /// three "Request", so this is where they land; the control stream refuses
2465 /// them.
2466 pub fn receive_response_on_stream(
2467 &mut self,
2468 request_id: VarInt,
2469 msg: ControlMessage,
2470 ) -> Result<(), EndpointError> {
2471 self.require_the_first_response_first(request_id.into_inner(), &msg)?;
2472 match msg {
2473 ControlMessage::SubscribeOk(ref m) => self.receive_subscribe_ok(request_id, m),
2474 ControlMessage::PublishDone(ref m) => self.receive_publish_done(request_id, m),
2475 ControlMessage::FetchOk(ref m) => self.receive_fetch_ok(request_id, m),
2476 ControlMessage::RequestOk(ref m) => self.receive_request_ok(request_id, m),
2477 ControlMessage::RequestError(ref m) => self.receive_request_error(request_id, m),
2478 ControlMessage::RequestUpdate(ref m) => self.receive_request_update(request_id, m),
2479 ControlMessage::GoAway(ref m) => self.receive_goaway_on_request_stream(request_id, m),
2480 ControlMessage::Namespace(ref m) => self.receive_namespace(m),
2481 ControlMessage::NamespaceDone(ref m) => self.receive_namespace_done(m),
2482 ControlMessage::PublishSkipped(ref m) => self.receive_publish_skipped(m),
2483 _ => Err(EndpointError::ResponseOnControlStream),
2484 }
2485 }
2486
2487 /// Process a GOAWAY that arrived on one request stream rather than on the
2488 /// control stream.
2489 ///
2490 /// Draft-19 Section 10.4: "A GOAWAY MAY also be sent on a request stream
2491 /// to initiate migration of that individual request. Upon receiving a
2492 /// GOAWAY on a request stream, the endpoint SHOULD re-issue that specific
2493 /// request on a session at the specified URI". The session keeps running —
2494 /// only this request is being moved — so the session state machine is not
2495 /// touched and no draining event follows. The server-side URI rule of the
2496 /// same section still applies.
2497 ///
2498 /// # Errors
2499 ///
2500 /// [`EndpointError::RepeatedGoAwayOnRequestStream`] if this request's
2501 /// stream has already carried one. The session is over: this endpoint's own
2502 /// state has moved to Closed and the code the transport should close with
2503 /// is in [`EndpointError::session_error_code`].
2504 pub fn receive_goaway_on_request_stream(
2505 &mut self,
2506 request_id: VarInt,
2507 msg: &GoAway,
2508 ) -> Result<(), EndpointError> {
2509 if self.role == Role::Server && !msg.new_session_uri.is_empty() {
2510 return Err(self.fail_session(EndpointError::GoAwayUriAtServer));
2511 }
2512 let id = request_id.into_inner();
2513 if self.subscriptions.contains_key(&id)
2514 || self.publishes.contains_key(&id)
2515 || self.fetches.contains_key(&id)
2516 || self.subscribe_namespaces.contains_key(&id)
2517 || self.subscribe_tracks.contains_key(&id)
2518 || self.publish_namespaces.contains_key(&id)
2519 || self.track_statuses.contains_key(&id)
2520 {
2521 // Section 10.4 counts per stream, so this is a separate first
2522 // GOAWAY on every request and a repeat only on the one that has
2523 // already carried one. The set is fed only by GOAWAYs accepted
2524 // here, and never pruned: a Request ID is spent once, so an entry
2525 // can never come to describe a different request.
2526 if self.goaway_request_streams.insert(id) {
2527 Ok(())
2528 } else {
2529 Err(self.fail_session(EndpointError::RepeatedGoAwayOnRequestStream(id)))
2530 }
2531 } else {
2532 Err(EndpointError::UnknownRequest(id))
2533 }
2534 }
2535
2536 // -- Responder side: requests the peer opened a stream with -----
2537
2538 /// Refuse a bidirectional stream the peer opened with a message type that
2539 /// does not begin a request, and end the session.
2540 ///
2541 /// Draft-19 Section 3.3: "Bidirectional streams MUST NOT begin with any
2542 /// other message type unless negotiated. If they do, the peer MUST close
2543 /// the Session with a PROTOCOL_VIOLATION." The returned error answers
2544 /// `Some(ProtocolViolation)` from
2545 /// [`EndpointError::session_error_code`], which is what tells the
2546 /// connection layer to put the close on the wire.
2547 pub fn refuse_non_request(&mut self, ty: MessageType) -> EndpointError {
2548 self.fail_session(EndpointError::NotARequest(ty))
2549 }
2550
2551 /// Register a request the **peer** opened a bidirectional stream with, and
2552 /// hand back the Request ID it carries.
2553 ///
2554 /// The mirror of [`receive_response_on_stream`](Self::receive_response_on_stream):
2555 /// that one is fed what comes back on a stream this endpoint opened, this
2556 /// one is fed the first message on a stream the peer opened.
2557 ///
2558 /// # What it enforces, and with which code
2559 ///
2560 /// Draft-19 Section 10.1: "If an endpoint receives a Request ID where the
2561 /// least significant bit is incorrect for the sender, or a duplicate
2562 /// Request ID, it MUST close the session with INVALID_REQUEST_ID." Both
2563 /// halves are checked here and both are returned as errors that answer
2564 /// `Some(InvalidRequestId)` from
2565 /// [`EndpointError::session_error_code`]. A message that opens no request
2566 /// stream at all is a different rule with a different code — see
2567 /// [`refuse_non_request`](Self::refuse_non_request).
2568 ///
2569 /// # Why there are no separate inbound maps
2570 ///
2571 /// The peer's ids and this endpoint's ids have opposite least significant
2572 /// bits, so they cannot collide. A peer's request goes into the same
2573 /// `HashMap` its outbound twin would, keyed the same way, and only the
2574 /// transition names differ — `on_subscribe_received` where the outbound
2575 /// path calls `on_subscribe_sent`.
2576 ///
2577 /// # Peer-controlled growth
2578 ///
2579 /// Every accepted request adds an entry that nothing removes, and the peer
2580 /// chooses how many to open. [`peer_request_count`](Self::peer_request_count)
2581 /// is what a responder can watch to impose its own ceiling; this method
2582 /// imposes none.
2583 pub fn receive_request_on_stream(
2584 &mut self,
2585 msg: &ControlMessage,
2586 ) -> Result<VarInt, EndpointError> {
2587 let request_id = match msg {
2588 ControlMessage::Subscribe(m) => m.request_id,
2589 ControlMessage::Publish(m) => m.request_id,
2590 ControlMessage::Fetch(m) => m.request_id,
2591 ControlMessage::PublishNamespace(m) => m.request_id,
2592 ControlMessage::SubscribeNamespace(m) => m.request_id,
2593 ControlMessage::SubscribeTracks(m) => m.request_id,
2594 ControlMessage::TrackStatus(m) => m.request_id,
2595 other => return Err(self.refuse_non_request(other.message_type())),
2596 };
2597 self.require_active_or_err()?;
2598
2599 let id = request_id.into_inner();
2600 if let Err(e) = self.request_ids.validate_peer_id(id) {
2601 return Err(self.fail_session(EndpointError::RequestId(e)));
2602 }
2603 // `insert` answers false when the id was already present, which is
2604 // exactly the duplicate the section names. Doing the check and the
2605 // record in one step means no path can record an id it did not check.
2606 if !self.peer_request_ids.insert(id) {
2607 return Err(self.fail_session(EndpointError::DuplicateRequestId(id)));
2608 }
2609
2610 match msg {
2611 ControlMessage::Subscribe(_) => {
2612 let mut sm = SubscriptionStateMachine::new();
2613 sm.on_subscribe_received()?;
2614 self.subscriptions.insert(id, sm);
2615 }
2616 ControlMessage::Publish(m) => {
2617 let alias = m.track_alias.into_inner();
2618 if let Some(conflict) =
2619 self.conflicting_track_alias(id, alias, &m.track_namespace, &m.track_name)
2620 {
2621 return Err(self.fail_session(conflict));
2622 }
2623 let mut sm = PublishStateMachine::new();
2624 sm.on_publish_received()?;
2625 self.publishes.insert(id, sm);
2626 // A PUBLISH names its track and its alias in the one message,
2627 // so the binding is complete on arrival. It counts against the
2628 // next one only once this endpoint has answered PUBLISH_OK,
2629 // which is what moves its state machine to Active.
2630 self.track_bindings.insert(
2631 id,
2632 TrackBinding {
2633 namespace: m.track_namespace.clone(),
2634 name: m.track_name.clone(),
2635 alias: Some(alias),
2636 kind: BindingKind::Publish,
2637 },
2638 );
2639 }
2640 ControlMessage::Fetch(m) => {
2641 if let Some(joining) = self.joining_subscription_missing(m) {
2642 self.unjoinable_fetches.insert(id, joining);
2643 }
2644 let mut sm = FetchStateMachine::new();
2645 sm.on_fetch_received()?;
2646 self.fetches.insert(id, sm);
2647 }
2648 ControlMessage::PublishNamespace(_) => {
2649 let mut sm = PublishNamespaceStateMachine::new();
2650 sm.on_publish_namespace_received()?;
2651 self.publish_namespaces.insert(id, sm);
2652 }
2653 ControlMessage::SubscribeNamespace(m) => {
2654 if let Some(established) = self.peer_namespace_overlap(&m.namespace_prefix, None) {
2655 self.overlapping_namespace_subscriptions.insert(id, established);
2656 }
2657 let mut sm = SubscribeNamespaceStateMachine::new();
2658 sm.on_subscribe_namespace_received()?;
2659 self.subscribe_namespaces.insert(id, sm);
2660 }
2661 // The seventh request kind, and the one draft-17 does not have.
2662 // It gets its own map for the same reason the outbound path gives
2663 // it one: Section 10.19 makes SUBSCRIBE_TRACKS and
2664 // SUBSCRIBE_NAMESPACE independent overlap spaces, so a responder
2665 // that merged them would answer the wrong request.
2666 ControlMessage::SubscribeTracks(m) => {
2667 // Its own space: a SUBSCRIBE_TRACKS is weighed against the
2668 // SUBSCRIBE_TRACKS the peer has open and against nothing else,
2669 // so one prefix may carry one of each.
2670 if let Some(established) = self.peer_tracks_overlap(&m.namespace_prefix, None) {
2671 self.overlapping_namespace_subscriptions.insert(id, established);
2672 }
2673 let mut sm = SubscribeNamespaceStateMachine::new();
2674 sm.on_subscribe_namespace_received()?;
2675 self.subscribe_tracks.insert(id, sm);
2676 }
2677 ControlMessage::TrackStatus(_) => {
2678 let mut sm = TrackStatusStateMachine::new();
2679 sm.on_track_status_received()?;
2680 self.track_statuses.insert(id, sm);
2681 }
2682 // Unreachable: the match above returned for every other variant.
2683 other => return Err(self.refuse_non_request(other.message_type())),
2684 }
2685
2686 // The request as it arrived, recorded last so that nothing above it
2687 // can leave one behind for a request it went on to refuse.
2688 self.inbound_requests.insert(id, msg.clone());
2689
2690 // After the request is recorded, never before. The answer to every
2691 // Range Filter rule is a REQUEST_ERROR, which names the Request ID of
2692 // the request it answers, so refusing here would leave nothing to
2693 // answer with.
2694 let parameters = request_parameters(msg);
2695 if let Some(rejection) = self.filter_verdict(parameters) {
2696 self.peer_filter_rejections.insert(id, rejection);
2697 }
2698 self.peer_request_filters.insert(id, range_filter_parameters(parameters));
2699 Ok(request_id)
2700 }
2701
2702 // -- What the peer asked for ------------------------------------
2703
2704 /// The SUBSCRIBE the peer sent under `request_id` and this endpoint has
2705 /// not answered yet.
2706 ///
2707 /// `None` once it has been answered, for an identifier this session has
2708 /// carried no SUBSCRIBE under, and for one this endpoint spent on a
2709 /// request of its own -- whose state is in the same map, but which never
2710 /// arrived here. The record itself lives on past the answer, because the
2711 /// subscription it opened runs on after it, for as long as the request
2712 /// stream does.
2713 pub fn pending_subscribe(&self, request_id: VarInt) -> Option<&Subscribe> {
2714 let id = request_id.into_inner();
2715 let unanswered = self
2716 .subscriptions
2717 .get(&id)
2718 .is_some_and(|sm| sm.state() == SubscriptionState::Subscribing);
2719 match self.inbound_requests.get(&id) {
2720 Some(ControlMessage::Subscribe(msg)) if unanswered => Some(msg),
2721 _ => None,
2722 }
2723 }
2724
2725 /// How many SUBSCRIBEs the peer has sent that are still waiting for an
2726 /// answer.
2727 pub fn pending_subscribe_count(&self) -> usize {
2728 self.inbound_requests
2729 .iter()
2730 .filter(|(id, msg)| {
2731 matches!(msg, ControlMessage::Subscribe(_))
2732 && self
2733 .subscriptions
2734 .get(*id)
2735 .is_some_and(|sm| sm.state() == SubscriptionState::Subscribing)
2736 })
2737 .count()
2738 }
2739
2740 /// The PUBLISH the peer sent under `request_id` and this endpoint has not
2741 /// answered yet.
2742 ///
2743 /// `None` once it has been answered, for an identifier this session has
2744 /// carried no PUBLISH under, and for one this endpoint spent on a request
2745 /// of its own -- whose state is in the same map, but which never arrived
2746 /// here. The record itself lives on past the answer, because the
2747 /// subscription the offer opened runs on after it, for as long as the
2748 /// request stream does.
2749 pub fn pending_publish(&self, request_id: VarInt) -> Option<&message::Publish> {
2750 let id = request_id.into_inner();
2751 let unanswered =
2752 self.publishes.get(&id).is_some_and(|sm| sm.state() == PublishState::Publishing);
2753 match self.inbound_requests.get(&id) {
2754 Some(ControlMessage::Publish(msg)) if unanswered => Some(msg),
2755 _ => None,
2756 }
2757 }
2758
2759 /// How many offers the peer has sent that are still waiting for an
2760 /// answer.
2761 pub fn pending_publish_count(&self) -> usize {
2762 self.inbound_requests
2763 .iter()
2764 .filter(|(id, msg)| {
2765 matches!(msg, ControlMessage::Publish(_))
2766 && self
2767 .publishes
2768 .get(*id)
2769 .is_some_and(|sm| sm.state() == PublishState::Publishing)
2770 })
2771 .count()
2772 }
2773
2774 /// The FETCH the peer sent under `request_id` and this endpoint has not
2775 /// answered yet.
2776 ///
2777 /// `None` once it has been answered, for an identifier this session has
2778 /// carried no FETCH under, and for one this endpoint spent on a request
2779 /// of its own -- whose state is in the same map, but which never arrived
2780 /// here. The record itself lives on past the answer, because the fetch is
2781 /// not over until its response stream is.
2782 pub fn pending_fetch(&self, request_id: VarInt) -> Option<&Fetch> {
2783 let id = request_id.into_inner();
2784 let unanswered = self
2785 .fetches
2786 .get(&id)
2787 .is_some_and(|sm| matches!(sm.state(), FetchState::Pending | FetchState::Unanswered));
2788 match self.inbound_requests.get(&id) {
2789 Some(ControlMessage::Fetch(msg)) if unanswered => Some(msg),
2790 _ => None,
2791 }
2792 }
2793
2794 /// How many FETCHes the peer has sent that are still waiting for an
2795 /// answer.
2796 pub fn pending_fetch_count(&self) -> usize {
2797 self.inbound_requests
2798 .iter()
2799 .filter(|(id, msg)| {
2800 matches!(msg, ControlMessage::Fetch(_))
2801 && self.fetches.get(*id).is_some_and(|sm| {
2802 matches!(sm.state(), FetchState::Pending | FetchState::Unanswered)
2803 })
2804 })
2805 .count()
2806 }
2807
2808 /// The PUBLISH_NAMESPACE the peer sent under `request_id` and this
2809 /// endpoint has not answered yet.
2810 ///
2811 /// `None` once it has been answered, for an identifier this session has
2812 /// carried no PUBLISH_NAMESPACE under, and for one this endpoint spent on
2813 /// a request of its own -- whose state is in the same map, but which
2814 /// never arrived here. The record itself lives on past the answer,
2815 /// because an announcement that was accepted stands until it is
2816 /// withdrawn.
2817 pub fn pending_publish_namespace(&self, request_id: VarInt) -> Option<&PublishNamespace> {
2818 let id = request_id.into_inner();
2819 let unanswered = self
2820 .publish_namespaces
2821 .get(&id)
2822 .is_some_and(|sm| sm.state() == PublishNamespaceState::Pending);
2823 match self.inbound_requests.get(&id) {
2824 Some(ControlMessage::PublishNamespace(msg)) if unanswered => Some(msg),
2825 _ => None,
2826 }
2827 }
2828
2829 /// How many announcements the peer has sent that are still waiting for an
2830 /// answer.
2831 pub fn pending_publish_namespace_count(&self) -> usize {
2832 self.inbound_requests
2833 .iter()
2834 .filter(|(id, msg)| {
2835 matches!(msg, ControlMessage::PublishNamespace(_))
2836 && self
2837 .publish_namespaces
2838 .get(*id)
2839 .is_some_and(|sm| sm.state() == PublishNamespaceState::Pending)
2840 })
2841 .count()
2842 }
2843
2844 /// The earliest namespace subscription the peer has made whose prefix
2845 /// overlaps `prefix`, and `None` when there is none.
2846 ///
2847 /// Only ones that have not ended count: the sentence weighs the arriving
2848 /// prefix against an "established" one, so one the peer has withdrawn and
2849 /// one this endpoint refused are both past. Drafts 07 through 11 say "an
2850 /// earlier" instead and count those too.
2851 ///
2852 /// One that has arrived and has not been answered does count. It is not
2853 /// established yet, but this endpoint is the one about to establish it,
2854 /// and accepting both would leave the session holding exactly the pair
2855 /// the sentence exists to prevent.
2856 ///
2857 /// The record of what the peer sent is what tells the two directions
2858 /// apart. The state machines live in one map per kind whichever end
2859 /// opened the request, so a prefix this endpoint asked about would be
2860 /// indistinguishable there; only requests that arrived are written into
2861 /// `inbound_requests`.
2862 ///
2863 /// The lowest Request ID wins when more than one overlaps, so the answer
2864 /// does not depend on the order a map happens to iterate in.
2865 ///
2866 /// `except` is the subscription a REQUEST_UPDATE is moving, which Section
2867 /// 10.2.19 weighs against "another active subscription of the same type"
2868 /// and therefore not against the prefix it is leaving behind.
2869 fn peer_namespace_overlap(&self, prefix: &TrackNamespace, except: Option<u64>) -> Option<u64> {
2870 self.inbound_requests
2871 .iter()
2872 .filter(|(&id, _)| Some(id) != except)
2873 .filter_map(|(&id, msg)| match msg {
2874 ControlMessage::SubscribeNamespace(m) => Some((id, &m.namespace_prefix)),
2875 _ => None,
2876 })
2877 .filter(|(id, _)| {
2878 self.subscribe_namespaces
2879 .get(id)
2880 .is_some_and(|sm| sm.state() != SubscribeNamespaceState::Done)
2881 })
2882 .filter_map(|(id, p)| prefixes_overlap(&p.0, &prefix.0).then_some(id))
2883 .min()
2884 }
2885
2886 /// The earliest track subscription the peer has made whose prefix
2887 /// overlaps `prefix`, and `None` when there is none.
2888 ///
2889 /// Only ones that have not ended count: the sentence weighs the arriving
2890 /// prefix against an "established" one, so one the peer has withdrawn and
2891 /// one this endpoint refused are both past. Drafts 07 through 11 say "an
2892 /// earlier" instead and count those too.
2893 ///
2894 /// One that has arrived and has not been answered does count. It is not
2895 /// established yet, but this endpoint is the one about to establish it,
2896 /// and accepting both would leave the session holding exactly the pair
2897 /// the sentence exists to prevent.
2898 ///
2899 /// The record of what the peer sent is what tells the two directions
2900 /// apart. The state machines live in one map per kind whichever end
2901 /// opened the request, so a prefix this endpoint asked about would be
2902 /// indistinguishable there; only requests that arrived are written into
2903 /// `inbound_requests`.
2904 ///
2905 /// The lowest Request ID wins when more than one overlaps, so the answer
2906 /// does not depend on the order a map happens to iterate in.
2907 ///
2908 /// `except` is the subscription a REQUEST_UPDATE is moving, which Section
2909 /// 10.2.19 weighs against "another active subscription of the same type"
2910 /// and therefore not against the prefix it is leaving behind.
2911 fn peer_tracks_overlap(&self, prefix: &TrackNamespace, except: Option<u64>) -> Option<u64> {
2912 self.inbound_requests
2913 .iter()
2914 .filter(|(&id, _)| Some(id) != except)
2915 .filter_map(|(&id, msg)| match msg {
2916 ControlMessage::SubscribeTracks(m) => Some((id, &m.namespace_prefix)),
2917 _ => None,
2918 })
2919 .filter(|(id, _)| {
2920 self.subscribe_tracks
2921 .get(id)
2922 .is_some_and(|sm| sm.state() != SubscribeNamespaceState::Done)
2923 })
2924 .filter_map(|(id, p)| prefixes_overlap(&p.0, &prefix.0).then_some(id))
2925 .min()
2926 }
2927
2928 /// The SUBSCRIBE_NAMESPACE the peer sent under `request_id` and this
2929 /// endpoint has not answered yet.
2930 ///
2931 /// `None` once it has been answered, for an identifier this session has
2932 /// carried no SUBSCRIBE_NAMESPACE under, and for one this endpoint spent
2933 /// on a request of its own -- whose state is in the same map, but which
2934 /// never arrived here. The record itself lives on past the answer,
2935 /// because a namespace subscription lasts as long as its stream does.
2936 pub fn pending_subscribe_namespace(&self, request_id: VarInt) -> Option<&SubscribeNamespace> {
2937 let id = request_id.into_inner();
2938 let unanswered = self
2939 .subscribe_namespaces
2940 .get(&id)
2941 .is_some_and(|sm| sm.state() == SubscribeNamespaceState::Pending);
2942 match self.inbound_requests.get(&id) {
2943 Some(ControlMessage::SubscribeNamespace(msg)) if unanswered => Some(msg),
2944 _ => None,
2945 }
2946 }
2947
2948 /// How many namespace subscriptions the peer has sent that are still
2949 /// waiting for an answer.
2950 pub fn pending_subscribe_namespace_count(&self) -> usize {
2951 self.inbound_requests
2952 .iter()
2953 .filter(|(id, msg)| {
2954 matches!(msg, ControlMessage::SubscribeNamespace(_))
2955 && self
2956 .subscribe_namespaces
2957 .get(*id)
2958 .is_some_and(|sm| sm.state() == SubscribeNamespaceState::Pending)
2959 })
2960 .count()
2961 }
2962
2963 /// The SUBSCRIBE_TRACKS the peer sent under `request_id` and this
2964 /// endpoint has not answered yet.
2965 ///
2966 /// `None` once it has been answered, for an identifier this session has
2967 /// carried no SUBSCRIBE_TRACKS under, and for one this endpoint spent on
2968 /// a request of its own -- whose state is in the same map, but which
2969 /// never arrived here. The record itself lives on past the answer,
2970 /// because the subscription it opened lasts as long as its stream does.
2971 pub fn pending_subscribe_tracks(&self, request_id: VarInt) -> Option<&SubscribeTracks> {
2972 let id = request_id.into_inner();
2973 let unanswered = self
2974 .subscribe_tracks
2975 .get(&id)
2976 .is_some_and(|sm| sm.state() == SubscribeNamespaceState::Pending);
2977 match self.inbound_requests.get(&id) {
2978 Some(ControlMessage::SubscribeTracks(msg)) if unanswered => Some(msg),
2979 _ => None,
2980 }
2981 }
2982
2983 /// How many track subscriptions the peer has sent that are still waiting
2984 /// for an answer.
2985 pub fn pending_subscribe_tracks_count(&self) -> usize {
2986 self.inbound_requests
2987 .iter()
2988 .filter(|(id, msg)| {
2989 matches!(msg, ControlMessage::SubscribeTracks(_))
2990 && self
2991 .subscribe_tracks
2992 .get(*id)
2993 .is_some_and(|sm| sm.state() == SubscribeNamespaceState::Pending)
2994 })
2995 .count()
2996 }
2997
2998 /// The TRACK_STATUS the peer sent under `request_id` and this endpoint
2999 /// has not answered yet.
3000 ///
3001 /// `None` once it has been answered, for an identifier this session has
3002 /// carried no TRACK_STATUS under, and for one this endpoint spent on a
3003 /// request of its own -- whose state is in the same map, but which never
3004 /// arrived here. The record itself lives on past the answer, because an
3005 /// update may still name it once it has been answered.
3006 pub fn pending_track_status(&self, request_id: VarInt) -> Option<&message::TrackStatus> {
3007 let id = request_id.into_inner();
3008 let unanswered =
3009 self.track_statuses.get(&id).is_some_and(|sm| sm.state() == TrackStatusState::Pending);
3010 match self.inbound_requests.get(&id) {
3011 Some(ControlMessage::TrackStatus(msg)) if unanswered => Some(msg),
3012 _ => None,
3013 }
3014 }
3015
3016 /// How many track statuses the peer has sent that are still waiting for
3017 /// an answer.
3018 pub fn pending_track_status_count(&self) -> usize {
3019 self.inbound_requests
3020 .iter()
3021 .filter(|(id, msg)| {
3022 matches!(msg, ControlMessage::TrackStatus(_))
3023 && self
3024 .track_statuses
3025 .get(*id)
3026 .is_some_and(|sm| sm.state() == TrackStatusState::Pending)
3027 })
3028 .count()
3029 }
3030
3031 /// Whether the Range Filters in `parameters` are ones this endpoint may
3032 /// accept.
3033 ///
3034 /// The three rules that need more than one parameter to see, in the order
3035 /// that reports the most specific fault: whether the filters read at all,
3036 /// then whether any budget was advertised, then whether the request stays
3037 /// inside it, then whether two filters share a key. A request with no Range
3038 /// Filter at all is not measured against the ceiling, so an endpoint that
3039 /// advertised nothing still takes ordinary requests — which is the whole of
3040 /// the traffic today, since draft-19 is the first draft with these
3041 /// parameters.
3042 fn filter_verdict(&self, parameters: &[KeyValuePair]) -> Option<FilterRejection> {
3043 let filters = match range_filter::decode_all_moqt::<Moqt18>(parameters) {
3044 Ok(filters) => filters,
3045 Err(e) => return Some(FilterRejection::Unreadable(e)),
3046 };
3047 if filters.is_empty() {
3048 return None;
3049 }
3050 if self.advertised_max_filter_ranges == 0 {
3051 return Some(FilterRejection::NoBudgetAdvertised);
3052 }
3053 let ranges = range_filter::total_ranges(&filters);
3054 if ranges as u64 > self.advertised_max_filter_ranges {
3055 return Some(FilterRejection::TooManyRanges {
3056 ranges,
3057 limit: self.advertised_max_filter_ranges,
3058 });
3059 }
3060 if let Some((parameter_type, set_id, property_type)) =
3061 range_filter::first_repeated_key(&filters)
3062 {
3063 return Some(FilterRejection::RepeatedFilter(parameter_type, set_id, property_type));
3064 }
3065 None
3066 }
3067
3068 /// Why request `id` must be answered with a REQUEST_ERROR, if it must.
3069 ///
3070 /// The caller builds the message; [`FilterRejection::request_error_code`]
3071 /// gives the code and the variant gives the reason phrase. Answering it
3072 /// clears the record.
3073 pub fn filter_rejection(&self, id: VarInt) -> Option<&FilterRejection> {
3074 self.peer_filter_rejections.get(&id.into_inner())
3075 }
3076
3077 /// Whether this response answers a REQUEST_UPDATE rather than the request
3078 /// that opened the stream. Section 10.9 gives an update the same two
3079 /// answers a request has: "The receiver of a REQUEST_UPDATE MUST respond
3080 /// with exactly one REQUEST_OK or REQUEST_ERROR message indicating if the
3081 /// update was successful, unless it is coalescing failed updates to produce
3082 /// just one REQUEST_ERROR for multiple REQUEST_UPDATE messages." Nothing in
3083 /// either message says which of the two it is answering, so the question is
3084 /// settled twice over.
3085 ///
3086 /// A SUBSCRIBE is answered with SUBSCRIBE_OK and a FETCH with FETCH_OK, so a
3087 /// REQUEST_OK on one of those streams has no other message it could be
3088 /// answering. That is the half that needs no ordering.
3089 ///
3090 /// Everywhere else it is ordering: the first REQUEST_OK or REQUEST_ERROR on
3091 /// a stream answers the request that opened it, and the ones after it answer
3092 /// updates. Both endpoints have to resolve it the same way and neither has
3093 /// anything else to resolve it with.
3094 fn answers_an_update(&self, id: u64, msg: &ControlMessage) -> bool {
3095 if !matches!(msg, ControlMessage::RequestOk(_) | ControlMessage::RequestError(_)) {
3096 return false;
3097 }
3098 // A request this endpoint made is answered by the peer, so a response
3099 // written here can only be answering an update the peer sent on it.
3100 // Section 10.9 names the one request that works that way: "A subscriber
3101 // can also send REQUEST_UPDATE to modify parameters of a subscription
3102 // established with PUBLISH."
3103 if self.publishes.contains_key(&id) && !self.inbound_requests.contains_key(&id) {
3104 return true;
3105 }
3106 if matches!(msg, ControlMessage::RequestOk(_))
3107 && (self.subscriptions.contains_key(&id) || self.fetches.contains_key(&id))
3108 {
3109 return true;
3110 }
3111 self.answered_peer_requests.contains(&id)
3112 }
3113
3114 /// Record a response as the answer to one or more outstanding updates.
3115 ///
3116 /// A REQUEST_OK answers exactly one: "The receiver MUST still send a
3117 /// REQUEST_OK for each successful update". A REQUEST_ERROR may answer every
3118 /// update still waiting, because Section 10.9.1 permits the receiver to
3119 /// coalesce them — "If the coalesced REQUEST_UPDATE results in
3120 /// REQUEST_ERROR, only a single REQUEST_ERROR will be sent and the sender of
3121 /// the REQUEST_UPDATEs will not always be able to determine which caused an
3122 /// error." Draft-17 has no such paragraph, and its endpoint answers one
3123 /// update per message in both directions.
3124 ///
3125 /// The credit MAX_REQUEST_UPDATES counts is restored once whatever the
3126 /// answer covered, which is Section 10.3.1.7 read literally: "Each
3127 /// REQUEST_OK or REQUEST_ERROR response restores one credit on that stream."
3128 /// The peer restores by the same sentence, so an endpoint that gave back one
3129 /// per coalesced update would be crediting a peer that is not.
3130 ///
3131 /// No state machine moves. An update changes a request's parameters and not
3132 /// its lifecycle, so a subscription that was Active before its update was
3133 /// answered is Active after it, whichever answer went out.
3134 ///
3135 /// # Errors
3136 ///
3137 /// [`EndpointError::NoUpdateToAnswer`], and nothing is written or spent.
3138 ///
3139 /// [`EndpointError::PeerPrefixOverlap`] and
3140 /// [`EndpointError::WrongOverlapRefusal`] when the update asked to move a
3141 /// namespace subscription onto a prefix that overlaps another of its kind
3142 /// and the answer is not the REQUEST_ERROR the sentence names. The same
3143 /// two the request itself is refused with, because it is the same rule
3144 /// under the same code; which of the two moments raised it is told apart
3145 /// by which call returned.
3146 fn answer_an_update(&mut self, id: u64, msg: &ControlMessage) -> Result<(), EndpointError> {
3147 let unanswered = self.unanswered_peer_updates.get(&id).copied().unwrap_or(0);
3148 if unanswered == 0 {
3149 return Err(EndpointError::NoUpdateToAnswer(id));
3150 }
3151 // The update's half of the overlap rule, and the reason it needs a
3152 // verdict of its own: an update is answered by the same two messages
3153 // on the same stream as the request, so the moment one of them is
3154 // about to be written is the one place both the answer and the code
3155 // the sentence names are known. Section 10.2.19: "If the new prefix
3156 // would share a common prefix with another active subscription of the
3157 // same type in the same session, the receiver MUST respond with
3158 // REQUEST_ERROR with error code PREFIX_OVERLAP."
3159 if let Some(&established) = self.overlapping_prefix_updates.get(&id) {
3160 let ControlMessage::RequestError(err) = msg else {
3161 return Err(EndpointError::PeerPrefixOverlap { request: id, established });
3162 };
3163 let required = RequestErrorCode::PrefixOverlap as u64;
3164 if err.error_code.into_inner() != required {
3165 return Err(EndpointError::WrongOverlapRefusal { request: id, required });
3166 }
3167 }
3168 let answered = if matches!(msg, ControlMessage::RequestError(_)) { unanswered } else { 1 };
3169 self.unanswered_peer_updates.insert(id, unanswered - answered);
3170 self.restore_update_credit(id);
3171 // The same rule the request's own REQUEST_ERROR spends: a filter this
3172 // endpoint owes an error about has been answered.
3173 if matches!(msg, ControlMessage::RequestError(_)) {
3174 self.peer_filter_rejections.remove(&id);
3175 }
3176 // The prefix moves on the acceptance and not before: "If the update is
3177 // accepted, NAMESPACE and NAMESPACE_DONE messages following the
3178 // REQUEST_OK will contain Track Namespace suffixes relative to the
3179 // updated prefix." A REQUEST_ERROR drops it and the subscription goes
3180 // on selecting what it selected before the update was sent.
3181 //
3182 // Nothing else is recomputed. A subscription this endpoint has already
3183 // refused keeps that verdict when ground it wanted comes free, because
3184 // the sentence that refused it names the moment it arrived and that
3185 // moment has passed.
3186 let accepted = matches!(msg, ControlMessage::RequestOk(_));
3187 if let Some(prefix) = self.updated_namespace_prefixes.remove(&id) {
3188 if accepted {
3189 match self.inbound_requests.get_mut(&id) {
3190 Some(ControlMessage::SubscribeNamespace(m)) => m.namespace_prefix = prefix,
3191 Some(ControlMessage::SubscribeTracks(m)) => m.namespace_prefix = prefix,
3192 // Unreachable: nothing else is ever written above.
3193 _ => {}
3194 }
3195 }
3196 }
3197 if !accepted {
3198 self.overlapping_prefix_updates.remove(&id);
3199 }
3200 Ok(())
3201 }
3202
3203 /// The identifier an arriving Joining Fetch names, when this session has
3204 /// no subscription it may join.
3205 ///
3206 /// Section 10.12.2:
3207 /// "If a publisher receives a Joining Fetch with a Request ID that
3208 /// does not correspond to a subscription in the same session in the
3209 /// Established or Pending (subscriber) states, it MUST return a
3210 /// REQUEST_ERROR with error code INVALID_JOINING_REQUEST_ID."
3211 /// A standalone fetch names none and answers `None`, and so does a joining
3212 /// one whose subscription is live. Either message can establish the one it
3213 /// joins: Section 5.1 says the Largest Location a Joining FETCH works from
3214 /// is the one saved "communicated in SUBSCRIBE_OK, PUBLISH or
3215 /// REQUEST_UPDATE_OK that changes the Forward State from 0 to 1".
3216 fn joining_subscription_missing(&self, msg: &Fetch) -> Option<u64> {
3217 let message::FetchPayload::Joining { joining_request_id: joined, .. } = &msg.fetch_payload
3218 else {
3219 return None;
3220 };
3221 let joined = joined.into_inner();
3222 let live =
3223 self.subscriptions.get(&joined).is_some_and(|s| s.state() != SubscriptionState::Done)
3224 || self.publishes.get(&joined).is_some_and(|p| p.state() != PublishState::Done);
3225 if live {
3226 None
3227 } else {
3228 Some(joined)
3229 }
3230 }
3231
3232 /// Drive the state machine for a message this endpoint is about to write
3233 /// on a request stream the peer opened.
3234 ///
3235 /// The mirror of [`receive_response_on_stream`](Self::receive_response_on_stream),
3236 /// and the reason the transitions are named `*_sent` rather than reusing
3237 /// the received-side ones: the state edges coincide, so a mis-dispatch
3238 /// would otherwise succeed silently instead of naming the wrong event in
3239 /// an `InvalidTransition`.
3240 ///
3241 /// Beyond the four responses this also takes the three messages draft-19
3242 /// Table 5 places on a request stream that a responder writes after its
3243 /// response: NAMESPACE and NAMESPACE_DONE on a SUBSCRIBE_NAMESPACE stream
3244 /// (Section 10.16 and Section 10.17) and PUBLISH_SKIPPED on a
3245 /// SUBSCRIBE_TRACKS stream (Section 10.20). All three require the request
3246 /// to have been accepted first, because each machine only leaves Pending on
3247 /// its REQUEST_OK.
3248 ///
3249 /// The caller writes `msg` only after this returns `Ok`. What it cannot
3250 /// undo is the opposite order: a write that fails afterwards leaves the
3251 /// state machine one step ahead of the wire, the same asymmetry the
3252 /// outbound request path already carries.
3253 ///
3254 /// # Errors
3255 ///
3256 /// [`EndpointError::UnknownRequest`] when no request of the answering kind
3257 /// carries that identifier, [`EndpointError::NotAResponse`] when the
3258 /// message is not one, [`EndpointError::UnjoinableSubscription`] and
3259 /// [`EndpointError::WrongJoiningRefusal`] for a Joining Fetch that named no
3260 /// live subscription, [`EndpointError::PeerPrefixOverlap`] and
3261 /// [`EndpointError::WrongOverlapRefusal`] for a namespace subscription that
3262 /// overlapped one already open or that a REQUEST_UPDATE asked to move onto
3263 /// an overlapping prefix, [`EndpointError::FilterMustBeRejected`] for a request whose Range Filters this draft says to reject, and each flow's own
3264 /// `InvalidTransition` for a request already answered.
3265 pub fn send_response_on_stream(
3266 &mut self,
3267 request_id: VarInt,
3268 msg: &ControlMessage,
3269 ) -> Result<(), EndpointError> {
3270 let id = request_id.into_inner();
3271 // A request whose Range Filters the draft says to reject may be
3272 // answered with a REQUEST_ERROR and with nothing else. Checked across
3273 // every acceptance rather than inside the REQUEST_OK arm, because
3274 // SUBSCRIBE and FETCH — the two requests that most obviously carry these
3275 // filters — are accepted with SUBSCRIBE_OK and FETCH_OK instead.
3276 if matches!(
3277 msg,
3278 ControlMessage::SubscribeOk(_)
3279 | ControlMessage::FetchOk(_)
3280 | ControlMessage::RequestOk(_)
3281 ) {
3282 if let Some(rejection) = self.peer_filter_rejections.get(&id) {
3283 return Err(EndpointError::FilterMustBeRejected(id, rejection.clone()));
3284 }
3285 }
3286 // An answer to an update reaches none of the arms below: the request it
3287 // belongs to has its own lifecycle and an update does not move it.
3288 // A refused update leaves this endpoint owing the peer a termination,
3289 // and the draft names the status that termination must carry. So the
3290 // request's ending is what the debt governs: a PUBLISH_DONE under any
3291 // other status is refused, and everything else the caller may still
3292 // have to write for this request - the answer to a second update it
3293 // has not answered yet - is left alone, because the sentence orders
3294 // nothing.
3295 if let ControlMessage::PublishDone(done) = msg {
3296 self.require_update_failure_status(id, done.status_code)?;
3297 }
3298 if self.answers_an_update(id, msg) {
3299 self.answer_an_update(id, msg)?;
3300 // Refusing an update is half of what the draft asks for, and which
3301 // half it is depends on what was being updated. A subscription is
3302 // owed the PUBLISH_DONE that ends it, recorded here so that the
3303 // next ending written for this request has to be that one. A
3304 // namespace request, a fetch and a track status are owed no
3305 // message at all, and recording one for them would keep a stream
3306 // open that has nothing left to carry.
3307 if matches!(msg, ControlMessage::RequestError(_)) && self.publishes_a_subscription(id) {
3308 self.owed_update_failures.insert(id);
3309 }
3310 return Ok(());
3311 }
3312 // A namespace subscription that overlapped one already open when it
3313 // arrived has one answer available to it, and this is where both the
3314 // answer and its code are known. An update's answer returned above, so
3315 // nothing here judges one.
3316 if let Some(&established) = self.overlapping_namespace_subscriptions.get(&id) {
3317 let ControlMessage::RequestError(err) = msg else {
3318 return Err(EndpointError::PeerPrefixOverlap { request: id, established });
3319 };
3320 let required = RequestErrorCode::PrefixOverlap as u64;
3321 if err.error_code.into_inner() != required {
3322 return Err(EndpointError::WrongOverlapRefusal { request: id, required });
3323 }
3324 }
3325 match msg {
3326 ControlMessage::SubscribeOk(_) => {
3327 let sm =
3328 self.subscriptions.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
3329 sm.on_subscribe_ok_sent()?;
3330 }
3331 ControlMessage::FetchOk(_) => {
3332 if let Some(&joining) = self.unjoinable_fetches.get(&id) {
3333 return Err(EndpointError::UnjoinableSubscription { fetch: id, joining });
3334 }
3335 let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
3336 sm.on_fetch_ok_sent()?;
3337 }
3338 ControlMessage::PublishDone(_) => {
3339 let sm =
3340 self.subscriptions.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
3341 sm.on_publish_done_sent()?;
3342 }
3343 // REQUEST_OK answers the kinds that have no response message of
3344 // their own, PUBLISH among them since draft-18 folded PUBLISH_OK
3345 // into it. The probe order does not matter: the maps are keyed by
3346 // Request ID and one id belongs to one request.
3347 ControlMessage::RequestOk(m) => {
3348 // Section 10.5 makes receiving Track Properties on anything but
3349 // a TRACK_STATUS response a session close, so putting them on
3350 // one of the others would be handing the peer a reason to close
3351 // this session. Refused before the write rather than after.
3352 if !m.track_properties.is_empty() && !self.track_statuses.contains_key(&id) {
3353 return Err(EndpointError::TrackPropertiesOnOutgoingRequestOk(id));
3354 }
3355 if let Some(sm) = self.publishes.get_mut(&id) {
3356 sm.on_publish_ok_sent()?;
3357 } else if let Some(sm) = self.subscribe_namespaces.get_mut(&id) {
3358 sm.on_subscribe_namespace_ok_sent()?;
3359 } else if let Some(sm) = self.subscribe_tracks.get_mut(&id) {
3360 sm.on_subscribe_namespace_ok_sent()?;
3361 } else if let Some(sm) = self.publish_namespaces.get_mut(&id) {
3362 sm.on_publish_namespace_ok_sent()?;
3363 } else if let Some(sm) = self.track_statuses.get_mut(&id) {
3364 sm.on_track_status_ok_sent()?;
3365 } else {
3366 return Err(EndpointError::UnknownRequest(id));
3367 }
3368 }
3369 ControlMessage::RequestError(err) => {
3370 if let Some(sm) = self.subscriptions.get_mut(&id) {
3371 sm.on_subscribe_error_sent()?;
3372 } else if self.fetches.contains_key(&id) {
3373 if self.unjoinable_fetches.contains_key(&id) {
3374 let required = RequestErrorCode::InvalidJoiningRequestId as u64;
3375 if err.error_code.into_inner() != required {
3376 return Err(EndpointError::WrongJoiningRefusal { fetch: id, required });
3377 }
3378 }
3379 let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
3380 sm.on_fetch_error_sent()?;
3381 } else if let Some(sm) = self.publishes.get_mut(&id) {
3382 sm.on_publish_error_sent()?;
3383 } else if let Some(sm) = self.subscribe_namespaces.get_mut(&id) {
3384 sm.on_subscribe_namespace_error_sent()?;
3385 } else if let Some(sm) = self.subscribe_tracks.get_mut(&id) {
3386 sm.on_subscribe_namespace_error_sent()?;
3387 } else if let Some(sm) = self.publish_namespaces.get_mut(&id) {
3388 sm.on_publish_namespace_error_sent()?;
3389 } else if let Some(sm) = self.track_statuses.get_mut(&id) {
3390 sm.on_track_status_error_sent()?;
3391 } else {
3392 return Err(EndpointError::UnknownRequest(id));
3393 }
3394 }
3395 ControlMessage::Namespace(_) => {
3396 let sm = self
3397 .subscribe_namespaces
3398 .get_mut(&id)
3399 .ok_or(EndpointError::UnknownRequest(id))?;
3400 sm.on_namespace_sent()?;
3401 }
3402 ControlMessage::NamespaceDone(_) => {
3403 let sm = self
3404 .subscribe_namespaces
3405 .get_mut(&id)
3406 .ok_or(EndpointError::UnknownRequest(id))?;
3407 sm.on_namespace_done_sent()?;
3408 }
3409 ControlMessage::PublishSkipped(_) => {
3410 let sm =
3411 self.subscribe_tracks.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
3412 sm.on_publish_skipped_sent()?;
3413 }
3414 other => return Err(EndpointError::NotAResponse(other.message_type())),
3415 }
3416 // After the match, not before it: the caller writes `msg` only once
3417 // this returns `Ok`, so a response that was refused restores nothing.
3418 if matches!(msg, ControlMessage::RequestOk(_) | ControlMessage::RequestError(_)) {
3419 self.restore_update_credit(id);
3420 }
3421 // Reached only by a response that answered the request itself, since an
3422 // update's answer returned above. From here on, a REQUEST_OK or
3423 // REQUEST_ERROR on this stream can only be answering an update.
3424 if matches!(
3425 msg,
3426 ControlMessage::SubscribeOk(_)
3427 | ControlMessage::FetchOk(_)
3428 | ControlMessage::RequestOk(_)
3429 | ControlMessage::RequestError(_)
3430 ) {
3431 self.answered_peer_requests.insert(id);
3432 }
3433 // For the same reason, and it matters more here: a rejection cleared by
3434 // a REQUEST_ERROR that was itself refused would leave the request
3435 // acceptable on the next attempt.
3436 if matches!(msg, ControlMessage::RequestError(_)) {
3437 self.peer_filter_rejections.remove(&id);
3438 }
3439 Ok(())
3440 }
3441
3442 /// Dispatch a message that arrived on a request stream the **peer** opened,
3443 /// after the request that opened it.
3444 ///
3445 /// Nothing that arrives here is a response: this endpoint is the responder
3446 /// on such a stream, so a SUBSCRIBE_OK or REQUEST_ERROR turning up is the
3447 /// peer answering its own request, and it is refused with
3448 /// [`EndpointError::UnexpectedOnPeerRequestStream`].
3449 ///
3450 /// Three messages are expected instead.
3451 ///
3452 /// REQUEST_UPDATE, because draft-19 Section 10.9 puts it on the request's
3453 /// own stream: "The sender of a request (SUBSCRIBE, PUBLISH, FETCH,
3454 /// PUBLISH_NAMESPACE, SUBSCRIBE_NAMESPACE, SUBSCRIBE_TRACKS) can later send
3455 /// a REQUEST_UPDATE on the same bidi stream as the request to modify it."
3456 /// It goes through the same [`receive_request_update`](Self::receive_request_update)
3457 /// the requester side uses, so the peer's update is held to the same rule
3458 /// its own would be: the message's Request ID must name the stream's
3459 /// request, and a kind the section does not allow to be updated —
3460 /// TRACK_STATUS, by Section 10.14 — closes the session.
3461 ///
3462 /// GOAWAY, because Section 10.4 lets one arrive on a request stream to
3463 /// migrate that request alone, in either direction.
3464 ///
3465 /// PUBLISH_DONE, because a peer that sent PUBLISH is the publisher and ends
3466 /// the publication it opened.
3467 pub fn receive_on_peer_request_stream(
3468 &mut self,
3469 request_id: VarInt,
3470 msg: ControlMessage,
3471 ) -> Result<(), EndpointError> {
3472 let id = request_id.into_inner();
3473 match msg {
3474 ControlMessage::RequestUpdate(ref m) => self.receive_request_update(request_id, m),
3475 // Section 10.4 puts no direction on it: "A GOAWAY MAY also be
3476 // sent on a request stream to initiate migration of that individual
3477 // request." A request the peer opened is a request stream, so a
3478 // GOAWAY on one migrates it the same way.
3479 ControlMessage::GoAway(ref m) => self.receive_goaway_on_request_stream(request_id, m),
3480 ControlMessage::PublishDone(_) => {
3481 let sm = self.publishes.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
3482 sm.on_publish_done_received()?;
3483 Ok(())
3484 }
3485 other => Err(EndpointError::UnexpectedOnPeerRequestStream(other.message_type())),
3486 }
3487 }
3488}
3489
3490// -- Responder-side state machine transitions -----------------------
3491//
3492// Each one is the same edge as an existing requester-side transition — the
3493// graph does not change with the direction — but under a name that says which
3494// way the message went, so a mis-dispatch names the responder event in the
3495// `InvalidTransition` it produces instead of quietly succeeding.
3496//
3497// They are written against the public surface of the machines rather than
3498// against the state field, so the rejected-state error has to be rebuilt to
3499// carry the responder event name.
3500
3501impl SubscriptionStateMachine {
3502 /// Idle -> Subscribing (SUBSCRIBE received from the peer).
3503 pub fn on_subscribe_received(&mut self) -> Result<(), SubscriptionError> {
3504 self.on_subscribe_sent().map_err(|_| SubscriptionError::InvalidTransition {
3505 from: self.state(),
3506 event: "on_subscribe_received".to_string(),
3507 })
3508 }
3509
3510 /// Subscribing -> Active (SUBSCRIBE_OK written on the peer's stream).
3511 pub fn on_subscribe_ok_sent(&mut self) -> Result<(), SubscriptionError> {
3512 self.on_subscribe_ok().map_err(|_| SubscriptionError::InvalidTransition {
3513 from: self.state(),
3514 event: "on_subscribe_ok_sent".to_string(),
3515 })
3516 }
3517
3518 /// Subscribing -> Done (REQUEST_ERROR written on the peer's stream).
3519 pub fn on_subscribe_error_sent(&mut self) -> Result<(), SubscriptionError> {
3520 self.on_subscribe_error().map_err(|_| SubscriptionError::InvalidTransition {
3521 from: self.state(),
3522 event: "on_subscribe_error_sent".to_string(),
3523 })
3524 }
3525
3526 /// Active -> Done (PUBLISH_DONE written on the peer's stream).
3527 pub fn on_publish_done_sent(&mut self) -> Result<(), SubscriptionError> {
3528 self.on_publish_done().map_err(|_| SubscriptionError::InvalidTransition {
3529 from: self.state(),
3530 event: "on_publish_done_sent".to_string(),
3531 })
3532 }
3533}
3534
3535impl FetchStateMachine {
3536 /// Idle -> Pending (FETCH received from the peer).
3537 pub fn on_fetch_received(&mut self) -> Result<(), FetchError> {
3538 self.on_fetch_sent().map_err(|_| FetchError::InvalidTransition {
3539 from: self.state(),
3540 event: "on_fetch_received".to_string(),
3541 })
3542 }
3543
3544 /// Pending -> Receiving, Unanswered -> Done (FETCH_OK written on the
3545 /// peer's stream).
3546 ///
3547 /// The state is named for the requester's view; for a responder the same
3548 /// node means the objects are being served rather than received. It is the
3549 /// same node in the graph, with the same edges, so it keeps its name.
3550 pub fn on_fetch_ok_sent(&mut self) -> Result<(), FetchError> {
3551 self.on_fetch_ok().map_err(|_| FetchError::InvalidTransition {
3552 from: self.state(),
3553 event: "on_fetch_ok_sent".to_string(),
3554 })
3555 }
3556
3557 /// Pending | Unanswered -> Done (REQUEST_ERROR written on the peer's
3558 /// stream).
3559 pub fn on_fetch_error_sent(&mut self) -> Result<(), FetchError> {
3560 self.on_fetch_error().map_err(|_| FetchError::InvalidTransition {
3561 from: self.state(),
3562 event: "on_fetch_error_sent".to_string(),
3563 })
3564 }
3565
3566 /// Receiving -> Done, Pending -> Unanswered (this endpoint finished the
3567 /// fetch data stream).
3568 pub fn on_stream_fin_sent(&mut self) -> Result<(), FetchError> {
3569 self.on_stream_fin().map_err(|_| FetchError::InvalidTransition {
3570 from: self.state(),
3571 event: "on_stream_fin_sent".to_string(),
3572 })
3573 }
3574}
3575
3576impl PublishStateMachine {
3577 /// Idle -> Publishing (PUBLISH received from the peer).
3578 pub fn on_publish_received(&mut self) -> Result<(), PublishFlowError> {
3579 self.on_publish_sent().map_err(|_| PublishFlowError::InvalidTransition {
3580 from: self.state(),
3581 event: "on_publish_received".to_string(),
3582 })
3583 }
3584
3585 /// Publishing -> Active (REQUEST_OK written on the peer's stream).
3586 ///
3587 /// Draft-18 folded PUBLISH_OK into REQUEST_OK and draft-19 keeps it that
3588 /// way, so the message that walks this edge is a REQUEST_OK here where on
3589 /// draft-17 it was a PUBLISH_OK of its own.
3590 pub fn on_publish_ok_sent(&mut self) -> Result<(), PublishFlowError> {
3591 self.on_publish_ok().map_err(|_| PublishFlowError::InvalidTransition {
3592 from: self.state(),
3593 event: "on_publish_ok_sent".to_string(),
3594 })
3595 }
3596
3597 /// Publishing -> Done (REQUEST_ERROR written on the peer's stream).
3598 pub fn on_publish_error_sent(&mut self) -> Result<(), PublishFlowError> {
3599 self.on_publish_error().map_err(|_| PublishFlowError::InvalidTransition {
3600 from: self.state(),
3601 event: "on_publish_error_sent".to_string(),
3602 })
3603 }
3604
3605 /// Active -> Done (PUBLISH_DONE received from the publishing peer).
3606 pub fn on_publish_done_received(&mut self) -> Result<(), PublishFlowError> {
3607 self.on_publish_done_sent().map_err(|_| PublishFlowError::InvalidTransition {
3608 from: self.state(),
3609 event: "on_publish_done_received".to_string(),
3610 })
3611 }
3612}
3613
3614impl PublishNamespaceStateMachine {
3615 /// Idle -> Pending (PUBLISH_NAMESPACE received from the peer).
3616 pub fn on_publish_namespace_received(&mut self) -> Result<(), NamespaceError> {
3617 self.on_publish_namespace_sent().map_err(|_| NamespaceError::InvalidTransition {
3618 from: format!("{:?}", self.state()),
3619 event: "on_publish_namespace_received".to_string(),
3620 })
3621 }
3622
3623 /// Pending -> Active (REQUEST_OK written on the peer's stream).
3624 pub fn on_publish_namespace_ok_sent(&mut self) -> Result<(), NamespaceError> {
3625 self.on_publish_namespace_ok().map_err(|_| NamespaceError::InvalidTransition {
3626 from: format!("{:?}", self.state()),
3627 event: "on_publish_namespace_ok_sent".to_string(),
3628 })
3629 }
3630
3631 /// Pending -> Done (REQUEST_ERROR written on the peer's stream).
3632 pub fn on_publish_namespace_error_sent(&mut self) -> Result<(), NamespaceError> {
3633 self.on_publish_namespace_error().map_err(|_| NamespaceError::InvalidTransition {
3634 from: format!("{:?}", self.state()),
3635 event: "on_publish_namespace_error_sent".to_string(),
3636 })
3637 }
3638}
3639
3640impl SubscribeNamespaceStateMachine {
3641 /// Idle -> Pending (SUBSCRIBE_NAMESPACE or SUBSCRIBE_TRACKS received from
3642 /// the peer).
3643 pub fn on_subscribe_namespace_received(&mut self) -> Result<(), NamespaceError> {
3644 self.on_subscribe_namespace_sent().map_err(|_| NamespaceError::InvalidTransition {
3645 from: format!("{:?}", self.state()),
3646 event: "on_subscribe_namespace_received".to_string(),
3647 })
3648 }
3649
3650 /// Pending -> Active (REQUEST_OK written on the peer's stream).
3651 pub fn on_subscribe_namespace_ok_sent(&mut self) -> Result<(), NamespaceError> {
3652 self.on_subscribe_namespace_ok().map_err(|_| NamespaceError::InvalidTransition {
3653 from: format!("{:?}", self.state()),
3654 event: "on_subscribe_namespace_ok_sent".to_string(),
3655 })
3656 }
3657
3658 /// Pending -> Done (REQUEST_ERROR written on the peer's stream).
3659 pub fn on_subscribe_namespace_error_sent(&mut self) -> Result<(), NamespaceError> {
3660 self.on_subscribe_namespace_error().map_err(|_| NamespaceError::InvalidTransition {
3661 from: format!("{:?}", self.state()),
3662 event: "on_subscribe_namespace_error_sent".to_string(),
3663 })
3664 }
3665
3666 /// Active -> Active (NAMESPACE written on the peer's SUBSCRIBE_NAMESPACE
3667 /// stream).
3668 ///
3669 /// Draft-19 Section 10.16 puts NAMESPACE "on the response stream of a
3670 /// SUBSCRIBE_NAMESPACE request", and Section 10.18 has the publisher send
3671 /// them only once the request has been accepted — "If the
3672 /// SUBSCRIBE_NAMESPACE is successful, the publisher will send matching
3673 /// NAMESPACE messages on the response stream." Requiring Active is what
3674 /// makes one written ahead of the REQUEST_OK an error rather than a frame
3675 /// on the wire.
3676 ///
3677 /// Draft-17 has no such edge: its message table has no Stream column and
3678 /// nothing there moves NAMESPACE off the control stream.
3679 pub fn on_namespace_sent(&mut self) -> Result<(), NamespaceError> {
3680 self.require_active("on_namespace_sent")
3681 }
3682
3683 /// Active -> Active (NAMESPACE_DONE written on the peer's
3684 /// SUBSCRIBE_NAMESPACE stream).
3685 ///
3686 /// Section 10.17: "All NAMESPACE_DONE messages are in response to a
3687 /// SUBSCRIBE_NAMESPACE". The namespace subscription outlives it — Section
3688 /// 10.18 has the publisher go on sending NAMESPACE and NAMESPACE_DONE "when
3689 /// there are changes to the namespaces being published" — so this ends one
3690 /// namespace, not the request, and the state does not move.
3691 pub fn on_namespace_done_sent(&mut self) -> Result<(), NamespaceError> {
3692 self.require_active("on_namespace_done_sent")
3693 }
3694
3695 /// Active -> Active (PUBLISH_SKIPPED written on the peer's
3696 /// SUBSCRIBE_TRACKS stream).
3697 ///
3698 /// Section 10.20: "All PUBLISH_SKIPPED messages are in response to a
3699 /// SUBSCRIBE_TRACKS". One skipped track says nothing about the rest, so
3700 /// like the two above this is a self-transition on an accepted request.
3701 pub fn on_publish_skipped_sent(&mut self) -> Result<(), NamespaceError> {
3702 self.require_active("on_publish_skipped_sent")
3703 }
3704
3705 /// The shared body of the three self-transitions above: accept the event
3706 /// when the request has been answered with REQUEST_OK, and name the event
3707 /// that was refused otherwise.
3708 fn require_active(&self, event: &str) -> Result<(), NamespaceError> {
3709 if self.state() == SubscribeNamespaceState::Active {
3710 Ok(())
3711 } else {
3712 Err(NamespaceError::InvalidTransition {
3713 from: format!("{:?}", self.state()),
3714 event: event.to_string(),
3715 })
3716 }
3717 }
3718}
3719
3720impl TrackStatusStateMachine {
3721 /// Idle -> Pending (TRACK_STATUS received from the peer).
3722 pub fn on_track_status_received(&mut self) -> Result<(), TrackStatusError> {
3723 self.on_track_status_sent().map_err(|_| TrackStatusError::InvalidTransition {
3724 from: self.state(),
3725 event: "on_track_status_received".to_string(),
3726 })
3727 }
3728
3729 /// Pending -> Done (REQUEST_OK written on the peer's stream).
3730 pub fn on_track_status_ok_sent(&mut self) -> Result<(), TrackStatusError> {
3731 self.on_track_status_ok().map_err(|_| TrackStatusError::InvalidTransition {
3732 from: self.state(),
3733 event: "on_track_status_ok_sent".to_string(),
3734 })
3735 }
3736
3737 /// Pending -> Done (REQUEST_ERROR written on the peer's stream).
3738 pub fn on_track_status_error_sent(&mut self) -> Result<(), TrackStatusError> {
3739 self.on_track_status_error().map_err(|_| TrackStatusError::InvalidTransition {
3740 from: self.state(),
3741 event: "on_track_status_error_sent".to_string(),
3742 })
3743 }
3744}
3745
3746#[cfg(test)]
3747mod responder_tests {
3748 use super::*;
3749 use crate::draft19::namespace::SubscribeNamespaceState;
3750 use crate::draft19::publish::PublishState;
3751 use crate::draft19::subscription::SubscriptionState;
3752 use moqtap_codec::kvp::KvpValue;
3753
3754 fn active_client() -> Endpoint {
3755 let mut ep = Endpoint::new(Role::Client);
3756 ep.connect().unwrap();
3757 let _ = ep.send_setup(vec![]).unwrap();
3758 ep.receive_setup(&Setup { options: vec![] }).unwrap();
3759 ep
3760 }
3761
3762 fn v(n: u64) -> VarInt {
3763 VarInt::from_u64(n).unwrap()
3764 }
3765
3766 fn ns() -> TrackNamespace {
3767 TrackNamespace(vec![b"live".to_vec()])
3768 }
3769
3770 fn peer_subscribe(id: u64) -> ControlMessage {
3771 ControlMessage::Subscribe(Subscribe {
3772 request_id: v(id),
3773 track_namespace: ns(),
3774 track_name: b"video".to_vec(),
3775 parameters: vec![],
3776 })
3777 }
3778
3779 fn peer_publish(id: u64) -> ControlMessage {
3780 ControlMessage::Publish(Publish {
3781 request_id: v(id),
3782 track_namespace: ns(),
3783 track_name: b"video".to_vec(),
3784 track_alias: v(7),
3785 parameters: vec![],
3786 track_properties: vec![],
3787 })
3788 }
3789
3790 fn request_ok() -> ControlMessage {
3791 ControlMessage::RequestOk(RequestOk { parameters: vec![], track_properties: vec![] })
3792 }
3793
3794 fn publish_done() -> ControlMessage {
3795 ControlMessage::PublishDone(PublishDone {
3796 status_code: v(0),
3797 stream_count: v(0),
3798 reason_phrase: Vec::new(),
3799 })
3800 }
3801
3802 /// The peer's requests and this endpoint's share one map per kind, and the
3803 /// opposite Request ID parity is what keeps them apart. Both directions
3804 /// are registered here and both are still there afterwards, which is the
3805 /// consequence a collision would destroy.
3806 #[test]
3807 fn a_peers_request_lives_beside_our_own_in_the_same_map() {
3808 let mut ep = active_client();
3809 let (ours, _) = ep.subscribe(ns(), b"video".to_vec(), vec![]).unwrap();
3810 assert_eq!(ours.into_inner(), 0, "a client allocates even Request IDs");
3811
3812 let theirs = ep.receive_request_on_stream(&peer_subscribe(1)).unwrap();
3813 assert_eq!(theirs.into_inner(), 1);
3814 assert_eq!(
3815 ep.active_subscription_count(),
3816 2,
3817 "the peer's subscription displaced ours in the map",
3818 );
3819 assert_eq!(ep.peer_request_count(), 1);
3820 }
3821
3822 /// Draft-19 Section 10.1: a Request ID whose least significant bit is
3823 /// wrong for the sender MUST close the session with INVALID_REQUEST_ID.
3824 /// The close is observable twice over — the endpoint stops accepting
3825 /// requests, and the code the connection layer will put on the wire is the
3826 /// one the section names.
3827 #[test]
3828 fn a_peer_id_with_our_own_parity_closes_the_session() {
3829 let mut ep = active_client();
3830 // 2 is even, so it is an id this client allocates, not one the server
3831 // may send.
3832 let err = ep.receive_request_on_stream(&peer_subscribe(2)).unwrap_err();
3833 assert_eq!(err.session_error_code(), Some(SessionErrorCode::InvalidRequestId));
3834 assert_eq!(err.to_string(), "request ID error: request ID 2 has wrong parity for Server");
3835 assert_eq!(ep.session_state(), SessionState::Closed);
3836 assert_eq!(ep.active_subscription_count(), 0, "a refused request was registered anyway");
3837 assert!(matches!(
3838 ep.receive_request_on_stream(&peer_subscribe(1)),
3839 Err(EndpointError::NotActive),
3840 ));
3841 }
3842
3843 /// The other half of the same sentence: a duplicate Request ID is also
3844 /// INVALID_REQUEST_ID. The id is remembered even though the first request
3845 /// is still open, which is why the second is caught.
3846 #[test]
3847 fn a_repeated_peer_request_id_closes_the_session() {
3848 let mut ep = active_client();
3849 ep.receive_request_on_stream(&peer_subscribe(1)).unwrap();
3850 let err = ep.receive_request_on_stream(&peer_publish(1)).unwrap_err();
3851 assert_eq!(err.session_error_code(), Some(SessionErrorCode::InvalidRequestId));
3852 assert_eq!(err.to_string(), "request 1 was already used by the peer");
3853 assert_eq!(ep.session_state(), SessionState::Closed);
3854 assert_eq!(ep.active_publish_count(), 0, "the duplicate was registered anyway");
3855 }
3856
3857 /// Draft-19 Section 3.3 gives a different code for a different rule: a
3858 /// bidirectional stream that begins with the wrong message type is a
3859 /// PROTOCOL_VIOLATION, not an INVALID_REQUEST_ID.
3860 #[test]
3861 fn a_stream_that_opens_no_request_closes_the_session_with_protocol_violation() {
3862 let mut ep = active_client();
3863 let not_a_request =
3864 ControlMessage::GoAway(GoAway { new_session_uri: Vec::new(), timeout: v(0) });
3865 let err = ep.receive_request_on_stream(¬_a_request).unwrap_err();
3866 assert_eq!(err.session_error_code(), Some(SessionErrorCode::ProtocolViolation));
3867 assert_eq!(err.to_string(), "GoAway does not begin a request stream");
3868 assert_eq!(ep.session_state(), SessionState::Closed);
3869 }
3870
3871 /// A peer's SUBSCRIBE runs the same graph our own does, in the other
3872 /// direction: received, then answered, then ended with PUBLISH_DONE by
3873 /// this endpoint rather than by the peer.
3874 #[test]
3875 fn answering_a_peers_subscribe_walks_the_subscription_to_done() {
3876 let mut ep = active_client();
3877 let id = ep.receive_request_on_stream(&peer_subscribe(1)).unwrap();
3878
3879 let ok = ControlMessage::SubscribeOk(SubscribeOk {
3880 track_alias: v(7),
3881 parameters: vec![],
3882 track_properties: vec![],
3883 });
3884 ep.send_response_on_stream(id, &ok).unwrap();
3885 assert_eq!(ep.subscriptions[&1].state(), SubscriptionState::Active);
3886
3887 ep.send_response_on_stream(id, &publish_done()).unwrap();
3888 assert_eq!(ep.subscriptions[&1].state(), SubscriptionState::Done);
3889 }
3890
3891 /// The responder transitions are separate from the requester ones so a
3892 /// mis-dispatch names the responder event rather than succeeding quietly.
3893 #[test]
3894 fn a_responder_transition_out_of_order_names_the_responder_event() {
3895 let mut ep = active_client();
3896 let id = ep.receive_request_on_stream(&peer_subscribe(1)).unwrap();
3897 // PUBLISH_DONE before SUBSCRIBE_OK: the subscription is not Active.
3898 let err = ep.send_response_on_stream(id, &publish_done()).unwrap_err();
3899 assert_eq!(
3900 err.to_string(),
3901 "subscription error: invalid transition from Subscribing on event on_publish_done_sent",
3902 );
3903 }
3904
3905 /// Draft-18 folded PUBLISH_OK into REQUEST_OK and draft-19 keeps the fold,
3906 /// so a peer's PUBLISH is accepted with REQUEST_OK here where draft-17
3907 /// answers with a PUBLISH_OK of its own. The peer is then the publisher,
3908 /// so PUBLISH_DONE comes back from it on the same stream — the one
3909 /// direction the requester path never has to handle, because there we are
3910 /// the publisher.
3911 #[test]
3912 fn a_peers_publish_is_accepted_with_request_ok_and_ended_by_the_peer() {
3913 let mut ep = active_client();
3914 let id = ep.receive_request_on_stream(&peer_publish(1)).unwrap();
3915 ep.send_response_on_stream(id, &request_ok()).unwrap();
3916 assert_eq!(ep.publishes[&1].state(), PublishState::Active);
3917
3918 ep.receive_on_peer_request_stream(id, publish_done()).unwrap();
3919 assert_eq!(ep.publishes[&1].state(), PublishState::Done);
3920 assert!(
3921 ep.receive_on_peer_request_stream(id, publish_done()).is_err(),
3922 "a second PUBLISH_DONE was accepted on a publication already Done",
3923 );
3924 }
3925
3926 /// This endpoint is the responder on a stream the peer opened, so a
3927 /// response arriving there is the peer answering itself. Routing it to the
3928 /// response dispatcher would look up a request we never made; refusing it
3929 /// is what the origin marker buys.
3930 #[test]
3931 fn a_response_on_a_peer_opened_stream_is_refused() {
3932 let mut ep = active_client();
3933 let id = ep.receive_request_on_stream(&peer_subscribe(1)).unwrap();
3934 let ok = ControlMessage::SubscribeOk(SubscribeOk {
3935 track_alias: v(7),
3936 parameters: vec![],
3937 track_properties: vec![],
3938 });
3939 let err = ep.receive_on_peer_request_stream(id, ok).unwrap_err();
3940 assert_eq!(
3941 err.to_string(),
3942 "SubscribeOk may not follow a request on a stream the peer opened",
3943 );
3944 }
3945
3946 /// A peer's REQUEST_UPDATE is held to the same rule the requester side is:
3947 /// draft-19 Section 10.9 puts it on its request's own stream, and the
3948 /// requester's receive path already refuses one whose Request ID names a
3949 /// different request. Routing the peer's updates through that same handler
3950 /// is what keeps the two directions from disagreeing.
3951 #[test]
3952 fn a_peers_request_update_is_held_to_its_own_stream() {
3953 let mut ep = active_client();
3954 let id = ep.receive_request_on_stream(&peer_subscribe(1)).unwrap();
3955 let ok = ControlMessage::SubscribeOk(SubscribeOk {
3956 track_alias: v(7),
3957 parameters: vec![],
3958 track_properties: vec![],
3959 });
3960 ep.send_response_on_stream(id, &ok).unwrap();
3961
3962 let update = |named: u64| {
3963 ControlMessage::RequestUpdate(RequestUpdate {
3964 request_id: v(named),
3965 parameters: vec![],
3966 })
3967 };
3968 ep.receive_on_peer_request_stream(id, update(1)).unwrap();
3969 assert_eq!(ep.subscriptions[&1].state(), SubscriptionState::Active);
3970
3971 // An update naming a different request than its stream is the
3972 // violation the section answers with a close.
3973 let err = ep.receive_on_peer_request_stream(id, update(3)).unwrap_err();
3974 assert!(matches!(err, EndpointError::UnexpectedRequestUpdate(3)), "{err}");
3975 assert_eq!(ep.session_state(), SessionState::Closed);
3976 }
3977
3978 /// SUBSCRIBE_TRACKS (0x51) is the seventh request kind, added in draft-18
3979 /// and absent from draft-17. A responder that carried draft-17's six over
3980 /// would refuse it as a non-request and close the session, so the
3981 /// consequence checked is that it is registered and answerable.
3982 ///
3983 /// Section 10.19 keeps its overlap space independent of
3984 /// SUBSCRIBE_NAMESPACE's, which is why it lands in its own map.
3985 #[test]
3986 fn a_peers_subscribe_tracks_is_a_request_of_its_own() {
3987 let mut ep = active_client();
3988 let tracks = ControlMessage::SubscribeTracks(SubscribeTracks {
3989 request_id: v(1),
3990 namespace_prefix: ns(),
3991 parameters: vec![],
3992 });
3993 let id = ep.receive_request_on_stream(&tracks).unwrap();
3994 assert_eq!(ep.active_subscribe_tracks_count(), 1);
3995 assert_eq!(
3996 ep.active_subscribe_namespace_count(),
3997 0,
3998 "SUBSCRIBE_TRACKS landed among the namespace subscriptions",
3999 );
4000 ep.send_response_on_stream(id, &request_ok()).unwrap();
4001 assert_eq!(ep.subscribe_tracks[&1].state(), SubscribeNamespaceState::Active);
4002
4003 // Section 10.20 puts PUBLISH_SKIPPED on this stream, and only on this
4004 // stream: a SUBSCRIBE_NAMESPACE has no such message.
4005 let skipped = ControlMessage::PublishSkipped(PublishSkipped {
4006 namespace_suffix: ns(),
4007 track_name: b"video".to_vec(),
4008 });
4009 ep.send_response_on_stream(id, &skipped).unwrap();
4010
4011 let mut other = active_client();
4012 let sub_ns = other
4013 .receive_request_on_stream(&ControlMessage::SubscribeNamespace(SubscribeNamespace {
4014 request_id: v(1),
4015 namespace_prefix: ns(),
4016 parameters: vec![],
4017 }))
4018 .unwrap();
4019 other.send_response_on_stream(sub_ns, &request_ok()).unwrap();
4020 assert!(
4021 other.send_response_on_stream(sub_ns, &skipped).is_err(),
4022 "PUBLISH_SKIPPED was accepted on a SUBSCRIBE_NAMESPACE stream",
4023 );
4024 }
4025
4026 /// Draft-19 Table 5 gives NAMESPACE and NAMESPACE_DONE the Stream value
4027 /// "Request", and Section 10.18 has the publisher send them only once the
4028 /// SUBSCRIBE_NAMESPACE has been accepted: "If the SUBSCRIBE_NAMESPACE is
4029 /// successful, the publisher will send matching NAMESPACE messages on the
4030 /// response stream."
4031 ///
4032 /// Draft-17 has neither edge — its message table has no Stream column — so
4033 /// this is the half of the responder a port from draft-17 would leave out.
4034 #[test]
4035 fn namespaces_are_announced_on_the_peers_subscribe_namespace_stream() {
4036 let mut ep = active_client();
4037 let id = ep
4038 .receive_request_on_stream(&ControlMessage::SubscribeNamespace(SubscribeNamespace {
4039 request_id: v(1),
4040 namespace_prefix: ns(),
4041 parameters: vec![],
4042 }))
4043 .unwrap();
4044
4045 let namespace = ControlMessage::Namespace(message::Namespace { namespace_suffix: ns() });
4046 let err = ep.send_response_on_stream(id, &namespace).unwrap_err();
4047 assert_eq!(
4048 err.to_string(),
4049 "namespace error: invalid transition from Pending on event on_namespace_sent",
4050 "a NAMESPACE was allowed ahead of the REQUEST_OK that accepts the request",
4051 );
4052
4053 ep.send_response_on_stream(id, &request_ok()).unwrap();
4054 ep.send_response_on_stream(id, &namespace).unwrap();
4055 ep.send_response_on_stream(
4056 id,
4057 &ControlMessage::NamespaceDone(message::NamespaceDone { namespace_suffix: ns() }),
4058 )
4059 .unwrap();
4060 // One namespace ending does not end the subscription to the prefix.
4061 assert_eq!(ep.subscribe_namespaces[&1].state(), SubscribeNamespaceState::Active);
4062 }
4063
4064 /// Draft-19 Section 10.5 answers Track Properties on a REQUEST_OK that is
4065 /// not a TRACK_STATUS response with a session close. The receive path
4066 /// already refuses them; a responder that writes them would be handing a
4067 /// conforming peer that reason, so the send path refuses them too — and
4068 /// without closing this session, because nothing reached the wire.
4069 #[test]
4070 fn track_properties_are_refused_on_the_way_out_too() {
4071 let properties = vec![KeyValuePair { key: v(0x04), value: KvpValue::Varint(v(1000)) }];
4072 let with_properties = ControlMessage::RequestOk(RequestOk {
4073 parameters: vec![],
4074 track_properties: properties.clone(),
4075 });
4076
4077 let mut ep = active_client();
4078 let id = ep
4079 .receive_request_on_stream(&ControlMessage::SubscribeNamespace(SubscribeNamespace {
4080 request_id: v(1),
4081 namespace_prefix: ns(),
4082 parameters: vec![],
4083 }))
4084 .unwrap();
4085 let err = ep.send_response_on_stream(id, &with_properties).unwrap_err();
4086 assert!(matches!(err, EndpointError::TrackPropertiesOnOutgoingRequestOk(1)), "{err}");
4087 assert_eq!(err.session_error_code(), None, "refusing our own write closed the session");
4088 assert_eq!(
4089 ep.subscribe_namespaces[&1].state(),
4090 SubscribeNamespaceState::Pending,
4091 "the refused response moved the state machine anyway",
4092 );
4093 // The request is still answerable without them.
4094 ep.send_response_on_stream(id, &request_ok()).unwrap();
4095
4096 // TRACK_STATUS_OK is the shape that carries them.
4097 let mut ep = active_client();
4098 let id = ep
4099 .receive_request_on_stream(&ControlMessage::TrackStatus(message::TrackStatus {
4100 request_id: v(1),
4101 track_namespace: ns(),
4102 track_name: b"video".to_vec(),
4103 parameters: vec![],
4104 }))
4105 .unwrap();
4106 ep.send_response_on_stream(id, &with_properties).unwrap();
4107 }
4108}
4109
4110#[cfg(test)]
4111mod tests {
4112 use super::*;
4113 use moqtap_codec::kvp::KvpValue;
4114
4115 fn v(n: u64) -> VarInt {
4116 VarInt::from_u64_moqt(n)
4117 }
4118
4119 fn ns(label: &str) -> TrackNamespace {
4120 TrackNamespace(vec![label.as_bytes().to_vec()])
4121 }
4122
4123 fn peer_subscribe(id: u64, label: &str) -> ControlMessage {
4124 ControlMessage::Subscribe(Subscribe {
4125 request_id: v(id),
4126 track_namespace: ns(label),
4127 track_name: b"video".to_vec(),
4128 parameters: vec![],
4129 })
4130 }
4131
4132 fn peer_fetch(id: u64, label: &str) -> ControlMessage {
4133 ControlMessage::Fetch(Fetch {
4134 request_id: v(id),
4135 fetch_type: FetchType::Standalone,
4136 fetch_payload: FetchPayload::Standalone {
4137 track_namespace: ns(label),
4138 track_name: b"video".to_vec(),
4139 start_group: v(0),
4140 start_object: v(0),
4141 end_group: v(1),
4142 end_object: v(0),
4143 },
4144 parameters: vec![],
4145 })
4146 }
4147
4148 fn peer_subscribe_namespace(id: u64, label: &str) -> ControlMessage {
4149 ControlMessage::SubscribeNamespace(SubscribeNamespace {
4150 request_id: v(id),
4151 namespace_prefix: ns(label),
4152 parameters: vec![],
4153 })
4154 }
4155
4156 fn peer_subscribe_tracks(id: u64, label: &str) -> ControlMessage {
4157 ControlMessage::SubscribeTracks(SubscribeTracks {
4158 request_id: v(id),
4159 namespace_prefix: ns(label),
4160 parameters: vec![],
4161 })
4162 }
4163
4164 fn peer_publish_namespace(id: u64, label: &str) -> ControlMessage {
4165 ControlMessage::PublishNamespace(PublishNamespace {
4166 request_id: v(id),
4167 track_namespace: ns(label),
4168 parameters: vec![],
4169 })
4170 }
4171
4172 fn peer_publish(id: u64, label: &str) -> ControlMessage {
4173 ControlMessage::Publish(Publish {
4174 request_id: v(id),
4175 track_namespace: ns(label),
4176 track_name: b"video".to_vec(),
4177 track_alias: v(id + 100),
4178 parameters: vec![],
4179 track_properties: vec![],
4180 })
4181 }
4182
4183 fn active(role: Role) -> Endpoint {
4184 let mut ep = Endpoint::new(role);
4185 ep.connect().unwrap();
4186 ep.receive_setup(&Setup { options: vec![] }).unwrap();
4187 assert_eq!(ep.session_state(), SessionState::Active);
4188 ep
4189 }
4190
4191 fn update(id: u64) -> RequestUpdate {
4192 RequestUpdate { request_id: v(id), parameters: vec![] }
4193 }
4194
4195 /// A session-fatal error must leave the endpoint unable to carry on: the
4196 /// state machine is Closed and every new request is refused. Asserting the
4197 /// error alone would let a caller ignore it and keep the session running,
4198 /// which is the behaviour draft-19 Section 10.9 forbids.
4199 fn assert_session_failed(ep: &mut Endpoint, err: EndpointError) {
4200 assert_eq!(
4201 err.session_error_code(),
4202 Some(SessionErrorCode::ProtocolViolation),
4203 "{err} should be fatal to the session"
4204 );
4205 assert_eq!(ep.session_state(), SessionState::Closed);
4206 assert!(matches!(
4207 ep.subscribe(ns("a"), b"b".to_vec(), vec![]),
4208 Err(EndpointError::NotActive)
4209 ));
4210 }
4211
4212 /// The other answer, and the one this file gives to a rule draft-19
4213 /// states no consequence for: the message is refused and the session runs
4214 /// on.
4215 ///
4216 /// The three request-stream messages below take this helper rather than
4217 /// [`Self::assert_session_failed`]. Section 3.3's opener sentence does not
4218 /// reach a message arriving on the control stream — it is about what a
4219 /// bidirectional stream may *begin* with — and no sentence in draft-19
4220 /// closes a session over a message being in the wrong place. Table 5's
4221 /// Stream column says where each message is sent and attaches no
4222 /// consequence to a peer that sends one elsewhere.
4223 ///
4224 /// REQUEST_UPDATE is the one exception and takes the helper above: Section
4225 /// 10.9 names two cases and closes over everything else, so a
4226 /// REQUEST_UPDATE on the control stream really is a close the draft asks
4227 /// for.
4228 ///
4229 /// Recovery is not an aspiration here. A control message carries its own
4230 /// length, so the next boundary on the stream is known however this one
4231 /// was refused, and the endpoint that follows really can carry on — which
4232 /// is what the third assertion checks rather than assumes.
4233 fn assert_refused_without_closing(ep: &mut Endpoint, err: &EndpointError) {
4234 assert_eq!(
4235 err.session_error_code(),
4236 None,
4237 "no sentence in draft-19 answers {err} with a close"
4238 );
4239 assert_eq!(ep.session_state(), SessionState::Active);
4240 ep.subscribe(ns("a"), b"b".to_vec(), vec![])
4241 .expect("the session survives a message it could not place");
4242 }
4243
4244 /// Draft-19 Table 5 gives REQUEST_UPDATE the Stream value "Request", and
4245 /// Section 10.9 has it sent "on the same bidi stream as the request".
4246 #[test]
4247 fn a_request_update_belongs_on_its_request_stream_and_not_the_control_stream() {
4248 let mut ep = active(Role::Client);
4249 // A PUBLISH this endpoint made and the peer accepted. Section 10.9
4250 // allows an update on exactly that among the requests made here, so
4251 // this drives the routing question without also being a violation.
4252 let (id, _) = ep.publish(ns("live"), b"video".to_vec(), v(7), vec![], vec![]).unwrap();
4253 ep.receive_request_ok(id, &RequestOk { parameters: vec![], track_properties: vec![] })
4254 .unwrap();
4255
4256 // The request stream is where it belongs, and the request-stream
4257 // dispatcher is the route that has to accept it.
4258 ep.receive_response_on_stream(id, ControlMessage::RequestUpdate(update(id.into_inner())))
4259 .unwrap();
4260
4261 // The control stream is not, and the draft answers that with a close.
4262 let err =
4263 ep.receive_message(ControlMessage::RequestUpdate(update(id.into_inner()))).unwrap_err();
4264 assert!(matches!(err, EndpointError::RequestUpdateOnControlStream), "{err}");
4265 assert_session_failed(&mut ep, err);
4266 }
4267
4268 /// The three messages draft-19 Table 5 places on a request stream that are
4269 /// not responses: NAMESPACE (0x8), NAMESPACE_DONE (0xE) and PUBLISH_SKIPPED
4270 /// (0xF).
4271 ///
4272 /// Table 5's Stream column reads "Request" for all three, the same value it
4273 /// gives REQUEST_UPDATE. Only SETUP is "Control" on its own; GOAWAY is
4274 /// "Control, Request". Inverting the placement accepts them on the control
4275 /// stream and refuses them on the request stream, so a conforming peer
4276 /// sending NAMESPACE on the SUBSCRIBE_NAMESPACE stream that asked for it
4277 /// would have its announcement dropped.
4278 #[test]
4279 fn namespace_and_publish_skipped_belong_on_a_request_stream() {
4280 /// A message name paired with a way to build a fresh one, since each
4281 /// case needs two copies and `ControlMessage` is consumed by both
4282 /// dispatchers.
4283 type Case = (&'static str, fn() -> ControlMessage);
4284
4285 let cases: [Case; 3] = [
4286 ("NAMESPACE", || {
4287 ControlMessage::Namespace(message::Namespace { namespace_suffix: ns("live") })
4288 }),
4289 ("NAMESPACE_DONE", || {
4290 ControlMessage::NamespaceDone(message::NamespaceDone {
4291 namespace_suffix: ns("live"),
4292 })
4293 }),
4294 ("PUBLISH_SKIPPED", || {
4295 ControlMessage::PublishSkipped(PublishSkipped {
4296 namespace_suffix: ns("live"),
4297 track_name: b"video".to_vec(),
4298 })
4299 }),
4300 ];
4301
4302 for (name, build) in cases {
4303 let mut ep = active(Role::Client);
4304 let id = ep.subscribe_namespace(ns("live"), vec![]).unwrap().0;
4305
4306 // Section 10.18 and Section 10.19 make REQUEST_OK or REQUEST_ERROR
4307 // the first message on this stream, so the answer comes before the
4308 // messages that follow it. Sending the NAMESPACE first is a
4309 // different rule's violation and would answer this test's question
4310 // with that rule's error.
4311 ep.receive_response_on_stream(
4312 id,
4313 ControlMessage::RequestOk(RequestOk {
4314 parameters: vec![],
4315 track_properties: vec![],
4316 }),
4317 )
4318 .expect("REQUEST_OK answers the namespace subscription");
4319
4320 // Where Table 5 puts it.
4321 ep.receive_response_on_stream(id, build())
4322 .unwrap_or_else(|e| panic!("{name} belongs on a request stream: {e:?}"));
4323
4324 // Where it does not. Refused, and the session left running.
4325 let err = match ep.receive_message(build()) {
4326 Err(e) => e,
4327 Ok(()) => panic!("{name} must be refused on the control stream: Ok(())"),
4328 };
4329 assert!(
4330 matches!(err, EndpointError::RequestMessageOnControlStream(m) if m == name),
4331 "{name} on the control stream gave {err}"
4332 );
4333 assert_refused_without_closing(&mut ep, &err);
4334 }
4335 }
4336
4337 /// Draft-19 Section 10.9: "An endpoint that receives a REQUEST_UPDATE
4338 /// other than in the two cases above MUST close the session with a
4339 /// PROTOCOL_VIOLATION." Section 10.14 names TRACK_STATUS as one such case:
4340 /// "the subscriber cannot send REQUEST_UPDATE."
4341 #[test]
4342 fn a_request_update_naming_a_non_updatable_request_closes_the_session() {
4343 // An id nothing was ever issued under.
4344 let mut ep = active(Role::Client);
4345 let err = ep.receive_request_update(v(40), &update(40)).unwrap_err();
4346 assert!(matches!(err, EndpointError::UnexpectedRequestUpdate(40)), "{err}");
4347 assert_session_failed(&mut ep, err);
4348
4349 // TRACK_STATUS, which the draft rules out by name.
4350 let mut ep = active(Role::Client);
4351 let (id, _) = ep.track_status(ns("live"), b"video".to_vec(), vec![]).unwrap();
4352 let err = ep.receive_request_update(id, &update(id.into_inner())).unwrap_err();
4353 assert!(matches!(err, EndpointError::UnexpectedRequestUpdate(_)), "{err}");
4354 assert_session_failed(&mut ep, err);
4355 }
4356
4357 /// Draft-19 Section 10.9's first case reaches every request kind: "The
4358 /// sender of a request (SUBSCRIBE, PUBLISH, FETCH, PUBLISH_NAMESPACE,
4359 /// SUBSCRIBE_NAMESPACE, SUBSCRIBE_TRACKS) can later send a REQUEST_UPDATE
4360 /// on the same bidi stream as the request to modify it."
4361 #[test]
4362 fn an_update_from_the_requester_resolves_against_every_request_kind() {
4363 let mut ep = active(Role::Client);
4364 let mut ids = Vec::new();
4365 for msg in [
4366 peer_subscribe(1, "a"),
4367 peer_fetch(3, "b"),
4368 peer_subscribe_namespace(5, "c"),
4369 peer_subscribe_tracks(7, "d"),
4370 peer_publish_namespace(9, "e"),
4371 peer_publish(11, "f"),
4372 ] {
4373 ids.push(ep.receive_request_on_stream(&msg).unwrap());
4374 }
4375 for id in ids {
4376 ep.receive_request_update(id, &update(id.into_inner()))
4377 .unwrap_or_else(|e| panic!("request {} should be updatable: {e}", id.into_inner()));
4378 }
4379 assert_eq!(ep.session_state(), SessionState::Active);
4380 }
4381
4382 /// Section 10.9's second case: "A subscriber can also send REQUEST_UPDATE
4383 /// to modify parameters of a subscription established with PUBLISH."
4384 ///
4385 /// The publication is this endpoint's, so this is the one request of its
4386 /// own that it may be sent an update on, and the one place the sender rule
4387 /// and the request-kind rule disagree.
4388 #[test]
4389 fn an_update_on_a_publish_this_endpoint_made_is_the_second_case() {
4390 let mut ep = active(Role::Client);
4391 let (id, _) = ep.publish(ns("live"), b"video".to_vec(), v(7), vec![], vec![]).unwrap();
4392 ep.receive_request_ok(id, &RequestOk { parameters: vec![], track_properties: vec![] })
4393 .unwrap();
4394 ep.receive_request_update(id, &update(id.into_inner())).unwrap();
4395 assert_eq!(ep.session_state(), SessionState::Active);
4396 }
4397
4398 /// The second case needs the subscription to exist before it can be
4399 /// updated, and the first case does not.
4400 ///
4401 /// Section 5.1: "Once either of these sequences is successful, the
4402 /// subscription moves to the Established state and can be updated by the
4403 /// subscriber using REQUEST_UPDATE." A PUBLISH this endpoint has sent and
4404 /// the peer has not answered is Pending, so a subscriber updating it is
4405 /// updating a subscription that does not exist yet.
4406 ///
4407 /// The first case rests on something else and keeps its own timing:
4408 /// Section 10.9 lets the sender of a request update it "later" with
4409 /// nothing said about the answer, and five of the six kinds it names are
4410 /// not subscriptions at all. Both halves are here so that the asymmetry is
4411 /// the thing asserted rather than a side effect of one of them.
4412 #[test]
4413 fn only_an_established_publication_may_be_updated_by_its_subscriber() {
4414 // Case two, before the answer: the subscription is not established.
4415 let mut ep = active(Role::Client);
4416 let (id, _) = ep.publish(ns("live"), b"video".to_vec(), v(7), vec![], vec![]).unwrap();
4417 let err = ep
4418 .receive_request_update(id, &update(id.into_inner()))
4419 .expect_err("a PUBLISH still waiting for its answer is not an established one");
4420 assert!(matches!(err, EndpointError::UnexpectedRequestUpdate(_)), "{err}");
4421 assert_session_failed(&mut ep, err);
4422
4423 // Case one, before the answer: allowed, and on the same beat.
4424 let mut ep = active(Role::Client);
4425 let peer = ep.receive_request_on_stream(&peer_subscribe(1, "live")).unwrap();
4426 ep.receive_request_update(peer, &update(peer.into_inner()))
4427 .expect("the sender of a request may update it before it is answered");
4428 assert_eq!(ep.session_state(), SessionState::Active);
4429 }
4430
4431 /// Section 10.9: "An endpoint that receives a REQUEST_UPDATE other than in
4432 /// the two cases above MUST close the session with a PROTOCOL_VIOLATION."
4433 ///
4434 /// Neither case reaches a SUBSCRIBE, FETCH or namespace request this
4435 /// endpoint made. Those are modified by the endpoint that made them, which
4436 /// is this one, and an update arriving on one came from the side that has
4437 /// no say over it.
4438 #[test]
4439 fn an_update_on_a_request_this_endpoint_made_closes_the_session() {
4440 for which in 0..5 {
4441 let mut ep = active(Role::Client);
4442 let (id, _) = match which {
4443 0 => ep.subscribe(ns("live"), b"video".to_vec(), vec![]).unwrap(),
4444 1 => {
4445 ep.fetch(ns("live"), b"video".to_vec(), v(0), v(0), v(1), v(0), vec![]).unwrap()
4446 }
4447 2 => ep.subscribe_namespace(ns("live"), vec![]).unwrap(),
4448 3 => ep.subscribe_tracks(ns("live"), vec![]).unwrap(),
4449 _ => ep.publish_namespace(ns("live"), vec![]).unwrap(),
4450 };
4451 let err = ep
4452 .receive_request_update(id, &update(id.into_inner()))
4453 .expect_err("an update on a request this endpoint made is neither case");
4454 assert!(matches!(err, EndpointError::UnexpectedRequestUpdate(_)), "{which}: {err}");
4455 assert_session_failed(&mut ep, err);
4456 }
4457 }
4458
4459 /// A REQUEST_UPDATE whose own Request ID names a different request than
4460 /// the stream it arrived on was sent on a stream that is not its
4461 /// request's, which is the same violation.
4462 #[test]
4463 fn a_request_update_whose_id_disagrees_with_its_stream_closes_the_session() {
4464 let mut ep = active(Role::Client);
4465 let (a, _) = ep.subscribe(ns("live"), b"video".to_vec(), vec![]).unwrap();
4466 let (b, _) = ep.subscribe(ns("live"), b"audio".to_vec(), vec![]).unwrap();
4467 let err = ep.receive_request_update(a, &update(b.into_inner())).unwrap_err();
4468 assert!(matches!(err, EndpointError::UnexpectedRequestUpdate(_)), "{err}");
4469 assert_session_failed(&mut ep, err);
4470 }
4471
4472 /// Draft-19 Section 10.5: Track Properties "are populated in
4473 /// TRACK_STATUS_OK; they are empty in PUBLISH_OK, REQUEST_UPDATE_OK,
4474 /// SUBSCRIBE_NAMESPACE_OK and PUBLISH_NAMESPACE_OK. If an endpoint
4475 /// receives Track Properties in one of these messages it MUST close the
4476 /// session with a PROTOCOL_VIOLATION."
4477 #[test]
4478 fn track_properties_on_a_request_ok_that_is_not_a_track_status_close_the_session() {
4479 let properties = vec![KeyValuePair { key: v(0x04), value: KvpValue::Varint(v(1000)) }];
4480
4481 let mut ep = active(Role::Client);
4482 let (id, _) = ep.subscribe_namespace(ns("live"), vec![]).unwrap();
4483 let err = ep
4484 .receive_request_ok(
4485 id,
4486 &RequestOk { parameters: vec![], track_properties: properties.clone() },
4487 )
4488 .unwrap_err();
4489 assert!(matches!(err, EndpointError::TrackPropertiesOnNonTrackStatus(_)), "{err}");
4490 assert_session_failed(&mut ep, err);
4491
4492 // TRACK_STATUS_OK is the shape that carries them, and still does.
4493 let mut ep = active(Role::Client);
4494 let (id, _) = ep.track_status(ns("live"), b"video".to_vec(), vec![]).unwrap();
4495 ep.receive_request_ok(id, &RequestOk { parameters: vec![], track_properties: properties })
4496 .unwrap();
4497 assert_eq!(ep.session_state(), SessionState::Active);
4498 }
4499
4500 /// Draft-19 Section 10.4: "A GOAWAY MAY also be sent on a request stream
4501 /// to initiate migration of that individual request." Table 5 gives GOAWAY
4502 /// the Stream value "Control, Request".
4503 ///
4504 /// The session must survive it — only the one request is being moved.
4505 #[test]
4506 fn a_goaway_on_a_request_stream_migrates_that_request_and_not_the_session() {
4507 let mut ep = active(Role::Client);
4508 let (id, _) = ep.subscribe(ns("live"), b"video".to_vec(), vec![]).unwrap();
4509
4510 let goaway =
4511 GoAway { new_session_uri: b"https://elsewhere.example/moq".to_vec(), timeout: v(0) };
4512 ep.receive_response_on_stream(id, ControlMessage::GoAway(goaway)).unwrap();
4513
4514 assert_eq!(ep.session_state(), SessionState::Active);
4515 // Per-request migration leaves the session-wide URI unset; the control
4516 // stream is what sets that.
4517 assert_eq!(ep.goaway_uri(), None);
4518 // And a new request is still allowed.
4519 ep.subscribe(ns("live"), b"audio".to_vec(), vec![]).unwrap();
4520 }
4521
4522 /// Draft-19 Section 10.4: "If a server receives a GOAWAY with a non-zero
4523 /// New Session URI Length it MUST close the session with a
4524 /// PROTOCOL_VIOLATION."
4525 #[test]
4526 fn a_server_refuses_a_goaway_carrying_a_new_session_uri() {
4527 let mut ep = active(Role::Server);
4528 let goaway =
4529 GoAway { new_session_uri: b"https://elsewhere.example/moq".to_vec(), timeout: v(0) };
4530 let err = ep.receive_goaway(&goaway).unwrap_err();
4531 assert!(matches!(err, EndpointError::GoAwayUriAtServer), "{err}");
4532 assert_eq!(ep.goaway_uri(), None);
4533 assert_session_failed(&mut ep, err);
4534
4535 // An empty URI is the form a server may legitimately receive: it says
4536 // the peer is going away, not where to go.
4537 let mut ep = active(Role::Server);
4538 ep.receive_goaway(&GoAway { new_session_uri: vec![], timeout: v(0) }).unwrap();
4539 assert_eq!(ep.session_state(), SessionState::Draining);
4540
4541 // A client is the side that may be redirected.
4542 let mut ep = active(Role::Client);
4543 ep.receive_goaway(&goaway).unwrap();
4544 assert_eq!(ep.goaway_uri(), Some(&b"https://elsewhere.example/moq"[..]));
4545 }
4546
4547 /// Draft-19 Section 10.12, Table 6: Fetch Type 0x2 is a Relative Joining
4548 /// Fetch and 0x3 an Absolute Joining Fetch.
4549 #[test]
4550 fn both_joining_fetch_types_can_be_built() {
4551 let mut ep = active(Role::Client);
4552 let (_, relative) = ep.joining_fetch(v(0), v(2), Vec::new()).unwrap();
4553 let (_, absolute) = ep.absolute_joining_fetch(v(0), v(9), Vec::new()).unwrap();
4554
4555 let types: Vec<FetchType> = [relative, absolute]
4556 .iter()
4557 .map(|m| match m {
4558 ControlMessage::Fetch(f) => f.fetch_type,
4559 other => panic!("expected a FETCH, got {other:?}"),
4560 })
4561 .collect();
4562 assert_eq!(types, vec![FetchType::RelativeJoining, FetchType::AbsoluteJoining]);
4563
4564 // Both are tracked as fetches, so their responses resolve.
4565 assert_eq!(ep.active_fetch_count(), 2);
4566 }
4567}