Skip to main content

driver_manager_utils/
dictionary_utils.rs

1// Copyright 2026 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
5use crate::errors::UtilsError;
6use fidl_fuchsia_component_sandbox::{
7    self as fsandbox, AggregateSource, Capability, CapabilityId, DictionaryItem, DirConnector,
8    DirConnectorRouterRouteResponse, RouteRequest,
9};
10use std::cell::Cell;
11use std::collections::HashMap;
12
13pub struct DictionaryUtil {
14    capability_store: fsandbox::CapabilityStoreProxy,
15    cap_id: Cell<CapabilityId>,
16}
17
18impl DictionaryUtil {
19    pub fn new(capability_store: fsandbox::CapabilityStoreProxy) -> Self {
20        Self { capability_store, cap_id: Cell::new(0) }
21    }
22}
23
24impl DictionaryUtil {
25    pub async fn import_dictionary(
26        &self,
27        dictionary: fsandbox::DictionaryRef,
28    ) -> Result<CapabilityId, UtilsError> {
29        let dest_id = self.next_cap_id();
30        let capability = Capability::Dictionary(dictionary);
31        self.capability_store.import(dest_id, capability).await??;
32        Ok(dest_id)
33    }
34
35    pub async fn copy_export_dictionary(
36        &self,
37        dictionary_id: CapabilityId,
38    ) -> Result<fsandbox::DictionaryRef, UtilsError> {
39        let dest_id = self.next_cap_id();
40        self.capability_store.dictionary_copy(dictionary_id, dest_id).await??;
41        let exported = self.capability_store.export(dest_id).await??;
42        if let Capability::Dictionary(d) = exported {
43            Ok(d)
44        } else {
45            Err(UtilsError::UnexpectedCapabilityType(
46                "Dictionary".to_string(),
47                format!("{:?}", exported),
48            ))
49        }
50    }
51
52    pub async fn dictionary_dir_connector_route(
53        &self,
54        dictionary_id: CapabilityId,
55        service_name: &str,
56    ) -> Result<DirConnector, UtilsError> {
57        let dest_id = self.next_cap_id();
58        self.capability_store.dictionary_get(dictionary_id, service_name, dest_id).await??;
59        let exported = self.capability_store.export(dest_id).await??;
60        let Capability::DirConnectorRouter(router) = exported else {
61            return Err(UtilsError::UnexpectedCapabilityType(
62                "DirConnectorRouter".to_string(),
63                format!("{:?}", exported),
64            ));
65        };
66
67        let routed = router.into_proxy().route(RouteRequest { ..Default::default() }).await??;
68        let DirConnectorRouterRouteResponse::DirConnector(connector) = routed else {
69            return Err(UtilsError::UnexpectedCapabilityType(
70                "DirConnector".to_string(),
71                format!("{:?}", routed),
72            ));
73        };
74
75        Ok(connector)
76    }
77
78    pub async fn create_aggregate_dictionary(
79        &self,
80        sources: HashMap<String, Vec<AggregateSource>>,
81    ) -> Result<CapabilityId, UtilsError> {
82        let dest_id = self.next_cap_id();
83        self.capability_store.dictionary_create(dest_id).await??;
84
85        for (service_name, sources) in sources.into_iter() {
86            let aggregate = self.capability_store.create_service_aggregate(sources).await??;
87
88            let imported = self.next_cap_id();
89            self.capability_store.import(imported, Capability::DirConnector(aggregate)).await??;
90
91            self.capability_store
92                .dictionary_insert(dest_id, &DictionaryItem { key: service_name, value: imported })
93                .await??;
94        }
95
96        Ok(dest_id)
97    }
98
99    fn next_cap_id(&self) -> CapabilityId {
100        self.cap_id.set(self.cap_id.get() + 1);
101        self.cap_id.get()
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use fidl::endpoints::{Proxy, create_proxy_and_stream};
109    use futures::StreamExt;
110
111    #[fuchsia::test]
112    async fn test_import_dictionary() {
113        let (proxy, mut stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
114        let util = DictionaryUtil::new(proxy);
115
116        let task = fuchsia_async::Task::local(async move {
117            if let Some(Ok(request)) = stream.next().await {
118                match request {
119                    fsandbox::CapabilityStoreRequest::Import { id, capability, responder } => {
120                        assert_eq!(id, 1);
121                        assert!(matches!(capability, Capability::Dictionary(_)));
122                        responder.send(Ok(())).unwrap();
123                    }
124                    _ => panic!("Unexpected request"),
125                }
126            }
127        });
128
129        let dictionary = fsandbox::DictionaryRef { token: fidl::EventPair::create().0 };
130        let id = util.import_dictionary(dictionary).await.expect("Import failed");
131        assert_eq!(id, 1);
132        task.await;
133    }
134
135    #[fuchsia::test]
136    async fn test_copy_export_dictionary() {
137        let (proxy, mut stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
138        let util = DictionaryUtil::new(proxy);
139
140        let task = fuchsia_async::Task::local(async move {
141            // dictionary_copy
142            if let Some(Ok(fsandbox::CapabilityStoreRequest::DictionaryCopy {
143                id,
144                dest_id,
145                responder,
146            })) = stream.next().await
147            {
148                assert_eq!(id, 100);
149                assert_eq!(dest_id, 1);
150                responder.send(Ok(())).unwrap();
151            } else {
152                panic!("Expected DictionaryCopy");
153            }
154
155            // export
156            if let Some(Ok(fsandbox::CapabilityStoreRequest::Export { id, responder })) =
157                stream.next().await
158            {
159                assert_eq!(id, 1);
160                responder
161                    .send(Ok(Capability::Dictionary(fsandbox::DictionaryRef {
162                        token: fidl::EventPair::create().0,
163                    })))
164                    .unwrap();
165            } else {
166                panic!("Expected Export");
167            }
168        });
169
170        let _ = util.copy_export_dictionary(100).await.expect("Copy export failed");
171        task.await;
172    }
173
174    #[fuchsia::test]
175    async fn test_dictionary_dir_connector_route() {
176        let (proxy, mut stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
177        let util = DictionaryUtil::new(proxy);
178
179        let task = fuchsia_async::Task::local(async move {
180            // dictionary_get
181            if let Some(Ok(fsandbox::CapabilityStoreRequest::DictionaryGet {
182                id,
183                key,
184                dest_id,
185                responder,
186            })) = stream.next().await
187            {
188                assert_eq!(id, 100);
189                assert_eq!(key, "service");
190                assert_eq!(dest_id, 1);
191                responder.send(Ok(())).unwrap();
192            } else {
193                panic!("Expected DictionaryGet");
194            }
195
196            // export
197            if let Some(Ok(fsandbox::CapabilityStoreRequest::Export { id, responder })) =
198                stream.next().await
199            {
200                assert_eq!(id, 1);
201
202                let (router_client, mut router_stream) =
203                    create_proxy_and_stream::<fsandbox::DirConnectorRouterMarker>();
204                fuchsia_async::Task::local(async move {
205                    if let Some(Ok(fsandbox::DirConnectorRouterRequest::Route {
206                        payload: _,
207                        responder,
208                    })) = router_stream.next().await
209                    {
210                        let connector =
211                            fsandbox::DirConnector { token: fidl::EventPair::create().0 };
212                        responder
213                            .send(Ok(DirConnectorRouterRouteResponse::DirConnector(connector)))
214                            .unwrap();
215                    }
216                })
217                .detach();
218
219                responder
220                    .send(Ok(Capability::DirConnectorRouter(
221                        router_client.into_client_end().unwrap(),
222                    )))
223                    .unwrap();
224            } else {
225                panic!("Expected Export");
226            }
227        });
228
229        let _ = util.dictionary_dir_connector_route(100, "service").await.expect("Route failed");
230        task.await;
231    }
232
233    #[fuchsia::test]
234    async fn test_create_aggregate_dictionary() {
235        let (proxy, mut stream) = create_proxy_and_stream::<fsandbox::CapabilityStoreMarker>();
236        let util = DictionaryUtil::new(proxy);
237
238        let task = fuchsia_async::Task::local(async move {
239            // dictionary_create
240            if let Some(Ok(fsandbox::CapabilityStoreRequest::DictionaryCreate { id, responder })) =
241                stream.next().await
242            {
243                assert_eq!(id, 1);
244                responder.send(Ok(())).unwrap();
245            } else {
246                panic!("Expected DictionaryCreate");
247            }
248
249            // create_service_aggregate
250            if let Some(Ok(fsandbox::CapabilityStoreRequest::CreateServiceAggregate {
251                sources: _,
252                responder,
253            })) = stream.next().await
254            {
255                let connector = fsandbox::DirConnector { token: fidl::EventPair::create().0 };
256                responder.send(Ok(connector)).unwrap();
257            } else {
258                panic!("Expected CreateServiceAggregate");
259            }
260
261            // import
262            if let Some(Ok(fsandbox::CapabilityStoreRequest::Import {
263                id,
264                capability,
265                responder,
266            })) = stream.next().await
267            {
268                assert_eq!(id, 2);
269                assert!(matches!(capability, Capability::DirConnector(_)));
270                responder.send(Ok(())).unwrap();
271            } else {
272                panic!("Expected Import");
273            }
274
275            // dictionary_insert
276            if let Some(Ok(fsandbox::CapabilityStoreRequest::DictionaryInsert {
277                id,
278                item,
279                responder,
280            })) = stream.next().await
281            {
282                assert_eq!(id, 1);
283                assert_eq!(item.key, "service");
284                assert_eq!(item.value, 2);
285                responder.send(Ok(())).unwrap();
286            } else {
287                panic!("Expected DictionaryInsert");
288            }
289        });
290
291        let mut sources = HashMap::new();
292        sources.insert(
293            "service".to_string(),
294            vec![fsandbox::AggregateSource {
295                dir_connector: Some(fsandbox::DirConnector { token: fidl::EventPair::create().0 }),
296                ..Default::default()
297            }],
298        );
299        let id = util.create_aggregate_dictionary(sources).await.expect("Create aggregate failed");
300        assert_eq!(id, 1);
301        task.await;
302    }
303}