Skip to main content

moqtap_client/draft23/
endpoint.rs

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