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