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