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