routing/bedrock/
request_metadata.rs

1// Copyright 2024 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 std::str::FromStr;
6
7use crate::rights::Rights;
8use crate::subdir::SubDir;
9use cm_rust::{Availability, CapabilityTypeName};
10use fidl::{persist, unpersist};
11use sandbox::{Capability, Data, Dict, DictKey};
12use {
13    fidl_fuchsia_component_internal as finternal, fidl_fuchsia_component_sandbox as fsandbox,
14    fidl_fuchsia_io as fio,
15};
16
17/// A type which has accessors for route request metadata of type T.
18pub trait Metadata<T> {
19    /// A key string used for setting and getting the metadata.
20    const KEY: &'static str;
21
22    /// Infallibly assigns `value` to `self`.
23    fn set_metadata(&self, value: T);
24
25    /// Retrieves the subdir metadata from `self`, if present.
26    fn get_metadata(&self) -> Option<T>;
27}
28
29impl Metadata<CapabilityTypeName> for Dict {
30    const KEY: &'static str = "type";
31
32    fn set_metadata(&self, value: CapabilityTypeName) {
33        let key = DictKey::new(<Self as Metadata<CapabilityTypeName>>::KEY)
34            .expect("dict key creation failed unexpectedly");
35        match self.insert(key, Capability::Data(Data::String(value.to_string().into()))) {
36            // When an entry already exists for a key in a Dict, insert() will
37            // still replace that entry with the new value, even though it
38            // returns an ItemAlreadyExists error. As a result, we can treat
39            // ItemAlreadyExists as a success case.
40            Ok(()) | Err(fsandbox::CapabilityStoreError::ItemAlreadyExists) => (),
41            // Dict::insert() only returns `CapabilityStoreError::ItemAlreadyExists` variant
42            Err(e) => panic!("unexpected error variant returned from Dict::insert(): {e:?}"),
43        }
44    }
45
46    fn get_metadata(&self) -> Option<CapabilityTypeName> {
47        let key = DictKey::new(<Self as Metadata<CapabilityTypeName>>::KEY)
48            .expect("dict key creation failed unexpectedly");
49        let capability = self.get(&key).ok()??;
50        match capability {
51            Capability::Data(Data::String(capability_type_name)) => {
52                CapabilityTypeName::from_str(&capability_type_name).ok()
53            }
54            _ => None,
55        }
56    }
57}
58
59impl Metadata<Availability> for Dict {
60    const KEY: &'static str = "availability";
61
62    fn set_metadata(&self, value: Availability) {
63        let key = DictKey::new(<Self as Metadata<Availability>>::KEY)
64            .expect("dict key creation failed unexpectedly");
65        match self.insert(key, Capability::Data(Data::String(value.to_string().into()))) {
66            // When an entry already exists for a key in a Dict, insert() will
67            // still replace that entry with the new value, even though it
68            // returns an ItemAlreadyExists error. As a result, we can treat
69            // ItemAlreadyExists as a success case.
70            Ok(()) | Err(fsandbox::CapabilityStoreError::ItemAlreadyExists) => (),
71            // Dict::insert() only returns `CapabilityStoreError::ItemAlreadyExists` variant
72            Err(e) => panic!("unexpected error variant returned from Dict::insert(): {e:?}"),
73        }
74    }
75
76    fn get_metadata(&self) -> Option<Availability> {
77        let key = DictKey::new(<Self as Metadata<Availability>>::KEY)
78            .expect("dict key creation failed unexpectedly");
79        let capability = self.get(&key).ok()??;
80        match capability {
81            Capability::Data(Data::String(availability)) => match &*availability {
82                "Optional" => Some(Availability::Optional),
83                "Required" => Some(Availability::Required),
84                "SameAsTarget" => Some(Availability::SameAsTarget),
85                "Transitional" => Some(Availability::Transitional),
86                _ => None,
87            },
88            _ => None,
89        }
90    }
91}
92
93impl Metadata<Rights> for Dict {
94    const KEY: &'static str = "rights";
95
96    fn set_metadata(&self, value: Rights) {
97        let key = DictKey::new(<Self as Metadata<Rights>>::KEY)
98            .expect("dict key creation failed unexpectedly");
99        match self.insert(key, Capability::Data(Data::Uint64(value.into()))) {
100            // When an entry already exists for a key in a Dict, insert() will
101            // still replace that entry with the new value, even though it
102            // returns an ItemAlreadyExists error. As a result, we can treat
103            // ItemAlreadyExists as a success case.
104            Ok(()) | Err(fsandbox::CapabilityStoreError::ItemAlreadyExists) => (),
105            // Dict::insert() only returns `CapabilityStoreError::ItemAlreadyExists` variant
106            Err(e) => panic!("unexpected error variant returned from Dict::insert(): {e:?}"),
107        }
108    }
109
110    fn get_metadata(&self) -> Option<Rights> {
111        let key = DictKey::new(<Self as Metadata<Rights>>::KEY)
112            .expect("dict key creation failed unexpectedly");
113        let capability = self.get(&key).ok()??;
114        let rights = match capability {
115            Capability::Data(Data::Uint64(rights)) => fio::Operations::from_bits(rights)?,
116            _ => None?,
117        };
118        Some(Rights::from(rights))
119    }
120}
121impl Metadata<finternal::EventStreamRouteMetadata> for Dict {
122    const KEY: &'static str = "event_stream_route_metadata";
123
124    fn set_metadata(&self, esrm: finternal::EventStreamRouteMetadata) {
125        let key = DictKey::new(<Self as Metadata<Rights>>::KEY)
126            .expect("dict key creation failed unexpectedly");
127        let value = persist(&esrm).expect("failed to persist event stream route metadata");
128        match self.insert(key, Data::Bytes(value.into()).into()) {
129            // When an entry already exists for a key in a Dict, insert() will
130            // still replace that entry with the new value, even though it
131            // returns an ItemAlreadyExists error. As a result, we can treat
132            // ItemAlreadyExists as a success case.
133            Ok(()) | Err(fsandbox::CapabilityStoreError::ItemAlreadyExists) => (),
134            // Dict::insert() only returns `CapabilityStoreError::ItemAlreadyExists` variant
135            Err(e) => panic!("unexpected error variant returned from Dict::insert(): {e:?}"),
136        }
137    }
138
139    fn get_metadata(&self) -> Option<finternal::EventStreamRouteMetadata> {
140        let key = DictKey::new(<Self as Metadata<Rights>>::KEY)
141            .expect("dict key creation failed unexpectedly");
142        let capability = self.get(&key).ok()??;
143        match capability {
144            Capability::Data(Data::Bytes(bytes)) => Some(unpersist(&bytes).ok()?),
145            _ => None,
146        }
147    }
148}
149
150/// The directory rights associated with the previous declaration in a multi-step route.
151pub struct IntermediateRights(pub Rights);
152
153impl Metadata<IntermediateRights> for Dict {
154    const KEY: &'static str = "intermediate_rights";
155
156    fn set_metadata(&self, value: IntermediateRights) {
157        let key = DictKey::new(<Self as Metadata<IntermediateRights>>::KEY)
158            .expect("dict key creation failed unexpectedly");
159        let IntermediateRights(value) = value;
160        match self.insert(key, Capability::Data(Data::Uint64(value.into()))) {
161            // When an entry already exists for a key in a Dict, insert() will
162            // still replace that entry with the new value, even though it
163            // returns an ItemAlreadyExists error. As a result, we can treat
164            // ItemAlreadyExists as a success case.
165            Ok(()) | Err(fsandbox::CapabilityStoreError::ItemAlreadyExists) => (),
166            // Dict::insert() only returns `CapabilityStoreError::ItemAlreadyExists` variant
167            Err(e) => panic!("unexpected error variant returned from Dict::insert(): {e:?}"),
168        }
169    }
170
171    fn get_metadata(&self) -> Option<IntermediateRights> {
172        let key = DictKey::new(<Self as Metadata<IntermediateRights>>::KEY)
173            .expect("dict key creation failed unexpectedly");
174        let capability = self.get(&key).ok()??;
175        let rights = match capability {
176            Capability::Data(Data::Uint64(rights)) => fio::Operations::from_bits(rights)?,
177            _ => None?,
178        };
179        Some(IntermediateRights(Rights::from(rights)))
180    }
181}
182
183/// A flag indicating that directory rights should be inherited from the capability declaration
184/// if they were not present in an expose or offer declaration.
185pub struct InheritRights(pub bool);
186
187impl Metadata<InheritRights> for Dict {
188    const KEY: &'static str = "inherit_rights";
189
190    fn set_metadata(&self, value: InheritRights) {
191        let key = DictKey::new(<Self as Metadata<InheritRights>>::KEY)
192            .expect("dict key creation failed unexpectedly");
193        let InheritRights(value) = value;
194        match self.insert(key, Capability::Data(Data::Uint64(value.into()))) {
195            // When an entry already exists for a key in a Dict, insert() will
196            // still replace that entry with the new value, even though it
197            // returns an ItemAlreadyExists error. As a result, we can treat
198            // ItemAlreadyExists as a success case.
199            Ok(()) | Err(fsandbox::CapabilityStoreError::ItemAlreadyExists) => (),
200            // Dict::insert() only returns `CapabilityStoreError::ItemAlreadyExists` variant
201            Err(e) => panic!("unexpected error variant returned from Dict::insert(): {e:?}"),
202        }
203    }
204
205    fn get_metadata(&self) -> Option<InheritRights> {
206        let key = DictKey::new(<Self as Metadata<InheritRights>>::KEY)
207            .expect("dict key creation failed unexpectedly");
208        let capability = self.get(&key).ok()??;
209        let inherit = match capability {
210            Capability::Data(Data::Uint64(inherit)) => inherit != 0,
211            _ => None?,
212        };
213        Some(InheritRights(inherit))
214    }
215}
216
217impl Metadata<SubDir> for Dict {
218    const KEY: &'static str = "subdir";
219
220    fn set_metadata(&self, value: SubDir) {
221        let key = DictKey::new(<Self as Metadata<SubDir>>::KEY)
222            .expect("dict key creation failed unexpectedly");
223        match self.insert(key, Capability::Data(Data::String(value.to_string().into()))) {
224            // When an entry already exists for a key in a Dict, insert() will
225            // still replace that entry with the new value, even though it
226            // returns an ItemAlreadyExists error. As a result, we can treat
227            // ItemAlreadyExists as a success case.
228            Ok(()) | Err(fsandbox::CapabilityStoreError::ItemAlreadyExists) => (),
229            // Dict::insert() only returns `CapabilityStoreError::ItemAlreadyExists` variant
230            Err(e) => panic!("unexpected error variant returned from Dict::insert(): {e:?}"),
231        }
232    }
233
234    fn get_metadata(&self) -> Option<SubDir> {
235        let key = DictKey::new(<Self as Metadata<SubDir>>::KEY)
236            .expect("dict key creation failed unexpectedly");
237        let capability = self.get(&key).ok()??;
238        match capability {
239            Capability::Data(Data::String(subdir)) => SubDir::new(subdir).ok(),
240            _ => None,
241        }
242    }
243}
244
245/// Returns a `Dict` containing Router Request metadata specifying a Protocol porcelain type.
246pub fn protocol_metadata(availability: cm_types::Availability) -> sandbox::Dict {
247    let metadata = sandbox::Dict::new();
248    metadata.set_metadata(CapabilityTypeName::Protocol);
249    metadata.set_metadata(availability);
250    metadata
251}
252
253/// Returns a `Dict` containing Router Request metadata specifying a Dictionary porcelain type.
254pub fn dictionary_metadata(availability: cm_types::Availability) -> sandbox::Dict {
255    let metadata = sandbox::Dict::new();
256    metadata.set_metadata(CapabilityTypeName::Dictionary);
257    metadata.set_metadata(availability);
258    metadata
259}
260
261/// Returns a `Dict` containing Router Request metadata specifying a Directory porcelain type.
262pub fn directory_metadata(
263    availability: cm_types::Availability,
264    rights: Option<Rights>,
265    subdir: Option<SubDir>,
266) -> sandbox::Dict {
267    let metadata = sandbox::Dict::new();
268    metadata.set_metadata(CapabilityTypeName::Directory);
269    if let Some(subdir) = subdir {
270        metadata.set_metadata(subdir);
271    }
272    metadata.set_metadata(availability);
273    match rights {
274        Some(rights) => {
275            metadata.set_metadata(rights);
276            metadata.set_metadata(InheritRights(false));
277        }
278        None => {
279            metadata.set_metadata(InheritRights(true));
280        }
281    }
282    metadata
283}
284
285/// Returns a `Dict` containing Router Request metadata specifying a Config porcelain type.
286pub fn config_metadata(availability: cm_types::Availability) -> sandbox::Dict {
287    let metadata = sandbox::Dict::new();
288    metadata.set_metadata(CapabilityTypeName::Config);
289    metadata.set_metadata(availability);
290    metadata
291}
292
293/// Returns a `Dict` containing Router Request metadata specifying a Runner porcelain type.
294pub fn runner_metadata(availability: cm_types::Availability) -> sandbox::Dict {
295    let metadata = sandbox::Dict::new();
296    metadata.set_metadata(CapabilityTypeName::Runner);
297    metadata.set_metadata(availability);
298    metadata
299}
300
301/// Returns a `Dict` Containing Router Request metadata specifying a Resolver porcelain type.
302pub fn resolver_metadata(availability: cm_types::Availability) -> sandbox::Dict {
303    let metadata = sandbox::Dict::new();
304    metadata.set_metadata(CapabilityTypeName::Resolver);
305    metadata.set_metadata(availability);
306    metadata
307}
308
309/// Returns a `Dict` Containing Router Request metadata specifying a Service porcelain type.
310pub fn service_metadata(availability: cm_types::Availability) -> sandbox::Dict {
311    let metadata = sandbox::Dict::new();
312    metadata.set_metadata(CapabilityTypeName::Service);
313    metadata.set_metadata(availability);
314    // Service capabilities are implemented as DirConnectors. When the Router<DirConnector> that
315    // connects to a component's outgoing directory wants to assemble a DirConnector, it pulls the
316    // set of rights that are allowed for that DirConnector from the route metadata. This gives us
317    // two choices: maintain a different Router<DirConnector> exclusively for connecting service
318    // capabilities to an outgoing directory that hard-codes R_STAR_DIR, or set R_STAR_DIR in the
319    // routing metadata and let the existing Router<DirConnector> use that information.
320    //
321    // It's less code duplication to do the latter, so we set the necessary bits to carry rights
322    // information in the routing metadata for service capability routing.
323    metadata.set_metadata(Rights::from(fio::R_STAR_DIR));
324    metadata.set_metadata(InheritRights(true));
325    metadata
326}
327
328pub fn event_stream_metadata(
329    availability: cm_types::Availability,
330    route_metadata: finternal::EventStreamRouteMetadata,
331) -> sandbox::Dict {
332    let metadata = sandbox::Dict::new();
333    metadata.set_metadata(CapabilityTypeName::EventStream);
334    metadata.set_metadata(availability);
335    metadata.set_metadata(route_metadata);
336    metadata
337}