eager_package_config/
pkg_resolver.rs
1use fuchsia_url::UnpinnedAbsolutePackageUrl;
6use omaha_client::cup_ecdsa::PublicKeys;
7use omaha_client::version::Version;
8use serde::{Deserialize, Serialize};
9
10#[cfg(target_os = "fuchsia")]
11use {fidl_fuchsia_io as fio, futures::stream::StreamExt as _, std::collections::HashSet};
12
13#[cfg(target_os = "fuchsia")]
14const EAGER_PACKAGE_CONFIG_PATH: &str = "/config/data/eager_package_config.json";
15
16#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
17pub struct EagerPackageConfigs {
18 pub packages: Vec<EagerPackageConfig>,
19}
20
21#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
22pub struct EagerPackageConfig {
23 pub url: UnpinnedAbsolutePackageUrl,
24 #[serde(default, skip_serializing_if = "is_false")]
25 pub executable: bool,
26 pub public_keys: PublicKeys,
27 pub minimum_required_version: Version,
28 #[serde(default = "return_true", skip_serializing_if = "bool::clone")]
29 pub cache_fallback: bool,
30}
31
32#[cfg(target_os = "fuchsia")]
33impl EagerPackageConfigs {
34 pub async fn from_namespace() -> Result<Self, EagerPackageConfigsError> {
37 Self::from_path(EAGER_PACKAGE_CONFIG_PATH).await
38 }
39
40 async fn from_path(path: &str) -> Result<Self, EagerPackageConfigsError> {
41 let proxy = fuchsia_fs::file::open_in_namespace(
42 path,
43 fio::PERM_READABLE | fio::Flags::FLAG_SEND_REPRESENTATION,
44 )
45 .map_err(EagerPackageConfigsError::Open)?;
46 match fuchsia_fs::file::read(&proxy).await {
47 Ok(json) => Self::from_json(&json),
48 Err(e) => {
49 match proxy.take_event_stream().next().await {
50 Some(Ok(fio::FileEvent::OnOpen_ { s, .. }))
52 if s == zx::Status::NOT_FOUND.into_raw() =>
53 {
54 Ok(EagerPackageConfigs { packages: Vec::new() })
55 }
56 Some(Err(fidl::Error::ClientChannelClosed { status, .. }))
58 if status == zx::Status::NOT_FOUND =>
59 {
60 Ok(EagerPackageConfigs { packages: Vec::new() })
61 }
62 _ => Err(EagerPackageConfigsError::Read(e)),
63 }
64 }
65 }
66 }
67
68 fn from_json(json: &[u8]) -> Result<Self, EagerPackageConfigsError> {
69 let configs: Self = serde_json::from_slice(json)?;
70 if configs.packages.iter().map(|config| config.url.path()).collect::<HashSet<_>>().len()
71 < configs.packages.len()
72 {
73 return Err(EagerPackageConfigsError::DuplicatePath);
74 }
75 Ok(configs)
76 }
77}
78
79#[cfg(target_os = "fuchsia")]
80#[derive(Debug, thiserror::Error)]
81pub enum EagerPackageConfigsError {
82 #[error("open eager package config")]
83 Open(#[from] fuchsia_fs::node::OpenError),
84 #[error("read eager package config")]
85 Read(#[from] fuchsia_fs::file::ReadError),
86 #[error("parse eager package config from json")]
87 Json(#[from] serde_json::Error),
88 #[error("eager package URL must have unique path")]
89 DuplicatePath,
90}
91
92fn return_true() -> bool {
93 true
94}
95
96fn is_false(b: &bool) -> bool {
97 !b
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103 use assert_matches::assert_matches;
104 use fuchsia_async as fasync;
105 use omaha_client::cup_ecdsa::test_support::{
106 make_default_json_public_keys_for_test, make_default_public_keys_for_test,
107 };
108
109 #[fasync::run_singlethreaded(test)]
110 async fn not_found_tmp() {
111 let configs = EagerPackageConfigs::from_path("/tmp/not-found").await.unwrap();
112 assert_eq!(configs.packages, vec![]);
113 }
114
115 #[fasync::run_singlethreaded(test)]
116 async fn not_found_pkg() {
117 let configs = EagerPackageConfigs::from_path("/pkg/not-found").await.unwrap();
118 assert_eq!(configs.packages, vec![]);
119 }
120
121 #[fasync::run_singlethreaded(test)]
122 async fn success() {
123 let json = serde_json::json!({
124 "packages":[
125 {
126 "url": "fuchsia-pkg://example.com/package_service_1",
127 "public_keys": make_default_json_public_keys_for_test(),
128 "minimum_required_version": "1.2.3.4"
129 }
130 ]
131 });
132 assert_eq!(
133 EagerPackageConfigs::from_json(json.to_string().as_bytes()).unwrap(),
134 EagerPackageConfigs {
135 packages: vec![EagerPackageConfig {
136 url: "fuchsia-pkg://example.com/package_service_1".parse().unwrap(),
137 executable: false,
138 public_keys: make_default_public_keys_for_test(),
139 minimum_required_version: [1, 2, 3, 4].into(),
140 cache_fallback: true,
141 }]
142 }
143 );
144 }
145
146 #[fasync::run_singlethreaded(test)]
147 async fn duplicate_path() {
148 let json = serde_json::json!({
149 "packages":[
150 {
151 "url": "fuchsia-pkg://example.com/package_service_1",
152 "public_keys": make_default_json_public_keys_for_test(),
153 "minimum_required_version": "1.2.3.4"
154 },
155 {
156 "url": "fuchsia-pkg://another-example.com/package_service_1",
157 "public_keys": make_default_json_public_keys_for_test(),
158 "minimum_required_version": "1.2.3.4"
159 }
160 ]
161 });
162 assert_matches!(
163 EagerPackageConfigs::from_json(json.to_string().as_bytes()),
164 Err(EagerPackageConfigsError::DuplicatePath)
165 );
166 }
167}