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