1use crate::{
10 app_set::{AppSet, AppSetExt as _},
11 async_generator,
12 configuration::Config,
13 cup_ecdsa::Cupv2Handler,
14 http_request::HttpRequest,
15 installer::{Installer, Plan},
16 metrics::MetricsReporter,
17 policy::PolicyEngine,
18 request_builder::RequestParams,
19 state_machine::{update_check, ControlHandle, StateMachine, StateMachineEvent},
20 storage::Storage,
21 time::Timer,
22};
23use futures::{channel::mpsc, lock::Mutex, prelude::*};
24use std::rc::Rc;
25
26#[cfg(test)]
27use crate::{
28 app_set::VecAppSet,
29 common::App,
30 cup_ecdsa::test_support::MockCupv2Handler,
31 http_request::StubHttpRequest,
32 installer::stub::{StubInstaller, StubPlan},
33 metrics::StubMetricsReporter,
34 policy::StubPolicyEngine,
35 state_machine::{RebootAfterUpdate, UpdateCheckError},
36 storage::StubStorage,
37 time::{timers::StubTimer, MockTimeSource},
38};
39
40#[derive(Debug)]
42pub struct StateMachineBuilder<PE, HR, IN, TM, MR, ST, AS, CH>
43where
44 PE: PolicyEngine,
45 HR: HttpRequest,
46 IN: Installer,
47 TM: Timer,
48 MR: MetricsReporter,
49 ST: Storage,
50 AS: AppSet,
51 CH: Cupv2Handler,
52{
53 policy_engine: PE,
54 http: HR,
55 installer: IN,
56 timer: TM,
57 metrics_reporter: MR,
58 storage: Rc<Mutex<ST>>,
59 config: Config,
60 app_set: Rc<Mutex<AS>>,
61 cup_handler: Option<CH>,
62}
63
64impl<'a, PE, HR, IN, TM, MR, ST, AS, CH> StateMachineBuilder<PE, HR, IN, TM, MR, ST, AS, CH>
65where
66 PE: 'a + PolicyEngine,
67 HR: 'a + HttpRequest,
68 IN: 'a + Installer,
69 TM: 'a + Timer,
70 MR: 'a + MetricsReporter,
71 ST: 'a + Storage,
72 AS: 'a + AppSet,
73 CH: 'a + Cupv2Handler,
74{
75 #[allow(clippy::too_many_arguments)]
77 pub fn new(
78 policy_engine: PE,
79 http: HR,
80 installer: IN,
81 timer: TM,
82 metrics_reporter: MR,
83 storage: Rc<Mutex<ST>>,
84 config: Config,
85 app_set: Rc<Mutex<AS>>,
86 cup_handler: Option<CH>,
87 ) -> Self {
88 Self {
89 policy_engine,
90 http,
91 installer,
92 timer,
93 metrics_reporter,
94 storage,
95 config,
96 app_set,
97 cup_handler,
98 }
99 }
100}
101
102impl<'a, PE, HR, IN, TM, MR, ST, AS, CH> StateMachineBuilder<PE, HR, IN, TM, MR, ST, AS, CH>
103where
104 PE: 'a + PolicyEngine,
105 HR: 'a + HttpRequest,
106 IN: 'a + Installer,
107 TM: 'a + Timer,
108 MR: 'a + MetricsReporter,
109 ST: 'a + Storage,
110 AS: 'a + AppSet,
111 CH: 'a + Cupv2Handler,
112{
113 pub fn policy_engine<PE2: 'a + PolicyEngine>(
115 self,
116 policy_engine: PE2,
117 ) -> StateMachineBuilder<PE2, HR, IN, TM, MR, ST, AS, CH> {
118 StateMachineBuilder {
119 policy_engine,
120 http: self.http,
121 installer: self.installer,
122 timer: self.timer,
123 metrics_reporter: self.metrics_reporter,
124 storage: self.storage,
125 config: self.config,
126 app_set: self.app_set,
127 cup_handler: self.cup_handler,
128 }
129 }
130
131 pub fn http<HR2: 'a + HttpRequest>(
133 self,
134 http: HR2,
135 ) -> StateMachineBuilder<PE, HR2, IN, TM, MR, ST, AS, CH> {
136 StateMachineBuilder {
137 policy_engine: self.policy_engine,
138 http,
139 installer: self.installer,
140 timer: self.timer,
141 metrics_reporter: self.metrics_reporter,
142 storage: self.storage,
143 config: self.config,
144 app_set: self.app_set,
145 cup_handler: self.cup_handler,
146 }
147 }
148
149 pub fn installer<IN2: 'a + Installer>(
151 self,
152 installer: IN2,
153 ) -> StateMachineBuilder<PE, HR, IN2, TM, MR, ST, AS, CH> {
154 StateMachineBuilder {
155 policy_engine: self.policy_engine,
156 http: self.http,
157 installer,
158 timer: self.timer,
159 metrics_reporter: self.metrics_reporter,
160 storage: self.storage,
161 config: self.config,
162 app_set: self.app_set,
163 cup_handler: self.cup_handler,
164 }
165 }
166
167 pub fn timer<TM2: 'a + Timer>(
169 self,
170 timer: TM2,
171 ) -> StateMachineBuilder<PE, HR, IN, TM2, MR, ST, AS, CH> {
172 StateMachineBuilder {
173 policy_engine: self.policy_engine,
174 http: self.http,
175 installer: self.installer,
176 timer,
177 metrics_reporter: self.metrics_reporter,
178 storage: self.storage,
179 config: self.config,
180 app_set: self.app_set,
181 cup_handler: self.cup_handler,
182 }
183 }
184
185 pub fn metrics_reporter<MR2: 'a + MetricsReporter>(
187 self,
188 metrics_reporter: MR2,
189 ) -> StateMachineBuilder<PE, HR, IN, TM, MR2, ST, AS, CH> {
190 StateMachineBuilder {
191 policy_engine: self.policy_engine,
192 http: self.http,
193 installer: self.installer,
194 timer: self.timer,
195 metrics_reporter,
196 storage: self.storage,
197 config: self.config,
198 app_set: self.app_set,
199 cup_handler: self.cup_handler,
200 }
201 }
202
203 pub fn storage<ST2: 'a + Storage>(
205 self,
206 storage: Rc<Mutex<ST2>>,
207 ) -> StateMachineBuilder<PE, HR, IN, TM, MR, ST2, AS, CH> {
208 StateMachineBuilder {
209 policy_engine: self.policy_engine,
210 http: self.http,
211 installer: self.installer,
212 timer: self.timer,
213 metrics_reporter: self.metrics_reporter,
214 storage,
215 config: self.config,
216 app_set: self.app_set,
217 cup_handler: self.cup_handler,
218 }
219 }
220
221 pub fn config(mut self, config: Config) -> Self {
223 self.config = config;
224 self
225 }
226
227 pub fn app_set<AS2: 'a + AppSet>(
229 self,
230 app_set: Rc<Mutex<AS2>>,
231 ) -> StateMachineBuilder<PE, HR, IN, TM, MR, ST, AS2, CH> {
232 StateMachineBuilder {
233 policy_engine: self.policy_engine,
234 http: self.http,
235 installer: self.installer,
236 timer: self.timer,
237 metrics_reporter: self.metrics_reporter,
238 storage: self.storage,
239 config: self.config,
240 app_set,
241 cup_handler: self.cup_handler,
242 }
243 }
244
245 pub fn cup_handler<CH2: 'a + Cupv2Handler>(
246 self,
247 cup_handler: Option<CH2>,
248 ) -> StateMachineBuilder<PE, HR, IN, TM, MR, ST, AS, CH2> {
249 StateMachineBuilder {
250 policy_engine: self.policy_engine,
251 http: self.http,
252 installer: self.installer,
253 timer: self.timer,
254 metrics_reporter: self.metrics_reporter,
255 storage: self.storage,
256 config: self.config,
257 app_set: self.app_set,
258 cup_handler,
259 }
260 }
261}
262
263impl<'a, PE, HR, IN, TM, MR, ST, AS, CH, IR, PL> StateMachineBuilder<PE, HR, IN, TM, MR, ST, AS, CH>
264where
265 PE: 'a + PolicyEngine<InstallResult = IR, InstallPlan = PL>,
266 HR: 'a + HttpRequest,
267 IN: 'a + Installer<InstallResult = IR, InstallPlan = PL>,
268 TM: 'a + Timer,
269 MR: 'a + MetricsReporter,
270 ST: 'a + Storage,
271 AS: 'a + AppSet,
272 CH: 'a + Cupv2Handler,
273 IR: 'static + Send,
274 PL: 'a + Plan,
275{
276 pub async fn build(self) -> StateMachine<PE, HR, IN, TM, MR, ST, AS, CH> {
277 let StateMachineBuilder {
278 policy_engine,
279 http,
280 installer,
281 timer,
282 metrics_reporter,
283 storage,
284 config,
285 app_set,
286 cup_handler,
287 } = self;
288
289 let context = {
290 let storage = storage.lock().await;
291 let mut app_set = app_set.lock().await;
292 let ((), context) = futures::join!(
293 app_set.load(&*storage),
294 update_check::Context::load(&*storage)
295 );
296 tracing::info!("Omaha app set: {:?}", app_set.get_apps());
297 context
298 };
299
300 let time_source = policy_engine.time_source().clone();
301
302 StateMachine {
303 config,
304 policy_engine,
305 http,
306 installer,
307 timer,
308 time_source,
309 metrics_reporter,
310 storage_ref: storage,
311 context,
312 app_set,
313 cup_handler,
314 }
315 }
316
317 pub async fn start(self) -> (ControlHandle, impl Stream<Item = StateMachineEvent> + 'a) {
322 let state_machine = self.build().await;
323
324 let (send, recv) = mpsc::channel(0);
325 (
326 ControlHandle(send),
327 async_generator::generate(move |co| state_machine.run(recv, co)).into_yielded(),
328 )
329 }
330
331 pub async fn oneshot_check(self) -> impl Stream<Item = StateMachineEvent> + 'a {
333 let mut state_machine = self.build().await;
334 let request_params = RequestParams::default();
335
336 async_generator::generate(move |mut co| async move {
337 state_machine
338 .start_update_check(request_params, &mut co)
339 .await;
340 })
341 .into_yielded()
342 }
343
344 #[cfg(test)]
346 pub(super) async fn oneshot(
347 self,
348 request_params: RequestParams,
349 ) -> Result<(update_check::Response, RebootAfterUpdate<IR>), UpdateCheckError> {
350 self.build().await.oneshot(request_params).await
351 }
352}
353
354#[cfg(test)]
355impl
356 StateMachineBuilder<
357 StubPolicyEngine<StubPlan, MockTimeSource>,
358 StubHttpRequest,
359 StubInstaller,
360 StubTimer,
361 StubMetricsReporter,
362 StubStorage,
363 VecAppSet,
364 MockCupv2Handler,
365 >
366{
367 pub fn new_stub() -> Self {
369 let config = crate::configuration::test_support::config_generator();
370
371 let app_set = VecAppSet::new(vec![App::builder()
372 .id("{00000000-0000-0000-0000-000000000001}")
373 .version([1, 2, 3, 4])
374 .cohort(crate::protocol::Cohort::new("stable-channel"))
375 .build()]);
376 let mock_time = MockTimeSource::new_from_now();
377
378 StateMachineBuilder::new(
379 StubPolicyEngine::new(mock_time),
380 StubHttpRequest,
381 StubInstaller::default(),
382 StubTimer,
383 StubMetricsReporter,
384 Rc::new(Mutex::new(StubStorage)),
385 config,
386 Rc::new(Mutex::new(app_set)),
387 Some(MockCupv2Handler::new()),
388 )
389 }
390}