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