Skip to main content

omaha_client/state_machine/
update_check.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
9/// The update_check module contains the structures and functions for performing a single update
10/// check with Omaha.
11use crate::{
12    common::{ProtocolState, UpdateCheckSchedule, UserCounting},
13    protocol::Cohort,
14    storage::{Storage, StorageExt},
15    time::PartialComplexTime,
16};
17use log::error;
18use std::convert::{TryFrom, TryInto};
19use std::time::Duration;
20
21// These are the keys used to persist data to storage.
22pub const CONSECUTIVE_FAILED_UPDATE_CHECKS: &str = "consecutive_failed_update_checks";
23pub const LAST_UPDATE_TIME: &str = "last_update_time";
24pub const SERVER_DICTATED_POLL_INTERVAL: &str = "server_dictated_poll_interval";
25
26/// The Context provides the protocol context for a given update check operation.
27///
28/// The Context provides the information that's passed to the Policy to allow
29/// it to properly reason about what can and cannot be done at this time.
30#[derive(Clone, Debug)]
31pub struct Context {
32    /// The last-computed time to next check for an update.
33    pub schedule: UpdateCheckSchedule,
34
35    /// The state of the protocol (retries, errors, etc.) as of the last update check that was
36    /// attempted.
37    pub state: ProtocolState,
38}
39
40impl Context {
41    /// Load and initialize update check context from persistent storage.
42    pub async fn load(storage: &impl Storage) -> Self {
43        let last_update_time =
44            storage.get_time(LAST_UPDATE_TIME).await.map(PartialComplexTime::Wall);
45        let server_dictated_poll_interval = storage
46            .get_int(SERVER_DICTATED_POLL_INTERVAL)
47            .await
48            .and_then(|t| u64::try_from(t).ok())
49            .map(Duration::from_micros);
50
51        let consecutive_failed_update_checks: u32 = storage
52            .get_int(CONSECUTIVE_FAILED_UPDATE_CHECKS)
53            .await
54            .unwrap_or(0)
55            .try_into()
56            .unwrap_or_default();
57
58        // last_check_time isn't really last_update_time, but we're not persisting our
59        // between-check wall time for reporting, and this is a reasonable-enough proxy.
60        Context {
61            schedule: UpdateCheckSchedule::builder()
62                .last_update_time(last_update_time)
63                .last_update_check_time(last_update_time)
64                .build(),
65            state: ProtocolState {
66                server_dictated_poll_interval,
67                consecutive_failed_update_checks,
68                ..Default::default()
69            },
70        }
71    }
72
73    /// Persist data in Context to |storage|, will try to set all of them to storage even if
74    /// previous set fails.
75    /// It will NOT call commit() on |storage|, caller is responsible to call commit().
76    pub async fn persist<'a>(&'a self, storage: &'a mut impl Storage) {
77        if let Err(e) = storage
78            .set_option_int(
79                LAST_UPDATE_TIME,
80                self.schedule
81                    .last_update_time
82                    .and_then(PartialComplexTime::checked_to_micros_since_epoch),
83            )
84            .await
85        {
86            error!("Unable to persist {}: {}", LAST_UPDATE_TIME, e);
87        }
88
89        if let Err(e) = storage
90            .set_option_int(
91                SERVER_DICTATED_POLL_INTERVAL,
92                self.state
93                    .server_dictated_poll_interval
94                    .map(|t| t.as_micros())
95                    .and_then(|t| i64::try_from(t).ok()),
96            )
97            .await
98        {
99            error!("Unable to persist {}: {}", SERVER_DICTATED_POLL_INTERVAL, e);
100        }
101
102        // By converting to an option, set_option_int will clean up storage associated with this
103        // value if it's the default (0).
104        let consecutive_failed_update_checks_option = {
105            if self.state.consecutive_failed_update_checks == 0 {
106                None
107            } else {
108                Some(self.state.consecutive_failed_update_checks as i64)
109            }
110        };
111
112        if let Err(e) = storage
113            .set_option_int(
114                CONSECUTIVE_FAILED_UPDATE_CHECKS,
115                consecutive_failed_update_checks_option,
116            )
117            .await
118        {
119            error!("Unable to persist {}: {}", CONSECUTIVE_FAILED_UPDATE_CHECKS, e);
120        }
121    }
122}
123
124/// The response context from the update check contains any extra information that Omaha returns to
125/// the client, separate from the data about a particular app itself.
126#[derive(Debug)]
127pub struct Response {
128    /// The set of responses for all the apps in the request.
129    pub app_responses: Vec<AppResponse>,
130}
131
132/// For each application that had an update check performed, a new App (potentially with new Cohort
133/// and UserCounting data) and a corresponding response Action are returned from the update check.
134#[derive(Debug)]
135pub struct AppResponse {
136    /// The returned information about an application.
137    pub app_id: String,
138
139    /// Cohort data returned from Omaha
140    pub cohort: Cohort,
141
142    pub user_counting: UserCounting,
143
144    /// The resultant action of its update check.
145    pub result: Action,
146}
147
148/// The Action is the result of an update check for a single App.
149///
150/// This is just informational, for the purposes of updating the protocol state.
151/// Any update action should already have been taken by the Installer.
152#[derive(Debug, Clone, PartialEq)]
153pub enum Action {
154    /// Omaha's response was "no update"
155    NoUpdate,
156
157    /// Policy deferred the update.  The update check was successful, and Omaha returned that an
158    /// update is available, but it is not able to be acted on at this time.
159    DeferredByPolicy,
160
161    /// Policy Denied the update.  The update check was successful, and Omaha returned that an
162    /// update is available, but it is not allowed to be installed per Policy.
163    DeniedByPolicy,
164
165    /// The install process encountered an error.
166    /// TODO: Attach an error to this
167    InstallPlanExecutionError,
168
169    /// An update was performed.
170    Updated,
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176    use crate::storage::MemStorage;
177    use futures::executor::block_on;
178
179    #[test]
180    fn test_load_context() {
181        block_on(async {
182            let mut storage = MemStorage::new();
183            let last_update_time = 123456789;
184            let poll_interval = Duration::from_micros(56789u64);
185            storage.set_int(LAST_UPDATE_TIME, last_update_time).await.unwrap();
186            storage
187                .set_int(SERVER_DICTATED_POLL_INTERVAL, poll_interval.as_micros() as i64)
188                .await
189                .unwrap();
190
191            storage.set_int(CONSECUTIVE_FAILED_UPDATE_CHECKS, 1234).await.unwrap();
192
193            let context = Context::load(&storage).await;
194
195            let last_update_time = PartialComplexTime::from_micros_since_epoch(last_update_time);
196            assert_eq!(context.schedule.last_update_time, Some(last_update_time));
197            assert_eq!(context.state.server_dictated_poll_interval, Some(poll_interval));
198            assert_eq!(context.state.consecutive_failed_update_checks, 1234);
199        });
200    }
201
202    #[test]
203    fn test_load_context_empty_storage() {
204        block_on(async {
205            let storage = MemStorage::new();
206            let context = Context::load(&storage).await;
207            assert_eq!(None, context.schedule.last_update_time);
208            assert_eq!(None, context.state.server_dictated_poll_interval);
209            assert_eq!(0, context.state.consecutive_failed_update_checks);
210        });
211    }
212
213    #[test]
214    fn test_persist_context() {
215        block_on(async {
216            let mut storage = MemStorage::new();
217            let last_update_time = PartialComplexTime::from_micros_since_epoch(123456789);
218            let server_dictated_poll_interval = Some(Duration::from_micros(56789));
219            let consecutive_failed_update_checks = 1234;
220            let context = Context {
221                schedule: UpdateCheckSchedule::builder().last_update_time(last_update_time).build(),
222                state: ProtocolState {
223                    server_dictated_poll_interval,
224                    consecutive_failed_update_checks,
225                    ..ProtocolState::default()
226                },
227            };
228            context.persist(&mut storage).await;
229            assert_eq!(Some(123456789), storage.get_int(LAST_UPDATE_TIME).await);
230            assert_eq!(Some(56789), storage.get_int(SERVER_DICTATED_POLL_INTERVAL).await);
231            assert_eq!(Some(1234), storage.get_int(CONSECUTIVE_FAILED_UPDATE_CHECKS).await);
232            assert!(!storage.committed());
233        });
234    }
235
236    #[test]
237    fn test_persist_context_remove_defaults() {
238        block_on(async {
239            let mut storage = MemStorage::new();
240            let last_update_time = PartialComplexTime::from_micros_since_epoch(123456789);
241            storage.set_int(SERVER_DICTATED_POLL_INTERVAL, 987654).await.unwrap();
242            storage.set_int(CONSECUTIVE_FAILED_UPDATE_CHECKS, 1234).await.unwrap();
243
244            let context = Context {
245                schedule: UpdateCheckSchedule::builder().last_update_time(last_update_time).build(),
246                state: ProtocolState {
247                    server_dictated_poll_interval: None,
248                    consecutive_failed_update_checks: 0,
249                    ..ProtocolState::default()
250                },
251            };
252            context.persist(&mut storage).await;
253            assert_eq!(Some(123456789), storage.get_int(LAST_UPDATE_TIME).await);
254            assert_eq!(None, storage.get_int(SERVER_DICTATED_POLL_INTERVAL).await);
255            assert_eq!(None, storage.get_int(CONSECUTIVE_FAILED_UPDATE_CHECKS).await);
256            assert!(!storage.committed());
257        });
258    }
259}