routing/bedrock/
sandbox_construction.rs

1// Copyright 2023 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::aggregate_router::{AggregateRouterFn, AggregateSource};
6use crate::bedrock::request_metadata::Metadata;
7use crate::bedrock::structured_dict::{
8    ComponentEnvironment, ComponentInput, ComponentOutput, StructuredDictMap,
9};
10use crate::bedrock::with_service_renames_and_filter::WithServiceRenamesAndFilter;
11use crate::capability_source::{
12    AggregateCapability, AggregateInstance, AggregateMember, AnonymizedAggregateSource,
13    CapabilitySource, FilteredAggregateProviderSource, InternalCapability, VoidSource,
14};
15use crate::component_instance::{ComponentInstanceInterface, WeakComponentInstanceInterface};
16use crate::error::{ErrorReporter, RouteRequestErrorInfo, RoutingError};
17use crate::{DictExt, LazyGet, Sources, WithPorcelain};
18use async_trait::async_trait;
19use cm_rust::{
20    CapabilityTypeName, ExposeDeclCommon, OfferDeclCommon, SourceName, SourcePath, UseDeclCommon,
21};
22use cm_types::{Availability, IterablePath, Name, SeparatedPath};
23use fidl::endpoints::DiscoverableProtocolMarker;
24use itertools::Itertools;
25use lazy_static::lazy_static;
26use log::warn;
27use moniker::{ChildName, Moniker};
28use router_error::RouterError;
29use sandbox::{
30    Capability, CapabilityBound, Connector, Data, Dict, DirEntry, Request, Routable, Router,
31    RouterResponse,
32};
33use std::collections::HashMap;
34use std::fmt::Debug;
35use std::sync::Arc;
36use {fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_sys2 as fsys};
37
38lazy_static! {
39    static ref NAMESPACE: Name = "namespace".parse().unwrap();
40    static ref RUNNER: Name = "runner".parse().unwrap();
41    static ref CONFIG: Name = "config".parse().unwrap();
42}
43
44/// All capabilities that are available to a component's program.
45#[derive(Debug, Clone)]
46pub struct ProgramInput {
47    // This will always have the following fields:
48    // - namespace: Dict
49    // - runner: Option<Router<Connector>>
50    // - config: Dict
51    inner: Dict,
52}
53
54impl Default for ProgramInput {
55    fn default() -> Self {
56        Self::new(Dict::new(), None, Dict::new())
57    }
58}
59
60impl From<ProgramInput> for Dict {
61    fn from(program_input: ProgramInput) -> Self {
62        program_input.inner
63    }
64}
65
66impl ProgramInput {
67    pub fn new(namespace: Dict, runner: Option<Router<Connector>>, config: Dict) -> Self {
68        let inner = Dict::new();
69        inner.insert(NAMESPACE.clone(), namespace.into()).unwrap();
70        if let Some(runner) = runner {
71            inner.insert(RUNNER.clone(), runner.into()).unwrap();
72        }
73        inner.insert(CONFIG.clone(), config.into()).unwrap();
74        ProgramInput { inner }
75    }
76
77    /// All of the capabilities that appear in a program's namespace.
78    pub fn namespace(&self) -> Dict {
79        let cap = self.inner.get(&*NAMESPACE).expect("capabilities must be cloneable").unwrap();
80        let Capability::Dictionary(dict) = cap else {
81            unreachable!("namespace entry must be a dict: {cap:?}");
82        };
83        dict
84    }
85
86    /// A router for the runner that a component has used (if any).
87    pub fn runner(&self) -> Option<Router<Connector>> {
88        let cap = self.inner.get(&*RUNNER).expect("capabilities must be cloneable");
89        match cap {
90            None => None,
91            Some(Capability::ConnectorRouter(r)) => Some(r),
92            cap => unreachable!("runner entry must be a router: {cap:?}"),
93        }
94    }
95
96    fn set_runner(&self, capability: Capability) {
97        self.inner.insert(RUNNER.clone(), capability).unwrap()
98    }
99
100    /// All of the config capabilities that a program will use.
101    pub fn config(&self) -> Dict {
102        let cap = self.inner.get(&*CONFIG).expect("capabilities must be cloneable").unwrap();
103        let Capability::Dictionary(dict) = cap else {
104            unreachable!("config entry must be a dict: {cap:?}");
105        };
106        dict
107    }
108}
109
110/// A component's sandbox holds all the routing dictionaries that a component has once its been
111/// resolved.
112#[derive(Default, Debug, Clone)]
113pub struct ComponentSandbox {
114    /// The dictionary containing all capabilities that a component's parent provided to it.
115    pub component_input: ComponentInput,
116
117    /// The dictionary containing all capabilities that a component makes available to its parent.
118    pub component_output: ComponentOutput,
119
120    /// The dictionary containing all capabilities that are available to a component's program.
121    pub program_input: ProgramInput,
122
123    /// The dictionary containing all capabilities that a component's program can provide.
124    pub program_output_dict: Dict,
125
126    /// The dictionary containing all framework capabilities that are available to a component.
127    pub framework_dict: Dict,
128
129    /// The dictionary containing all capabilities that a component declares based on another
130    /// capability. Currently this is only the storage admin protocol.
131    pub capability_sourced_capabilities_dict: Dict,
132
133    /// The dictionary containing all dictionaries declared by this component.
134    pub declared_dictionaries: Dict,
135
136    /// This set holds a component input dictionary for each child of a component. Each dictionary
137    /// contains all capabilities the component has made available to a specific collection.
138    pub child_inputs: StructuredDictMap<ComponentInput>,
139
140    /// This set holds a component input dictionary for each collection declared by a component.
141    /// Each dictionary contains all capabilities the component has made available to a specific
142    /// collection.
143    pub collection_inputs: StructuredDictMap<ComponentInput>,
144}
145
146impl ComponentSandbox {
147    /// Copies all of the entries from the given sandbox into this one. Panics if the given sandbox
148    /// is holding any entries that cannot be copied. Panics if there are any duplicate entries.
149    pub fn append(&self, sandbox: &ComponentSandbox) {
150        // We destructure the sandbox here to ensure that this code is updated if the contents of
151        // the sandbox change.
152        let ComponentSandbox {
153            component_input,
154            component_output,
155            program_input,
156            program_output_dict,
157            framework_dict,
158            capability_sourced_capabilities_dict,
159            declared_dictionaries,
160            child_inputs,
161            collection_inputs,
162        } = sandbox;
163        for (copy_from, copy_to) in [
164            (&component_input.capabilities(), &self.component_input.capabilities()),
165            (&component_input.environment().debug(), &self.component_input.environment().debug()),
166            (
167                &component_input.environment().runners(),
168                &self.component_input.environment().runners(),
169            ),
170            (
171                &component_input.environment().resolvers(),
172                &self.component_input.environment().resolvers(),
173            ),
174            (&component_output.capabilities(), &self.component_output.capabilities()),
175            (&component_output.framework(), &self.component_output.framework()),
176            (&program_input.namespace(), &self.program_input.namespace()),
177            (&program_input.config(), &self.program_input.config()),
178            (&program_output_dict, &self.program_output_dict),
179            (&framework_dict, &self.framework_dict),
180            (&capability_sourced_capabilities_dict, &self.capability_sourced_capabilities_dict),
181            (&declared_dictionaries, &self.declared_dictionaries),
182        ] {
183            copy_to.append(copy_from).expect("sandbox capability is not cloneable");
184        }
185        if let Some(runner_router) = program_input.runner() {
186            self.program_input.set_runner(runner_router.into());
187        }
188        self.child_inputs.append(child_inputs).unwrap();
189        self.collection_inputs.append(collection_inputs).unwrap();
190    }
191}
192
193/// Once a component has been resolved and its manifest becomes known, this function produces the
194/// various dicts the component needs based on the contents of its manifest.
195pub fn build_component_sandbox<C: ComponentInstanceInterface + 'static>(
196    component: &Arc<C>,
197    child_component_output_dictionary_routers: HashMap<ChildName, Router<Dict>>,
198    decl: &cm_rust::ComponentDecl,
199    component_input: ComponentInput,
200    program_output_dict: Dict,
201    framework_dict: Dict,
202    capability_sourced_capabilities_dict: Dict,
203    declared_dictionaries: Dict,
204    error_reporter: impl ErrorReporter,
205    aggregate_router_fn: &AggregateRouterFn<C>,
206) -> ComponentSandbox {
207    let component_output = ComponentOutput::new();
208    let program_input = ProgramInput::default();
209    let environments: StructuredDictMap<ComponentEnvironment> = Default::default();
210    let child_inputs: StructuredDictMap<ComponentInput> = Default::default();
211    let collection_inputs: StructuredDictMap<ComponentInput> = Default::default();
212
213    for environment_decl in &decl.environments {
214        environments
215            .insert(
216                environment_decl.name.clone(),
217                build_environment(
218                    component,
219                    &child_component_output_dictionary_routers,
220                    &component_input,
221                    environment_decl,
222                    &program_output_dict,
223                    &error_reporter,
224                ),
225            )
226            .ok();
227    }
228
229    for child in &decl.children {
230        let environment;
231        if let Some(environment_name) = child.environment.as_ref() {
232            environment = environments.get(environment_name).expect(
233                "child references nonexistent environment, \
234                    this should be prevented in manifest validation",
235            );
236        } else {
237            environment = component_input.environment();
238        }
239        let input = ComponentInput::new(environment);
240        let name = Name::new(child.name.as_str()).expect("child is static so name is not long");
241        child_inputs.insert(name, input).ok();
242    }
243
244    for collection in &decl.collections {
245        let environment;
246        if let Some(environment_name) = collection.environment.as_ref() {
247            environment = environments.get(environment_name).expect(
248                "collection references nonexistent environment, \
249                    this should be prevented in manifest validation",
250            )
251        } else {
252            environment = component_input.environment();
253        }
254        let input = ComponentInput::new(environment);
255        collection_inputs.insert(collection.name.clone(), input).ok();
256    }
257
258    for use_ in decl.uses.iter() {
259        match use_ {
260            cm_rust::UseDecl::Service(_)
261                if matches!(use_.source(), cm_rust::UseSource::Collection(_)) =>
262            {
263                let cm_rust::UseSource::Collection(collection_name) = use_.source() else {
264                    unreachable!();
265                };
266                let availability = *use_.availability();
267                let aggregate = (aggregate_router_fn)(
268                    component.clone(),
269                    vec![AggregateSource::Collection { collection_name: collection_name.clone() }],
270                    CapabilitySource::AnonymizedAggregate(AnonymizedAggregateSource {
271                        capability: AggregateCapability::Service(use_.source_name().clone()),
272                        moniker: component.moniker().clone(),
273                        members: vec![AggregateMember::try_from(use_).unwrap()],
274                        sources: Sources::new(cm_rust::CapabilityTypeName::Service),
275                        instances: vec![],
276                    }),
277                    CapabilityTypeName::Service,
278                    availability,
279                )
280                .with_porcelain_with_default(CapabilityTypeName::Service)
281                .availability(availability)
282                .target(component)
283                .error_info(use_)
284                .error_reporter(error_reporter.clone())
285                .build();
286                if let Err(e) = program_input
287                    .namespace()
288                    .insert_capability(use_.path().unwrap(), aggregate.into())
289                {
290                    warn!("failed to insert {} in program input dict: {e:?}", use_.path().unwrap())
291                }
292            }
293            cm_rust::UseDecl::Service(_) => extend_dict_with_use::<DirEntry, _>(
294                component,
295                &child_component_output_dictionary_routers,
296                &component_input,
297                &program_input,
298                &program_output_dict,
299                &framework_dict,
300                &capability_sourced_capabilities_dict,
301                use_,
302                error_reporter.clone(),
303            ),
304            cm_rust::UseDecl::Protocol(_) | cm_rust::UseDecl::Runner(_) => {
305                extend_dict_with_use::<Connector, _>(
306                    component,
307                    &child_component_output_dictionary_routers,
308                    &component_input,
309                    &program_input,
310                    &program_output_dict,
311                    &framework_dict,
312                    &capability_sourced_capabilities_dict,
313                    use_,
314                    error_reporter.clone(),
315                )
316            }
317            cm_rust::UseDecl::Config(config) => extend_dict_with_config_use(
318                component,
319                &child_component_output_dictionary_routers,
320                &component_input,
321                &program_input,
322                &program_output_dict,
323                config,
324                error_reporter.clone(),
325            ),
326            _ => (),
327        }
328    }
329
330    // The runner may be specified by either use declaration or in the program section of the
331    // manifest. If there's no use declaration for a runner and there is one set in the program
332    // section, then let's synthesize a use decl for it and add it to the sandbox.
333    if !decl.uses.iter().any(|u| matches!(u, cm_rust::UseDecl::Runner(_))) {
334        if let Some(runner_name) = decl.program.as_ref().and_then(|p| p.runner.as_ref()) {
335            extend_dict_with_use::<Connector, _>(
336                component,
337                &child_component_output_dictionary_routers,
338                &component_input,
339                &program_input,
340                &program_output_dict,
341                &framework_dict,
342                &capability_sourced_capabilities_dict,
343                &cm_rust::UseDecl::Runner(cm_rust::UseRunnerDecl {
344                    source: cm_rust::UseSource::Environment,
345                    source_name: runner_name.clone(),
346                    source_dictionary: Default::default(),
347                }),
348                error_reporter.clone(),
349            );
350        }
351    }
352
353    for offer_bundle in group_offer_aggregates(&decl.offers).into_iter() {
354        let first_offer = offer_bundle.first().unwrap();
355        let get_target_dict = || match first_offer.target() {
356            cm_rust::OfferTarget::Child(child_ref) => {
357                assert!(child_ref.collection.is_none(), "unexpected dynamic offer target");
358                let child_name = Name::new(child_ref.name.as_str())
359                    .expect("child is static so name is not long");
360                if child_inputs.get(&child_name).is_none() {
361                    child_inputs.insert(child_name.clone(), Default::default()).ok();
362                }
363                child_inputs
364                    .get(&child_name)
365                    .expect("component input was just added")
366                    .capabilities()
367            }
368            cm_rust::OfferTarget::Collection(name) => {
369                if collection_inputs.get(&name).is_none() {
370                    collection_inputs.insert(name.clone(), Default::default()).ok();
371                }
372                collection_inputs
373                    .get(&name)
374                    .expect("collection input was just added")
375                    .capabilities()
376            }
377            cm_rust::OfferTarget::Capability(name) => {
378                let dict = match declared_dictionaries
379                    .get(name)
380                    .expect("dictionaries must be cloneable")
381                {
382                    Some(dict) => dict,
383                    None => {
384                        let dict = Dict::new();
385                        declared_dictionaries
386                            .insert(name.clone(), Capability::Dictionary(dict.clone()))
387                            .ok();
388                        Capability::Dictionary(dict)
389                    }
390                };
391                let Capability::Dictionary(dict) = dict else {
392                    panic!("wrong type in dict");
393                };
394                dict
395            }
396        };
397        match first_offer {
398            cm_rust::OfferDecl::Service(_)
399                if offer_bundle.len() == 1
400                    && !matches!(first_offer.source(), cm_rust::OfferSource::Collection(_)) =>
401            {
402                extend_dict_with_offer::<DirEntry, _>(
403                    component,
404                    &child_component_output_dictionary_routers,
405                    &component_input,
406                    &program_output_dict,
407                    &framework_dict,
408                    &capability_sourced_capabilities_dict,
409                    first_offer,
410                    &(get_target_dict)(),
411                    error_reporter.clone(),
412                );
413            }
414            cm_rust::OfferDecl::Service(_) => {
415                let aggregate_router = new_aggregate_router_from_service_offers(
416                    &offer_bundle,
417                    component,
418                    &child_component_output_dictionary_routers,
419                    &component_input,
420                    &program_output_dict,
421                    &framework_dict,
422                    &capability_sourced_capabilities_dict,
423                    error_reporter.clone(),
424                    aggregate_router_fn,
425                );
426                (get_target_dict)()
427                    .insert(first_offer.target_name().clone(), aggregate_router.into())
428                    .expect("failed to insert capability into target dict")
429            }
430            cm_rust::OfferDecl::Config(_) => extend_dict_with_offer::<Data, _>(
431                component,
432                &child_component_output_dictionary_routers,
433                &component_input,
434                &program_output_dict,
435                &framework_dict,
436                &capability_sourced_capabilities_dict,
437                first_offer,
438                &(get_target_dict)(),
439                error_reporter.clone(),
440            ),
441            cm_rust::OfferDecl::Dictionary(_) => extend_dict_with_offer::<Dict, _>(
442                component,
443                &child_component_output_dictionary_routers,
444                &component_input,
445                &program_output_dict,
446                &framework_dict,
447                &capability_sourced_capabilities_dict,
448                first_offer,
449                &(get_target_dict)(),
450                error_reporter.clone(),
451            ),
452            cm_rust::OfferDecl::Protocol(_)
453            | cm_rust::OfferDecl::Runner(_)
454            | cm_rust::OfferDecl::Resolver(_) => extend_dict_with_offer::<Connector, _>(
455                component,
456                &child_component_output_dictionary_routers,
457                &component_input,
458                &program_output_dict,
459                &framework_dict,
460                &capability_sourced_capabilities_dict,
461                first_offer,
462                &(get_target_dict)(),
463                error_reporter.clone(),
464            ),
465            _ => {}
466        }
467    }
468
469    for expose_bundle in group_expose_aggregates(&decl.exposes).into_iter() {
470        let first_expose = expose_bundle.first().unwrap();
471        match first_expose {
472            cm_rust::ExposeDecl::Service(_)
473                if expose_bundle.len() == 1
474                    && !matches!(first_expose.source(), cm_rust::ExposeSource::Collection(_)) =>
475            {
476                extend_dict_with_expose::<DirEntry, _>(
477                    component,
478                    &child_component_output_dictionary_routers,
479                    &program_output_dict,
480                    &framework_dict,
481                    &capability_sourced_capabilities_dict,
482                    first_expose,
483                    &component_output,
484                    error_reporter.clone(),
485                );
486            }
487            cm_rust::ExposeDecl::Service(_) => {
488                let mut aggregate_sources = vec![];
489                let temp_component_output = ComponentOutput::new();
490                for expose in expose_bundle.iter() {
491                    extend_dict_with_expose::<DirEntry, _>(
492                        component,
493                        &child_component_output_dictionary_routers,
494                        &program_output_dict,
495                        &framework_dict,
496                        &capability_sourced_capabilities_dict,
497                        expose,
498                        &temp_component_output,
499                        error_reporter.clone(),
500                    );
501                    match temp_component_output.capabilities().remove(first_expose.target_name()) {
502                        Some(Capability::DirEntryRouter(router)) => {
503                            let source_instance = match expose.source() {
504                                cm_rust::ExposeSource::Self_ => AggregateInstance::Self_,
505                                cm_rust::ExposeSource::Child(name) => AggregateInstance::Child(
506                                    moniker::ChildName::new(name.clone().to_long(), None),
507                                ),
508                                other_source => {
509                                    warn!(
510                                        "unsupported source found in expose aggregate: {:?}",
511                                        other_source
512                                    );
513                                    continue;
514                                }
515                            };
516                            aggregate_sources
517                                .push(AggregateSource::DirectoryRouter { source_instance, router })
518                        }
519                        None => match expose.source() {
520                            cm_rust::ExposeSource::Collection(collection_name) => {
521                                aggregate_sources.push(AggregateSource::Collection {
522                                    collection_name: collection_name.clone(),
523                                });
524                            }
525                            _ => continue,
526                        },
527                        other_value => panic!("unexpected dictionary entry: {:?}", other_value),
528                    }
529                }
530                let availability = *first_expose.availability();
531                let aggregate = (aggregate_router_fn)(
532                    component.clone(),
533                    aggregate_sources,
534                    CapabilitySource::AnonymizedAggregate(AnonymizedAggregateSource {
535                        capability: AggregateCapability::Service(
536                            first_expose.target_name().clone(),
537                        ),
538                        moniker: component.moniker().clone(),
539                        members: expose_bundle
540                            .iter()
541                            .filter_map(|e| AggregateMember::try_from(*e).ok())
542                            .collect(),
543                        sources: Sources::new(cm_rust::CapabilityTypeName::Service),
544                        instances: vec![],
545                    }),
546                    CapabilityTypeName::Service,
547                    availability,
548                );
549                component_output
550                    .capabilities()
551                    .insert(first_expose.target_name().clone(), aggregate.into())
552                    .expect("failed to insert capability into target dict")
553            }
554            cm_rust::ExposeDecl::Config(_) => extend_dict_with_expose::<Data, _>(
555                component,
556                &child_component_output_dictionary_routers,
557                &program_output_dict,
558                &framework_dict,
559                &capability_sourced_capabilities_dict,
560                first_expose,
561                &component_output,
562                error_reporter.clone(),
563            ),
564            cm_rust::ExposeDecl::Dictionary(_) => extend_dict_with_expose::<Dict, _>(
565                component,
566                &child_component_output_dictionary_routers,
567                &program_output_dict,
568                &framework_dict,
569                &capability_sourced_capabilities_dict,
570                first_expose,
571                &component_output,
572                error_reporter.clone(),
573            ),
574            cm_rust::ExposeDecl::Protocol(_)
575            | cm_rust::ExposeDecl::Runner(_)
576            | cm_rust::ExposeDecl::Resolver(_) => extend_dict_with_expose::<Connector, _>(
577                component,
578                &child_component_output_dictionary_routers,
579                &program_output_dict,
580                &framework_dict,
581                &capability_sourced_capabilities_dict,
582                first_expose,
583                &component_output,
584                error_reporter.clone(),
585            ),
586            _ => {}
587        }
588    }
589
590    ComponentSandbox {
591        component_input,
592        component_output,
593        program_input,
594        program_output_dict,
595        framework_dict,
596        capability_sourced_capabilities_dict,
597        declared_dictionaries,
598        child_inputs,
599        collection_inputs,
600    }
601}
602
603fn new_aggregate_router_from_service_offers<C: ComponentInstanceInterface + 'static>(
604    offer_bundle: &Vec<&cm_rust::OfferDecl>,
605    component: &Arc<C>,
606    child_component_output_dictionary_routers: &HashMap<ChildName, Router<Dict>>,
607    component_input: &ComponentInput,
608    program_output_dict: &Dict,
609    framework_dict: &Dict,
610    capability_sourced_capabilities_dict: &Dict,
611    error_reporter: impl ErrorReporter,
612    aggregate_router_fn: &AggregateRouterFn<C>,
613) -> Router<DirEntry> {
614    let mut aggregate_sources = vec![];
615    let dict_for_source_router = Dict::new();
616    let source = new_aggregate_capability_source(component.moniker().clone(), offer_bundle.clone());
617    for offer in offer_bundle.iter() {
618        if matches!(&source, &CapabilitySource::FilteredAggregateProvider(_)) {
619            if let cm_rust::OfferDecl::Service(offer_service_decl) = offer {
620                if offer_service_decl
621                    .source_instance_filter
622                    .as_ref()
623                    .and_then(|v| v.first())
624                    .is_none()
625                    && offer_service_decl
626                        .renamed_instances
627                        .as_ref()
628                        .and_then(|v| v.first())
629                        .is_none()
630                {
631                    // If we're a filtering aggregate and no filter or renames have been
632                    // set, then all instances here are ignored, and there's no point in
633                    // including the router in the aggregate.
634                    continue;
635                }
636            }
637        }
638        extend_dict_with_offer::<DirEntry, _>(
639            component,
640            &child_component_output_dictionary_routers,
641            &component_input,
642            &program_output_dict,
643            &framework_dict,
644            &capability_sourced_capabilities_dict,
645            offer,
646            &dict_for_source_router,
647            error_reporter.clone(),
648        );
649        match dict_for_source_router.remove(offer.target_name()) {
650            Some(Capability::DirEntryRouter(router)) => {
651                let source_instance = match offer.source() {
652                    cm_rust::OfferSource::Self_ => AggregateInstance::Self_,
653                    cm_rust::OfferSource::Parent => AggregateInstance::Parent,
654                    cm_rust::OfferSource::Child(child_ref) => {
655                        AggregateInstance::Child(moniker::ChildName::new(
656                            child_ref.name.clone(),
657                            child_ref.collection.clone(),
658                        ))
659                    }
660                    other_source => {
661                        warn!("unsupported source found in offer aggregate: {:?}", other_source);
662                        continue;
663                    }
664                };
665                aggregate_sources.push(AggregateSource::DirectoryRouter { source_instance, router })
666            }
667            None => match offer.source() {
668                // `extend_dict_with_offer` doesn't insert a capability for offers with a source of
669                // `OfferSource::Collection`. This is because at this stage there's nothing in the
670                // collection, and thus no routers to things in the collection.
671                cm_rust::OfferSource::Collection(collection_name) => {
672                    aggregate_sources.push(AggregateSource::Collection {
673                        collection_name: collection_name.clone(),
674                    });
675                }
676                _ => continue,
677            },
678            other => warn!("found unexpected entry in dictionary: {:?}", other),
679        }
680    }
681    let availability = *offer_bundle.first().unwrap().availability();
682    (aggregate_router_fn)(
683        component.clone(),
684        aggregate_sources,
685        source,
686        CapabilityTypeName::Service,
687        availability,
688    )
689}
690
691fn new_aggregate_capability_source(
692    moniker: Moniker,
693    offers: Vec<&cm_rust::OfferDecl>,
694) -> CapabilitySource {
695    let offer_service_decls = offers
696        .iter()
697        .map(|o| match o {
698            cm_rust::OfferDecl::Service(o) => o,
699            _ => panic!("cannot aggregate non-service capabilities, manifest validation should prevent this"),
700        }).collect::<Vec<_>>();
701    // This is a filtered offer if any of the offers set a filter or rename mapping.
702    let is_filtered_offer = offer_service_decls.iter().any(|o| {
703        o.source_instance_filter.as_ref().map(|v| !v.is_empty()).unwrap_or(false)
704            || o.renamed_instances.as_ref().map(|v| !v.is_empty()).unwrap_or(false)
705    });
706    let capability =
707        AggregateCapability::Service(offer_service_decls.first().unwrap().target_name.clone());
708    if is_filtered_offer {
709        CapabilitySource::FilteredAggregateProvider(FilteredAggregateProviderSource {
710            capability,
711            moniker,
712            offer_service_decls: offer_service_decls.into_iter().cloned().collect(),
713            sources: Sources::new(cm_rust::CapabilityTypeName::Service).component().collection(),
714        })
715    } else {
716        let members = offers.iter().filter_map(|o| AggregateMember::try_from(*o).ok()).collect();
717        CapabilitySource::AnonymizedAggregate(AnonymizedAggregateSource {
718            capability,
719            moniker,
720            members,
721            sources: Sources::new(cm_rust::CapabilityTypeName::Service).component().collection(),
722            instances: vec![],
723        })
724    }
725}
726
727/// Groups together a set of offers into sub-sets of those that have the same target and target
728/// name. This is useful for identifying which offers are part of an aggregation of capabilities,
729/// and which are for standalone routes.
730fn group_offer_aggregates(offers: &Vec<cm_rust::OfferDecl>) -> Vec<Vec<&cm_rust::OfferDecl>> {
731    let mut groupings = HashMap::new();
732    for offer in offers.iter() {
733        groupings.entry((offer.target(), offer.target_name())).or_insert(vec![]).push(offer);
734    }
735    groupings.into_iter().map(|(_key, grouping)| grouping).collect()
736}
737
738/// Identical to `group_offer_aggregates`, but for exposes.
739fn group_expose_aggregates(exposes: &Vec<cm_rust::ExposeDecl>) -> Vec<Vec<&cm_rust::ExposeDecl>> {
740    let mut groupings = HashMap::new();
741    for expose in exposes.iter() {
742        groupings.entry((expose.target(), expose.target_name())).or_insert(vec![]).push(expose);
743    }
744    groupings.into_iter().map(|(_key, grouping)| grouping).collect()
745}
746
747fn build_environment<C: ComponentInstanceInterface + 'static>(
748    component: &Arc<C>,
749    child_component_output_dictionary_routers: &HashMap<ChildName, Router<Dict>>,
750    component_input: &ComponentInput,
751    environment_decl: &cm_rust::EnvironmentDecl,
752    program_output_dict: &Dict,
753    error_reporter: &impl ErrorReporter,
754) -> ComponentEnvironment {
755    let mut environment = ComponentEnvironment::new();
756    if environment_decl.extends == fdecl::EnvironmentExtends::Realm {
757        if let Ok(e) = component_input.environment().shallow_copy() {
758            environment = e;
759        } else {
760            warn!("failed to copy component_input.environment");
761        }
762    }
763    let debug = environment_decl.debug_capabilities.iter().map(|debug_registration| {
764        let cm_rust::DebugRegistration::Protocol(debug) = debug_registration;
765        (
766            &debug.source_name,
767            debug.target_name.clone(),
768            &debug.source,
769            CapabilityTypeName::Protocol,
770            RouteRequestErrorInfo::from(debug_registration),
771        )
772    });
773    let runners = environment_decl.runners.iter().map(|runner| {
774        (
775            &runner.source_name,
776            runner.target_name.clone(),
777            &runner.source,
778            CapabilityTypeName::Runner,
779            RouteRequestErrorInfo::from(runner),
780        )
781    });
782    let resolvers = environment_decl.resolvers.iter().map(|resolver| {
783        (
784            &resolver.resolver,
785            Name::new(&resolver.scheme).unwrap(),
786            &resolver.source,
787            CapabilityTypeName::Resolver,
788            RouteRequestErrorInfo::from(resolver),
789        )
790    });
791    let moniker = component.moniker();
792    for (source_name, target_name, source, porcelain_type, route_request) in
793        debug.chain(runners).chain(resolvers)
794    {
795        let source_path =
796            SeparatedPath { dirname: Default::default(), basename: source_name.clone() };
797        let router: Router<Connector> = match &source {
798            cm_rust::RegistrationSource::Parent => {
799                use_from_parent_router::<Connector>(component_input, source_path, moniker)
800            }
801            cm_rust::RegistrationSource::Self_ => program_output_dict
802                .get_router_or_not_found::<Connector>(
803                    &source_path,
804                    RoutingError::use_from_self_not_found(
805                        moniker,
806                        source_path.iter_segments().join("/"),
807                    ),
808                ),
809            cm_rust::RegistrationSource::Child(child_name) => {
810                let child_name = ChildName::parse(child_name).expect("invalid child name");
811                let Some(child_component_output) =
812                    child_component_output_dictionary_routers.get(&child_name)
813                else {
814                    continue;
815                };
816                child_component_output.clone().lazy_get(
817                    source_path,
818                    RoutingError::use_from_child_expose_not_found(
819                        &child_name,
820                        moniker,
821                        source_name.clone(),
822                    ),
823                )
824            }
825        };
826        let router = router
827            .with_porcelain_no_default(porcelain_type)
828            .availability(Availability::Required)
829            .target(component)
830            .error_info(route_request)
831            .error_reporter(error_reporter.clone());
832        let dict_to_insert_to = match porcelain_type {
833            CapabilityTypeName::Protocol => environment.debug(),
834            CapabilityTypeName::Runner => environment.runners(),
835            CapabilityTypeName::Resolver => environment.resolvers(),
836            c => panic!("unexpected capability type {}", c),
837        };
838        match dict_to_insert_to.insert_capability(&target_name, router.into()) {
839            Ok(()) => (),
840            Err(_e) => {
841                // The only reason this will happen is if we're shadowing something else in the
842                // environment. `insert_capability` will still insert the new capability when it
843                // returns an error, so we can safely ignore this.
844            }
845        }
846    }
847    environment
848}
849
850/// Extends the given `target_input` to contain the capabilities described in `dynamic_offers`.
851pub fn extend_dict_with_offers<C: ComponentInstanceInterface + 'static>(
852    component: &Arc<C>,
853    static_offers: &Vec<cm_rust::OfferDecl>,
854    child_component_output_dictionary_routers: &HashMap<ChildName, Router<Dict>>,
855    component_input: &ComponentInput,
856    dynamic_offers: &Vec<cm_rust::OfferDecl>,
857    program_output_dict: &Dict,
858    framework_dict: &Dict,
859    capability_sourced_capabilities_dict: &Dict,
860    target_input: &ComponentInput,
861    error_reporter: impl ErrorReporter,
862    aggregate_router_fn: &AggregateRouterFn<C>,
863) {
864    for offer_bundle in group_offer_aggregates(dynamic_offers).into_iter() {
865        let first_offer = offer_bundle.first().unwrap();
866        match first_offer {
867            cm_rust::OfferDecl::Service(_) => {
868                let static_offer_bundles = group_offer_aggregates(static_offers);
869                let maybe_static_offer_bundle = static_offer_bundles.into_iter().find(|bundle| {
870                    bundle.first().unwrap().target_name() == first_offer.target_name()
871                });
872                let mut combined_offer_bundle = offer_bundle.clone();
873                if let Some(mut static_offer_bundle) = maybe_static_offer_bundle {
874                    // We are aggregating together dynamic and static offers, as there are static
875                    // offers with the same target name as our current dynamic offers. We already
876                    // populated a router for the static bundle in the target input, let's toss
877                    // that and generate a new one with the expanded set of offers.
878                    let _ = target_input.capabilities().remove(first_offer.target_name());
879                    combined_offer_bundle.append(&mut static_offer_bundle);
880                }
881                if combined_offer_bundle.len() == 1
882                    && !matches!(first_offer.source(), cm_rust::OfferSource::Collection(_))
883                {
884                    extend_dict_with_offer::<DirEntry, _>(
885                        component,
886                        &child_component_output_dictionary_routers,
887                        &component_input,
888                        &program_output_dict,
889                        &framework_dict,
890                        &capability_sourced_capabilities_dict,
891                        first_offer,
892                        &target_input.capabilities(),
893                        error_reporter.clone(),
894                    );
895                } else {
896                    let aggregate_router = new_aggregate_router_from_service_offers(
897                        &combined_offer_bundle,
898                        component,
899                        &child_component_output_dictionary_routers,
900                        &component_input,
901                        &program_output_dict,
902                        &framework_dict,
903                        &capability_sourced_capabilities_dict,
904                        error_reporter.clone(),
905                        aggregate_router_fn,
906                    );
907                    target_input
908                        .capabilities()
909                        .insert(first_offer.target_name().clone(), aggregate_router.into())
910                        .expect("failed to insert capability into target dict");
911                }
912            }
913            cm_rust::OfferDecl::Config(_) => extend_dict_with_offer::<Data, _>(
914                component,
915                &child_component_output_dictionary_routers,
916                component_input,
917                program_output_dict,
918                framework_dict,
919                capability_sourced_capabilities_dict,
920                first_offer,
921                &target_input.capabilities(),
922                error_reporter.clone(),
923            ),
924            cm_rust::OfferDecl::Dictionary(_) => extend_dict_with_offer::<Dict, _>(
925                component,
926                &child_component_output_dictionary_routers,
927                component_input,
928                program_output_dict,
929                framework_dict,
930                capability_sourced_capabilities_dict,
931                first_offer,
932                &target_input.capabilities(),
933                error_reporter.clone(),
934            ),
935            cm_rust::OfferDecl::Protocol(_)
936            | cm_rust::OfferDecl::Runner(_)
937            | cm_rust::OfferDecl::Resolver(_) => extend_dict_with_offer::<Connector, _>(
938                component,
939                &child_component_output_dictionary_routers,
940                component_input,
941                program_output_dict,
942                framework_dict,
943                capability_sourced_capabilities_dict,
944                first_offer,
945                &target_input.capabilities(),
946                error_reporter.clone(),
947            ),
948            _ => {}
949        }
950    }
951}
952
953pub fn is_supported_use(use_: &cm_rust::UseDecl) -> bool {
954    matches!(
955        use_,
956        cm_rust::UseDecl::Config(_)
957            | cm_rust::UseDecl::Protocol(_)
958            | cm_rust::UseDecl::Runner(_)
959            | cm_rust::UseDecl::Service(_)
960    )
961}
962
963// Add the `config_use` to the `program_input_dict`, so the component is able to
964// access this configuration.
965fn extend_dict_with_config_use<C: ComponentInstanceInterface + 'static>(
966    component: &Arc<C>,
967    child_component_output_dictionary_routers: &HashMap<ChildName, Router<Dict>>,
968    component_input: &ComponentInput,
969    program_input: &ProgramInput,
970    program_output_dict: &Dict,
971    config_use: &cm_rust::UseConfigurationDecl,
972    error_reporter: impl ErrorReporter,
973) {
974    let moniker = component.moniker();
975    let source_path = config_use.source_path();
976    let porcelain_type = CapabilityTypeName::Config;
977    let router: Router<Data> = match config_use.source() {
978        cm_rust::UseSource::Parent => {
979            use_from_parent_router::<Data>(component_input, source_path.to_owned(), moniker)
980        }
981        cm_rust::UseSource::Self_ => program_output_dict.get_router_or_not_found::<Data>(
982            &source_path,
983            RoutingError::use_from_self_not_found(moniker, source_path.iter_segments().join("/")),
984        ),
985        cm_rust::UseSource::Child(child_name) => {
986            let child_name = ChildName::parse(child_name).expect("invalid child name");
987            let Some(child_component_output) =
988                child_component_output_dictionary_routers.get(&child_name)
989            else {
990                panic!("use declaration in manifest for component {} has a source of a nonexistent child {}, this should be prevented by manifest validation", moniker, child_name);
991            };
992            child_component_output.clone().lazy_get(
993                source_path.to_owned(),
994                RoutingError::use_from_child_expose_not_found(
995                    &child_name,
996                    &moniker,
997                    config_use.source_name().clone(),
998                ),
999            )
1000        }
1001        // The following are not used with config capabilities.
1002        cm_rust::UseSource::Environment => return,
1003        cm_rust::UseSource::Debug => return,
1004        cm_rust::UseSource::Capability(_) => return,
1005        cm_rust::UseSource::Framework => return,
1006        cm_rust::UseSource::Collection(_) => return,
1007    };
1008
1009    let availability = *config_use.availability();
1010    match program_input.config().insert_capability(
1011        &config_use.target_name,
1012        router
1013            .with_porcelain_with_default(porcelain_type)
1014            .availability(availability)
1015            .target(component)
1016            .error_info(config_use)
1017            .error_reporter(error_reporter)
1018            .into(),
1019    ) {
1020        Ok(()) => (),
1021        Err(e) => {
1022            warn!("failed to insert {} in program input dict: {e:?}", config_use.target_name)
1023        }
1024    }
1025}
1026
1027fn extend_dict_with_use<T, C: ComponentInstanceInterface + 'static>(
1028    component: &Arc<C>,
1029    child_component_output_dictionary_routers: &HashMap<ChildName, Router<Dict>>,
1030    component_input: &ComponentInput,
1031    program_input: &ProgramInput,
1032    program_output_dict: &Dict,
1033    framework_dict: &Dict,
1034    capability_sourced_capabilities_dict: &Dict,
1035    use_: &cm_rust::UseDecl,
1036    error_reporter: impl ErrorReporter,
1037) where
1038    T: CapabilityBound + Clone,
1039    Router<T>: TryFrom<Capability> + Into<Capability>,
1040{
1041    if !is_supported_use(use_) {
1042        return;
1043    }
1044    let moniker = component.moniker();
1045    if let cm_rust::UseDecl::Config(config) = use_ {
1046        return extend_dict_with_config_use(
1047            component,
1048            child_component_output_dictionary_routers,
1049            component_input,
1050            program_input,
1051            program_output_dict,
1052            config,
1053            error_reporter,
1054        );
1055    };
1056
1057    let source_path = use_.source_path();
1058    let porcelain_type = CapabilityTypeName::from(use_);
1059    let router: Router<T> = match use_.source() {
1060        cm_rust::UseSource::Parent => {
1061            use_from_parent_router::<T>(component_input, source_path.to_owned(), moniker)
1062        }
1063        cm_rust::UseSource::Self_ => program_output_dict.get_router_or_not_found::<T>(
1064            &source_path,
1065            RoutingError::use_from_self_not_found(moniker, source_path.iter_segments().join("/")),
1066        ),
1067        cm_rust::UseSource::Child(child_name) => {
1068            let child_name = ChildName::parse(child_name).expect("invalid child name");
1069            let Some(child_component_output) =
1070                child_component_output_dictionary_routers.get(&child_name)
1071            else {
1072                panic!("use declaration in manifest for component {} has a source of a nonexistent child {}, this should be prevented by manifest validation", moniker, child_name);
1073            };
1074            child_component_output.clone().lazy_get(
1075                source_path.to_owned(),
1076                RoutingError::use_from_child_expose_not_found(
1077                    &child_name,
1078                    &moniker,
1079                    use_.source_name().clone(),
1080                ),
1081            )
1082        }
1083        cm_rust::UseSource::Framework if use_.is_from_dictionary() => {
1084            Router::<T>::new_error(RoutingError::capability_from_framework_not_found(
1085                moniker,
1086                source_path.iter_segments().join("/"),
1087            ))
1088        }
1089        cm_rust::UseSource::Framework => framework_dict.get_router_or_not_found::<T>(
1090            &source_path,
1091            RoutingError::capability_from_framework_not_found(
1092                moniker,
1093                source_path.iter_segments().join("/"),
1094            ),
1095        ),
1096        cm_rust::UseSource::Capability(capability_name) => {
1097            let err = RoutingError::capability_from_capability_not_found(
1098                moniker,
1099                capability_name.as_str().to_string(),
1100            );
1101            if source_path.iter_segments().join("/") == fsys::StorageAdminMarker::PROTOCOL_NAME {
1102                capability_sourced_capabilities_dict.get_router_or_not_found(&capability_name, err)
1103            } else {
1104                Router::<T>::new_error(err)
1105            }
1106        }
1107        cm_rust::UseSource::Debug => {
1108            let cm_rust::UseDecl::Protocol(use_protocol) = use_ else {
1109                panic!("non-protocol capability used with a debug source, this should be prevented by manifest validation");
1110            };
1111            component_input.environment().debug().get_router_or_not_found::<T>(
1112                &use_protocol.source_name,
1113                RoutingError::use_from_environment_not_found(
1114                    moniker,
1115                    "protocol",
1116                    &use_protocol.source_name,
1117                ),
1118            )
1119        }
1120        cm_rust::UseSource::Environment => {
1121            let cm_rust::UseDecl::Runner(use_runner) = use_ else {
1122                panic!("non-runner capability used with an environment source, this should be prevented by manifest validation");
1123            };
1124            component_input.environment().runners().get_router_or_not_found::<T>(
1125                &use_runner.source_name,
1126                RoutingError::use_from_environment_not_found(
1127                    moniker,
1128                    "runner",
1129                    &use_runner.source_name,
1130                ),
1131            )
1132        }
1133        cm_rust::UseSource::Collection(_) => {
1134            // Collection sources are handled separately, in `build_component_sandbox`
1135            return;
1136        }
1137    };
1138
1139    let availability = *use_.availability();
1140    let router = router
1141        .with_porcelain_with_default(porcelain_type)
1142        .availability(availability)
1143        .target(&component)
1144        .error_info(use_)
1145        .error_reporter(error_reporter)
1146        .build();
1147
1148    if let Some(target_path) = use_.path() {
1149        if let Err(e) = program_input.namespace().insert_capability(target_path, router.into()) {
1150            warn!("failed to insert {} in program input dict: {e:?}", target_path)
1151        }
1152    } else {
1153        match use_ {
1154            cm_rust::UseDecl::Runner(_) => {
1155                assert!(program_input.runner().is_none(), "component can't use multiple runners");
1156                program_input.set_runner(router.into());
1157            }
1158            _ => panic!("unexpected capability type: {:?}", use_),
1159        }
1160    }
1161}
1162
1163/// Builds a router that obtains a capability that the program uses from `parent`.
1164fn use_from_parent_router<T>(
1165    component_input: &ComponentInput,
1166    source_path: impl IterablePath + 'static + Debug,
1167    moniker: &Moniker,
1168) -> Router<T>
1169where
1170    T: CapabilityBound + Clone,
1171    Router<T>: TryFrom<Capability>,
1172{
1173    let err = if moniker == &Moniker::root() {
1174        RoutingError::register_from_component_manager_not_found(
1175            source_path.iter_segments().join("/"),
1176        )
1177    } else {
1178        RoutingError::use_from_parent_not_found(moniker, source_path.iter_segments().join("/"))
1179    };
1180    component_input.capabilities().get_router_or_not_found::<T>(&source_path, err)
1181}
1182
1183fn is_supported_offer(offer: &cm_rust::OfferDecl) -> bool {
1184    matches!(
1185        offer,
1186        cm_rust::OfferDecl::Config(_)
1187            | cm_rust::OfferDecl::Protocol(_)
1188            | cm_rust::OfferDecl::Dictionary(_)
1189            | cm_rust::OfferDecl::Runner(_)
1190            | cm_rust::OfferDecl::Resolver(_)
1191            | cm_rust::OfferDecl::Service(_)
1192    )
1193}
1194
1195fn extend_dict_with_offer<T, C: ComponentInstanceInterface + 'static>(
1196    component: &Arc<C>,
1197    child_component_output_dictionary_routers: &HashMap<ChildName, Router<Dict>>,
1198    component_input: &ComponentInput,
1199    program_output_dict: &Dict,
1200    framework_dict: &Dict,
1201    capability_sourced_capabilities_dict: &Dict,
1202    offer: &cm_rust::OfferDecl,
1203    target_dict: &Dict,
1204    error_reporter: impl ErrorReporter,
1205) where
1206    T: CapabilityBound + Clone,
1207    Router<T>: TryFrom<Capability> + Into<Capability> + WithServiceRenamesAndFilter,
1208{
1209    assert!(is_supported_offer(offer), "{offer:?}");
1210
1211    let source_path = offer.source_path();
1212    let target_name = offer.target_name();
1213    if target_dict.get_capability(&target_name).is_some() {
1214        panic!(
1215            "duplicate sources for {} {} in a dict, unable to populate dict entry, manifest \
1216                validation should prevent this",
1217            cm_rust::CapabilityTypeName::from(offer),
1218            target_name
1219        );
1220    }
1221    let porcelain_type = CapabilityTypeName::from(offer);
1222    let router: Router<T> = match offer.source() {
1223        cm_rust::OfferSource::Parent => {
1224            let err = if component.moniker() == &Moniker::root() {
1225                RoutingError::register_from_component_manager_not_found(
1226                    offer.source_name().to_string(),
1227                )
1228            } else {
1229                RoutingError::offer_from_parent_not_found(
1230                    &component.moniker(),
1231                    source_path.iter_segments().join("/"),
1232                )
1233            };
1234            component_input.capabilities().get_router_or_not_found::<T>(&source_path, err)
1235        }
1236        cm_rust::OfferSource::Self_ => program_output_dict.get_router_or_not_found::<T>(
1237            &source_path,
1238            RoutingError::offer_from_self_not_found(
1239                &component.moniker(),
1240                source_path.iter_segments().join("/"),
1241            ),
1242        ),
1243        cm_rust::OfferSource::Child(child_ref) => {
1244            let child_name: ChildName = child_ref.clone().try_into().expect("invalid child ref");
1245            match child_component_output_dictionary_routers.get(&child_name) {
1246                None => Router::<T>::new_error(RoutingError::offer_from_child_instance_not_found(
1247                    &child_name,
1248                    &component.moniker(),
1249                    source_path.iter_segments().join("/"),
1250                )),
1251                Some(child_component_output) => child_component_output.clone().lazy_get(
1252                    source_path.to_owned(),
1253                    RoutingError::offer_from_child_expose_not_found(
1254                        &child_name,
1255                        &component.moniker(),
1256                        offer.source_name().clone(),
1257                    ),
1258                ),
1259            }
1260        }
1261        cm_rust::OfferSource::Framework => {
1262            if offer.is_from_dictionary() {
1263                warn!(
1264                    "routing from framework with dictionary path is not supported: {source_path}"
1265                );
1266                return;
1267            }
1268            framework_dict.get_router_or_not_found::<T>(
1269                &source_path,
1270                RoutingError::capability_from_framework_not_found(
1271                    &component.moniker(),
1272                    source_path.iter_segments().join("/"),
1273                ),
1274            )
1275        }
1276        cm_rust::OfferSource::Capability(capability_name) => {
1277            let err = RoutingError::capability_from_capability_not_found(
1278                &component.moniker(),
1279                capability_name.as_str().to_string(),
1280            );
1281            if source_path.iter_segments().join("/") == fsys::StorageAdminMarker::PROTOCOL_NAME {
1282                capability_sourced_capabilities_dict.get_router_or_not_found(&capability_name, err)
1283            } else {
1284                Router::<T>::new_error(err)
1285            }
1286        }
1287        cm_rust::OfferSource::Void => UnavailableRouter::new::<T>(
1288            InternalCapability::Protocol(offer.source_name().clone()),
1289            component,
1290        ),
1291        cm_rust::OfferSource::Collection(_collection_name) => {
1292            // There's nothing in a collection at this stage, and thus we can't get any routers to
1293            // things in the collection. What's more: the contents of the collection can change
1294            // over time, so it must be monitored. We don't handle collections here, they're
1295            // handled in a different way by whoever called `extend_dict_with_offer`.
1296            return;
1297        }
1298    };
1299
1300    // Offered capabilities need to support default requests in the case of offer-to-dictionary.
1301    // This is a corollary of the fact that program_input_dictionary and
1302    // component_output_dictionary support default requests, and we need this to cover the case
1303    // where the use or expose is from a dictionary.
1304    //
1305    // Technically, we could restrict this to the case of offer-to-dictionary, not offer in
1306    // general. However, supporting the general case simplifies the logic and establishes a nice
1307    // symmetry between program_input_dict, component_output_dict, and {child,collection}_inputs.
1308    let availability = *offer.availability();
1309    let router = router
1310        .with_porcelain_with_default(porcelain_type)
1311        .availability(availability)
1312        .target(component)
1313        .error_info(offer)
1314        .error_reporter(error_reporter)
1315        .build()
1316        .with_service_renames_and_filter(offer.clone());
1317    match target_dict.insert_capability(target_name, router.into()) {
1318        Ok(()) => (),
1319        Err(e) => warn!("failed to insert {target_name} into target dict: {e:?}"),
1320    }
1321}
1322
1323pub fn is_supported_expose(expose: &cm_rust::ExposeDecl) -> bool {
1324    matches!(
1325        expose,
1326        cm_rust::ExposeDecl::Config(_)
1327            | cm_rust::ExposeDecl::Protocol(_)
1328            | cm_rust::ExposeDecl::Dictionary(_)
1329            | cm_rust::ExposeDecl::Runner(_)
1330            | cm_rust::ExposeDecl::Resolver(_)
1331            | cm_rust::ExposeDecl::Service(_)
1332    )
1333}
1334
1335fn extend_dict_with_expose<T, C: ComponentInstanceInterface + 'static>(
1336    component: &Arc<C>,
1337    child_component_output_dictionary_routers: &HashMap<ChildName, Router<Dict>>,
1338    program_output_dict: &Dict,
1339    framework_dict: &Dict,
1340    capability_sourced_capabilities_dict: &Dict,
1341    expose: &cm_rust::ExposeDecl,
1342    target_component_output: &ComponentOutput,
1343    error_reporter: impl ErrorReporter,
1344) where
1345    T: CapabilityBound + Clone,
1346    Router<T>: TryFrom<Capability> + Into<Capability>,
1347{
1348    assert!(is_supported_expose(expose), "{expose:?}");
1349
1350    // Exposing to the framework is vestigial
1351    let target_dict = match expose.target() {
1352        cm_rust::ExposeTarget::Parent => target_component_output.capabilities(),
1353        cm_rust::ExposeTarget::Framework => target_component_output.framework(),
1354    };
1355    let source_path = expose.source_path();
1356    let target_name = expose.target_name();
1357
1358    let porcelain_type = CapabilityTypeName::from(expose);
1359    let router: Router<T> = match expose.source() {
1360        cm_rust::ExposeSource::Self_ => program_output_dict.get_router_or_not_found::<T>(
1361            &source_path,
1362            RoutingError::expose_from_self_not_found(
1363                &component.moniker(),
1364                source_path.iter_segments().join("/"),
1365            ),
1366        ),
1367        cm_rust::ExposeSource::Child(child_name) => {
1368            let child_name = ChildName::parse(child_name).expect("invalid static child name");
1369            if let Some(child_component_output) =
1370                child_component_output_dictionary_routers.get(&child_name)
1371            {
1372                child_component_output.clone().lazy_get(
1373                    source_path.to_owned(),
1374                    RoutingError::expose_from_child_expose_not_found(
1375                        &child_name,
1376                        &component.moniker(),
1377                        expose.source_name().clone(),
1378                    ),
1379                )
1380            } else {
1381                Router::<T>::new_error(RoutingError::expose_from_child_instance_not_found(
1382                    &child_name,
1383                    &component.moniker(),
1384                    expose.source_name().clone(),
1385                ))
1386            }
1387        }
1388        cm_rust::ExposeSource::Framework => {
1389            if expose.is_from_dictionary() {
1390                warn!(
1391                    "routing from framework with dictionary path is not supported: {source_path}"
1392                );
1393                return;
1394            }
1395            framework_dict.get_router_or_not_found::<T>(
1396                &source_path,
1397                RoutingError::capability_from_framework_not_found(
1398                    &component.moniker(),
1399                    source_path.iter_segments().join("/"),
1400                ),
1401            )
1402        }
1403        cm_rust::ExposeSource::Capability(capability_name) => {
1404            let err = RoutingError::capability_from_capability_not_found(
1405                &component.moniker(),
1406                capability_name.as_str().to_string(),
1407            );
1408            if source_path.iter_segments().join("/") == fsys::StorageAdminMarker::PROTOCOL_NAME {
1409                capability_sourced_capabilities_dict
1410                    .clone()
1411                    .get_router_or_not_found::<T>(&capability_name, err)
1412            } else {
1413                Router::<T>::new_error(err)
1414            }
1415        }
1416        cm_rust::ExposeSource::Void => UnavailableRouter::new(
1417            InternalCapability::Protocol(expose.source_name().clone()),
1418            component,
1419        ),
1420        // There's nothing in a collection at this stage, and thus we can't get any routers to
1421        // things in the collection. What's more: the contents of the collection can change over
1422        // time, so it must be monitored. We don't handle collections here, they're handled in a
1423        // different way by whoever called `extend_dict_with_expose`.
1424        cm_rust::ExposeSource::Collection(_name) => return,
1425    };
1426    let availability = *expose.availability();
1427    match target_dict.insert_capability(
1428        target_name,
1429        router
1430            .with_porcelain_with_default(porcelain_type)
1431            .availability(availability)
1432            .target(component)
1433            .error_info(expose)
1434            .error_reporter(error_reporter)
1435            .into(),
1436    ) {
1437        Ok(()) => (),
1438        Err(e) => warn!("failed to insert {target_name} into target_dict: {e:?}"),
1439    }
1440}
1441
1442struct UnavailableRouter<C: ComponentInstanceInterface> {
1443    capability: InternalCapability,
1444    component: WeakComponentInstanceInterface<C>,
1445}
1446
1447impl<C: ComponentInstanceInterface + 'static> UnavailableRouter<C> {
1448    fn new<T: CapabilityBound>(capability: InternalCapability, component: &Arc<C>) -> Router<T> {
1449        Router::<T>::new(UnavailableRouter { capability, component: component.as_weak() })
1450    }
1451}
1452
1453#[async_trait]
1454impl<T: CapabilityBound, C: ComponentInstanceInterface + 'static> Routable<T>
1455    for UnavailableRouter<C>
1456{
1457    async fn route(
1458        &self,
1459        request: Option<Request>,
1460        debug: bool,
1461    ) -> Result<RouterResponse<T>, RouterError> {
1462        if debug {
1463            let data = CapabilitySource::Void(VoidSource {
1464                capability: self.capability.clone(),
1465                moniker: self.component.moniker.clone(),
1466            })
1467            .try_into()
1468            .expect("failed to convert capability source to Data");
1469            return Ok(RouterResponse::<T>::Debug(data));
1470        }
1471        let request = request.ok_or_else(|| RouterError::InvalidArgs)?;
1472        let availability = request.metadata.get_metadata().ok_or(RouterError::InvalidArgs)?;
1473        match availability {
1474            cm_rust::Availability::Required | cm_rust::Availability::SameAsTarget => {
1475                Err(RoutingError::SourceCapabilityIsVoid {
1476                    moniker: self.component.moniker.clone(),
1477                }
1478                .into())
1479            }
1480            cm_rust::Availability::Optional | cm_rust::Availability::Transitional => {
1481                Ok(RouterResponse::Unavailable)
1482            }
1483        }
1484    }
1485}