Skip to main content

fdf_metadata/
lib.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
5mod serde;
6
7use fdf_component::ServiceOffer;
8use fidl_fuchsia_driver_framework as fdf;
9use fidl_next::{Responder, ServerEnd};
10use fidl_next_fuchsia_driver_metadata as fmetadata;
11use fidl_next_fuchsia_hardware_platform_device as fdevice_next;
12use fuchsia_async as fasync;
13use fuchsia_component::directory::Directory;
14use fuchsia_component::server::{ServiceFs, ServiceObjTrait};
15use std::sync::Arc;
16
17pub use serde::*;
18
19pub struct MetadataServer {
20    data: Option<Arc<Vec<u8>>>,
21    name: String,
22}
23
24impl MetadataServer {
25    pub fn new(name: impl Into<String>) -> Self {
26        Self { data: None, name: name.into() }
27    }
28
29    pub fn with_metadata<T: fidl::Persistable>(self, metadata: &T) -> Result<Self, zx::Status> {
30        let data = fidl::persist(metadata).map_err(|_| zx::Status::INTERNAL)?;
31        Ok(Self { data: Some(Arc::new(data)), name: self.name })
32    }
33
34    pub async fn forward_from_pdev(
35        self,
36        pdev: &fidl_fuchsia_hardware_platform_device::DeviceProxy,
37    ) -> Result<Self, zx::Status> {
38        let data = pdev
39            .get_metadata(&self.name)
40            .await
41            .map_err(|_| zx::Status::INTERNAL)?
42            .map_err(zx::Status::err_from_raw)?;
43        Ok(Self { data: Some(Arc::new(data)), name: self.name })
44    }
45
46    pub async fn forward_from_pdev_next(
47        self,
48        pdev: &fidl_next::Client<fdevice_next::Device>,
49    ) -> Result<Self, zx::Status> {
50        let data = pdev.get_metadata(&self.name).await.map_err(|_| zx::Status::INTERNAL)??.metadata;
51        Ok(Self { data: Some(Arc::new(data)), name: self.name })
52    }
53
54    pub async fn forward_from_incoming(
55        self,
56        incoming: &fdf_component::Incoming,
57    ) -> Result<Self, zx::Status> {
58        self.forward_from_incoming_instance(incoming, fuchsia_component::DEFAULT_SERVICE_INSTANCE)
59            .await
60    }
61
62    pub async fn forward_from_incoming_instance(
63        self,
64        incoming: &fdf_component::Incoming,
65        instance_name: &str,
66    ) -> Result<Self, zx::Status> {
67        let path = format!("/svc/{}/{}/metadata", self.name, instance_name);
68        let (client_end, server_end) = fidl_next::fuchsia::create_channel::<fmetadata::Metadata>();
69        incoming
70            .open(&path, fidl_fuchsia_io::Flags::PROTOCOL_SERVICE, server_end.into_untyped())
71            .map_err(|_| zx::Status::NOT_FOUND)?;
72        let client = client_end.spawn();
73        let data = match client.get_persisted_metadata().await {
74            Ok(Ok(resp)) => resp.persisted_metadata,
75            Ok(Err(status)) => return Err(status),
76            Err(
77                fidl_next::Error::Protocol(fidl_next::ProtocolError::PeerClosed)
78                | fidl_next::Error::Protocol(fidl_next::ProtocolError::PeerClosedWithEpitaph(_)),
79            ) => {
80                return Err(zx::Status::NOT_FOUND);
81            }
82            Err(_) => return Err(zx::Status::INTERNAL),
83        };
84        Ok(Self { data: Some(Arc::new(data)), name: self.name })
85    }
86
87    pub fn create_handler(&self, scope: fasync::ScopeHandle) -> Option<MetadataHandler> {
88        let data = self.data.clone()?;
89        Some(MetadataHandler { data, scope })
90    }
91
92    pub fn create_service_offer(&self) -> Option<ServiceOffer<fmetadata::Service>> {
93        Some(ServiceOffer::<fmetadata::Service>::new_next())
94    }
95
96    pub fn serve<O>(
97        &self,
98        fs: &mut ServiceFs<O>,
99        scope: fasync::ScopeHandle,
100        instance_name: impl Into<String>,
101    ) -> Option<fdf::Offer>
102    where
103        O: ServiceObjTrait,
104    {
105        let data = self.data.clone()?;
106        let handler = MetadataHandler { data, scope };
107
108        let offer = ServiceOffer::<fmetadata::Service>::new_with_name(self.name.clone())
109            .add_default_named_next(fs, instance_name, handler)
110            .build_zircon_offer_next();
111
112        Some(offer)
113    }
114
115    /// Like [`MetadataServer::serve`], but serves connections on `dispatcher` instead of a
116    /// fuchsia-async scope.
117    ///
118    /// The handler keeps a refcounted [`libasync::AsyncDispatcher`] rather than `dispatcher`
119    /// itself, so `dispatcher` doesn't have to be `'static`. Connections that arrive after the
120    /// dispatcher shuts down are closed.
121    pub fn serve_on<O>(
122        &self,
123        fs: &mut ServiceFs<O>,
124        dispatcher: impl libasync::GetAsyncDispatcher,
125        instance_name: impl Into<String>,
126    ) -> Option<fdf::Offer>
127    where
128        O: ServiceObjTrait,
129    {
130        let data = self.data.clone()?;
131        let handler =
132            MetadataDispatcherHandler { data, dispatcher: dispatcher.get_async_dispatcher() };
133
134        let offer = ServiceOffer::<fmetadata::Service>::new_with_name(self.name.clone())
135            .add_default_named_next(fs, instance_name, handler)
136            .build_zircon_offer_next();
137
138        Some(offer)
139    }
140}
141
142pub struct MetadataHandler {
143    data: Arc<Vec<u8>>,
144    scope: fasync::ScopeHandle,
145}
146
147impl fmetadata::ServiceHandler for MetadataHandler {
148    fn metadata(&self, server_end: ServerEnd<fmetadata::Metadata>) {
149        let data = self.data.clone();
150        self.scope.spawn_local(async move {
151            let dispatcher = fidl_next::ServerDispatcher::new(server_end);
152            let _ = dispatcher.run_local(MetadataServerImpl { data }).await;
153        });
154    }
155}
156
157struct MetadataDispatcherHandler {
158    data: Arc<Vec<u8>>,
159    dispatcher: libasync::AsyncDispatcher,
160}
161
162impl fmetadata::ServiceHandler for MetadataDispatcherHandler {
163    fn metadata(&self, server_end: ServerEnd<fmetadata::Metadata>) {
164        let data = self.data.clone();
165        libasync_fidl::AsyncChannel::server_from_zx_channel_on_dispatcher(
166            server_end,
167            self.dispatcher.clone(),
168        )
169        .spawn(MetadataServerImpl { data });
170    }
171}
172
173struct MetadataServerImpl {
174    data: Arc<Vec<u8>>,
175}
176
177impl<T: fidl_next::Transport> fmetadata::MetadataServerHandler<T> for MetadataServerImpl {
178    async fn get_persisted_metadata(
179        &mut self,
180        responder: Responder<fmetadata::metadata::GetPersistedMetadata, T>,
181    ) {
182        let _ = responder.respond(self.data.as_slice()).await;
183    }
184}
185
186impl fmetadata::MetadataLocalServerHandler for MetadataServerImpl {
187    async fn get_persisted_metadata(
188        &mut self,
189        responder: Responder<fmetadata::metadata::GetPersistedMetadata>,
190    ) {
191        let _ = responder.respond(self.data.as_slice()).await;
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198    use fidl_fuchsia_driver_metadata::MetadataMarker;
199    use fidl_fuchsia_io as fio;
200    use futures::StreamExt;
201    use libasync::AsAsyncDispatcherRef;
202    use libasync_scope_dispatcher::ScopeDispatcher;
203
204    #[fuchsia::test]
205    async fn forward_from_incoming_serves_metadata() {
206        let scope = fasync::Scope::new();
207        let mut parent_fs = ServiceFs::new();
208        let expected = fidl_fuchsia_driver_metadata::Dictionary {
209            entries: Some(vec![fidl_fuchsia_driver_metadata::DictionaryEntry {
210                key: "key".to_string(),
211                value: fidl_fuchsia_driver_metadata::DictionaryValue::Int64(123),
212            }]),
213            ..Default::default()
214        };
215
216        let parent_server = MetadataServer::new("test.Metadata")
217            .with_metadata(&expected)
218            .expect("with_metadata failed");
219        parent_server
220            .serve(&mut parent_fs, scope.to_handle(), fuchsia_component::DEFAULT_SERVICE_INSTANCE)
221            .expect("serve failed");
222
223        let (client, server) =
224            fidl::endpoints::create_endpoints::<fidl_fuchsia_io::DirectoryMarker>();
225        parent_fs.serve_connection(server).expect("serve_connection failed");
226        scope.spawn_local(parent_fs.collect());
227
228        let incoming = fdf_component::Incoming::from(client);
229        let forwarded = MetadataServer::new("test.Metadata")
230            .forward_from_incoming(&incoming)
231            .await
232            .expect("forward_from_incoming failed");
233
234        let mut child_fs = ServiceFs::new();
235        forwarded.serve(&mut child_fs, scope.to_handle(), "child").expect("child serve failed");
236        let (child_client, child_server) =
237            fidl::endpoints::create_endpoints::<fidl_fuchsia_io::DirectoryMarker>();
238        child_fs.serve_connection(child_server).expect("child serve_connection failed");
239        scope.spawn_local(child_fs.collect());
240
241        let child_incoming = fdf_component::Incoming::from(child_client);
242        let (client_end, server_end) = fidl_next::fuchsia::create_channel::<fmetadata::Metadata>();
243        child_incoming
244            .open(
245                "/svc/test.Metadata/child/metadata",
246                fidl_fuchsia_io::Flags::PROTOCOL_SERVICE,
247                server_end.into_untyped(),
248            )
249            .expect("open failed");
250        let client = client_end.spawn();
251        let persisted = client
252            .get_persisted_metadata()
253            .await
254            .expect("FIDL call failed")
255            .expect("GetPersistedMetadata failed")
256            .persisted_metadata;
257        let decoded: fidl_fuchsia_driver_metadata::Dictionary =
258            fidl::unpersist(&persisted).expect("unpersist failed");
259        assert_eq!(decoded, expected);
260    }
261
262    #[fuchsia::test]
263    async fn forward_from_incoming_missing_returns_not_found() {
264        let scope = fasync::Scope::new();
265        let mut parent_fs =
266            ServiceFs::<fuchsia_component::server::ServiceObjLocal<'_, ()>>::new_local();
267        let (client, server) =
268            fidl::endpoints::create_endpoints::<fidl_fuchsia_io::DirectoryMarker>();
269        parent_fs.serve_connection(server).expect("serve_connection failed");
270        scope.spawn_local(parent_fs.collect());
271
272        let incoming = fdf_component::Incoming::from(client);
273        let result =
274            MetadataServer::new("test.MissingMetadata").forward_from_incoming(&incoming).await;
275        assert_eq!(result.err(), Some(zx::Status::NOT_FOUND));
276    }
277
278    #[fuchsia::test]
279    async fn serve_on_borrowed_dispatcher() {
280        let dispatcher = ScopeDispatcher::new();
281        let server = MetadataServer { data: Some(Arc::new(vec![1, 2, 3])), name: "test".into() };
282        let mut fs = ServiceFs::new();
283        // A borrowed `AsyncDispatcherRef<'_>` is enough; `serve_on` takes its own refcount.
284        assert!(
285            server.serve_on(&mut fs, dispatcher.as_async_dispatcher_ref(), "default").is_some()
286        );
287
288        let (dir, dir_server) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
289        fs.serve_connection(dir_server).unwrap();
290        let _fs = fasync::Task::local(fs.collect::<()>());
291
292        let metadata = fuchsia_component::client::connect_to_named_protocol_at_dir_root::<
293            MetadataMarker,
294        >(&dir, "svc/test/default/metadata")
295        .unwrap();
296        assert_eq!(metadata.get_persisted_metadata().await.unwrap(), Ok(vec![1, 2, 3]));
297
298        dispatcher.shutdown().await;
299    }
300}