moqtap_client/draft15/endpoint.rs
1use std::collections::HashMap;
2use std::sync::{Arc, Mutex, MutexGuard};
3
4use crate::draft15::fetch::{FetchError, FetchState, FetchStateMachine};
5use crate::draft15::namespace::{
6 NamespaceError, PublishNamespaceState, PublishNamespaceStateMachine, SubscribeNamespaceState,
7 SubscribeNamespaceStateMachine,
8};
9use crate::draft15::publish::{
10 PublishError as PublishFlowError, PublishState, PublishStateMachine,
11};
12use crate::draft15::session::request_id::{RequestIdAllocator, RequestIdError, Role};
13use crate::draft15::session::setup::{self, SetupError};
14use crate::draft15::session::state::{SessionError, SessionState, SessionStateMachine};
15use crate::draft15::subscription::{
16 SubscriptionError, SubscriptionState, SubscriptionStateMachine,
17};
18use crate::draft15::track_status::{TrackStatusError, TrackStatusState, TrackStatusStateMachine};
19use crate::forwarding_preference::{ObjectForwardingPreference, TrackForwardingPreferences};
20use crate::malformed_tracks::{MalformedTrackCondition, MalformedTracks};
21use crate::track_locations::{ObjectLocation, ObjectRole, TrackLocations, TrackObjects};
22use moqtap_codec::draft15::error_codes::{RequestErrorCode, SessionErrorCode};
23use moqtap_codec::draft15::message::{
24 self, ClientSetup, ControlMessage, Fetch, FetchCancel, FetchPayload, FetchType, GoAway,
25 MaxRequestId, PublishDone, PublishNamespace, PublishNamespaceCancel, PublishNamespaceDone,
26 RequestError, RequestOk, RequestsBlocked, ServerSetup, Subscribe, SubscribeNamespace,
27 SubscribeOk, SubscribeUpdate, Unsubscribe, UnsubscribeNamespace,
28};
29use moqtap_codec::kvp::{KeyValuePair, KvpValue};
30use moqtap_codec::types::*;
31use moqtap_codec::varint::VarInt;
32
33/// Errors that can occur during endpoint operations.
34#[derive(Debug, thiserror::Error)]
35pub enum EndpointError {
36 /// A GOAWAY carrying a New Session URI arrived at a server.
37 ///
38 /// Section 9.4: "If a server receives a GOAWAY with a non-zero New
39 /// Session URI Length it MUST terminate the session with a
40 /// PROTOCOL_VIOLATION." Migration is something a server offers a client, never the
41 /// other way round.
42 #[error("GOAWAY carrying a New Session URI received at a server")]
43 GoAwayUriAtServer,
44 /// A session-level state machine error.
45 #[error("session error: {0}")]
46 Session(#[from] SessionError),
47 /// A request ID allocation or validation error.
48 #[error("request ID error: {0}")]
49 RequestId(#[from] RequestIdError),
50 /// This endpoint was asked to advertise a Maximum Request ID that does
51 /// not increase, and refused. Nothing was written.
52 ///
53 /// The send-side mirror of the rule a peer breaks by sending one — Section 9.5:
54 /// "The Maximum Request ID MUST only increase within a session". No closing
55 /// mark, because the draft's sentence does not close there: it runs on
56 /// into the receipt half, which is the peer's side of this rule and not
57 /// this one's. Its own
58 /// variant, and not the received one, because the two are opposite
59 /// findings that would otherwise arrive as the same value: the received one
60 /// is a peer in violation and this one is a caller of this library asking
61 /// for a message that would put this endpoint in violation, and
62 /// [`EndpointError::session_error_code`] answers `Some` for it either way.
63 ///
64 /// Not fatal. The message is refused instead of built, the ceiling stays
65 /// where it was, and nothing reaches the peer to object to.
66 #[error(
67 "the Maximum Request ID already advertised is {advertised}, so {offered} would not increase it"
68 )]
69 MaxRequestIdWouldNotIncrease {
70 /// The ceiling this endpoint has already advertised.
71 advertised: u64,
72 /// The value it was asked to advertise instead.
73 offered: u64,
74 },
75 /// A subscription state machine error.
76 #[error("subscription error: {0}")]
77 Subscription(#[from] SubscriptionError),
78 /// A fetch state machine error.
79 #[error("fetch error: {0}")]
80 Fetch(#[from] FetchError),
81 /// A namespace state machine error.
82 #[error("namespace error: {0}")]
83 Namespace(#[from] NamespaceError),
84 /// A track status state machine error.
85 #[error("track status error: {0}")]
86 TrackStatus(#[from] TrackStatusError),
87 /// A publish flow state machine error.
88 #[error("publish flow error: {0}")]
89 PublishFlow(#[from] PublishFlowError),
90 /// A setup negotiation error.
91 #[error("setup error: {0}")]
92 Setup(#[from] SetupError),
93 /// The request ID does not match any known state machine.
94 #[error("unknown request ID: {0}")]
95 UnknownRequest(u64),
96 /// A message that only a subscription can carry named a track status.
97 ///
98 /// Section 9.19 treats a TRACK_STATUS as a SUBSCRIBE "except it does not
99 /// create downstream subscription state", and says what follows from that
100 /// in the same breath: "the subscriber cannot send SUBSCRIBE_UPDATE or
101 /// UNSUBSCRIBE". Both messages are about a subscription, and this request
102 /// opened none for them to name.
103 ///
104 /// Separate from [`EndpointError::UnknownRequest`], which says the
105 /// identifier names nothing at all. This one says it names something, and
106 /// that what it names is the one request kind neither message applies to.
107 #[error("request {0} is a track status, which cannot be updated or unsubscribed")]
108 NotASubscription(u64),
109 /// The track namespace does not match any announcement this endpoint made.
110 ///
111 /// PUBLISH_NAMESPACE_DONE and PUBLISH_NAMESPACE_CANCEL name a namespace on
112 /// this draft where the PUBLISH_NAMESPACE they are about named a Request
113 /// ID, so a namespace that names nothing is a miss this endpoint has to be
114 /// able to report on its own announcements as well as on the peer's.
115 #[error("this endpoint has made no live announcement for this namespace")]
116 UnknownNamespace,
117 /// The session is not in the Active state.
118 #[error("session not active")]
119 NotActive,
120 /// The session is draining and cannot accept new requests.
121 #[error("session is draining, no new requests allowed")]
122 Draining,
123 /// A second GOAWAY arrived on the control stream.
124 ///
125 /// The GOAWAY that says the peer is going away is one message, and the
126 /// draft answers a repeat of it with a session close rather than with an
127 /// error about the second message: there is no state a second one could
128 /// move that the first has not already moved.
129 #[error("a second GOAWAY arrived on the control stream")]
130 RepeatedGoAway,
131 /// The peer named a Track Alias it is already using for another track.
132 ///
133 /// Draft-15 states it twice, once per message. Section 9.10: "The same
134 /// Track Alias MUST NOT be used to refer to two different Tracks
135 /// simultaneously. If a subscriber receives a SUBSCRIBE_OK that uses the
136 /// same Track Alias as a different track with an Established subscription,
137 /// it MUST close the session with error DUPLICATE_TRACK_ALIAS." Section
138 /// 9.13 is the same sentence with PUBLISH in place of SUBSCRIBE_OK.
139 ///
140 /// The session is over: this endpoint's own state has moved to Closed and
141 /// the code the transport should close with is in
142 /// [`EndpointError::session_error_code`].
143 #[error("track alias {alias} already names request {established}'s track; request {offered} names a different one")]
144 DuplicateTrackAlias {
145 /// The alias both tracks are named by.
146 alias: u64,
147 /// The request whose live subscription holds the alias.
148 established: u64,
149 /// The request whose message arrived naming it for another track.
150 offered: u64,
151 },
152 /// This endpoint was asked to give a Track Alias to a second track.
153 ///
154 /// The same rule as [`EndpointError::DuplicateTrackAlias`] read at the end
155 /// that chooses the alias. Section 9.13 states it as a prohibition on the
156 /// publisher before it states what the subscriber does about one: "The
157 /// same Track Alias MUST NOT be used to refer to two different Tracks
158 /// simultaneously."
159 ///
160 /// The message is refused instead of built, and nothing else moves: no
161 /// Request ID is spent, no publish flow is created, and the session stays
162 /// as it was. The alias never reaches the peer, so there is nothing for
163 /// the peer to close over.
164 #[error("track alias {alias} already names request {held}'s track")]
165 TrackAliasInUse {
166 /// The alias that is already spoken for.
167 alias: u64,
168 /// The request whose live flow holds it.
169 held: u64,
170 },
171 /// A track's objects were framed two different ways.
172 ///
173 /// Section 10: "Every Track has a single 'Object Forwarding Preference'
174 /// and the Original Publisher MUST NOT mix different forwarding
175 /// preferences within a single track (see Section 2.4.2)."
176 ///
177 /// The framing is the preference: an object on a subgroup stream has the
178 /// Subgroup preference and an object in a datagram has the Datagram one,
179 /// so the track's first object settles the property and this is every
180 /// later object measured against it.
181 ///
182 /// # Why this one ends no session
183 ///
184 /// Drafts 07 through 11 finish that paragraph with a close and a code.
185 /// This draft replaces the sentence with the cross-reference above, and
186 /// what it points at is a different answer rather than the same one worded
187 /// differently. Section 2.4.2 lists "An Object is received with a
188 /// different Forwarding Preference than previously observed from the same
189 /// Track" among the conditions that make a track malformed, and says of
190 /// all of them: "When a subscriber detects a Malformed Track, it MUST
191 /// UNSUBSCRIBE any subscription and FETCH_CANCEL any fetch for that Track
192 /// from that publisher, and SHOULD deliver an error to the application".
193 ///
194 /// So this error carries no code in [`EndpointError::session_error_code`],
195 /// and the connection's `close_for_data_stream` declines it. Ending the
196 /// session over it would be this crate inventing a consequence the draft
197 /// withdrew.
198 ///
199 /// This is half of that answer and not all of it. "SHOULD deliver an error
200 /// to the application" is what this error is. The unsubscribe and cancel
201 /// the same sentence requires is not done: those are control messages, and
202 /// a data path holding `&self` has no way to send one. It is also one
203 /// condition of eight in that section rather than a rule of its own, so
204 /// the answer belongs to the family and not to this arm. A caller that
205 /// wants it has the error to act on.
206 #[error(
207 "track alias {alias} carries objects framed as {established}, and one is \
208 framed as {offered}"
209 )]
210 MixedForwardingPreference {
211 /// The Track Alias the offending object named.
212 alias: u64,
213 /// The framing the track's earlier objects settled on.
214 established: ObjectForwardingPreference,
215 /// The framing the offending object used.
216 offered: ObjectForwardingPreference,
217 },
218 /// An object arriving after the track's final object.
219 ///
220 /// Section 2.4.2 lists the condition: "An Object is received on a
221 /// Track whose Group and Object ID are larger than the final Object in the
222 /// Track. The final Object in a Track is the Object with Status
223 /// END_OF_TRACK or the last Object sent in a FETCH whose response indicated
224 /// End of Track."
225 ///
226 /// **Larger is Section 1.4.1's comparison and not a reading of the
227 /// words.** That section puts one Location below another when "A.Group <
228 /// B.Group || (A.Group == B.Group && A.Object < B.Object)", so an Object in
229 /// a later group is past the end whatever its own Object ID is.
230 ///
231 /// **A Malformed Track and not a session error**, like the arm above it and
232 /// for the same sentence: Section 2.4.2 answers its whole list at
233 /// once, and on this draft that answer is "it MUST UNSUBSCRIBE any
234 /// subscription and FETCH_CANCEL any fetch for that Track from that
235 /// publisher". The messages are the connection's; this is the error half.
236 #[error(
237 "the object at group {group}, object {object} on track alias {alias} arrived \
238 after the track's final object at group {final_group}, object {final_object}"
239 )]
240 ObjectPastFinalObject {
241 /// The Track Alias the offending object named.
242 alias: u64,
243 /// The Group ID it named.
244 group: u64,
245 /// The Object ID it named.
246 object: u64,
247 /// The Group ID of the object the track ended at.
248 final_group: u64,
249 /// The Object ID of the object the track ended at.
250 final_object: u64,
251 },
252 /// SUBSCRIBE_UPDATE named a Request ID this session has never carried a
253 /// request under.
254 ///
255 /// Section 9.2.1.7: "If the length of the Subscription Filter does not match
256 /// the parameter length, the publisher MUST close the session with
257 /// PROTOCOL_VIOLATION."
258 ///
259 /// A request that has **ended** is not this: it existed. That is why the
260 /// record of an inbound SUBSCRIBE outlives the subscription, and why an
261 /// update naming an ended one is refused by the flow rather than by the
262 /// session.
263 #[error("SUBSCRIBE_UPDATE names request {0}, which this session has never carried")]
264 UpdateForUnknownRequest(u64),
265
266 /// A Joining Fetch named a subscription this session cannot join.
267 ///
268 /// Section 9.16.2: "If a publisher receives a Joining Fetch with a Request ID that
269 /// does not correspond to an existing Subscribe in the same session, it
270 /// MUST return a REQUEST_ERROR with error code INVALID_JOINING_REQUEST_ID".
271 ///
272 /// A refusal and not a session close, so the session runs on and the error
273 /// names both identifiers: the fetch to refuse, and the subscription it
274 /// asked to join.
275 #[error("FETCH {fetch} joins request {joining}, which is no live subscription of the peer's")]
276 UnjoinableSubscription {
277 /// The fetch that named it.
278 fetch: u64,
279 /// The identifier it named.
280 joining: u64,
281 },
282
283 /// A Joining Fetch was refused under a code other than the one the same
284 /// sentence names for it.
285 ///
286 /// The reason travels with the refusal, so a subscriber told the wrong one
287 /// retries the wrong thing: it can rebuild a fetch whose range was refused,
288 /// and cannot rebuild one whose subscription is gone.
289 #[error("refusing FETCH {fetch} for the subscription it joins takes error code {required}")]
290 WrongJoiningRefusal {
291 /// The fetch being refused.
292 fetch: u64,
293 /// The code the draft names for that refusal.
294 required: u64,
295 },
296 /// A message about an announcement named a namespace the peer has not
297 /// announced.
298 ///
299 /// Section 9.22 says what a cancellation is for: the subscriber "will stop
300 /// sending new subscriptions for tracks within the provided Track
301 /// Namespace". What a withdrawal ends and a cancellation revokes is an
302 /// announcement the **peer** made, so the record they reach for is the one
303 /// this endpoint keeps of the peer's announcements.
304 ///
305 /// Separate from [`EndpointError::UnknownNamespace`], which is the same
306 /// miss on the announcements this endpoint made, so a caller can tell which
307 /// of the two maps came up empty.
308 #[error("the peer has made no live announcement for this namespace")]
309 UnknownPeerNamespace,
310 /// The peer subscribed to a namespace prefix overlapping one it is
311 /// already subscribed to.
312 ///
313 /// Section 9.23: "A subscriber cannot make overlapping namespace
314 /// subscriptions on a single session. Within a session, if a publisher
315 /// receives a SUBSCRIBE_NAMESPACE with a Track Namespace Prefix that
316 /// shares a common prefix with an established namespace subscription, it
317 /// MUST respond with REQUEST_ERROR with error code PREFIX_OVERLAP."
318 ///
319 /// Taken when the message arrives, which is the moment the sentence
320 /// names, and read again when an answer is built: a request this endpoint
321 /// may not accept is one no later call can accept.
322 ///
323 /// The refusal itself is not this error. It is a message the peer is
324 /// owed, so the request is recorded like any other and refused through
325 /// the same call that refuses any other, under the code the sentence
326 /// names.
327 #[error(
328 "request {request} subscribes to a namespace prefix overlapping request {established}"
329 )]
330 PeerPrefixOverlap {
331 /// The request that arrived.
332 request: u64,
333 /// The namespace subscription it overlaps.
334 established: u64,
335 },
336 /// This endpoint was asked to subscribe to a namespace prefix overlapping
337 /// one it is already subscribed to.
338 ///
339 /// The first half of the same sentence, which is addressed to the
340 /// subscriber: "A subscriber cannot make overlapping namespace
341 /// subscriptions on a single session."
342 ///
343 /// The message is refused instead of built, and nothing else moves: no
344 /// Request ID is spent, no state machine is created and the session stays
345 /// as it was. The request never reaches the peer, so there is nothing for
346 /// the peer to refuse.
347 #[error("the namespace prefix overlaps request {established}, which this endpoint made")]
348 OwnPrefixOverlap {
349 /// The namespace subscription this endpoint already has.
350 established: u64,
351 },
352 /// A namespace subscription that overlaps another was refused under a
353 /// code other than the one the sentence names.
354 ///
355 /// The same shape as [`EndpointError::WrongJoiningRefusal`]: a rule that
356 /// names the code its refusal carries is not satisfied by a refusal under
357 /// any other, because the peer reads the code to learn what went wrong.
358 #[error("request {request} overlaps a namespace subscription and must be refused with code {required:#x}")]
359 WrongOverlapRefusal {
360 /// The request being refused.
361 request: u64,
362 /// The code the sentence names for it.
363 required: u64,
364 },
365}
366
367/// Whether two namespace prefixes overlap.
368///
369/// Section 9.23: "A subscriber cannot make overlapping namespace
370/// subscriptions on a single session. Within a session, if a publisher
371/// receives a SUBSCRIBE_NAMESPACE with a Track Namespace Prefix that shares a
372/// common prefix with an established namespace subscription, it MUST respond
373/// with REQUEST_ERROR with error code PREFIX_OVERLAP."
374///
375/// A namespace matches a namespace subscription when the subscription's
376/// prefix is a prefix of it, so two prefixes select overlapping sets of
377/// namespaces exactly when one of them is a prefix of the other. Equal
378/// prefixes are that case as well: every prefix is a prefix of itself, and
379/// two equal ones select the same set.
380///
381/// "Shares a common prefix with" is read as the relation drafts 07 through 14
382/// spell out at greater length. Taken at its word it would forbid every
383/// second namespace subscription in a session, since any two prefixes share
384/// the empty one, and nothing else in this draft supports that.
385fn prefixes_overlap(a: &[Vec<u8>], b: &[Vec<u8>]) -> bool {
386 let shared = a.len().min(b.len());
387 a[..shared] == b[..shared]
388}
389
390impl EndpointError {
391 /// Whose doing this is — the peer's, or this endpoint's, or a variant that
392 /// cannot say.
393 ///
394 /// The companion of [`EndpointError::session_error_code`], which answers
395 /// *what the draft requires be done about it*. Neither answers the other's
396 /// question and the pair is what a caller needs: a code without a side
397 /// names nobody, and a side without a code is not grounds to publish
398 /// anything.
399 ///
400 /// Exhaustive, with no wildcard arm, so a variant added to this draft's
401 /// `EndpointError` is a compile error here rather than a silent arrival on
402 /// the wrong side of the answer. See
403 /// [`EndpointFault`](crate::above_codec_rules::EndpointFault) for the three
404 /// answers and for the collision that made the third one necessary.
405 pub fn fault(&self) -> crate::above_codec_rules::EndpointFault {
406 use crate::above_codec_rules::{AboveCodecRule as Rule, EndpointFault as Fault};
407
408 match self {
409 // Raised on both a receive path and a send path, so the
410 // variant cannot say which end is at fault. The state machines
411 // render as `invalid transition from X on event Y` whichever end
412 // asked for the transition, and the unknown-request errors name
413 // an id that may be one the peer sent or one a caller here made
414 // up.
415 EndpointError::Session(..)
416 | EndpointError::Subscription(..)
417 | EndpointError::Fetch(..)
418 | EndpointError::Namespace(..)
419 | EndpointError::TrackStatus(..)
420 | EndpointError::PublishFlow(..)
421 | EndpointError::Setup(..)
422 | EndpointError::UnknownRequest(..)
423 | EndpointError::UnknownNamespace
424 | EndpointError::UnknownPeerNamespace => Fault::EitherEnd,
425
426 // Raised on the way out. Nothing reached the wire, so none of
427 // these is evidence about a peer — including the ones a peer
428 // caused, where what failed is this side's attempt to accept
429 // something the draft says to refuse.
430 EndpointError::MaxRequestIdWouldNotIncrease { .. }
431 | EndpointError::NotActive
432 | EndpointError::Draining
433 | EndpointError::TrackAliasInUse { .. }
434 | EndpointError::UnjoinableSubscription { .. }
435 | EndpointError::WrongJoiningRefusal { .. }
436 | EndpointError::PeerPrefixOverlap { .. }
437 | EndpointError::OwnPrefixOverlap { .. }
438 | EndpointError::WrongOverlapRefusal { .. } => Fault::ThisEndpoint,
439
440 // Raised reading what the peer sent.
441 EndpointError::DuplicateTrackAlias { .. } => Fault::Peer(Rule::DuplicateTrackAlias),
442 EndpointError::GoAwayUriAtServer => Fault::Peer(Rule::GoAwayAtServer),
443 EndpointError::MixedForwardingPreference { .. } => {
444 Fault::Peer(Rule::MixedForwardingPreference)
445 }
446 EndpointError::ObjectPastFinalObject { .. } => Fault::Peer(Rule::ObjectPastFinalObject),
447 EndpointError::RepeatedGoAway => Fault::Peer(Rule::RepeatedGoAway),
448 EndpointError::UpdateForUnknownRequest(..) => {
449 Fault::Peer(Rule::RequestUpdateForTheWrongRequest)
450 }
451 EndpointError::NotASubscription(..) => Fault::Peer(Rule::TrackStatusIsNotASubscription),
452
453 // The Request ID rules, which are the peer's whenever they are
454 // read off the wire. The mirror — this endpoint asked to advertise
455 // a ceiling that does not increase — is
456 // `MaxRequestIdWouldNotIncrease` above, which is a variant of its
457 // own so that the two never arrive as one value.
458 EndpointError::RequestId(e) => match e {
459 RequestIdError::Decreased(..) => Fault::Peer(Rule::MaxRequestIdDecreased),
460 RequestIdError::ExceedsMax(..) => Fault::Peer(Rule::RequestIdCeiling),
461 RequestIdError::WrongParity(..) => Fault::Peer(Rule::RequestIdParity),
462 RequestIdError::OutOfSequence { .. } => Fault::Peer(Rule::RequestIdOutOfSequence),
463 // This endpoint has spent the budget the peer granted it.
464 RequestIdError::Blocked => Fault::ThisEndpoint,
465 },
466 }
467 }
468
469 /// The code to close the session with, when draft-15 answers this error
470 /// with a close rather than leaving it to the one request it concerns.
471 ///
472 /// `None` means the error is recoverable: the caller may report it, give
473 /// up on the request it concerns, and keep the session running. `Some`
474 /// means the draft ends the session, and the endpoint has already moved
475 /// its own state to Closed - the code is what the transport should carry.
476 ///
477 /// The table grows one rule at a time, and a rule joins it with a gate
478 /// that drives the bytes at a real connection and reads the close code
479 /// back off the wire. An arm added without one asserts nothing: from
480 /// inside the process the session ends either way, and only the peer can
481 /// tell the difference.
482 pub fn session_error_code(&self) -> Option<SessionErrorCode> {
483 match self {
484 // Section 9.5 answers a ceiling that does not increase with a
485 // close, and names this code for it.
486 EndpointError::RequestId(RequestIdError::Decreased(..)) => {
487 Some(SessionErrorCode::ProtocolViolation)
488 }
489 // The same section answers a Request ID that reaches the ceiling
490 // this endpoint advertised, and names a different code for it.
491 EndpointError::RequestId(RequestIdError::ExceedsMax(..)) => {
492 Some(SessionErrorCode::TooManyRequests)
493 }
494 // Section 9.1 answers a Request ID that is not the peer's to
495 // spend with a close, and names INVALID_REQUEST_ID for it. Both
496 // halves of that sentence arrive here: "not valid for the peer"
497 // is an ID out of this endpoint's own half of the space, and "not
498 // the next in sequence" is a new request carrying an ID other than
499 // the next one the peer's sequence calls for.
500 //
501 // The sentence names a third condition too, an ID that "exceeds
502 // the received MAX_REQUEST_ID", and answers it with the same code.
503 // It is answered above with the code the MAX_REQUEST_ID section
504 // names instead, for the reason written at
505 // `validate_peer_request_id`.
506 EndpointError::RequestId(
507 RequestIdError::WrongParity(..) | RequestIdError::OutOfSequence { .. },
508 ) => Some(SessionErrorCode::InvalidRequestId),
509 // Section 9.4 answers a GOAWAY that repeats one already
510 // received, and names this code in the same sentence.
511 EndpointError::RepeatedGoAway => Some(SessionErrorCode::ProtocolViolation),
512 // The same section answers a migration URI arriving at a server
513 // with a close, and names this code for it. Only a server may
514 // offer one, so a client that sends one is telling a server where
515 // to reconnect, which it has no standing to do.
516 EndpointError::GoAwayUriAtServer => Some(SessionErrorCode::ProtocolViolation),
517 // Sections 9.10 and 9.13 name this code in the sentence that
518 // states the rule, and name no other. A close carrying
519 // PROTOCOL_VIOLATION would tell the peer a different thing went
520 // wrong.
521 EndpointError::DuplicateTrackAlias { .. } => {
522 Some(SessionErrorCode::DuplicateTrackAlias)
523 }
524 // Section 9.11 answers an update naming a request the session has
525 // never carried with a close, and names this code for it.
526 EndpointError::UpdateForUnknownRequest(_) => Some(SessionErrorCode::ProtocolViolation),
527 _ => None,
528 }
529 }
530}
531
532/// Unified MoQT endpoint wrapping session lifecycle, request ID allocation,
533/// and all per-request state machines (subscriptions, fetches, namespaces).
534pub struct Endpoint {
535 role: Role,
536 session: SessionStateMachine,
537 request_ids: RequestIdAllocator,
538 /// Tracks the MAX_REQUEST_ID we have advertised to the peer (monotonic).
539 advertised_max_id: u64,
540 /// Each subscription this endpoint opened, behind a lock apiece.
541 ///
542 /// The lock is what lets the data plane end one. Section 2.4.2's answer to a
543 /// Malformed Track is a message per request, the conditions that make a
544 /// track malformed are detected where objects arrive, and objects arrive
545 /// through a shared reference to the connection carrying them.
546 subscriptions: HashMap<u64, Mutex<SubscriptionStateMachine>>,
547 /// Each fetch this endpoint made, behind a lock apiece, for the reason the
548 /// subscriptions above are: the same sentence ends a fetch for a malformed
549 /// track and ends it from the same place.
550 fetches: HashMap<u64, Mutex<FetchStateMachine>>,
551 /// The track each fetch this endpoint made is for.
552 ///
553 /// # Why this is not in `track_bindings`
554 ///
555 /// That table exists to answer questions about Track Aliases, and a fetch
556 /// has none: its objects arrive on a stream that opens by naming the
557 /// Request ID, so no alias rule can ever be about a fetch. An entry that
558 /// can never hold an alias would be one every reader of that table had to
559 /// learn to skip.
560 ///
561 /// # Why a Joining Fetch is resolved here rather than when it is read
562 ///
563 /// A Joining Fetch names no track. It names the subscription it joins, and
564 /// Section 9.16.2 takes the rest from there: "A publisher receiving a
565 /// Joining Fetch uses properties of the associated Subscribe to determine
566 /// the Track Namespace, Track Name and End Location such that it is
567 /// contiguous with the associated Subscribe." So its track is the joined
568 /// subscription's, and it is looked up once, as the fetch is made.
569 ///
570 /// The other place to look it up is the withdrawal, through the request the
571 /// fetch joined, and that fails in the case a Joining Fetch exists for: one
572 /// fills a buffer behind the live edge, so it outlives the subscription it
573 /// joined, and the lookup would come up empty exactly while there was still
574 /// a fetch to cancel.
575 fetch_tracks: HashMap<u64, FetchTrack>,
576 /// The namespace prefix each SUBSCRIBE_NAMESPACE this endpoint sent asked
577 /// about, which the state machine beside it does not hold.
578 ///
579 /// Read by [`Endpoint::own_prefix_overlap`] and by nothing else. The
580 /// withdrawal names the Request ID here, so until the rule about
581 /// overlapping prefixes was judged there was nothing to remember a prefix
582 /// for.
583 subscribe_namespace_prefixes: HashMap<u64, TrackNamespace>,
584 subscribe_namespaces: HashMap<u64, SubscribeNamespaceStateMachine>,
585 /// Namespace subscriptions the **peer** made, keyed by the Request ID it
586 /// opened each under.
587 ///
588 /// Kept apart from `subscribe_namespaces`, which holds the ones this endpoint made:
589 /// one Request ID names one request whichever end opened it, and the two
590 /// ends answer opposite halves of the flow.
591 inbound_subscribe_namespaces: HashMap<u64, InboundSubscribeNamespace>,
592 publish_namespaces: HashMap<u64, PublishNamespaceStateMachine>,
593 /// Announcements the **peer** made, keyed by the Request ID it
594 /// opened each under.
595 inbound_publish_namespaces: HashMap<u64, InboundPublishNamespace>,
596 /// Maps publish_namespace request_id -> TrackNamespace for lookup by
597 /// namespace (needed for PublishNamespaceDone/Cancel which use namespace
598 /// instead of request_id).
599 publish_namespace_namespaces: HashMap<u64, TrackNamespace>,
600 track_statuses: HashMap<u64, TrackStatusStateMachine>,
601 /// Track statuses the **peer** asked about, keyed by the Request ID it
602 /// asked under.
603 ///
604 /// Kept apart from `track_statuses`, which holds the ones this endpoint
605 /// asked about: the two are answered by opposite ends, and one Request ID
606 /// belongs to one request whichever end opened it.
607 inbound_track_statuses: HashMap<u64, InboundTrackStatus>,
608 /// Both directions' publish flows, behind a lock apiece.
609 ///
610 /// A subscription the peer opened with PUBLISH is one of the two ways this
611 /// endpoint receives a track, so the withdrawal a Malformed Track calls for
612 /// has to reach these as well as the subscriptions above.
613 publishes: HashMap<u64, Mutex<PublishStateMachine>>,
614 /// The PUBLISH each offer the **peer** made arrived as, keyed by the
615 /// Request ID it carries.
616 ///
617 ///
618 /// The state of each one stays in `publishes` beside this endpoint's own
619 /// offers, because every step after arrival names one Request ID and a
620 /// Request ID the peer allocated can never be one this endpoint
621 /// allocated. What a map of state machines cannot hold is the offer
622 /// itself, and the offer is what the answer is decided from. Section 5.1:
623 /// "A publisher initiates a subscription to a track by sending the
624 /// PUBLISH message. The subscriber either accepts or rejects the
625 /// subscription using PUBLISH_OK or REQUEST_ERROR."
626 ///
627 ///
628 /// `track_bindings` already keeps the Full Track Name and the Track
629 /// Alias, because the rules about aliases read them. The parameters are
630 /// here and nowhere else.
631 inbound_publishes: HashMap<u64, message::Publish>,
632 /// Subscriptions the **peer** opened with SUBSCRIBE, by Request ID.
633 ///
634 /// Section 9.11 puts the update in the subscriber's hands, so one that
635 /// arrives names a subscription in here and never one in `subscriptions`.
636 ///
637 /// The record holds the SUBSCRIBE itself and not only its state, because
638 /// the answer is built out of the request: the track a SUBSCRIBE_OK is
639 /// about is named nowhere else, and the alias it hands out has to be
640 /// judged against that name. A PUBLISH needs no such record because it
641 /// carries its track and its alias in the one message.
642 inbound_subscribes: HashMap<u64, InboundSubscribe>,
643 /// Every FETCH the peer has sent, from arrival to the end of the fetch.
644 ///
645 /// Separate from `fetches`, which holds the ones this endpoint made. The
646 /// record holds the FETCH itself and not only its state, because the
647 /// answer is built out of the request: a Joining Fetch has to be judged
648 /// against the subscription it names, and that name is nowhere else.
649 inbound_fetches: HashMap<u64, InboundFetch>,
650 goaway_uri: Option<Vec<u8>>,
651 /// What Full Track Name the peer has attached each Track Alias to, per
652 /// Request ID.
653 ///
654 /// Sections 9.10 and 9.13 forbid one alias naming two tracks at once, and the
655 /// "at once" is what makes this a table rather than a set: an alias the
656 /// peer used for a track whose subscription has ended is free again. The
657 /// table therefore records the binding and reads liveness back off the
658 /// subscription's own state machine, rather than keeping a second copy of
659 /// it that every path ending a subscription would have to remember to
660 /// prune.
661 /// What each track's objects have been framed as, so far.
662 ///
663 /// Behind a lock because this is the one endpoint fact a *data* stream
664 /// settles, and the data plane reaches the endpoint through `&Connection`:
665 /// a caller may hold one across tasks while it reads streams and datagrams,
666 /// so there is no `&mut` to reach the rest of this struct with.
667 forwarding_preferences: Mutex<TrackForwardingPreferences>,
668 /// The tracks this endpoint has found malformed and withdrawn from.
669 ///
670 /// Behind a lock for the same reason the record above it is: it is written
671 /// from the data plane, which holds a shared reference.
672 malformed: Mutex<MalformedTracks>,
673 /// Where each track this endpoint receives has ended, once an end-of-track
674 /// object has said so.
675 ///
676 /// Behind an `Arc` because the stream that reads a track's objects holds a
677 /// handle onto it and the endpoint cannot be reached from there.
678 locations: Arc<Mutex<TrackLocations>>,
679 track_bindings: HashMap<u64, TrackBinding>,
680}
681
682/// The track a fetch this endpoint made asked for.
683///
684/// A standalone FETCH carries both fields and a Joining Fetch carries neither,
685/// so what is kept is the answer rather than the question: whichever way the
686/// fetch named its track, this is the track.
687#[derive(Debug, Clone)]
688struct FetchTrack {
689 namespace: TrackNamespace,
690 name: Vec<u8>,
691}
692
693/// A Track Alias the peer has attached to a Full Track Name, and the request
694/// whose lifetime the attachment follows.
695#[derive(Debug, Clone)]
696struct TrackBinding {
697 namespace: TrackNamespace,
698 name: Vec<u8>,
699 /// The alias, once the peer has named one.
700 ///
701 /// A SUBSCRIBE this endpoint sends names a track and waits for its alias,
702 /// so the binding exists with no alias in it from the moment the request
703 /// is made until its SUBSCRIBE_OK arrives. A PUBLISH carries both at once
704 /// and is never in that state.
705 alias: Option<u64>,
706 kind: BindingKind,
707}
708
709/// Which of the two sequences Section 5.1 names established the subscription
710/// that owns a binding, and so which state machine says whether it still has
711/// one.
712#[derive(Debug, Clone, Copy, PartialEq, Eq)]
713enum BindingKind {
714 /// This endpoint's SUBSCRIBE, established by the peer's SUBSCRIBE_OK.
715 Subscribe,
716 /// A PUBLISH, established by the PUBLISH_OK answering it - whichever end
717 /// sent which. Both are held in the same map, which Request ID parity
718 /// keeps from colliding.
719 Publish,
720 /// The peer's SUBSCRIBE, established by this endpoint's SUBSCRIBE_OK.
721 ///
722 /// The alias is this endpoint's to choose on that one, because Section
723 /// 9.10 carries it in the answer rather than in the request.
724 PeerSubscribe,
725}
726
727/// A subscription the peer opened with SUBSCRIBE.
728struct InboundSubscribe {
729 /// The message as it arrived, which is what the application answers from.
730 message: message::Subscribe,
731 /// How far the subscription it opened has got.
732 state: SubscriptionStateMachine,
733}
734/// A fetch the peer opened with FETCH.
735struct InboundFetch {
736 /// The message as it arrived, which is what the application answers from.
737 message: Fetch,
738 /// How far the fetch it opened has got.
739 state: FetchStateMachine,
740 /// The subscription a Joining Fetch named and this session had none live
741 /// for when the FETCH arrived, which is when the rule about it is read.
742 unjoinable: Option<u64>,
743}
744
745/// An announcement the peer made with PUBLISH_NAMESPACE.
746///
747/// Kept apart from the announcements this endpoint made because the two are
748/// answered by opposite ends: this one is waiting for an answer from here,
749/// and the other for one from the peer.
750struct InboundPublishNamespace {
751 /// The message as it arrived, which is what the application answers from.
752 message: PublishNamespace,
753 /// How far the announcement it makes has got.
754 state: PublishNamespaceStateMachine,
755}
756/// A track status the peer asked for with TRACK_STATUS.
757///
758/// Section 9.19 treats the request as a SUBSCRIBE "except it does not create
759/// downstream subscription state", so it is recorded here and not among the
760/// subscriptions the peer has opened. Nothing that names a subscription can
761/// find it, which is the whole of what that exception asks for.
762struct InboundTrackStatus {
763 /// The message as it arrived, which is what the answer is built from.
764 message: message::TrackStatus,
765 /// How far the request it opened has got.
766 state: TrackStatusStateMachine,
767}
768/// A SUBSCRIBE_NAMESPACE the peer sent, and how far the namespace subscription it opens
769/// has got.
770///
771/// Kept apart from `subscribe_namespaces`, which holds the ones this endpoint made: the
772/// two are answered by opposite ends, and this one is waiting for an answer
773/// from here.
774struct InboundSubscribeNamespace {
775 /// The message as it arrived, which is what the answer is built from.
776 message: SubscribeNamespace,
777 /// How far the namespace subscription it opens has got.
778 state: SubscribeNamespaceStateMachine,
779 /// The namespace subscription this one overlapped when it arrived,
780 /// which is when the rule about it is read.
781 overlaps: Option<u64>,
782}
783impl Endpoint {
784 /// Create a new endpoint with the given role.
785 pub fn new(role: Role) -> Self {
786 Self {
787 role,
788 session: SessionStateMachine::new(),
789 request_ids: RequestIdAllocator::new(role),
790 advertised_max_id: 0,
791 subscriptions: HashMap::new(),
792 fetches: HashMap::new(),
793 fetch_tracks: HashMap::new(),
794 subscribe_namespaces: HashMap::new(),
795 subscribe_namespace_prefixes: HashMap::new(),
796 inbound_subscribe_namespaces: HashMap::new(),
797 publish_namespaces: HashMap::new(),
798 inbound_publish_namespaces: HashMap::new(),
799 publish_namespace_namespaces: HashMap::new(),
800 track_statuses: HashMap::new(),
801 inbound_track_statuses: HashMap::new(),
802 publishes: HashMap::new(),
803 inbound_publishes: HashMap::new(),
804 inbound_subscribes: HashMap::new(),
805 inbound_fetches: HashMap::new(),
806 goaway_uri: None,
807 track_bindings: HashMap::new(),
808 forwarding_preferences: Mutex::new(TrackForwardingPreferences::new()),
809 malformed: Mutex::new(MalformedTracks::new()),
810 locations: Arc::new(Mutex::new(TrackLocations::new())),
811 }
812 }
813
814 /// The Track Alias the peer attached to `request_id`, once it has named
815 /// one.
816 ///
817 /// Answers for a subscription this endpoint asked for from the moment its
818 /// SUBSCRIBE_OK arrives, and for one the peer offered from the moment its
819 /// PUBLISH does. `None` before that, and for a Request ID this session has
820 /// no track for.
821 pub fn track_alias_for(&self, request_id: VarInt) -> Option<VarInt> {
822 let alias = self.track_bindings.get(&request_id.into_inner())?.alias?;
823 VarInt::from_u64(alias).ok()
824 }
825
826 /// The refusal Sections 9.10 and 9.13 require when `alias` already names a
827 /// different track whose subscription is still live, or `None` when it is
828 /// free.
829 ///
830 /// # Why the set is read rather than kept
831 ///
832 /// "Established" is a subscription state Section 5.1 defines, and this
833 /// endpoint's subscription state machine holds it: a subscription reaches
834 /// Active on SUBSCRIBE_OK and leaves it on the message that ends the flow. Asking it is what makes an alias free again the moment its track's
835 /// subscription ends, with nothing to prune on the way out - and a path
836 /// that ended a subscription without telling this table would otherwise
837 /// leave the alias held forever and refuse the peer's next, conforming,
838 /// use of it.
839 ///
840 /// # Why the request's own binding is skipped
841 ///
842 /// A SUBSCRIBE_OK is judged before its own alias is written down, so the
843 /// skip is not what keeps it from finding itself. A PUBLISH is: it carries
844 /// its alias and its track in the one message, and a second PUBLISH under
845 /// a Request ID already bound is refused by the duplicate-Request-ID rule
846 /// before it reaches here.
847 fn conflicting_track_alias(
848 &self,
849 request_id: u64,
850 alias: u64,
851 namespace: &TrackNamespace,
852 name: &[u8],
853 ) -> Option<EndpointError> {
854 for (&id, binding) in &self.track_bindings {
855 if id == request_id || binding.alias != Some(alias) {
856 continue;
857 }
858 if binding.namespace == *namespace && binding.name == name {
859 continue;
860 }
861 if self.binding_is_established(id, binding.kind) {
862 return Some(EndpointError::DuplicateTrackAlias {
863 alias,
864 established: id,
865 offered: request_id,
866 });
867 }
868 }
869 None
870 }
871
872 /// The request already using `alias` for a track other than (`namespace`,
873 /// `name`), or `None` when this endpoint may give the alias to that track.
874 ///
875 /// Separate from [`Self::conflicting_track_alias`] because the two answer
876 /// different questions about the same table. That one judges a message
877 /// that has arrived and ends the session over it; this one judges one that
878 /// has not been built and declines to build it.
879 fn alias_held_elsewhere(
880 &self,
881 alias: u64,
882 namespace: &TrackNamespace,
883 name: &[u8],
884 ) -> Option<EndpointError> {
885 self.track_bindings.iter().find_map(|(&id, binding)| {
886 let other_track = binding.namespace != *namespace || binding.name != name;
887 (binding.alias == Some(alias)
888 && other_track
889 && self.binding_is_in_use(id, binding.kind))
890 .then_some(EndpointError::TrackAliasInUse { alias, held: id })
891 })
892 }
893
894 /// The track a live binding has given `alias` to.
895 ///
896 /// Read rather than kept, for the reason the alias table beside it gives: a
897 /// binding whose request has ended holds nothing, and an alias that is free
898 /// again may name a different track next. That is exactly why the
899 /// forwarding-preference record below is keyed on the track this returns
900 /// and never on the alias itself.
901 fn track_for_alias(&self, alias: u64) -> Option<(&TrackNamespace, &[u8])> {
902 self.track_bindings.iter().find_map(|(&id, binding)| {
903 (binding.alias == Some(alias) && self.binding_is_in_use(id, binding.kind))
904 .then_some((&binding.namespace, binding.name.as_slice()))
905 })
906 }
907
908 /// The record a stream carrying `alias`'s objects measures them against.
909 ///
910 /// `None` for an alias no live binding names: an object for one breaks a
911 /// different rule, and measuring it against a track this endpoint never
912 /// asked for would answer that one with the wrong sentence.
913 pub fn track_objects(&self, alias: u64) -> Option<TrackObjects> {
914 let (namespace, name) = self.track_for_alias(alias)?;
915 Some(TrackObjects::new(
916 Arc::clone(&self.locations),
917 namespace.clone(),
918 name.to_vec(),
919 alias,
920 ))
921 }
922
923 /// Record or judge one object that arrived outside a subgroup stream, and
924 /// report Section 2.4.2's Malformed Track when it arrived after the
925 /// place an end-of-track object put the end.
926 ///
927 /// One rule and not two. The placement rule drafts 08 through 13 state
928 /// about an end-of-track object is not in this draft, so an object that
929 /// ends a track here is judged against nothing and only settles where the
930 /// track stopped.
931 ///
932 /// `&self`, because the call site is the data plane's.
933 pub fn note_received_object(
934 &self,
935 alias: u64,
936 at: ObjectLocation,
937 role: ObjectRole,
938 ) -> Result<(), EndpointError> {
939 let Some(objects) = self.track_objects(alias) else { return Ok(()) };
940 objects.note_past_final(at, role).map_err(|end| EndpointError::ObjectPastFinalObject {
941 alias,
942 group: at.group,
943 object: at.object,
944 final_group: end.group,
945 final_object: end.object,
946 })
947 }
948
949 /// Record how a track's object was framed, and report Section 10's "MUST NOT
950 /// mix" when it disagrees with what that track's earlier objects used.
951 ///
952 /// `&self`, because the call sites are the data plane's: a subgroup header
953 /// arriving, a datagram arriving, and the two writers that produce them.
954 ///
955 /// An alias no live binding names records nothing and reports nothing. An
956 /// object for such an alias breaks a different rule — the one about objects
957 /// nobody asked for — and answering that one here would answer it with the
958 /// wrong sentence.
959 pub fn note_object_forwarding_preference(
960 &self,
961 alias: u64,
962 seen: ObjectForwardingPreference,
963 ) -> Result<(), EndpointError> {
964 let Some((namespace, name)) = self.track_for_alias(alias) else { return Ok(()) };
965 self.forwarding_preferences
966 .lock()
967 .unwrap_or_else(|poisoned| poisoned.into_inner())
968 .observe(namespace, name, seen)
969 .map_err(|established| EndpointError::MixedForwardingPreference {
970 alias,
971 established,
972 offered: seen,
973 })
974 }
975
976 /// The subscription `id` opened, locked for a read or a transition.
977 ///
978 /// `&self`, which is the whole point of the lock. Section 2.4.2 answers a
979 /// Malformed Track with a message per request, and the conditions that
980 /// make a track malformed are detected on the data plane, where this
981 /// endpoint is reached through a shared reference. A flow that could only
982 /// be moved through `&mut self` would leave that sentence unanswerable
983 /// from the only place it is ever read.
984 ///
985 /// Hold one guard at a time. Nothing here takes a second while a first is
986 /// live, and [`Self::withdraw_malformed_track`] collects the requests it
987 /// is going to move before it moves any of them for that reason.
988 fn subscription(&self, id: u64) -> Option<MutexGuard<'_, SubscriptionStateMachine>> {
989 self.subscriptions
990 .get(&id)
991 .map(|sm| sm.lock().unwrap_or_else(|poisoned| poisoned.into_inner()))
992 }
993
994 /// The publish flow `id` opened, locked for a read or a transition.
995 ///
996 /// One map for both directions, which is what makes this one accessor:
997 /// the offer this endpoint made and the offer the peer made are the same
998 /// flow read from opposite ends, and Request ID parity keeps them apart.
999 fn publish_flow(&self, id: u64) -> Option<MutexGuard<'_, PublishStateMachine>> {
1000 self.publishes.get(&id).map(|sm| sm.lock().unwrap_or_else(|p| p.into_inner()))
1001 }
1002
1003 /// The fetch `id` opened, locked for a read or a transition.
1004 fn fetch_flow(&self, id: u64) -> Option<MutexGuard<'_, FetchStateMachine>> {
1005 self.fetches.get(&id).map(|sm| sm.lock().unwrap_or_else(|p| p.into_inner()))
1006 }
1007
1008 /// The condition a track was withdrawn for, or `None` for a track this
1009 /// endpoint has found nothing wrong with.
1010 ///
1011 /// The withdrawal happens without the application asking for it, so this
1012 /// is how an application that missed the error learns why a subscription
1013 /// it never ended has ended.
1014 ///
1015 /// # Why this takes a track and not the alias the object carried
1016 ///
1017 /// An alias only means anything through a live binding, and the
1018 /// withdrawal ends the binding it would have been resolved through. An
1019 /// accessor taking an alias would therefore answer `None` from the
1020 /// instant it had something to say, which is worse than not existing.
1021 /// The record is keyed on the track, and so is this.
1022 pub fn malformed_track(
1023 &self,
1024 namespace: &TrackNamespace,
1025 name: &[u8],
1026 ) -> Option<MalformedTrackCondition> {
1027 self.malformed
1028 .lock()
1029 .unwrap_or_else(|poisoned| poisoned.into_inner())
1030 .condition(namespace, name)
1031 }
1032
1033 /// Withdraw from a track a condition has just shown to be malformed, and
1034 /// give back the messages Section 2.4.2 asks for.
1035 ///
1036 /// "When a subscriber detects a Malformed Track, it MUST UNSUBSCRIBE any
1037 /// subscription and FETCH_CANCEL any fetch for that Track from that
1038 /// publisher, and SHOULD deliver an error to the application." This is the
1039 /// first half. The second is the error the detecting path returns, which
1040 /// is why nothing here reports anything: a caller that gets an empty list
1041 /// back is not being told the track was fine.
1042 ///
1043 /// `&self`, because the call site is the data plane's.
1044 ///
1045 /// # What comes back, and what does not
1046 ///
1047 /// One message for each request through which this endpoint is *receiving*
1048 /// the track: an UNSUBSCRIBE for a SUBSCRIBE it sent and for a PUBLISH the
1049 /// peer sent that it accepted, and a FETCH_CANCEL for each fetch it made
1050 /// for that track. A request that names the track the other way round is
1051 /// not one of those — a peer subscribing to this endpoint makes it the
1052 /// publisher, and a publisher does not unsubscribe from what it is
1053 /// serving.
1054 ///
1055 /// Empty for an alias no live binding names — there is no track to
1056 /// withdraw from — and for a track whose only requests are in a state that
1057 /// has no ending of that kind left in it.
1058 ///
1059 /// # Two records, one scan
1060 ///
1061 /// Subscriptions are found through the alias table and fetches through
1062 /// their own, because a fetch never holds an alias. Both are keyed by
1063 /// Request ID and a Request ID names one request, so the two lists cannot
1064 /// overlap and the withdrawal below can tell from the id alone which
1065 /// message a request takes.
1066 ///
1067 /// # What makes the answer happen once
1068 ///
1069 /// Not the record. The requests this withdraws end here, and a request
1070 /// that has ended has no second ending in it, so a publisher that goes on
1071 /// mixing a track's framing is answered once however many objects it
1072 /// sends. The record is read afterwards, by an application asking why a
1073 /// track it never gave up was given up.
1074 ///
1075 /// Which leaves the case the two answers differ on: an application that
1076 /// subscribes to the same track again. That request has never been
1077 /// withdrawn from, and a publisher mixing its framing again has broken the
1078 /// sentence again, so it is withdrawn from too.
1079 pub fn withdraw_malformed_track(
1080 &self,
1081 alias: u64,
1082 condition: MalformedTrackCondition,
1083 ) -> Vec<ControlMessage> {
1084 let Some((namespace, name)) = self.track_for_alias(alias) else { return Vec::new() };
1085 let (namespace, name) = (namespace.clone(), name.to_vec());
1086 self.malformed
1087 .lock()
1088 .unwrap_or_else(|poisoned| poisoned.into_inner())
1089 .note(&namespace, &name, condition);
1090 // Collected before any of them is moved. The liveness checks below
1091 // take each request's own lock and the transitions take it again, so
1092 // the scan finishes first and holds nothing while it runs.
1093 let mut ids: Vec<u64> = self
1094 .track_bindings
1095 .iter()
1096 .filter(|(&id, binding)| {
1097 binding.namespace == namespace
1098 && binding.name == name
1099 && self.binding_is_in_use(id, binding.kind)
1100 })
1101 .map(|(&id, _)| id)
1102 .chain(self.fetch_tracks.iter().filter_map(|(&id, track)| {
1103 (track.namespace == namespace
1104 && track.name == name
1105 && self.fetch_flow(id).is_some_and(|sm| sm.state() != FetchState::Done))
1106 .then_some(id)
1107 }))
1108 .collect();
1109 // A HashMap iterates in no order, and two requests for one track is a
1110 // shape a peer can produce. Sorted so the wire is the same twice.
1111 ids.sort_unstable();
1112 ids.into_iter().filter_map(|id| self.withdraw_one(id)).collect()
1113 }
1114
1115 /// The message ending one request, or `None` when that request is not one
1116 /// this endpoint receives a track through or is not in a state that can be
1117 /// ended that way.
1118 fn withdraw_one(&self, id: u64) -> Option<ControlMessage> {
1119 let request_id = VarInt::from_u64(id).ok()?;
1120 if let Some(mut sm) = self.subscription(id) {
1121 sm.on_unsubscribe().ok()?;
1122 return Some(ControlMessage::Unsubscribe(Unsubscribe { request_id }));
1123 }
1124 if let Some(mut sm) = self.fetch_flow(id) {
1125 sm.on_fetch_cancel().ok()?;
1126 return Some(ControlMessage::FetchCancel(FetchCancel { request_id }));
1127 }
1128 // `publishes` holds both directions and the offer's own message is
1129 // what tells them apart: only one the peer made is a track this
1130 // endpoint receives.
1131 self.inbound_publishes.get(&id)?;
1132 self.publish_flow(id)?.on_unsubscribe_sent().ok()?;
1133 Some(ControlMessage::Unsubscribe(Unsubscribe { request_id }))
1134 }
1135
1136 /// Whether a binding's request has put its alias in play at all.
1137 /// Broader than [`Self::binding_is_established`], and the two sentences are
1138 /// why. What a subscriber must close over is qualified - "the same Track
1139 /// Alias as a different track with an Established subscription" - and the
1140 /// prohibition on the publisher is not: "The same Track Alias MUST NOT be
1141 /// used to refer to two different Tracks simultaneously." Once a PUBLISH
1142 /// carrying an alias has been sent, giving that alias to a second track is
1143 /// what that sentence forbids, answered or not.
1144 fn binding_is_in_use(&self, id: u64, kind: BindingKind) -> bool {
1145 match kind {
1146 BindingKind::Subscribe => {
1147 self.subscription(id).is_some_and(|sm| sm.state() != SubscriptionState::Done)
1148 }
1149 BindingKind::Publish => {
1150 self.publish_flow(id).is_some_and(|sm| sm.state() != PublishState::Done)
1151 }
1152 BindingKind::PeerSubscribe => self
1153 .inbound_subscribes
1154 .get(&id)
1155 .is_some_and(|s| s.state.state() != SubscriptionState::Done),
1156 }
1157 }
1158
1159 /// Whether the request that owns a binding still has a live subscription.
1160 ///
1161 /// The two kinds are answered by two different state machines because the
1162 /// two sequences Section 5.1 names end in different places: a SUBSCRIBE
1163 /// this endpoint made is live once its SUBSCRIBE_OK arrives, a PUBLISH the
1164 /// peer made once this endpoint has answered PUBLISH_OK.
1165 fn binding_is_established(&self, id: u64, kind: BindingKind) -> bool {
1166 match kind {
1167 BindingKind::Subscribe => {
1168 self.subscription(id).is_some_and(|sm| sm.state() == SubscriptionState::Active)
1169 }
1170 BindingKind::Publish => {
1171 self.publish_flow(id).is_some_and(|sm| sm.state() == PublishState::Active)
1172 }
1173 BindingKind::PeerSubscribe => self
1174 .inbound_subscribes
1175 .get(&id)
1176 .is_some_and(|s| s.state.state() == SubscriptionState::Active),
1177 }
1178 }
1179
1180 /// The conflict a SUBSCRIBE_OK's alias has with the tracks already bound.
1181 ///
1182 /// Separate from [`Self::conflicting_track_alias`] because the track a
1183 /// SUBSCRIBE_OK is about is not in the SUBSCRIBE_OK: it is the one this
1184 /// endpoint's own SUBSCRIBE asked for, which is why the request has to be
1185 /// looked up before the alias can be judged.
1186 fn conflicting_alias_for_subscribe_ok(&self, id: u64, alias: u64) -> Option<EndpointError> {
1187 let binding = self.track_bindings.get(&id)?;
1188 self.conflicting_track_alias(id, alias, &binding.namespace, &binding.name)
1189 }
1190
1191 // -- Accessors --------------------------------------------------
1192
1193 /// Returns the role (client or server) of this endpoint.
1194 pub fn role(&self) -> Role {
1195 self.role
1196 }
1197
1198 /// Returns the current session state.
1199 pub fn session_state(&self) -> SessionState {
1200 self.session.state()
1201 }
1202
1203 /// Returns the URI from a received GOAWAY message, if any.
1204 pub fn goaway_uri(&self) -> Option<&[u8]> {
1205 self.goaway_uri.as_deref()
1206 }
1207
1208 /// Returns whether this endpoint is blocked on request ID allocation.
1209 pub fn is_blocked(&self) -> bool {
1210 self.request_ids.is_blocked()
1211 }
1212
1213 /// Returns the number of active subscription state machines.
1214 pub fn active_subscription_count(&self) -> usize {
1215 self.subscriptions.len()
1216 }
1217
1218 /// Returns the number of active fetch state machines.
1219 pub fn active_fetch_count(&self) -> usize {
1220 self.fetches.len()
1221 }
1222
1223 /// Returns the number of active subscribe-namespace state machines.
1224 pub fn active_subscribe_namespace_count(&self) -> usize {
1225 self.subscribe_namespaces.len()
1226 }
1227
1228 /// Returns the number of active publish-namespace state machines.
1229 pub fn active_publish_namespace_count(&self) -> usize {
1230 self.publish_namespaces.len()
1231 }
1232
1233 /// Returns the number of active track status state machines.
1234 pub fn active_track_status_count(&self) -> usize {
1235 self.track_statuses.len()
1236 }
1237
1238 /// Returns the number of active publish state machines.
1239 pub fn active_publish_count(&self) -> usize {
1240 self.publishes.len()
1241 }
1242
1243 // -- Session lifecycle ------------------------------------------
1244
1245 /// Transition from Connecting to SetupExchange.
1246 pub fn connect(&mut self) -> Result<(), EndpointError> {
1247 self.session.on_connect()?;
1248 Ok(())
1249 }
1250
1251 /// Close the session (SetupExchange, Active or Draining -> Closed).
1252 pub fn close(&mut self) -> Result<(), EndpointError> {
1253 self.session.on_close()?;
1254 Ok(())
1255 }
1256
1257 // -- Client setup (draft-15: no versions) -----------------------
1258
1259 /// Generate a CLIENT_SETUP message (client-side).
1260 /// Draft-15 uses ALPN for version negotiation -- no versions field.
1261 pub fn send_client_setup(
1262 &mut self,
1263 parameters: Vec<KeyValuePair>,
1264 ) -> Result<ControlMessage, EndpointError> {
1265 let msg = ClientSetup { parameters };
1266 setup::validate_client_setup(&msg)?;
1267 // A MAX_REQUEST_ID parameter in our own CLIENT_SETUP is a grant to
1268 // the peer, so it is the first value of the ceiling we advertise.
1269 self.record_advertised_max(&msg.parameters);
1270 Ok(ControlMessage::ClientSetup(msg))
1271 }
1272
1273 /// Process a SERVER_SETUP message (client-side). Transitions to Active.
1274 /// If the server includes a MAX_REQUEST_ID parameter (key 0x02), the
1275 /// request ID allocator is initialized with that value.
1276 pub fn receive_server_setup(&mut self, msg: &ServerSetup) -> Result<(), EndpointError> {
1277 setup::validate_server_setup(msg)?;
1278 self.session.on_setup_complete()?;
1279 // Extract MAX_REQUEST_ID (key 0x02) from setup parameters if present
1280 for param in &msg.parameters {
1281 if param.key == VarInt::from_u64(0x02).unwrap() {
1282 if let KvpValue::Varint(v) = ¶m.value {
1283 self.request_ids.update_max(v.into_inner())?;
1284 }
1285 }
1286 }
1287 Ok(())
1288 }
1289
1290 // -- Server setup -----------------------------------------------
1291
1292 /// Process CLIENT_SETUP and generate SERVER_SETUP (server-side).
1293 /// Draft-15 uses ALPN for version negotiation -- just transition and
1294 /// respond.
1295 pub fn receive_client_setup_and_respond(
1296 &mut self,
1297 client_setup: &ClientSetup,
1298 ) -> Result<ControlMessage, EndpointError> {
1299 setup::validate_client_setup(client_setup)?;
1300 // Section 9.3.1.3 puts no role restriction on MAX_REQUEST_ID, so a
1301 // CLIENT_SETUP may carry it and it grants this endpoint its budget.
1302 for param in &client_setup.parameters {
1303 if param.key == VarInt::from_u64(0x02).unwrap() {
1304 if let KvpValue::Varint(v) = ¶m.value {
1305 self.request_ids.update_max(v.into_inner())?;
1306 }
1307 }
1308 }
1309 self.session.on_setup_complete()?;
1310 let msg = ServerSetup { parameters: vec![] };
1311 Ok(ControlMessage::ServerSetup(msg))
1312 }
1313
1314 // -- MAX_REQUEST_ID ---------------------------------------------
1315
1316 /// Take a Request ID from a new request the peer sent, holding it to all
1317 /// three halves of the rule.
1318 ///
1319 /// Section 9.1 closes the session with INVALID_REQUEST_ID on a request ID
1320 /// "that is not valid for the peer", and Section 9.5 closes it with
1321 /// TOO_MANY_REQUESTS on one at or above the ceiling this endpoint
1322 /// advertised. The parity half belongs to the allocator; the ceiling half
1323 /// needs `advertised_max_id`, which is this endpoint's grant to the peer
1324 /// and not the peer's grant to it, so the two numbers are different and
1325 /// only one of them answers this question.
1326 ///
1327 /// The third is the sequence. Draft-15 rewrote "not expected" as "not the
1328 /// next in sequence", which says the same thing in a way that cannot be
1329 /// read as advisory: each endpoint starts at 0 or 1 by role and steps by 2
1330 /// per request, so the next value is a number. A repeat and a skip are both
1331 /// refused, and neither is caught by parity or by the ceiling - a repeat has
1332 /// the right parity and sits below the ceiling by construction, having been
1333 /// accepted once already.
1334 ///
1335 /// Taking the ID is what advances the sequence, so this is for a **new**
1336 /// request only. A response, a cancellation or an update that names the
1337 /// request it modifies all carry an ID that has already been spent, and
1338 /// passing one here would refuse it.
1339 ///
1340 /// # The ceiling is a session rule, not a request rule
1341 ///
1342 /// Section 9.5: a Request ID "equal to or larger than this" received by the
1343 /// endpoint that sent the MAX_REQUEST_ID in any request message - the list
1344 /// [`Self::receive_request`] carries - closes the session, and
1345 /// TOO_MANY_REQUESTS is the code. Which is also why the number measured
1346 /// against is the one **this** endpoint sent. An id that reaches the ceiling
1347 /// is not a request to refuse with an error message: the session is over, so
1348 /// this moves the endpoint's own state to Closed and leaves the code to
1349 /// [`EndpointError::session_error_code`].
1350 ///
1351 /// # Two sections, two codes
1352 ///
1353 /// This draft states the rule twice and does not agree with itself.
1354 /// Section 9.1 folds the ceiling into the sentence about the ID sequence -
1355 /// "a new request with a Request ID that is not the next in sequence or
1356 /// exceeds the received MAX_REQUEST_ID" - and answers it with
1357 /// INVALID_REQUEST_ID instead. It also measures against the *received*
1358 /// ceiling, which is the budget the peer granted this endpoint rather than
1359 /// the one this endpoint granted the peer, and measuring an incoming
1360 /// request against our own budget is not a rule that can be applied. Ten
1361 /// drafts state the ceiling in the MAX_REQUEST_ID section and all ten name
1362 /// the same code there; two of them restate it elsewhere. The
1363 /// MAX_REQUEST_ID section is the one followed here.
1364 ///
1365 /// # Errors
1366 ///
1367 /// [`RequestIdError::WrongParity`] if the ID belongs to this endpoint's
1368 /// half of the space, [`RequestIdError::ExceedsMax`] if it is not
1369 /// below the advertised ceiling, or [`RequestIdError::OutOfSequence`] if it
1370 /// is not the one the peer's sequence called for. A ceiling that was never
1371 /// raised is 0, which refuses every ID, matching a default that reads "the
1372 /// peer MUST NOT send requests".
1373 ///
1374 /// All three end the session rather than the one request, so all three
1375 /// move this endpoint's own session to Closed on the way out and all three
1376 /// answer `Some` from [`EndpointError::session_error_code`]. Returning one
1377 /// of them without ending the session would leave a peer that broke the
1378 /// rule free to keep sending, and never told.
1379 pub fn validate_peer_request_id(&mut self, id: u64) -> Result<(), EndpointError> {
1380 if let Err(e) = self.request_ids.validate_peer_id(id) {
1381 return Err(self.fail_session(EndpointError::RequestId(e)));
1382 }
1383 if id >= self.advertised_max_id {
1384 return Err(self.fail_session(EndpointError::RequestId(RequestIdError::ExceedsMax(
1385 id,
1386 self.advertised_max_id,
1387 ))));
1388 }
1389 if let Err(e) = self.request_ids.record_peer_id(id) {
1390 return Err(self.fail_session(EndpointError::RequestId(e)));
1391 }
1392 Ok(())
1393 }
1394
1395 /// Record a MAX_REQUEST_ID parameter this endpoint is about to send as
1396 /// the ceiling it has advertised to the peer.
1397 fn record_advertised_max(&mut self, parameters: &[KeyValuePair]) {
1398 for param in parameters {
1399 if param.key == VarInt::from_u64(0x02).unwrap() {
1400 if let KvpValue::Varint(v) = ¶m.value {
1401 self.advertised_max_id = v.into_inner();
1402 }
1403 }
1404 }
1405 }
1406
1407 /// Process an incoming MAX_REQUEST_ID message, ending the session if the
1408 /// ceiling it carries does not increase.
1409 ///
1410 /// Section 9.5: "The Maximum Request ID MUST only increase within a
1411 /// session, and receipt of a MAX_REQUEST_ID message with an equal or
1412 /// smaller Request ID value is a PROTOCOL_VIOLATION." Section 3.4 lists
1413 /// PROTOCOL_VIOLATION (0x3) among the codes for terminating the session -
1414 /// "The remote endpoint performed an action that was disallowed by the
1415 /// specification" - so naming it of a *receipt* is this draft saying the
1416 /// session ends, and with which code. Draft-16 states the same rule with
1417 /// the verb in it: "it MUST close the session with a PROTOCOL_VIOLATION".
1418 ///
1419 /// # Errors
1420 ///
1421 /// [`RequestIdError::Decreased`] if the value does not increase, with the
1422 /// session already moved to Closed.
1423 pub fn receive_max_request_id(&mut self, msg: &MaxRequestId) -> Result<(), EndpointError> {
1424 if let Err(err) = self.request_ids.update_max(msg.request_id.into_inner()) {
1425 return Err(self.fail_session(err.into()));
1426 }
1427 Ok(())
1428 }
1429
1430 /// Generate a MAX_REQUEST_ID message (typically server-side).
1431 ///
1432 /// Section 9.5: "The Maximum Request ID MUST only increase within a
1433 /// session", and a peer that receives an equal or smaller value closes
1434 /// the session. The ceiling starts at 0 and 0 is not greater than 0, so
1435 /// the first value that may go on the wire is 1 and there is no opening
1436 /// case where a repeat is allowed.
1437 ///
1438 /// # Errors
1439 ///
1440 /// [`EndpointError::MaxRequestIdWouldNotIncrease`] if the value does not
1441 /// strictly increase. Its own variant rather than the one a *received*
1442 /// ceiling that did not increase raises, so that a refusal to write is
1443 /// never read back as a peer in violation.
1444 pub fn send_max_request_id(&mut self, max_id: VarInt) -> Result<ControlMessage, EndpointError> {
1445 let new_val = max_id.into_inner();
1446 if new_val <= self.advertised_max_id {
1447 return Err(EndpointError::MaxRequestIdWouldNotIncrease {
1448 advertised: self.advertised_max_id,
1449 offered: new_val,
1450 });
1451 }
1452 self.advertised_max_id = new_val;
1453 Ok(ControlMessage::MaxRequestId(MaxRequestId { request_id: max_id }))
1454 }
1455
1456 /// Generate a REQUESTS_BLOCKED message indicating that this endpoint
1457 /// wants to create a new request but is blocked by the current
1458 /// MAX_REQUEST_ID.
1459 pub fn send_requests_blocked(&self) -> Result<ControlMessage, EndpointError> {
1460 let max_id = self.request_ids.max_id();
1461 Ok(ControlMessage::RequestsBlocked(RequestsBlocked {
1462 maximum_request_id: VarInt::from_u64(max_id).unwrap(),
1463 }))
1464 }
1465
1466 /// Process an incoming REQUESTS_BLOCKED message from the peer.
1467 pub fn receive_requests_blocked(&self, _msg: &RequestsBlocked) -> Result<(), EndpointError> {
1468 Ok(())
1469 }
1470
1471 // -- GoAway -----------------------------------------------------
1472
1473 /// Process an incoming GOAWAY message. Transitions to Draining.
1474 ///
1475 /// # Errors
1476 ///
1477 /// [`EndpointError::GoAwayUriAtServer`] if this endpoint is the server and
1478 /// the GOAWAY carries a New Session URI. The session is over: this
1479 /// endpoint's own state has moved to Closed and the code the transport
1480 /// should close with is in [`EndpointError::session_error_code`].
1481 ///
1482 /// [`EndpointError::RepeatedGoAway`] if a GOAWAY has already been
1483 /// received. The session is over: this endpoint's own state has moved to
1484 /// Closed and the code the transport should close with is in
1485 /// [`EndpointError::session_error_code`].
1486 pub fn receive_goaway(&mut self, msg: &GoAway) -> Result<(), EndpointError> {
1487 // Section 9.4: "If a server receives a GOAWAY with a non-zero New
1488 // Session URI Length it MUST terminate the session with a
1489 // PROTOCOL_VIOLATION." Refused before the URI is stored rather than
1490 // after, so an application reading `goaway_uri` back can never be
1491 // handed somewhere a client chose to send it. The session ends with
1492 // it: the sentence names a close and a code, and an endpoint that
1493 // raised the error and carried on would keep serving a peer it had
1494 // just found in violation.
1495 if self.role == Role::Server && !msg.new_session_uri.is_empty() {
1496 return Err(self.fail_session(EndpointError::GoAwayUriAtServer));
1497 }
1498 // Section 9.4: "The endpoint MUST terminate the session with a
1499 // PROTOCOL_VIOLATION (Section 3.4) if it receives multiple GOAWAY messages."
1500 // Draining is reached from nowhere else - `on_goaway` is its only
1501 // entry and this method is that method's only caller - so the session
1502 // state is the record of the first GOAWAY having arrived.
1503 if self.session.state() == SessionState::Draining {
1504 return Err(self.fail_session(EndpointError::RepeatedGoAway));
1505 }
1506 self.session.on_goaway()?;
1507 self.goaway_uri = Some(msg.new_session_uri.clone());
1508 Ok(())
1509 }
1510
1511 // -- Subscribe flow ---------------------------------------------
1512
1513 fn require_active_or_err(&self) -> Result<(), EndpointError> {
1514 match self.session.state() {
1515 SessionState::Active => Ok(()),
1516 SessionState::Draining => Err(EndpointError::Draining),
1517 _ => Err(EndpointError::NotActive),
1518 }
1519 }
1520
1521 /// Record that the session is over because the peer broke a rule this
1522 /// draft answers with a session close, and hand the error back unchanged.
1523 ///
1524 /// The state move is what makes the violation stick: every request entry
1525 /// point goes through
1526 /// [`require_active_or_err`](Self::require_active_or_err), so a caller
1527 /// that ignores the returned error still cannot start anything new.
1528 /// Closing on the wire is the connection layer's job - see
1529 /// [`EndpointError::session_error_code`] for the code it should use.
1530 fn fail_session(&mut self, err: EndpointError) -> EndpointError {
1531 // `on_close` accepts SetupExchange, Active and Draining. A violation
1532 // seen in Connecting or Closed leaves the state machine alone: there
1533 // is no session to close, and the error itself is still the answer.
1534 //
1535 // SetupExchange is in that set because the Termination section says
1536 // "The Transport Session can be terminated at any point", and the
1537 // Setup exchange is a point. So a violation caught while the setup is
1538 // still in flight does close the session, and the discarded result is
1539 // safe because that is one of the states `on_close` accepts.
1540 let _ = self.session.on_close();
1541 err
1542 }
1543
1544 /// Send a SUBSCRIBE message. Allocates a request ID and creates a
1545 /// subscription state machine. Draft-15: simplified, no group_order/
1546 /// filter_type/forward args.
1547 pub fn subscribe(
1548 &mut self,
1549 track_namespace: TrackNamespace,
1550 track_name: Vec<u8>,
1551 parameters: Vec<KeyValuePair>,
1552 ) -> Result<(VarInt, ControlMessage), EndpointError> {
1553 self.require_active_or_err()?;
1554 let req_id = self.request_ids.allocate()?;
1555
1556 let mut sm = SubscriptionStateMachine::new();
1557 sm.on_subscribe_sent()?;
1558 self.subscriptions.insert(req_id.into_inner(), Mutex::new(sm));
1559 // The track is recorded here because this is the only place it is
1560 // known: a SUBSCRIBE_OK names an alias and not the track it is for.
1561 self.track_bindings.insert(
1562 req_id.into_inner(),
1563 TrackBinding {
1564 namespace: track_namespace.clone(),
1565 name: track_name.clone(),
1566 alias: None,
1567 kind: BindingKind::Subscribe,
1568 },
1569 );
1570
1571 let msg = ControlMessage::Subscribe(Subscribe {
1572 request_id: req_id,
1573 track_namespace,
1574 track_name,
1575 parameters,
1576 });
1577 Ok((req_id, msg))
1578 }
1579
1580 /// Process an incoming SUBSCRIBE_OK. Draft-15: has request_id,
1581 /// track_alias, parameters.
1582 pub fn receive_subscribe_ok(&mut self, msg: &SubscribeOk) -> Result<(), EndpointError> {
1583 let id = msg.request_id.into_inner();
1584 if !self.subscriptions.contains_key(&id) {
1585 return Err(EndpointError::UnknownRequest(id));
1586 }
1587 let alias = msg.track_alias.into_inner();
1588 // Judged before the transition, so that the subscription this message
1589 // is about is not yet live and cannot be found as its own conflict,
1590 // and so that a refused SUBSCRIBE_OK leaves no alias behind.
1591 if let Some(conflict) = self.conflicting_alias_for_subscribe_ok(id, alias) {
1592 return Err(self.fail_session(conflict));
1593 }
1594 self.subscription(id).expect("checked above").on_subscribe_ok()?;
1595 if let Some(binding) = self.track_bindings.get_mut(&id) {
1596 binding.alias = Some(alias);
1597 }
1598 Ok(())
1599 }
1600
1601 /// Send an UNSUBSCRIBE message for an active subscription.
1602 ///
1603 /// Section 5.1 gives the subscriber this for a subscription that came
1604 /// either way round, so a request the peer opened with PUBLISH ends here
1605 /// too. The two kinds share a map and cannot collide: a Request ID belongs
1606 /// to whichever endpoint allocated it, and the two halves of the space
1607 /// have opposite least significant bits.
1608 pub fn unsubscribe(&mut self, request_id: VarInt) -> Result<ControlMessage, EndpointError> {
1609 let id = request_id.into_inner();
1610 if let Some(mut sm) = self.subscription(id) {
1611 sm.on_unsubscribe()?;
1612 return Ok(ControlMessage::Unsubscribe(Unsubscribe { request_id }));
1613 }
1614 let mut sm = self.publish_flow(id).ok_or(EndpointError::UnknownRequest(id))?;
1615 sm.on_unsubscribe_sent()?;
1616 Ok(ControlMessage::Unsubscribe(Unsubscribe { request_id }))
1617 }
1618
1619 /// Send a SUBSCRIBE_UPDATE narrowing a subscription this endpoint opened.
1620 ///
1621 /// Section 9.11 gives the message to the sender of the request, which is
1622 /// what this endpoint is for every subscription in `subscriptions`. It
1623 /// carries a Request ID of its own and is listed among the messages that
1624 /// step the sequence, so one is allocated here and handed back with the
1625 /// message.
1626 ///
1627 /// # Errors
1628 ///
1629 /// [`EndpointError::UnknownRequest`] when this endpoint opened no
1630 /// subscription under that identifier, and the subscription flow's own
1631 /// `InvalidTransition` when the one it names has already ended.
1632 pub fn subscribe_update(
1633 &mut self,
1634 subscription_request_id: VarInt,
1635 parameters: Vec<KeyValuePair>,
1636 ) -> Result<(VarInt, ControlMessage), EndpointError> {
1637 self.require_active_or_err()?;
1638 let id = subscription_request_id.into_inner();
1639 self.subscription(id).ok_or(EndpointError::UnknownRequest(id))?.on_subscribe_update()?;
1640 let request_id = self.request_ids.allocate()?;
1641 Ok((
1642 request_id,
1643 ControlMessage::SubscribeUpdate(SubscribeUpdate {
1644 request_id,
1645 subscription_request_id,
1646 parameters,
1647 }),
1648 ))
1649 }
1650
1651 /// Process an incoming SUBSCRIBE_UPDATE.
1652 ///
1653 /// Section 9.11 puts the message in the subscriber's hands, so one that
1654 /// arrives is about a subscription the **peer** opened, and is looked for
1655 /// among those and not among this endpoint's own.
1656 ///
1657 /// # Errors
1658 ///
1659 /// [`EndpointError::UpdateForUnknownRequest`] when the Request ID names no
1660 /// request this session has carried, with the session already moved to
1661 /// Closed, and the subscription flow's own `InvalidTransition` when it
1662 /// names one that has already ended.
1663 pub fn receive_subscribe_update(&mut self, msg: &SubscribeUpdate) -> Result<(), EndpointError> {
1664 let id = msg.subscription_request_id.into_inner();
1665 if let Some(sub) = self.inbound_subscribes.get_mut(&id) {
1666 sub.state.on_subscribe_update_received()?;
1667 return Ok(());
1668 }
1669 // Section 9.11 states the close: "This MUST match an existing Request
1670 // ID", and the publisher must end the session with PROTOCOL_VIOLATION
1671 // "if the subscriber specifies an invalid Subscription Request ID". The
1672 // two halves are quoted apart because the rendering carries a stray
1673 // backtick in front of the code name, so the sentence it holds is not
1674 // one anybody wrote. Existence is the only test it states, which is
1675 // wider than *no subscription the peer opened*: every request this
1676 // session has carried has existed. Reading the field name as the test
1677 // instead - that the identifier must be a SUBSCRIBE's - is contradicted
1678 // in the same section, which leaves a parameter "previously set on the
1679 // subscription in SUBSCRIBE, PUBLISH_OK or SUBSCRIBE_UPDATE" unchanged
1680 // when an update omits it: a subscription established by PUBLISH is
1681 // updatable and its identifier is a PUBLISH's. Only a subscription the
1682 // peer opened has a transition for an update, so for the rest the
1683 // message is accepted and the state left alone. Section 9.19 takes the
1684 // update away from this request kind outright: "the subscriber cannot
1685 // send SUBSCRIBE_UPDATE or UNSUBSCRIBE". A track status the peer asked
1686 // for and one this endpoint asked for are the same kind of request and
1687 // neither is a subscription, so both are refused. The check comes
1688 // before the set below because an identifier naming a track status has
1689 // existed, and being told so is what would let it through.
1690 if self.track_statuses.contains_key(&id) || self.inbound_track_statuses.contains_key(&id) {
1691 return Err(EndpointError::NotASubscription(id));
1692 }
1693 let existed = self.subscriptions.contains_key(&id)
1694 || self.publishes.contains_key(&id)
1695 || self.fetches.contains_key(&id)
1696 || self.subscribe_namespaces.contains_key(&id)
1697 || self.publish_namespaces.contains_key(&id);
1698 if existed {
1699 return Ok(());
1700 }
1701 Err(self.fail_session(EndpointError::UpdateForUnknownRequest(id)))
1702 }
1703
1704 /// Process an incoming PUBLISH_DONE (subscriber side -- publisher
1705 /// finished). Draft-15: has request_id, status_code, stream_count,
1706 /// reason_phrase.
1707 ///
1708 /// Section 5.1 gives the publisher this for a subscription that came
1709 /// either way round, so one the peer opened with PUBLISH ends here too.
1710 pub fn receive_publish_done(&mut self, msg: &PublishDone) -> Result<(), EndpointError> {
1711 let id = msg.request_id.into_inner();
1712 if let Some(mut sm) = self.subscription(id) {
1713 sm.on_publish_done()?;
1714 return Ok(());
1715 }
1716 let mut sm = self.publish_flow(id).ok_or(EndpointError::UnknownRequest(id))?;
1717 sm.on_publish_done_received()?;
1718 Ok(())
1719 }
1720
1721 // -- Answering a SUBSCRIBE the peer sent ------------------------
1722
1723 /// Process an incoming SUBSCRIBE, recording the subscription it opens.
1724 ///
1725 /// The Request ID has already been checked by [`Self::receive_request`],
1726 /// which every request message passes through before its own handler.
1727 /// There is no Track Alias to judge here: Section 9.10 carries it in the
1728 /// SUBSCRIBE_OK, so this endpoint chooses it, and it is judged when the
1729 /// answer is built.
1730 ///
1731 /// # Errors
1732 ///
1733 /// The session error when the session is not established, and the
1734 /// subscription flow's own `InvalidTransition` for a second SUBSCRIBE
1735 /// under a Request ID already carrying one.
1736 pub fn receive_subscribe(&mut self, msg: &message::Subscribe) -> Result<(), EndpointError> {
1737 self.require_active_or_err()?;
1738 let id = msg.request_id.into_inner();
1739 let mut state = SubscriptionStateMachine::new();
1740 state.on_subscribe_received()?;
1741 self.inbound_subscribes.insert(id, InboundSubscribe { message: msg.clone(), state });
1742 Ok(())
1743 }
1744
1745 /// The SUBSCRIBE the peer sent under `request_id` and this endpoint has
1746 /// not answered yet.
1747 ///
1748 /// `None` once it has been answered, and for an identifier this session
1749 /// has no inbound subscription for. The record itself lives on for as long
1750 /// as the session does, which is what lets an update say whether the
1751 /// request it names has ever existed.
1752 pub fn pending_subscribe(&self, request_id: VarInt) -> Option<&message::Subscribe> {
1753 self.inbound_subscribes
1754 .get(&request_id.into_inner())
1755 .filter(|s| s.state.state() == SubscriptionState::Subscribing)
1756 .map(|s| &s.message)
1757 }
1758
1759 /// How many SUBSCRIBEs the peer has sent that are still waiting for an
1760 /// answer.
1761 pub fn pending_subscribe_count(&self) -> usize {
1762 self.inbound_subscribes
1763 .values()
1764 .filter(|s| s.state.state() == SubscriptionState::Subscribing)
1765 .count()
1766 }
1767
1768 /// Build the SUBSCRIBE_OK accepting a subscription the peer opened, giving
1769 /// its track a Track Alias.
1770 ///
1771 /// # Errors
1772 ///
1773 /// [`EndpointError::UnknownRequest`] if the peer opened no subscription
1774 /// under that identifier, [`EndpointError::TrackAliasInUse`] if a live
1775 /// track of this endpoint's already holds the alias, and
1776 /// [`EndpointError::Subscription`] if the request has already been
1777 /// answered: Section 5.1 says "A publisher MUST send exactly one
1778 /// SUBSCRIBE_OK or REQUEST_ERROR in response to a SUBSCRIBE."
1779 pub fn send_subscribe_ok(
1780 &mut self,
1781 request_id: VarInt,
1782 track_alias: VarInt,
1783 parameters: Vec<KeyValuePair>,
1784 ) -> Result<ControlMessage, EndpointError> {
1785 let id = request_id.into_inner();
1786 let alias = track_alias.into_inner();
1787 let sub = self.inbound_subscribes.get(&id).ok_or(EndpointError::UnknownRequest(id))?;
1788 let namespace = sub.message.track_namespace.clone();
1789 let name = sub.message.track_name.clone();
1790 if let Some(refusal) = self.alias_held_elsewhere(alias, &namespace, &name) {
1791 return Err(refusal);
1792 }
1793 let sub = self.inbound_subscribes.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
1794 sub.state.on_subscribe_ok_sent()?;
1795 self.track_bindings.insert(
1796 id,
1797 TrackBinding { namespace, name, alias: Some(alias), kind: BindingKind::PeerSubscribe },
1798 );
1799 Ok(ControlMessage::SubscribeOk(message::SubscribeOk {
1800 request_id,
1801 track_alias,
1802 parameters,
1803 }))
1804 }
1805
1806 /// Build the REQUEST_ERROR refusing a request the peer opened.
1807 ///
1808 /// One message refuses more than one kind of request, so the record it ends
1809 /// is found by the identifier: the maps are keyed by Request ID and one id
1810 /// belongs to one request. A subscription, a fetch, an announcement the peer
1811 /// made, a track status it asked for and a namespace it subscribed to all
1812 /// end here.
1813 ///
1814 /// Section 9.19: "A publisher responds to a failed TRACK_STATUS with an
1815 /// appropriate REQUEST_ERROR message."
1816 ///
1817 /// Section 9.16.2 names the code a Joining Fetch naming an unjoinable
1818 /// subscription is refused with, so that refusal cannot go out under any
1819 /// other.
1820 ///
1821 /// # Errors
1822 ///
1823 /// [`EndpointError::UnknownRequest`] if the peer opened no subscription, no
1824 /// fetch, no announcement and no namespace subscription under that
1825 /// identifier,
1826 /// [`EndpointError::WrongJoiningRefusal`] if a Joining Fetch is refused
1827 /// under the wrong code, [`EndpointError::WrongOverlapRefusal`] if an
1828 /// overlapping namespace subscription is refused under the wrong code, and
1829 /// the flow's own `InvalidTransition` if the request has already been
1830 /// answered.
1831 pub fn send_request_error(
1832 &mut self,
1833 request_id: VarInt,
1834 error_code: VarInt,
1835 reason_phrase: Vec<u8>,
1836 ) -> Result<ControlMessage, EndpointError> {
1837 let id = request_id.into_inner();
1838 if self.inbound_subscribes.contains_key(&id) {
1839 let sub =
1840 self.inbound_subscribes.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
1841 sub.state.on_request_error_sent()?;
1842 } else if let Some(fetch) = self.inbound_fetches.get_mut(&id) {
1843 if fetch.unjoinable.is_some() {
1844 let required = RequestErrorCode::InvalidJoiningRequestId as u64;
1845 if error_code.into_inner() != required {
1846 return Err(EndpointError::WrongJoiningRefusal { fetch: id, required });
1847 }
1848 }
1849 fetch.state.on_fetch_error_sent()?;
1850 } else if let Some(ann) = self.inbound_publish_namespaces.get_mut(&id) {
1851 ann.state.on_publish_namespace_error_sent()?;
1852 } else if let Some(req) = self.inbound_track_statuses.get_mut(&id) {
1853 req.state.on_track_status_error_sent()?;
1854 } else if let Some(sub) = self.inbound_subscribe_namespaces.get_mut(&id) {
1855 if sub.overlaps.is_some() {
1856 let required = RequestErrorCode::PrefixOverlap as u64;
1857 if error_code.into_inner() != required {
1858 return Err(EndpointError::WrongOverlapRefusal { request: id, required });
1859 }
1860 }
1861 sub.state.on_subscribe_namespace_error_sent()?;
1862 } else {
1863 return Err(EndpointError::UnknownRequest(id));
1864 }
1865 Ok(ControlMessage::RequestError(message::RequestError {
1866 request_id,
1867 error_code,
1868 reason_phrase,
1869 }))
1870 }
1871
1872 /// Process an incoming UNSUBSCRIBE, ending a subscription this endpoint
1873 /// publishes and freeing the Track Alias it held.
1874 /// Section 9.12: "A Subscriber issues an UNSUBSCRIBE message to a Publisher
1875 /// indicating it is no longer interested in receiving the specified Track,
1876 /// indicating that the Publisher stop sending Objects as soon as possible."
1877 /// The message travels from subscriber to publisher, so what it can end is
1878 /// whatever this endpoint publishes - and Section 5.1 gives that two
1879 /// sources, not one: "A subscription can be initiated and moved to the
1880 /// Pending state by either a publisher or a subscriber."
1881 ///
1882 /// # Errors
1883 ///
1884 /// [`EndpointError::UnknownRequest`] if this endpoint publishes no
1885 /// subscription under that identifier, [`EndpointError::NotASubscription`]
1886 /// for one that names a track status, [`EndpointError::Subscription`] if
1887 /// it is one the peer opened that this endpoint never accepted or has
1888 /// already ended, and [`EndpointError::PublishFlow`] for the same of one
1889 /// this endpoint opened.
1890 pub fn receive_unsubscribe(&mut self, msg: &message::Unsubscribe) -> Result<(), EndpointError> {
1891 let id = msg.request_id.into_inner();
1892 // The other half of the same sentence. Section 9.19: "the subscriber
1893 // cannot send SUBSCRIBE_UPDATE or UNSUBSCRIBE". Refused by name rather than left
1894 // to the miss below, which would say the identifier names nothing when
1895 // it names a request this session is carrying.
1896 if self.track_statuses.contains_key(&id) || self.inbound_track_statuses.contains_key(&id) {
1897 return Err(EndpointError::NotASubscription(id));
1898 }
1899 // The offers this endpoint made itself. `publishes` holds both
1900 // directions and the offer's own message is what tells them apart: one
1901 // the peer made was written down when it arrived and one of this
1902 // endpoint's never was. A peer sending UNSUBSCRIBE for its own PUBLISH
1903 // is the publisher ending a subscription the sentence above gives the
1904 // subscriber, so it falls to the miss below rather than being taken.
1905 if !self.inbound_publishes.contains_key(&id) {
1906 if let Some(mut sm) = self.publish_flow(id) {
1907 sm.on_unsubscribe_received()?;
1908 return Ok(());
1909 }
1910 }
1911 let sub = self.inbound_subscribes.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
1912 sub.state.on_unsubscribe_received()?;
1913 Ok(())
1914 }
1915
1916 // -- Fetch flow -------------------------------------------------
1917
1918 /// Send a standalone FETCH message. Allocates a request ID and creates a
1919 /// fetch state machine. Draft-15: has end_object.
1920 /// `parameters` are the request's own, as they are on every other request
1921 /// this endpoint makes. FETCH carried an empty list on all five of these
1922 /// drafts while SUBSCRIBE, TRACK_STATUS, PUBLISH_NAMESPACE and PUBLISH
1923 /// took the caller's, which made it the only one an application could not
1924 /// attach an authorization token to.
1925 #[allow(clippy::too_many_arguments)]
1926 pub fn fetch(
1927 &mut self,
1928 track_namespace: TrackNamespace,
1929 track_name: Vec<u8>,
1930 start_group: VarInt,
1931 start_object: VarInt,
1932 end_group: VarInt,
1933 end_object: VarInt,
1934 parameters: Vec<KeyValuePair>,
1935 ) -> Result<(VarInt, ControlMessage), EndpointError> {
1936 self.require_active_or_err()?;
1937 let req_id = self.request_ids.allocate()?;
1938
1939 let mut sm = FetchStateMachine::new();
1940 sm.on_fetch_sent()?;
1941 self.fetches.insert(req_id.into_inner(), Mutex::new(sm));
1942 // Recorded before the two of them are moved into the message: a fetch
1943 // that has left no track behind is one no withdrawal can find.
1944 self.fetch_tracks.insert(
1945 req_id.into_inner(),
1946 FetchTrack { namespace: track_namespace.clone(), name: track_name.clone() },
1947 );
1948
1949 let msg = ControlMessage::Fetch(Fetch {
1950 request_id: req_id,
1951 fetch_type: FetchType::Standalone,
1952 fetch_payload: FetchPayload::Standalone {
1953 track_namespace,
1954 track_name,
1955 start_group,
1956 start_object,
1957 end_group,
1958 end_object,
1959 },
1960 parameters,
1961 });
1962 Ok((req_id, msg))
1963 }
1964
1965 /// Send a Relative Joining Fetch (Fetch Type 0x2). Allocates a request ID.
1966 ///
1967 /// `joining_start` is an offset rather than a group number: draft-15
1968 /// Section 9.16.2.1 has the publisher set the Start Location to
1969 /// "{Subscribe Largest Location.Group - Joining Start, 0}". To name the starting group
1970 /// outright, use [`absolute_joining_fetch`](Self::absolute_joining_fetch).
1971 ///
1972 /// `parameters` are the request's own, as they are on every other request
1973 /// this endpoint makes.
1974 pub fn joining_fetch(
1975 &mut self,
1976 joining_request_id: VarInt,
1977 joining_start: VarInt,
1978 parameters: Vec<KeyValuePair>,
1979 ) -> Result<(VarInt, ControlMessage), EndpointError> {
1980 self.joining_fetch_of_type(
1981 FetchType::RelativeJoining,
1982 joining_request_id,
1983 joining_start,
1984 parameters,
1985 )
1986 }
1987
1988 /// Send an Absolute Joining Fetch (Fetch Type 0x3). Allocates a request ID.
1989 ///
1990 /// Draft-15 Section 9.16.2.1: "For an Absolute Joining Fetch, the
1991 /// publisher sets the Start Location to {Joining Start, 0}." So
1992 /// `joining_start` is the group to begin at rather than a count back from
1993 /// one, and Section 9.16.2 leaves the choice with the subscriber: "The
1994 /// subscriber can set the Start Location to an absolute Location or a
1995 /// Location relative to the current group." Only the relative form could be sent
1996 /// before, so an application that knew which group it wanted had to
1997 /// express it as an offset from a largest group it may never have been
1998 /// told.
1999 pub fn absolute_joining_fetch(
2000 &mut self,
2001 joining_request_id: VarInt,
2002 joining_start: VarInt,
2003 parameters: Vec<KeyValuePair>,
2004 ) -> Result<(VarInt, ControlMessage), EndpointError> {
2005 self.joining_fetch_of_type(
2006 FetchType::AbsoluteJoining,
2007 joining_request_id,
2008 joining_start,
2009 parameters,
2010 )
2011 }
2012
2013 /// The two above, which differ in the Fetch Type and in what the publisher
2014 /// then reads Joining Start as. Nothing else about the message changes.
2015 fn joining_fetch_of_type(
2016 &mut self,
2017 fetch_type: FetchType,
2018 joining_request_id: VarInt,
2019 joining_start: VarInt,
2020 parameters: Vec<KeyValuePair>,
2021 ) -> Result<(VarInt, ControlMessage), EndpointError> {
2022 self.require_active_or_err()?;
2023 let req_id = self.request_ids.allocate()?;
2024
2025 let mut sm = FetchStateMachine::new();
2026 sm.on_fetch_sent()?;
2027 self.fetches.insert(req_id.into_inner(), Mutex::new(sm));
2028 // Resolved through the join, once, here. Section 9.16.2 has the
2029 // publisher take the Track Namespace and Track Name from the
2030 // subscription this names, so that is the track, and a Request ID this
2031 // session holds no track for leaves the fetch out of the record
2032 // entirely rather than putting a guess in it.
2033 if let Some(binding) = self.track_bindings.get(&joining_request_id.into_inner()) {
2034 let track =
2035 FetchTrack { namespace: binding.namespace.clone(), name: binding.name.clone() };
2036 self.fetch_tracks.insert(req_id.into_inner(), track);
2037 }
2038
2039 let msg = ControlMessage::Fetch(Fetch {
2040 request_id: req_id,
2041 fetch_type,
2042 fetch_payload: FetchPayload::Joining { joining_request_id, joining_start },
2043 parameters,
2044 });
2045 Ok((req_id, msg))
2046 }
2047
2048 /// Process an incoming FETCH_OK. Draft-15: has request_id, end_of_track,
2049 /// end_group, end_object, parameters.
2050 pub fn receive_fetch_ok(&mut self, msg: &message::FetchOk) -> Result<(), EndpointError> {
2051 let id = msg.request_id.into_inner();
2052 let mut sm = self.fetch_flow(id).ok_or(EndpointError::UnknownRequest(id))?;
2053 sm.on_fetch_ok()?;
2054 Ok(())
2055 }
2056
2057 /// Send a FETCH_CANCEL message.
2058 pub fn fetch_cancel(&mut self, request_id: VarInt) -> Result<ControlMessage, EndpointError> {
2059 let id = request_id.into_inner();
2060 let mut sm = self.fetch_flow(id).ok_or(EndpointError::UnknownRequest(id))?;
2061 sm.on_fetch_cancel()?;
2062 Ok(ControlMessage::FetchCancel(FetchCancel { request_id }))
2063 }
2064
2065 /// Notify that a fetch data stream received FIN.
2066 ///
2067 /// It may arrive before the FETCH_OK or REQUEST_ERROR answering the
2068 /// request, which leaves the fetch in `FetchState::Unanswered` until the
2069 /// answer lands.
2070 pub fn on_fetch_stream_fin(&mut self, request_id: VarInt) -> Result<(), EndpointError> {
2071 let id = request_id.into_inner();
2072 let mut sm = self.fetch_flow(id).ok_or(EndpointError::UnknownRequest(id))?;
2073 sm.on_stream_fin()?;
2074 Ok(())
2075 }
2076
2077 /// Notify that a fetch data stream was reset.
2078 ///
2079 /// As with a FIN, it may arrive before the answer to the request.
2080 pub fn on_fetch_stream_reset(&mut self, request_id: VarInt) -> Result<(), EndpointError> {
2081 let id = request_id.into_inner();
2082 let mut sm = self.fetch_flow(id).ok_or(EndpointError::UnknownRequest(id))?;
2083 sm.on_stream_reset()?;
2084 Ok(())
2085 }
2086
2087 // -- Answering a FETCH the peer sent ----------------------------
2088
2089 /// Process an incoming FETCH, recording the fetch it opens.
2090 ///
2091 /// The Request ID has already been checked by [`Self::receive_request`],
2092 /// which every request message passes through before its own handler.
2093 ///
2094 /// A Joining Fetch is recorded like any other. Section 9.16.2 answers one
2095 /// naming a subscription this session cannot join with a refusal and not
2096 /// a session close, and a refusal is a message this endpoint has to
2097 /// build, so the request it refuses has to be on record first.
2098 ///
2099 /// # Errors
2100 ///
2101 /// The session error when the session is not established, and the fetch
2102 /// flow's own `InvalidTransition` for a second FETCH under an identifier
2103 /// already carrying one.
2104 pub fn receive_fetch(&mut self, msg: &Fetch) -> Result<(), EndpointError> {
2105 self.require_active_or_err()?;
2106 let id = msg.request_id.into_inner();
2107 let unjoinable = self.joining_subscription_missing(msg);
2108 let mut state = FetchStateMachine::new();
2109 state.on_fetch_received()?;
2110 self.inbound_fetches.insert(id, InboundFetch { message: msg.clone(), state, unjoinable });
2111 Ok(())
2112 }
2113
2114 /// The FETCH the peer sent under `request_id` and this endpoint has not
2115 /// answered yet.
2116 ///
2117 /// `None` once it has been answered, and for an identifier this session
2118 /// has no inbound fetch for. The record itself lives on past the answer,
2119 /// because the fetch is not over until its data stream is.
2120 pub fn pending_fetch(&self, request_id: VarInt) -> Option<&Fetch> {
2121 self.inbound_fetches
2122 .get(&request_id.into_inner())
2123 .filter(|f| matches!(f.state.state(), FetchState::Pending | FetchState::Unanswered))
2124 .map(|f| &f.message)
2125 }
2126
2127 /// How many FETCHes the peer has sent that are still waiting for an
2128 /// answer.
2129 pub fn pending_fetch_count(&self) -> usize {
2130 self.inbound_fetches
2131 .values()
2132 .filter(|f| matches!(f.state.state(), FetchState::Pending | FetchState::Unanswered))
2133 .count()
2134 }
2135
2136 /// The identifier an arriving Joining Fetch names, when this session has
2137 /// no subscription it may join.
2138 ///
2139 /// Section 9.16.2:
2140 /// "If a publisher receives a Joining Fetch with a Request ID that
2141 /// does not correspond to an existing Subscribe in the same session, it
2142 /// MUST return a REQUEST_ERROR with error code INVALID_JOINING_REQUEST_ID".
2143 ///
2144 /// The verdict is taken as the FETCH arrives, because that is the moment
2145 /// the sentence names, and it is kept. A subscription that ends between
2146 /// the FETCH and its answer does not turn a fetch that could be joined
2147 /// into one that could not.
2148 ///
2149 /// A standalone fetch names none and answers `None`, and so does a joining
2150 /// one whose subscription is live. The subscription is one the peer opened,
2151 /// because the peer is the end that fetches and this endpoint is the one
2152 /// answering.
2153 /// "Existing" is read as "has not ended". Draft-16 Section 9.16.2 states
2154 /// the same rule with the states named - "in the Established or Pending
2155 /// (subscriber) states" - which is the same set read the same way.
2156 ///
2157 /// A subscription established by a PUBLISH counts: Section 5.1 says the
2158 /// Largest Location saved "in PUBLISH or SUBSCRIBE_OK when establishing a
2159 /// subscription" is the one a Joining FETCH uses. The drafts before this
2160 /// one say "an existing Subscribe" and reach only what a SUBSCRIBE opened.
2161 fn joining_subscription_missing(&self, msg: &Fetch) -> Option<u64> {
2162 let message::FetchPayload::Joining { joining_request_id: joined, .. } = &msg.fetch_payload
2163 else {
2164 return None;
2165 };
2166 let joined = joined.into_inner();
2167 let live = self
2168 .inbound_subscribes
2169 .get(&joined)
2170 .is_some_and(|s| s.state.state() != SubscriptionState::Done)
2171 || self.publish_flow(joined).is_some_and(|p| p.state() != PublishState::Done);
2172 if live {
2173 None
2174 } else {
2175 Some(joined)
2176 }
2177 }
2178
2179 /// Build the FETCH_OK accepting a fetch the peer opened.
2180 ///
2181 /// # Errors
2182 ///
2183 /// [`EndpointError::UnknownRequest`] if the peer opened no fetch under
2184 /// that identifier, [`EndpointError::UnjoinableSubscription`] for a
2185 /// Joining Fetch naming a subscription this session cannot join, and the
2186 /// fetch flow's own `InvalidTransition` for a second answer: Section 5.2
2187 /// says the publisher "MUST send exactly one FETCH_OK or REQUEST_ERROR in
2188 /// response to a FETCH".
2189 pub fn send_fetch_ok(
2190 &mut self,
2191 request_id: VarInt,
2192 end_of_track: u8,
2193 end_group: VarInt,
2194 end_object: VarInt,
2195 parameters: Vec<KeyValuePair>,
2196 ) -> Result<ControlMessage, EndpointError> {
2197 let id = request_id.into_inner();
2198 let unjoinable =
2199 self.inbound_fetches.get(&id).ok_or(EndpointError::UnknownRequest(id))?.unjoinable;
2200 if let Some(joining) = unjoinable {
2201 return Err(EndpointError::UnjoinableSubscription { fetch: id, joining });
2202 }
2203 let fetch = self.inbound_fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2204 fetch.state.on_fetch_ok_sent()?;
2205 Ok(ControlMessage::FetchOk(message::FetchOk {
2206 request_id,
2207 end_of_track,
2208 end_group,
2209 end_object,
2210 parameters,
2211 }))
2212 }
2213
2214 /// Process an incoming FETCH_CANCEL, ending the fetch the peer opened.
2215 ///
2216 /// Section 9.18: the subscriber sends it to stop a fetch it no longer
2217 /// wants, so the record this endpoint serves the fetch from is the one it
2218 /// ends.
2219 ///
2220 /// # Errors
2221 ///
2222 /// [`EndpointError::UnknownRequest`] if the peer opened no fetch under
2223 /// that identifier, and the fetch flow's own `InvalidTransition` for a fetch
2224 /// that has already ended.
2225 pub fn receive_fetch_cancel(&mut self, msg: &FetchCancel) -> Result<(), EndpointError> {
2226 let id = msg.request_id.into_inner();
2227 let fetch = self.inbound_fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2228 fetch.state.on_fetch_cancel_received()?;
2229 Ok(())
2230 }
2231
2232 /// Note that this endpoint finished the data stream serving a fetch the
2233 /// peer opened.
2234 ///
2235 /// A fetch is over when its answer and its data stream have both settled,
2236 /// and this is the second of those for the end that serves it.
2237 ///
2238 /// # Errors
2239 ///
2240 /// [`EndpointError::UnknownRequest`] if the peer opened no fetch under
2241 /// that identifier, and the fetch flow's own `InvalidTransition` from a state
2242 /// the stream cannot close from.
2243 pub fn on_peer_fetch_stream_fin(&mut self, request_id: VarInt) -> Result<(), EndpointError> {
2244 let id = request_id.into_inner();
2245 let fetch = self.inbound_fetches.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2246 fetch.state.on_stream_fin_sent()?;
2247 Ok(())
2248 }
2249
2250 // -- Subscribe Namespace flow -----------------------------------
2251
2252 /// Send a SUBSCRIBE_NAMESPACE message. Draft-15: uses namespace_prefix.
2253 ///
2254 /// Section 9.23 addresses the first half of the overlap rule to this end
2255 /// of the session: "A subscriber cannot make overlapping namespace
2256 /// subscriptions on a single session." So a prefix overlapping one this
2257 /// endpoint has already asked about is refused here rather than built and
2258 /// sent for the peer to refuse.
2259 ///
2260 /// # Errors
2261 ///
2262 /// The session error when the session is not established,
2263 /// [`EndpointError::OwnPrefixOverlap`] when the prefix overlaps one this
2264 /// endpoint is already subscribed to, and the Request ID allocator's own
2265 /// error when the peer has granted no room for another request.
2266 pub fn subscribe_namespace(
2267 &mut self,
2268 namespace_prefix: TrackNamespace,
2269 parameters: Vec<KeyValuePair>,
2270 ) -> Result<(VarInt, ControlMessage), EndpointError> {
2271 self.require_active_or_err()?;
2272 // The subscriber's half of the rule, refused before the message
2273 // exists. A publisher that follows this draft answers it with a
2274 // refusal, so building it spends a Request ID on a namespace
2275 // subscription that is not going to open.
2276 if let Some(established) = self.own_prefix_overlap(&namespace_prefix) {
2277 return Err(EndpointError::OwnPrefixOverlap { established });
2278 }
2279 let req_id = self.request_ids.allocate()?;
2280
2281 let mut sm = SubscribeNamespaceStateMachine::new();
2282 sm.on_subscribe_namespace_sent()?;
2283 self.subscribe_namespaces.insert(req_id.into_inner(), sm);
2284 self.subscribe_namespace_prefixes.insert(req_id.into_inner(), namespace_prefix.clone());
2285
2286 let msg = ControlMessage::SubscribeNamespace(SubscribeNamespace {
2287 request_id: req_id,
2288 namespace_prefix,
2289 parameters,
2290 });
2291 Ok((req_id, msg))
2292 }
2293
2294 /// Send an UNSUBSCRIBE_NAMESPACE message. Draft-15: just request_id.
2295 pub fn unsubscribe_namespace(
2296 &mut self,
2297 request_id: VarInt,
2298 ) -> Result<ControlMessage, EndpointError> {
2299 let id = request_id.into_inner();
2300 let sm = self.subscribe_namespaces.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2301 sm.on_unsubscribe_namespace()?;
2302 Ok(ControlMessage::UnsubscribeNamespace(UnsubscribeNamespace { request_id }))
2303 }
2304
2305 // -- Answering a SUBSCRIBE_NAMESPACE the peer sent --------------
2306
2307 /// Process an incoming SUBSCRIBE_NAMESPACE, recording the namespace
2308 /// subscription it opens.
2309 ///
2310 /// Section 9.23: "The subscriber sends the SUBSCRIBE_NAMESPACE control
2311 /// message to a publisher to request the current set of matching
2312 /// published namespaces and established subscriptions, as well as future
2313 /// updates to the set."
2314 ///
2315 /// The set it asks for is this endpoint's to decide, and deciding needs
2316 /// both the request and somewhere to answer from. The record holds the
2317 /// message and not only its state, because every message that answers
2318 /// this request or ends it names the prefix the request carried, and
2319 /// nothing else here has it.
2320 ///
2321 /// # Errors
2322 ///
2323 /// The session error when the session is not established.
2324 pub fn receive_subscribe_namespace(
2325 &mut self,
2326 msg: &SubscribeNamespace,
2327 ) -> Result<(), EndpointError> {
2328 self.require_active_or_err()?;
2329 let overlaps = self.peer_prefix_overlap(&msg.namespace_prefix);
2330 let mut state = SubscribeNamespaceStateMachine::new();
2331 state.on_subscribe_namespace_received()?;
2332 self.inbound_subscribe_namespaces.insert(
2333 msg.request_id.into_inner(),
2334 InboundSubscribeNamespace { message: msg.clone(), state, overlaps },
2335 );
2336 Ok(())
2337 }
2338
2339 /// The earliest namespace subscription the peer has made whose prefix
2340 /// overlaps `prefix`, and `None` when there is none.
2341 ///
2342 /// Only ones that have not ended count: the sentence weighs the arriving
2343 /// prefix against "an established namespace subscription", so one the
2344 /// peer has withdrawn and one this endpoint refused are both past. Drafts
2345 /// 07 through 11 say "an earlier" instead and count those too.
2346 ///
2347 /// One that has arrived and has not been answered does count. It is not
2348 /// established yet, but this endpoint is the one about to establish it,
2349 /// and accepting both would leave the session holding exactly the pair
2350 /// the sentence exists to prevent.
2351 ///
2352 /// Namespace subscriptions this endpoint made are a separate set and are
2353 /// not consulted. This endpoint is the subscriber for those, so a prefix
2354 /// it asked about says nothing about what the peer may ask about.
2355 ///
2356 /// The lowest Request ID wins when more than one overlaps, so the answer
2357 /// does not depend on the order a map happens to iterate in. Identifiers
2358 /// are handed out in increasing order, so the lowest of them is the
2359 /// earliest request.
2360 fn peer_prefix_overlap(&self, prefix: &TrackNamespace) -> Option<u64> {
2361 self.inbound_subscribe_namespaces
2362 .iter()
2363 .filter(|(_, s)| s.state.state() != SubscribeNamespaceState::Done)
2364 .filter_map(|(&id, s)| {
2365 prefixes_overlap(&s.message.namespace_prefix.0, &prefix.0).then_some(id)
2366 })
2367 .min()
2368 }
2369
2370 /// The earliest namespace subscription **this endpoint** has made whose
2371 /// prefix overlaps `prefix`, and `None` when there is none.
2372 ///
2373 /// The subscriber's half of the same sentence reads over this endpoint's
2374 /// own requests, and takes the same view of which of them are past as
2375 /// [`Self::peer_prefix_overlap`] takes of the peer's.
2376 fn own_prefix_overlap(&self, prefix: &TrackNamespace) -> Option<u64> {
2377 self.subscribe_namespace_prefixes
2378 .iter()
2379 .filter_map(|(&id, key)| prefixes_overlap(&key.0, &prefix.0).then_some(id))
2380 .filter(|id| {
2381 self.subscribe_namespaces
2382 .get(id)
2383 .is_some_and(|sm| sm.state() != SubscribeNamespaceState::Done)
2384 })
2385 .min()
2386 }
2387
2388 /// The SUBSCRIBE_NAMESPACE the peer sent under `request_id` and this
2389 /// endpoint has not answered yet.
2390 ///
2391 /// `None` once it has been answered, and for an identifier the peer has
2392 /// subscribed to nothing under. The record itself lives on past the
2393 /// answer, because a namespace subscription that was accepted is not over
2394 /// until it is withdrawn.
2395 pub fn pending_subscribe_namespace(&self, request_id: VarInt) -> Option<&SubscribeNamespace> {
2396 self.inbound_subscribe_namespaces
2397 .get(&request_id.into_inner())
2398 .filter(|s| s.state.state() == SubscribeNamespaceState::Pending)
2399 .map(|s| &s.message)
2400 }
2401
2402 /// How many namespace subscriptions the peer has made that are still
2403 /// waiting for an answer.
2404 pub fn pending_subscribe_namespace_count(&self) -> usize {
2405 self.inbound_subscribe_namespaces
2406 .values()
2407 .filter(|s| s.state.state() == SubscribeNamespaceState::Pending)
2408 .count()
2409 }
2410
2411 /// Process an incoming UNSUBSCRIBE_NAMESPACE, ending the namespace
2412 /// subscription the peer made.
2413 ///
2414 /// Section 6.1: "An UNSUBSCRIBE_NAMESPACE withdraws a previous
2415 /// SUBSCRIBE_NAMESPACE."
2416 ///
2417 /// Section 9.24 names what it carries: "Request ID: The Request ID of the
2418 /// SUBSCRIBE_NAMESPACE", so the record it ends is found by identifier and
2419 /// not by prefix.
2420 ///
2421 /// # Errors
2422 ///
2423 /// [`EndpointError::UnknownRequest`] if the peer has subscribed to
2424 /// nothing under that identifier, and the namespace flow's own
2425 /// `InvalidTransition` for one this endpoint never accepted.
2426 pub fn receive_unsubscribe_namespace(
2427 &mut self,
2428 msg: &UnsubscribeNamespace,
2429 ) -> Result<(), EndpointError> {
2430 let id = msg.request_id.into_inner();
2431 let sub = self
2432 .inbound_subscribe_namespaces
2433 .get_mut(&id)
2434 .ok_or(EndpointError::UnknownRequest(id))?;
2435 sub.state.on_unsubscribe_namespace_received()?;
2436 Ok(())
2437 }
2438
2439 // -- Publish Namespace flow -------------------------------------
2440
2441 /// Send a PUBLISH_NAMESPACE message.
2442 pub fn publish_namespace(
2443 &mut self,
2444 track_namespace: TrackNamespace,
2445 parameters: Vec<KeyValuePair>,
2446 ) -> Result<(VarInt, ControlMessage), EndpointError> {
2447 self.require_active_or_err()?;
2448 let req_id = self.request_ids.allocate()?;
2449
2450 let mut sm = PublishNamespaceStateMachine::new();
2451 sm.on_publish_namespace_sent()?;
2452 self.publish_namespaces.insert(req_id.into_inner(), sm);
2453 self.publish_namespace_namespaces.insert(req_id.into_inner(), track_namespace.clone());
2454
2455 let msg = ControlMessage::PublishNamespace(PublishNamespace {
2456 request_id: req_id,
2457 track_namespace,
2458 parameters,
2459 });
2460 Ok((req_id, msg))
2461 }
2462
2463 // -- Answering a PUBLISH_NAMESPACE the peer sent ----------------
2464
2465 /// Process an incoming PUBLISH_NAMESPACE, recording the announcement it
2466 /// makes.
2467 ///
2468 /// Section 9.20: "The publisher sends the PUBLISH_NAMESPACE control message
2469 /// to advertise that it has tracks available within a Track Namespace. The
2470 /// receiver verifies the publisher is authorized to publish tracks under
2471 /// this namespace."
2472 ///
2473 /// Verifying is the application's to do, and it needs both the message to
2474 /// verify and somewhere to answer from. The Request ID has already been
2475 /// checked by [`Self::receive_request`], which every request message passes
2476 /// through before its own handler, so a repeat of one the peer has already
2477 /// spent never reaches here.
2478 ///
2479 /// # Errors
2480 ///
2481 /// The session error when the session is not established.
2482 pub fn receive_publish_namespace(
2483 &mut self,
2484 msg: &PublishNamespace,
2485 ) -> Result<(), EndpointError> {
2486 self.require_active_or_err()?;
2487 let id = msg.request_id.into_inner();
2488 let mut state = PublishNamespaceStateMachine::new();
2489 state.on_publish_namespace_received()?;
2490 self.inbound_publish_namespaces
2491 .insert(id, InboundPublishNamespace { message: msg.clone(), state });
2492 Ok(())
2493 }
2494
2495 /// The PUBLISH_NAMESPACE the peer sent under `request_id` and this endpoint
2496 /// has not answered yet.
2497 ///
2498 /// `None` once it has been answered, and for an identifier the peer has
2499 /// announced nothing under. The record itself lives on past the answer,
2500 /// because an announcement that was accepted is not over until it is
2501 /// withdrawn or cancelled.
2502 pub fn pending_publish_namespace(&self, request_id: VarInt) -> Option<&PublishNamespace> {
2503 self.inbound_publish_namespaces
2504 .get(&request_id.into_inner())
2505 .filter(|a| a.state.state() == PublishNamespaceState::Pending)
2506 .map(|a| &a.message)
2507 }
2508
2509 /// How many announcements the peer has made that are still waiting for an
2510 /// answer.
2511 pub fn pending_publish_namespace_count(&self) -> usize {
2512 self.inbound_publish_namespaces
2513 .values()
2514 .filter(|a| a.state.state() == PublishNamespaceState::Pending)
2515 .count()
2516 }
2517
2518 /// The identifier of a live announcement the peer made for `namespace`.
2519 ///
2520 /// Section 9.21: "The publisher sends the PUBLISH_NAMESPACE_DONE control
2521 /// message to indicate its intent to stop serving new subscriptions for
2522 /// tracks within the provided Track Namespace." and Section 9.22 says what
2523 /// a cancellation is for: the subscriber "will stop sending new
2524 /// subscriptions for tracks within the provided Track Namespace". Both name
2525 /// a namespace where the PUBLISH_NAMESPACE they are about named a Request
2526 /// ID, so one record has to be reachable both ways.
2527 ///
2528 /// It is stored under the identifier, which is unique, and found by
2529 /// namespace with a scan of the same map. A second map from namespace to
2530 /// identifier would be quicker and could fall out of step with the first;
2531 /// there is nothing here for it to disagree with.
2532 ///
2533 /// An announcement that has ended is skipped, so a namespace announced
2534 /// again after being withdrawn finds the live one. Which of two live
2535 /// announcements for the same namespace is found is not decided here,
2536 /// because no sentence in the draft makes a second one for a namespace
2537 /// already announced an error.
2538 fn inbound_publish_namespace_id(&self, namespace: &TrackNamespace) -> Option<u64> {
2539 self.inbound_publish_namespaces
2540 .iter()
2541 .find(|(_, a)| {
2542 a.message.track_namespace == *namespace
2543 && a.state.state() != PublishNamespaceState::Done
2544 })
2545 .map(|(id, _)| *id)
2546 }
2547
2548 /// Build the REQUEST_OK accepting an announcement the peer made or a track
2549 /// status it asked for.
2550 ///
2551 /// One message accepts more than one kind of request, so the record it
2552 /// moves is found by the identifier: the maps are keyed by Request ID and
2553 /// one id belongs to one request.
2554 ///
2555 /// Section 6.2: "A subscriber MUST send exactly one REQUEST_OK or
2556 /// REQUEST_ERROR in response to a PUBLISH_NAMESPACE. The publisher SHOULD
2557 /// close the session with a protocol error if it receives more than one."
2558 /// Section 9.19 gives a track status the same answer - "If successful, the
2559 /// publisher responds with a REQUEST_OK message with the same parameters it
2560 /// would have set in a SUBSCRIBE_OK" - and adds that "Track Alias is not
2561 /// used", which is why accepting one binds nothing. Section 6.1 gives a
2562 /// namespace subscription the same answer: "A publisher MUST send exactly
2563 /// one REQUEST_OK or REQUEST_ERROR in response to a SUBSCRIBE_NAMESPACE."
2564 ///
2565 /// One answer and no second one: the flow moves on the first, and a second
2566 /// call finds a record that has left Pending.
2567 ///
2568 /// # Errors
2569 ///
2570 /// [`EndpointError::UnknownRequest`] if the peer has announced nothing,
2571 /// asked about nothing and subscribed to no namespace under that
2572 /// identifier, [`EndpointError::PeerPrefixOverlap`] if it is a namespace
2573 /// subscription overlapping one the peer already has, and the answering
2574 /// flow's own `InvalidTransition` for a request already answered.
2575 pub fn send_request_ok(
2576 &mut self,
2577 request_id: VarInt,
2578 parameters: Vec<KeyValuePair>,
2579 ) -> Result<ControlMessage, EndpointError> {
2580 let id = request_id.into_inner();
2581 if let Some(ann) = self.inbound_publish_namespaces.get_mut(&id) {
2582 ann.state.on_publish_namespace_ok_sent()?;
2583 } else if let Some(req) = self.inbound_track_statuses.get_mut(&id) {
2584 req.state.on_track_status_ok_sent()?;
2585 } else if let Some(sub) = self.inbound_subscribe_namespaces.get_mut(&id) {
2586 // The MUST names one answer for this request, and it is not this one.
2587 if let Some(established) = sub.overlaps {
2588 return Err(EndpointError::PeerPrefixOverlap { request: id, established });
2589 }
2590 sub.state.on_subscribe_namespace_ok_sent()?;
2591 } else {
2592 return Err(EndpointError::UnknownRequest(id));
2593 }
2594 Ok(ControlMessage::RequestOk(message::RequestOk { request_id, parameters }))
2595 }
2596
2597 /// Process an incoming PUBLISH_NAMESPACE_DONE, ending the announcement the
2598 /// peer made.
2599 ///
2600 /// Section 9.21: "The publisher sends the PUBLISH_NAMESPACE_DONE control
2601 /// message to indicate its intent to stop serving new subscriptions for
2602 /// tracks within the provided Track Namespace."
2603 ///
2604 /// The announcement it ends is the peer's, so the record it reads is the
2605 /// one this endpoint keeps of what the peer announced. An announcement this
2606 /// endpoint made is withdrawn by [`Self::publish_namespace_done`], which is
2607 /// the same message travelling the other way.
2608 ///
2609 /// # Errors
2610 ///
2611 /// [`EndpointError::UnknownPeerNamespace`] if the peer has no live
2612 /// announcement for that namespace, and the namespace flow's own
2613 /// `InvalidTransition` for one this endpoint never accepted.
2614 pub fn receive_publish_namespace_done(
2615 &mut self,
2616 msg: &PublishNamespaceDone,
2617 ) -> Result<(), EndpointError> {
2618 let id = self
2619 .inbound_publish_namespace_id(&msg.track_namespace)
2620 .ok_or(EndpointError::UnknownPeerNamespace)?;
2621 let ann = self
2622 .inbound_publish_namespaces
2623 .get_mut(&id)
2624 .ok_or(EndpointError::UnknownRequest(id))?;
2625 ann.state.on_publish_namespace_done_received()?;
2626 Ok(())
2627 }
2628
2629 /// Build the PUBLISH_NAMESPACE_CANCEL revoking an acceptance.
2630 ///
2631 /// Section 8.5 names what a cancellation revokes: a namespace "it
2632 /// previously responded REQUEST_OK to". Section 9.22 says what it does: the
2633 /// subscriber "will stop sending new subscriptions for tracks within the
2634 /// provided Track Namespace".
2635 ///
2636 /// Previously responded REQUEST_OK to is a state, and it is Active: an
2637 /// announcement reaches it by being accepted and no other way. One still
2638 /// waiting for an answer, one refused and one already ended are all refused
2639 /// here rather than sent.
2640 ///
2641 /// The announcement is the peer's. An announcement this endpoint made is
2642 /// not cancelled by its own publisher; the peer cancels it, and that
2643 /// arrives at [`Self::receive_publish_namespace_cancel`].
2644 ///
2645 /// # Errors
2646 ///
2647 /// [`EndpointError::UnknownPeerNamespace`] if the peer has no live
2648 /// announcement for that namespace, and the namespace flow's own
2649 /// `InvalidTransition` for one this endpoint never accepted.
2650 pub fn publish_namespace_cancel(
2651 &mut self,
2652 track_namespace: TrackNamespace,
2653 error_code: VarInt,
2654 reason_phrase: Vec<u8>,
2655 ) -> Result<ControlMessage, EndpointError> {
2656 let id = self
2657 .inbound_publish_namespace_id(&track_namespace)
2658 .ok_or(EndpointError::UnknownPeerNamespace)?;
2659 let ann = self
2660 .inbound_publish_namespaces
2661 .get_mut(&id)
2662 .ok_or(EndpointError::UnknownRequest(id))?;
2663 ann.state.on_publish_namespace_cancel_sent()?;
2664 Ok(ControlMessage::PublishNamespaceCancel(PublishNamespaceCancel {
2665 track_namespace,
2666 error_code,
2667 reason_phrase,
2668 }))
2669 }
2670
2671 /// Send the PUBLISH_NAMESPACE_DONE withdrawing an announcement this
2672 /// endpoint made.
2673 ///
2674 /// Section 9.21: "The publisher sends the PUBLISH_NAMESPACE_DONE control
2675 /// message to indicate its intent to stop serving new subscriptions for
2676 /// tracks within the provided Track Namespace." This endpoint is that
2677 /// publisher, so the record it ends is one [`Self::publish_namespace`]
2678 /// opened.
2679 ///
2680 /// # Errors
2681 ///
2682 /// [`EndpointError::UnknownNamespace`] if this endpoint has announced
2683 /// nothing under that namespace, and the namespace flow's own
2684 /// `InvalidTransition` for an announcement the peer has not accepted, or
2685 /// has already cancelled.
2686 pub fn publish_namespace_done(
2687 &mut self,
2688 track_namespace: TrackNamespace,
2689 ) -> Result<ControlMessage, EndpointError> {
2690 let id = self
2691 .own_publish_namespace_id(&track_namespace)
2692 .ok_or(EndpointError::UnknownNamespace)?;
2693 let sm = self.publish_namespaces.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2694 sm.on_publish_namespace_done()?;
2695 Ok(ControlMessage::PublishNamespaceDone(PublishNamespaceDone { track_namespace }))
2696 }
2697
2698 /// Process an incoming PUBLISH_NAMESPACE_CANCEL, ending an announcement
2699 /// this endpoint made.
2700 ///
2701 /// Section 8.5 names what the peer is revoking: a namespace "it previously
2702 /// responded REQUEST_OK to". What it responded to is an announcement this
2703 /// endpoint made, so the record this reads is the outbound one.
2704 ///
2705 /// # Errors
2706 ///
2707 /// [`EndpointError::UnknownNamespace`] if this endpoint has announced
2708 /// nothing under that namespace, and the namespace flow's own
2709 /// `InvalidTransition` for an announcement the peer never accepted.
2710 pub fn receive_publish_namespace_cancel(
2711 &mut self,
2712 msg: &PublishNamespaceCancel,
2713 ) -> Result<(), EndpointError> {
2714 let id = self
2715 .own_publish_namespace_id(&msg.track_namespace)
2716 .ok_or(EndpointError::UnknownNamespace)?;
2717 let sm = self.publish_namespaces.get_mut(&id).ok_or(EndpointError::UnknownRequest(id))?;
2718 sm.on_publish_namespace_cancel()?;
2719 Ok(())
2720 }
2721
2722 /// The identifier of an announcement this endpoint made for `namespace`.
2723 ///
2724 /// The mirror of [`Self::inbound_publish_namespace_id`], over the other
2725 /// map: PUBLISH_NAMESPACE_DONE and PUBLISH_NAMESPACE_CANCEL name a
2726 /// namespace on this draft, and the announcements this endpoint made are
2727 /// filed under the Request IDs it allocated.
2728 fn own_publish_namespace_id(&self, namespace: &TrackNamespace) -> Option<u64> {
2729 self.publish_namespace_namespaces.iter().find(|(_, ns)| *ns == namespace).map(|(id, _)| *id)
2730 }
2731
2732 // -- Track Status flow ------------------------------------------
2733
2734 /// Send a TRACK_STATUS message. Allocates a request ID. Draft-15:
2735 /// simplified with parameters.
2736 pub fn track_status(
2737 &mut self,
2738 track_namespace: TrackNamespace,
2739 track_name: Vec<u8>,
2740 parameters: Vec<KeyValuePair>,
2741 ) -> Result<(VarInt, ControlMessage), EndpointError> {
2742 self.require_active_or_err()?;
2743 let req_id = self.request_ids.allocate()?;
2744 let mut sm = TrackStatusStateMachine::new();
2745 sm.on_track_status_sent()?;
2746 self.track_statuses.insert(req_id.into_inner(), sm);
2747 let msg = ControlMessage::TrackStatus(message::TrackStatus {
2748 request_id: req_id,
2749 track_namespace,
2750 track_name,
2751 parameters,
2752 });
2753 Ok((req_id, msg))
2754 }
2755
2756 // -- Answering a TRACK_STATUS the peer sent ---------------------
2757
2758 /// Process an incoming TRACK_STATUS, recording what the peer asked about.
2759 ///
2760 /// Section 9.19: the receiver of one "treats it identically as if it had
2761 /// received a SUBSCRIBE message, except it does not create downstream
2762 /// subscription state or send any Objects". The exception is why this does
2763 /// not reach for the subscriptions the peer has opened: nothing that names
2764 /// a subscription is to find this request, and the surest way to hold to
2765 /// that is for it never to be one.
2766 ///
2767 /// The same section says the answer carries no alias at all here - "Track
2768 /// Alias is not used" - so unlike a subscription the peer opens, accepting
2769 /// this one binds nothing.
2770 ///
2771 /// # Errors
2772 ///
2773 /// The session error when the session is not established.
2774 pub fn receive_track_status(
2775 &mut self,
2776 msg: &message::TrackStatus,
2777 ) -> Result<(), EndpointError> {
2778 self.require_active_or_err()?;
2779 let id = msg.request_id.into_inner();
2780 let mut state = TrackStatusStateMachine::new();
2781 state.on_track_status_received()?;
2782 self.inbound_track_statuses.insert(id, InboundTrackStatus { message: msg.clone(), state });
2783 Ok(())
2784 }
2785
2786 /// The TRACK_STATUS the peer sent under `request_id` and this endpoint has
2787 /// not answered yet.
2788 ///
2789 /// `None` once it has been answered, and for an identifier this session has
2790 /// carried no track status under.
2791 pub fn pending_track_status(&self, request_id: VarInt) -> Option<&message::TrackStatus> {
2792 self.inbound_track_statuses
2793 .get(&request_id.into_inner())
2794 .filter(|t| t.state.state() == TrackStatusState::Pending)
2795 .map(|t| &t.message)
2796 }
2797
2798 /// How many track statuses the peer has asked about that are still waiting
2799 /// for an answer.
2800 pub fn pending_track_status_count(&self) -> usize {
2801 self.inbound_track_statuses
2802 .values()
2803 .filter(|t| t.state.state() == TrackStatusState::Pending)
2804 .count()
2805 }
2806
2807 // -- Publish flow (publisher side) ------------------------------
2808
2809 /// Send a PUBLISH message (publisher side). Allocates a request ID.
2810 /// Draft-15: takes track_alias, no forward.
2811 pub fn publish(
2812 &mut self,
2813 track_namespace: TrackNamespace,
2814 track_name: Vec<u8>,
2815 track_alias: VarInt,
2816 parameters: Vec<KeyValuePair>,
2817 ) -> Result<(VarInt, ControlMessage), EndpointError> {
2818 self.require_active_or_err()?;
2819 // Section 9.13: "The same Track Alias MUST NOT be used to refer to
2820 // two different Tracks simultaneously." Refused before the
2821 // Request ID is allocated, so a refusal spends nothing.
2822 let alias = track_alias.into_inner();
2823 if let Some(refusal) = self.alias_held_elsewhere(alias, &track_namespace, &track_name) {
2824 return Err(refusal);
2825 }
2826 let req_id = self.request_ids.allocate()?;
2827 let mut sm = PublishStateMachine::new();
2828 sm.on_publish_sent()?;
2829 self.publishes.insert(req_id.into_inner(), Mutex::new(sm));
2830 // The alias and the track travel together in a PUBLISH, so the binding
2831 // is complete the moment the message is built.
2832 self.track_bindings.insert(
2833 req_id.into_inner(),
2834 TrackBinding {
2835 namespace: track_namespace.clone(),
2836 name: track_name.clone(),
2837 alias: Some(alias),
2838 kind: BindingKind::Publish,
2839 },
2840 );
2841 let msg = ControlMessage::Publish(message::Publish {
2842 request_id: req_id,
2843 track_namespace,
2844 track_name,
2845 track_alias,
2846 parameters,
2847 });
2848 Ok((req_id, msg))
2849 }
2850
2851 /// Process an incoming PUBLISH_OK (publisher side). Draft-15: has
2852 /// request_id, parameters.
2853 pub fn receive_publish_ok(&mut self, msg: &message::PublishOk) -> Result<(), EndpointError> {
2854 let id = msg.request_id.into_inner();
2855 let mut sm = self.publish_flow(id).ok_or(EndpointError::UnknownRequest(id))?;
2856 sm.on_publish_ok()?;
2857 Ok(())
2858 }
2859
2860 /// Send a PUBLISH_DONE message (publisher finishing). Draft-15: has
2861 /// stream_count.
2862 pub fn send_publish_done(
2863 &mut self,
2864 request_id: VarInt,
2865 status_code: VarInt,
2866 stream_count: VarInt,
2867 reason_phrase: Vec<u8>,
2868 ) -> Result<ControlMessage, EndpointError> {
2869 let id = request_id.into_inner();
2870 // Section 9.15 gives the publisher this message for a subscription
2871 // that came either way round, so a subscription the peer opened with
2872 // SUBSCRIBE ends here too. Two records, one message.
2873 if let Some(sub) = self.inbound_subscribes.get_mut(&id) {
2874 sub.state.on_publish_done_sent()?;
2875 return Ok(ControlMessage::PublishDone(PublishDone {
2876 request_id,
2877 status_code,
2878 stream_count,
2879 reason_phrase,
2880 }));
2881 }
2882 let mut sm = self.publish_flow(id).ok_or(EndpointError::UnknownRequest(id))?;
2883 sm.on_publish_done_sent()?;
2884 Ok(ControlMessage::PublishDone(PublishDone {
2885 request_id,
2886 status_code,
2887 stream_count,
2888 reason_phrase,
2889 }))
2890 }
2891
2892 // -- Consolidated responses (draft-15) --------------------------
2893
2894 /// Process an incoming REQUEST_OK (consolidated ok for namespace/
2895 /// track-status flows).
2896 pub fn receive_request_ok(&mut self, msg: &RequestOk) -> Result<(), EndpointError> {
2897 let id = msg.request_id.into_inner();
2898 // Check subscribe_namespaces first
2899 if let Some(sm) = self.subscribe_namespaces.get_mut(&id) {
2900 sm.on_subscribe_namespace_ok()?;
2901 return Ok(());
2902 }
2903 // Then publish_namespaces
2904 if let Some(sm) = self.publish_namespaces.get_mut(&id) {
2905 sm.on_publish_namespace_ok()?;
2906 return Ok(());
2907 }
2908 // Then track_statuses
2909 if let Some(sm) = self.track_statuses.get_mut(&id) {
2910 sm.on_track_status_ok()?;
2911 return Ok(());
2912 }
2913 Err(EndpointError::UnknownRequest(id))
2914 }
2915
2916 /// Process an incoming REQUEST_ERROR (consolidated error).
2917 pub fn receive_request_error(&mut self, msg: &RequestError) -> Result<(), EndpointError> {
2918 let id = msg.request_id.into_inner();
2919 // Check subscriptions
2920 if let Some(mut sm) = self.subscription(id) {
2921 sm.on_subscribe_error()?;
2922 return Ok(());
2923 }
2924 // Check fetches
2925 if let Some(mut sm) = self.fetch_flow(id) {
2926 sm.on_fetch_error()?;
2927 return Ok(());
2928 }
2929 // Check publishes
2930 if let Some(mut sm) = self.publish_flow(id) {
2931 sm.on_publish_error()?;
2932 return Ok(());
2933 }
2934 // Check subscribe_namespaces
2935 if let Some(sm) = self.subscribe_namespaces.get_mut(&id) {
2936 sm.on_subscribe_namespace_error()?;
2937 return Ok(());
2938 }
2939 // Check publish_namespaces
2940 if let Some(sm) = self.publish_namespaces.get_mut(&id) {
2941 sm.on_publish_namespace_error()?;
2942 return Ok(());
2943 }
2944 // Check track_statuses
2945 if let Some(sm) = self.track_statuses.get_mut(&id) {
2946 sm.on_track_status_error()?;
2947 return Ok(());
2948 }
2949 // Unknown ID -- silently ignore per spec
2950 Ok(())
2951 }
2952
2953 /// Process an incoming PUBLISH message, which opens a subscription this
2954 /// endpoint is the subscriber of.
2955 ///
2956 /// Section 5.1: "A publisher initiates a subscription to a track by
2957 /// sending the PUBLISH message. The subscriber either accepts or rejects
2958 /// the subscription using PUBLISH_OK or REQUEST_ERROR." The record made here
2959 /// outlives that answer, because everything the section gives the
2960 /// subscription afterwards names this same request: an UNSUBSCRIBE from
2961 /// this endpoint, a PUBLISH_DONE from the peer, and the Track Alias the
2962 /// offer spends for as long as it lasts.
2963 ///
2964 /// # Errors
2965 ///
2966 /// [`EndpointError::DuplicateTrackAlias`] when the Track Alias offered is
2967 /// one a different live track already holds, which ends the session, and
2968 /// the publish flow's own `InvalidTransition` for a second PUBLISH under a
2969 /// request id already carrying one.
2970 pub fn receive_publish(&mut self, msg: &message::Publish) -> Result<(), EndpointError> {
2971 self.require_active_or_err()?;
2972 // Section 9.13 answers a PUBLISH naming an alias another live track
2973 // already holds with a session close, in the same words Section 9.10
2974 // uses for a SUBSCRIBE_OK. Judged before anything is written down, so
2975 // that a refused offer leaves no binding behind.
2976 let id = msg.request_id.into_inner();
2977 let alias = msg.track_alias.into_inner();
2978 if let Some(conflict) =
2979 self.conflicting_track_alias(id, alias, &msg.track_namespace, &msg.track_name)
2980 {
2981 return Err(self.fail_session(conflict));
2982 }
2983 let mut sm = PublishStateMachine::new();
2984 sm.on_publish_received()?;
2985 self.publishes.insert(id, Mutex::new(sm));
2986 // The offer as it arrived. The state machine above says how far it
2987 // has got and the binding says which track it is about; neither can
2988 // say what was offered, and that is what an answer is built from.
2989 self.inbound_publishes.insert(id, msg.clone());
2990 // A PUBLISH names its track and its alias in the one message, so the
2991 // binding is complete on arrival. It counts against the next one only
2992 // once this endpoint has answered PUBLISH_OK, which is what moves the
2993 // subscription to Active.
2994 self.track_bindings.insert(
2995 id,
2996 TrackBinding {
2997 namespace: msg.track_namespace.clone(),
2998 name: msg.track_name.clone(),
2999 alias: Some(alias),
3000 kind: BindingKind::Publish,
3001 },
3002 );
3003 Ok(())
3004 }
3005
3006 /// The PUBLISH the peer sent under `request_id` and this endpoint has not
3007 /// answered yet.
3008 ///
3009 /// `None` once it has been answered, and for an identifier this session
3010 /// has carried no offer from the peer under -- an offer of this
3011 /// endpoint's own included, whose state is in the same map but whose
3012 /// message was never received. The record itself lives on past the
3013 /// answer, because the subscription the offer opened is not over until an
3014 /// UNSUBSCRIBE or a PUBLISH_DONE ends it.
3015 pub fn pending_publish(&self, request_id: VarInt) -> Option<&message::Publish> {
3016 let id = request_id.into_inner();
3017 let unanswered =
3018 self.publish_flow(id).is_some_and(|sm| sm.state() == PublishState::Publishing);
3019 self.inbound_publishes.get(&id).filter(|_| unanswered)
3020 }
3021
3022 /// How many offers the peer has made that are still waiting for an answer.
3023 pub fn pending_publish_count(&self) -> usize {
3024 self.inbound_publishes
3025 .keys()
3026 .filter(|id| {
3027 self.publish_flow(**id).is_some_and(|sm| sm.state() == PublishState::Publishing)
3028 })
3029 .count()
3030 }
3031 /// Generate a PUBLISH_OK accepting a PUBLISH the peer sent, which
3032 /// establishes the subscription it opened.
3033 ///
3034 /// # Errors
3035 ///
3036 /// [`EndpointError::UnknownRequest`] when no PUBLISH arrived under that
3037 /// id, and the publish flow's own `InvalidTransition` for a second answer:
3038 /// Section 5.1 says "A subscriber MUST send exactly one PUBLISH_OK or
3039 /// REQUEST_ERROR in response to a PUBLISH."
3040 pub fn send_publish_ok(
3041 &mut self,
3042 request_id: VarInt,
3043 parameters: Vec<KeyValuePair>,
3044 ) -> Result<ControlMessage, EndpointError> {
3045 let id = request_id.into_inner();
3046 let mut sm = self.publish_flow(id).ok_or(EndpointError::UnknownRequest(id))?;
3047 sm.on_publish_ok_sent()?;
3048 Ok(ControlMessage::PublishOk(message::PublishOk { request_id, parameters }))
3049 }
3050
3051 /// Generate the REQUEST_ERROR that rejects a PUBLISH the peer sent, which
3052 /// ends the subscription it opened before it was established.
3053 ///
3054 /// This draft has no PUBLISH_ERROR: Section 5.1 answers a rejected PUBLISH
3055 /// with the one REQUEST_ERROR every request kind shares.
3056 ///
3057 /// # Errors
3058 ///
3059 /// [`EndpointError::UnknownRequest`] when no PUBLISH arrived under that
3060 /// id, and the publish flow's own `InvalidTransition` for a second answer:
3061 /// Section 5.1 says "A subscriber MUST send exactly one PUBLISH_OK or
3062 /// REQUEST_ERROR in response to a PUBLISH."
3063 pub fn send_publish_error(
3064 &mut self,
3065 request_id: VarInt,
3066 error_code: VarInt,
3067 reason_phrase: Vec<u8>,
3068 ) -> Result<ControlMessage, EndpointError> {
3069 let id = request_id.into_inner();
3070 let mut sm = self.publish_flow(id).ok_or(EndpointError::UnknownRequest(id))?;
3071 sm.on_publish_error_sent()?;
3072 Ok(ControlMessage::RequestError(RequestError { request_id, error_code, reason_phrase }))
3073 }
3074
3075 /// Hold a Request ID a peer allocated to the rules Section 9.1 states
3076 /// about it.
3077 ///
3078 /// "The client's Request ID starts at 0 and are even and the server's
3079 /// Request ID starts at 1 and are odd. The Request ID increments by 2 ...
3080 /// If an endpoint receives a Request ID that is not valid for the peer, it
3081 /// MUST close the session with Invalid Request ID." Parity says which end
3082 /// may have chosen it; the ceiling this endpoint advertised says how far
3083 /// the peer may go.
3084 ///
3085 /// This is the call site [`Self::validate_peer_request_id`] did not have.
3086 /// The rule was implemented and then applied to nothing, so a peer could
3087 /// open requests with ids from this endpoint's own half of the space, or
3088 /// past the ceiling it had advertised, and neither was noticed.
3089 ///
3090 /// A message that is a response rather than a request carries the id of a
3091 /// request this endpoint made, so it is not checked here - it is checked by
3092 /// finding the state machine it names.
3093 ///
3094 /// # The list below is the rule, not a convenience
3095 ///
3096 /// Every message named here spends one of the peer's Request IDs, and
3097 /// nothing else does. That makes the list load-bearing in a way it was not
3098 /// before the sequence was tracked: a request left out of it spends an ID
3099 /// this endpoint never counts, so the peer's **next** request looks like a
3100 /// skip and a conforming session is closed over it. Section 9.1 names
3101 /// the set, and SUBSCRIBE_UPDATE is in it - it carries a Request ID of its own,
3102 /// alongside the separate field naming the request it modifies.
3103 ///
3104 /// # Errors
3105 ///
3106 /// The request-id errors, for a wrong parity, an id at or above the
3107 /// advertised ceiling, or one that is not the next in the peer's sequence.
3108 pub fn receive_request(&mut self, msg: &ControlMessage) -> Result<(), EndpointError> {
3109 let request_id = match msg {
3110 ControlMessage::Subscribe(m) => m.request_id,
3111 ControlMessage::Fetch(m) => m.request_id,
3112 ControlMessage::Publish(m) => m.request_id,
3113 ControlMessage::PublishNamespace(m) => m.request_id,
3114 ControlMessage::SubscribeNamespace(m) => m.request_id,
3115 ControlMessage::TrackStatus(m) => m.request_id,
3116 ControlMessage::SubscribeUpdate(m) => m.request_id,
3117 _ => return Ok(()),
3118 };
3119 self.validate_peer_request_id(request_id.into_inner())
3120 }
3121
3122 // -- Unified message dispatch -----------------------------------
3123
3124 /// Dispatch an incoming control message to the appropriate handler.
3125 pub fn receive_message(&mut self, msg: ControlMessage) -> Result<(), EndpointError> {
3126 self.receive_request(&msg)?;
3127 match msg {
3128 ControlMessage::GoAway(ref m) => self.receive_goaway(m),
3129 ControlMessage::MaxRequestId(ref m) => self.receive_max_request_id(m),
3130 ControlMessage::RequestsBlocked(ref m) => self.receive_requests_blocked(m),
3131 ControlMessage::SubscribeOk(ref m) => self.receive_subscribe_ok(m),
3132 ControlMessage::SubscribeUpdate(ref m) => self.receive_subscribe_update(m),
3133 ControlMessage::Subscribe(ref m) => self.receive_subscribe(m),
3134 ControlMessage::Unsubscribe(ref m) => self.receive_unsubscribe(m),
3135 ControlMessage::Fetch(ref m) => self.receive_fetch(m),
3136 ControlMessage::FetchCancel(ref m) => self.receive_fetch_cancel(m),
3137 ControlMessage::Publish(ref m) => self.receive_publish(m),
3138 ControlMessage::PublishDone(ref m) => self.receive_publish_done(m),
3139 ControlMessage::PublishOk(ref m) => self.receive_publish_ok(m),
3140 ControlMessage::FetchOk(ref m) => self.receive_fetch_ok(m),
3141 ControlMessage::RequestOk(ref m) => self.receive_request_ok(m),
3142 ControlMessage::RequestError(ref m) => self.receive_request_error(m),
3143 ControlMessage::TrackStatus(ref m) => self.receive_track_status(m),
3144 ControlMessage::PublishNamespaceDone(ref m) => self.receive_publish_namespace_done(m),
3145 ControlMessage::PublishNamespace(ref m) => self.receive_publish_namespace(m),
3146 ControlMessage::PublishNamespaceCancel(ref m) => {
3147 self.receive_publish_namespace_cancel(m)
3148 }
3149 ControlMessage::SubscribeNamespace(ref m) => self.receive_subscribe_namespace(m),
3150 ControlMessage::UnsubscribeNamespace(ref m) => self.receive_unsubscribe_namespace(m),
3151 _ => Ok(()),
3152 }
3153 }
3154}
3155
3156impl PublishStateMachine {
3157 /// Idle -> Publishing (PUBLISH received from the peer).
3158 pub fn on_publish_received(&mut self) -> Result<(), PublishFlowError> {
3159 self.on_publish_sent().map_err(|_| PublishFlowError::InvalidTransition {
3160 from: self.state(),
3161 event: "on_publish_received".to_string(),
3162 })
3163 }
3164
3165 /// Publishing -> Active (PUBLISH_OK sent to the offering peer).
3166 pub fn on_publish_ok_sent(&mut self) -> Result<(), PublishFlowError> {
3167 self.on_publish_ok().map_err(|_| PublishFlowError::InvalidTransition {
3168 from: self.state(),
3169 event: "on_publish_ok_sent".to_string(),
3170 })
3171 }
3172
3173 /// Publishing -> Done (REQUEST_ERROR sent to the offering peer).
3174 pub fn on_publish_error_sent(&mut self) -> Result<(), PublishFlowError> {
3175 self.on_publish_error().map_err(|_| PublishFlowError::InvalidTransition {
3176 from: self.state(),
3177 event: "on_publish_error_sent".to_string(),
3178 })
3179 }
3180
3181 /// Active -> Done (PUBLISH_DONE received from the offering peer).
3182 pub fn on_publish_done_received(&mut self) -> Result<(), PublishFlowError> {
3183 self.on_publish_done_sent().map_err(|_| PublishFlowError::InvalidTransition {
3184 from: self.state(),
3185 event: "on_publish_done_received".to_string(),
3186 })
3187 }
3188
3189 /// Active -> Done (UNSUBSCRIBE sent to the offering peer).
3190 ///
3191 /// The same transition as the one above and a separate name, because a
3192 /// refusal has to say which of the two events was refused.
3193 pub fn on_unsubscribe_sent(&mut self) -> Result<(), PublishFlowError> {
3194 self.on_publish_done_sent().map_err(|_| PublishFlowError::InvalidTransition {
3195 from: self.state(),
3196 event: "on_unsubscribe_sent".to_string(),
3197 })
3198 }
3199
3200 /// Active -> Done (UNSUBSCRIBE received from the subscribing peer).
3201 ///
3202 /// The mirror of the one above, on an offer this endpoint made rather than
3203 /// one it took. Both directions are held in this one map, so the event
3204 /// name is the only thing that says which of them was refused.
3205 pub fn on_unsubscribe_received(&mut self) -> Result<(), PublishFlowError> {
3206 self.on_publish_done_sent().map_err(|_| PublishFlowError::InvalidTransition {
3207 from: self.state(),
3208 event: "on_unsubscribe_received".to_string(),
3209 })
3210 }
3211}