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