moqtap_client/draft18/fetch.rs
1/// Fetch lifecycle states.
2///
3/// A fetch settles two things and settles them independently: the publisher's
4/// answer, which arrives on the control stream, and the response stream that
5/// carries the objects. Section 10.13: "A publisher MAY send Objects in
6/// response to a FETCH before the FETCH_OK message is sent, but the FETCH_OK
7/// MUST NOT be sent until the End Location is known."
8///
9/// Section 10.12.3 states the other direction of the same freedom: "The
10/// FETCH_OK or REQUEST_ERROR can come at any time relative to object delivery."
11/// So the fetch is over when both have settled, in whichever order they settle.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum FetchState {
14 /// Initial state before any FETCH message is sent.
15 Idle,
16 /// FETCH has been sent; the answer is owed and the response stream
17 /// has not ended.
18 Pending,
19 /// FETCH_OK received; data is being received on the stream.
20 Receiving,
21 /// The response stream has ended and the answer is still owed.
22 Unanswered,
23 /// Fetch has ended (error, cancel, FIN, or reset).
24 Done,
25}
26
27/// Errors that can occur during fetch state transitions.
28#[derive(Debug, thiserror::Error, PartialEq, Eq)]
29pub enum FetchError {
30 /// An event was received that is not valid for the current state.
31 #[error("invalid transition from {from:?} on event {event}")]
32 InvalidTransition {
33 /// The state the machine was in when the invalid event arrived.
34 from: FetchState,
35 /// The name of the event that was rejected.
36 event: String,
37 },
38}
39
40/// Pure state machine for a MoQT fetch request.
41/// Transitions: Idle -> Pending -> Receiving -> Done when the answer comes
42/// first, and Idle -> Pending -> Unanswered -> Done when the response stream
43/// ends first.
44pub struct FetchStateMachine {
45 state: FetchState,
46}
47
48impl Default for FetchStateMachine {
49 fn default() -> Self {
50 Self::new()
51 }
52}
53
54impl FetchStateMachine {
55 /// Creates a new state machine in the [`FetchState::Idle`] state.
56 pub fn new() -> Self {
57 Self { state: FetchState::Idle }
58 }
59
60 /// Returns the current state of the fetch request.
61 pub fn state(&self) -> FetchState {
62 self.state
63 }
64
65 /// Idle -> Pending (FETCH sent).
66 pub fn on_fetch_sent(&mut self) -> Result<(), FetchError> {
67 match self.state {
68 FetchState::Idle => {
69 self.state = FetchState::Pending;
70 Ok(())
71 }
72 _ => Err(FetchError::InvalidTransition {
73 from: self.state,
74 event: "on_fetch_sent".to_string(),
75 }),
76 }
77 }
78
79 /// Pending -> Receiving, Unanswered -> Done (FETCH_OK received).
80 ///
81 /// From Unanswered the objects have already been delivered, so the FETCH_OK
82 /// describing them is the last thing the fetch was waiting for.
83 pub fn on_fetch_ok(&mut self) -> Result<(), FetchError> {
84 match self.state {
85 FetchState::Pending => {
86 self.state = FetchState::Receiving;
87 Ok(())
88 }
89 FetchState::Unanswered => {
90 self.state = FetchState::Done;
91 Ok(())
92 }
93 _ => Err(FetchError::InvalidTransition {
94 from: self.state,
95 event: "on_fetch_ok".to_string(),
96 }),
97 }
98 }
99
100 /// Pending | Unanswered -> Done (REQUEST_ERROR received).
101 pub fn on_fetch_error(&mut self) -> Result<(), FetchError> {
102 match self.state {
103 FetchState::Pending | FetchState::Unanswered => {
104 self.state = FetchState::Done;
105 Ok(())
106 }
107 _ => Err(FetchError::InvalidTransition {
108 from: self.state,
109 event: "on_fetch_error".to_string(),
110 }),
111 }
112 }
113
114 /// Pending | Receiving | Unanswered -> Done, Done -> Done (this fetch's
115 /// request stream was cancelled).
116 ///
117 /// This draft carries no FETCH_CANCEL message. Section 3.3.2: "Once a request
118 /// stream has been opened, the request MAY be cancelled by either endpoint."
119 ///
120 /// `Idle` is refused, on the other half of the same sentence: nothing has
121 /// been written, so there is no stream to terminate. `Done` stays `Done` —
122 /// nothing finishes a request stream's send half on the ordinary path, so a
123 /// caller that walks away from a request that has already ended still
124 /// resets the stream, and that reset is an ordinary end rather than a
125 /// fault.
126 pub fn on_request_cancelled(&mut self) -> Result<(), FetchError> {
127 match self.state {
128 FetchState::Pending | FetchState::Receiving | FetchState::Unanswered => {
129 self.state = FetchState::Done;
130 Ok(())
131 }
132 FetchState::Done => Ok(()),
133 FetchState::Idle => Err(FetchError::InvalidTransition {
134 from: self.state,
135 event: "on_request_cancelled".to_string(),
136 }),
137 }
138 }
139
140 /// Receiving -> Done, Pending -> Unanswered (stream FIN received).
141 ///
142 /// A fetch that has already ended ignores the close of its response stream,
143 /// whichever of QUIC's two endings it is. Section 10.12.3: a relay whose
144 /// upstream FETCH failed "sends a REQUEST_ERROR and can reset the
145 /// unidirectional stream", and may "wait until the cached objects have been
146 /// delivered before resetting the stream", so that close arrives after the
147 /// error ended the fetch. This draft has no FETCH_CANCEL message; Section
148 /// 3.3.2 cancels a request by terminating the directions of its stream,
149 /// which reaches this machine the same way.
150 pub fn on_stream_fin(&mut self) -> Result<(), FetchError> {
151 match self.state {
152 FetchState::Receiving => {
153 self.state = FetchState::Done;
154 Ok(())
155 }
156 FetchState::Pending => {
157 self.state = FetchState::Unanswered;
158 Ok(())
159 }
160 FetchState::Done => Ok(()),
161 _ => Err(FetchError::InvalidTransition {
162 from: self.state,
163 event: "on_stream_fin".to_string(),
164 }),
165 }
166 }
167
168 /// Receiving -> Done, Pending -> Unanswered (stream RESET received).
169 ///
170 /// The same tolerance of a late close as
171 /// [`FetchStateMachine::on_stream_fin`], for the same reason.
172 pub fn on_stream_reset(&mut self) -> Result<(), FetchError> {
173 match self.state {
174 FetchState::Receiving => {
175 self.state = FetchState::Done;
176 Ok(())
177 }
178 FetchState::Pending => {
179 self.state = FetchState::Unanswered;
180 Ok(())
181 }
182 FetchState::Done => Ok(()),
183 _ => Err(FetchError::InvalidTransition {
184 from: self.state,
185 event: "on_stream_reset".to_string(),
186 }),
187 }
188 }
189}