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.
89/// 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;
2021// 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";
2526/// 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.
32pub schedule: UpdateCheckSchedule,
3334/// The state of the protocol (retries, errors, etc.) as of the last update check that was
35 /// attempted.
36pub state: ProtocolState,
37}
3839impl Context {
40/// Load and initialize update check context from persistent storage.
41pub async fn load(storage: &impl Storage) -> Self {
42let last_update_time = storage
43 .get_time(LAST_UPDATE_TIME)
44 .await
45.map(PartialComplexTime::Wall);
46let 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);
5152let 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();
5859// 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.
61Context {
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 }
7374/// 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().
77pub async fn persist<'a>(&'a self, storage: &'a mut impl Storage) {
78if let Err(e) = storage
79 .set_option_int(
80 LAST_UPDATE_TIME,
81self.schedule
82 .last_update_time
83 .and_then(PartialComplexTime::checked_to_micros_since_epoch),
84 )
85 .await
86{
87error!("Unable to persist {}: {}", LAST_UPDATE_TIME, e);
88 }
8990if let Err(e) = storage
91 .set_option_int(
92 SERVER_DICTATED_POLL_INTERVAL,
93self.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{
100error!("Unable to persist {}: {}", SERVER_DICTATED_POLL_INTERVAL, e);
101 }
102103// By converting to an option, set_option_int will clean up storage associated with this
104 // value if it's the default (0).
105let consecutive_failed_update_checks_option = {
106if self.state.consecutive_failed_update_checks == 0 {
107None
108} else {
109Some(self.state.consecutive_failed_update_checks as i64)
110 }
111 };
112113if let Err(e) = storage
114 .set_option_int(
115 CONSECUTIVE_FAILED_UPDATE_CHECKS,
116 consecutive_failed_update_checks_option,
117 )
118 .await
119{
120error!(
121"Unable to persist {}: {}",
122 CONSECUTIVE_FAILED_UPDATE_CHECKS, e
123 );
124 }
125 }
126}
127128/// 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.
133pub app_responses: Vec<AppResponse>,
134}
135136/// 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.
141pub app_id: String,
142143/// Cohort data returned from Omaha
144pub cohort: Cohort,
145146pub user_counting: UserCounting,
147148/// The resultant action of its update check.
149pub result: Action,
150}
151152/// 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"
158NoUpdate,
159160/// 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.
162DeferredByPolicy,
163164/// 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.
166DeniedByPolicy,
167168/// The install process encountered an error.
169 /// TODO: Attach an error to this
170InstallPlanExecutionError,
171172/// An update was performed.
173Updated,
174}
175176#[cfg(test)]
177mod tests {
178use super::*;
179use crate::storage::MemStorage;
180use futures::executor::block_on;
181182#[test]
183fn test_load_context() {
184 block_on(async {
185let mut storage = MemStorage::new();
186let last_update_time = 123456789;
187let 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();
199200 storage
201 .set_int(CONSECUTIVE_FAILED_UPDATE_CHECKS, 1234)
202 .await
203.unwrap();
204205let context = Context::load(&storage).await;
206207let last_update_time = PartialComplexTime::from_micros_since_epoch(last_update_time);
208assert_eq!(context.schedule.last_update_time, Some(last_update_time));
209assert_eq!(
210 context.state.server_dictated_poll_interval,
211Some(poll_interval)
212 );
213assert_eq!(context.state.consecutive_failed_update_checks, 1234);
214 });
215 }
216217#[test]
218fn test_load_context_empty_storage() {
219 block_on(async {
220let storage = MemStorage::new();
221let context = Context::load(&storage).await;
222assert_eq!(None, context.schedule.last_update_time);
223assert_eq!(None, context.state.server_dictated_poll_interval);
224assert_eq!(0, context.state.consecutive_failed_update_checks);
225 });
226 }
227228#[test]
229fn test_persist_context() {
230 block_on(async {
231let mut storage = MemStorage::new();
232let last_update_time = PartialComplexTime::from_micros_since_epoch(123456789);
233let server_dictated_poll_interval = Some(Duration::from_micros(56789));
234let consecutive_failed_update_checks = 1234;
235let 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;
246assert_eq!(Some(123456789), storage.get_int(LAST_UPDATE_TIME).await);
247assert_eq!(
248Some(56789),
249 storage.get_int(SERVER_DICTATED_POLL_INTERVAL).await
250);
251assert_eq!(
252Some(1234),
253 storage.get_int(CONSECUTIVE_FAILED_UPDATE_CHECKS).await
254);
255assert!(!storage.committed());
256 });
257 }
258259#[test]
260fn test_persist_context_remove_defaults() {
261 block_on(async {
262let mut storage = MemStorage::new();
263let 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();
272273let 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;
284assert_eq!(Some(123456789), storage.get_int(LAST_UPDATE_TIME).await);
285assert_eq!(None, storage.get_int(SERVER_DICTATED_POLL_INTERVAL).await);
286assert_eq!(
287None,
288 storage.get_int(CONSECUTIVE_FAILED_UPDATE_CHECKS).await
289);
290assert!(!storage.committed());
291 });
292 }
293}