system_update_configurator/
service.rs1use crate::bridge::{Bridge, OptOutPreference};
6use anyhow::anyhow;
7use fidl_fuchsia_update_config::{
8 OptOutAdminError, OptOutAdminRequest, OptOutAdminRequestStream, OptOutRequest,
9 OptOutRequestStream,
10};
11use fuchsia_component::server::{ServiceFs, ServiceObjLocal};
12use futures::channel::mpsc;
13use futures::prelude::*;
14use log::warn;
15
16pub type Fs = ServiceFs<ServiceObjLocal<'static, IncomingService>>;
19
20pub enum IncomingService {
22 OptOut(OptOutRequestStream),
24
25 OptOutAdmin(OptOutAdminRequestStream),
27}
28
29enum IncomingRequest {
30 OptOut(OptOutRequest),
31 OptOutAdmin(OptOutAdminRequest),
32}
33
34pub async fn serve(mut fs: Fs, storage: &mut dyn Bridge) {
36 fs.dir("svc")
37 .add_fidl_service(IncomingService::OptOut)
38 .add_fidl_service(IncomingService::OptOutAdmin);
39
40 serve_connections(fs, storage).await
41}
42
43async fn handle_request(req: IncomingRequest, storage: &mut dyn Bridge) {
44 match req {
45 IncomingRequest::OptOut(OptOutRequest::Get { responder }) => {
46 let res = match storage.get_opt_out().await {
47 Ok(value) => value.into(),
48 Err(e) => {
49 warn!(
50 "Could not determine opt-out status, closing the request channel: {:#}",
51 anyhow!(e)
52 );
53 return;
54 }
55 };
56
57 if let Err(e) = responder.send(res) {
58 warn!("Could not respond to OptOut::Get request: {:#}", anyhow!(e));
59 }
60 }
61 IncomingRequest::OptOutAdmin(OptOutAdminRequest::Set { value, responder }) => {
62 let res = match storage.set_opt_out(value.into()).await {
63 Ok(()) => Ok(()),
64 Err(_) => Err(OptOutAdminError::Internal),
65 };
66
67 if let Err(e) = responder.send(res) {
68 warn!("Could not respond to OptOut::Set request: {:#}", anyhow!(e));
69 }
70 }
71 }
72}
73
74async fn serve_connections(
76 connections: impl Stream<Item = IncomingService> + 'static,
77 storage: &mut dyn Bridge,
78) {
79 let (send_requests, mut recv_requests) = mpsc::channel(1);
80
81 let forward_requests =
83 fuchsia_async::Task::local(connections.for_each_concurrent(None, move |conn| {
84 let send_requests = send_requests.clone().sink_map_err(SinkError::Forward);
85 async move {
86 let res = match conn {
87 IncomingService::OptOut(conn) => {
88 conn.map_ok(IncomingRequest::OptOut)
89 .map_err(SinkError::Read)
90 .forward(send_requests)
91 .await
92 }
93 IncomingService::OptOutAdmin(conn) => {
94 conn.map_ok(IncomingRequest::OptOutAdmin)
95 .map_err(SinkError::Read)
96 .forward(send_requests)
97 .await
98 }
99 };
100 match res {
101 Ok(()) => {}
102 Err(e @ SinkError::Read(_)) => {
103 warn!("Closing request channel: {:#}", anyhow!(e))
104 }
105 Err(SinkError::Forward(_)) => {
106 }
108 }
109 }
110 }));
111
112 while let Some(request) = recv_requests.next().await {
114 handle_request(request, storage).await;
115 }
116
117 forward_requests.await
118}
119
120#[derive(Debug, thiserror::Error)]
123enum SinkError {
124 #[error("while reading the request")]
125 Read(#[source] fidl::Error),
126
127 #[error("while forwarding the request to the handler")]
128 Forward(#[source] mpsc::SendError),
129}
130
131impl From<fidl_fuchsia_update_config::OptOutPreference> for OptOutPreference {
132 fn from(x: fidl_fuchsia_update_config::OptOutPreference) -> Self {
133 use fidl_fuchsia_update_config::OptOutPreference::*;
134 match x {
135 AllowAllUpdates => OptOutPreference::AllowAllUpdates,
136 AllowOnlySecurityUpdates => OptOutPreference::AllowOnlySecurityUpdates,
137 }
138 }
139}
140
141impl From<OptOutPreference> for fidl_fuchsia_update_config::OptOutPreference {
142 fn from(x: OptOutPreference) -> Self {
143 use fidl_fuchsia_update_config::OptOutPreference::*;
144 match x {
145 OptOutPreference::AllowAllUpdates => AllowAllUpdates,
146 OptOutPreference::AllowOnlySecurityUpdates => AllowOnlySecurityUpdates,
147 }
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use crate::bridge;
155 use assert_matches::assert_matches;
156 use fidl_fuchsia_update_config::{
157 OptOutAdminMarker, OptOutAdminProxy, OptOutMarker,
158 OptOutPreference as FidlOptOutPreference, OptOutProxy,
159 };
160 use fuchsia_async::Task;
161
162 fn spawn_serve(mut storage: impl Bridge + 'static) -> (Connector, Task<()>) {
163 let (send, recv) = mpsc::unbounded();
164
165 let svc = async move { serve_connections(recv, &mut storage).await };
166
167 (Connector(send), Task::local(svc))
168 }
169
170 struct Connector(mpsc::UnboundedSender<IncomingService>);
171
172 impl Connector {
173 fn opt_out(&self) -> OptOutProxy {
174 let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<OptOutMarker>();
175 self.0.unbounded_send(IncomingService::OptOut(stream)).unwrap();
176 proxy
177 }
178 fn opt_out_admin(&self) -> OptOutAdminProxy {
179 let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<OptOutAdminMarker>();
180 self.0.unbounded_send(IncomingService::OptOutAdmin(stream)).unwrap();
181 proxy
182 }
183 }
184
185 #[fuchsia::test]
186 async fn start_stop() {
187 let fs = ServiceFs::new_local();
188
189 serve(fs, &mut bridge::testing::Error).await
190 }
191
192 #[fuchsia::test]
193 async fn get_initial_state() {
194 let (connector, svc) =
195 spawn_serve(bridge::testing::Fake::new(OptOutPreference::AllowAllUpdates));
196 assert_eq!(connector.opt_out().get().await.unwrap(), FidlOptOutPreference::AllowAllUpdates);
197 drop(connector);
198 svc.await;
199
200 let (connector, svc) =
201 spawn_serve(bridge::testing::Fake::new(OptOutPreference::AllowOnlySecurityUpdates));
202 assert_eq!(
203 connector.opt_out().get().await.unwrap(),
204 FidlOptOutPreference::AllowOnlySecurityUpdates
205 );
206 drop(connector);
207 svc.await;
208 }
209
210 #[fuchsia::test]
211 async fn uses_storage_to_provide_preference() {
212 let storage = bridge::testing::Fake::new(OptOutPreference::AllowAllUpdates);
213 let (connector, svc) = spawn_serve(storage);
214
215 let get1 = connector.opt_out();
216 let get2 = connector.opt_out();
217 let set1 = connector.opt_out_admin();
218
219 assert_eq!(get1.get().await.unwrap(), FidlOptOutPreference::AllowAllUpdates);
220 assert_eq!(get2.get().await.unwrap(), FidlOptOutPreference::AllowAllUpdates);
221
222 assert_matches!(set1.set(FidlOptOutPreference::AllowOnlySecurityUpdates).await, Ok(Ok(())));
223
224 assert_eq!(get1.get().await.unwrap(), FidlOptOutPreference::AllowOnlySecurityUpdates);
225 assert_eq!(get2.get().await.unwrap(), FidlOptOutPreference::AllowOnlySecurityUpdates);
226
227 assert_matches!(set1.set(FidlOptOutPreference::AllowAllUpdates).await, Ok(Ok(())));
228
229 assert_eq!(get1.get().await.unwrap(), FidlOptOutPreference::AllowAllUpdates);
230 assert_eq!(connector.opt_out().get().await.unwrap(), FidlOptOutPreference::AllowAllUpdates);
231
232 drop(get1);
235 drop(get2);
236 drop(set1);
237 drop(connector);
238 svc.await;
239 }
240
241 #[fuchsia::test]
242 async fn closes_connection_on_read_error() {
243 let (storage, fail_requests) =
244 bridge::testing::Fake::new_with_error_toggle(OptOutPreference::AllowAllUpdates);
245 let (connector, svc) = spawn_serve(storage);
246
247 let proxy1 = connector.opt_out();
248 let proxy2 = connector.opt_out();
249
250 assert_eq!(proxy1.get().await.unwrap(), FidlOptOutPreference::AllowAllUpdates);
252 assert_eq!(proxy2.get().await.unwrap(), FidlOptOutPreference::AllowAllUpdates);
253
254 fail_requests.set(true);
255 assert_matches!(proxy1.get().await, Err(_));
256
257 fail_requests.set(false);
260 assert_matches!(proxy1.get().await, Err(_));
261 assert_eq!(proxy2.get().await.unwrap(), FidlOptOutPreference::AllowAllUpdates);
262
263 drop(proxy2);
266
267 drop(connector);
268 svc.await;
269 }
270
271 #[fuchsia::test]
272 async fn responds_with_error_on_write_error() {
273 let (storage, fail_requests) =
274 bridge::testing::Fake::new_with_error_toggle(OptOutPreference::AllowAllUpdates);
275 let (connector, svc) = spawn_serve(storage);
276
277 let proxy = connector.opt_out_admin();
278
279 fail_requests.set(true);
280 assert_matches!(
281 proxy.set(FidlOptOutPreference::AllowAllUpdates).await,
282 Ok(Err(OptOutAdminError::Internal))
283 );
284
285 fail_requests.set(false);
287 assert_matches!(proxy.set(FidlOptOutPreference::AllowAllUpdates).await, Ok(Ok(())));
288
289 drop(proxy);
292 drop(connector);
293 svc.await;
294 }
295}