Skip to main content

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}