moqtap_client/draft09/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 7.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 latest group and object are known."
8///
9/// Section 7.7 says what that freedom is for: a relay "MAY start sending
10/// objects immediately in response to a FETCH, even if sending the FETCH_OK
11/// takes longer because it requires going upstream to populate the latest
12/// object." So the fetch is over when both have settled, in whichever order
13/// they settle.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum FetchState {
16 /// Initial state before any FETCH message is sent.
17 Idle,
18 /// FETCH has been sent; the answer is owed and the response stream
19 /// has not ended.
20 Pending,
21 /// FETCH_OK received; data is being received on the stream.
22 Receiving,
23 /// The response stream has ended and the answer is still owed.
24 Unanswered,
25 /// Fetch has ended (error, cancel, FIN, or reset).
26 Done,
27}
28
29/// Errors that can occur during fetch state transitions.
30#[derive(Debug, thiserror::Error, PartialEq, Eq)]
31pub enum FetchError {
32 /// An event was received that is not valid for the current state.
33 #[error("invalid transition from {from:?} on event {event}")]
34 InvalidTransition {
35 /// The state the machine was in when the invalid event arrived.
36 from: FetchState,
37 /// The name of the event that was rejected.
38 event: String,
39 },
40}
41
42/// Pure state machine for a MoQT fetch request.
43/// Transitions: Idle → Pending → Receiving → Done when the answer comes first,
44/// and Idle → Pending → Unanswered → Done when the response stream ends first.
45pub struct FetchStateMachine {
46 state: FetchState,
47}
48
49impl Default for FetchStateMachine {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55impl FetchStateMachine {
56 /// Creates a new state machine in the [`FetchState::Idle`] state.
57 pub fn new() -> Self {
58 Self { state: FetchState::Idle }
59 }
60
61 /// Returns the current state of the fetch request.
62 pub fn state(&self) -> FetchState {
63 self.state
64 }
65
66 /// Idle → Pending (FETCH sent).
67 pub fn on_fetch_sent(&mut self) -> Result<(), FetchError> {
68 match self.state {
69 FetchState::Idle => {
70 self.state = FetchState::Pending;
71 Ok(())
72 }
73 _ => Err(FetchError::InvalidTransition {
74 from: self.state,
75 event: "on_fetch_sent".to_string(),
76 }),
77 }
78 }
79
80 /// Pending → Receiving, Unanswered → Done (FETCH_OK received).
81 ///
82 /// From Unanswered the objects have already been delivered, so the FETCH_OK
83 /// describing them is the last thing the fetch was waiting for.
84 pub fn on_fetch_ok(&mut self) -> Result<(), FetchError> {
85 match self.state {
86 FetchState::Pending => {
87 self.state = FetchState::Receiving;
88 Ok(())
89 }
90 FetchState::Unanswered => {
91 self.state = FetchState::Done;
92 Ok(())
93 }
94 _ => Err(FetchError::InvalidTransition {
95 from: self.state,
96 event: "on_fetch_ok".to_string(),
97 }),
98 }
99 }
100
101 /// Pending | Unanswered → Done (FETCH_ERROR received).
102 pub fn on_fetch_error(&mut self) -> Result<(), FetchError> {
103 match self.state {
104 FetchState::Pending | FetchState::Unanswered => {
105 self.state = FetchState::Done;
106 Ok(())
107 }
108 _ => Err(FetchError::InvalidTransition {
109 from: self.state,
110 event: "on_fetch_error".to_string(),
111 }),
112 }
113 }
114
115 /// Pending | Receiving | Unanswered → Done (FETCH_CANCEL sent).
116 pub fn on_fetch_cancel(&mut self) -> Result<(), FetchError> {
117 match self.state {
118 FetchState::Pending | FetchState::Receiving | FetchState::Unanswered => {
119 self.state = FetchState::Done;
120 Ok(())
121 }
122 _ => Err(FetchError::InvalidTransition {
123 from: self.state,
124 event: "on_fetch_cancel".to_string(),
125 }),
126 }
127 }
128
129 /// Receiving → Done, Pending → Unanswered (stream FIN received).
130 ///
131 /// A fetch that has already ended ignores the close of its response stream,
132 /// whichever of QUIC's two endings it is. Section 7.8: the publisher of a
133 /// cancelled fetch "SHOULD close the unidirectional stream as soon as
134 /// possible", and the cancel is what ended the fetch, so that close arrives
135 /// afterwards.
136 pub fn on_stream_fin(&mut self) -> Result<(), FetchError> {
137 match self.state {
138 FetchState::Receiving => {
139 self.state = FetchState::Done;
140 Ok(())
141 }
142 FetchState::Pending => {
143 self.state = FetchState::Unanswered;
144 Ok(())
145 }
146 FetchState::Done => Ok(()),
147 _ => Err(FetchError::InvalidTransition {
148 from: self.state,
149 event: "on_stream_fin".to_string(),
150 }),
151 }
152 }
153
154 /// Receiving → Done, Pending → Unanswered (stream RESET received).
155 ///
156 /// The same tolerance of a late close as
157 /// [`FetchStateMachine::on_stream_fin`], for the same reason.
158 pub fn on_stream_reset(&mut self) -> Result<(), FetchError> {
159 match self.state {
160 FetchState::Receiving => {
161 self.state = FetchState::Done;
162 Ok(())
163 }
164 FetchState::Pending => {
165 self.state = FetchState::Unanswered;
166 Ok(())
167 }
168 FetchState::Done => Ok(()),
169 _ => Err(FetchError::InvalidTransition {
170 from: self.state,
171 event: "on_stream_reset".to_string(),
172 }),
173 }
174 }
175}
176
177/// The same transitions, named for the end that serves the fetch.
178///
179/// A fetch this endpoint answers passes through the states in the same order
180/// as one it makes, with every message going the other way: the FETCH arrives
181/// instead of leaving, the answer leaves instead of arriving. Sharing the
182/// transitions and not the names is what lets a refusal say which event was
183/// refused, rather than naming the mirror image of it.
184impl FetchStateMachine {
185 /// Idle -> Pending (FETCH received from the peer).
186 pub fn on_fetch_received(&mut self) -> Result<(), FetchError> {
187 self.on_fetch_sent().map_err(|_| FetchError::InvalidTransition {
188 from: self.state(),
189 event: "on_fetch_received".to_string(),
190 })
191 }
192
193 /// Pending -> Receiving, Unanswered -> Done (FETCH_OK sent).
194 ///
195 /// The state is named for the requester's view; for the end answering, the
196 /// same node means the objects are being served rather than received. It is
197 /// the same node in the graph, with the same edges, so it keeps its name.
198 pub fn on_fetch_ok_sent(&mut self) -> Result<(), FetchError> {
199 self.on_fetch_ok().map_err(|_| FetchError::InvalidTransition {
200 from: self.state(),
201 event: "on_fetch_ok_sent".to_string(),
202 })
203 }
204
205 /// Pending | Unanswered -> Done (FETCH_ERROR sent).
206 pub fn on_fetch_error_sent(&mut self) -> Result<(), FetchError> {
207 self.on_fetch_error().map_err(|_| FetchError::InvalidTransition {
208 from: self.state(),
209 event: "on_fetch_error_sent".to_string(),
210 })
211 }
212
213 /// Pending | Receiving | Unanswered -> Done (FETCH_CANCEL received).
214 pub fn on_fetch_cancel_received(&mut self) -> Result<(), FetchError> {
215 self.on_fetch_cancel().map_err(|_| FetchError::InvalidTransition {
216 from: self.state(),
217 event: "on_fetch_cancel_received".to_string(),
218 })
219 }
220
221 /// Receiving -> Done, Pending -> Unanswered (this endpoint finished the
222 /// fetch data stream).
223 pub fn on_stream_fin_sent(&mut self) -> Result<(), FetchError> {
224 self.on_stream_fin().map_err(|_| FetchError::InvalidTransition {
225 from: self.state(),
226 event: "on_stream_fin_sent".to_string(),
227 })
228 }
229}