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