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