moqtap_client/draft07/endpoint.rs
1use std::collections::{HashMap, HashSet};
2use std::sync::Mutex;
3
4use crate::draft07::fetch::{FetchError, FetchState, FetchStateMachine};
5use crate::draft07::namespace::{
6 AnnounceState, AnnounceStateMachine, NamespaceError, SubscribeAnnouncesState,
7 SubscribeAnnouncesStateMachine,
8};
9use crate::draft07::session::setup::{self, SetupError};
10use crate::draft07::session::state::{SessionError, SessionState, SessionStateMachine};
11use crate::draft07::session::subscribe_id::{SubscribeIdAllocator, SubscribeIdError};
12use crate::draft07::subscription::{
13 SubscriptionError, SubscriptionState, SubscriptionStateMachine,
14};
15use crate::draft07::track_status::{TrackStatusError, TrackStatusState, TrackStatusStateMachine};
16use crate::forwarding_preference::{ObjectForwardingPreference, TrackForwardingPreferences};
17use moqtap_codec::draft07::error_codes::{SessionErrorCode, SubscribeErrorCode};
18use moqtap_codec::draft07::message::{
19 self, Announce, AnnounceCancel, AnnounceError, AnnounceOk, ClientSetup, ControlMessage, Fetch,
20 FetchCancel, GoAway, MaxSubscribeId, ServerSetup, Subscribe, SubscribeAnnounces,
21 SubscribeAnnouncesError, SubscribeAnnouncesOk, SubscribeDone, SubscribeError, SubscribeOk,
22 SubscribeUpdate, TrackStatus, TrackStatusRequest, Unannounce, Unsubscribe,
23 UnsubscribeAnnounces,
24};
25use moqtap_codec::kvp::KeyValuePair;
26use moqtap_codec::types::*;
27use moqtap_codec::varint::VarInt;
28
29/// Key identifying a namespace (used for Announce / SubscribeAnnounces maps).
30type NamespaceKey = Vec<Vec<u8>>;
31
32/// Key identifying a track (namespace + track name).
33type TrackKey = (Vec<Vec<u8>>, Vec<u8>);
34
35/// Which side of the session this endpoint is.
36///
37/// Two rules on this draft turn on the answer, and both live in the setup and
38/// migration messages rather than in any per-request state: the PATH parameter
39/// belongs to the client, and a GOAWAY may only travel from the server to the
40/// client. Subscribe IDs on this draft are a single session-wide sequence and do
41/// not depend on who allocates them, so nothing else reads this.
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum Role {
44 /// The endpoint that opened the session.
45 Client,
46 /// The endpoint that accepted it.
47 Server,
48}
49
50/// Errors that can occur during draft-07 endpoint operations.
51#[derive(Debug, thiserror::Error)]
52pub enum EndpointError {
53 /// A GOAWAY arrived at a server.
54 ///
55 /// Section 6.3: "The server MUST terminate the session with a Protocol
56 /// Violation (Section 3.5) if it receives a GOAWAY message." This draft states the rule
57 /// about the message rather than about its contents - a GOAWAY carrying no
58 /// URI at all is refused here just the same. Draft-08 replaced the sentence
59 /// with a narrower one that turns on a non-zero New Session URI Length, so
60 /// this refusal is draft-07's alone and does not carry forward.
61 #[error("GOAWAY received at a server")]
62 GoAwayAtServer,
63 /// A session-level state machine error.
64 #[error("session error: {0}")]
65 Session(#[from] SessionError),
66 /// A subscribe ID allocation or validation error.
67 #[error("subscribe ID error: {0}")]
68 SubscribeId(#[from] SubscribeIdError),
69 /// A subscription state machine error.
70 #[error("subscription error: {0}")]
71 Subscription(#[from] SubscriptionError),
72 /// A fetch state machine error.
73 #[error("fetch error: {0}")]
74 Fetch(#[from] FetchError),
75 /// A namespace state machine error.
76 #[error("namespace error: {0}")]
77 Namespace(#[from] NamespaceError),
78 /// A track status state machine error.
79 #[error("track status error: {0}")]
80 TrackStatus(#[from] TrackStatusError),
81 /// A setup negotiation error.
82 #[error("setup error: {0}")]
83 Setup(#[from] SetupError),
84 /// The subscribe ID does not match any known state machine.
85 #[error("unknown subscribe ID: {0}")]
86 UnknownSubscribe(u64),
87 /// The track namespace does not match any known state machine.
88 #[error("unknown namespace")]
89 UnknownNamespace,
90 /// The (namespace, track) pair does not match any known track status request.
91 #[error("unknown track status request")]
92 UnknownTrackStatus,
93 /// A message about a track status named a track the peer has not asked
94 /// about.
95 ///
96 /// Section 6.12 makes the request the subscriber's: "A potential subscriber
97 /// sends a 'TRACK_STATUS_REQUEST' message on the control stream to obtain
98 /// information about the current status of a given track." What an answer
99 /// answers is therefore a request the **peer** made, so the record it
100 /// reaches for is the one this endpoint keeps of what the peer has asked
101 /// about.
102 ///
103 /// Separate from [`EndpointError::UnknownTrackStatus`], which is the same
104 /// miss on the requests this endpoint made, so a caller can tell which of
105 /// the two maps came up empty.
106 #[error("the peer has asked for no status of this track")]
107 UnknownPeerTrackStatus,
108 /// A SUBSCRIBE arrived for a namespace the peer had cancelled with
109 /// ANNOUNCE_CANCEL.
110 #[error("subscribe for a namespace the peer cancelled")]
111 SubscribeAfterAnnounceCancel,
112 /// The session is not in the Active state.
113 #[error("session not active")]
114 NotActive,
115 /// The session is draining and cannot accept new requests.
116 #[error("session is draining, no new requests allowed")]
117 Draining,
118 /// A filter that names a start location was asked for through a helper
119 /// that has no start location to give it.
120 #[error("this filter type needs a start location; use the range form of this call")]
121 FilterNeedsRange,
122 /// A setup parameter's value could not be read as the type its key implies.
123 #[error("setup parameter {0:#x} has a malformed value")]
124 MalformedSetupParameter(
125 /// Key of the offending parameter.
126 u64,
127 ),
128 /// A Subscribe ID the peer chose did not increase on the last one it used.
129 #[error("peer subscribe ID {0} does not increase on {1}")]
130 PeerSubscribeIdNotIncreasing(
131 /// The Subscribe ID that arrived.
132 u64,
133 /// The highest Subscribe ID the peer had used before it.
134 u64,
135 ),
136 /// A second GOAWAY arrived on the control stream.
137 ///
138 /// The GOAWAY that says the peer is going away is one message, and the
139 /// draft answers a repeat of it with a session close rather than with an
140 /// error about the second message: there is no state a second one could
141 /// move that the first has not already moved.
142 #[error("a second GOAWAY arrived on the control stream")]
143 RepeatedGoAway,
144
145 /// A Track Alias names two tracks at once.
146 ///
147 /// Section 6.4, on the Track Alias the subscriber chooses in SUBSCRIBE:
148 /// "If the Track Alias is already being used for a different track, the
149 /// publisher MUST close the session with a Duplicate Track Alias error".
150 /// Section 6.16 states the other end of the same rule, on the alias a
151 /// SUBSCRIBE_ERROR may offer to retry with: "If this Track Alias is
152 /// already in use, the subscriber MUST close the connection with a
153 /// Duplicate Track Alias error".
154 ///
155 /// The session is over: this endpoint's own state has moved to Closed and
156 /// the code the transport should close with is in
157 /// [`EndpointError::session_error_code`].
158 #[error(
159 "track alias {alias} already names the track of {established_side} subscribe \
160 {established}; {offered_side} subscribe {offered} names a different one"
161 )]
162 DuplicateTrackAlias {
163 /// The alias both tracks are named by.
164 alias: u64,
165 /// Which end opened the subscription that holds the alias.
166 established_side: SubscribeSide,
167 /// That subscription's identifier, in its own end's sequence.
168 established: u64,
169 /// Which end opened the subscription naming it for another track.
170 offered_side: SubscribeSide,
171 /// That subscription's identifier, in its own end's sequence.
172 offered: u64,
173 },
174 /// This endpoint was asked to give a Track Alias to a second track.
175 ///
176 /// The same rule as [`EndpointError::DuplicateTrackAlias`] read at the end
177 /// that chooses the alias. Section 3.5 describes the code as "The
178 /// endpoint attempted to use a Track Alias that was already in use", and
179 /// Section 6.4 says what the receiving publisher does about it, so a
180 /// SUBSCRIBE built this way is one the peer must answer by ending the
181 /// session.
182 ///
183 /// The message is refused instead, and nothing else moves: no Subscribe ID
184 /// is spent, no subscription is created, and the session stays as it was.
185 /// The alias never reaches the peer, so there is nothing for the peer to
186 /// close over.
187 #[error("track alias {alias} already names the track of {side} subscribe {held}")]
188 TrackAliasInUse {
189 /// The alias that is already spoken for.
190 alias: u64,
191 /// Which end opened the subscription holding it.
192 side: SubscribeSide,
193 /// That subscription's identifier, in its own end's sequence.
194 held: u64,
195 },
196 /// A track's objects were framed two different ways.
197 ///
198 /// Section 7: "Every Track has a single 'Object Forwarding Preference' and
199 /// the Original Publisher MUST NOT mix different forwarding preferences
200 /// within a single track. If a subscriber receives different forwarding
201 /// preferences for a track, it SHOULD close the session with an error of
202 /// 'Protocol Violation'."
203 ///
204 /// The framing is the preference: an object on a subgroup stream has the
205 /// Subgroup preference and an object in a datagram has the Datagram one,
206 /// so the track's first object settles the property and this is every
207 /// later object measured against it.
208 #[error(
209 "track alias {alias} carries objects framed as {established}, and one is \
210 framed as {offered}"
211 )]
212 MixedForwardingPreference {
213 /// The Track Alias the offending object named.
214 alias: u64,
215 /// The framing the track's earlier objects settled on.
216 established: ObjectForwardingPreference,
217 /// The framing the offending object used.
218 offered: ObjectForwardingPreference,
219 },
220 /// A SUBSCRIBE_UPDATE named an identifier no subscription the peer opened
221 /// has ever been given.
222 ///
223 /// Section 6.5: "A publisher SHOULD close the Session as a 'Protocol
224 /// Violation' if the SUBSCRIBE_UPDATE violates either rule or if the
225 /// subscriber specifies a Subscribe ID that does not exist within the Session."
226 ///
227 /// **SHOULD**, so this is reported and the session is left running. From
228 /// draft-12 the same sentence says MUST, and there the session ends. An
229 /// endpoint that wants the close on these drafts has everything it needs
230 /// to make it: the error names the identifier that was not found.
231 ///
232 /// A subscription that has **ended** is not this: it existed. That is why
233 /// the record of an inbound SUBSCRIBE outlives the subscription, and why
234 /// an update naming an ended one is refused by the flow rather than by
235 /// this error.
236 #[error("SUBSCRIBE_UPDATE names subscribe {0}, which no subscription the peer opened has had")]
237 UpdateForUnknownSubscribe(u64),
238 /// A message about an announcement named a namespace the peer has not
239 /// announced.
240 ///
241 /// Section 6.11 says what a cancellation is for: the subscriber "will stop
242 /// sending new subscriptions for tracks within the provided Track
243 /// Namespace". What a withdrawal ends and a cancellation revokes is an
244 /// announcement the **peer** made, so the record they reach for is the one
245 /// this endpoint keeps of the peer's announcements.
246 ///
247 /// Separate from [`EndpointError::UnknownNamespace`], which is the same
248 /// miss on the announcements this endpoint made, so a caller can tell which
249 /// of the two maps came up empty.
250 #[error("the peer has made no live announcement for this namespace")]
251 UnknownPeerNamespace,
252 /// A message about a namespace subscription named a prefix the peer has
253 /// not subscribed to.
254 ///
255 /// Section 6.14: "A subscriber issues a UNSUBSCRIBE_ANNOUNCES message to a publisher indicating it is no longer interested in ANNOUNCE and UNANNOUNCE messages for the specified track namespace prefix."
256 ///
257 /// What a withdrawal ends is a namespace subscription the **peer** made,
258 /// so the record it reaches for is the one this endpoint keeps of the
259 /// peer's. A namespace subscription this endpoint made is withdrawn by
260 /// [`Endpoint::unsubscribe_announces`], which is the same message travelling the other
261 /// way and answers with [`EndpointError::UnknownNamespace`].
262 #[error("the peer has made no live namespace subscription for this prefix")]
263 UnknownPeerNamespaceSubscription,
264 /// The peer subscribed to a namespace prefix overlapping one it is
265 /// already subscribed to.
266 ///
267 /// Section 6.13: "A subscriber cannot make overlapping namespace
268 /// subscriptions on a single session. Within a session, if a publisher
269 /// receives a SUBSCRIBE_ANNOUNCES with a Track Namespace Prefix that is a
270 /// prefix of an earlier SUBSCRIBE_ANNOUNCES or vice versa, it MUST
271 /// respond with SUBSCRIBE_ANNOUNCES_ERROR, with error code
272 /// SUBSCRIBE_ANNOUNCES_OVERLAP."
273 ///
274 /// The request is refused where it arrives and nothing is written down for
275 /// it, which is the only outcome this draft can express. SUBSCRIBE_ANNOUNCES
276 /// carries no Request ID here, so the acceptance, the refusal and the
277 /// withdrawal all name a Track Namespace Prefix and nothing else. Two
278 /// namespace subscriptions under one prefix would therefore have answers
279 /// that cannot be told apart, and an equal prefix is the first case the
280 /// sentence above names.
281 ///
282 /// The code the sentence gives the refusal, SUBSCRIBE_ANNOUNCES_OVERLAP, is
283 /// named in prose and appears in no registry this draft defines, so there
284 /// is no number for this crate to put on the wire. A caller that wants to
285 /// send the refusal builds it from the message it has just been handed.
286 #[error("the namespace prefix the peer subscribed to overlaps one it already has")]
287 PeerPrefixOverlap,
288 /// This endpoint was asked to subscribe to a namespace prefix overlapping
289 /// one it is already subscribed to.
290 ///
291 /// The first half of the same sentence, which is addressed to the
292 /// subscriber: "A subscriber cannot make overlapping namespace
293 /// subscriptions on a single session."
294 ///
295 /// The message is refused instead of built, and nothing else moves: no
296 /// state machine is created and the session stays as it was. The request
297 /// never reaches the peer, so there is nothing for the peer to refuse.
298 ///
299 /// A subscription that has been withdrawn still counts, because the
300 /// publisher's half of the sentence weighs a new prefix against "an
301 /// earlier SUBSCRIBE_ANNOUNCES" rather than against a live one. Drafts
302 /// from 12 on say "active" instead, and there a withdrawn one stops
303 /// counting.
304 #[error("the namespace prefix overlaps one this endpoint is already subscribed to")]
305 OwnPrefixOverlap,
306}
307
308/// Whether two namespace prefixes overlap.
309///
310/// Section 6.13: "A subscriber cannot make overlapping namespace
311/// subscriptions on a single session. Within a session, if a publisher
312/// receives a SUBSCRIBE_ANNOUNCES with a Track Namespace Prefix that is a
313/// prefix of an earlier SUBSCRIBE_ANNOUNCES or vice versa, it MUST respond
314/// with SUBSCRIBE_ANNOUNCES_ERROR, with error code
315/// SUBSCRIBE_ANNOUNCES_OVERLAP."
316///
317/// A namespace matches a namespace subscription when the subscription's
318/// prefix is a prefix of it, so two prefixes select overlapping sets of
319/// namespaces exactly when one of them is a prefix of the other. Equal
320/// prefixes are that case as well: every prefix is a prefix of itself, and
321/// two equal ones select the same set.
322fn prefixes_overlap(a: &[Vec<u8>], b: &[Vec<u8>]) -> bool {
323 let shared = a.len().min(b.len());
324 a[..shared] == b[..shared]
325}
326
327/// Which end of the session opened a subscription.
328///
329/// It takes this and an identifier together to name one on this draft. Each
330/// end allocates Subscribe IDs from zero, nothing in the draft separates the
331/// two sequences, and this endpoint keeps the peer's apart from its own - so
332/// the peer's subscribe 3 and this endpoint's subscribe 3 are two
333/// subscriptions, not one.
334#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
335pub enum SubscribeSide {
336 /// A SUBSCRIBE this endpoint sent, carrying the alias it chose.
337 Ours,
338 /// A SUBSCRIBE the peer sent, carrying the alias the peer chose.
339 Peers,
340}
341
342impl std::fmt::Display for SubscribeSide {
343 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
344 match self {
345 SubscribeSide::Ours => f.write_str("our"),
346 SubscribeSide::Peers => f.write_str("the peer's"),
347 }
348 }
349}
350
351/// A SUBSCRIBE the peer sent, and how far the subscription it opened has got.
352struct InboundSubscribe {
353 /// The message as it arrived, which is what the application answers from.
354 message: Subscribe,
355 /// The subscription's state, driven from the publisher's end.
356 state: SubscriptionStateMachine,
357}
358/// A FETCH the peer sent, and how far the fetch it opened has got.
359struct InboundFetch {
360 /// The message as it arrived, which is what the application answers from.
361 message: Fetch,
362 /// The fetch's state, driven from the end that serves it.
363 state: FetchStateMachine,
364}
365
366/// A Track Alias attached to a Full Track Name, and nothing else: the
367/// subscription whose lifetime the attachment follows is the map's key.
368///
369/// SUBSCRIBE carries the alias and the track in the one message, whichever end
370/// sends it, so a binding is complete from the moment it is made. That stops
371/// being true at draft-12, where the alias arrives in the answer instead.
372#[derive(Debug, Clone)]
373struct TrackBinding {
374 namespace: TrackNamespace,
375 name: Vec<u8>,
376 alias: u64,
377}
378
379impl EndpointError {
380 /// The code to close the session with, when draft-07 answers this error
381 /// with a close rather than leaving it to the one request it concerns.
382 ///
383 /// `None` means the error is recoverable: the caller may report it, give
384 /// up on the request it concerns, and keep the session running. `Some`
385 /// means the draft ends the session, and the endpoint has already moved
386 /// its own state to Closed - the code is what the transport should carry.
387 ///
388 /// The table grows one rule at a time, and a rule joins it with a gate
389 /// that drives the bytes at a real connection and reads the close code
390 /// back off the wire. An arm added without one asserts nothing: from
391 /// inside the process the session ends either way, and only the peer can
392 /// tell the difference.
393 pub fn session_error_code(&self) -> Option<SessionErrorCode> {
394 match self {
395 // Section 6.20 answers a ceiling that does not increase with a
396 // close, and names this code for it.
397 EndpointError::SubscribeId(SubscribeIdError::Decreased(..)) => {
398 Some(SessionErrorCode::ProtocolViolation)
399 }
400 // The same section answers a Subscribe ID that reaches the ceiling
401 // this endpoint advertised, and names a different code for it.
402 EndpointError::SubscribeId(SubscribeIdError::ExceedsMax(..)) => {
403 Some(SessionErrorCode::TooManySubscribes)
404 }
405 // Section 6.11 answers a SUBSCRIBE arriving for a namespace
406 // the peer has cancelled, and names this code in the same
407 // sentence: "it SHOULD close the session as a 'Protocol
408 // Violation'".
409 EndpointError::SubscribeAfterAnnounceCancel => {
410 Some(SessionErrorCode::ProtocolViolation)
411 }
412 // Section 6.3 answers a GOAWAY that repeats one already
413 // received, and names this code in the same sentence.
414 EndpointError::RepeatedGoAway => Some(SessionErrorCode::ProtocolViolation),
415 // The same section answers a GOAWAY arriving at a server at all -
416 // this draft states the rule about the message rather than about
417 // the URI it carries - and names this code for it.
418 EndpointError::GoAwayAtServer => Some(SessionErrorCode::ProtocolViolation),
419 // Section 6.4 answers a SUBSCRIBE whose Track Alias already
420 // names a different track with a session close, and Section 6.16
421 // answers the retry alias a SUBSCRIBE_ERROR offers the same way.
422 // Section 3.5 names this code for both.
423 EndpointError::DuplicateTrackAlias { .. } => {
424 Some(SessionErrorCode::DuplicateTrackAlias)
425 }
426 // Section 7 answers a track whose objects mix forwarding
427 // preferences, and names this code in the same sentence: "it SHOULD
428 // close the session with an error of 'Protocol Violation'".
429 //
430 // SHOULD, so the close is the caller's to make. The code lives here
431 // and `Connection::close_for_data_stream` is what carries it, the
432 // same opt-in every other rule broken on a data stream takes.
433 EndpointError::MixedForwardingPreference { .. } => {
434 Some(SessionErrorCode::ProtocolViolation)
435 }
436 _ => None,
437 }
438 }
439}
440
441/// Unified draft-07 MoQT endpoint wrapping session lifecycle, subscribe ID
442/// allocation, and all per-flow state machines (subscriptions, fetches,
443/// announces, subscribe-announces, track statuses).
444pub struct Endpoint {
445 /// Which side of the session this is. Read only by the GOAWAY rule.
446 role: Role,
447 session: SessionStateMachine,
448 subscribe_ids: SubscribeIdAllocator,
449 /// Tracks the MAX_SUBSCRIBE_ID we have advertised to the peer.
450 advertised_max_id: u64,
451 /// The highest Subscribe ID the peer has used, once it has used one.
452 peer_highest_subscribe_id: Option<u64>,
453 subscriptions: HashMap<u64, SubscriptionStateMachine>,
454 /// Subscriptions the peer opened with SUBSCRIBE, each from the moment its
455 /// message arrived to the end of the flow.
456 ///
457 /// Separate from `subscriptions`, which holds the ones this endpoint
458 /// opened, because the identifiers do not separate themselves: see
459 /// [`SubscribeSide`].
460 inbound_subscribes: HashMap<u64, InboundSubscribe>,
461 /// Every FETCH the peer has sent, from arrival to the end of the fetch.
462 ///
463 /// Separate from `fetches`, which holds the ones this endpoint made,
464 /// because the identifiers do not separate themselves: both ends allocate
465 /// from zero, so one number can name a fetch at each end at once.
466 inbound_fetches: HashMap<u64, InboundFetch>,
467 /// Every Track Alias in use in this session, and the track each one names.
468 ///
469 /// "Already being used" is what makes this a table rather than a set: an
470 /// alias whose subscription has ended is free again. The table records the
471 /// binding and reads liveness back off the subscription's own state
472 /// machine, rather than keeping a second copy of it that every path ending
473 /// a subscription would have to remember to prune.
474 /// What each track's objects have been framed as, so far.
475 ///
476 /// Behind a lock because this is the one endpoint fact a *data* stream
477 /// settles, and the data plane reaches the endpoint through `&Connection`:
478 /// a caller may hold one across tasks while it reads streams and datagrams,
479 /// so there is no `&mut` to reach the rest of this struct with.
480 forwarding_preferences: Mutex<TrackForwardingPreferences>,
481 track_bindings: HashMap<(SubscribeSide, u64), TrackBinding>,
482 fetches: HashMap<u64, FetchStateMachine>,
483 subscribe_announces: HashMap<NamespaceKey, SubscribeAnnouncesStateMachine>,
484 /// Namespace subscriptions the **peer** made, keyed by the prefix each
485 /// one names.
486 ///
487 /// The prefix is the whole of a request's name on this draft: the
488 /// acceptance, the refusal and the withdrawal all carry a Track Namespace
489 /// Prefix and nothing else, so one prefix has one record here. A second
490 /// SUBSCRIBE_ANNOUNCES under a prefix already subscribed replaces it, which is
491 /// a case Section 6.13 forbids rather than one this map decides.
492 inbound_subscribe_announces: HashMap<NamespaceKey, InboundSubscribeAnnounces>,
493 announces: HashMap<NamespaceKey, AnnounceStateMachine>,
494 /// Namespaces the peer has cancelled with ANNOUNCE_CANCEL.
495 ///
496 /// Separate from the announcement's own state, which reaches Done by two
497 /// routes: this endpoint sending UNANNOUNCE, and the peer sending
498 /// ANNOUNCE_CANCEL. Section 6.11 is about the second only, so a Done
499 /// announcement is not enough to judge by.
500 ///
501 /// It is retired rather than accumulated: announcing the same namespace
502 /// again replaces the entry below and clears this, because the peer's
503 /// cancel was of the announcement that is now over.
504 announce_cancelled: HashSet<NamespaceKey>,
505 /// Announcements the **peer** made, keyed by the namespace each
506 /// names, which is all this draft's ANNOUNCE carries to name it by.
507 inbound_announces: HashMap<NamespaceKey, InboundAnnounce>,
508 track_statuses: HashMap<TrackKey, TrackStatusStateMachine>,
509 /// Track statuses the **peer** asked about, keyed by the track each names.
510 ///
511 /// Kept apart from `track_statuses`, which holds the ones this endpoint
512 /// asked about: the two are answered by opposite ends. This draft's
513 /// TRACK_STATUS_REQUEST carries no identifier of its own, so the track it
514 /// names is the only thing an answer can be matched to, which is the key
515 /// the outbound map is under for the same reason.
516 inbound_track_statuses: HashMap<TrackKey, InboundTrackStatus>,
517 negotiated_version: Option<VarInt>,
518 offered_versions: Vec<VarInt>,
519 goaway_uri: Option<Vec<u8>>,
520}
521
522impl Default for Endpoint {
523 fn default() -> Self {
524 Self::new(Role::Client)
525 }
526}
527
528/// An announcement the peer made with ANNOUNCE.
529///
530/// Kept apart from the announcements this endpoint made because the two are
531/// answered by opposite ends: this one is waiting for an answer from here,
532/// and the other for one from the peer.
533struct InboundAnnounce {
534 /// The message as it arrived, which is what the application answers from.
535 message: Announce,
536 /// How far the announcement it makes has got.
537 state: AnnounceStateMachine,
538}
539/// A track status the peer asked for with TRACK_STATUS_REQUEST.
540///
541/// Kept apart from the ones this endpoint asked for because the two are
542/// answered by opposite ends: this one is waiting for an answer from here,
543/// and the other for one from the peer.
544struct InboundTrackStatus {
545 /// The message as it arrived, which is what the answer is built from.
546 message: TrackStatusRequest,
547 /// How far the request it opened has got.
548 state: TrackStatusStateMachine,
549}
550/// A SUBSCRIBE_ANNOUNCES the peer sent, and how far the namespace subscription it opens
551/// has got.
552///
553/// Kept apart from `subscribe_announces`, which holds the ones this endpoint made: the
554/// two are answered by opposite ends, and this one is waiting for an answer
555/// from here.
556struct InboundSubscribeAnnounces {
557 /// The message as it arrived, which is what the answer is built from.
558 message: SubscribeAnnounces,
559 /// How far the namespace subscription it opens has got.
560 state: SubscribeAnnouncesStateMachine,
561}
562impl Endpoint {
563 /// Create a new draft-07 endpoint for the given role.
564 pub fn new(role: Role) -> Self {
565 Self {
566 role,
567 session: SessionStateMachine::new(),
568 subscribe_ids: SubscribeIdAllocator::new(),
569 advertised_max_id: 0,
570 peer_highest_subscribe_id: None,
571 subscriptions: HashMap::new(),
572 inbound_subscribes: HashMap::new(),
573 inbound_fetches: HashMap::new(),
574 track_bindings: HashMap::new(),
575 forwarding_preferences: Mutex::new(TrackForwardingPreferences::new()),
576 fetches: HashMap::new(),
577 subscribe_announces: HashMap::new(),
578 inbound_subscribe_announces: HashMap::new(),
579 announces: HashMap::new(),
580 announce_cancelled: HashSet::new(),
581 inbound_announces: HashMap::new(),
582 track_statuses: HashMap::new(),
583 inbound_track_statuses: HashMap::new(),
584 negotiated_version: None,
585 offered_versions: Vec::new(),
586 goaway_uri: None,
587 }
588 }
589
590 // ── Track aliases ──────────────────────────────────────────
591
592 /// The subscription already using `alias` for a track other than
593 /// (`namespace`, `name`), or `None` when the alias is free for that track.
594 ///
595 /// # Why the set is read rather than kept
596 ///
597 /// Section 6.4 says "already being used", and a subscription that has
598 /// ended is not using anything. Asking each binding's own state machine is
599 /// what makes an alias free again the instant its track's subscription
600 /// ends, with nothing to prune on the way out - and a path that ended a
601 /// subscription without telling this table would otherwise hold the alias
602 /// forever and refuse the peer's next, conforming, use of it.
603 ///
604 /// # Why a binding for the same track is not a conflict
605 ///
606 /// The rule is about a Track Alias naming two tracks, not about naming one
607 /// track twice. A second subscription to the track an alias already names
608 /// breaks nothing this section states.
609 fn alias_holder(
610 &self,
611 alias: u64,
612 namespace: &TrackNamespace,
613 name: &[u8],
614 ) -> Option<(SubscribeSide, u64)> {
615 self.track_bindings.iter().find_map(|(&key, binding)| {
616 let other_track = binding.namespace != *namespace || binding.name != name;
617 (binding.alias == alias && other_track && self.binding_is_live(key)).then_some(key)
618 })
619 }
620
621 /// The track a live binding has given `alias` to.
622 ///
623 /// Read rather than kept, for the reason the alias table beside it gives: a
624 /// binding whose request has ended holds nothing, and an alias that is free
625 /// again may name a different track next. That is exactly why the
626 /// forwarding-preference record below is keyed on the track this returns
627 /// and never on the alias itself.
628 fn track_for_alias(&self, alias: u64) -> Option<(&TrackNamespace, &[u8])> {
629 self.track_bindings.iter().find_map(|(&key, binding)| {
630 (binding.alias == alias && self.binding_is_live(key))
631 .then_some((&binding.namespace, binding.name.as_slice()))
632 })
633 }
634
635 /// Record how a track's object was framed, and report Section 7's "MUST NOT
636 /// mix" when it disagrees with what that track's earlier objects used.
637 ///
638 /// `&self`, because the call sites are the data plane's: a subgroup header
639 /// arriving, a datagram arriving, and the two writers that produce them.
640 ///
641 /// An alias no live binding names records nothing and reports nothing. An
642 /// object for such an alias breaks a different rule — the one about objects
643 /// nobody asked for — and answering that one here would answer it with the
644 /// wrong sentence.
645 pub fn note_object_forwarding_preference(
646 &self,
647 alias: u64,
648 seen: ObjectForwardingPreference,
649 ) -> Result<(), EndpointError> {
650 let Some((namespace, name)) = self.track_for_alias(alias) else { return Ok(()) };
651 self.forwarding_preferences
652 .lock()
653 .unwrap_or_else(|poisoned| poisoned.into_inner())
654 .observe(namespace, name, seen)
655 .map_err(|established| EndpointError::MixedForwardingPreference {
656 alias,
657 established,
658 offered: seen,
659 })
660 }
661
662 /// Whether the subscription that owns a binding is still standing.
663 ///
664 /// Subscribing counts as well as Active, which is what separates this draft
665 /// from draft-12 onwards: there the sentence is, at draft-12 Section 8.8,
666 /// "a different track with an active subscription" and the alias arrives in
667 /// the answer, so only an answered request holds one. Here the alias is in
668 /// the SUBSCRIBE itself, and the sentence puts no qualifier on "already
669 /// being used" - so it is in use from the moment that message is sent or
670 /// received, and stays in use until the subscription ends.
671 fn binding_is_live(&self, key: (SubscribeSide, u64)) -> bool {
672 let state = match key.0 {
673 SubscribeSide::Ours => self.subscriptions.get(&key.1).map(|sm| sm.state()),
674 SubscribeSide::Peers => self.inbound_subscribes.get(&key.1).map(|s| s.state.state()),
675 };
676 matches!(state, Some(SubscriptionState::Subscribing | SubscriptionState::Active))
677 }
678
679 /// The close Section 6.4 requires of an arriving SUBSCRIBE whose Track
680 /// Alias is spoken for, or `None` when it is free.
681 fn conflicting_track_alias(
682 &self,
683 side: SubscribeSide,
684 id: u64,
685 alias: u64,
686 namespace: &TrackNamespace,
687 name: &[u8],
688 ) -> Option<EndpointError> {
689 let (established_side, established) = self.alias_holder(alias, namespace, name)?;
690 Some(EndpointError::DuplicateTrackAlias {
691 alias,
692 established_side,
693 established,
694 offered_side: side,
695 offered: id,
696 })
697 }
698
699 /// The close Section 6.16 requires of a SUBSCRIBE_ERROR offering a Track
700 /// Alias to retry with, or `None` when the offer can be taken up.
701 ///
702 /// The track is not in the SUBSCRIBE_ERROR: it is the one this endpoint's
703 /// own SUBSCRIBE asked for, so the request has to be looked up before the
704 /// alias offered for it can be judged. An offer of an alias this endpoint
705 /// already holds for that same track is the retry succeeding, not a
706 /// conflict.
707 fn conflicting_retry_alias(&self, id: u64, alias: u64) -> Option<EndpointError> {
708 let binding = self.track_bindings.get(&(SubscribeSide::Ours, id))?;
709 let (established_side, established) =
710 self.alias_holder(alias, &binding.namespace, &binding.name)?;
711 Some(EndpointError::DuplicateTrackAlias {
712 alias,
713 established_side,
714 established,
715 offered_side: SubscribeSide::Ours,
716 offered: id,
717 })
718 }
719
720 // ── Accessors ──────────────────────────────────────────────
721
722 /// Returns which side of the session this endpoint is.
723 pub fn role(&self) -> Role {
724 self.role
725 }
726
727 /// Returns the current session state.
728 pub fn session_state(&self) -> SessionState {
729 self.session.state()
730 }
731
732 /// Returns the negotiated MoQT version, if setup is complete.
733 pub fn negotiated_version(&self) -> Option<VarInt> {
734 self.negotiated_version
735 }
736
737 /// Returns the URI from a received GOAWAY message, if any.
738 pub fn goaway_uri(&self) -> Option<&[u8]> {
739 self.goaway_uri.as_deref()
740 }
741
742 /// Returns whether this endpoint is blocked on subscribe ID allocation.
743 pub fn is_blocked(&self) -> bool {
744 self.subscribe_ids.is_blocked()
745 }
746
747 /// Returns the number of active subscription state machines.
748 pub fn active_subscription_count(&self) -> usize {
749 self.subscriptions.len()
750 }
751
752 /// Returns the number of active fetch state machines.
753 pub fn active_fetch_count(&self) -> usize {
754 self.fetches.len()
755 }
756
757 /// Returns the number of active subscribe-announces state machines.
758 pub fn active_subscribe_announces_count(&self) -> usize {
759 self.subscribe_announces.len()
760 }
761
762 /// Returns the number of active announce state machines.
763 pub fn active_announce_count(&self) -> usize {
764 self.announces.len()
765 }
766
767 /// Returns the number of active track status state machines.
768 pub fn active_track_status_count(&self) -> usize {
769 self.track_statuses.len()
770 }
771
772 // ── Session lifecycle ──────────────────────────────────────
773
774 /// Transition from Connecting to SetupExchange.
775 pub fn connect(&mut self) -> Result<(), EndpointError> {
776 self.session.on_connect()?;
777 Ok(())
778 }
779
780 /// Close the session (SetupExchange, Active or Draining -> Closed).
781 pub fn close(&mut self) -> Result<(), EndpointError> {
782 self.session.on_close()?;
783 Ok(())
784 }
785
786 // ── Client setup ───────────────────────────────────────────
787
788 /// Generate a CLIENT_SETUP message (client-side).
789 pub fn send_client_setup(
790 &mut self,
791 versions: Vec<VarInt>,
792 parameters: Vec<KeyValuePair>,
793 ) -> Result<ControlMessage, EndpointError> {
794 self.offered_versions = versions.clone();
795 let msg = ClientSetup { supported_versions: versions, parameters };
796 setup::validate_client_setup(&msg)?;
797 self.record_advertised_max(&msg.parameters);
798 Ok(ControlMessage::ClientSetup(msg))
799 }
800
801 /// Process a SERVER_SETUP message (client-side). Transitions to Active.
802 /// If the server includes a MAX_SUBSCRIBE_ID parameter (key 0x02), the
803 /// subscribe ID allocator is initialized with that value.
804 pub fn receive_server_setup(&mut self, msg: &ServerSetup) -> Result<(), EndpointError> {
805 setup::validate_server_setup(msg)?;
806 let version = setup::negotiate_version(&self.offered_versions, msg.selected_version)?;
807 self.negotiated_version = Some(version);
808 self.session.on_setup_complete()?;
809 self.read_granted_max(&msg.parameters)?;
810 Ok(())
811 }
812
813 // ── Server setup ───────────────────────────────────────────
814
815 /// Process CLIENT_SETUP and generate SERVER_SETUP (server-side).
816 pub fn receive_client_setup_and_respond(
817 &mut self,
818 client_setup: &ClientSetup,
819 selected_version: VarInt,
820 ) -> Result<ControlMessage, EndpointError> {
821 self.receive_client_setup_and_respond_with(client_setup, selected_version, Vec::new())
822 }
823
824 /// Process CLIENT_SETUP and generate SERVER_SETUP carrying `parameters`.
825 ///
826 /// The form that can answer with a MAX_SUBSCRIBE_ID. Section 6.2.2.3
827 /// describes the parameter as communicating "an initial value for the
828 /// Maximum Subscribe ID to the receiving subscriber. The default value is
829 /// 0, so if not specified, the peer MUST NOT create subscriptions" - so a
830 /// server that never sends it has told the client it may not subscribe,
831 /// and every SUBSCRIBE the client tries is answered Blocked until a
832 /// MAX_SUBSCRIBE_ID message arrives.
833 ///
834 /// A MAX_SUBSCRIBE_ID among `parameters` is recorded as the ceiling this
835 /// endpoint has advertised, which is the number a peer's Subscribe IDs are
836 /// measured against.
837 ///
838 /// # Errors
839 ///
840 /// The setup errors, and a malformed MAX_SUBSCRIBE_ID in the CLIENT_SETUP.
841 pub fn receive_client_setup_and_respond_with(
842 &mut self,
843 client_setup: &ClientSetup,
844 selected_version: VarInt,
845 parameters: Vec<KeyValuePair>,
846 ) -> Result<ControlMessage, EndpointError> {
847 setup::validate_client_setup(client_setup)?;
848 // Section 6.2.2.3 puts no role restriction on MAX_SUBSCRIBE_ID, so a
849 // CLIENT_SETUP may carry it and it grants this endpoint its budget.
850 self.read_granted_max(&client_setup.parameters)?;
851 let version = setup::negotiate_version(&client_setup.supported_versions, selected_version)?;
852 self.negotiated_version = Some(version);
853 self.session.on_setup_complete()?;
854 self.record_advertised_max(¶meters);
855 let msg = ServerSetup { selected_version: version, parameters };
856 Ok(ControlMessage::ServerSetup(msg))
857 }
858
859 /// Take the budget a peer's setup parameters grant this endpoint.
860 ///
861 /// An explicit 0 is the same as the parameter's absence - Section
862 /// 6.2.2.3 gives it a default of 0 - so it is not put through the
863 /// only-increase rule, which belongs to the MAX_SUBSCRIBE_ID message.
864 fn read_granted_max(&mut self, parameters: &[KeyValuePair]) -> Result<(), EndpointError> {
865 for param in parameters {
866 if param.key == VarInt::from_u64(0x02).unwrap() {
867 let max = setup::setup_varint(¶m.value)
868 .ok_or(EndpointError::MalformedSetupParameter(0x02))?;
869 if max > 0 {
870 self.subscribe_ids.update_max(max)?;
871 }
872 }
873 }
874 Ok(())
875 }
876
877 /// Record a MAX_SUBSCRIBE_ID parameter this endpoint is about to send as
878 /// the ceiling it has advertised to the peer.
879 ///
880 /// The peer's Subscribe IDs are bound by this number, and this endpoint's
881 /// own by the one the peer advertised. The two are different values and
882 /// measuring against the wrong one accepts ids a conforming peer would
883 /// never send and refuses ids it may.
884 fn record_advertised_max(&mut self, parameters: &[KeyValuePair]) {
885 for param in parameters {
886 if param.key == VarInt::from_u64(0x02).unwrap() {
887 if let Some(max) = setup::setup_varint(¶m.value) {
888 self.advertised_max_id = max;
889 }
890 }
891 }
892 }
893
894 /// Hold a Subscribe ID the peer chose to the rules Section 6.4
895 /// states about it.
896 ///
897 /// "Subscribe ID is a variable length integer that MUST be unique and
898 /// monotonically increasing within a session and MUST be less than the
899 /// session's Maximum Subscribe ID", and Section 6.7 repeats the
900 /// first half for FETCH - so the two share one sequence and are checked
901 /// together here.
902 ///
903 /// The ceiling is the one **this** endpoint advertised, not the one the
904 /// peer granted us: those are different numbers, and either may be the
905 /// larger. Strictly increasing gives uniqueness as well, so one high-water
906 /// mark answers both halves of the sentence.
907 ///
908 /// # The ceiling is a session rule, not a request rule
909 ///
910 /// Section 6.20: "If a Subscribe ID equal or larger than this is received
911 /// in any message, including SUBSCRIBE, the publisher MUST close the
912 /// session with an error of 'Too Many Subscribes'." An id that reaches
913 /// the ceiling is not a SUBSCRIBE to refuse with a SUBSCRIBE_ERROR: the
914 /// session is over, so this moves the endpoint's own state to Closed and
915 /// leaves the code to [`EndpointError::session_error_code`].
916 ///
917 /// # Errors
918 ///
919 /// [`SubscribeIdError::ExceedsMax`] if the id reaches the advertised
920 /// ceiling, and [`EndpointError::PeerSubscribeIdNotIncreasing`] if it does
921 /// not increase on the last one the peer used.
922 pub fn validate_peer_subscribe_id(&mut self, id: u64) -> Result<(), EndpointError> {
923 if id >= self.advertised_max_id {
924 return Err(self.fail_session(EndpointError::SubscribeId(
925 SubscribeIdError::ExceedsMax(id, self.advertised_max_id),
926 )));
927 }
928 if let Some(highest) = self.peer_highest_subscribe_id {
929 if id <= highest {
930 return Err(EndpointError::PeerSubscribeIdNotIncreasing(id, highest));
931 }
932 }
933 self.peer_highest_subscribe_id = Some(id);
934 Ok(())
935 }
936
937 /// Process an incoming SUBSCRIBE, checking the Subscribe ID the peer chose.
938 ///
939 /// # Errors
940 ///
941 /// Whatever [`Self::validate_peer_subscribe_id`] answers.
942 pub fn receive_subscribe(&mut self, msg: &Subscribe) -> Result<(), EndpointError> {
943 let id = msg.subscribe_id.into_inner();
944 self.validate_peer_subscribe_id(id)?;
945 // Section 6.11: "If a publisher receives new subscriptions for that
946 // namespace after receiving an ANNOUNCE_CANCEL, it SHOULD close the
947 // session as a 'Protocol Violation'." No other draft states it - the
948 // sentence is draft-07's alone - and this endpoint is the publisher of
949 // a SUBSCRIBE that arrives, so this is where it is answered.
950 //
951 // A SHOULD, and this endpoint takes it: the draft names both the
952 // action and the code, so closing is conforming and leaving the
953 // subscription open would serve a namespace the peer has said it no
954 // longer wants announced.
955 if self.announce_cancelled.contains(&msg.track_namespace.0) {
956 return Err(self.fail_session(EndpointError::SubscribeAfterAnnounceCancel));
957 }
958 // Section 6.4: "If the Track Alias is already being used for a
959 // different track, the publisher MUST close the session with a
960 // Duplicate Track Alias error". This endpoint is the publisher of a
961 // SUBSCRIBE that arrives, so this is where that close is raised.
962 // Judged before anything is written down, so a refused SUBSCRIBE
963 // leaves no binding behind.
964 let alias = msg.track_alias.into_inner();
965 if let Some(conflict) = self.conflicting_track_alias(
966 SubscribeSide::Peers,
967 id,
968 alias,
969 &msg.track_namespace,
970 &msg.track_name,
971 ) {
972 return Err(self.fail_session(conflict));
973 }
974 let mut state = SubscriptionStateMachine::new();
975 state.on_subscribe_received()?;
976 self.inbound_subscribes.insert(id, InboundSubscribe { message: msg.clone(), state });
977 self.track_bindings.insert(
978 (SubscribeSide::Peers, id),
979 TrackBinding {
980 namespace: msg.track_namespace.clone(),
981 name: msg.track_name.clone(),
982 alias,
983 },
984 );
985 Ok(())
986 }
987
988 // ── MAX_SUBSCRIBE_ID ───────────────────────────────────────
989
990 /// Process an incoming MAX_SUBSCRIBE_ID message, ending the session if the
991 /// ceiling it carries does not increase.
992 ///
993 /// Section 6.20: "The Maximum Subscribe Id MUST only increase within a
994 /// session, and receipt of a MAX_SUBSCRIBE_ID message with an equal or
995 /// smaller Subscribe ID value is a 'Protocol Violation'." Section 3.5 lists
996 /// Protocol Violation among the codes for terminating the session - "The
997 /// remote endpoint performed an action that was disallowed by the
998 /// specification" - so naming it of a *receipt* is this draft saying the
999 /// session ends, and with which code. Draft-16 Section 9.5 states the same
1000 /// rule with the verb in it: "it MUST close the session with a
1001 /// PROTOCOL_VIOLATION".
1002 ///
1003 /// # Errors
1004 ///
1005 /// [`SubscribeIdError::Decreased`] if the value does not increase, with
1006 /// the session already moved to Closed.
1007 pub fn receive_max_subscribe_id(&mut self, msg: &MaxSubscribeId) -> Result<(), EndpointError> {
1008 if let Err(err) = self.subscribe_ids.update_max(msg.subscribe_id.into_inner()) {
1009 return Err(self.fail_session(err.into()));
1010 }
1011 Ok(())
1012 }
1013
1014 /// Generate a MAX_SUBSCRIBE_ID message (typically server-side).
1015 ///
1016 /// Section 6.20: "The Maximum Subscribe ID MUST only increase within a
1017 /// session", and a peer that receives an equal or smaller value closes
1018 /// the session. The ceiling starts at 0 and 0 is not greater than 0, so
1019 /// the first value that may go on the wire is 1 and there is no opening
1020 /// case where a repeat is allowed.
1021 ///
1022 /// # Errors
1023 ///
1024 /// The decrease error if the value does not strictly increase.
1025 pub fn send_max_subscribe_id(
1026 &mut self,
1027 max_id: VarInt,
1028 ) -> Result<ControlMessage, EndpointError> {
1029 let new_val = max_id.into_inner();
1030 if new_val <= self.advertised_max_id {
1031 return Err(EndpointError::SubscribeId(SubscribeIdError::Decreased(
1032 self.advertised_max_id,
1033 new_val,
1034 )));
1035 }
1036 self.advertised_max_id = new_val;
1037 Ok(ControlMessage::MaxSubscribeId(MaxSubscribeId { subscribe_id: max_id }))
1038 }
1039
1040 // ── GoAway ─────────────────────────────────────────────────
1041
1042 /// Process an incoming GOAWAY message. Transitions to Draining.
1043 ///
1044 /// # Errors
1045 ///
1046 /// [`EndpointError::GoAwayAtServer`] if this endpoint is the server.
1047 /// Section 6.3: "The server MUST terminate the session with a Protocol
1048 /// Violation (Section 3.5) if it receives a GOAWAY message." Migration is something a
1049 /// server offers a client, and on this draft the direction is stated about
1050 /// the message rather than about the URI it carries - so an empty GOAWAY is
1051 /// refused here too, where on draft-08 and later it would be allowed
1052 /// through. Refused before the session is moved to Draining, so a server
1053 /// cannot be talked into draining by a client that had no standing to ask.
1054 /// The session is over: this endpoint's own state has moved to Closed and
1055 /// the code the transport should close with is in
1056 /// [`EndpointError::session_error_code`].
1057 ///
1058 /// [`EndpointError::RepeatedGoAway`] if a GOAWAY has already been
1059 /// received. The session is over: this endpoint's own state has moved to
1060 /// Closed and the code the transport should close with is in
1061 /// [`EndpointError::session_error_code`].
1062 pub fn receive_goaway(&mut self, msg: &GoAway) -> Result<(), EndpointError> {
1063 if self.role == Role::Server {
1064 return Err(self.fail_session(EndpointError::GoAwayAtServer));
1065 }
1066 // Section 6.3: "The client MUST terminate the session with a Protocol
1067 // Violation (Section 3.5) if it receives multiple GOAWAY messages." Draining is
1068 // reached from nowhere else - `on_goaway` is its only entry and this
1069 // method is that method's only caller - so the session state is the
1070 // record of the first GOAWAY having arrived. The subject is the client
1071 // because the sentence beside it makes any GOAWAY at a server a close,
1072 // so a server never reaches a second one.
1073 if self.session.state() == SessionState::Draining {
1074 return Err(self.fail_session(EndpointError::RepeatedGoAway));
1075 }
1076 self.session.on_goaway()?;
1077 self.goaway_uri = Some(msg.new_session_uri.clone());
1078 Ok(())
1079 }
1080
1081 // ── Subscribe flow ─────────────────────────────────────────
1082
1083 fn require_active_or_err(&self) -> Result<(), EndpointError> {
1084 match self.session.state() {
1085 SessionState::Active => Ok(()),
1086 SessionState::Draining => Err(EndpointError::Draining),
1087 _ => Err(EndpointError::NotActive),
1088 }
1089 }
1090
1091 /// Record that the session is over because the peer broke a rule this
1092 /// draft answers with a session close, and hand the error back unchanged.
1093 ///
1094 /// The state move is what makes the violation stick: every request entry
1095 /// point goes through
1096 /// [`require_active_or_err`](Self::require_active_or_err), so a caller
1097 /// that ignores the returned error still cannot start anything new.
1098 /// Closing on the wire is the connection layer's job - see
1099 /// [`EndpointError::session_error_code`] for the code it should use.
1100 fn fail_session(&mut self, err: EndpointError) -> EndpointError {
1101 // `on_close` accepts SetupExchange, Active and Draining. A violation
1102 // seen in Connecting or Closed leaves the state machine alone: there
1103 // is no session to close, and the error itself is still the answer.
1104 //
1105 // SetupExchange is in that set because the Termination section says
1106 // "The Transport Session can be terminated at any point", and the
1107 // Setup exchange is a point. So a violation caught while the setup is
1108 // still in flight does close the session rather than being recorded
1109 // and forgotten, which is what this discarded result used to mean.
1110 let _ = self.session.on_close();
1111 err
1112 }
1113
1114 /// Send a SUBSCRIBE message. Allocates an ID and creates a subscription
1115 /// state machine.
1116 ///
1117 /// `AbsoluteStart` and `AbsoluteRange` name a start location, which this
1118 /// call has no way to supply, and are answered with
1119 /// [`EndpointError::FilterNeedsRange`] - use [`Self::subscribe_range`] for
1120 /// those. Without the refusal this call would hand back a message whose
1121 /// filter announces fields the message does not carry, and the frame that
1122 /// goes on the wire is short by exactly those fields.
1123 pub fn subscribe(
1124 &mut self,
1125 track_alias: VarInt,
1126 track_namespace: TrackNamespace,
1127 track_name: Vec<u8>,
1128 subscriber_priority: u8,
1129 group_order: GroupOrder,
1130 filter_type: FilterType,
1131 ) -> Result<(VarInt, ControlMessage), EndpointError> {
1132 if matches!(filter_type, FilterType::AbsoluteStart | FilterType::AbsoluteRange) {
1133 return Err(EndpointError::FilterNeedsRange);
1134 }
1135 self.subscribe_inner(
1136 track_alias,
1137 track_namespace,
1138 track_name,
1139 subscriber_priority,
1140 group_order,
1141 filter_type,
1142 None,
1143 None,
1144 )
1145 }
1146
1147 /// Send a SUBSCRIBE for a range of the track, starting at a given
1148 /// location.
1149 ///
1150 /// The Filter Type is derived from the arguments rather than taken beside
1151 /// them, so the message cannot name a filter whose fields it does not
1152 /// carry.
1153 #[allow(clippy::too_many_arguments)]
1154 pub fn subscribe_range(
1155 &mut self,
1156 track_alias: VarInt,
1157 track_namespace: TrackNamespace,
1158 track_name: Vec<u8>,
1159 subscriber_priority: u8,
1160 group_order: GroupOrder,
1161 start_location: Location,
1162 end_location: Option<Location>,
1163 ) -> Result<(VarInt, ControlMessage), EndpointError> {
1164 let filter_type = match end_location {
1165 Some(_) => FilterType::AbsoluteRange,
1166 None => FilterType::AbsoluteStart,
1167 };
1168 self.subscribe_inner(
1169 track_alias,
1170 track_namespace,
1171 track_name,
1172 subscriber_priority,
1173 group_order,
1174 filter_type,
1175 Some(start_location),
1176 end_location,
1177 )
1178 }
1179
1180 #[allow(clippy::too_many_arguments)]
1181 fn subscribe_inner(
1182 &mut self,
1183 track_alias: VarInt,
1184 track_namespace: TrackNamespace,
1185 track_name: Vec<u8>,
1186 subscriber_priority: u8,
1187 group_order: GroupOrder,
1188 filter_type: FilterType,
1189 start_location: Option<Location>,
1190 end_location: Option<Location>,
1191 ) -> Result<(VarInt, ControlMessage), EndpointError> {
1192 self.require_active_or_err()?;
1193 // The alias travels in the SUBSCRIBE, so this is the last point at
1194 // which giving it to a second track can still be taken back. Refused
1195 // before the Subscribe ID is allocated, so a refusal spends nothing.
1196 let alias = track_alias.into_inner();
1197 if let Some((side, held)) = self.alias_holder(alias, &track_namespace, &track_name) {
1198 return Err(EndpointError::TrackAliasInUse { alias, side, held });
1199 }
1200 let sub_id = self.subscribe_ids.allocate()?;
1201
1202 let mut sm = SubscriptionStateMachine::new();
1203 sm.on_subscribe_sent()?;
1204 self.subscriptions.insert(sub_id.into_inner(), sm);
1205 self.track_bindings.insert(
1206 (SubscribeSide::Ours, sub_id.into_inner()),
1207 TrackBinding { namespace: track_namespace.clone(), name: track_name.clone(), alias },
1208 );
1209
1210 let (end_group, end_object) = match end_location {
1211 Some(end) => (Some(end.group), Some(end.object)),
1212 None => (None, None),
1213 };
1214 let msg = ControlMessage::Subscribe(Subscribe {
1215 subscribe_id: sub_id,
1216 track_alias,
1217 track_namespace,
1218 track_name,
1219 subscriber_priority,
1220 group_order,
1221 filter_type,
1222 start_location,
1223 end_group,
1224 end_object,
1225 parameters: vec![],
1226 });
1227 Ok((sub_id, msg))
1228 }
1229
1230 /// Process an incoming SUBSCRIBE_OK.
1231 pub fn receive_subscribe_ok(&mut self, msg: &SubscribeOk) -> Result<(), EndpointError> {
1232 let id = msg.subscribe_id.into_inner();
1233 let sm = self.subscriptions.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1234 sm.on_subscribe_ok()?;
1235 Ok(())
1236 }
1237
1238 /// Process an incoming SUBSCRIBE_ERROR.
1239 pub fn receive_subscribe_error(&mut self, msg: &SubscribeError) -> Result<(), EndpointError> {
1240 let id = msg.subscribe_id.into_inner();
1241 // Section 6.16 gives SUBSCRIBE_ERROR a Track Alias field with one
1242 // meaning: an alias to retry the SUBSCRIBE with. Judged before the
1243 // subscription is ended, because the request it names is what says
1244 // which track the offered alias would be for.
1245 if SubscribeErrorCode::from_u64(msg.error_code.into_inner())
1246 == Some(SubscribeErrorCode::RetryTrackAlias)
1247 {
1248 if let Some(conflict) = self.conflicting_retry_alias(id, msg.track_alias.into_inner()) {
1249 return Err(self.fail_session(conflict));
1250 }
1251 }
1252 let sm = self.subscriptions.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1253 sm.on_subscribe_error()?;
1254 Ok(())
1255 }
1256
1257 /// Send an UNSUBSCRIBE message for an active subscription.
1258 pub fn unsubscribe(&mut self, subscribe_id: VarInt) -> Result<ControlMessage, EndpointError> {
1259 let id = subscribe_id.into_inner();
1260 let sm = self.subscriptions.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1261 sm.on_unsubscribe()?;
1262 Ok(ControlMessage::Unsubscribe(Unsubscribe { subscribe_id }))
1263 }
1264
1265 /// Send a SUBSCRIBE_UPDATE narrowing a subscription this endpoint opened.
1266 ///
1267 /// Section 6.5 gives the message to the subscriber, which is what this
1268 /// endpoint is for every subscription in `subscriptions`. No identifier is
1269 /// spent: the update's one identifier field names the subscription being
1270 /// modified rather than opening a request of its own.
1271 ///
1272 /// The narrowing rules the same section states are the caller's to keep.
1273 ///
1274 /// # Errors
1275 ///
1276 /// [`EndpointError::UnknownSubscribe`] when this endpoint opened no
1277 /// subscription under that identifier, and the subscription flow's own
1278 /// `InvalidTransition` when the one it names has already ended.
1279 #[allow(clippy::too_many_arguments)]
1280 pub fn subscribe_update(
1281 &mut self,
1282 subscribe_id: VarInt,
1283 start_group: VarInt,
1284 start_object: VarInt,
1285 end_group: VarInt,
1286 end_object: VarInt,
1287 subscriber_priority: u8,
1288 parameters: Vec<KeyValuePair>,
1289 ) -> Result<ControlMessage, EndpointError> {
1290 self.require_active_or_err()?;
1291 let id = subscribe_id.into_inner();
1292 let sm = self.subscriptions.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1293 sm.on_subscribe_update()?;
1294 Ok(ControlMessage::SubscribeUpdate(SubscribeUpdate {
1295 subscribe_id,
1296 start_group,
1297 start_object,
1298 end_group,
1299 end_object,
1300 subscriber_priority,
1301 parameters,
1302 }))
1303 }
1304
1305 /// Process an incoming SUBSCRIBE_UPDATE.
1306 ///
1307 /// Section 6.5: "A subscriber issues a SUBSCRIBE_UPDATE to a publisher to
1308 /// request a change to a prior subscription." One that arrives is therefore
1309 /// about a subscription the **peer** opened, which is why it is looked for
1310 /// among those and not among this endpoint's own.
1311 ///
1312 /// # Errors
1313 ///
1314 /// [`EndpointError::UpdateForUnknownSubscribe`] when the identifier names no
1315 /// subscription the peer has opened in this session, and the subscription
1316 /// flow's own `InvalidTransition` when it names one that has already
1317 /// ended. Neither ends the session: Section 6.5 says SHOULD.
1318 pub fn receive_subscribe_update(&mut self, msg: &SubscribeUpdate) -> Result<(), EndpointError> {
1319 let id = msg.subscribe_id.into_inner();
1320 let sub = self
1321 .inbound_subscribes
1322 .get_mut(&id)
1323 .ok_or(EndpointError::UpdateForUnknownSubscribe(id))?;
1324 sub.state.on_subscribe_update_received()?;
1325 Ok(())
1326 }
1327
1328 /// Process an incoming SUBSCRIBE_DONE (subscriber side — publisher finished).
1329 pub fn receive_subscribe_done(&mut self, msg: &SubscribeDone) -> Result<(), EndpointError> {
1330 let id = msg.subscribe_id.into_inner();
1331 let sm = self.subscriptions.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1332 sm.on_subscribe_done()?;
1333 Ok(())
1334 }
1335
1336 // ── Answering a SUBSCRIBE the peer sent ────────────────────
1337
1338 /// The SUBSCRIBE the peer sent under `subscribe_id` and this endpoint has
1339 /// not answered yet.
1340 ///
1341 /// `None` once it has been answered, and for an identifier this session
1342 /// has no inbound subscription for.
1343 pub fn pending_subscribe(&self, subscribe_id: VarInt) -> Option<&Subscribe> {
1344 self.inbound_subscribes
1345 .get(&subscribe_id.into_inner())
1346 .filter(|s| s.state.state() == SubscriptionState::Subscribing)
1347 .map(|s| &s.message)
1348 }
1349
1350 /// How many SUBSCRIBEs the peer has sent that are still waiting for an
1351 /// answer.
1352 pub fn pending_subscribe_count(&self) -> usize {
1353 self.inbound_subscribes
1354 .values()
1355 .filter(|s| s.state.state() == SubscriptionState::Subscribing)
1356 .count()
1357 }
1358
1359 /// Build the SUBSCRIBE_OK accepting a subscription the peer opened.
1360 ///
1361 /// # Errors
1362 ///
1363 /// [`EndpointError::UnknownSubscribe`] if the peer opened no subscription
1364 /// under that identifier, and [`EndpointError::Subscription`] if it has
1365 /// already been answered.
1366 pub fn send_subscribe_ok(
1367 &mut self,
1368 subscribe_id: VarInt,
1369 expires: VarInt,
1370 group_order: GroupOrder,
1371 parameters: Vec<KeyValuePair>,
1372 ) -> Result<ControlMessage, EndpointError> {
1373 let id = subscribe_id.into_inner();
1374 let sub =
1375 self.inbound_subscribes.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1376 sub.state.on_subscribe_ok_sent()?;
1377 Ok(ControlMessage::SubscribeOk(SubscribeOk {
1378 subscribe_id,
1379 expires,
1380 group_order,
1381 content_exists: ContentExists::NoLargestLocation,
1382 largest_group_id: None,
1383 largest_object_id: None,
1384 parameters,
1385 }))
1386 }
1387
1388 /// Build the SUBSCRIBE_ERROR rejecting a subscription the peer opened.
1389 ///
1390 /// The Track Alias goes back out with the refusal because Section 6.16
1391 /// gives the field a use: an alias to retry with, when the code is 'Retry
1392 /// Track Alias'. Under any other code the peer reads nothing from it.
1393 ///
1394 /// # Errors
1395 ///
1396 /// [`EndpointError::UnknownSubscribe`] if the peer opened no subscription
1397 /// under that identifier, and [`EndpointError::Subscription`] if it has
1398 /// already been answered.
1399 pub fn send_subscribe_error(
1400 &mut self,
1401 subscribe_id: VarInt,
1402 error_code: VarInt,
1403 reason_phrase: Vec<u8>,
1404 track_alias: VarInt,
1405 ) -> Result<ControlMessage, EndpointError> {
1406 let id = subscribe_id.into_inner();
1407 let sub =
1408 self.inbound_subscribes.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1409 sub.state.on_subscribe_error_sent()?;
1410 Ok(ControlMessage::SubscribeError(SubscribeError {
1411 subscribe_id,
1412 error_code,
1413 reason_phrase,
1414 track_alias,
1415 }))
1416 }
1417
1418 /// Build the SUBSCRIBE_DONE ending a subscription this endpoint accepted.
1419 ///
1420 /// # Errors
1421 ///
1422 /// [`EndpointError::UnknownSubscribe`] if the peer opened no subscription
1423 /// under that identifier, and [`EndpointError::Subscription`] if it is not
1424 /// one this endpoint accepted and has not already ended.
1425 pub fn send_subscribe_done(
1426 &mut self,
1427 subscribe_id: VarInt,
1428 status_code: VarInt,
1429 reason_phrase: Vec<u8>,
1430 ) -> Result<ControlMessage, EndpointError> {
1431 let id = subscribe_id.into_inner();
1432 let sub =
1433 self.inbound_subscribes.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1434 sub.state.on_subscribe_done_sent()?;
1435 Ok(ControlMessage::SubscribeDone(SubscribeDone {
1436 subscribe_id,
1437 status_code,
1438 content_exists: ContentExists::NoLargestLocation,
1439 final_group: None,
1440 final_object: None,
1441 reason_phrase,
1442 }))
1443 }
1444
1445 /// Process an incoming UNSUBSCRIBE, ending the subscription the peer
1446 /// opened and freeing the Track Alias it held.
1447 ///
1448 /// # Errors
1449 ///
1450 /// [`EndpointError::UnknownSubscribe`] if the peer opened no subscription
1451 /// under that identifier, and [`EndpointError::Subscription`] if it is not
1452 /// one this endpoint accepted and has not already ended.
1453 pub fn receive_unsubscribe(&mut self, msg: &Unsubscribe) -> Result<(), EndpointError> {
1454 let id = msg.subscribe_id.into_inner();
1455 let sub =
1456 self.inbound_subscribes.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1457 sub.state.on_unsubscribe_received()?;
1458 Ok(())
1459 }
1460
1461 // ── Fetch flow ─────────────────────────────────────────────
1462
1463 /// Send a FETCH message. Allocates a subscribe ID and creates a fetch state machine.
1464 #[allow(clippy::too_many_arguments)]
1465 pub fn fetch(
1466 &mut self,
1467 track_namespace: TrackNamespace,
1468 track_name: Vec<u8>,
1469 subscriber_priority: u8,
1470 group_order: GroupOrder,
1471 start_group: VarInt,
1472 start_object: VarInt,
1473 end_group: VarInt,
1474 end_object: VarInt,
1475 ) -> Result<(VarInt, ControlMessage), EndpointError> {
1476 self.require_active_or_err()?;
1477 let sub_id = self.subscribe_ids.allocate()?;
1478
1479 let mut sm = FetchStateMachine::new();
1480 sm.on_fetch_sent()?;
1481 self.fetches.insert(sub_id.into_inner(), sm);
1482
1483 let msg = ControlMessage::Fetch(Fetch {
1484 subscribe_id: sub_id,
1485 track_namespace,
1486 track_name,
1487 subscriber_priority,
1488 group_order,
1489 start_group,
1490 start_object,
1491 end_group,
1492 end_object,
1493 parameters: vec![],
1494 });
1495 Ok((sub_id, msg))
1496 }
1497
1498 /// Process an incoming FETCH_OK.
1499 pub fn receive_fetch_ok(&mut self, msg: &message::FetchOk) -> Result<(), EndpointError> {
1500 let id = msg.subscribe_id.into_inner();
1501 let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1502 sm.on_fetch_ok()?;
1503 Ok(())
1504 }
1505
1506 /// Process an incoming FETCH_ERROR.
1507 pub fn receive_fetch_error(&mut self, msg: &message::FetchError) -> Result<(), EndpointError> {
1508 let id = msg.subscribe_id.into_inner();
1509 let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1510 sm.on_fetch_error()?;
1511 Ok(())
1512 }
1513
1514 /// Send a FETCH_CANCEL message.
1515 pub fn fetch_cancel(&mut self, subscribe_id: VarInt) -> Result<ControlMessage, EndpointError> {
1516 let id = subscribe_id.into_inner();
1517 let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1518 sm.on_fetch_cancel()?;
1519 Ok(ControlMessage::FetchCancel(FetchCancel { subscribe_id }))
1520 }
1521
1522 /// Notify that a fetch data stream received FIN.
1523 ///
1524 /// It may arrive before the FETCH_OK or FETCH_ERROR answering the
1525 /// request, which leaves the fetch in `FetchState::Unanswered` until the
1526 /// answer lands.
1527 pub fn on_fetch_stream_fin(&mut self, subscribe_id: VarInt) -> Result<(), EndpointError> {
1528 let id = subscribe_id.into_inner();
1529 let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1530 sm.on_stream_fin()?;
1531 Ok(())
1532 }
1533
1534 /// Notify that a fetch data stream was reset.
1535 ///
1536 /// As with a FIN, it may arrive before the answer to the request.
1537 pub fn on_fetch_stream_reset(&mut self, subscribe_id: VarInt) -> Result<(), EndpointError> {
1538 let id = subscribe_id.into_inner();
1539 let sm = self.fetches.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1540 sm.on_stream_reset()?;
1541 Ok(())
1542 }
1543
1544 // ── Answering a FETCH the peer sent ────────────────────────
1545
1546 /// Process an incoming FETCH, recording the fetch it opens.
1547 ///
1548 /// The Subscribe ID is checked here, in the same sequence a SUBSCRIBE
1549 /// draws from: Section 6.7 gives it the same "unique and monotonically
1550 /// increasing within a session" requirement.
1551 ///
1552 /// # Errors
1553 ///
1554 /// Whatever [`Self::validate_peer_subscribe_id`] answers, and the fetch
1555 /// flow's own `InvalidTransition` for a second FETCH under an identifier
1556 /// already carrying one.
1557 pub fn receive_fetch(&mut self, msg: &Fetch) -> Result<(), EndpointError> {
1558 self.validate_peer_subscribe_id(msg.subscribe_id.into_inner())?;
1559 let id = msg.subscribe_id.into_inner();
1560 let mut state = FetchStateMachine::new();
1561 state.on_fetch_received()?;
1562 self.inbound_fetches.insert(id, InboundFetch { message: msg.clone(), state });
1563 Ok(())
1564 }
1565
1566 /// The FETCH the peer sent under `subscribe_id` and this endpoint has not
1567 /// answered yet.
1568 ///
1569 /// `None` once it has been answered, and for an identifier this session
1570 /// has no inbound fetch for. The record itself lives on past the answer,
1571 /// because the fetch is not over until its data stream is.
1572 pub fn pending_fetch(&self, subscribe_id: VarInt) -> Option<&Fetch> {
1573 self.inbound_fetches
1574 .get(&subscribe_id.into_inner())
1575 .filter(|f| matches!(f.state.state(), FetchState::Pending | FetchState::Unanswered))
1576 .map(|f| &f.message)
1577 }
1578
1579 /// How many FETCHes the peer has sent that are still waiting for an
1580 /// answer.
1581 pub fn pending_fetch_count(&self) -> usize {
1582 self.inbound_fetches
1583 .values()
1584 .filter(|f| matches!(f.state.state(), FetchState::Pending | FetchState::Unanswered))
1585 .count()
1586 }
1587
1588 /// Build the FETCH_OK accepting a fetch the peer opened.
1589 ///
1590 /// # Errors
1591 ///
1592 /// [`EndpointError::UnknownSubscribe`] if the peer opened no fetch under
1593 /// that identifier, and the fetch flow's own `InvalidTransition` for a
1594 /// second answer. This draft states no rule about how many answers a
1595 /// FETCH gets; the flow refuses the second one because a fetch that has
1596 /// been answered has left the state an answer is sent from.
1597 pub fn send_fetch_ok(
1598 &mut self,
1599 subscribe_id: VarInt,
1600 group_order: GroupOrder,
1601 end_of_track: u8,
1602 largest_group_id: Option<VarInt>,
1603 largest_object_id: Option<VarInt>,
1604 parameters: Vec<KeyValuePair>,
1605 ) -> Result<ControlMessage, EndpointError> {
1606 let id = subscribe_id.into_inner();
1607 let fetch = self.inbound_fetches.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1608 fetch.state.on_fetch_ok_sent()?;
1609 Ok(ControlMessage::FetchOk(message::FetchOk {
1610 subscribe_id,
1611 group_order,
1612 end_of_track,
1613 largest_group_id,
1614 largest_object_id,
1615 parameters,
1616 }))
1617 }
1618
1619 /// Build the FETCH_ERROR refusing a fetch the peer opened.
1620 ///
1621 /// # Errors
1622 ///
1623 /// [`EndpointError::UnknownSubscribe`] if the peer opened no fetch under
1624 /// that identifier, and the fetch flow's own `InvalidTransition` if it has
1625 /// already been answered.
1626 pub fn send_fetch_error(
1627 &mut self,
1628 subscribe_id: VarInt,
1629 error_code: VarInt,
1630 reason_phrase: Vec<u8>,
1631 ) -> Result<ControlMessage, EndpointError> {
1632 let id = subscribe_id.into_inner();
1633 let fetch = self.inbound_fetches.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1634 fetch.state.on_fetch_error_sent()?;
1635 Ok(ControlMessage::FetchError(message::FetchError {
1636 subscribe_id,
1637 error_code,
1638 reason_phrase,
1639 }))
1640 }
1641
1642 /// Process an incoming FETCH_CANCEL, ending the fetch the peer opened.
1643 ///
1644 /// Section 6.8: the subscriber sends it to stop a fetch it no longer
1645 /// wants, so the record this endpoint serves the fetch from is the one it
1646 /// ends.
1647 ///
1648 /// # Errors
1649 ///
1650 /// [`EndpointError::UnknownSubscribe`] if the peer opened no fetch under
1651 /// that identifier, and the fetch flow's own `InvalidTransition` for a fetch
1652 /// that has already ended.
1653 pub fn receive_fetch_cancel(&mut self, msg: &FetchCancel) -> Result<(), EndpointError> {
1654 let id = msg.subscribe_id.into_inner();
1655 let fetch = self.inbound_fetches.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1656 fetch.state.on_fetch_cancel_received()?;
1657 Ok(())
1658 }
1659
1660 /// Note that this endpoint finished the data stream serving a fetch the
1661 /// peer opened.
1662 ///
1663 /// A fetch is over when its answer and its data stream have both settled,
1664 /// and this is the second of those for the end that serves it.
1665 ///
1666 /// # Errors
1667 ///
1668 /// [`EndpointError::UnknownSubscribe`] if the peer opened no fetch under
1669 /// that identifier, and the fetch flow's own `InvalidTransition` from a state
1670 /// the stream cannot close from.
1671 pub fn on_peer_fetch_stream_fin(&mut self, subscribe_id: VarInt) -> Result<(), EndpointError> {
1672 let id = subscribe_id.into_inner();
1673 let fetch = self.inbound_fetches.get_mut(&id).ok_or(EndpointError::UnknownSubscribe(id))?;
1674 fetch.state.on_stream_fin_sent()?;
1675 Ok(())
1676 }
1677
1678 // ── Subscribe Announces flow ───────────────────────────────
1679
1680 /// Send a SUBSCRIBE_ANNOUNCES message.
1681 ///
1682 /// Section 6.13: "A subscriber cannot make overlapping namespace
1683 /// subscriptions on a single session."
1684 ///
1685 /// # Errors
1686 ///
1687 /// The session error when the session is not established, and
1688 /// [`EndpointError::OwnPrefixOverlap`] when the prefix overlaps one this
1689 /// endpoint has already subscribed to.
1690 pub fn subscribe_announces(
1691 &mut self,
1692 track_namespace_prefix: TrackNamespace,
1693 ) -> Result<ControlMessage, EndpointError> {
1694 self.require_active_or_err()?;
1695 let key = track_namespace_prefix.0.clone();
1696 // The subscriber's half of the rule, refused before the message
1697 // exists. A publisher that follows this draft would answer it with
1698 // SUBSCRIBE_ANNOUNCES_ERROR, so building it wastes a round trip and
1699 // leaves this endpoint holding a namespace subscription that is not
1700 // going to open.
1701 if self.subscribe_announces.keys().any(|k| prefixes_overlap(k, &key)) {
1702 return Err(EndpointError::OwnPrefixOverlap);
1703 }
1704 let mut sm = SubscribeAnnouncesStateMachine::new();
1705 sm.on_subscribe_announces_sent()?;
1706 self.subscribe_announces.insert(key, sm);
1707 Ok(ControlMessage::SubscribeAnnounces(SubscribeAnnounces {
1708 track_namespace_prefix,
1709 parameters: vec![],
1710 }))
1711 }
1712
1713 /// Process an incoming SUBSCRIBE_ANNOUNCES_OK.
1714 pub fn receive_subscribe_announces_ok(
1715 &mut self,
1716 msg: &SubscribeAnnouncesOk,
1717 ) -> Result<(), EndpointError> {
1718 let sm = self
1719 .subscribe_announces
1720 .get_mut(&msg.track_namespace_prefix.0)
1721 .ok_or(EndpointError::UnknownNamespace)?;
1722 sm.on_subscribe_announces_ok()?;
1723 Ok(())
1724 }
1725
1726 /// Process an incoming SUBSCRIBE_ANNOUNCES_ERROR.
1727 pub fn receive_subscribe_announces_error(
1728 &mut self,
1729 msg: &SubscribeAnnouncesError,
1730 ) -> Result<(), EndpointError> {
1731 let sm = self
1732 .subscribe_announces
1733 .get_mut(&msg.track_namespace_prefix.0)
1734 .ok_or(EndpointError::UnknownNamespace)?;
1735 sm.on_subscribe_announces_error()?;
1736 Ok(())
1737 }
1738
1739 /// Send an UNSUBSCRIBE_ANNOUNCES message.
1740 pub fn unsubscribe_announces(
1741 &mut self,
1742 track_namespace_prefix: TrackNamespace,
1743 ) -> Result<ControlMessage, EndpointError> {
1744 let sm = self
1745 .subscribe_announces
1746 .get_mut(&track_namespace_prefix.0)
1747 .ok_or(EndpointError::UnknownNamespace)?;
1748 sm.on_unsubscribe_announces()?;
1749 Ok(ControlMessage::UnsubscribeAnnounces(UnsubscribeAnnounces { track_namespace_prefix }))
1750 }
1751
1752 // ── Answering a SUBSCRIBE_ANNOUNCES the peer sent ──────────
1753
1754 /// Process an incoming SUBSCRIBE_ANNOUNCES, recording the namespace
1755 /// subscription it opens.
1756 ///
1757 /// Section 6.13: "The subscriber sends the SUBSCRIBE_ANNOUNCES control
1758 /// message to a publisher to request the current set of matching
1759 /// announcements, as well as future updates to the set."
1760 ///
1761 /// The set it asks for is this endpoint's to decide, and deciding needs
1762 /// both the request and somewhere to answer from. The record holds the
1763 /// message and not only its state, because every message that answers
1764 /// this request or ends it names the prefix the request carried, and
1765 /// nothing else here has it.
1766 ///
1767 /// # Errors
1768 ///
1769 /// The session error when the session is not established, and
1770 /// [`EndpointError::PeerPrefixOverlap`] when the prefix overlaps one the
1771 /// peer has already subscribed to.
1772 pub fn receive_subscribe_announces(
1773 &mut self,
1774 msg: &SubscribeAnnounces,
1775 ) -> Result<(), EndpointError> {
1776 self.require_active_or_err()?;
1777 // Judged on arrival, because that is the moment the sentence names:
1778 // "if a publisher receives a SUBSCRIBE_ANNOUNCES ... it MUST respond
1779 // with SUBSCRIBE_ANNOUNCES_ERROR". Nothing is recorded for a request
1780 // this endpoint may not accept, so no later call can accept it.
1781 if self.peer_prefix_overlap(&msg.track_namespace_prefix) {
1782 return Err(EndpointError::PeerPrefixOverlap);
1783 }
1784 let mut state = SubscribeAnnouncesStateMachine::new();
1785 state.on_subscribe_announces_received()?;
1786 self.inbound_subscribe_announces.insert(
1787 msg.track_namespace_prefix.0.clone(),
1788 InboundSubscribeAnnounces { message: msg.clone(), state },
1789 );
1790 Ok(())
1791 }
1792
1793 /// Whether `prefix` overlaps a namespace subscription the peer has already
1794 /// made on this session.
1795 ///
1796 /// Every one of them counts, including a subscription the peer has since
1797 /// withdrawn: the sentence weighs the arriving prefix against "an earlier
1798 /// SUBSCRIBE_ANNOUNCES", and one that has ended was still earlier.
1799 ///
1800 /// Namespace subscriptions this endpoint made are a separate set and are
1801 /// not consulted. This endpoint is the subscriber for those, so a prefix
1802 /// it asked about says nothing about what the peer may ask about.
1803 fn peer_prefix_overlap(&self, prefix: &TrackNamespace) -> bool {
1804 self.inbound_subscribe_announces.keys().any(|k| prefixes_overlap(k, &prefix.0))
1805 }
1806
1807 /// The SUBSCRIBE_ANNOUNCES the peer sent for `prefix` and this endpoint
1808 /// has not answered yet.
1809 ///
1810 /// `None` once it has been answered, and for a prefix the peer has
1811 /// subscribed to nothing under. The record itself lives on past the
1812 /// answer, because a namespace subscription that was accepted is not over
1813 /// until it is withdrawn.
1814 pub fn pending_subscribe_announces(
1815 &self,
1816 prefix: &TrackNamespace,
1817 ) -> Option<&SubscribeAnnounces> {
1818 self.inbound_subscribe_announces
1819 .get(&prefix.0)
1820 .filter(|s| s.state.state() == SubscribeAnnouncesState::Pending)
1821 .map(|s| &s.message)
1822 }
1823
1824 /// How many namespace subscriptions the peer has made that are still
1825 /// waiting for an answer.
1826 pub fn pending_subscribe_announces_count(&self) -> usize {
1827 self.inbound_subscribe_announces
1828 .values()
1829 .filter(|s| s.state.state() == SubscribeAnnouncesState::Pending)
1830 .count()
1831 }
1832
1833 /// Build the SUBSCRIBE_ANNOUNCES_OK accepting a namespace subscription
1834 /// the peer made.
1835 ///
1836 /// Section 6.13: "The publisher will respond with SUBSCRIBE_ANNOUNCES_OK
1837 /// or SUBSCRIBE_ANNOUNCES_ERROR."
1838 ///
1839 /// One answer and no second one: the flow moves on the first, and a
1840 /// second call finds a record that has left Pending.
1841 ///
1842 /// # Errors
1843 ///
1844 /// [`EndpointError::UnknownPeerNamespaceSubscription`] if the peer has
1845 /// subscribed to nothing under that prefix, and the namespace flow's own
1846 /// `InvalidTransition` for a request already answered.
1847 pub fn send_subscribe_announces_ok(
1848 &mut self,
1849 track_namespace_prefix: TrackNamespace,
1850 ) -> Result<ControlMessage, EndpointError> {
1851 let sub = self
1852 .inbound_subscribe_announces
1853 .get_mut(&track_namespace_prefix.0)
1854 .ok_or(EndpointError::UnknownPeerNamespaceSubscription)?;
1855 sub.state.on_subscribe_announces_ok_sent()?;
1856 Ok(ControlMessage::SubscribeAnnouncesOk(SubscribeAnnouncesOk { track_namespace_prefix }))
1857 }
1858
1859 /// Build the SUBSCRIBE_ANNOUNCES_ERROR refusing a namespace subscription
1860 /// the peer made.
1861 ///
1862 /// The other half of the same sentence: one message back, whichever of
1863 /// the two it is.
1864 ///
1865 /// # Errors
1866 ///
1867 /// [`EndpointError::UnknownPeerNamespaceSubscription`] if the peer has
1868 /// subscribed to nothing under that prefix, and the namespace flow's own
1869 /// `InvalidTransition` for a request already answered.
1870 pub fn send_subscribe_announces_error(
1871 &mut self,
1872 track_namespace_prefix: TrackNamespace,
1873 error_code: VarInt,
1874 reason_phrase: Vec<u8>,
1875 ) -> Result<ControlMessage, EndpointError> {
1876 let sub = self
1877 .inbound_subscribe_announces
1878 .get_mut(&track_namespace_prefix.0)
1879 .ok_or(EndpointError::UnknownPeerNamespaceSubscription)?;
1880 sub.state.on_subscribe_announces_error_sent()?;
1881 Ok(ControlMessage::SubscribeAnnouncesError(SubscribeAnnouncesError {
1882 track_namespace_prefix,
1883 error_code,
1884 reason_phrase,
1885 }))
1886 }
1887
1888 /// Process an incoming UNSUBSCRIBE_ANNOUNCES, ending the namespace
1889 /// subscription the peer made.
1890 ///
1891 /// Section 6.14: "A subscriber issues a UNSUBSCRIBE_ANNOUNCES message to
1892 /// a publisher indicating it is no longer interested in ANNOUNCE and
1893 /// UNANNOUNCE messages for the specified track namespace prefix."
1894 ///
1895 /// The subscription it ends is the peer's, so the record it reads is the
1896 /// one this endpoint keeps of what the peer subscribed to. One this
1897 /// endpoint made is withdrawn by [`Endpoint::unsubscribe_announces`],
1898 /// which is the same message travelling the other way.
1899 ///
1900 /// # Errors
1901 ///
1902 /// [`EndpointError::UnknownPeerNamespaceSubscription`] if the peer has no
1903 /// live namespace subscription for that prefix, and the namespace flow's
1904 /// own `InvalidTransition` for one this endpoint never accepted.
1905 pub fn receive_unsubscribe_announces(
1906 &mut self,
1907 msg: &UnsubscribeAnnounces,
1908 ) -> Result<(), EndpointError> {
1909 let sub = self
1910 .inbound_subscribe_announces
1911 .get_mut(&msg.track_namespace_prefix.0)
1912 .ok_or(EndpointError::UnknownPeerNamespaceSubscription)?;
1913 sub.state.on_unsubscribe_announces_received()?;
1914 Ok(())
1915 }
1916
1917 // ── Announce flow ──────────────────────────────────────────
1918
1919 /// Send an ANNOUNCE message.
1920 pub fn announce(
1921 &mut self,
1922 track_namespace: TrackNamespace,
1923 ) -> Result<ControlMessage, EndpointError> {
1924 self.require_active_or_err()?;
1925 let key = track_namespace.0.clone();
1926 let mut sm = AnnounceStateMachine::new();
1927 sm.on_announce_sent()?;
1928 self.announce_cancelled.remove(&key);
1929 self.announces.insert(key, sm);
1930 Ok(ControlMessage::Announce(Announce { track_namespace, parameters: vec![] }))
1931 }
1932
1933 /// Process an incoming ANNOUNCE_OK.
1934 pub fn receive_announce_ok(&mut self, msg: &AnnounceOk) -> Result<(), EndpointError> {
1935 let sm = self
1936 .announces
1937 .get_mut(&msg.track_namespace.0)
1938 .ok_or(EndpointError::UnknownNamespace)?;
1939 sm.on_announce_ok()?;
1940 Ok(())
1941 }
1942
1943 /// Process an incoming ANNOUNCE_ERROR.
1944 pub fn receive_announce_error(&mut self, msg: &AnnounceError) -> Result<(), EndpointError> {
1945 let sm = self
1946 .announces
1947 .get_mut(&msg.track_namespace.0)
1948 .ok_or(EndpointError::UnknownNamespace)?;
1949 sm.on_announce_error()?;
1950 Ok(())
1951 }
1952
1953 /// Process an incoming ANNOUNCE_CANCEL.
1954 pub fn receive_announce_cancel(&mut self, msg: &AnnounceCancel) -> Result<(), EndpointError> {
1955 let sm = self
1956 .announces
1957 .get_mut(&msg.track_namespace.0)
1958 .ok_or(EndpointError::UnknownNamespace)?;
1959 sm.on_announce_cancel()?;
1960 self.announce_cancelled.insert(msg.track_namespace.0.clone());
1961 Ok(())
1962 }
1963
1964 /// Send an UNANNOUNCE message (publisher withdrawing).
1965 pub fn unannounce(
1966 &mut self,
1967 track_namespace: TrackNamespace,
1968 ) -> Result<ControlMessage, EndpointError> {
1969 let sm =
1970 self.announces.get_mut(&track_namespace.0).ok_or(EndpointError::UnknownNamespace)?;
1971 sm.on_unannounce()?;
1972 Ok(ControlMessage::Unannounce(Unannounce { track_namespace }))
1973 }
1974
1975 // ── Answering an ANNOUNCE the peer sent ────────────────────
1976
1977 /// Process an incoming ANNOUNCE, recording the announcement it makes.
1978 ///
1979 /// Section 6.21: "The publisher sends the ANNOUNCE control message to
1980 /// advertise where the receiver can route SUBSCRIBEs for tracks within the
1981 /// announced Track Namespace. The receiver verifies the publisher is
1982 /// authorized to publish tracks under this namespace."
1983 ///
1984 /// Verifying is the application's to do, and it needs both the message to
1985 /// verify and somewhere to answer from. This draft's ANNOUNCE carries no
1986 /// Request ID, so the namespace it names is what the record is filed under,
1987 /// and a second one naming a namespace already held replaces it. No
1988 /// sentence makes a repeat an error, and the newest advertisement is the
1989 /// one an answer has to be built from.
1990 ///
1991 /// # Errors
1992 ///
1993 /// The session error when the session is not established.
1994 pub fn receive_announce(&mut self, msg: &Announce) -> Result<(), EndpointError> {
1995 self.require_active_or_err()?;
1996 let key = msg.track_namespace.0.clone();
1997 let mut state = AnnounceStateMachine::new();
1998 state.on_announce_received()?;
1999 self.inbound_announces.insert(key, InboundAnnounce { message: msg.clone(), state });
2000 Ok(())
2001 }
2002
2003 /// The ANNOUNCE the peer sent for `track_namespace` and this endpoint has
2004 /// not answered yet.
2005 ///
2006 /// `None` once it has been answered, and for a namespace the peer has
2007 /// announced nothing under. The record itself lives on past the answer,
2008 /// because an announcement that was accepted is not over until it is
2009 /// withdrawn or cancelled.
2010 pub fn pending_announce(&self, track_namespace: &TrackNamespace) -> Option<&Announce> {
2011 self.inbound_announces
2012 .get(&track_namespace.0)
2013 .filter(|a| a.state.state() == AnnounceState::Pending)
2014 .map(|a| &a.message)
2015 }
2016
2017 /// How many announcements the peer has made that are still waiting for an
2018 /// answer.
2019 pub fn pending_announce_count(&self) -> usize {
2020 self.inbound_announces
2021 .values()
2022 .filter(|a| a.state.state() == AnnounceState::Pending)
2023 .count()
2024 }
2025
2026 /// Build the ANNOUNCE_OK accepting an announcement the peer made.
2027 ///
2028 /// Section 5.2: "The entity receiving the ANNOUNCE MUST send only a single
2029 /// response to a given ANNOUNCE of either ANNOUNCE_OK or ANNOUNCE_ERROR."
2030 ///
2031 /// One answer and no second one: the flow moves on the first, and a second
2032 /// call finds a record that has left Pending.
2033 ///
2034 /// # Errors
2035 ///
2036 /// [`EndpointError::UnknownPeerNamespace`] if the peer has announced
2037 /// nothing under that namespace, and the namespace flow's own
2038 /// `InvalidTransition` for an announcement already answered.
2039 pub fn send_announce_ok(
2040 &mut self,
2041 track_namespace: TrackNamespace,
2042 ) -> Result<ControlMessage, EndpointError> {
2043 let ann = self
2044 .inbound_announces
2045 .get_mut(&track_namespace.0)
2046 .ok_or(EndpointError::UnknownPeerNamespace)?;
2047 ann.state.on_announce_ok_sent()?;
2048 Ok(ControlMessage::AnnounceOk(AnnounceOk { track_namespace }))
2049 }
2050
2051 /// Build the ANNOUNCE_ERROR refusing an announcement the peer made.
2052 ///
2053 /// The same sentence in Section 5.2 answers both ways: one message back and
2054 /// no second one, whichever of the two it is.
2055 ///
2056 /// # Errors
2057 ///
2058 /// [`EndpointError::UnknownPeerNamespace`] if the peer has announced
2059 /// nothing under that namespace, and the namespace flow's own
2060 /// `InvalidTransition` for an announcement already answered.
2061 pub fn send_announce_error(
2062 &mut self,
2063 track_namespace: TrackNamespace,
2064 error_code: VarInt,
2065 reason_phrase: Vec<u8>,
2066 ) -> Result<ControlMessage, EndpointError> {
2067 let ann = self
2068 .inbound_announces
2069 .get_mut(&track_namespace.0)
2070 .ok_or(EndpointError::UnknownPeerNamespace)?;
2071 ann.state.on_announce_error_sent()?;
2072 Ok(ControlMessage::AnnounceError(AnnounceError {
2073 track_namespace,
2074 error_code,
2075 reason_phrase,
2076 }))
2077 }
2078
2079 /// Process an incoming UNANNOUNCE, ending the announcement the peer made.
2080 ///
2081 /// Section 6.22: "The publisher sends the UNANNOUNCE control message to
2082 /// indicate its intent to stop serving new subscriptions for tracks within
2083 /// the provided Track Namespace."
2084 ///
2085 /// The announcement it ends is the peer's, so the record it reads is the
2086 /// one this endpoint keeps of what the peer announced. An announcement this
2087 /// endpoint made is withdrawn by [`Self::unannounce`], which is the same
2088 /// message travelling the other way.
2089 ///
2090 /// # Errors
2091 ///
2092 /// [`EndpointError::UnknownPeerNamespace`] if the peer has no live
2093 /// announcement for that namespace, and the namespace flow's own
2094 /// `InvalidTransition` for one this endpoint never accepted.
2095 pub fn receive_unannounce(&mut self, msg: &Unannounce) -> Result<(), EndpointError> {
2096 let ann = self
2097 .inbound_announces
2098 .get_mut(&msg.track_namespace.0)
2099 .ok_or(EndpointError::UnknownPeerNamespace)?;
2100 ann.state.on_unannounce_received()?;
2101 Ok(())
2102 }
2103
2104 /// Build the ANNOUNCE_CANCEL revoking an acceptance.
2105 ///
2106 /// Section 5.2 names what a cancellation revokes: a namespace "it
2107 /// previously responded ANNOUNCE_OK to". Section 6.11 says what it does:
2108 /// the subscriber "will stop sending new subscriptions for tracks within
2109 /// the provided Track Namespace".
2110 ///
2111 /// Previously responded ANNOUNCE_OK to is a state, and it is Active: an
2112 /// announcement reaches it by being accepted and no other way. One still
2113 /// waiting for an answer, one refused and one already ended are all refused
2114 /// here rather than sent.
2115 ///
2116 /// The announcement is the peer's. An announcement this endpoint made is
2117 /// not cancelled by its own publisher; the peer cancels it, and that
2118 /// arrives at [`Self::receive_announce_cancel`].
2119 ///
2120 /// # Errors
2121 ///
2122 /// [`EndpointError::UnknownPeerNamespace`] if the peer has no live
2123 /// announcement for that namespace, and the namespace flow's own
2124 /// `InvalidTransition` for one this endpoint never accepted.
2125 pub fn announce_cancel(
2126 &mut self,
2127 track_namespace: TrackNamespace,
2128 error_code: VarInt,
2129 reason_phrase: Vec<u8>,
2130 ) -> Result<ControlMessage, EndpointError> {
2131 let ann = self
2132 .inbound_announces
2133 .get_mut(&track_namespace.0)
2134 .ok_or(EndpointError::UnknownPeerNamespace)?;
2135 ann.state.on_announce_cancel_sent()?;
2136 Ok(ControlMessage::AnnounceCancel(AnnounceCancel {
2137 track_namespace,
2138 error_code,
2139 reason_phrase,
2140 }))
2141 }
2142
2143 // ── Track Status flow ──────────────────────────────────────
2144
2145 /// Send a TRACK_STATUS_REQUEST message.
2146 pub fn track_status_request(
2147 &mut self,
2148 track_namespace: TrackNamespace,
2149 track_name: Vec<u8>,
2150 ) -> Result<ControlMessage, EndpointError> {
2151 self.require_active_or_err()?;
2152 let key = (track_namespace.0.clone(), track_name.clone());
2153 let mut sm = TrackStatusStateMachine::new();
2154 sm.on_track_status_request_sent()?;
2155 self.track_statuses.insert(key, sm);
2156 Ok(ControlMessage::TrackStatusRequest(TrackStatusRequest { track_namespace, track_name }))
2157 }
2158
2159 /// Process an incoming TRACK_STATUS reply.
2160 pub fn receive_track_status(&mut self, msg: &TrackStatus) -> Result<(), EndpointError> {
2161 let key = (msg.track_namespace.0.clone(), msg.track_name.clone());
2162 let sm = self.track_statuses.get_mut(&key).ok_or(EndpointError::UnknownTrackStatus)?;
2163 sm.on_track_status()?;
2164 Ok(())
2165 }
2166
2167 // ── Answering a TRACK_STATUS_REQUEST the peer sent ─────────
2168
2169 /// Process an incoming TRACK_STATUS_REQUEST, recording what the peer asked
2170 /// about.
2171 ///
2172 /// Section 6.12: "A potential subscriber sends a 'TRACK_STATUS_REQUEST'
2173 /// message on the control stream to obtain information about the current
2174 /// status of a given track."
2175 ///
2176 /// Answering is the application's to do, and it needs both the request and
2177 /// somewhere to answer from. This draft's request carries no Request ID, so
2178 /// the track it names is what the record is filed under, and a second
2179 /// request for a track already asked about replaces it. No sentence makes a
2180 /// repeat an error, and the newest request is the one an answer has to be
2181 /// built from.
2182 ///
2183 /// # Errors
2184 ///
2185 /// The session error when the session is not established.
2186 pub fn receive_track_status_request(
2187 &mut self,
2188 msg: &TrackStatusRequest,
2189 ) -> Result<(), EndpointError> {
2190 self.require_active_or_err()?;
2191 let key = (msg.track_namespace.0.clone(), msg.track_name.clone());
2192 let mut state = TrackStatusStateMachine::new();
2193 state.on_track_status_request_received()?;
2194 self.inbound_track_statuses.insert(key, InboundTrackStatus { message: msg.clone(), state });
2195 Ok(())
2196 }
2197
2198 /// The TRACK_STATUS_REQUEST the peer sent about this track and this
2199 /// endpoint has not answered yet.
2200 ///
2201 /// `None` once it has been answered, and for a track the peer has asked
2202 /// nothing about.
2203 pub fn pending_track_status_request(
2204 &self,
2205 track_namespace: &TrackNamespace,
2206 track_name: &[u8],
2207 ) -> Option<&TrackStatusRequest> {
2208 self.inbound_track_statuses
2209 .get(&(track_namespace.0.clone(), track_name.to_vec()))
2210 .filter(|t| t.state.state() == TrackStatusState::Pending)
2211 .map(|t| &t.message)
2212 }
2213
2214 /// How many track statuses the peer has asked about that are still waiting
2215 /// for an answer.
2216 pub fn pending_track_status_request_count(&self) -> usize {
2217 self.inbound_track_statuses
2218 .values()
2219 .filter(|t| t.state.state() == TrackStatusState::Pending)
2220 .count()
2221 }
2222
2223 /// Build the TRACK_STATUS answering a request the peer sent.
2224 ///
2225 /// Section 6.12 leaves the answering end no discretion about whether to
2226 /// answer: "A TRACK_STATUS message MUST be sent in response to each
2227 /// TRACK_STATUS_REQUEST." What it bounds is how many, and that half is what
2228 /// the record carries: the request leaves `Pending` on the first answer, so
2229 /// a second call finds nothing left to answer.
2230 ///
2231 /// Section 6.23 says which track the answer is about, and this draft has
2232 /// no identifier to say it with, so the caller names the track and the
2233 /// message repeats it.
2234 ///
2235 /// # Errors
2236 ///
2237 /// [`EndpointError::UnknownPeerTrackStatus`] if the peer has asked nothing
2238 /// about that track, and the flow's own `InvalidTransition` for a request
2239 /// already answered.
2240 pub fn send_track_status(
2241 &mut self,
2242 track_namespace: TrackNamespace,
2243 track_name: Vec<u8>,
2244 status_code: VarInt,
2245 last_group_id: VarInt,
2246 last_object_id: VarInt,
2247 ) -> Result<ControlMessage, EndpointError> {
2248 let key = (track_namespace.0.clone(), track_name.clone());
2249 let req = self
2250 .inbound_track_statuses
2251 .get_mut(&key)
2252 .ok_or(EndpointError::UnknownPeerTrackStatus)?;
2253 req.state.on_track_status_sent()?;
2254 Ok(ControlMessage::TrackStatus(TrackStatus {
2255 track_namespace,
2256 track_name,
2257 status_code,
2258 last_group_id,
2259 last_object_id,
2260 }))
2261 }
2262
2263 // ── Unified message dispatch ───────────────────────────────
2264
2265 /// Dispatch an incoming control message to the appropriate handler.
2266 pub fn receive_message(&mut self, msg: ControlMessage) -> Result<(), EndpointError> {
2267 match msg {
2268 ControlMessage::GoAway(ref m) => self.receive_goaway(m),
2269 ControlMessage::MaxSubscribeId(ref m) => self.receive_max_subscribe_id(m),
2270 ControlMessage::SubscribeOk(ref m) => self.receive_subscribe_ok(m),
2271 ControlMessage::SubscribeError(ref m) => self.receive_subscribe_error(m),
2272 ControlMessage::SubscribeUpdate(ref m) => self.receive_subscribe_update(m),
2273 ControlMessage::SubscribeDone(ref m) => self.receive_subscribe_done(m),
2274 ControlMessage::FetchOk(ref m) => self.receive_fetch_ok(m),
2275 ControlMessage::FetchError(ref m) => self.receive_fetch_error(m),
2276 ControlMessage::SubscribeAnnouncesOk(ref m) => self.receive_subscribe_announces_ok(m),
2277 ControlMessage::SubscribeAnnouncesError(ref m) => {
2278 self.receive_subscribe_announces_error(m)
2279 }
2280 ControlMessage::AnnounceOk(ref m) => self.receive_announce_ok(m),
2281 ControlMessage::AnnounceError(ref m) => self.receive_announce_error(m),
2282 ControlMessage::AnnounceCancel(ref m) => self.receive_announce_cancel(m),
2283 ControlMessage::TrackStatus(ref m) => self.receive_track_status(m),
2284 ControlMessage::TrackStatusRequest(ref m) => self.receive_track_status_request(m),
2285 ControlMessage::Subscribe(ref m) => self.receive_subscribe(m),
2286 ControlMessage::Fetch(ref m) => self.receive_fetch(m),
2287 ControlMessage::FetchCancel(ref m) => self.receive_fetch_cancel(m),
2288 ControlMessage::Unsubscribe(ref m) => self.receive_unsubscribe(m),
2289 ControlMessage::Announce(ref m) => self.receive_announce(m),
2290 ControlMessage::Unannounce(ref m) => self.receive_unannounce(m),
2291 ControlMessage::SubscribeAnnounces(ref m) => self.receive_subscribe_announces(m),
2292 ControlMessage::UnsubscribeAnnounces(ref m) => self.receive_unsubscribe_announces(m),
2293 _ => Ok(()),
2294 }
2295 }
2296}