routing/
config.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 crate::bedrock::dict_ext::DictExt;
6use crate::bedrock::request_metadata;
7use crate::capability_source::{
8    CapabilitySource, CapabilityToCapabilitySource, ComponentCapability, ComponentSource,
9};
10use crate::component_instance::ComponentInstanceInterface;
11use crate::{RouteRequest, RouterResponse, RoutingError};
12use cm_rust::FidlIntoNative;
13use sandbox::Data;
14use std::sync::Arc;
15
16/// Get a specific configuration use declaration from the structured
17/// config key value.
18pub fn get_use_config_from_key<'a>(
19    key: &str,
20    decl: &'a cm_rust::ComponentDecl,
21) -> Option<&'a cm_rust::UseConfigurationDecl> {
22    decl.uses.iter().find_map(|use_| match use_ {
23        cm_rust::UseDecl::Config(c) => (c.target_name == key).then_some(c),
24        _ => None,
25    })
26}
27
28fn source_to_value(
29    default: &Option<cm_rust::ConfigValue>,
30    source: CapabilitySource,
31) -> Result<Option<cm_rust::ConfigValue>, RoutingError> {
32    let moniker = source.source_moniker();
33    let cap = match source {
34        CapabilitySource::Void(_) => {
35            return Ok(default.clone());
36        }
37        CapabilitySource::Capability(CapabilityToCapabilitySource {
38            source_capability, ..
39        }) => source_capability,
40        CapabilitySource::Component(ComponentSource { capability, .. }) => capability,
41        o => {
42            return Err(RoutingError::unsupported_route_source(moniker, o.type_name().to_string()));
43        }
44    };
45
46    let cap = match cap {
47        ComponentCapability::Config(c) => c,
48        c => {
49            return Err(RoutingError::unsupported_capability_type(moniker, c.type_name()));
50        }
51    };
52    Ok(Some(cap.value))
53}
54
55pub async fn route_config_value_with_bedrock<C>(
56    use_config: &cm_rust::UseConfigurationDecl,
57    component: &Arc<C>,
58) -> Result<Option<cm_rust::ConfigValue>, router_error::RouterError>
59where
60    C: ComponentInstanceInterface + 'static,
61{
62    let component_sandbox =
63        component.component_sandbox().await.map_err(|e| RoutingError::from(e))?;
64    let capability =
65        match component_sandbox.program_input.config().get_capability(&use_config.target_name) {
66            Some(c) => c,
67            None => {
68                return Err(RoutingError::BedrockNotPresentInDictionary {
69                    name: use_config.target_name.to_string(),
70                    moniker: component.moniker().clone().into(),
71                }
72                .into());
73            }
74        };
75    let sandbox::Capability::DataRouter(router) = capability else {
76        return Err(RoutingError::BedrockWrongCapabilityType {
77            actual: format!("{:?}", capability),
78            expected: "Router".to_string(),
79            moniker: component.moniker().clone().into(),
80        }
81        .into());
82    };
83    let request =
84        sandbox::Request { metadata: request_metadata::config_metadata(use_config.availability) };
85    let data = match router.route(Some(request), false, component.as_weak().into()).await? {
86        RouterResponse::<Data>::Capability(d) => d,
87        RouterResponse::<Data>::Unavailable => return Ok(use_config.default.clone()),
88        RouterResponse::<Data>::Debug(_) => {
89            return Err(RoutingError::RouteUnexpectedDebug {
90                type_name: cm_rust::CapabilityTypeName::Config,
91                moniker: component.moniker().clone().into(),
92            }
93            .into());
94        }
95    };
96    let sandbox::Data::Bytes(bytes) = data else {
97        return Err(RoutingError::BedrockWrongCapabilityType {
98            actual: format!("{:?}", data),
99            expected: "Data::bytes".to_string(),
100            moniker: component.moniker().clone().into(),
101        }
102        .into());
103    };
104    let config_value: fidl_fuchsia_component_decl::ConfigValue = match fidl::unpersist(&bytes) {
105        Ok(v) => v,
106        Err(_) => {
107            return Err(RoutingError::BedrockWrongCapabilityType {
108                actual: "{unknown}".into(),
109                expected: "fuchsia.component.decl.ConfigValue".into(),
110                moniker: component.moniker().clone().into(),
111            }
112            .into());
113        }
114    };
115
116    Ok(Some(config_value.fidl_into_native()))
117}
118
119/// Route the given `use_config` from a specific `component`.
120/// This returns the configuration value as a result.
121/// This will return Ok(None) if it was routed successfully, but it
122/// was an optional capability.
123pub async fn route_config_value<C>(
124    use_config: &cm_rust::UseConfigurationDecl,
125    component: &Arc<C>,
126) -> Result<Option<cm_rust::ConfigValue>, router_error::RouterError>
127where
128    C: ComponentInstanceInterface + 'static,
129{
130    if let Ok(Some(value)) = route_config_value_with_bedrock(use_config, component).await {
131        return Ok(Some(value));
132    }
133    let source = crate::route_capability(
134        RouteRequest::UseConfig(use_config.clone()),
135        component,
136        &mut crate::mapper::NoopRouteMapper,
137    )
138    .await?;
139    Ok(source_to_value(&use_config.default, source.source)?)
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145    use crate::capability_source::VoidSource;
146    use moniker::Moniker;
147
148    #[test]
149    fn config_from_void() {
150        let void_source = CapabilitySource::Void(VoidSource {
151            capability: crate::capability_source::InternalCapability::Config(
152                "test".parse().unwrap(),
153            ),
154            moniker: Moniker::root(),
155        });
156        assert_eq!(Ok(None), source_to_value(&None, void_source));
157    }
158
159    #[test]
160    fn config_from_capability() {
161        let test_value: cm_rust::ConfigValue = cm_rust::ConfigSingleValue::Uint8(5).into();
162        let void_source = CapabilitySource::Capability(CapabilityToCapabilitySource {
163            source_capability: crate::capability_source::ComponentCapability::Config(
164                cm_rust::ConfigurationDecl {
165                    name: "test".parse().unwrap(),
166                    value: test_value.clone(),
167                },
168            ),
169            moniker: Moniker::root(),
170        });
171        assert_eq!(Ok(Some(test_value)), source_to_value(&None, void_source));
172    }
173
174    #[test]
175    fn config_from_component() {
176        let test_value: cm_rust::ConfigValue = cm_rust::ConfigSingleValue::Uint8(5).into();
177        let void_source = CapabilitySource::Component(ComponentSource {
178            capability: crate::capability_source::ComponentCapability::Config(
179                cm_rust::ConfigurationDecl {
180                    name: "test".parse().unwrap(),
181                    value: test_value.clone(),
182                },
183            ),
184            moniker: Moniker::root(),
185        });
186        assert_eq!(Ok(Some(test_value)), source_to_value(&None, void_source));
187    }
188}