Skip to main content

fidl_fuchsia_update_installer_ext/
options.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Wrapper types for the Options table.
6
7use cobalt_client::traits::AsEventCode;
8use fuchsia_inspect::{self as inspect, ArrayProperty as _};
9use proptest::prelude::*;
10use proptest_derive::Arbitrary;
11use serde::{Deserialize, Serialize};
12use thiserror::Error;
13
14/// Who or what initiated the update installation.
15#[derive(Clone, Debug, Copy, PartialEq, Arbitrary, Serialize, Deserialize)]
16pub enum Initiator {
17    /// The install was initiated by an interactive user, or the user is
18    /// otherwise blocked and waiting for the result of this update.
19    User,
20
21    /// The install was initiated by a service, in the background.
22    Service,
23}
24
25impl Initiator {
26    fn name(&self) -> &'static str {
27        match self {
28            Initiator::User => "User",
29            Initiator::Service => "Service",
30        }
31    }
32}
33
34impl AsEventCode for Initiator {
35    fn as_event_code(&self) -> u32 {
36        use cobalt_sw_delivery_registry::SoftwareDeliveryMetricDimensionInitiator::*;
37        match self {
38            Initiator::Service => AutomaticUpdateCheck,
39            Initiator::User => UserInitiatedCheck,
40        }
41        .as_event_code()
42    }
43}
44
45/// A byte range.
46#[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary, Serialize, Deserialize)]
47pub struct Range {
48    /// The start offset in bytes.
49    pub offset: u64,
50    /// The size of the range in bytes.
51    pub size: u64,
52}
53
54/// Configuration options for an update attempt.
55#[derive(Clone, Debug, PartialEq, Arbitrary, Serialize, Deserialize)]
56pub struct Options {
57    /// What initiated this update attempt.
58    pub initiator: Initiator,
59
60    /// If an update is already in progress, it's acceptable to instead attach a
61    /// Monitor to that in-progress update instead of failing this request to
62    /// install the update.  Setting this option to true may convert situations
63    /// that would have resulted in the ALREADY_IN_PROGRESS to be treated as
64    /// non-error cases. A controller, if provided, will be ignored if the
65    /// running update attempt already has a controller.
66    pub allow_attach_to_existing_attempt: bool,
67
68    /// Determines if the installer should update the recovery partition if an
69    /// update is available.  Defaults to true.
70    pub should_write_recovery: bool,
71
72    /// Optional range parameter to be used as the `Range` HTTP header when fetching the manifest.
73    pub manifest_range: Option<Range>,
74
75    /// Optional HTTP headers to be included when fetching the manifest.
76    #[serde(skip)]
77    #[proptest(strategy = "prop::collection::vec(any_header(), 0..2)")]
78    pub manifest_headers: Vec<fidl_fuchsia_net_http::Header>,
79
80    /// Determines if the installer should commit the update by setting the
81    /// non-current configuration active. If false, the installer will not
82    /// reboot at the end of the update attempt, and any `RebootController`
83    /// passed to `Installer.StartUpdate` will be ignored. Defaults to true.
84    #[serde(default = "default_should_commit")]
85    pub should_commit: bool,
86}
87
88fn default_should_commit() -> bool {
89    true
90}
91
92fn any_header() -> impl Strategy<Value = fidl_fuchsia_net_http::Header> {
93    (prop::collection::vec(any::<u8>(), 0..10), prop::collection::vec(any::<u8>(), 0..10))
94        .prop_map(|(name, value)| fidl_fuchsia_net_http::Header { name, value })
95}
96
97impl Options {
98    /// Serializes Options to a Fuchsia Inspect node.
99    pub fn write_to_inspect(&self, node: &inspect::Node) {
100        let Options {
101            initiator,
102            allow_attach_to_existing_attempt,
103            should_write_recovery,
104            manifest_range,
105            manifest_headers,
106            should_commit,
107        } = self;
108        node.record_string("initiator", initiator.name());
109        node.record_bool("allow_attach_to_existing_attempt", *allow_attach_to_existing_attempt);
110        node.record_bool("should_write_recovery", *should_write_recovery);
111        node.record_bool("should_commit", *should_commit);
112        if let Some(range) = manifest_range {
113            node.record_child("manifest_range", |range_node| {
114                range_node.record_uint("offset", range.offset);
115                range_node.record_uint("size", range.size);
116            });
117        }
118        // Only record the names of the headers, to avoid leaking potentially sensitive data.
119        if !manifest_headers.is_empty() {
120            let header_names =
121                node.create_string_array("manifest_header_names", manifest_headers.len());
122            for (i, header) in manifest_headers.iter().enumerate() {
123                header_names.set(i, String::from_utf8_lossy(&header.name));
124            }
125            node.record(header_names);
126        }
127    }
128}
129
130/// Errors for parsing fidl_update_installer_ext Options struct.
131#[derive(Error, Debug, PartialEq)]
132pub enum OptionsParseError {
133    /// Initiator is None.
134    #[error("missing initiator")]
135    MissingInitiator,
136}
137
138impl From<fidl_fuchsia_update_installer::Range> for Range {
139    fn from(data: fidl_fuchsia_update_installer::Range) -> Self {
140        Self { offset: data.offset, size: data.size }
141    }
142}
143
144impl From<&Range> for fidl_fuchsia_update_installer::Range {
145    fn from(range: &Range) -> Self {
146        Self { offset: range.offset, size: range.size }
147    }
148}
149
150impl TryFrom<fidl_fuchsia_update_installer::Options> for Options {
151    type Error = OptionsParseError;
152
153    fn try_from(data: fidl_fuchsia_update_installer::Options) -> Result<Self, OptionsParseError> {
154        let initiator =
155            data.initiator.map(|o| o.into()).ok_or(OptionsParseError::MissingInitiator)?;
156
157        let manifest_range = data.manifest_range.map(Range::from);
158        let manifest_headers = data.manifest_headers.unwrap_or_default();
159
160        Ok(Self {
161            initiator,
162            allow_attach_to_existing_attempt: data
163                .allow_attach_to_existing_attempt
164                .unwrap_or(false),
165            should_write_recovery: data.should_write_recovery.unwrap_or(true),
166            manifest_range,
167            manifest_headers,
168            should_commit: data.should_commit.unwrap_or(true),
169        })
170    }
171}
172
173impl From<&Options> for fidl_fuchsia_update_installer::Options {
174    fn from(options: &Options) -> Self {
175        Self {
176            initiator: Some(options.initiator.into()),
177            allow_attach_to_existing_attempt: Some(options.allow_attach_to_existing_attempt),
178            should_write_recovery: Some(options.should_write_recovery),
179            manifest_range: options.manifest_range.as_ref().map(|r| r.into()),
180            manifest_headers: if options.manifest_headers.is_empty() {
181                None
182            } else {
183                Some(options.manifest_headers.clone())
184            },
185            should_commit: Some(options.should_commit),
186            ..Default::default()
187        }
188    }
189}
190
191impl From<Options> for fidl_fuchsia_update_installer::Options {
192    fn from(data: Options) -> Self {
193        (&data).into()
194    }
195}
196
197impl From<fidl_fuchsia_update_installer::Initiator> for Initiator {
198    fn from(fidl_initiator: fidl_fuchsia_update_installer::Initiator) -> Self {
199        match fidl_initiator {
200            fidl_fuchsia_update_installer::Initiator::User => Initiator::User,
201            fidl_fuchsia_update_installer::Initiator::Service => Initiator::Service,
202        }
203    }
204}
205
206impl From<Initiator> for fidl_fuchsia_update_installer::Initiator {
207    fn from(initiator: Initiator) -> Self {
208        match initiator {
209            Initiator::User => fidl_fuchsia_update_installer::Initiator::User,
210            Initiator::Service => fidl_fuchsia_update_installer::Initiator::Service,
211        }
212    }
213}
214
215#[cfg(test)]
216mod tests {
217
218    use super::*;
219
220    proptest! {
221        #[test]
222        /// Verifies that converting any instance of Options to fidl_fuchsia_update_installer::Options
223        /// and back to Options produces exactly the same options that we started with.
224        fn options_roundtrips_through_fidl(options: Options) {
225            let as_fidl: fidl_fuchsia_update_installer::Options = options.clone().into();
226            prop_assert_eq!(as_fidl.try_into(), Ok(options));
227        }
228
229        #[test]
230        /// Verifies that a fidl_fuchsia_update_installer::Options without an Initiator raises an error.
231        fn fidl_options_sans_initiator_error(options: Options) {
232            let mut as_fidl: fidl_fuchsia_update_installer::Options = options.into();
233            as_fidl.initiator = None;
234            prop_assert_eq!(Options::try_from(as_fidl), Err(OptionsParseError::MissingInitiator));
235        }
236    }
237}