update_package/
update_mode.rs
1use fidl_fuchsia_io as fio;
8use serde::{Deserialize, Serialize};
9use std::str::FromStr;
10use thiserror::Error;
11use zx_status::Status;
12
13#[derive(Debug, Error)]
15#[allow(missing_docs)]
16pub enum ParseUpdateModeError {
17 #[error("while opening the file")]
18 OpenFile(#[source] fuchsia_fs::node::OpenError),
19
20 #[error("while reading the file")]
21 ReadFile(#[source] fuchsia_fs::file::ReadError),
22
23 #[error("while deserializing: '{0:?}'")]
24 Deserialize(String, #[source] serde_json::Error),
25
26 #[error("update mode not supported: '{0}'")]
27 UpdateModeNotSupported(String),
28}
29
30#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
32#[serde(tag = "version", content = "content", deny_unknown_fields)]
33enum UpdateModeFile {
34 #[serde(rename = "1")]
35 Version1 {
36 #[serde(rename = "mode")]
41 update_mode: String,
42 },
43}
44
45#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47pub enum UpdateMode {
48 Normal,
50 ForceRecovery,
52}
53
54impl std::default::Default for UpdateMode {
55 fn default() -> Self {
56 Self::Normal
57 }
58}
59
60impl FromStr for UpdateMode {
61 type Err = ParseUpdateModeError;
62
63 fn from_str(s: &str) -> Result<Self, Self::Err> {
64 match s {
65 "normal" => Ok(UpdateMode::Normal),
66 "force-recovery" => Ok(UpdateMode::ForceRecovery),
67 other => Err(ParseUpdateModeError::UpdateModeNotSupported(other.to_string())),
68 }
69 }
70}
71
72pub(crate) async fn update_mode(
73 proxy: &fio::DirectoryProxy,
74) -> Result<Option<UpdateMode>, ParseUpdateModeError> {
75 let fopen_res =
77 fuchsia_fs::directory::open_file(proxy, "update-mode", fio::PERM_READABLE).await;
78 if let Err(fuchsia_fs::node::OpenError::OpenError(Status::NOT_FOUND)) = fopen_res {
79 return Ok(None);
80 }
81
82 let contents =
84 fuchsia_fs::file::read_to_string(&fopen_res.map_err(ParseUpdateModeError::OpenFile)?)
85 .await
86 .map_err(ParseUpdateModeError::ReadFile)?;
87
88 match serde_json::from_str(&contents)
90 .map_err(|e| ParseUpdateModeError::Deserialize(contents, e))?
91 {
92 UpdateModeFile::Version1 { update_mode } => update_mode.parse().map(Some),
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use crate::TestUpdatePackage;
100 use assert_matches::assert_matches;
101 use fuchsia_async as fasync;
102 use proptest::prelude::*;
103 use serde_json::json;
104
105 fn valid_update_mode_json_value(mode: &str) -> serde_json::Value {
106 json!({
107 "version": "1",
108 "content": {
109 "mode": mode,
110 },
111 })
112 }
113
114 fn valid_update_mode_json_string(mode: &str) -> String {
115 valid_update_mode_json_value(mode).to_string()
116 }
117
118 proptest! {
119 #[test]
120 fn test_json_serialize_roundtrip(s in ".+") {
121 let starting_json_value = valid_update_mode_json_value(&s);
123 let deserialized_object: UpdateModeFile =
124 serde_json::from_value(starting_json_value.clone())
125 .expect("json to deserialize");
126 assert_eq!(deserialized_object, UpdateModeFile::Version1 { update_mode: s });
127
128 let final_json_value =
132 serde_json::to_value(&deserialized_object)
133 .expect("serialize to value");
134 assert_eq!(final_json_value, starting_json_value);
135 }
136 }
137
138 #[fasync::run_singlethreaded(test)]
139 async fn parse_update_mode_success_normal() {
140 let p = TestUpdatePackage::new()
141 .add_file("update-mode", &valid_update_mode_json_string("normal"))
142 .await;
143 assert_matches!(p.update_mode().await, Ok(Some(UpdateMode::Normal)));
144 }
145
146 #[fasync::run_singlethreaded(test)]
147 async fn parse_update_mode_success_force_recovery() {
148 let p = TestUpdatePackage::new()
149 .add_file("update-mode", &valid_update_mode_json_string("force-recovery"))
150 .await;
151 assert_matches!(p.update_mode().await, Ok(Some(UpdateMode::ForceRecovery)));
152 }
153
154 #[fasync::run_singlethreaded(test)]
155 async fn parse_update_mode_success_missing_update_mode_file() {
156 let p = TestUpdatePackage::new();
157 assert_matches!(p.update_mode().await, Ok(None));
158 }
159
160 #[fasync::run_singlethreaded(test)]
161 async fn parse_update_mode_fail_unsupported_mode() {
162 let p = TestUpdatePackage::new()
163 .add_file("update-mode", &valid_update_mode_json_string("potato"))
164 .await;
165 assert_matches!(
166 p.update_mode().await,
167 Err(ParseUpdateModeError::UpdateModeNotSupported(mode)) if mode=="potato"
168 );
169 }
170
171 #[fasync::run_singlethreaded(test)]
172 async fn parse_update_mode_fail_deserialize() {
173 let p = TestUpdatePackage::new().add_file("update-mode", "oh no! this isn't json.").await;
174 assert_matches!(
175 p.update_mode().await,
176 Err(ParseUpdateModeError::Deserialize(s,_)) if s == "oh no! this isn't json."
177 );
178 }
179}