Skip to main content

omaha_client/
state_machine.rs

1// Copyright 2019 The Fuchsia Authors
2//
3// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
4// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
5// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
6// This file may not be copied, modified, or distributed except according to
7// those terms.
8
9use crate::{
10    app_set::{AppSet, AppSetExt as _},
11    async_generator,
12    common::{App, CheckOptions, CheckTiming},
13    configuration::Config,
14    cup_ecdsa::{CupDecorationError, CupVerificationError, Cupv2Handler, RequestMetadata},
15    http_request::{self, HttpRequest},
16    installer::{AppInstallResult, Installer, Plan},
17    metrics::{ClockType, Metrics, MetricsReporter, UpdateCheckFailureReason},
18    policy::{CheckDecision, PolicyEngine, UpdateDecision},
19    protocol::{
20        self,
21        request::{Event, EventErrorCode, EventResult, EventType, GUID, InstallSource},
22        response::{OmahaStatus, Response, UpdateCheck, parse_json_response},
23    },
24    request_builder::{self, RequestBuilder, RequestParams},
25    storage::{Storage, StorageExt},
26    time::{ComplexTime, PartialComplexTime, TimeSource, Timer},
27};
28
29use anyhow::anyhow;
30use futures::{
31    channel::{mpsc, oneshot},
32    future::{self, BoxFuture, Fuse},
33    lock::Mutex,
34    prelude::*,
35    select,
36};
37use http::{Response as HttpResponse, response::Parts};
38use log::{error, info, warn};
39use p256::ecdsa::DerSignature;
40use std::{
41    cmp::min,
42    collections::HashMap,
43    convert::TryInto,
44    rc::Rc,
45    str::Utf8Error,
46    time::{Duration, Instant, SystemTime},
47};
48use thiserror::Error;
49
50pub mod update_check;
51
52mod builder;
53pub use builder::StateMachineBuilder;
54
55mod observer;
56use observer::StateMachineProgressObserver;
57pub use observer::{InstallProgress, StateMachineEvent};
58
59const INSTALL_PLAN_ID: &str = "install_plan_id";
60const UPDATE_FIRST_SEEN_TIME: &str = "update_first_seen_time";
61const UPDATE_FINISH_TIME: &str = "update_finish_time";
62const TARGET_VERSION: &str = "target_version";
63const CONSECUTIVE_FAILED_INSTALL_ATTEMPTS: &str = "consecutive_failed_install_attempts";
64// How long do we wait after not allowed to reboot to check again.
65const CHECK_REBOOT_ALLOWED_INTERVAL: Duration = Duration::from_secs(30 * 60);
66// This header contains the number of seconds client must not contact server again.
67const X_RETRY_AFTER: &str = "X-Retry-After";
68// How many requests we will make to Omaha before giving up.
69const MAX_OMAHA_REQUEST_ATTEMPTS: u64 = 3;
70
71/// This is the core state machine for a client's update check.  It is instantiated and used to
72/// perform update checks over time or to perform a single update check process.
73#[derive(Debug)]
74pub struct StateMachine<PE, HR, IN, TM, MR, ST, AS, CH>
75where
76    PE: PolicyEngine,
77    HR: HttpRequest,
78    IN: Installer,
79    TM: Timer,
80    MR: MetricsReporter,
81    ST: Storage,
82    AS: AppSet,
83{
84    /// The immutable configuration of the client itself.
85    config: Config,
86
87    policy_engine: PE,
88
89    http: HR,
90
91    installer: IN,
92
93    timer: TM,
94
95    time_source: PE::TimeSource,
96
97    metrics_reporter: MR,
98
99    storage_ref: Rc<Mutex<ST>>,
100
101    /// Context for update check.
102    context: update_check::Context,
103
104    /// The list of apps used for update check.
105    /// When locking both storage and app_set, make sure to always lock storage first.
106    app_set: Rc<Mutex<AS>>,
107
108    cup_handler: Option<CH>,
109}
110
111#[derive(Copy, Clone, Debug, Eq, PartialEq)]
112pub enum State {
113    Idle,
114    CheckingForUpdates(InstallSource),
115    ErrorCheckingForUpdate,
116    NoUpdateAvailable,
117    InstallationDeferredByPolicy,
118    InstallingUpdate,
119    WaitingForReboot,
120    InstallationError,
121}
122
123/// This is the set of errors that can occur when making a request to Omaha.  This is an internal
124/// collection of error types.
125#[derive(Error, Debug)]
126pub enum OmahaRequestError {
127    #[error("Unexpected JSON error constructing update check")]
128    Json(#[from] serde_json::Error),
129
130    #[error("Error building update check HTTP request")]
131    HttpBuilder(#[from] http::Error),
132
133    #[error("Error decorating outgoing request with CUPv2 parameters")]
134    CupDecoration(#[from] CupDecorationError),
135
136    #[error("Error validating incoming response with CUPv2 protocol")]
137    CupValidation(#[from] CupVerificationError),
138
139    // TODO: This still contains hyper user error which should be split out.
140    #[error("HTTP transport error performing update check")]
141    HttpTransport(#[from] http_request::Error),
142
143    #[error("HTTP error performing update check: {0}")]
144    HttpStatus(hyper::StatusCode),
145}
146
147impl From<request_builder::Error> for OmahaRequestError {
148    fn from(err: request_builder::Error) -> Self {
149        match err {
150            request_builder::Error::Json(e) => OmahaRequestError::Json(e),
151            request_builder::Error::Http(e) => OmahaRequestError::HttpBuilder(e),
152            request_builder::Error::Cup(e) => OmahaRequestError::CupDecoration(e),
153        }
154    }
155}
156
157impl From<http::StatusCode> for OmahaRequestError {
158    fn from(sc: http::StatusCode) -> Self {
159        OmahaRequestError::HttpStatus(sc)
160    }
161}
162
163/// This is the set of errors that can occur when parsing the response body from Omaha.  This is an
164/// internal collection of error types.
165#[derive(Error, Debug)]
166pub enum ResponseParseError {
167    #[error("Response was not valid UTF-8")]
168    Utf8(#[from] Utf8Error),
169
170    #[error("Unexpected JSON error parsing update check response")]
171    Json(#[from] serde_json::Error),
172}
173
174#[derive(Error, Debug)]
175pub enum UpdateCheckError {
176    #[error("Error checking with Omaha")]
177    OmahaRequest(#[from] OmahaRequestError),
178
179    #[error("Error parsing Omaha response")]
180    ResponseParser(#[from] ResponseParseError),
181
182    #[error("Unable to create an install plan")]
183    InstallPlan(#[source] anyhow::Error),
184}
185
186/// A handle to interact with the state machine running in another task.
187#[derive(Clone)]
188pub struct ControlHandle(mpsc::Sender<ControlRequest>);
189
190/// Error indicating that the state machine task no longer exists.
191#[derive(Debug, Clone, Error, PartialEq, Eq)]
192#[error("state machine dropped before all its control handles")]
193pub struct StateMachineGone;
194
195impl From<mpsc::SendError> for StateMachineGone {
196    fn from(_: mpsc::SendError) -> Self {
197        StateMachineGone
198    }
199}
200
201impl From<oneshot::Canceled> for StateMachineGone {
202    fn from(_: oneshot::Canceled) -> Self {
203        StateMachineGone
204    }
205}
206
207enum ControlRequest {
208    StartUpdateCheck { options: CheckOptions, responder: oneshot::Sender<StartUpdateCheckResponse> },
209}
210
211/// Responses to a request to start an update check now.
212#[derive(Debug, Clone, PartialEq, Eq)]
213pub enum StartUpdateCheckResponse {
214    /// The state machine was idle and the request triggered an update check.
215    Started,
216
217    /// The state machine was already processing an update check and ignored this request and
218    /// options.
219    AlreadyRunning,
220
221    /// The update check was throttled by policy.
222    Throttled,
223}
224
225impl ControlHandle {
226    /// Ask the state machine to start an update check with the provided options, returning whether
227    /// or not the state machine started a check or was already running one.
228    pub async fn start_update_check(
229        &mut self,
230        options: CheckOptions,
231    ) -> Result<StartUpdateCheckResponse, StateMachineGone> {
232        let (responder, receive_response) = oneshot::channel();
233        self.0.send(ControlRequest::StartUpdateCheck { options, responder }).await?;
234        Ok(receive_response.await?)
235    }
236}
237
238#[derive(Debug)]
239enum RebootAfterUpdate<T> {
240    Needed(T),
241    NotNeeded,
242}
243
244impl<PE, HR, IN, TM, MR, ST, AS, IR, PL, CH> StateMachine<PE, HR, IN, TM, MR, ST, AS, CH>
245where
246    PE: PolicyEngine<InstallResult = IR, InstallPlan = PL>,
247    HR: HttpRequest,
248    IN: Installer<InstallResult = IR, InstallPlan = PL>,
249    TM: Timer,
250    MR: MetricsReporter,
251    ST: Storage,
252    AS: AppSet,
253    CH: Cupv2Handler,
254    IR: 'static + Send,
255    PL: Plan,
256{
257    /// Ask policy engine for the next update check time and update the context and yield event.
258    async fn update_next_update_time(
259        &mut self,
260        co: &mut async_generator::Yield<StateMachineEvent>,
261    ) -> CheckTiming {
262        let apps = self.app_set.lock().await.get_apps();
263        let timing = self
264            .policy_engine
265            .compute_next_update_time(&apps, &self.context.schedule, &self.context.state)
266            .await;
267        self.context.schedule.next_update_time = Some(timing);
268
269        co.yield_(StateMachineEvent::ScheduleChange(self.context.schedule)).await;
270        info!("Calculated check timing: {}", timing);
271        timing
272    }
273
274    /// Return a future that will wait until the given check timing.
275    async fn make_wait_to_next_check(
276        &mut self,
277        check_timing: CheckTiming,
278    ) -> Fuse<BoxFuture<'static, ()>> {
279        if let Some(minimum_wait) = check_timing.minimum_wait {
280            // If there's a minimum wait, also wait at least that long, by joining the two
281            // timers so that both need to be true (in case `next_update_time` turns out to be
282            // very close to now)
283            future::join(
284                self.timer.wait_for(minimum_wait),
285                self.timer.wait_until(check_timing.time),
286            )
287            .map(|_| ())
288            .boxed()
289            .fuse()
290        } else {
291            // Otherwise just setup the timer for the waiting until the next time.  This is a
292            // wait until either the monotonic or wall times have passed.
293            self.timer.wait_until(check_timing.time).fuse()
294        }
295    }
296
297    async fn run(
298        mut self,
299        mut control: mpsc::Receiver<ControlRequest>,
300        mut co: async_generator::Yield<StateMachineEvent>,
301    ) {
302        {
303            let app_set = self.app_set.lock().await;
304            if !app_set.all_valid() {
305                error!("App set not valid, not starting state machine: {:#?}", app_set.get_apps());
306                return;
307            }
308        }
309
310        let state_machine_start_monotonic_time = self.time_source.now_in_monotonic();
311
312        let mut should_report_waited_for_reboot_duration = false;
313
314        let update_finish_time = {
315            let storage = self.storage_ref.lock().await;
316            let update_finish_time = storage.get_time(UPDATE_FINISH_TIME).await;
317            if update_finish_time.is_some()
318                && let Some(target_version) = storage.get_string(TARGET_VERSION).await
319                && target_version == self.config.os.version
320            {
321                should_report_waited_for_reboot_duration = true;
322            }
323            update_finish_time
324        };
325
326        loop {
327            info!("Initial context: {:?}", self.context);
328
329            if should_report_waited_for_reboot_duration {
330                match self.report_waited_for_reboot_duration(
331                    update_finish_time.unwrap(),
332                    state_machine_start_monotonic_time,
333                    self.time_source.now(),
334                ) {
335                    Ok(()) => {
336                        // If the report was successful, don't try again on the next loop.
337                        should_report_waited_for_reboot_duration = false;
338
339                        let mut storage = self.storage_ref.lock().await;
340                        storage.remove_or_log(UPDATE_FINISH_TIME).await;
341                        storage.remove_or_log(TARGET_VERSION).await;
342                        storage.commit_or_log().await;
343                    }
344                    Err(e) => {
345                        warn!("Couldn't report wait for reboot duration: {:#}, will try again", e);
346                    }
347                }
348            }
349
350            let (mut options, responder) = {
351                let check_timing = self.update_next_update_time(&mut co).await;
352                let mut wait_to_next_check = self.make_wait_to_next_check(check_timing).await;
353
354                // Wait for either the next check time or a request to start an update check.  Use
355                // the default check options with the timed check, or those sent with a request.
356                select! {
357                    () = wait_to_next_check => (CheckOptions::default(), None),
358                    ControlRequest::StartUpdateCheck{options, responder} = control.select_next_some() => {
359                        (options, Some(responder))
360                    }
361                }
362            };
363
364            let reboot_after_update = {
365                let apps = self.app_set.lock().await.get_apps();
366                info!("Checking to see if an update check is allowed at this time for {:?}", apps);
367                let decision = self
368                    .policy_engine
369                    .update_check_allowed(
370                        &apps,
371                        &self.context.schedule,
372                        &self.context.state,
373                        &options,
374                    )
375                    .await;
376
377                info!("The update check decision is: {:?}", decision);
378
379                let request_params = match decision {
380                    // Positive results, will continue with the update check process
381                    CheckDecision::Ok(rp) | CheckDecision::OkUpdateDeferred(rp) => rp,
382
383                    // Negative results, exit early
384                    CheckDecision::TooSoon
385                    | CheckDecision::ThrottledByPolicy
386                    | CheckDecision::DeniedByPolicy => {
387                        info!("The update check is not allowed at this time.");
388                        if let Some(responder) = responder {
389                            let _ = responder.send(StartUpdateCheckResponse::Throttled);
390                        }
391                        continue;
392                    }
393                };
394                if let Some(responder) = responder {
395                    let _ = responder.send(StartUpdateCheckResponse::Started);
396                }
397
398                // "start" the update check itself (well, create the future that is the update check)
399                let update_check = self.start_update_check(request_params, &mut co).fuse();
400                futures::pin_mut!(update_check);
401
402                // Wait for the update check to complete, handling any control requests that come in
403                // during the check.
404                loop {
405                    select! {
406                        update_check_result = update_check => break update_check_result,
407                        ControlRequest::StartUpdateCheck{
408                            options: new_options,
409                            responder
410                        } = control.select_next_some() => {
411                            if new_options.source == InstallSource::OnDemand {
412                                info!("Got on demand update check request, ensuring ongoing check is on demand");
413                                // TODO(63180): merge CheckOptions in Policy, not here.
414                                options.source = InstallSource::OnDemand;
415                            }
416
417                            let _ = responder.send(StartUpdateCheckResponse::AlreadyRunning);
418                        }
419                    }
420                }
421            };
422
423            if let RebootAfterUpdate::Needed(install_result) = reboot_after_update {
424                Self::yield_state(State::WaitingForReboot, &mut co).await;
425                self.wait_for_reboot(options, &mut control, install_result, &mut co).await;
426            }
427
428            Self::yield_state(State::Idle, &mut co).await;
429        }
430    }
431
432    async fn wait_for_reboot(
433        &mut self,
434        mut options: CheckOptions,
435        control: &mut mpsc::Receiver<ControlRequest>,
436        install_result: IN::InstallResult,
437        co: &mut async_generator::Yield<StateMachineEvent>,
438    ) {
439        if !self.policy_engine.reboot_allowed(&options, &install_result).await {
440            let wait_to_see_if_reboot_allowed =
441                self.timer.wait_for(CHECK_REBOOT_ALLOWED_INTERVAL).fuse();
442            futures::pin_mut!(wait_to_see_if_reboot_allowed);
443
444            let check_timing = self.update_next_update_time(co).await;
445            let wait_to_next_ping = self.make_wait_to_next_check(check_timing).await;
446            futures::pin_mut!(wait_to_next_ping);
447
448            loop {
449                // Wait for either the next time to check if reboot allowed or the next
450                // ping time or a request to start an update check.
451
452                select! {
453                    () = wait_to_see_if_reboot_allowed => {
454                        if self.policy_engine.reboot_allowed(&options, &install_result).await {
455                            break;
456                        }
457                        info!("Reboot not allowed at the moment, will try again in 30 minutes...");
458                        wait_to_see_if_reboot_allowed.set(
459                            self.timer.wait_for(CHECK_REBOOT_ALLOWED_INTERVAL).fuse()
460                        );
461                    },
462                    () = wait_to_next_ping => {
463                        self.ping_omaha(co).await;
464                        let check_timing = self.update_next_update_time(co).await;
465                        wait_to_next_ping.set(self.make_wait_to_next_check(check_timing).await);
466                    },
467                    ControlRequest::StartUpdateCheck{
468                        options: new_options,
469                        responder
470                    } = control.select_next_some() => {
471                        let _ = responder.send(StartUpdateCheckResponse::AlreadyRunning);
472                        if new_options.source == InstallSource::OnDemand {
473                            info!("Waiting for reboot, but ensuring that InstallSource is OnDemand");
474                            options.source = InstallSource::OnDemand;
475
476                            if self.policy_engine.reboot_allowed(&options, &install_result).await {
477                                info!("Upgraded update check request to on demand, policy allowed reboot");
478                                break;
479                            }
480                        };
481                    }
482                }
483            }
484        }
485        info!("Rebooting the system at the end of a successful update");
486        if let Err(e) = self.installer.perform_reboot().await {
487            error!("Unable to reboot the system: {}", e);
488        }
489    }
490
491    /// Report the duration the previous boot waited to reboot based on the update finish time in
492    /// storage, and the current time. Does not report a metric if there's an inconsistency in the
493    /// times stored or computed, i.e. if the reboot time is later than the current time.
494    /// Returns an error if time seems incorrect, e.g. update_finish_time is in the future.
495    fn report_waited_for_reboot_duration(
496        &mut self,
497        update_finish_time: SystemTime,
498        state_machine_start_monotonic_time: Instant,
499        now: ComplexTime,
500    ) -> Result<(), anyhow::Error> {
501        // If `update_finish_time` is in the future we don't have correct time, try again
502        // on the next loop.
503        let update_finish_time_to_now =
504            now.wall_duration_since(update_finish_time).map_err(|e| {
505                anyhow!(
506                    "Update finish time later than now, can't report waited for reboot duration,
507                    update finish time: {:?}, now: {:?}, error: {:?}",
508                    update_finish_time,
509                    now,
510                    e,
511                )
512            })?;
513
514        // It might take a while for us to get here, but we only want to report the
515        // time from update finish to state machine start after reboot, so we subtract
516        // the duration since then using monotonic time.
517
518        // We only want to report this metric if we can actually compute it.
519        // If for whatever reason the clock was wrong on the previous boot, or monotonic
520        // time is going backwards, better not to report this metric than to report an
521        // incorrect default value.
522        let state_machine_start_to_now = now
523            .mono
524            .checked_duration_since(state_machine_start_monotonic_time)
525            .ok_or_else(|| {
526                error!("Monotonic time appears to have gone backwards");
527                anyhow!(
528                    "State machine start later than now, can't report waited for reboot duration. \
529                    State machine start: {:?}, now: {:?}",
530                    state_machine_start_monotonic_time,
531                    now.mono,
532                )
533            })?;
534
535        let waited_for_reboot_duration =
536            update_finish_time_to_now.checked_sub(state_machine_start_to_now).ok_or_else(|| {
537                anyhow!(
538                    "Can't report waiting for reboot duration, update finish time to now smaller \
539                    than state machine start to now. Update finish time to now: {:?}, state \
540                    machine start to now: {:?}",
541                    update_finish_time_to_now,
542                    state_machine_start_to_now,
543                )
544            })?;
545
546        info!("Waited {} seconds for reboot.", waited_for_reboot_duration.as_secs());
547        self.report_metrics(Metrics::WaitedForRebootDuration(waited_for_reboot_duration));
548        Ok(())
549    }
550
551    /// Report update check interval based on the last check time stored in storage.
552    /// It will also persist the new last check time to storage.
553    async fn report_check_interval(&mut self, install_source: InstallSource) {
554        let now = self.time_source.now();
555
556        match self.context.schedule.last_update_check_time {
557            // This is our first run; report the interval between that time and now,
558            // and update the context with the complex time.
559            Some(PartialComplexTime::Wall(t)) => match now.wall_duration_since(t) {
560                Ok(interval) => self.report_metrics(Metrics::UpdateCheckInterval {
561                    interval,
562                    clock: ClockType::Wall,
563                    install_source,
564                }),
565                Err(e) => warn!("Last check time is in the future: {}", e),
566            },
567
568            // We've reported an update check before, or we at least have a
569            // PartialComplexTime with a monotonic component. Report our interval
570            // between these Instants. (N.B. strictly speaking, we should only
571            // ever have a PCT::Complex here.)
572            Some(PartialComplexTime::Complex(t)) => match now.mono.checked_duration_since(t.mono) {
573                Some(interval) => self.report_metrics(Metrics::UpdateCheckInterval {
574                    interval,
575                    clock: ClockType::Monotonic,
576                    install_source,
577                }),
578                None => error!("Monotonic time in the past"),
579            },
580
581            // No last check time in storage, and no big deal. We'll continue from
582            // monotonic time from now on. This is the only place other than loading
583            // context from storage where the time can be set, so it's either unset
584            // because no storage, or a complex time. No need to match
585            // Some(PartialComplexTime::Monotonic)
586            _ => {}
587        }
588
589        self.context.schedule.last_update_check_time = now.into();
590    }
591
592    /// Perform update check and handle the result, including updating the update check context
593    /// and cohort.
594    /// Returns whether reboot is needed after the update.
595    async fn start_update_check(
596        &mut self,
597        request_params: RequestParams,
598        co: &mut async_generator::Yield<StateMachineEvent>,
599    ) -> RebootAfterUpdate<IN::InstallResult> {
600        let apps = self.app_set.lock().await.get_apps();
601        let result = self.perform_update_check(request_params, apps, co).await;
602
603        let (result, reboot_after_update) = match result {
604            Ok((result, reboot_after_update)) => {
605                info!("Update check result: {:?}", result);
606                // Update check succeeded, update |last_update_time|.
607                self.context.schedule.last_update_time = Some(self.time_source.now().into());
608
609                // Determine if any app failed to install, or we had a successful update.
610                let install_success =
611                    result.app_responses.iter().fold(None, |result, app| {
612                        match (result, &app.result) {
613                            (_, update_check::Action::InstallPlanExecutionError) => Some(false),
614                            (None, update_check::Action::Updated) => Some(true),
615                            (result, _) => result,
616                        }
617                    });
618
619                // Update check succeeded, reset |consecutive_failed_update_checks| to 0 and
620                // report metrics.
621                self.report_attempts_to_successful_check(true).await;
622
623                self.app_set.lock().await.update_from_omaha(&result.app_responses);
624
625                // Only report |attempts_to_successful_install| if we get an error trying to
626                // install, or we succeed to install an update without error.
627                if let Some(success) = install_success {
628                    self.report_attempts_to_successful_install(success).await;
629                }
630
631                (Ok(result), reboot_after_update)
632                // TODO: update consecutive_proxied_requests
633            }
634            Err(error) => {
635                error!("Update check failed: {:?}", error);
636
637                let failure_reason = match &error {
638                    UpdateCheckError::ResponseParser(_) | UpdateCheckError::InstallPlan(_) => {
639                        // We talked to Omaha, update |last_update_time|.
640                        self.context.schedule.last_update_time =
641                            Some(self.time_source.now().into());
642
643                        UpdateCheckFailureReason::Omaha
644                    }
645                    UpdateCheckError::OmahaRequest(request_error) => match request_error {
646                        OmahaRequestError::Json(_)
647                        | OmahaRequestError::HttpBuilder(_)
648                        | OmahaRequestError::CupDecoration(_)
649                        | OmahaRequestError::CupValidation(_) => UpdateCheckFailureReason::Internal,
650                        OmahaRequestError::HttpTransport(_) | OmahaRequestError::HttpStatus(_) => {
651                            UpdateCheckFailureReason::Network
652                        }
653                    },
654                };
655                self.report_metrics(Metrics::UpdateCheckFailureReason(failure_reason));
656
657                self.report_attempts_to_successful_check(false).await;
658                (Err(error), RebootAfterUpdate::NotNeeded)
659            }
660        };
661
662        co.yield_(StateMachineEvent::ScheduleChange(self.context.schedule)).await;
663        co.yield_(StateMachineEvent::ProtocolStateChange(self.context.state.clone())).await;
664        co.yield_(StateMachineEvent::UpdateCheckResult(result)).await;
665
666        self.persist_data().await;
667
668        reboot_after_update
669    }
670
671    // Update self.context.state.consecutive_failed_update_checks and report the metric if
672    // `success`. Does not persist the value to storage, but rather relies on the caller.
673    async fn report_attempts_to_successful_check(&mut self, success: bool) {
674        let attempts = self.context.state.consecutive_failed_update_checks + 1;
675        if success {
676            self.context.state.consecutive_failed_update_checks = 0;
677            self.report_metrics(Metrics::AttemptsToSuccessfulCheck(attempts as u64));
678        } else {
679            self.context.state.consecutive_failed_update_checks = attempts;
680        }
681    }
682
683    /// Update `CONSECUTIVE_FAILED_INSTALL_ATTEMPTS` in storage and report the metrics if
684    /// `success`. Does not commit the change to storage.
685    async fn report_attempts_to_successful_install(&mut self, success: bool) {
686        let storage_ref = self.storage_ref.clone();
687        let mut storage = storage_ref.lock().await;
688        let attempts = storage.get_int(CONSECUTIVE_FAILED_INSTALL_ATTEMPTS).await.unwrap_or(0) + 1;
689
690        self.report_metrics(Metrics::AttemptsToSuccessfulInstall {
691            count: attempts as u64,
692            successful: success,
693        });
694
695        if success {
696            storage.remove_or_log(CONSECUTIVE_FAILED_INSTALL_ATTEMPTS).await;
697        } else if let Err(e) = storage.set_int(CONSECUTIVE_FAILED_INSTALL_ATTEMPTS, attempts).await
698        {
699            error!("Unable to persist {}: {}", CONSECUTIVE_FAILED_INSTALL_ATTEMPTS, e);
700        }
701    }
702
703    /// Persist all necessary data to storage.
704    async fn persist_data(&self) {
705        let mut storage = self.storage_ref.lock().await;
706        self.context.persist(&mut *storage).await;
707        self.app_set.lock().await.persist(&mut *storage).await;
708
709        storage.commit_or_log().await;
710    }
711
712    /// This function constructs the chain of async futures needed to perform all of the async tasks
713    /// that comprise an update check.
714    async fn perform_update_check(
715        &mut self,
716        request_params: RequestParams,
717        apps: Vec<App>,
718        co: &mut async_generator::Yield<StateMachineEvent>,
719    ) -> Result<(update_check::Response, RebootAfterUpdate<IN::InstallResult>), UpdateCheckError>
720    {
721        Self::yield_state(State::CheckingForUpdates(request_params.source), co).await;
722
723        self.report_check_interval(request_params.source).await;
724
725        // Construct a request for the app(s).
726        let config = self.config.clone();
727        let mut request_builder = RequestBuilder::new(&config, &request_params);
728        for app in &apps {
729            request_builder = request_builder.add_update_check(app).add_ping(app);
730        }
731        let session_id = GUID::new();
732        request_builder = request_builder.session_id(session_id.clone());
733
734        let mut omaha_request_attempt = 1;
735
736        // Attempt in an loop of up to MAX_OMAHA_REQUEST_ATTEMPTS to communicate with Omaha.
737        // exit the loop early on success or an error that isn't related to a transport issue.
738        let loop_result = loop {
739            // Mark the start time for the request to omaha.
740            let omaha_check_start_time = self.time_source.now_in_monotonic();
741            request_builder = request_builder.request_id(GUID::new());
742            let result = self.do_omaha_request_and_update_context(&request_builder, co).await;
743
744            // Report the response time of the omaha request.
745            {
746                // don't use Instant::elapsed(), it doesn't use the right TimeSource, and can panic!
747                // as a result
748                let now = self.time_source.now_in_monotonic();
749                let duration = now.checked_duration_since(omaha_check_start_time);
750
751                if let Some(response_time) = duration {
752                    self.report_metrics(Metrics::UpdateCheckResponseTime {
753                        response_time,
754                        successful: result.is_ok(),
755                    });
756                } else {
757                    // If this happens, it's a bug.
758                    error!(
759                        "now: {:?}, is before omaha_check_start_time: {:?}",
760                        now, omaha_check_start_time
761                    );
762                }
763            }
764
765            match result {
766                Ok(res) => {
767                    break Ok(res);
768                }
769                Err(OmahaRequestError::Json(e)) => {
770                    error!("Unable to construct request body! {:?}", e);
771                    Self::yield_state(State::ErrorCheckingForUpdate, co).await;
772                    break Err(UpdateCheckError::OmahaRequest(e.into()));
773                }
774                Err(OmahaRequestError::HttpBuilder(e)) => {
775                    error!("Unable to construct HTTP request! {:?}", e);
776                    Self::yield_state(State::ErrorCheckingForUpdate, co).await;
777                    break Err(UpdateCheckError::OmahaRequest(e.into()));
778                }
779                Err(OmahaRequestError::CupDecoration(e)) => {
780                    error!("Unable to decorate HTTP request with CUPv2 parameters! {:?}", e);
781                    Self::yield_state(State::ErrorCheckingForUpdate, co).await;
782                    break Err(UpdateCheckError::OmahaRequest(e.into()));
783                }
784                Err(OmahaRequestError::CupValidation(e)) => {
785                    error!("Unable to validate HTTP response with CUPv2 parameters! {:?}", e);
786                    Self::yield_state(State::ErrorCheckingForUpdate, co).await;
787                    break Err(UpdateCheckError::OmahaRequest(e.into()));
788                }
789                Err(OmahaRequestError::HttpTransport(e)) => {
790                    warn!("Unable to contact Omaha: {:?}", e);
791                    // Don't retry if the error was caused by user code, which means we weren't
792                    // using the library correctly.
793                    if omaha_request_attempt >= MAX_OMAHA_REQUEST_ATTEMPTS
794                        || e.is_user()
795                        || self.context.state.server_dictated_poll_interval.is_some()
796                    {
797                        Self::yield_state(State::ErrorCheckingForUpdate, co).await;
798                        break Err(UpdateCheckError::OmahaRequest(e.into()));
799                    }
800                }
801                Err(OmahaRequestError::HttpStatus(e)) => {
802                    warn!("Unable to contact Omaha: {:?}", e);
803                    if omaha_request_attempt >= MAX_OMAHA_REQUEST_ATTEMPTS
804                        || self.context.state.server_dictated_poll_interval.is_some()
805                    {
806                        Self::yield_state(State::ErrorCheckingForUpdate, co).await;
807                        break Err(UpdateCheckError::OmahaRequest(e.into()));
808                    }
809                }
810            }
811
812            // TODO(https://fxbug.dev/42117854): Move this to Policy.
813            // Randomized exponential backoff of 1, 2, & 4 seconds, +/- 500ms.
814            let backoff_time_secs = 1 << (omaha_request_attempt - 1);
815            let backoff_time = randomize(backoff_time_secs * 1000, 1000);
816            info!("Waiting {} ms before retrying...", backoff_time);
817            self.timer.wait_for(Duration::from_millis(backoff_time)).await;
818
819            omaha_request_attempt += 1;
820        };
821
822        self.report_metrics(Metrics::RequestsPerCheck {
823            count: omaha_request_attempt,
824            successful: loop_result.is_ok(),
825        });
826
827        let (_parts, data, request_metadata, signature) = loop_result?;
828
829        let response = match Self::parse_omaha_response(&data) {
830            Ok(res) => res,
831            Err(err) => {
832                warn!("Unable to parse Omaha response: {:?}", err);
833                Self::yield_state(State::ErrorCheckingForUpdate, co).await;
834                self.report_omaha_event_and_update_context(
835                    &request_params,
836                    Event::error(EventErrorCode::ParseResponse),
837                    &apps,
838                    &session_id,
839                    &apps.iter().map(|app| (app.id.clone(), None)).collect(),
840                    None,
841                    co,
842                )
843                .await;
844                return Err(UpdateCheckError::ResponseParser(err));
845            }
846        };
847
848        info!("result: {:?}", response);
849
850        co.yield_(StateMachineEvent::OmahaServerResponse(response.clone())).await;
851
852        let statuses = Self::get_app_update_statuses(&response);
853        for (app_id, status) in &statuses {
854            // TODO:  Report or metric statuses other than 'no-update' and 'ok'
855            info!("Omaha update check status: {} => {:?}", app_id, status);
856        }
857
858        let apps_with_update: Vec<_> = response
859            .apps
860            .iter()
861            .filter(|app| {
862                matches!(app.update_check, Some(UpdateCheck { status: OmahaStatus::Ok, .. }))
863            })
864            .collect();
865
866        if apps_with_update.is_empty() {
867            // A successful, no-update, check
868
869            Self::yield_state(State::NoUpdateAvailable, co).await;
870            Self::make_not_updated_result(response, update_check::Action::NoUpdate)
871        } else {
872            info!(
873                "At least one app has an update, proceeding to build and process an Install Plan"
874            );
875            // A map from app id to the new version of the app, if an app has no update, then it
876            // won't appear in this map, if an app has update but there's no version in the omaha
877            // response, then its entry will be None.
878            let next_versions: HashMap<String, Option<String>> = apps_with_update
879                .iter()
880                .map(|app| (app.id.clone(), app.get_manifest_version()))
881                .collect();
882            let install_plan = match self
883                .installer
884                .try_create_install_plan(
885                    &request_params,
886                    request_metadata.as_ref(),
887                    &response,
888                    data,
889                    signature.map(|s| s.as_bytes().to_vec()),
890                )
891                .await
892            {
893                Ok(plan) => plan,
894                Err(e) => {
895                    error!("Unable to construct install plan! {}", e);
896                    Self::yield_state(State::InstallingUpdate, co).await;
897                    Self::yield_state(State::InstallationError, co).await;
898                    self.report_omaha_event_and_update_context(
899                        &request_params,
900                        Event::error(EventErrorCode::ConstructInstallPlan),
901                        &apps,
902                        &session_id,
903                        &next_versions,
904                        None,
905                        co,
906                    )
907                    .await;
908                    return Err(UpdateCheckError::InstallPlan(e.into()));
909                }
910            };
911
912            info!("Validating Install Plan with Policy");
913            let install_plan_decision = self.policy_engine.update_can_start(&install_plan).await;
914            match install_plan_decision {
915                UpdateDecision::Ok => {
916                    info!("Proceeding with install plan.");
917                }
918                UpdateDecision::DeferredByPolicy => {
919                    info!("Install plan was deferred by Policy.");
920                    // Report "error" to Omaha (as this is an event that needs reporting as the
921                    // install isn't starting immediately.
922                    let event = Event {
923                        event_type: EventType::UpdateComplete,
924                        event_result: EventResult::UpdateDeferred,
925                        ..Event::default()
926                    };
927                    self.report_omaha_event_and_update_context(
928                        &request_params,
929                        event,
930                        &apps,
931                        &session_id,
932                        &next_versions,
933                        None,
934                        co,
935                    )
936                    .await;
937
938                    Self::yield_state(State::InstallationDeferredByPolicy, co).await;
939
940                    return Self::make_not_updated_result(
941                        response,
942                        update_check::Action::DeferredByPolicy,
943                    );
944                }
945                UpdateDecision::DeniedByPolicy => {
946                    warn!("Install plan was denied by Policy, see Policy logs for reasoning");
947                    self.report_omaha_event_and_update_context(
948                        &request_params,
949                        Event::error(EventErrorCode::DeniedByPolicy),
950                        &apps,
951                        &session_id,
952                        &next_versions,
953                        None,
954                        co,
955                    )
956                    .await;
957
958                    return Self::make_not_updated_result(
959                        response,
960                        update_check::Action::DeniedByPolicy,
961                    );
962                }
963            }
964
965            Self::yield_state(State::InstallingUpdate, co).await;
966            self.report_omaha_event_and_update_context(
967                &request_params,
968                Event::success(EventType::UpdateDownloadStarted),
969                &apps,
970                &session_id,
971                &next_versions,
972                None,
973                co,
974            )
975            .await;
976
977            let install_plan_id = install_plan.id();
978            let update_start_time = self.time_source.now_in_walltime();
979            let update_first_seen_time =
980                self.record_update_first_seen_time(&install_plan_id, update_start_time).await;
981
982            let (send, mut recv) = mpsc::channel(0);
983            let observer = StateMachineProgressObserver(send);
984            let perform_install = async {
985                let result = self.installer.perform_install(&install_plan, Some(&observer)).await;
986                // Drop observer so that we can stop waiting for the next progress.
987                drop(observer);
988                result
989            };
990            let yield_progress = async {
991                while let Some(progress) = recv.next().await {
992                    co.yield_(StateMachineEvent::InstallProgressChange(progress)).await;
993                }
994            };
995
996            let ((install_result, mut app_install_results), ()) =
997                future::join(perform_install, yield_progress).await;
998            let no_apps_failed = app_install_results.iter().all(|result| {
999                matches!(result, AppInstallResult::Installed | AppInstallResult::Deferred)
1000            });
1001            let update_finish_time = self.time_source.now_in_walltime();
1002            let install_duration = match update_finish_time.duration_since(update_start_time) {
1003                Ok(duration) => {
1004                    let metrics = if no_apps_failed {
1005                        Metrics::SuccessfulUpdateDuration(duration)
1006                    } else {
1007                        Metrics::FailedUpdateDuration(duration)
1008                    };
1009                    self.report_metrics(metrics);
1010                    Some(duration)
1011                }
1012                Err(e) => {
1013                    warn!("Update start time is in the future: {}", e);
1014                    None
1015                }
1016            };
1017
1018            let config = self.config.clone();
1019            let mut request_builder = RequestBuilder::new(&config, &request_params);
1020            let mut events = vec![];
1021            let mut installed_apps = vec![];
1022            for (response_app, app_install_result) in
1023                apps_with_update.iter().zip(&app_install_results)
1024            {
1025                match apps.iter().find(|app| app.id == response_app.id) {
1026                    Some(app) => {
1027                        let event = match app_install_result {
1028                            AppInstallResult::Installed => {
1029                                installed_apps.push(app);
1030                                Event::success(EventType::UpdateDownloadFinished)
1031                            }
1032                            AppInstallResult::Deferred => Event {
1033                                event_type: EventType::UpdateComplete,
1034                                event_result: EventResult::UpdateDeferred,
1035                                ..Event::default()
1036                            },
1037                            AppInstallResult::Failed(_) => {
1038                                Event::error(EventErrorCode::Installation)
1039                            }
1040                        };
1041                        let event = Event {
1042                            previous_version: Some(app.version.to_string()),
1043                            next_version: response_app.get_manifest_version(),
1044                            download_time_ms: install_duration
1045                                .and_then(|d| d.as_millis().try_into().ok()),
1046                            ..event
1047                        };
1048                        request_builder = request_builder.add_event(app, event.clone());
1049                        events.push(event);
1050                    }
1051                    None => {
1052                        error!("unknown app id in omaha response: {:?}", response_app.id);
1053                    }
1054                }
1055            }
1056            request_builder =
1057                request_builder.session_id(session_id.clone()).request_id(GUID::new());
1058            if let Err(e) = self.do_omaha_request_and_update_context(&request_builder, co).await {
1059                for event in events {
1060                    self.report_metrics(Metrics::OmahaEventLost(event));
1061                }
1062                warn!("Unable to report event to Omaha: {:?}", e);
1063            }
1064
1065            // TODO: Verify downloaded update if needed.
1066
1067            // For apps that successfully installed, we need to report an extra `UpdateComplete` event.
1068            if !installed_apps.is_empty() {
1069                self.report_omaha_event_and_update_context(
1070                    &request_params,
1071                    Event::success(EventType::UpdateComplete),
1072                    installed_apps,
1073                    &session_id,
1074                    &next_versions,
1075                    install_duration,
1076                    co,
1077                )
1078                .await;
1079            }
1080
1081            let mut errors = vec![];
1082            let daystart = response.daystart;
1083            let app_responses = response
1084                .apps
1085                .into_iter()
1086                .map(|app| update_check::AppResponse {
1087                    app_id: app.id,
1088                    cohort: app.cohort,
1089                    user_counting: daystart.clone().into(),
1090                    result: match app.update_check {
1091                        Some(UpdateCheck { status: OmahaStatus::Ok, .. }) => {
1092                            match app_install_results.remove(0) {
1093                                AppInstallResult::Installed => update_check::Action::Updated,
1094                                AppInstallResult::Deferred => {
1095                                    update_check::Action::DeferredByPolicy
1096                                }
1097                                AppInstallResult::Failed(e) => {
1098                                    errors.push(e);
1099                                    update_check::Action::InstallPlanExecutionError
1100                                }
1101                            }
1102                        }
1103                        _ => update_check::Action::NoUpdate,
1104                    },
1105                })
1106                .collect();
1107
1108            if !errors.is_empty() {
1109                for e in errors {
1110                    co.yield_(StateMachineEvent::InstallerError(Some(Box::new(e)))).await;
1111                }
1112                Self::yield_state(State::InstallationError, co).await;
1113
1114                return Ok((
1115                    update_check::Response { app_responses },
1116                    RebootAfterUpdate::NotNeeded,
1117                ));
1118            }
1119
1120            match update_finish_time.duration_since(update_first_seen_time) {
1121                Ok(duration) => {
1122                    self.report_metrics(Metrics::SuccessfulUpdateFromFirstSeen(duration))
1123                }
1124                Err(e) => warn!("Update first seen time is in the future: {}", e),
1125            }
1126            {
1127                let mut storage = self.storage_ref.lock().await;
1128                if let Err(e) = storage.set_time(UPDATE_FINISH_TIME, update_finish_time).await {
1129                    error!("Unable to persist {}: {}", UPDATE_FINISH_TIME, e);
1130                }
1131                let app_set = self.app_set.lock().await;
1132                let system_app_id = app_set.get_system_app_id();
1133                // If not found then this is not a system update, so no need to write target version.
1134                if let Some(next_version) = next_versions.get(system_app_id) {
1135                    let target_version = next_version.as_deref().unwrap_or_else(|| {
1136                        error!("Target version string not found in Omaha response.");
1137                        "UNKNOWN"
1138                    });
1139                    if let Err(e) = storage.set_string(TARGET_VERSION, target_version).await {
1140                        error!("Unable to persist {}: {}", TARGET_VERSION, e);
1141                    }
1142                }
1143                storage.commit_or_log().await;
1144            }
1145
1146            let reboot_after_update = if self.policy_engine.reboot_needed(&install_plan).await {
1147                RebootAfterUpdate::Needed(install_result)
1148            } else {
1149                RebootAfterUpdate::NotNeeded
1150            };
1151
1152            Ok((update_check::Response { app_responses }, reboot_after_update))
1153        }
1154    }
1155
1156    /// Report the given |event| to Omaha, errors occurred during reporting are logged but not
1157    /// acted on.
1158    #[allow(clippy::too_many_arguments)]
1159    async fn report_omaha_event_and_update_context<'a>(
1160        &'a mut self,
1161        request_params: &'a RequestParams,
1162        event: Event,
1163        apps: impl IntoIterator<Item = &App>,
1164        session_id: &GUID,
1165        next_versions: &HashMap<String, Option<String>>,
1166        install_duration: Option<Duration>,
1167        co: &mut async_generator::Yield<StateMachineEvent>,
1168    ) {
1169        let config = self.config.clone();
1170        let mut request_builder = RequestBuilder::new(&config, request_params);
1171        for app in apps {
1172            // Skip apps with no update.
1173            if let Some(next_version) = next_versions.get(&app.id) {
1174                let event = Event {
1175                    previous_version: Some(app.version.to_string()),
1176                    next_version: next_version.clone(),
1177                    download_time_ms: install_duration.and_then(|d| d.as_millis().try_into().ok()),
1178                    ..event.clone()
1179                };
1180                request_builder = request_builder.add_event(app, event);
1181            }
1182        }
1183        request_builder = request_builder.session_id(session_id.clone()).request_id(GUID::new());
1184        if let Err(e) = self.do_omaha_request_and_update_context(&request_builder, co).await {
1185            self.report_metrics(Metrics::OmahaEventLost(event));
1186            warn!("Unable to report event to Omaha: {:?}", e);
1187        }
1188    }
1189
1190    /// Sends a ping to Omaha and updates context and app_set.
1191    async fn ping_omaha(&mut self, co: &mut async_generator::Yield<StateMachineEvent>) {
1192        let apps = self.app_set.lock().await.get_apps();
1193        let request_params = RequestParams {
1194            source: InstallSource::ScheduledTask,
1195            use_configured_proxies: true,
1196            disable_updates: false,
1197            offer_update_if_same_version: false,
1198        };
1199        let config = self.config.clone();
1200        let mut request_builder = RequestBuilder::new(&config, &request_params);
1201        for app in &apps {
1202            request_builder = request_builder.add_ping(app);
1203        }
1204        request_builder = request_builder.session_id(GUID::new()).request_id(GUID::new());
1205
1206        let (_parts, data, _request_metadata, _signature) =
1207            match self.do_omaha_request_and_update_context(&request_builder, co).await {
1208                Ok(res) => res,
1209                Err(e) => {
1210                    error!("Ping Omaha failed: {:#}", anyhow!(e));
1211                    self.context.state.consecutive_failed_update_checks += 1;
1212                    self.persist_data().await;
1213                    return;
1214                }
1215            };
1216
1217        let response = match Self::parse_omaha_response(&data) {
1218            Ok(res) => res,
1219            Err(e) => {
1220                error!("Unable to parse Omaha response: {:#}", anyhow!(e));
1221                self.context.state.consecutive_failed_update_checks += 1;
1222                self.persist_data().await;
1223                return;
1224            }
1225        };
1226
1227        self.context.state.consecutive_failed_update_checks = 0;
1228
1229        // Even though this is a ping, we should still update the last_update_time for
1230        // policy to compute the next ping time.
1231        self.context.schedule.last_update_time = Some(self.time_source.now().into());
1232        co.yield_(StateMachineEvent::ScheduleChange(self.context.schedule)).await;
1233
1234        let app_responses = Self::make_app_responses(response, update_check::Action::NoUpdate);
1235        self.app_set.lock().await.update_from_omaha(&app_responses);
1236
1237        self.persist_data().await;
1238    }
1239
1240    /// Make an http request to Omaha, and collect the response into an error or a blob of bytes
1241    /// that can be parsed.
1242    ///
1243    /// Given the http client and the request build, this makes the http request, and then coalesces
1244    /// the various errors into a single error type for easier error handling by the make process
1245    /// flow.
1246    ///
1247    /// This function also converts an HTTP error response into an Error, to divert those into the
1248    /// error handling paths instead of the Ok() path.
1249    ///
1250    /// If a valid X-Retry-After header is found in the response, this function will update the
1251    /// server dictated poll interval in context.
1252    async fn do_omaha_request_and_update_context<'a>(
1253        &'a mut self,
1254        builder: &RequestBuilder<'a>,
1255        co: &mut async_generator::Yield<StateMachineEvent>,
1256    ) -> Result<(Parts, Vec<u8>, Option<RequestMetadata>, Option<DerSignature>), OmahaRequestError>
1257    {
1258        let (request, request_metadata) = builder.build(self.cup_handler.as_ref())?;
1259        let response = Self::make_request(&mut self.http, request).await?;
1260
1261        let signature: Option<DerSignature> = if let (Some(handler), Some(metadata)) =
1262            (self.cup_handler.as_ref(), &request_metadata)
1263        {
1264            let signature = handler
1265                .verify_response(metadata, &response, metadata.public_key_id)
1266                .map_err(|e| {
1267                    error!("Could not verify response: {:?}", e);
1268                    e
1269                })?;
1270            Some(signature)
1271        } else {
1272            None
1273        };
1274
1275        let (parts, body) = response.into_parts();
1276
1277        // Clients MUST respect this header even if paired with non-successful HTTP response code.
1278        let server_dictated_poll_interval = parts.headers.get(X_RETRY_AFTER).and_then(|header| {
1279            match header
1280                .to_str()
1281                .map_err(|e| anyhow!(e))
1282                .and_then(|s| s.parse::<u64>().map_err(|e| anyhow!(e)))
1283            {
1284                Ok(seconds) => {
1285                    // Servers SHOULD NOT send a value in excess of 86400 (24 hours), and clients
1286                    // SHOULD treat values greater than 86400 as 86400.
1287                    Some(Duration::from_secs(min(seconds, 86400)))
1288                }
1289                Err(e) => {
1290                    error!("Unable to parse {} header: {:#}", X_RETRY_AFTER, e);
1291                    None
1292                }
1293            }
1294        });
1295        if self.context.state.server_dictated_poll_interval != server_dictated_poll_interval {
1296            self.context.state.server_dictated_poll_interval = server_dictated_poll_interval;
1297            co.yield_(StateMachineEvent::ProtocolStateChange(self.context.state.clone())).await;
1298            let mut storage = self.storage_ref.lock().await;
1299            self.context.persist(&mut *storage).await;
1300            storage.commit_or_log().await;
1301        }
1302        if !parts.status.is_success() {
1303            // Convert HTTP failure responses into Errors.
1304            Err(OmahaRequestError::HttpStatus(parts.status))
1305        } else {
1306            // Pass successful responses to the caller.
1307            info!("Omaha HTTP response: {}", parts.status);
1308            Ok((parts, body, request_metadata, signature))
1309        }
1310    }
1311
1312    /// Make an http request and collect the response body into a Vec of bytes.
1313    ///
1314    /// Specifically, this takes the body of the response and concatenates it into a single Vec of
1315    /// bytes so that any errors in receiving it can be captured immediately, instead of needing to
1316    /// handle them as part of parsing the response body.
1317    async fn make_request(
1318        http_client: &mut HR,
1319        request: http::Request<http_request::Body>,
1320    ) -> Result<HttpResponse<Vec<u8>>, http_request::Error> {
1321        info!("Making http request to: {}", request.uri());
1322        http_client.request(request).await.map_err(|err| {
1323            warn!("Unable to perform request: {}", err);
1324            err
1325        })
1326    }
1327
1328    /// This method takes the response bytes from Omaha, and converts them into a protocol::Response
1329    /// struct, returning all of the various errors that can occur in that process as a consolidated
1330    /// error enum.
1331    fn parse_omaha_response(data: &[u8]) -> Result<Response, ResponseParseError> {
1332        parse_json_response(data).map_err(ResponseParseError::Json)
1333    }
1334
1335    /// Utility to extract pairs of app id => omaha status response, to make it easier to ask
1336    /// questions about the response.
1337    fn get_app_update_statuses(response: &Response) -> Vec<(&str, &OmahaStatus)> {
1338        response
1339            .apps
1340            .iter()
1341            .filter_map(|app| app.update_check.as_ref().map(|u| (app.id.as_str(), &u.status)))
1342            .collect()
1343    }
1344
1345    /// Utility to take a set of protocol::response::Apps and then construct a set of AppResponse
1346    /// from the update check based on those app IDs.
1347    ///
1348    /// TODO(https://fxbug.dev/42170288): Change the Policy and Installer to return a set of results, one for
1349    ///                        each app ID, then make this match that.
1350    fn make_app_responses(
1351        response: protocol::response::Response,
1352        action: update_check::Action,
1353    ) -> Vec<update_check::AppResponse> {
1354        let daystart = response.daystart;
1355        response
1356            .apps
1357            .into_iter()
1358            .map(|app| update_check::AppResponse {
1359                app_id: app.id,
1360                cohort: app.cohort,
1361                user_counting: daystart.clone().into(),
1362                result: action.clone(),
1363            })
1364            .collect()
1365    }
1366
1367    /// Make an Ok result for `perform_update_check()` when update wasn't installed/failed.
1368    fn make_not_updated_result(
1369        response: protocol::response::Response,
1370        action: update_check::Action,
1371    ) -> Result<(update_check::Response, RebootAfterUpdate<IN::InstallResult>), UpdateCheckError>
1372    {
1373        Ok((
1374            update_check::Response { app_responses: Self::make_app_responses(response, action) },
1375            RebootAfterUpdate::NotNeeded,
1376        ))
1377    }
1378
1379    /// Send the state to the observer.
1380    async fn yield_state(state: State, co: &mut async_generator::Yield<StateMachineEvent>) {
1381        co.yield_(StateMachineEvent::StateChange(state)).await;
1382    }
1383
1384    fn report_metrics(&mut self, metrics: Metrics) {
1385        if let Err(err) = self.metrics_reporter.report_metrics(metrics) {
1386            warn!("Unable to report metrics: {:?}", err);
1387        }
1388    }
1389
1390    async fn record_update_first_seen_time(
1391        &mut self,
1392        install_plan_id: &str,
1393        now: SystemTime,
1394    ) -> SystemTime {
1395        let mut storage = self.storage_ref.lock().await;
1396        let previous_id = storage.get_string(INSTALL_PLAN_ID).await;
1397        if let Some(previous_id) = previous_id
1398            && previous_id == install_plan_id
1399        {
1400            return storage.get_time(UPDATE_FIRST_SEEN_TIME).await.unwrap_or(now);
1401        }
1402        // Update INSTALL_PLAN_ID and UPDATE_FIRST_SEEN_TIME for new update.
1403        if let Err(e) = storage.set_string(INSTALL_PLAN_ID, install_plan_id).await {
1404            error!("Unable to persist {}: {}", INSTALL_PLAN_ID, e);
1405            return now;
1406        }
1407        if let Err(e) = storage.set_time(UPDATE_FIRST_SEEN_TIME, now).await {
1408            error!("Unable to persist {}: {}", UPDATE_FIRST_SEEN_TIME, e);
1409            let _ = storage.remove(INSTALL_PLAN_ID).await;
1410            return now;
1411        }
1412        storage.commit_or_log().await;
1413        now
1414    }
1415}
1416
1417/// Return a random number in [n - range / 2, n - range / 2 + range).
1418fn randomize(n: u64, range: u64) -> u64 {
1419    n - range / 2 + rand::random::<u64>() % range
1420}
1421
1422#[cfg(test)]
1423impl<PE, HR, IN, TM, MR, ST, AS, IR, PL, CH> StateMachine<PE, HR, IN, TM, MR, ST, AS, CH>
1424where
1425    PE: PolicyEngine<InstallResult = IR, InstallPlan = PL>,
1426    HR: HttpRequest,
1427    IN: Installer<InstallResult = IR, InstallPlan = PL>,
1428    TM: Timer,
1429    MR: MetricsReporter,
1430    ST: Storage,
1431    AS: AppSet,
1432    CH: Cupv2Handler,
1433    IR: 'static + Send,
1434    PL: Plan,
1435{
1436    /// Run perform_update_check once, returning the update check result.
1437    async fn oneshot(
1438        &mut self,
1439        request_params: RequestParams,
1440    ) -> Result<(update_check::Response, RebootAfterUpdate<IN::InstallResult>), UpdateCheckError>
1441    {
1442        let apps = self.app_set.lock().await.get_apps();
1443
1444        async_generator::generate(move |mut co| async move {
1445            self.perform_update_check(request_params, apps, &mut co).await
1446        })
1447        .into_complete()
1448        .await
1449    }
1450
1451    /// Run start_upate_check once, discarding its states.
1452    async fn run_once(&mut self) {
1453        let request_params = RequestParams::default();
1454
1455        async_generator::generate(move |mut co| async move {
1456            self.start_update_check(request_params, &mut co).await;
1457        })
1458        .map(|_| ())
1459        .collect::<()>()
1460        .await;
1461    }
1462}
1463
1464#[cfg(test)]
1465mod tests {
1466    use super::update_check::{
1467        Action, CONSECUTIVE_FAILED_UPDATE_CHECKS, LAST_UPDATE_TIME, SERVER_DICTATED_POLL_INTERVAL,
1468    };
1469    use super::*;
1470    use crate::{
1471        app_set::VecAppSet,
1472        common::{
1473            App, CheckOptions, PersistedApp, ProtocolState, UpdateCheckSchedule, UserCounting,
1474        },
1475        configuration::Updater,
1476        cup_ecdsa::test_support::{MockCupv2Handler, make_cup_handler_for_test},
1477        http_request::mock::MockHttpRequest,
1478        installer::{
1479            ProgressObserver,
1480            stub::{StubInstallErrors, StubInstaller, StubPlan},
1481        },
1482        metrics::MockMetricsReporter,
1483        policy::{MockPolicyEngine, StubPolicyEngine},
1484        protocol::{Cohort, request::OS, response},
1485        storage::MemStorage,
1486        time::{
1487            MockTimeSource, PartialComplexTime,
1488            timers::{BlockingTimer, MockTimer, RequestedWait},
1489        },
1490        version::Version,
1491    };
1492    use assert_matches::assert_matches;
1493    use futures::executor::{LocalPool, block_on};
1494    use futures::future::LocalBoxFuture;
1495    use futures::task::LocalSpawnExt;
1496    use pretty_assertions::assert_eq;
1497    use serde_json::json;
1498    use std::cell::RefCell;
1499    use std::time::Duration;
1500
1501    fn make_test_app_set() -> Rc<Mutex<VecAppSet>> {
1502        Rc::new(Mutex::new(VecAppSet::new(vec![
1503            App::builder()
1504                .id("{00000000-0000-0000-0000-000000000001}")
1505                .version([1, 2, 3, 4])
1506                .cohort(Cohort::new("stable-channel"))
1507                .build(),
1508        ])))
1509    }
1510
1511    fn make_update_available_response() -> HttpResponse<Vec<u8>> {
1512        let response = json!({"response":{
1513          "server": "prod",
1514          "protocol": "3.0",
1515          "app": [{
1516            "appid": "{00000000-0000-0000-0000-000000000001}",
1517            "status": "ok",
1518            "updatecheck": {
1519              "status": "ok"
1520            }
1521          }],
1522        }});
1523        HttpResponse::new(serde_json::to_vec(&response).unwrap())
1524    }
1525
1526    fn make_noupdate_httpresponse() -> Vec<u8> {
1527        serde_json::to_vec(
1528            &(json!({"response":{
1529              "server": "prod",
1530              "protocol": "3.0",
1531              "app": [{
1532                "appid": "{00000000-0000-0000-0000-000000000001}",
1533                "status": "ok",
1534                "updatecheck": {
1535                  "status": "noupdate"
1536                }
1537              }]
1538            }})),
1539        )
1540        .unwrap()
1541    }
1542
1543    // Assert that the last request made to |http| is equal to the request built by
1544    // |request_builder|.
1545    async fn assert_request<'a>(http: &MockHttpRequest, request_builder: RequestBuilder<'a>) {
1546        let cup_handler = make_cup_handler_for_test();
1547        let (request, _request_metadata) = request_builder.build(Some(&cup_handler)).unwrap();
1548        let body = http_request::to_bytes(request).await.unwrap();
1549        // Compare string instead of Vec<u8> for easier debugging.
1550        let body_str = String::from_utf8_lossy(&body);
1551        http.assert_body_str(&body_str).await;
1552    }
1553
1554    #[test]
1555    fn run_simple_check_with_noupdate_result() {
1556        block_on(async {
1557            let http = MockHttpRequest::new(HttpResponse::new(make_noupdate_httpresponse()));
1558
1559            StateMachineBuilder::new_stub()
1560                .http(http)
1561                .oneshot(RequestParams::default())
1562                .await
1563                .unwrap();
1564
1565            info!("update check complete!");
1566        });
1567    }
1568
1569    #[test]
1570    fn test_cohort_returned_with_noupdate_result() {
1571        block_on(async {
1572            let response = json!({"response":{
1573              "server": "prod",
1574              "protocol": "3.0",
1575              "app": [{
1576                "appid": "{00000000-0000-0000-0000-000000000001}",
1577                "status": "ok",
1578                "cohort": "1",
1579                "cohortname": "stable-channel",
1580                "updatecheck": {
1581                  "status": "noupdate"
1582                }
1583              }]
1584            }});
1585            let response = serde_json::to_vec(&response).unwrap();
1586            let http = MockHttpRequest::new(HttpResponse::new(response));
1587
1588            let (response, reboot_after_update) = StateMachineBuilder::new_stub()
1589                .http(http)
1590                .oneshot(RequestParams::default())
1591                .await
1592                .unwrap();
1593            assert_eq!("{00000000-0000-0000-0000-000000000001}", response.app_responses[0].app_id);
1594            assert_eq!(Some("1".into()), response.app_responses[0].cohort.id);
1595            assert_eq!(Some("stable-channel".into()), response.app_responses[0].cohort.name);
1596            assert_eq!(None, response.app_responses[0].cohort.hint);
1597
1598            assert_matches!(reboot_after_update, RebootAfterUpdate::NotNeeded);
1599        });
1600    }
1601
1602    #[test]
1603    fn test_cohort_returned_with_update_result() {
1604        block_on(async {
1605            let response = json!({"response":{
1606              "server": "prod",
1607              "protocol": "3.0",
1608              "app": [{
1609                "appid": "{00000000-0000-0000-0000-000000000001}",
1610                "status": "ok",
1611                "cohort": "1",
1612                "cohortname": "stable-channel",
1613                "updatecheck": {
1614                  "status": "ok"
1615                }
1616              }]
1617            }});
1618            let response = serde_json::to_vec(&response).unwrap();
1619            let http = MockHttpRequest::new(HttpResponse::new(response));
1620
1621            let (response, reboot_after_update) = StateMachineBuilder::new_stub()
1622                .http(http)
1623                .oneshot(RequestParams::default())
1624                .await
1625                .unwrap();
1626            assert_eq!("{00000000-0000-0000-0000-000000000001}", response.app_responses[0].app_id);
1627            assert_eq!(Some("1".into()), response.app_responses[0].cohort.id);
1628            assert_eq!(Some("stable-channel".into()), response.app_responses[0].cohort.name);
1629            assert_eq!(None, response.app_responses[0].cohort.hint);
1630
1631            assert_matches!(reboot_after_update, RebootAfterUpdate::Needed(()));
1632        });
1633    }
1634
1635    #[test]
1636    fn test_report_parse_response_error() {
1637        block_on(async {
1638            let http = MockHttpRequest::new(HttpResponse::new("invalid response".into()));
1639
1640            let mut state_machine = StateMachineBuilder::new_stub().http(http).build().await;
1641
1642            let response = state_machine.oneshot(RequestParams::default()).await;
1643            assert_matches!(response, Err(UpdateCheckError::ResponseParser(_)));
1644
1645            let request_params = RequestParams::default();
1646            let mut request_builder = RequestBuilder::new(&state_machine.config, &request_params);
1647            let event = Event {
1648                previous_version: Some("1.2.3.4".to_string()),
1649                ..Event::error(EventErrorCode::ParseResponse)
1650            };
1651            let apps = state_machine.app_set.lock().await.get_apps();
1652            request_builder = request_builder
1653                .add_event(&apps[0], event)
1654                .session_id(GUID::from_u128(0))
1655                .request_id(GUID::from_u128(2));
1656            assert_request(&state_machine.http, request_builder).await;
1657        });
1658    }
1659
1660    #[test]
1661    fn test_report_construct_install_plan_error() {
1662        block_on(async {
1663            let response = json!({"response":{
1664              "server": "prod",
1665              "protocol": "4.0",
1666              "app": [{
1667                "appid": "{00000000-0000-0000-0000-000000000001}",
1668                "status": "ok",
1669                "updatecheck": {
1670                  "status": "ok"
1671                }
1672              }],
1673            }});
1674            let response = serde_json::to_vec(&response).unwrap();
1675            let http = MockHttpRequest::new(HttpResponse::new(response));
1676
1677            let mut state_machine = StateMachineBuilder::new_stub().http(http).build().await;
1678
1679            let response = state_machine.oneshot(RequestParams::default()).await;
1680            assert_matches!(response, Err(UpdateCheckError::InstallPlan(_)));
1681
1682            let request_params = RequestParams::default();
1683            let mut request_builder = RequestBuilder::new(&state_machine.config, &request_params);
1684            let event = Event {
1685                previous_version: Some("1.2.3.4".to_string()),
1686                ..Event::error(EventErrorCode::ConstructInstallPlan)
1687            };
1688            let apps = state_machine.app_set.lock().await.get_apps();
1689            request_builder = request_builder
1690                .add_event(&apps[0], event)
1691                .session_id(GUID::from_u128(0))
1692                .request_id(GUID::from_u128(2));
1693            assert_request(&state_machine.http, request_builder).await;
1694        });
1695    }
1696
1697    #[test]
1698    fn test_report_installation_error() {
1699        block_on(async {
1700            let response = json!({"response":{
1701              "server": "prod",
1702              "protocol": "3.0",
1703              "app": [{
1704                "appid": "{00000000-0000-0000-0000-000000000001}",
1705                "status": "ok",
1706                "updatecheck": {
1707                  "status": "ok",
1708                  "manifest": {
1709                      "version": "5.6.7.8",
1710                      "actions": {
1711                          "action": [],
1712                      },
1713                      "packages": {
1714                          "package": [],
1715                      },
1716                  }
1717                }
1718              }],
1719            }});
1720            let response = serde_json::to_vec(&response).unwrap();
1721            let http = MockHttpRequest::new(HttpResponse::new(response));
1722
1723            let mut state_machine = StateMachineBuilder::new_stub()
1724                .http(http)
1725                .installer(StubInstaller { should_fail: true })
1726                .build()
1727                .await;
1728
1729            let (response, reboot_after_update) =
1730                state_machine.oneshot(RequestParams::default()).await.unwrap();
1731            assert_eq!(Action::InstallPlanExecutionError, response.app_responses[0].result);
1732            assert_matches!(reboot_after_update, RebootAfterUpdate::NotNeeded);
1733
1734            let request_params = RequestParams::default();
1735            let mut request_builder = RequestBuilder::new(&state_machine.config, &request_params);
1736            let event = Event {
1737                previous_version: Some("1.2.3.4".to_string()),
1738                next_version: Some("5.6.7.8".to_string()),
1739                download_time_ms: Some(0),
1740                ..Event::error(EventErrorCode::Installation)
1741            };
1742            let apps = state_machine.app_set.lock().await.get_apps();
1743            request_builder = request_builder
1744                .add_event(&apps[0], event)
1745                .session_id(GUID::from_u128(0))
1746                .request_id(GUID::from_u128(3));
1747            assert_request(&state_machine.http, request_builder).await;
1748        });
1749    }
1750
1751    #[test]
1752    fn test_report_installation_error_multi_app() {
1753        block_on(async {
1754            // Intentionally made the app order in response and app_set different.
1755            let response = json!({"response":{
1756              "server": "prod",
1757              "protocol": "3.0",
1758              "app": [{
1759                "appid": "appid_3",
1760                "status": "ok",
1761                "updatecheck": {
1762                  "status": "ok",
1763                  "manifest": {
1764                      "version": "5.6.7.8",
1765                      "actions": {
1766                          "action": [],
1767                      },
1768                      "packages": {
1769                          "package": [],
1770                      },
1771                  }
1772                }
1773              },{
1774                "appid": "appid_1",
1775                "status": "ok",
1776                "updatecheck": {
1777                  "status": "ok",
1778                  "manifest": {
1779                      "version": "1.2.3.4",
1780                      "actions": {
1781                          "action": [],
1782                      },
1783                      "packages": {
1784                          "package": [],
1785                      },
1786                  }
1787                }
1788              },{
1789                "appid": "appid_2",
1790                "status": "ok",
1791                "updatecheck": {
1792                  "status": "noupdate",
1793                }
1794              }],
1795            }});
1796            let response = serde_json::to_vec(&response).unwrap();
1797            let mut http = MockHttpRequest::new(HttpResponse::new(response));
1798            http.add_response(HttpResponse::new(vec![]));
1799            let app_set = VecAppSet::new(vec![
1800                App::builder().id("appid_1").version([1, 2, 3, 3]).build(),
1801                App::builder().id("appid_2").version([9, 9, 9, 9]).build(),
1802                App::builder().id("appid_3").version([5, 6, 7, 7]).build(),
1803            ]);
1804            let app_set = Rc::new(Mutex::new(app_set));
1805            let (send_install, mut recv_install) = mpsc::channel(0);
1806
1807            let mut state_machine = StateMachineBuilder::new_stub()
1808                .app_set(Rc::clone(&app_set))
1809                .http(http)
1810                .installer(BlockingInstaller { on_install: send_install, on_reboot: None })
1811                .build()
1812                .await;
1813
1814            let recv_install_fut = async move {
1815                let unblock_install = recv_install.next().await.unwrap();
1816                unblock_install
1817                    .send(vec![AppInstallResult::Deferred, AppInstallResult::Installed])
1818                    .unwrap();
1819            };
1820
1821            let (oneshot_result, ()) =
1822                future::join(state_machine.oneshot(RequestParams::default()), recv_install_fut)
1823                    .await;
1824            let (response, reboot_after_update) = oneshot_result.unwrap();
1825
1826            assert_eq!("appid_3", response.app_responses[0].app_id);
1827            assert_eq!(Action::DeferredByPolicy, response.app_responses[0].result);
1828            assert_eq!("appid_1", response.app_responses[1].app_id);
1829            assert_eq!(Action::Updated, response.app_responses[1].result);
1830            assert_eq!("appid_2", response.app_responses[2].app_id);
1831            assert_eq!(Action::NoUpdate, response.app_responses[2].result);
1832            assert_matches!(reboot_after_update, RebootAfterUpdate::Needed(()));
1833
1834            let request_params = RequestParams::default();
1835            let apps = app_set.lock().await.get_apps();
1836
1837            let mut request_builder = RequestBuilder::new(&state_machine.config, &request_params);
1838            let event = Event {
1839                previous_version: Some("1.2.3.3".to_string()),
1840                next_version: Some("1.2.3.4".to_string()),
1841                download_time_ms: Some(0),
1842                ..Event::success(EventType::UpdateComplete)
1843            };
1844            request_builder = request_builder
1845                .add_event(&apps[0], event)
1846                .session_id(GUID::from_u128(0))
1847                .request_id(GUID::from_u128(4));
1848            assert_request(&state_machine.http, request_builder).await;
1849
1850            let mut request_builder = RequestBuilder::new(&state_machine.config, &request_params);
1851            let event1 = Event {
1852                previous_version: Some("1.2.3.3".to_string()),
1853                next_version: Some("1.2.3.4".to_string()),
1854                download_time_ms: Some(0),
1855                ..Event::success(EventType::UpdateDownloadFinished)
1856            };
1857            let event2 = Event {
1858                previous_version: Some("5.6.7.7".to_string()),
1859                next_version: Some("5.6.7.8".to_string()),
1860                download_time_ms: Some(0),
1861                event_type: EventType::UpdateComplete,
1862                event_result: EventResult::UpdateDeferred,
1863                ..Event::default()
1864            };
1865            request_builder = request_builder
1866                .add_event(&apps[2], event2)
1867                .add_event(&apps[0], event1)
1868                .session_id(GUID::from_u128(0))
1869                .request_id(GUID::from_u128(3));
1870            assert_request(&state_machine.http, request_builder).await;
1871        });
1872    }
1873
1874    // Test that our observer can see when there's an installation error, and that it gets
1875    // the right error type.
1876    #[test]
1877    fn test_observe_installation_error() {
1878        block_on(async {
1879            let http = MockHttpRequest::new(make_update_available_response());
1880
1881            let actual_errors = StateMachineBuilder::new_stub()
1882                .http(http)
1883                .installer(StubInstaller { should_fail: true })
1884                .oneshot_check()
1885                .await
1886                .filter_map(|event| {
1887                    future::ready(match event {
1888                        StateMachineEvent::InstallerError(Some(e)) => {
1889                            Some(*e.downcast::<StubInstallErrors>().unwrap())
1890                        }
1891                        _ => None,
1892                    })
1893                })
1894                .collect::<Vec<StubInstallErrors>>()
1895                .await;
1896
1897            let expected_errors = vec![StubInstallErrors::Failed];
1898            assert_eq!(actual_errors, expected_errors);
1899        });
1900    }
1901
1902    #[test]
1903    fn test_report_deferred_by_policy() {
1904        block_on(async {
1905            let http = MockHttpRequest::new(make_update_available_response());
1906
1907            let policy_engine = MockPolicyEngine {
1908                update_decision: UpdateDecision::DeferredByPolicy,
1909                ..MockPolicyEngine::default()
1910            };
1911            let mut state_machine = StateMachineBuilder::new_stub()
1912                .policy_engine(policy_engine)
1913                .http(http)
1914                .build()
1915                .await;
1916
1917            let (response, reboot_after_update) =
1918                state_machine.oneshot(RequestParams::default()).await.unwrap();
1919            assert_eq!(Action::DeferredByPolicy, response.app_responses[0].result);
1920            assert_matches!(reboot_after_update, RebootAfterUpdate::NotNeeded);
1921
1922            let request_params = RequestParams::default();
1923            let mut request_builder = RequestBuilder::new(&state_machine.config, &request_params);
1924            let event = Event {
1925                event_type: EventType::UpdateComplete,
1926                event_result: EventResult::UpdateDeferred,
1927                previous_version: Some("1.2.3.4".to_string()),
1928                ..Event::default()
1929            };
1930            let apps = state_machine.app_set.lock().await.get_apps();
1931            request_builder = request_builder
1932                .add_event(&apps[0], event)
1933                .session_id(GUID::from_u128(0))
1934                .request_id(GUID::from_u128(2));
1935            assert_request(&state_machine.http, request_builder).await;
1936        });
1937    }
1938
1939    #[test]
1940    fn test_report_denied_by_policy() {
1941        block_on(async {
1942            let response = make_update_available_response();
1943            let http = MockHttpRequest::new(response);
1944            let policy_engine = MockPolicyEngine {
1945                update_decision: UpdateDecision::DeniedByPolicy,
1946                ..MockPolicyEngine::default()
1947            };
1948
1949            let mut state_machine = StateMachineBuilder::new_stub()
1950                .policy_engine(policy_engine)
1951                .http(http)
1952                .build()
1953                .await;
1954
1955            let (response, reboot_after_update) =
1956                state_machine.oneshot(RequestParams::default()).await.unwrap();
1957            assert_eq!(Action::DeniedByPolicy, response.app_responses[0].result);
1958            assert_matches!(reboot_after_update, RebootAfterUpdate::NotNeeded);
1959
1960            let request_params = RequestParams::default();
1961            let mut request_builder = RequestBuilder::new(&state_machine.config, &request_params);
1962            let event = Event {
1963                previous_version: Some("1.2.3.4".to_string()),
1964                ..Event::error(EventErrorCode::DeniedByPolicy)
1965            };
1966            let apps = state_machine.app_set.lock().await.get_apps();
1967            request_builder = request_builder
1968                .add_event(&apps[0], event)
1969                .session_id(GUID::from_u128(0))
1970                .request_id(GUID::from_u128(2));
1971            assert_request(&state_machine.http, request_builder).await;
1972        });
1973    }
1974
1975    #[test]
1976    fn test_wait_timer() {
1977        let mut pool = LocalPool::new();
1978        let mock_time = MockTimeSource::new_from_now();
1979        let next_update_time = mock_time.now() + Duration::from_secs(111);
1980        let (timer, mut timers) = BlockingTimer::new();
1981        let policy_engine = MockPolicyEngine {
1982            check_timing: Some(CheckTiming::builder().time(next_update_time).build()),
1983            time_source: mock_time,
1984            ..MockPolicyEngine::default()
1985        };
1986
1987        let (_ctl, state_machine) = pool.run_until(
1988            StateMachineBuilder::new_stub().policy_engine(policy_engine).timer(timer).start(),
1989        );
1990
1991        pool.spawner().spawn_local(state_machine.map(|_| ()).collect()).unwrap();
1992
1993        // With otherwise stub implementations, the pool stalls when a timer is awaited.  Dropping
1994        // the state machine will panic if any timer durations were not used.
1995        let blocked_timer = pool.run_until(timers.next()).unwrap();
1996        assert_eq!(blocked_timer.requested_wait(), RequestedWait::Until(next_update_time.into()));
1997    }
1998
1999    #[test]
2000    fn test_cohort_and_user_counting_updates_are_used_in_subsequent_requests() {
2001        block_on(async {
2002            let response = json!({"response":{
2003                "server": "prod",
2004                "protocol": "3.0",
2005                "daystart": {
2006                  "elapsed_days": 1234567,
2007                  "elapsed_seconds": 3645
2008                },
2009                "app": [{
2010                  "appid": "{00000000-0000-0000-0000-000000000001}",
2011                  "status": "ok",
2012                  "cohort": "1",
2013                  "cohortname": "stable-channel",
2014                  "updatecheck": {
2015                    "status": "noupdate"
2016                  }
2017                }]
2018            }});
2019            let response = serde_json::to_vec(&response).unwrap();
2020            let mut http = MockHttpRequest::new(HttpResponse::new(response.clone()));
2021            http.add_response(HttpResponse::new(response));
2022            let apps = make_test_app_set();
2023
2024            let mut state_machine =
2025                StateMachineBuilder::new_stub().http(http).app_set(apps.clone()).build().await;
2026
2027            // Run it the first time.
2028            state_machine.run_once().await;
2029
2030            let apps = apps.lock().await.get_apps();
2031            assert_eq!(Some("1".to_string()), apps[0].cohort.id);
2032            assert_eq!(None, apps[0].cohort.hint);
2033            assert_eq!(Some("stable-channel".to_string()), apps[0].cohort.name);
2034            assert_eq!(UserCounting::ClientRegulatedByDate(Some(1234567)), apps[0].user_counting);
2035
2036            // Run it the second time.
2037            state_machine.run_once().await;
2038
2039            let request_params = RequestParams::default();
2040            let expected_request_builder =
2041                RequestBuilder::new(&state_machine.config, &request_params)
2042                    .add_update_check(&apps[0])
2043                    .add_ping(&apps[0])
2044                    .session_id(GUID::from_u128(2))
2045                    .request_id(GUID::from_u128(3));
2046            // Check that the second update check used the new app.
2047            assert_request(&state_machine.http, expected_request_builder).await;
2048        });
2049    }
2050
2051    #[test]
2052    fn test_user_counting_returned() {
2053        block_on(async {
2054            let response = json!({"response":{
2055            "server": "prod",
2056            "protocol": "3.0",
2057            "daystart": {
2058              "elapsed_days": 1234567,
2059              "elapsed_seconds": 3645
2060            },
2061            "app": [{
2062              "appid": "{00000000-0000-0000-0000-000000000001}",
2063              "status": "ok",
2064              "cohort": "1",
2065              "cohortname": "stable-channel",
2066              "updatecheck": {
2067                "status": "noupdate"
2068                  }
2069              }]
2070            }});
2071            let response = serde_json::to_vec(&response).unwrap();
2072            let http = MockHttpRequest::new(HttpResponse::new(response));
2073
2074            let (response, reboot_after_update) = StateMachineBuilder::new_stub()
2075                .http(http)
2076                .oneshot(RequestParams::default())
2077                .await
2078                .unwrap();
2079
2080            assert_eq!(
2081                UserCounting::ClientRegulatedByDate(Some(1234567)),
2082                response.app_responses[0].user_counting
2083            );
2084            assert_matches!(reboot_after_update, RebootAfterUpdate::NotNeeded);
2085        });
2086    }
2087
2088    #[test]
2089    fn test_observe_state() {
2090        block_on(async {
2091            let actual_states = StateMachineBuilder::new_stub()
2092                .oneshot_check()
2093                .await
2094                .filter_map(|event| {
2095                    future::ready(match event {
2096                        StateMachineEvent::StateChange(state) => Some(state),
2097                        _ => None,
2098                    })
2099                })
2100                .collect::<Vec<State>>()
2101                .await;
2102
2103            let expected_states = vec![
2104                State::CheckingForUpdates(InstallSource::ScheduledTask),
2105                State::ErrorCheckingForUpdate,
2106            ];
2107            assert_eq!(actual_states, expected_states);
2108        });
2109    }
2110
2111    #[test]
2112    fn test_observe_schedule() {
2113        block_on(async {
2114            let mock_time = MockTimeSource::new_from_now();
2115            let actual_schedules = StateMachineBuilder::new_stub()
2116                .policy_engine(StubPolicyEngine::new(&mock_time))
2117                .oneshot_check()
2118                .await
2119                .filter_map(|event| {
2120                    future::ready(match event {
2121                        StateMachineEvent::ScheduleChange(schedule) => Some(schedule),
2122                        _ => None,
2123                    })
2124                })
2125                .collect::<Vec<UpdateCheckSchedule>>()
2126                .await;
2127
2128            // The resultant schedule should only contain the timestamp of the above update check.
2129            let expected_schedule = UpdateCheckSchedule::builder()
2130                .last_update_time(mock_time.now())
2131                .last_update_check_time(mock_time.now())
2132                .build();
2133
2134            assert_eq!(actual_schedules, vec![expected_schedule]);
2135        });
2136    }
2137
2138    #[test]
2139    fn test_observe_protocol_state() {
2140        block_on(async {
2141            let actual_protocol_states = StateMachineBuilder::new_stub()
2142                .oneshot_check()
2143                .await
2144                .filter_map(|event| {
2145                    future::ready(match event {
2146                        StateMachineEvent::ProtocolStateChange(state) => Some(state),
2147                        _ => None,
2148                    })
2149                })
2150                .collect::<Vec<ProtocolState>>()
2151                .await;
2152
2153            let expected_protocol_state =
2154                ProtocolState { consecutive_failed_update_checks: 1, ..ProtocolState::default() };
2155
2156            assert_eq!(actual_protocol_states, vec![expected_protocol_state]);
2157        });
2158    }
2159
2160    #[test]
2161    fn test_observe_omaha_server_response() {
2162        block_on(async {
2163            let response = json!({"response":{
2164              "server": "prod",
2165              "protocol": "3.0",
2166              "app": [{
2167                "appid": "{00000000-0000-0000-0000-000000000001}",
2168                "status": "ok",
2169                "cohort": "1",
2170                "cohortname": "stable-channel",
2171                "updatecheck": {
2172                  "status": "noupdate"
2173                }
2174              }]
2175            }});
2176            let response = serde_json::to_vec(&response).unwrap();
2177            let expected_omaha_response = response::parse_json_response(&response).unwrap();
2178            let http = MockHttpRequest::new(HttpResponse::new(response));
2179
2180            let actual_omaha_response = StateMachineBuilder::new_stub()
2181                .http(http)
2182                .oneshot_check()
2183                .await
2184                .filter_map(|event| {
2185                    future::ready(match event {
2186                        StateMachineEvent::OmahaServerResponse(response) => Some(response),
2187                        _ => None,
2188                    })
2189                })
2190                .collect::<Vec<response::Response>>()
2191                .await;
2192
2193            assert_eq!(actual_omaha_response, vec![expected_omaha_response]);
2194        });
2195    }
2196
2197    #[test]
2198    fn test_metrics_report_omaha_event_lost() {
2199        block_on(async {
2200            // This is sufficient to trigger a lost Omaha event as oneshot triggers an
2201            // update check, which gets the invalid response (but hasn't checked the
2202            // validity yet). This invalid response still contains an OK status, resulting
2203            // in the UpdateCheckResponseTime and RequestsPerCheck events being generated
2204            // reporting success.
2205            //
2206            // The response is then parsed and found to be incorrect; this parse error is
2207            // attempted to be sent back to Omaha as an event with the ParseResponse error
2208            // associated. However, the MockHttpRequest has already consumed the one
2209            // response it knew how to give; this event is reported via HTTP, but is "lost"
2210            // because the mock responds with a 500 error when it has no responses left to
2211            // return.
2212            //
2213            // That finally results in the OmahaEventLost.
2214            let http = MockHttpRequest::new(HttpResponse::new("invalid response".into()));
2215            let mut metrics_reporter = MockMetricsReporter::new();
2216            let _response = StateMachineBuilder::new_stub()
2217                .http(http)
2218                .metrics_reporter(&mut metrics_reporter)
2219                .oneshot(RequestParams::default())
2220                .await;
2221
2222            // FIXME(https://github.com/rust-lang/rustfmt/issues/4530) rustfmt doesn't wrap slice
2223            // patterns yet.
2224            #[rustfmt::skip]
2225            assert_matches!(
2226                metrics_reporter.metrics.as_slice(),
2227                [
2228                    Metrics::UpdateCheckResponseTime { response_time: _, successful: true },
2229                    Metrics::RequestsPerCheck { count: 1, successful: true },
2230                    Metrics::OmahaEventLost(Event {
2231                        event_type: EventType::UpdateComplete,
2232                        event_result: EventResult::Error,
2233                        errorcode: Some(EventErrorCode::ParseResponse),
2234                        previous_version: None,
2235                        next_version: None,
2236                        download_time_ms: None,
2237                    })
2238                ]
2239            );
2240        });
2241    }
2242
2243    #[test]
2244    fn test_metrics_report_update_check_response_time() {
2245        block_on(async {
2246            let mut metrics_reporter = MockMetricsReporter::new();
2247            let _response = StateMachineBuilder::new_stub()
2248                .metrics_reporter(&mut metrics_reporter)
2249                .oneshot(RequestParams::default())
2250                .await;
2251
2252            // FIXME(https://github.com/rust-lang/rustfmt/issues/4530) rustfmt doesn't wrap slice
2253            // patterns yet.
2254            #[rustfmt::skip]
2255            assert_matches!(
2256                metrics_reporter.metrics.as_slice(),
2257                [
2258                    Metrics::UpdateCheckResponseTime { response_time: _, successful: true },
2259                    Metrics::RequestsPerCheck { count: 1, successful: true },
2260                ]
2261            );
2262        });
2263    }
2264
2265    #[test]
2266    fn test_metrics_report_update_check_response_time_on_failure() {
2267        block_on(async {
2268            let mut metrics_reporter = MockMetricsReporter::new();
2269            let mut http = MockHttpRequest::default();
2270
2271            for _ in 0..MAX_OMAHA_REQUEST_ATTEMPTS {
2272                http.add_error(http_request::mock_errors::make_transport_error());
2273            }
2274
2275            // Note: we exit the update loop before we fetch the successful result, so we never see
2276            // this result.
2277            http.add_response(hyper::Response::default());
2278
2279            let _response = StateMachineBuilder::new_stub()
2280                .http(http)
2281                .metrics_reporter(&mut metrics_reporter)
2282                .oneshot(RequestParams::default())
2283                .await;
2284
2285            // FIXME(https://github.com/rust-lang/rustfmt/issues/4530) rustfmt doesn't wrap slice
2286            // patterns yet.
2287            #[rustfmt::skip]
2288            assert_matches!(
2289                metrics_reporter.metrics.as_slice(),
2290                [
2291                    Metrics::UpdateCheckResponseTime { response_time: _, successful: false },
2292                    Metrics::UpdateCheckResponseTime { response_time: _, successful: false },
2293                    Metrics::UpdateCheckResponseTime { response_time: _, successful: false },
2294                    Metrics::RequestsPerCheck { count: 3, successful: false },
2295                ]
2296            );
2297        });
2298    }
2299
2300    #[test]
2301    fn test_metrics_report_update_check_response_time_on_failure_followed_by_success() {
2302        block_on(async {
2303            let mut metrics_reporter = MockMetricsReporter::new();
2304            let mut http = MockHttpRequest::default();
2305
2306            for _ in 0..MAX_OMAHA_REQUEST_ATTEMPTS - 1 {
2307                http.add_error(http_request::mock_errors::make_transport_error());
2308            }
2309            http.add_response(hyper::Response::default());
2310
2311            let _response = StateMachineBuilder::new_stub()
2312                .http(http)
2313                .metrics_reporter(&mut metrics_reporter)
2314                .oneshot(RequestParams::default())
2315                .await;
2316
2317            // FIXME(https://github.com/rust-lang/rustfmt/issues/4530) rustfmt doesn't wrap slice
2318            // patterns yet.
2319            #[rustfmt::skip]
2320            assert_matches!(
2321                metrics_reporter.metrics.as_slice(),
2322                [
2323                    Metrics::UpdateCheckResponseTime { response_time: _, successful: false },
2324                    Metrics::UpdateCheckResponseTime { response_time: _, successful: false },
2325                    Metrics::UpdateCheckResponseTime { response_time: _, successful: true },
2326                    Metrics::RequestsPerCheck { count: 3, successful: true },
2327                    Metrics::OmahaEventLost(Event {
2328                        event_type: EventType::UpdateComplete,
2329                        event_result: EventResult::Error,
2330                        errorcode: Some(EventErrorCode::ParseResponse),
2331                        previous_version: None,
2332                        next_version: None,
2333                        download_time_ms: None
2334                    }),
2335                ]
2336            );
2337        });
2338    }
2339
2340    #[test]
2341    fn test_metrics_report_requests_per_check() {
2342        block_on(async {
2343            let mut metrics_reporter = MockMetricsReporter::new();
2344            let _response = StateMachineBuilder::new_stub()
2345                .metrics_reporter(&mut metrics_reporter)
2346                .oneshot(RequestParams::default())
2347                .await;
2348
2349            assert!(
2350                metrics_reporter
2351                    .metrics
2352                    .contains(&Metrics::RequestsPerCheck { count: 1, successful: true })
2353            );
2354        });
2355    }
2356
2357    #[test]
2358    fn test_metrics_report_requests_per_check_on_failure_followed_by_success() {
2359        block_on(async {
2360            let mut metrics_reporter = MockMetricsReporter::new();
2361            let mut http = MockHttpRequest::default();
2362
2363            for _ in 0..MAX_OMAHA_REQUEST_ATTEMPTS - 1 {
2364                http.add_error(http_request::mock_errors::make_transport_error());
2365            }
2366
2367            http.add_response(hyper::Response::default());
2368
2369            let _response = StateMachineBuilder::new_stub()
2370                .http(http)
2371                .metrics_reporter(&mut metrics_reporter)
2372                .oneshot(RequestParams::default())
2373                .await;
2374
2375            assert!(!metrics_reporter.metrics.is_empty());
2376            assert!(metrics_reporter.metrics.contains(&Metrics::RequestsPerCheck {
2377                count: MAX_OMAHA_REQUEST_ATTEMPTS,
2378                successful: true
2379            }));
2380        });
2381    }
2382
2383    #[test]
2384    fn test_metrics_report_requests_per_check_on_failure() {
2385        block_on(async {
2386            let mut metrics_reporter = MockMetricsReporter::new();
2387            let mut http = MockHttpRequest::default();
2388
2389            for _ in 0..MAX_OMAHA_REQUEST_ATTEMPTS {
2390                http.add_error(http_request::mock_errors::make_transport_error());
2391            }
2392
2393            // Note we will give up before we get this successful request.
2394            http.add_response(hyper::Response::default());
2395
2396            let _response = StateMachineBuilder::new_stub()
2397                .http(http)
2398                .metrics_reporter(&mut metrics_reporter)
2399                .oneshot(RequestParams::default())
2400                .await;
2401
2402            assert!(!metrics_reporter.metrics.is_empty());
2403            assert!(metrics_reporter.metrics.contains(&Metrics::RequestsPerCheck {
2404                count: MAX_OMAHA_REQUEST_ATTEMPTS,
2405                successful: false
2406            }));
2407        });
2408    }
2409
2410    #[test]
2411    fn test_requests_per_check_backoff_with_mock_timer() {
2412        block_on(async {
2413            let mut timer = MockTimer::new();
2414            timer.expect_for_range(Duration::from_millis(500), Duration::from_millis(1500));
2415            timer.expect_for_range(Duration::from_millis(1500), Duration::from_millis(2500));
2416            let requested_waits = timer.get_requested_waits_view();
2417            let response = StateMachineBuilder::new_stub()
2418                .http(MockHttpRequest::empty())
2419                .timer(timer)
2420                .oneshot(RequestParams::default())
2421                .await;
2422
2423            let waits = requested_waits.borrow();
2424            assert_eq!(waits.len(), 2);
2425            assert_matches!(
2426                waits[0],
2427                RequestedWait::For(d) if d >= Duration::from_millis(500) && d <= Duration::from_millis(1500)
2428            );
2429            assert_matches!(
2430                waits[1],
2431                RequestedWait::For(d) if d >= Duration::from_millis(1500) && d <= Duration::from_millis(2500)
2432            );
2433
2434            assert_matches!(
2435                response,
2436                Err(UpdateCheckError::OmahaRequest(OmahaRequestError::HttpStatus(_)))
2437            );
2438        });
2439    }
2440
2441    #[test]
2442    fn test_metrics_report_update_check_failure_reason_omaha() {
2443        block_on(async {
2444            let mut metrics_reporter = MockMetricsReporter::new();
2445            let mut state_machine = StateMachineBuilder::new_stub()
2446                .metrics_reporter(&mut metrics_reporter)
2447                .build()
2448                .await;
2449
2450            state_machine.run_once().await;
2451
2452            assert!(
2453                metrics_reporter
2454                    .metrics
2455                    .contains(&Metrics::UpdateCheckFailureReason(UpdateCheckFailureReason::Omaha))
2456            );
2457        });
2458    }
2459
2460    #[test]
2461    fn test_metrics_report_update_check_failure_reason_network() {
2462        block_on(async {
2463            let mut metrics_reporter = MockMetricsReporter::new();
2464            let mut state_machine = StateMachineBuilder::new_stub()
2465                .http(MockHttpRequest::empty())
2466                .metrics_reporter(&mut metrics_reporter)
2467                .build()
2468                .await;
2469
2470            state_machine.run_once().await;
2471
2472            assert!(
2473                metrics_reporter.metrics.contains(&Metrics::UpdateCheckFailureReason(
2474                    UpdateCheckFailureReason::Network
2475                ))
2476            );
2477        });
2478    }
2479
2480    #[test]
2481    fn test_persist_last_update_time() {
2482        block_on(async {
2483            let storage = Rc::new(Mutex::new(MemStorage::new()));
2484
2485            StateMachineBuilder::new_stub()
2486                .storage(Rc::clone(&storage))
2487                .oneshot_check()
2488                .await
2489                .map(|_| ())
2490                .collect::<()>()
2491                .await;
2492
2493            let storage = storage.lock().await;
2494            storage.get_int(LAST_UPDATE_TIME).await.unwrap();
2495            assert!(storage.committed());
2496        });
2497    }
2498
2499    #[test]
2500    fn test_persist_server_dictated_poll_interval() {
2501        block_on(async {
2502            let response = HttpResponse::builder()
2503                .header(X_RETRY_AFTER, 1234)
2504                .body(make_noupdate_httpresponse())
2505                .unwrap();
2506            let http = MockHttpRequest::new(response);
2507            let storage = Rc::new(Mutex::new(MemStorage::new()));
2508
2509            let mut state_machine = StateMachineBuilder::new_stub()
2510                .http(http)
2511                .storage(Rc::clone(&storage))
2512                .build()
2513                .await;
2514            state_machine.oneshot(RequestParams::default()).await.unwrap();
2515
2516            assert_eq!(
2517                state_machine.context.state.server_dictated_poll_interval,
2518                Some(Duration::from_secs(1234))
2519            );
2520
2521            let storage = storage.lock().await;
2522            assert_eq!(storage.get_int(SERVER_DICTATED_POLL_INTERVAL).await, Some(1234000000));
2523            assert!(storage.committed());
2524        });
2525    }
2526
2527    #[test]
2528    fn test_persist_server_dictated_poll_interval_http_error() {
2529        block_on(async {
2530            let response = HttpResponse::builder()
2531                .status(hyper::StatusCode::INTERNAL_SERVER_ERROR)
2532                .header(X_RETRY_AFTER, 1234)
2533                .body(vec![])
2534                .unwrap();
2535            let http = MockHttpRequest::new(response);
2536            let storage = Rc::new(Mutex::new(MemStorage::new()));
2537
2538            let mut state_machine = StateMachineBuilder::new_stub()
2539                .http(http)
2540                .storage(Rc::clone(&storage))
2541                .build()
2542                .await;
2543            assert_matches!(
2544                state_machine.oneshot(RequestParams::default()).await,
2545                Err(UpdateCheckError::OmahaRequest(OmahaRequestError::HttpStatus(_)))
2546            );
2547
2548            assert_eq!(
2549                state_machine.context.state.server_dictated_poll_interval,
2550                Some(Duration::from_secs(1234))
2551            );
2552
2553            let storage = storage.lock().await;
2554            assert_eq!(storage.get_int(SERVER_DICTATED_POLL_INTERVAL).await, Some(1234000000));
2555            assert!(storage.committed());
2556        });
2557    }
2558
2559    #[test]
2560    fn test_persist_server_dictated_poll_interval_max_duration() {
2561        block_on(async {
2562            let response = HttpResponse::builder()
2563                .status(hyper::StatusCode::INTERNAL_SERVER_ERROR)
2564                .header(X_RETRY_AFTER, 123456789)
2565                .body(vec![])
2566                .unwrap();
2567            let http = MockHttpRequest::new(response);
2568            let storage = Rc::new(Mutex::new(MemStorage::new()));
2569
2570            let mut state_machine = StateMachineBuilder::new_stub()
2571                .http(http)
2572                .storage(Rc::clone(&storage))
2573                .build()
2574                .await;
2575            assert_matches!(
2576                state_machine.oneshot(RequestParams::default()).await,
2577                Err(UpdateCheckError::OmahaRequest(OmahaRequestError::HttpStatus(_)))
2578            );
2579
2580            assert_eq!(
2581                state_machine.context.state.server_dictated_poll_interval,
2582                Some(Duration::from_secs(86400))
2583            );
2584
2585            let storage = storage.lock().await;
2586            assert_eq!(storage.get_int(SERVER_DICTATED_POLL_INTERVAL).await, Some(86400000000));
2587            assert!(storage.committed());
2588        });
2589    }
2590
2591    #[test]
2592    fn test_server_dictated_poll_interval_with_transport_error_no_retry() {
2593        block_on(async {
2594            let mut http = MockHttpRequest::empty();
2595            http.add_error(http_request::mock_errors::make_transport_error());
2596            let mut storage = MemStorage::new();
2597            let _ = storage.set_int(SERVER_DICTATED_POLL_INTERVAL, 1234000000).await;
2598            let _ = storage.commit().await;
2599            let storage = Rc::new(Mutex::new(storage));
2600
2601            let mut state_machine = StateMachineBuilder::new_stub()
2602                .http(http)
2603                .storage(Rc::clone(&storage))
2604                .build()
2605                .await;
2606            // This verifies that state machine does not retry because MockHttpRequest will only
2607            // return the transport error on the first request, any additional requests will get
2608            // HttpStatus error.
2609            assert_matches!(
2610                state_machine.oneshot(RequestParams::default()).await,
2611                Err(UpdateCheckError::OmahaRequest(OmahaRequestError::HttpTransport(_)))
2612            );
2613
2614            assert_eq!(
2615                state_machine.context.state.server_dictated_poll_interval,
2616                Some(Duration::from_secs(1234))
2617            );
2618        });
2619    }
2620
2621    #[test]
2622    fn test_persist_app() {
2623        block_on(async {
2624            let storage = Rc::new(Mutex::new(MemStorage::new()));
2625            let app_set = make_test_app_set();
2626
2627            StateMachineBuilder::new_stub()
2628                .storage(Rc::clone(&storage))
2629                .app_set(app_set.clone())
2630                .oneshot_check()
2631                .await
2632                .map(|_| ())
2633                .collect::<()>()
2634                .await;
2635
2636            let storage = storage.lock().await;
2637            let apps = app_set.lock().await.get_apps();
2638            storage.get_string(&apps[0].id).await.unwrap();
2639            assert!(storage.committed());
2640        });
2641    }
2642
2643    #[test]
2644    fn test_load_last_update_time() {
2645        block_on(async {
2646            let mut storage = MemStorage::new();
2647            let mut mock_time = MockTimeSource::new_from_now();
2648            mock_time.truncate_submicrosecond_walltime();
2649            let last_update_time = mock_time.now_in_walltime() - Duration::from_secs(999);
2650            storage.set_time(LAST_UPDATE_TIME, last_update_time).await.unwrap();
2651
2652            let state_machine = StateMachineBuilder::new_stub()
2653                .policy_engine(StubPolicyEngine::new(&mock_time))
2654                .storage(Rc::new(Mutex::new(storage)))
2655                .build()
2656                .await;
2657
2658            assert_eq!(
2659                state_machine.context.schedule.last_update_time.unwrap(),
2660                PartialComplexTime::Wall(last_update_time)
2661            );
2662        });
2663    }
2664
2665    #[test]
2666    fn test_load_server_dictated_poll_interval() {
2667        block_on(async {
2668            let mut storage = MemStorage::new();
2669            storage.set_int(SERVER_DICTATED_POLL_INTERVAL, 56789).await.unwrap();
2670
2671            let state_machine =
2672                StateMachineBuilder::new_stub().storage(Rc::new(Mutex::new(storage))).build().await;
2673
2674            assert_eq!(
2675                Some(Duration::from_micros(56789)),
2676                state_machine.context.state.server_dictated_poll_interval
2677            );
2678        });
2679    }
2680
2681    #[test]
2682    fn test_load_app() {
2683        block_on(async {
2684            let app_set = VecAppSet::new(vec![
2685                App::builder()
2686                    .id("{00000000-0000-0000-0000-000000000001}")
2687                    .version([1, 2, 3, 4])
2688                    .build(),
2689            ]);
2690            let mut storage = MemStorage::new();
2691            let persisted_app = PersistedApp {
2692                cohort: Cohort {
2693                    id: Some("cohort_id".to_string()),
2694                    hint: Some("test_channel".to_string()),
2695                    name: None,
2696                },
2697                user_counting: UserCounting::ClientRegulatedByDate(Some(22222)),
2698            };
2699            let json = serde_json::to_string(&persisted_app).unwrap();
2700            let apps = app_set.get_apps();
2701            storage.set_string(&apps[0].id, &json).await.unwrap();
2702
2703            let app_set = Rc::new(Mutex::new(app_set));
2704
2705            let _state_machine = StateMachineBuilder::new_stub()
2706                .storage(Rc::new(Mutex::new(storage)))
2707                .app_set(Rc::clone(&app_set))
2708                .build()
2709                .await;
2710
2711            let apps = app_set.lock().await.get_apps();
2712            assert_eq!(persisted_app.cohort, apps[0].cohort);
2713            assert_eq!(UserCounting::ClientRegulatedByDate(Some(22222)), apps[0].user_counting);
2714        });
2715    }
2716
2717    #[test]
2718    fn test_report_check_interval_with_no_storage() {
2719        block_on(async {
2720            let mut mock_time = MockTimeSource::new_from_now();
2721            let mut state_machine = StateMachineBuilder::new_stub()
2722                .policy_engine(StubPolicyEngine::new(mock_time.clone()))
2723                .metrics_reporter(MockMetricsReporter::new())
2724                .build()
2725                .await;
2726
2727            state_machine.report_check_interval(InstallSource::ScheduledTask).await;
2728            // No metrics should be reported because no LAST_UPDATE_TIME in storage.
2729            assert!(state_machine.metrics_reporter.metrics.is_empty());
2730
2731            // A second update check should report metrics.
2732            let interval = Duration::from_micros(999999);
2733            mock_time.advance(interval);
2734
2735            state_machine.report_check_interval(InstallSource::ScheduledTask).await;
2736
2737            assert_eq!(
2738                state_machine.metrics_reporter.metrics,
2739                vec![Metrics::UpdateCheckInterval {
2740                    interval,
2741                    clock: ClockType::Monotonic,
2742                    install_source: InstallSource::ScheduledTask,
2743                }]
2744            );
2745        });
2746    }
2747
2748    #[test]
2749    fn test_report_check_interval_mono_transition() {
2750        block_on(async {
2751            let mut mock_time = MockTimeSource::new_from_now();
2752            let mut state_machine = StateMachineBuilder::new_stub()
2753                .policy_engine(StubPolicyEngine::new(mock_time.clone()))
2754                .metrics_reporter(MockMetricsReporter::new())
2755                .build()
2756                .await;
2757
2758            // Make sure that, provided a wall time, we get an initial report
2759            // using the wall time.
2760            let initial_duration = Duration::from_secs(999);
2761            let initial_time = mock_time.now_in_walltime() - initial_duration;
2762            state_machine.context.schedule.last_update_check_time =
2763                Some(PartialComplexTime::Wall(initial_time));
2764            state_machine.report_check_interval(InstallSource::ScheduledTask).await;
2765
2766            // Advance one more time, and this time we should see a monotonic delta.
2767            let interval = Duration::from_micros(999999);
2768            mock_time.advance(interval);
2769            state_machine.report_check_interval(InstallSource::ScheduledTask).await;
2770
2771            // One final time, to demonstrate monotonic time edges to
2772            // monotonic time.
2773            mock_time.advance(interval);
2774            state_machine.report_check_interval(InstallSource::ScheduledTask).await;
2775            assert_eq!(
2776                state_machine.metrics_reporter.metrics,
2777                vec![
2778                    Metrics::UpdateCheckInterval {
2779                        interval: initial_duration,
2780                        clock: ClockType::Wall,
2781                        install_source: InstallSource::ScheduledTask,
2782                    },
2783                    Metrics::UpdateCheckInterval {
2784                        interval,
2785                        clock: ClockType::Monotonic,
2786                        install_source: InstallSource::ScheduledTask,
2787                    },
2788                    Metrics::UpdateCheckInterval {
2789                        interval,
2790                        clock: ClockType::Monotonic,
2791                        install_source: InstallSource::ScheduledTask,
2792                    },
2793                ]
2794            );
2795        });
2796    }
2797
2798    #[derive(Debug)]
2799    pub struct TestInstaller {
2800        reboot_called: Rc<RefCell<bool>>,
2801        install_fails: usize,
2802        mock_time: MockTimeSource,
2803    }
2804    struct TestInstallerBuilder {
2805        install_fails: usize,
2806        mock_time: MockTimeSource,
2807    }
2808    impl TestInstaller {
2809        fn builder(mock_time: MockTimeSource) -> TestInstallerBuilder {
2810            TestInstallerBuilder { install_fails: 0, mock_time }
2811        }
2812    }
2813    impl TestInstallerBuilder {
2814        fn add_install_fail(mut self) -> Self {
2815            self.install_fails += 1;
2816            self
2817        }
2818        fn build(self) -> TestInstaller {
2819            TestInstaller {
2820                reboot_called: Rc::new(RefCell::new(false)),
2821                install_fails: self.install_fails,
2822                mock_time: self.mock_time,
2823            }
2824        }
2825    }
2826    const INSTALL_DURATION: Duration = Duration::from_micros(98765433);
2827
2828    impl Installer for TestInstaller {
2829        type InstallPlan = StubPlan;
2830        type Error = StubInstallErrors;
2831        type InstallResult = ();
2832
2833        fn perform_install<'a>(
2834            &'a mut self,
2835            _install_plan: &StubPlan,
2836            observer: Option<&'a dyn ProgressObserver>,
2837        ) -> LocalBoxFuture<'a, (Self::InstallResult, Vec<AppInstallResult<Self::Error>>)> {
2838            if self.install_fails > 0 {
2839                self.install_fails -= 1;
2840                future::ready(((), vec![AppInstallResult::Failed(StubInstallErrors::Failed)]))
2841                    .boxed()
2842            } else {
2843                self.mock_time.advance(INSTALL_DURATION);
2844                async move {
2845                    if let Some(observer) = observer {
2846                        observer.receive_progress(None, 0.0, None, None).await;
2847                        observer.receive_progress(None, 0.3, None, None).await;
2848                        observer.receive_progress(None, 0.9, None, None).await;
2849                        observer.receive_progress(None, 1.0, None, None).await;
2850                    }
2851                    ((), vec![AppInstallResult::Installed])
2852                }
2853                .boxed_local()
2854            }
2855        }
2856
2857        fn perform_reboot(&mut self) -> LocalBoxFuture<'_, Result<(), anyhow::Error>> {
2858            self.reboot_called.replace(true);
2859            future::ready(Ok(())).boxed_local()
2860        }
2861
2862        fn try_create_install_plan<'a>(
2863            &'a self,
2864            _request_params: &'a RequestParams,
2865            _request_metadata: Option<&'a RequestMetadata>,
2866            _response: &'a Response,
2867            _response_bytes: Vec<u8>,
2868            _ecdsa_signature: Option<Vec<u8>>,
2869        ) -> LocalBoxFuture<'a, Result<Self::InstallPlan, Self::Error>> {
2870            future::ready(Ok(StubPlan)).boxed_local()
2871        }
2872    }
2873
2874    #[test]
2875    fn test_report_successful_update_duration() {
2876        block_on(async {
2877            let http = MockHttpRequest::new(make_update_available_response());
2878            let storage = Rc::new(Mutex::new(MemStorage::new()));
2879
2880            let mut mock_time = MockTimeSource::new_from_now();
2881            mock_time.truncate_submicrosecond_walltime();
2882            let now = mock_time.now();
2883
2884            let update_completed_time = now + INSTALL_DURATION;
2885            let expected_update_duration = update_completed_time.wall_duration_since(now).unwrap();
2886
2887            let first_seen_time = now - Duration::from_micros(1000);
2888
2889            let expected_duration_since_first_seen =
2890                update_completed_time.wall_duration_since(first_seen_time).unwrap();
2891
2892            let mut state_machine = StateMachineBuilder::new_stub()
2893                .http(http)
2894                .installer(TestInstaller::builder(mock_time.clone()).build())
2895                .policy_engine(StubPolicyEngine::new(mock_time.clone()))
2896                .metrics_reporter(MockMetricsReporter::new())
2897                .storage(Rc::clone(&storage))
2898                .build()
2899                .await;
2900
2901            {
2902                let mut storage = storage.lock().await;
2903                storage.set_string(INSTALL_PLAN_ID, "").await.unwrap();
2904                storage.set_time(UPDATE_FIRST_SEEN_TIME, first_seen_time).await.unwrap();
2905                storage.commit().await.unwrap();
2906            }
2907
2908            state_machine.run_once().await;
2909
2910            #[rustfmt::skip]
2911            assert_matches!(
2912                state_machine.metrics_reporter.metrics.as_slice(),
2913                [
2914                    Metrics::UpdateCheckResponseTime { response_time: _, successful: true },
2915                    Metrics::RequestsPerCheck { count: 1, successful: true },
2916                    Metrics::OmahaEventLost(Event { event_type: EventType::UpdateDownloadStarted, event_result: EventResult::Success, .. }),
2917                    Metrics::SuccessfulUpdateDuration(install_duration),
2918                    Metrics::OmahaEventLost(Event { event_type: EventType::UpdateDownloadFinished, event_result: EventResult::Success, .. }),
2919                    Metrics::OmahaEventLost(Event { event_type: EventType::UpdateComplete, event_result: EventResult::Success, .. }),
2920                    Metrics::SuccessfulUpdateFromFirstSeen(duration_since_first_seen),
2921                    Metrics::AttemptsToSuccessfulCheck(1),
2922                    Metrics::AttemptsToSuccessfulInstall { count: 1, successful: true },
2923                ]
2924                if
2925                    *install_duration == expected_update_duration &&
2926                    *duration_since_first_seen == expected_duration_since_first_seen
2927            );
2928        });
2929    }
2930
2931    #[test]
2932    fn test_report_failed_update_duration() {
2933        block_on(async {
2934            let http = MockHttpRequest::new(make_update_available_response());
2935            let mut state_machine = StateMachineBuilder::new_stub()
2936                .http(http)
2937                .installer(StubInstaller { should_fail: true })
2938                .metrics_reporter(MockMetricsReporter::new())
2939                .build()
2940                .await;
2941            // clock::mock::set(time::i64_to_time(123456789));
2942
2943            state_machine.run_once().await;
2944
2945            assert!(
2946                state_machine
2947                    .metrics_reporter
2948                    .metrics
2949                    .contains(&Metrics::FailedUpdateDuration(Duration::from_micros(0)))
2950            );
2951        });
2952    }
2953
2954    #[test]
2955    fn test_record_update_first_seen_time() {
2956        block_on(async {
2957            let storage = Rc::new(Mutex::new(MemStorage::new()));
2958            let mut state_machine =
2959                StateMachineBuilder::new_stub().storage(Rc::clone(&storage)).build().await;
2960
2961            let mut mock_time = MockTimeSource::new_from_now();
2962            mock_time.truncate_submicrosecond_walltime();
2963            let now = mock_time.now_in_walltime();
2964            assert_eq!(state_machine.record_update_first_seen_time("id", now).await, now);
2965            {
2966                let storage = storage.lock().await;
2967                assert_eq!(storage.get_string(INSTALL_PLAN_ID).await, Some("id".to_string()));
2968                assert_eq!(storage.get_time(UPDATE_FIRST_SEEN_TIME).await, Some(now));
2969                assert_eq!(storage.len(), 2);
2970                assert!(storage.committed());
2971            }
2972
2973            mock_time.advance(Duration::from_secs(1000));
2974            let now2 = mock_time.now_in_walltime();
2975            assert_eq!(state_machine.record_update_first_seen_time("id", now2).await, now);
2976            {
2977                let storage = storage.lock().await;
2978                assert_eq!(storage.get_string(INSTALL_PLAN_ID).await, Some("id".to_string()));
2979                assert_eq!(storage.get_time(UPDATE_FIRST_SEEN_TIME).await, Some(now));
2980                assert_eq!(storage.len(), 2);
2981                assert!(storage.committed());
2982            }
2983            assert_eq!(state_machine.record_update_first_seen_time("id2", now2).await, now2);
2984            {
2985                let storage = storage.lock().await;
2986                assert_eq!(storage.get_string(INSTALL_PLAN_ID).await, Some("id2".to_string()));
2987                assert_eq!(storage.get_time(UPDATE_FIRST_SEEN_TIME).await, Some(now2));
2988                assert_eq!(storage.len(), 2);
2989                assert!(storage.committed());
2990            }
2991        });
2992    }
2993
2994    #[test]
2995    fn test_report_attempts_to_successful_check() {
2996        block_on(async {
2997            let storage = Rc::new(Mutex::new(MemStorage::new()));
2998            let mut state_machine = StateMachineBuilder::new_stub()
2999                .installer(StubInstaller { should_fail: true })
3000                .metrics_reporter(MockMetricsReporter::new())
3001                .storage(Rc::clone(&storage))
3002                .build()
3003                .await;
3004
3005            state_machine.report_attempts_to_successful_check(true).await;
3006
3007            // consecutive_failed_update_attempts should be zero (there were no previous failures)
3008            // but we should record an attempt in metrics
3009            assert_eq!(state_machine.context.state.consecutive_failed_update_checks, 0);
3010            assert_eq!(
3011                state_machine.metrics_reporter.metrics,
3012                vec![Metrics::AttemptsToSuccessfulCheck(1)]
3013            );
3014
3015            state_machine.report_attempts_to_successful_check(false).await;
3016            assert_eq!(state_machine.context.state.consecutive_failed_update_checks, 1);
3017
3018            state_machine.report_attempts_to_successful_check(false).await;
3019            assert_eq!(state_machine.context.state.consecutive_failed_update_checks, 2);
3020
3021            // consecutive_failed_update_attempts should be reset to zero on success
3022            // but we should record the previous number of failed attempts (2) + 1 in metrics
3023            state_machine.report_attempts_to_successful_check(true).await;
3024            assert_eq!(state_machine.context.state.consecutive_failed_update_checks, 0);
3025            assert_eq!(
3026                state_machine.metrics_reporter.metrics,
3027                vec![Metrics::AttemptsToSuccessfulCheck(1), Metrics::AttemptsToSuccessfulCheck(3)]
3028            );
3029        });
3030    }
3031
3032    #[test]
3033    fn test_ping_omaha_updates_consecutive_failed_update_checks_and_persists() {
3034        block_on(async {
3035            let mut http = MockHttpRequest::empty();
3036            http.add_error(http_request::mock_errors::make_transport_error());
3037            http.add_response(HttpResponse::new(vec![]));
3038            let response = json!({"response":{
3039              "server": "prod",
3040              "protocol": "3.0",
3041              "app": [{
3042                "appid": "{00000000-0000-0000-0000-000000000001}",
3043                "status": "ok",
3044              }],
3045            }});
3046            let response = serde_json::to_vec(&response).unwrap();
3047            http.add_response(HttpResponse::new(response));
3048
3049            let storage = Rc::new(Mutex::new(MemStorage::new()));
3050
3051            // Start out with a value in storage...
3052            {
3053                let mut storage = storage.lock().await;
3054                let _ = storage.set_int(CONSECUTIVE_FAILED_UPDATE_CHECKS, 1).await;
3055                let _ = storage.commit().await;
3056            }
3057
3058            let mut state_machine = StateMachineBuilder::new_stub()
3059                .storage(Rc::clone(&storage))
3060                .http(http)
3061                .build()
3062                .await;
3063
3064            async_generator::generate(move |mut co| async move {
3065                // Failed ping increases `consecutive_failed_update_checks`, adding the value from
3066                // storage.
3067                state_machine.ping_omaha(&mut co).await;
3068                assert_eq!(state_machine.context.state.consecutive_failed_update_checks, 2);
3069                {
3070                    let storage = storage.lock().await;
3071                    assert_eq!(storage.get_int(CONSECUTIVE_FAILED_UPDATE_CHECKS).await, Some(2));
3072                }
3073
3074                state_machine.ping_omaha(&mut co).await;
3075                assert_eq!(state_machine.context.state.consecutive_failed_update_checks, 3);
3076                {
3077                    let storage = storage.lock().await;
3078                    assert_eq!(storage.get_int(CONSECUTIVE_FAILED_UPDATE_CHECKS).await, Some(3));
3079                }
3080
3081                // Successful ping resets `consecutive_failed_update_checks`.
3082                state_machine.ping_omaha(&mut co).await;
3083                assert_eq!(state_machine.context.state.consecutive_failed_update_checks, 0);
3084                {
3085                    let storage = storage.lock().await;
3086                    assert_eq!(storage.get_int(CONSECUTIVE_FAILED_UPDATE_CHECKS).await, None);
3087                }
3088            })
3089            .into_complete()
3090            .await;
3091        });
3092    }
3093
3094    #[test]
3095    fn test_report_attempts_to_successful_install() {
3096        block_on(async {
3097            let http = MockHttpRequest::new(make_update_available_response());
3098            let storage = Rc::new(Mutex::new(MemStorage::new()));
3099
3100            let mock_time = MockTimeSource::new_from_now();
3101
3102            let mut state_machine = StateMachineBuilder::new_stub()
3103                .http(http)
3104                .installer(TestInstaller::builder(mock_time.clone()).build())
3105                .policy_engine(StubPolicyEngine::new(mock_time.clone()))
3106                .metrics_reporter(MockMetricsReporter::new())
3107                .storage(Rc::clone(&storage))
3108                .build()
3109                .await;
3110
3111            state_machine.run_once().await;
3112
3113            // FIXME(https://github.com/rust-lang/rustfmt/issues/4530) rustfmt doesn't wrap slice
3114            // patterns yet.
3115            #[rustfmt::skip]
3116            assert_matches!(
3117                state_machine.metrics_reporter.metrics.as_slice(),
3118                [
3119                    Metrics::UpdateCheckResponseTime { response_time: _, successful: true },
3120                    Metrics::RequestsPerCheck { count: 1, successful: true },
3121                    Metrics::OmahaEventLost(Event { event_type: EventType::UpdateDownloadStarted, event_result: EventResult::Success, .. }),
3122                    Metrics::SuccessfulUpdateDuration(_),
3123                    Metrics::OmahaEventLost(Event { event_type: EventType::UpdateDownloadFinished, event_result: EventResult::Success, .. }),
3124                    Metrics::OmahaEventLost(Event { event_type: EventType::UpdateComplete, event_result: EventResult::Success, .. }),
3125                    Metrics::SuccessfulUpdateFromFirstSeen(_),
3126                    Metrics::AttemptsToSuccessfulCheck(1),
3127                    Metrics::AttemptsToSuccessfulInstall { count: 1, successful: true },
3128                ]
3129            );
3130        });
3131    }
3132
3133    #[test]
3134    fn test_report_attempts_to_successful_install_fails_then_succeeds() {
3135        block_on(async {
3136            let mut http = MockHttpRequest::new(make_update_available_response());
3137            // Responses to events. This first batch corresponds to the install failure, so these
3138            // should be the update download started, and another for a failed install.
3139            // `Event::error(EventErrorCode::Installation)`.
3140            http.add_response(HttpResponse::new(vec![]));
3141            http.add_response(HttpResponse::new(vec![]));
3142
3143            // Respond to the next request.
3144            http.add_response(make_update_available_response());
3145            // Responses to events. This corresponds to the update download started, and the other
3146            // for a successful install.
3147            http.add_response(HttpResponse::new(vec![]));
3148            http.add_response(HttpResponse::new(vec![]));
3149
3150            let storage = Rc::new(Mutex::new(MemStorage::new()));
3151            let mock_time = MockTimeSource::new_from_now();
3152
3153            let mut state_machine = StateMachineBuilder::new_stub()
3154                .http(http)
3155                .installer(TestInstaller::builder(mock_time.clone()).add_install_fail().build())
3156                .policy_engine(StubPolicyEngine::new(mock_time.clone()))
3157                .metrics_reporter(MockMetricsReporter::new())
3158                .storage(Rc::clone(&storage))
3159                .build()
3160                .await;
3161
3162            state_machine.run_once().await;
3163            state_machine.run_once().await;
3164
3165            // FIXME(https://github.com/rust-lang/rustfmt/issues/4530) rustfmt doesn't wrap slice
3166            // patterns yet.
3167            #[rustfmt::skip]
3168            assert_matches!(
3169                state_machine.metrics_reporter.metrics.as_slice(),
3170                [
3171                    Metrics::UpdateCheckResponseTime { response_time: _, successful: true },
3172                    Metrics::RequestsPerCheck { count: 1, successful: true },
3173                    Metrics::FailedUpdateDuration(_),
3174                    Metrics::AttemptsToSuccessfulCheck(1),
3175                    Metrics::AttemptsToSuccessfulInstall { count: 1, successful: false },
3176                    Metrics::UpdateCheckInterval { .. },
3177                    Metrics::UpdateCheckResponseTime { response_time: _, successful: true },
3178                    Metrics::RequestsPerCheck { count: 1, successful: true },
3179                    Metrics::SuccessfulUpdateDuration(_),
3180                    Metrics::OmahaEventLost(Event { .. }),
3181                    Metrics::SuccessfulUpdateFromFirstSeen(_),
3182                    Metrics::AttemptsToSuccessfulCheck(1),
3183                    Metrics::AttemptsToSuccessfulInstall { count: 2, successful: true }
3184                ]
3185            );
3186        });
3187    }
3188
3189    #[test]
3190    fn test_report_attempts_to_successful_install_does_not_report_for_no_update() {
3191        block_on(async {
3192            let response = json!({"response":{
3193              "server": "prod",
3194              "protocol": "3.0",
3195              "app": [{
3196                "appid": "{00000000-0000-0000-0000-000000000001}",
3197                "status": "ok",
3198                "updatecheck": {
3199                  "status": "noupdate",
3200                  "info": "no update for you"
3201                }
3202              }],
3203            }});
3204            let response = serde_json::to_vec(&response).unwrap();
3205            let http = MockHttpRequest::new(HttpResponse::new(response.clone()));
3206
3207            let storage = Rc::new(Mutex::new(MemStorage::new()));
3208            let mock_time = MockTimeSource::new_from_now();
3209
3210            let mut state_machine = StateMachineBuilder::new_stub()
3211                .http(http)
3212                .installer(TestInstaller::builder(mock_time.clone()).build())
3213                .policy_engine(StubPolicyEngine::new(mock_time.clone()))
3214                .metrics_reporter(MockMetricsReporter::new())
3215                .storage(Rc::clone(&storage))
3216                .build()
3217                .await;
3218
3219            state_machine.run_once().await;
3220
3221            // FIXME(https://github.com/rust-lang/rustfmt/issues/4530) rustfmt doesn't wrap slice
3222            // patterns yet.
3223            #[rustfmt::skip]
3224            assert_matches!(
3225                state_machine.metrics_reporter.metrics.as_slice(),
3226                [
3227                    Metrics::UpdateCheckResponseTime { response_time: _, successful: true },
3228                    Metrics::RequestsPerCheck { count: 1, successful: true },
3229                    Metrics::AttemptsToSuccessfulCheck(1),
3230                ]
3231            );
3232        });
3233    }
3234
3235    #[test]
3236    fn test_successful_update_triggers_reboot() {
3237        let mut pool = LocalPool::new();
3238        let spawner = pool.spawner();
3239
3240        let http = MockHttpRequest::new(make_update_available_response());
3241        let mock_time = MockTimeSource::new_from_now();
3242        let next_update_time = mock_time.now();
3243        let (timer, mut timers) = BlockingTimer::new();
3244
3245        let installer = TestInstaller::builder(mock_time.clone()).build();
3246        let reboot_called = Rc::clone(&installer.reboot_called);
3247        let (_ctl, state_machine) = pool.run_until(
3248            StateMachineBuilder::new_stub()
3249                .http(http)
3250                .installer(installer)
3251                .policy_engine(StubPolicyEngine::new(mock_time))
3252                .timer(timer)
3253                .start(),
3254        );
3255        let observer = TestObserver::default();
3256        spawner.spawn_local(observer.observe(state_machine)).unwrap();
3257
3258        let blocked_timer = pool.run_until(timers.next()).unwrap();
3259        assert_eq!(blocked_timer.requested_wait(), RequestedWait::Until(next_update_time.into()));
3260        blocked_timer.unblock();
3261        pool.run_until_stalled();
3262
3263        assert!(*reboot_called.borrow());
3264    }
3265
3266    #[test]
3267    fn test_skip_reboot_if_not_needed() {
3268        let mut pool = LocalPool::new();
3269        let spawner = pool.spawner();
3270
3271        let http = MockHttpRequest::new(make_update_available_response());
3272        let mock_time = MockTimeSource::new_from_now();
3273        let next_update_time = mock_time.now();
3274        let reboot_check_options_received = Rc::new(RefCell::new(vec![]));
3275        let policy_engine = MockPolicyEngine {
3276            reboot_check_options_received: Rc::clone(&reboot_check_options_received),
3277            check_timing: Some(CheckTiming::builder().time(next_update_time).build()),
3278            time_source: mock_time.clone(),
3279            reboot_needed: Rc::new(RefCell::new(false)),
3280            ..MockPolicyEngine::default()
3281        };
3282        let (timer, mut timers) = BlockingTimer::new();
3283
3284        let installer = TestInstaller::builder(mock_time).build();
3285        let reboot_called = Rc::clone(&installer.reboot_called);
3286        let (_ctl, state_machine) = pool.run_until(
3287            StateMachineBuilder::new_stub()
3288                .http(http)
3289                .installer(installer)
3290                .policy_engine(policy_engine)
3291                .timer(timer)
3292                .start(),
3293        );
3294        let observer = TestObserver::default();
3295        spawner.spawn_local(observer.observe(state_machine)).unwrap();
3296
3297        let blocked_timer = pool.run_until(timers.next()).unwrap();
3298        assert_eq!(blocked_timer.requested_wait(), RequestedWait::Until(next_update_time.into()));
3299        blocked_timer.unblock();
3300        pool.run_until_stalled();
3301
3302        assert_eq!(
3303            observer.take_states(),
3304            vec![
3305                State::CheckingForUpdates(InstallSource::ScheduledTask),
3306                State::InstallingUpdate,
3307                State::Idle
3308            ]
3309        );
3310
3311        assert_eq!(*reboot_check_options_received.borrow(), vec![]);
3312        assert!(!*reboot_called.borrow());
3313    }
3314
3315    #[test]
3316    fn test_failed_update_does_not_trigger_reboot() {
3317        let mut pool = LocalPool::new();
3318        let spawner = pool.spawner();
3319
3320        let http = MockHttpRequest::new(make_update_available_response());
3321        let mock_time = MockTimeSource::new_from_now();
3322        let next_update_time = mock_time.now();
3323        let (timer, mut timers) = BlockingTimer::new();
3324
3325        let installer = TestInstaller::builder(mock_time.clone()).add_install_fail().build();
3326        let reboot_called = Rc::clone(&installer.reboot_called);
3327        let (_ctl, state_machine) = pool.run_until(
3328            StateMachineBuilder::new_stub()
3329                .http(http)
3330                .installer(installer)
3331                .policy_engine(StubPolicyEngine::new(mock_time))
3332                .timer(timer)
3333                .start(),
3334        );
3335        let observer = TestObserver::default();
3336        spawner.spawn_local(observer.observe(state_machine)).unwrap();
3337
3338        let blocked_timer = pool.run_until(timers.next()).unwrap();
3339        assert_eq!(blocked_timer.requested_wait(), RequestedWait::Until(next_update_time.into()));
3340        blocked_timer.unblock();
3341        pool.run_until_stalled();
3342
3343        assert!(!*reboot_called.borrow());
3344    }
3345
3346    // Verify that if we are in the middle of checking for or applying an update, a new OnDemand
3347    // update check request will "upgrade" the inflight check request to behave as if it was
3348    // OnDemand. In particular, this should cause an immediate reboot.
3349    #[test]
3350    fn test_reboots_immediately_if_user_initiated_update_requests_occurs_during_install() {
3351        let mut pool = LocalPool::new();
3352        let spawner = pool.spawner();
3353
3354        let http = MockHttpRequest::new(make_update_available_response());
3355        let mock_time = MockTimeSource::new_from_now();
3356
3357        let (send_install, mut recv_install) = mpsc::channel(0);
3358        let (send_reboot, mut recv_reboot) = mpsc::channel(0);
3359        let reboot_check_options_received = Rc::new(RefCell::new(vec![]));
3360        let policy_engine = MockPolicyEngine {
3361            reboot_check_options_received: Rc::clone(&reboot_check_options_received),
3362            check_timing: Some(CheckTiming::builder().time(mock_time.now()).build()),
3363            ..MockPolicyEngine::default()
3364        };
3365
3366        let (mut ctl, state_machine) = pool.run_until(
3367            StateMachineBuilder::new_stub()
3368                .http(http)
3369                .installer(BlockingInstaller {
3370                    on_install: send_install,
3371                    on_reboot: Some(send_reboot),
3372                })
3373                .policy_engine(policy_engine)
3374                .start(),
3375        );
3376
3377        let observer = TestObserver::default();
3378        spawner.spawn_local(observer.observe(state_machine)).unwrap();
3379
3380        let unblock_install = pool.run_until(recv_install.next()).unwrap();
3381        pool.run_until_stalled();
3382        assert_eq!(
3383            observer.take_states(),
3384            vec![State::CheckingForUpdates(InstallSource::ScheduledTask), State::InstallingUpdate]
3385        );
3386
3387        pool.run_until(async {
3388            assert_eq!(
3389                ctl.start_update_check(CheckOptions { source: InstallSource::OnDemand }).await,
3390                Ok(StartUpdateCheckResponse::AlreadyRunning)
3391            );
3392        });
3393
3394        pool.run_until_stalled();
3395        assert_eq!(observer.take_states(), vec![]);
3396
3397        unblock_install.send(vec![AppInstallResult::Installed]).unwrap();
3398        pool.run_until_stalled();
3399        assert_eq!(observer.take_states(), vec![State::WaitingForReboot]);
3400
3401        let unblock_reboot = pool.run_until(recv_reboot.next()).unwrap();
3402        pool.run_until_stalled();
3403        unblock_reboot.send(Ok(())).unwrap();
3404
3405        // Make sure when we checked whether we could reboot, it was from an OnDemand source
3406        assert_eq!(
3407            *reboot_check_options_received.borrow(),
3408            vec![CheckOptions { source: InstallSource::OnDemand }]
3409        );
3410    }
3411
3412    // Verifies that if the state machine is done with an install and waiting for a reboot, and a
3413    // user-initiated UpdateCheckRequest comes in, we reboot immediately.
3414    #[test]
3415    fn test_reboots_immediately_when_check_now_comes_in_during_wait() {
3416        let mut pool = LocalPool::new();
3417        let spawner = pool.spawner();
3418
3419        let mut http = MockHttpRequest::new(make_update_available_response());
3420        // Responses to events.
3421        http.add_response(HttpResponse::new(vec![]));
3422        http.add_response(HttpResponse::new(vec![]));
3423        http.add_response(HttpResponse::new(vec![]));
3424        // Response to the ping.
3425        http.add_response(make_update_available_response());
3426        let mut mock_time = MockTimeSource::new_from_now();
3427        mock_time.truncate_submicrosecond_walltime();
3428        let next_update_time = mock_time.now() + Duration::from_secs(1000);
3429        let (timer, mut timers) = BlockingTimer::new();
3430        let reboot_allowed = Rc::new(RefCell::new(false));
3431        let reboot_check_options_received = Rc::new(RefCell::new(vec![]));
3432        let policy_engine = MockPolicyEngine {
3433            time_source: mock_time.clone(),
3434            reboot_allowed: Rc::clone(&reboot_allowed),
3435            check_timing: Some(CheckTiming::builder().time(next_update_time).build()),
3436            reboot_check_options_received: Rc::clone(&reboot_check_options_received),
3437            ..MockPolicyEngine::default()
3438        };
3439        let installer = TestInstaller::builder(mock_time.clone()).build();
3440        let reboot_called = Rc::clone(&installer.reboot_called);
3441        let storage_ref = Rc::new(Mutex::new(MemStorage::new()));
3442        let apps = make_test_app_set();
3443
3444        let (mut ctl, state_machine) = pool.run_until(
3445            StateMachineBuilder::new_stub()
3446                .app_set(apps)
3447                .http(http)
3448                .installer(installer)
3449                .policy_engine(policy_engine)
3450                .timer(timer)
3451                .storage(Rc::clone(&storage_ref))
3452                .start(),
3453        );
3454
3455        let observer = TestObserver::default();
3456        spawner.spawn_local(observer.observe(state_machine)).unwrap();
3457
3458        // The first wait before update check.
3459        let blocked_timer = pool.run_until(timers.next()).unwrap();
3460        assert_eq!(blocked_timer.requested_wait(), RequestedWait::Until(next_update_time.into()));
3461        blocked_timer.unblock();
3462        pool.run_until_stalled();
3463
3464        // The timers for reboot and ping, even though the order should be deterministic, but that
3465        // is an implementation detail, the test should still pass if that order changes.
3466        let blocked_timer1 = pool.run_until(timers.next()).unwrap();
3467        let blocked_timer2 = pool.run_until(timers.next()).unwrap();
3468        let (wait_for_reboot_timer, _wait_for_next_ping_timer) =
3469            match blocked_timer1.requested_wait() {
3470                RequestedWait::For(_) => (blocked_timer1, blocked_timer2),
3471                RequestedWait::Until(_) => (blocked_timer2, blocked_timer1),
3472            };
3473        // This is the timer waiting for next reboot_allowed check.
3474        assert_eq!(
3475            wait_for_reboot_timer.requested_wait(),
3476            RequestedWait::For(CHECK_REBOOT_ALLOWED_INTERVAL)
3477        );
3478
3479        // If we send an update check request that's from a user (source == OnDemand), we should
3480        // short-circuit the wait for reboot, and update immediately.
3481        assert!(!*reboot_called.borrow());
3482        *reboot_allowed.borrow_mut() = true;
3483        pool.run_until(async {
3484            assert_eq!(
3485                ctl.start_update_check(CheckOptions { source: InstallSource::OnDemand }).await,
3486                Ok(StartUpdateCheckResponse::AlreadyRunning)
3487            );
3488        });
3489        pool.run_until_stalled();
3490        assert!(*reboot_called.borrow());
3491
3492        // Check that we got one check for reboot from a Scheduled Task (the start of the wait),
3493        // and then another came in with OnDemand, as we "upgraded it" with our OnDemand check
3494        // request
3495        assert_eq!(
3496            *reboot_check_options_received.borrow(),
3497            vec![
3498                CheckOptions { source: InstallSource::ScheduledTask },
3499                CheckOptions { source: InstallSource::OnDemand },
3500            ]
3501        );
3502    }
3503
3504    // Verifies that if reboot is not allowed, state machine will send pings to Omaha while waiting
3505    // for reboot, and it will reply AlreadyRunning to any StartUpdateCheck requests, and when it's
3506    // finally time to reboot, it will trigger reboot.
3507    #[test]
3508    fn test_wait_for_reboot() {
3509        let mut pool = LocalPool::new();
3510        let spawner = pool.spawner();
3511
3512        let mut http = MockHttpRequest::new(make_update_available_response());
3513        // Responses to events.
3514        http.add_response(HttpResponse::new(vec![]));
3515        http.add_response(HttpResponse::new(vec![]));
3516        http.add_response(HttpResponse::new(vec![]));
3517        // Response to the ping.
3518        http.add_response(make_update_available_response());
3519        let ping_request_viewer = MockHttpRequest::from_request_cell(http.get_request_cell());
3520        let mut mock_time = MockTimeSource::new_from_now();
3521        mock_time.truncate_submicrosecond_walltime();
3522        let next_update_time = mock_time.now() + Duration::from_secs(1000);
3523        let (timer, mut timers) = BlockingTimer::new();
3524        let reboot_allowed = Rc::new(RefCell::new(false));
3525        let policy_engine = MockPolicyEngine {
3526            time_source: mock_time.clone(),
3527            reboot_allowed: Rc::clone(&reboot_allowed),
3528            check_timing: Some(CheckTiming::builder().time(next_update_time).build()),
3529            ..MockPolicyEngine::default()
3530        };
3531        let installer = TestInstaller::builder(mock_time.clone()).build();
3532        let reboot_called = Rc::clone(&installer.reboot_called);
3533        let storage_ref = Rc::new(Mutex::new(MemStorage::new()));
3534        let apps = make_test_app_set();
3535
3536        let (mut ctl, state_machine) = pool.run_until(
3537            StateMachineBuilder::new_stub()
3538                .app_set(apps.clone())
3539                .http(http)
3540                .installer(installer)
3541                .policy_engine(policy_engine)
3542                .timer(timer)
3543                .storage(Rc::clone(&storage_ref))
3544                .start(),
3545        );
3546
3547        let observer = TestObserver::default();
3548        spawner.spawn_local(observer.observe(state_machine)).unwrap();
3549
3550        // The first wait before update check.
3551        let blocked_timer = pool.run_until(timers.next()).unwrap();
3552        assert_eq!(blocked_timer.requested_wait(), RequestedWait::Until(next_update_time.into()));
3553        blocked_timer.unblock();
3554        pool.run_until_stalled();
3555
3556        // The timers for reboot and ping, even though the order should be deterministic, but that
3557        // is an implementation detail, the test should still pass if that order changes.
3558        let blocked_timer1 = pool.run_until(timers.next()).unwrap();
3559        let blocked_timer2 = pool.run_until(timers.next()).unwrap();
3560        let (wait_for_reboot_timer, wait_for_next_ping_timer) =
3561            match blocked_timer1.requested_wait() {
3562                RequestedWait::For(_) => (blocked_timer1, blocked_timer2),
3563                RequestedWait::Until(_) => (blocked_timer2, blocked_timer1),
3564            };
3565        // This is the timer waiting for next reboot_allowed check.
3566        assert_eq!(
3567            wait_for_reboot_timer.requested_wait(),
3568            RequestedWait::For(CHECK_REBOOT_ALLOWED_INTERVAL)
3569        );
3570        // This is the timer waiting for the next ping.
3571        assert_eq!(
3572            wait_for_next_ping_timer.requested_wait(),
3573            RequestedWait::Until(next_update_time.into())
3574        );
3575        // Unblock the ping.
3576        mock_time.advance(Duration::from_secs(1000));
3577        wait_for_next_ping_timer.unblock();
3578        pool.run_until_stalled();
3579
3580        // Verify that it sends a ping.
3581        let config = crate::configuration::test_support::config_generator();
3582        let request_params = RequestParams::default();
3583
3584        let apps = pool.run_until(apps.lock()).get_apps();
3585        let mut expected_request_builder = RequestBuilder::new(&config, &request_params)
3586            // 0: session id for update check
3587            // 1: request id for update check
3588            // 2-4: request id for events
3589            .session_id(GUID::from_u128(5))
3590            .request_id(GUID::from_u128(6));
3591        for app in &apps {
3592            expected_request_builder = expected_request_builder.add_ping(app);
3593        }
3594        pool.run_until(assert_request(&ping_request_viewer, expected_request_builder));
3595
3596        pool.run_until(async {
3597            assert_eq!(
3598                ctl.start_update_check(CheckOptions::default()).await,
3599                Ok(StartUpdateCheckResponse::AlreadyRunning)
3600            );
3601        });
3602
3603        // Last update time is updated in storage.
3604        pool.run_until(async {
3605            let storage = storage_ref.lock().await;
3606            let context = update_check::Context::load(&*storage).await;
3607            assert_eq!(context.schedule.last_update_time, Some(mock_time.now_in_walltime().into()));
3608        });
3609
3610        // State machine should be waiting for the next ping.
3611        let wait_for_next_ping_timer = pool.run_until(timers.next()).unwrap();
3612        assert_eq!(
3613            wait_for_next_ping_timer.requested_wait(),
3614            RequestedWait::Until(next_update_time.into())
3615        );
3616
3617        // Let state machine check reboot_allowed again, but still don't allow it.
3618        wait_for_reboot_timer.unblock();
3619        pool.run_until_stalled();
3620        assert!(!*reboot_called.borrow());
3621
3622        // State machine should be waiting for the next reboot.
3623        let wait_for_reboot_timer = pool.run_until(timers.next()).unwrap();
3624        assert_eq!(
3625            wait_for_reboot_timer.requested_wait(),
3626            RequestedWait::For(CHECK_REBOOT_ALLOWED_INTERVAL)
3627        );
3628
3629        // Time for a second ping.
3630        wait_for_next_ping_timer.unblock();
3631        pool.run_until_stalled();
3632
3633        // Verify that it sends another ping.
3634        let mut expected_request_builder = RequestBuilder::new(&config, &request_params)
3635            .session_id(GUID::from_u128(7))
3636            .request_id(GUID::from_u128(8));
3637        for app in &apps {
3638            expected_request_builder = expected_request_builder.add_ping(app);
3639        }
3640        pool.run_until(assert_request(&ping_request_viewer, expected_request_builder));
3641
3642        assert!(!*reboot_called.borrow());
3643
3644        // Now allow reboot.
3645        *reboot_called.borrow_mut() = true;
3646        wait_for_reboot_timer.unblock();
3647        pool.run_until_stalled();
3648        assert!(*reboot_called.borrow());
3649    }
3650
3651    #[derive(Debug)]
3652    struct BlockingInstaller {
3653        on_install: mpsc::Sender<oneshot::Sender<Vec<AppInstallResult<StubInstallErrors>>>>,
3654        on_reboot: Option<mpsc::Sender<oneshot::Sender<Result<(), anyhow::Error>>>>,
3655    }
3656
3657    impl Installer for BlockingInstaller {
3658        type InstallPlan = StubPlan;
3659        type Error = StubInstallErrors;
3660        type InstallResult = ();
3661
3662        fn perform_install(
3663            &mut self,
3664            _install_plan: &StubPlan,
3665            _observer: Option<&dyn ProgressObserver>,
3666        ) -> LocalBoxFuture<'_, (Self::InstallResult, Vec<AppInstallResult<Self::Error>>)> {
3667            let (send, recv) = oneshot::channel();
3668            let send_fut = self.on_install.send(send);
3669
3670            async move {
3671                send_fut.await.unwrap();
3672                ((), recv.await.unwrap())
3673            }
3674            .boxed_local()
3675        }
3676
3677        fn perform_reboot(&mut self) -> LocalBoxFuture<'_, Result<(), anyhow::Error>> {
3678            match &mut self.on_reboot {
3679                Some(on_reboot) => {
3680                    let (send, recv) = oneshot::channel();
3681                    let send_fut = on_reboot.send(send);
3682
3683                    async move {
3684                        send_fut.await.unwrap();
3685                        recv.await.unwrap()
3686                    }
3687                    .boxed_local()
3688                }
3689                None => future::ready(Ok(())).boxed_local(),
3690            }
3691        }
3692
3693        fn try_create_install_plan<'a>(
3694            &'a self,
3695            _request_params: &'a RequestParams,
3696            _request_metadata: Option<&'a RequestMetadata>,
3697            _response: &'a Response,
3698            _response_bytes: Vec<u8>,
3699            _ecdsa_signature: Option<Vec<u8>>,
3700        ) -> LocalBoxFuture<'a, Result<Self::InstallPlan, Self::Error>> {
3701            future::ready(Ok(StubPlan)).boxed_local()
3702        }
3703    }
3704
3705    #[derive(Debug, Default)]
3706    struct TestObserver {
3707        states: Rc<RefCell<Vec<State>>>,
3708    }
3709
3710    impl TestObserver {
3711        fn observe<T: Stream<Item = StateMachineEvent>>(
3712            &self,
3713            s: T,
3714        ) -> impl Future<Output = ()> + use<T> {
3715            let states = Rc::clone(&self.states);
3716            async move {
3717                futures::pin_mut!(s);
3718                while let Some(event) = s.next().await {
3719                    if let StateMachineEvent::StateChange(state) = event {
3720                        states.borrow_mut().push(state);
3721                    }
3722                }
3723            }
3724        }
3725
3726        fn observe_until_terminal<T: Stream<Item = StateMachineEvent>>(
3727            &self,
3728            s: T,
3729        ) -> impl Future<Output = ()> + use<T> {
3730            let states = Rc::clone(&self.states);
3731            async move {
3732                futures::pin_mut!(s);
3733                while let Some(event) = s.next().await {
3734                    if let StateMachineEvent::StateChange(state) = event {
3735                        states.borrow_mut().push(state);
3736                        match state {
3737                            State::Idle | State::WaitingForReboot => return,
3738                            _ => {}
3739                        }
3740                    }
3741                }
3742            }
3743        }
3744
3745        fn take_states(&self) -> Vec<State> {
3746            std::mem::take(&mut *self.states.borrow_mut())
3747        }
3748    }
3749
3750    #[test]
3751    fn test_start_update_during_update_replies_with_in_progress() {
3752        let mut pool = LocalPool::new();
3753        let spawner = pool.spawner();
3754
3755        let http = MockHttpRequest::new(make_update_available_response());
3756        let (send_install, mut recv_install) = mpsc::channel(0);
3757        let (mut ctl, state_machine) = pool.run_until(
3758            StateMachineBuilder::new_stub()
3759                .http(http)
3760                .installer(BlockingInstaller { on_install: send_install, on_reboot: None })
3761                .start(),
3762        );
3763
3764        let observer = TestObserver::default();
3765        spawner.spawn_local(observer.observe_until_terminal(state_machine)).unwrap();
3766
3767        let unblock_install = pool.run_until(recv_install.next()).unwrap();
3768        pool.run_until_stalled();
3769        assert_eq!(
3770            observer.take_states(),
3771            vec![State::CheckingForUpdates(InstallSource::ScheduledTask), State::InstallingUpdate]
3772        );
3773
3774        pool.run_until(async {
3775            assert_eq!(
3776                ctl.start_update_check(CheckOptions::default()).await,
3777                Ok(StartUpdateCheckResponse::AlreadyRunning)
3778            );
3779        });
3780        pool.run_until_stalled();
3781        assert_eq!(observer.take_states(), vec![]);
3782
3783        unblock_install.send(vec![AppInstallResult::Installed]).unwrap();
3784        pool.run_until_stalled();
3785
3786        assert_eq!(observer.take_states(), vec![State::WaitingForReboot]);
3787    }
3788
3789    #[test]
3790    fn test_start_update_during_timer_starts_update() {
3791        let mut pool = LocalPool::new();
3792        let spawner = pool.spawner();
3793
3794        let mut mock_time = MockTimeSource::new_from_now();
3795        let next_update_time = mock_time.now() + Duration::from_secs(321);
3796
3797        let (timer, mut timers) = BlockingTimer::new();
3798        let policy_engine = MockPolicyEngine {
3799            check_timing: Some(CheckTiming::builder().time(next_update_time).build()),
3800            time_source: mock_time.clone(),
3801            ..MockPolicyEngine::default()
3802        };
3803        let (mut ctl, state_machine) = pool.run_until(
3804            StateMachineBuilder::new_stub().policy_engine(policy_engine).timer(timer).start(),
3805        );
3806
3807        let observer = TestObserver::default();
3808        spawner.spawn_local(observer.observe(state_machine)).unwrap();
3809
3810        let blocked_timer = pool.run_until(timers.next()).unwrap();
3811        assert_eq!(blocked_timer.requested_wait(), RequestedWait::Until(next_update_time.into()));
3812        mock_time.advance(Duration::from_secs(200));
3813        assert_eq!(observer.take_states(), vec![]);
3814
3815        // Nothing happens while the timer is waiting.
3816        pool.run_until_stalled();
3817        assert_eq!(observer.take_states(), vec![]);
3818
3819        blocked_timer.unblock();
3820        let blocked_timer = pool.run_until(timers.next()).unwrap();
3821        assert_eq!(blocked_timer.requested_wait(), RequestedWait::Until(next_update_time.into()));
3822        assert_eq!(
3823            observer.take_states(),
3824            vec![
3825                State::CheckingForUpdates(InstallSource::ScheduledTask),
3826                State::ErrorCheckingForUpdate,
3827                State::Idle
3828            ]
3829        );
3830
3831        // Unless a control signal to start an update check comes in.
3832        pool.run_until(async {
3833            assert_eq!(
3834                ctl.start_update_check(CheckOptions::default()).await,
3835                Ok(StartUpdateCheckResponse::Started)
3836            );
3837        });
3838        pool.run_until_stalled();
3839        assert_eq!(
3840            observer.take_states(),
3841            vec![
3842                State::CheckingForUpdates(InstallSource::ScheduledTask),
3843                State::ErrorCheckingForUpdate,
3844                State::Idle
3845            ]
3846        );
3847    }
3848
3849    #[test]
3850    fn test_start_update_check_returns_throttled() {
3851        let mut pool = LocalPool::new();
3852        let spawner = pool.spawner();
3853
3854        let mut mock_time = MockTimeSource::new_from_now();
3855        let next_update_time = mock_time.now() + Duration::from_secs(321);
3856
3857        let (timer, mut timers) = BlockingTimer::new();
3858        let policy_engine = MockPolicyEngine {
3859            check_timing: Some(CheckTiming::builder().time(next_update_time).build()),
3860            time_source: mock_time.clone(),
3861            check_decision: CheckDecision::ThrottledByPolicy,
3862            ..MockPolicyEngine::default()
3863        };
3864        let (mut ctl, state_machine) = pool.run_until(
3865            StateMachineBuilder::new_stub().policy_engine(policy_engine).timer(timer).start(),
3866        );
3867
3868        let observer = TestObserver::default();
3869        spawner.spawn_local(observer.observe(state_machine)).unwrap();
3870
3871        let blocked_timer = pool.run_until(timers.next()).unwrap();
3872        assert_eq!(blocked_timer.requested_wait(), RequestedWait::Until(next_update_time.into()));
3873        mock_time.advance(Duration::from_secs(200));
3874        assert_eq!(observer.take_states(), vec![]);
3875
3876        pool.run_until(async {
3877            assert_eq!(
3878                ctl.start_update_check(CheckOptions::default()).await,
3879                Ok(StartUpdateCheckResponse::Throttled)
3880            );
3881        });
3882        pool.run_until_stalled();
3883        assert_eq!(observer.take_states(), vec![]);
3884    }
3885
3886    #[test]
3887    fn test_progress_observer() {
3888        block_on(async {
3889            let http = MockHttpRequest::new(make_update_available_response());
3890            let mock_time = MockTimeSource::new_from_now();
3891            let progresses = StateMachineBuilder::new_stub()
3892                .http(http)
3893                .installer(TestInstaller::builder(mock_time.clone()).build())
3894                .policy_engine(StubPolicyEngine::new(mock_time))
3895                .oneshot_check()
3896                .await
3897                .filter_map(|event| {
3898                    future::ready(match event {
3899                        StateMachineEvent::InstallProgressChange(InstallProgress { progress }) => {
3900                            Some(progress)
3901                        }
3902                        _ => None,
3903                    })
3904                })
3905                .collect::<Vec<f32>>()
3906                .await;
3907            assert_eq!(progresses, [0.0, 0.3, 0.9, 1.0]);
3908        });
3909    }
3910
3911    #[test]
3912    // A scenario in which
3913    // (now_in_monotonic - state_machine_start_in_monotonic) > (update_finish_time - now_in_wall)
3914    // should not panic.
3915    fn test_report_waited_for_reboot_duration_doesnt_panic_on_wrong_current_time() {
3916        block_on(async {
3917            let metrics_reporter = MockMetricsReporter::new();
3918
3919            let state_machine_start_monotonic = Instant::now();
3920            let update_finish_time = SystemTime::now();
3921
3922            // Set the monotonic increase in time larger than the wall time increase since the end
3923            // of the last update.
3924            // This can happen if we don't have a reliable current wall time.
3925            let now_wall = update_finish_time + Duration::from_secs(1);
3926            let now_monotonic = state_machine_start_monotonic + Duration::from_secs(10);
3927
3928            let mut state_machine =
3929                StateMachineBuilder::new_stub().metrics_reporter(metrics_reporter).build().await;
3930
3931            // Time has advanced monotonically since we noted the start of the state machine for
3932            // longer than the wall time difference between update finish time and now.
3933            // This computation should currently overflow.
3934            state_machine
3935                .report_waited_for_reboot_duration(
3936                    update_finish_time,
3937                    state_machine_start_monotonic,
3938                    ComplexTime { wall: now_wall, mono: now_monotonic },
3939                )
3940                .expect_err("should overflow and error out");
3941
3942            // We should have reported no metrics
3943            assert!(state_machine.metrics_reporter.metrics.is_empty());
3944        });
3945    }
3946
3947    #[test]
3948    fn test_report_waited_for_reboot_duration() {
3949        let mut pool = LocalPool::new();
3950        let spawner = pool.spawner();
3951
3952        let response = json!({"response": {
3953            "server": "prod",
3954            "protocol": "3.0",
3955            "app": [{
3956            "appid": "{00000000-0000-0000-0000-000000000001}",
3957            "status": "ok",
3958            "updatecheck": {
3959                "status": "ok",
3960                "manifest": {
3961                    "version": "1.2.3.5",
3962                    "actions": {
3963                        "action": [],
3964                    },
3965                    "packages": {
3966                        "package": [],
3967                    },
3968                }
3969            }
3970            }],
3971        }});
3972        let response = serde_json::to_vec(&response).unwrap();
3973        let http = MockHttpRequest::new(HttpResponse::new(response));
3974        let mut mock_time = MockTimeSource::new_from_now();
3975        mock_time.truncate_submicrosecond_walltime();
3976        let storage = Rc::new(Mutex::new(MemStorage::new()));
3977
3978        // Do one update.
3979        assert_matches!(
3980            pool.run_until(
3981                StateMachineBuilder::new_stub()
3982                    .http(http)
3983                    .policy_engine(StubPolicyEngine::new(mock_time.clone()))
3984                    .storage(Rc::clone(&storage))
3985                    .oneshot(RequestParams::default())
3986            ),
3987            Ok(_)
3988        );
3989
3990        mock_time.advance(Duration::from_secs(999));
3991
3992        // Execute state machine `run()`, simulating that we already rebooted.
3993        let config = Config {
3994            updater: Updater { name: "updater".to_string(), version: Version::from([0, 1]) },
3995            os: OS { version: "1.2.3.5".to_string(), ..OS::default() },
3996            service_url: "http://example.com/".to_string(),
3997            omaha_public_keys: None,
3998        };
3999        let metrics_reporter = Rc::new(RefCell::new(MockMetricsReporter::new()));
4000        let (_ctl, state_machine) = pool.run_until(
4001            StateMachineBuilder::new_stub()
4002                .config(config)
4003                .metrics_reporter(Rc::clone(&metrics_reporter))
4004                .policy_engine(StubPolicyEngine::new(mock_time.clone()))
4005                .storage(Rc::clone(&storage))
4006                .timer(MockTimer::new())
4007                .start(),
4008        );
4009
4010        // Move state machine forward using observer.
4011        let observer = TestObserver::default();
4012        spawner.spawn_local(observer.observe(state_machine)).unwrap();
4013        pool.run_until_stalled();
4014
4015        assert_eq!(
4016            metrics_reporter
4017                .borrow()
4018                .metrics
4019                .iter()
4020                .filter(|m| matches!(m, Metrics::WaitedForRebootDuration(_)))
4021                .collect::<Vec<_>>(),
4022            vec![&Metrics::WaitedForRebootDuration(Duration::from_secs(999))]
4023        );
4024
4025        // Verify that storage is cleaned up.
4026        pool.run_until(async {
4027            let storage = storage.lock().await;
4028            assert_eq!(storage.get_time(UPDATE_FINISH_TIME).await, None);
4029            assert_eq!(storage.get_string(TARGET_VERSION).await, None);
4030            assert!(storage.committed());
4031        })
4032    }
4033
4034    // The same as |run_simple_check_with_noupdate_result|, but with CUPv2 protocol validation.
4035    #[test]
4036    fn run_cup_but_decoration_error() {
4037        block_on(async {
4038            let http = MockHttpRequest::new(HttpResponse::new(make_noupdate_httpresponse()));
4039
4040            let stub_cup_handler = MockCupv2Handler::new().set_decoration_error(|| {
4041                Some(CupDecorationError::ParseError("".parse::<http::Uri>().unwrap_err()))
4042            });
4043
4044            assert_matches!(
4045                StateMachineBuilder::new_stub()
4046                    .http(http)
4047                    .cup_handler(Some(stub_cup_handler))
4048                    .oneshot(RequestParams::default())
4049                    .await,
4050                Err(UpdateCheckError::OmahaRequest(OmahaRequestError::CupDecoration(
4051                    CupDecorationError::ParseError(_)
4052                )))
4053            );
4054
4055            info!("update check complete!");
4056        });
4057    }
4058
4059    #[test]
4060    fn run_cup_but_verification_error() {
4061        block_on(async {
4062            let http = MockHttpRequest::new(HttpResponse::new(make_noupdate_httpresponse()));
4063
4064            let stub_cup_handler = MockCupv2Handler::new()
4065                .set_verification_error(|| Some(CupVerificationError::EtagHeaderMissing));
4066
4067            assert_matches!(
4068                StateMachineBuilder::new_stub()
4069                    .http(http)
4070                    .cup_handler(Some(stub_cup_handler))
4071                    .oneshot(RequestParams::default())
4072                    .await,
4073                Err(UpdateCheckError::OmahaRequest(OmahaRequestError::CupValidation(
4074                    CupVerificationError::EtagHeaderMissing
4075                )))
4076            );
4077
4078            info!("update check complete!");
4079        });
4080    }
4081
4082    #[test]
4083    fn run_cup_valid() {
4084        block_on(async {
4085            let http = MockHttpRequest::new(HttpResponse::new(make_noupdate_httpresponse()));
4086
4087            assert_matches!(
4088                StateMachineBuilder::new_stub()
4089                    .http(http)
4090                    // Default stub_cup_handler, which is permissive.
4091                    .oneshot(RequestParams::default())
4092                    .await,
4093                Ok(_)
4094            );
4095
4096            info!("update check complete!");
4097        });
4098    }
4099}