1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
// Copyright 2019 The Fuchsia Authors
//
// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
// This file may not be copied, modified, or distributed except according to
// those terms.

use crate::{
    common::{App, CheckOptions, CheckTiming, ProtocolState, UpdateCheckSchedule},
    installer::Plan,
    request_builder::RequestParams,
    time::{ComplexTime, TimeSource},
};
use futures::future::BoxFuture;

#[cfg(test)]
mod mock;
#[cfg(test)]
pub use mock::MockPolicyEngine;
mod stub;
pub use stub::StubPolicy;
pub use stub::StubPolicyEngine;
use typed_builder::TypedBuilder;

/// Data about the local system that's needed to fulfill Policy questions
#[derive(Clone, Debug, TypedBuilder)]
pub struct PolicyData {
    /// The current time at the start of the update
    #[builder(setter(into))]
    pub current_time: ComplexTime,
}

/// Reasons why a check can/cannot be performed at this time
#[derive(Clone, Debug, PartialEq)]
pub enum CheckDecision {
    /// positive responses
    Ok(RequestParams),
    /// but with caveats:
    OkUpdateDeferred(RequestParams),

    /// negative responses
    TooSoon,
    ThrottledByPolicy,
    DeniedByPolicy,
}

#[cfg(test)]
impl Default for CheckDecision {
    fn default() -> Self {
        CheckDecision::Ok(RequestParams::default())
    }
}

/// Reasons why an update can/cannot be performed at this time
#[derive(Clone, Debug, PartialEq)]
pub enum UpdateDecision {
    /// Update can be performed.
    Ok,
    /// Update is deferred by Policy.
    DeferredByPolicy,
    /// Update is rejected by Policy.
    DeniedByPolicy,
}

#[cfg(test)]
// Disable clippy lint for this impl as we want to derive it manually so that it is only available
// for tests.
#[allow(clippy::derivable_impls)]
impl Default for UpdateDecision {
    fn default() -> Self {
        UpdateDecision::Ok
    }
}

/// The policy implementation itself
pub trait Policy {
    type ComputeNextUpdateTimePolicyData;
    type UpdateCheckAllowedPolicyData;
    type UpdateCanStartPolicyData;
    type RebootPolicyData;
    type InstallPlan: Plan;

    /// When should the next update happen?
    fn compute_next_update_time(
        policy_data: &Self::ComputeNextUpdateTimePolicyData,
        apps: &[App],
        scheduling: &UpdateCheckSchedule,
        protocol_state: &ProtocolState,
    ) -> CheckTiming;

    /// Given the current State, and the current PolicyData, is an update check
    /// allowed at this time.  A CheckDecision is used to return the reasoning, as in
    /// some cases, instead of an update check, the SM will instead notify Omaha that
    /// it would perform an update, but instead just tell the device whether or not
    /// an update is available.
    fn update_check_allowed(
        policy_data: &Self::UpdateCheckAllowedPolicyData,
        apps: &[App],
        scheduling: &UpdateCheckSchedule,
        protocol_state: &ProtocolState,
        check_options: &CheckOptions,
    ) -> CheckDecision;

    /// Given the current State, the current PolicyData, can the proposed InstallPlan
    /// be executed at this time.
    fn update_can_start(
        policy_data: &Self::UpdateCanStartPolicyData,
        proposed_install_plan: &Self::InstallPlan,
    ) -> UpdateDecision;

    /// Given the current PolicyData, is reboot allowed right now.
    fn reboot_allowed(policy_data: &Self::RebootPolicyData, check_options: &CheckOptions) -> bool;

    /// Given the InstallPlan, is reboot needed after update has been installed.
    fn reboot_needed(install_plan: &Self::InstallPlan) -> bool;
}

pub trait PolicyEngine {
    type TimeSource: TimeSource + Clone;
    type InstallResult;
    type InstallPlan: Plan;

    /// Provides the time source used by the PolicyEngine to the state machine.
    fn time_source(&self) -> &Self::TimeSource;

    /// When should the next update happen?
    fn compute_next_update_time<'a>(
        &'a mut self,
        apps: &'a [App],
        scheduling: &'a UpdateCheckSchedule,
        protocol_state: &'a ProtocolState,
    ) -> BoxFuture<'a, CheckTiming>;

    /// Given the context provided by State, does the Policy allow an update check to
    /// happen at this time?  This should be consistent with the compute_next_update_time
    /// so that during background updates, the result of compute_next_update_time will
    /// result in a CheckDecision::Ok() value from this function.
    fn update_check_allowed<'a>(
        &'a mut self,
        apps: &'a [App],
        scheduling: &'a UpdateCheckSchedule,
        protocol_state: &'a ProtocolState,
        check_options: &'a CheckOptions,
    ) -> BoxFuture<'a, CheckDecision>;

    /// Given the current State, the current PolicyData, can the proposed InstallPlan
    /// be executed at this time.
    fn update_can_start<'a>(
        &'a mut self,
        proposed_install_plan: &'a Self::InstallPlan,
    ) -> BoxFuture<'a, UpdateDecision>;

    /// Is reboot allowed right now.
    fn reboot_allowed<'a>(
        &'a mut self,
        check_options: &'a CheckOptions,
        install_result: &'a Self::InstallResult,
    ) -> BoxFuture<'a, bool>;

    /// Given the InstallPlan, is reboot needed after update has been installed.
    fn reboot_needed<'a>(&'a mut self, install_plan: &'a Self::InstallPlan) -> BoxFuture<'a, bool>;
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::time::MockTimeSource;

    #[test]
    pub fn test_policy_data_builder_with_system_time() {
        let current_time = MockTimeSource::new_from_now().now();
        let policy_data = PolicyData::builder().current_time(current_time).build();
        assert_eq!(policy_data.current_time, current_time);
    }

    #[test]
    pub fn test_policy_data_builder_with_clock() {
        let source = MockTimeSource::new_from_now();
        let current_time = source.now();
        let policy_data = PolicyData::builder().current_time(source.now()).build();
        assert_eq!(policy_data.current_time, current_time);
    }
}