1use anyhow::{Context, Result, bail, format_err};
6use async_utils::async_once::Once;
7use cm_types::Name;
8use fidl::AsHandleRef;
9use fidl::endpoints::ServerEnd;
10use fidl_fuchsia_data as fdata;
11use fidl_fuchsia_io as fio;
12use fidl_fuchsia_process as fprocess;
13use fidl_fuchsia_process_lifecycle as fpl;
14use fuchsia_component::directory::AsRefDirectory;
15use fuchsia_component::server::{ServiceFs, ServiceObj, ServiceObjTrait};
16use fuchsia_fs::directory::{WatchEvent, Watcher};
17use futures::prelude::*;
18use std::borrow::Cow;
19use std::path::Path;
20use std::sync::Arc;
21
22fn extract_event_filename<'a>(path: &'a Path) -> Option<Cow<'a, str>> {
25 let s = path.to_str()?;
26 if s == "." {
27 Some(Cow::Borrowed("."))
29 } else {
30 Some(Cow::Borrowed(s))
33 }
34}
35
36async fn wait_for_first_instance(svc: &fio::DirectoryProxy) -> Result<String> {
37 const INPUT_SERVICE: &str = "input";
38 let (service_dir, request) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
39 svc.as_ref_directory().open(
40 INPUT_SERVICE,
41 fio::Flags::PROTOCOL_DIRECTORY | fio::PERM_READABLE,
42 request.into(),
43 )?;
44 let watcher = Watcher::new(&service_dir).await.context("failed to create watcher")?;
45
46 let mut stream =
47 watcher.map(|result| result.context("failed to get watcher event")).try_filter_map(|msg| {
48 futures::future::ok(match msg.event {
49 WatchEvent::EXISTING | WatchEvent::ADD_FILE => {
50 let filename = extract_event_filename(msg.filename.as_path())
51 .expect("filename must be valid utf8");
52
53 if filename.as_ref() == "." { None } else { Some(filename.into_owned()) }
54 }
55 _ => None,
56 })
57 });
58
59 let first = stream.try_next().await?.ok_or_else(|| {
60 format_err!("Watcher stream closed unexpectedly before finding an instance")
61 })?;
62
63 Ok(format!("{INPUT_SERVICE}/{first}"))
64}
65
66async fn connect_request(
67 svc: &fio::DirectoryProxy,
68 request: zx::Channel,
69 protocol_name: &Name,
70 instance_dir: &str,
71) {
72 let target_path = format!("{instance_dir}/{}", protocol_name.as_str());
73
74 if let Err(e) = svc.as_ref_directory().open(&target_path, fio::Flags::PROTOCOL_SERVICE, request)
75 {
76 log::error!("[service-broker] Failed to forward connection to {target_path}: {e}");
77 }
78}
79
80async fn first_instance_to_protocol<'a>(
81 svc: fio::DirectoryProxy,
82 fs: &mut ServiceFs<ServiceObj<'a, ()>>,
83 protocol_name: Name,
84 scope: &'a fuchsia_async::Scope,
85) -> Result<()> {
86 let cached_instance: Arc<Once<String>> = Arc::new(Once::new());
87 let svc_arc = Arc::new(svc);
88
89 fs.dir("svc").add_service_at("output", move |request: zx::Channel| {
90 let svc = Arc::clone(&svc_arc);
91 let protocol_name = protocol_name.clone();
92 let cached_instance = Arc::clone(&cached_instance);
93
94 scope.spawn(async move {
95 let init_future = async || wait_for_first_instance(&svc).await;
98
99 match cached_instance.get_or_try_init(init_future).await {
100 Ok(instance_dir) => {
101 connect_request(&svc, request, &protocol_name, instance_dir).await;
102 }
103 Err(e) => {
104 log::error!(
105 "[service-broker] Failed to resolve first instance: {e}, {protocol_name}"
106 );
107 }
108 }
109 });
110
111 Some(())
112 });
113
114 Ok(())
115}
116
117async fn first_instance_to_default<T: ServiceObjTrait>(
118 svc: fio::DirectoryProxy,
119 fs: &mut ServiceFs<T>,
120) -> Result<()> {
121 let instance_dir_path = wait_for_first_instance(&svc).await?;
124 let (instance_dir, request) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
125 svc.as_ref_directory().open(
126 &instance_dir_path,
127 fio::Flags::PROTOCOL_DIRECTORY | fio::PERM_READABLE,
128 request.into(),
129 )?;
130
131 fs.dir("svc").dir("output").add_remote("default", instance_dir);
132 Ok(())
133}
134
135async fn filter_and_rename<T: ServiceObjTrait>(
136 _svc: fio::DirectoryProxy,
137 _fs: &mut ServiceFs<T>,
138 _filter: &Vec<String>,
139 _rename: &Vec<String>,
140) -> Result<()> {
141 bail!("filter_and_rename policy is not yet implemented");
142 }
144
145fn get_value<'a>(dict: &'a fdata::Dictionary, key: &str) -> Option<&'a fdata::DictionaryValue> {
146 match &dict.entries {
147 Some(entries) => {
148 for entry in entries {
149 if entry.key == key {
150 return entry.value.as_ref().map(|val| &**val);
151 }
152 }
153 None
154 }
155 _ => None,
156 }
157}
158
159fn get_program_string<'a>(program: &'a fdata::Dictionary, key: &str) -> Result<&'a str> {
160 if let Some(fdata::DictionaryValue::Str(value)) = get_value(program, key) {
161 Ok(value)
162 } else {
163 Err(format_err!("{key} not found in program or is not a string"))
164 }
165}
166
167fn get_program_strvec<'a>(
168 program: &'a fdata::Dictionary,
169 key: &str,
170) -> Result<Option<&'a Vec<String>>> {
171 match get_value(program, key) {
172 Some(args_value) => match args_value {
173 fdata::DictionaryValue::StrVec(vec) => Ok(Some(vec)),
174 _ => Err(format_err!(
175 "Expected {key} in program to be vector of strings, found something else"
176 )),
177 },
178 None => Ok(None),
179 }
180}
181
182pub async fn main(
183 ns_entries: Vec<fprocess::NameInfo>,
184 directory_request: ServerEnd<fio::DirectoryMarker>,
185 lifecycle: ServerEnd<fpl::LifecycleMarker>,
186 program: Option<fdata::Dictionary>,
187) -> Result<()> {
188 drop(lifecycle);
189 if directory_request.as_handle_ref().is_invalid() {
190 bail!("No valid handle found for outgoing directory");
191 }
192 let Some(svc) = ns_entries.into_iter().find(|e| e.path == "/svc") else {
193 bail!("No /svc in namespace");
194 };
195 let Some(program) = program else {
196 bail!("No program section provided");
197 };
198 let scope = fuchsia_async::Scope::new();
199 let svc = svc.directory.into_proxy();
200 let mut fs = ServiceFs::new();
201 match get_program_string(&program, "policy")? {
202 "first_instance_to_protocol" => {
203 let protocol_name_str = get_program_string(&program, "protocol_name")?;
204
205 let protocol_name = Name::new(protocol_name_str).map_err(|e| {
206 format_err!("Invalid protocol_name '{protocol_name_str}' in program dict: {e}")
207 })?;
208
209 first_instance_to_protocol(svc, &mut fs, protocol_name, &scope).await
210 }
211 "first_instance_to_default" => first_instance_to_default(svc, &mut fs).await,
212 "filter_and_rename" => {
213 let empty = vec![];
214 let filter = get_program_strvec(&program, "filter")?.unwrap_or(&empty);
215 let rename = get_program_strvec(&program, "rename")?.unwrap_or(&empty);
216 filter_and_rename(svc, &mut fs, filter, rename).await
217 }
218 policy => Err(format_err!("Unsupported policy specified: {policy}")),
219 }?;
220
221 log::debug!("[service-broker] Initialized.");
222
223 fs.serve_connection(directory_request).context("failed to serve outgoing namespace")?;
224 fs.collect::<()>().await;
225 Ok(())
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use fidl::endpoints::{Proxy, create_endpoints, create_proxy};
232 use fidl_fuchsia_data as fdata;
233 use fuchsia_async as fasync;
234 use futures::StreamExt;
235
236 fn make_program_dict(entries: Vec<(&str, fdata::DictionaryValue)>) -> fdata::Dictionary {
237 let entries = entries
238 .into_iter()
239 .map(|(k, v)| fdata::DictionaryEntry { key: k.to_string(), value: Some(Box::new(v)) })
240 .collect();
241 fdata::Dictionary { entries: Some(entries), ..Default::default() }
242 }
243
244 #[test]
245 fn test_get_program_string() {
246 let dict = make_program_dict(vec![(
247 "policy",
248 fdata::DictionaryValue::Str("first_instance_to_protocol".to_string()),
249 )]);
250
251 assert_eq!(get_program_string(&dict, "policy").unwrap(), "first_instance_to_protocol");
252
253 let err = get_program_string(&dict, "missing_key").unwrap_err();
254 assert_eq!(err.to_string(), "missing_key not found in program or is not a string");
255 }
256
257 #[test]
258 fn test_get_program_strvec() {
259 let dict = make_program_dict(vec![(
260 "filter",
261 fdata::DictionaryValue::StrVec(vec!["fuchsia.foo.Bar".to_string()]),
262 )]);
263
264 let vec = get_program_strvec(&dict, "filter").unwrap().unwrap();
265 assert_eq!(vec.len(), 1);
266 assert_eq!(vec[0], "fuchsia.foo.Bar");
267
268 assert!(get_program_strvec(&dict, "rename").unwrap().is_none());
269 }
270
271 #[fuchsia::test]
272 async fn test_filter_and_rename_graceful_failure() {
273 let (dir_proxy, _server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
274 let mut fs = ServiceFs::<ServiceObj<'_, ()>>::new();
275
276 let result = filter_and_rename(dir_proxy, &mut fs, &vec![], &vec![]).await;
277 assert!(result.is_err());
278 assert_eq!(
279 result.unwrap_err().to_string(),
280 "filter_and_rename policy is not yet implemented"
281 );
282 }
283
284 #[fuchsia::test]
285 async fn test_broker_caching_and_routing_end_to_end() {
286 let (svc_dir, svc_server_end) = create_proxy::<fio::DirectoryMarker>();
287 let mut fake_svc_fs = ServiceFs::new();
288
289 fake_svc_fs.dir("input").dir("instance_123").add_service_at(
290 "my_protocol",
291 |req: zx::Channel| {
292 let _ = req.write(&[1], &mut []);
293 Some(())
294 },
295 );
296
297 fasync::Task::spawn(async move {
298 fake_svc_fs.serve_connection(svc_server_end).unwrap();
299 fake_svc_fs.collect::<()>().await;
300 })
301 .detach();
302
303 let ns_entries = vec![fprocess::NameInfo {
304 path: "/svc".to_string(),
305 directory: svc_dir.into_channel().unwrap().into_zx_channel().into(),
306 }];
307
308 let (out_dir, out_server_end) = create_proxy::<fio::DirectoryMarker>();
309 let (_, lifecycle_server_end) = create_endpoints::<fpl::LifecycleMarker>();
310
311 let program_dict = make_program_dict(vec![
312 ("policy", fdata::DictionaryValue::Str("first_instance_to_protocol".to_string())),
313 ("protocol_name", fdata::DictionaryValue::Str("my_protocol".to_string())),
314 ]);
315
316 fasync::Task::spawn(async move {
317 let res =
318 main(ns_entries, out_server_end, lifecycle_server_end, Some(program_dict)).await;
319 assert!(res.is_ok(), "Broker main task failed");
320 })
321 .detach();
322
323 let (client_end, server_end) = zx::Channel::create();
324
325 out_dir
326 .open("svc/output", fio::Flags::PROTOCOL_SERVICE, &fio::Options::default(), server_end)
327 .expect("Failed to send open request to broker");
328
329 let signals = fasync::OnSignals::new(&client_end, zx::Signals::CHANNEL_READABLE)
330 .await
331 .expect("Failed waiting for signal. Routing may have dropped the channel.");
332
333 assert!(signals.contains(zx::Signals::CHANNEL_READABLE));
334
335 let (client_end2, server_end2) = zx::Channel::create();
336 out_dir
337 .open("svc/output", fio::Flags::PROTOCOL_SERVICE, &fio::Options::default(), server_end2)
338 .unwrap();
339
340 let _ = fasync::OnSignals::new(&client_end2, zx::Signals::CHANNEL_READABLE).await.unwrap();
341 }
342}