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