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