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 = sandbox::Request {
84        target: component.as_weak().into(),
85        metadata: request_metadata::config_metadata(use_config.availability),
86    };
87    let data = match router.route(Some(request), false).await? {
88        RouterResponse::<Data>::Capability(d) => d,
89        RouterResponse::<Data>::Unavailable => return Ok(use_config.default.clone()),
90        RouterResponse::<Data>::Debug(_) => {
91            return Err(RoutingError::RouteUnexpectedDebug {
92                type_name: cm_rust::CapabilityTypeName::Config,
93                moniker: component.moniker().clone().into(),
94            }
95            .into());
96        }
97    };
98    let sandbox::Data::Bytes(bytes) = data else {
99        return Err(RoutingError::BedrockWrongCapabilityType {
100            actual: format!("{:?}", data),
101            expected: "Data::bytes".to_string(),
102            moniker: component.moniker().clone().into(),
103        }
104        .into());
105    };
106    let config_value: fidl_fuchsia_component_decl::ConfigValue = match fidl::unpersist(&bytes) {
107        Ok(v) => v,
108        Err(_) => {
109            return Err(RoutingError::BedrockWrongCapabilityType {
110                actual: "{unknown}".into(),
111                expected: "fuchsia.component.decl.ConfigValue".into(),
112                moniker: component.moniker().clone().into(),
113            }
114            .into())
115        }
116    };
117
118    Ok(Some(config_value.fidl_into_native()))
119}
120
121/// Route the given `use_config` from a specific `component`.
122/// This returns the configuration value as a result.
123/// This will return Ok(None) if it was routed successfully, but it
124/// was an optional capability.
125pub async fn route_config_value<C>(
126    use_config: &cm_rust::UseConfigurationDecl,
127    component: &Arc<C>,
128) -> Result<Option<cm_rust::ConfigValue>, router_error::RouterError>
129where
130    C: ComponentInstanceInterface + 'static,
131{
132    if let Ok(Some(value)) = route_config_value_with_bedrock(use_config, component).await {
133        return Ok(Some(value));
134    }
135    let source = crate::route_capability(
136        RouteRequest::UseConfig(use_config.clone()),
137        component,
138        &mut crate::mapper::NoopRouteMapper,
139    )
140    .await?;
141    Ok(source_to_value(&use_config.default, source.source)?)
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147    use crate::capability_source::VoidSource;
148    use moniker::Moniker;
149
150    #[test]
151    fn config_from_void() {
152        let void_source = CapabilitySource::Void(VoidSource {
153            capability: crate::capability_source::InternalCapability::Config(
154                "test".parse().unwrap(),
155            ),
156            moniker: Moniker::root(),
157        });
158        assert_eq!(Ok(None), source_to_value(&None, void_source));
159    }
160
161    #[test]
162    fn config_from_capability() {
163        let test_value: cm_rust::ConfigValue = cm_rust::ConfigSingleValue::Uint8(5).into();
164        let void_source = CapabilitySource::Capability(CapabilityToCapabilitySource {
165            source_capability: crate::capability_source::ComponentCapability::Config(
166                cm_rust::ConfigurationDecl {
167                    name: "test".parse().unwrap(),
168                    value: test_value.clone(),
169                },
170            ),
171            moniker: Moniker::root(),
172        });
173        assert_eq!(Ok(Some(test_value)), source_to_value(&None, void_source));
174    }
175
176    #[test]
177    fn config_from_component() {
178        let test_value: cm_rust::ConfigValue = cm_rust::ConfigSingleValue::Uint8(5).into();
179        let void_source = CapabilitySource::Component(ComponentSource {
180            capability: crate::capability_source::ComponentCapability::Config(
181                cm_rust::ConfigurationDecl {
182                    name: "test".parse().unwrap(),
183                    value: test_value.clone(),
184                },
185            ),
186            moniker: Moniker::root(),
187        });
188        assert_eq!(Ok(Some(test_value)), source_to_value(&None, void_source));
189    }
190}