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