moqtap_client/draft12/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 8.17: "A publisher MAY send Objects in response
6/// to a FETCH before the FETCH_OK message is sent, but the FETCH_OK MUST NOT be
7/// sent until the End Location is known."
8///
9/// Section 8.16 states the other direction of the same freedom: "The FETCH_OK
10/// or FETCH_ERROR can come at any time relative to object delivery." So the
11/// 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 first,
42/// and Idle → Pending → Unanswered → Done when the response stream ends first.
43pub struct FetchStateMachine {
44 state: FetchState,
45}
46
47impl Default for FetchStateMachine {
48 fn default() -> Self {
49 Self::new()
50 }
51}
52
53impl FetchStateMachine {
54 /// Creates a new state machine in the [`FetchState::Idle`] state.
55 pub fn new() -> Self {
56 Self { state: FetchState::Idle }
57 }
58
59 /// Returns the current state of the fetch request.
60 pub fn state(&self) -> FetchState {
61 self.state
62 }
63
64 /// Idle → Pending (FETCH sent).
65 pub fn on_fetch_sent(&mut self) -> Result<(), FetchError> {
66 match self.state {
67 FetchState::Idle => {
68 self.state = FetchState::Pending;
69 Ok(())
70 }
71 _ => Err(FetchError::InvalidTransition {
72 from: self.state,
73 event: "on_fetch_sent".to_string(),
74 }),
75 }
76 }
77
78 /// Pending → Receiving, Unanswered → Done (FETCH_OK received).
79 ///
80 /// From Unanswered the objects have already been delivered, so the FETCH_OK
81 /// describing them is the last thing the fetch was waiting for.
82 pub fn on_fetch_ok(&mut self) -> Result<(), FetchError> {
83 match self.state {
84 FetchState::Pending => {
85 self.state = FetchState::Receiving;
86 Ok(())
87 }
88 FetchState::Unanswered => {
89 self.state = FetchState::Done;
90 Ok(())
91 }
92 _ => Err(FetchError::InvalidTransition {
93 from: self.state,
94 event: "on_fetch_ok".to_string(),
95 }),
96 }
97 }
98
99 /// Pending | Unanswered → Done (FETCH_ERROR received).
100 pub fn on_fetch_error(&mut self) -> Result<(), FetchError> {
101 match self.state {
102 FetchState::Pending | FetchState::Unanswered => {
103 self.state = FetchState::Done;
104 Ok(())
105 }
106 _ => Err(FetchError::InvalidTransition {
107 from: self.state,
108 event: "on_fetch_error".to_string(),
109 }),
110 }
111 }
112
113 /// Pending | Receiving | Unanswered → Done (FETCH_CANCEL sent).
114 pub fn on_fetch_cancel(&mut self) -> Result<(), FetchError> {
115 match self.state {
116 FetchState::Pending | FetchState::Receiving | FetchState::Unanswered => {
117 self.state = FetchState::Done;
118 Ok(())
119 }
120 _ => Err(FetchError::InvalidTransition {
121 from: self.state,
122 event: "on_fetch_cancel".to_string(),
123 }),
124 }
125 }
126
127 /// Receiving → Done, Pending → Unanswered (stream FIN received).
128 ///
129 /// A fetch that has already ended ignores the close of its response stream,
130 /// whichever of QUIC's two endings it is, and both of the ways a fetch ends
131 /// early leave one behind. Section 8.19: the publisher of a cancelled fetch
132 /// "SHOULD promptly close the unidirectional stream, even if it is in the
133 /// middle of delivering an object". Section 8.16: a relay whose upstream
134 /// FETCH failed "sends a FETCH_ERROR and can reset the unidirectional
135 /// stream", and may "wait until the cached objects have been delivered
136 /// before resetting the stream".
137 pub fn on_stream_fin(&mut self) -> Result<(), FetchError> {
138 match self.state {
139 FetchState::Receiving => {
140 self.state = FetchState::Done;
141 Ok(())
142 }
143 FetchState::Pending => {
144 self.state = FetchState::Unanswered;
145 Ok(())
146 }
147 FetchState::Done => Ok(()),
148 _ => Err(FetchError::InvalidTransition {
149 from: self.state,
150 event: "on_stream_fin".to_string(),
151 }),
152 }
153 }
154
155 /// Receiving → Done, Pending → Unanswered (stream RESET received).
156 ///
157 /// The same tolerance of a late close as
158 /// [`FetchStateMachine::on_stream_fin`], for the same reason.
159 pub fn on_stream_reset(&mut self) -> Result<(), FetchError> {
160 match self.state {
161 FetchState::Receiving => {
162 self.state = FetchState::Done;
163 Ok(())
164 }
165 FetchState::Pending => {
166 self.state = FetchState::Unanswered;
167 Ok(())
168 }
169 FetchState::Done => Ok(()),
170 _ => Err(FetchError::InvalidTransition {
171 from: self.state,
172 event: "on_stream_reset".to_string(),
173 }),
174 }
175 }
176}
177
178/// The same transitions, named for the end that serves the fetch.
179///
180/// A fetch this endpoint answers passes through the states in the same order
181/// as one it makes, with every message going the other way: the FETCH arrives
182/// instead of leaving, the answer leaves instead of arriving. Sharing the
183/// transitions and not the names is what lets a refusal say which event was
184/// refused, rather than naming the mirror image of it.
185impl FetchStateMachine {
186 /// Idle -> Pending (FETCH received from the peer).
187 pub fn on_fetch_received(&mut self) -> Result<(), FetchError> {
188 self.on_fetch_sent().map_err(|_| FetchError::InvalidTransition {
189 from: self.state(),
190 event: "on_fetch_received".to_string(),
191 })
192 }
193
194 /// Pending -> Receiving, Unanswered -> Done (FETCH_OK sent).
195 ///
196 /// The state is named for the requester's view; for the end answering, the
197 /// same node means the objects are being served rather than received. It is
198 /// the same node in the graph, with the same edges, so it keeps its name.
199 pub fn on_fetch_ok_sent(&mut self) -> Result<(), FetchError> {
200 self.on_fetch_ok().map_err(|_| FetchError::InvalidTransition {
201 from: self.state(),
202 event: "on_fetch_ok_sent".to_string(),
203 })
204 }
205
206 /// Pending | Unanswered -> Done (FETCH_ERROR sent).
207 pub fn on_fetch_error_sent(&mut self) -> Result<(), FetchError> {
208 self.on_fetch_error().map_err(|_| FetchError::InvalidTransition {
209 from: self.state(),
210 event: "on_fetch_error_sent".to_string(),
211 })
212 }
213
214 /// Pending | Receiving | Unanswered -> Done (FETCH_CANCEL received).
215 pub fn on_fetch_cancel_received(&mut self) -> Result<(), FetchError> {
216 self.on_fetch_cancel().map_err(|_| FetchError::InvalidTransition {
217 from: self.state(),
218 event: "on_fetch_cancel_received".to_string(),
219 })
220 }
221
222 /// Receiving -> Done, Pending -> Unanswered (this endpoint finished the
223 /// fetch data stream).
224 pub fn on_stream_fin_sent(&mut self) -> Result<(), FetchError> {
225 self.on_stream_fin().map_err(|_| FetchError::InvalidTransition {
226 from: self.state(),
227 event: "on_stream_fin_sent".to_string(),
228 })
229 }
230}