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