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