Skip to main content

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::use_dictionary_router::UseDictionaryRouter;
11use crate::bedrock::with_service_renames_and_filter::WithServiceRenamesAndFilter;
12use crate::capability_source::{
13    AggregateCapability, AggregateInstance, AggregateMember, AnonymizedAggregateSource,
14    CapabilitySource, ComponentCapability, ComponentSource, FilteredAggregateProviderSource,
15    InternalCapability, VoidSource,
16};
17use crate::component_instance::{ComponentInstanceInterface, WeakComponentInstanceInterface};
18use crate::error::{ErrorReporter, RouteRequestErrorInfo, RoutingError};
19use crate::{DictExt, LazyGet, Sources, WithPorcelain};
20use async_trait::async_trait;
21use cm_rust::{
22    CapabilityTypeName, DictionaryValue, ExposeDecl, ExposeDeclCommon, NativeIntoFidl, OfferDecl,
23    OfferDeclCommon, SourceName, SourcePath, UseDeclCommon,
24};
25use cm_types::{Availability, BorrowedSeparatedPath, IterablePath, Name, SeparatedPath};
26use fidl::endpoints::DiscoverableProtocolMarker;
27use fidl_fuchsia_component as fcomponent;
28use fidl_fuchsia_component_decl as fdecl;
29use fidl_fuchsia_component_internal as finternal;
30use fidl_fuchsia_io as fio;
31use fidl_fuchsia_sys2 as fsys;
32use fuchsia_sync::Mutex;
33use futures::FutureExt;
34use itertools::Itertools;
35use log::warn;
36use moniker::{ChildName, Moniker};
37use router_error::RouterError;
38use sandbox::{
39    Capability, CapabilityBound, Connector, Data, Dict, DirConnector, Request, Routable, Router,
40    RouterResponse, WeakInstanceToken,
41};
42use std::collections::{BTreeMap, HashMap};
43use std::fmt::Debug;
44use std::sync::{Arc, LazyLock};
45
46/// This type comes from `UseEventStreamDecl`.
47pub type EventStreamFilter = Option<BTreeMap<String, DictionaryValue>>;
48
49/// Contains all of the information needed to find and use a source of an event stream.
50#[derive(Clone)]
51pub struct EventStreamSourceRouter {
52    /// The source router that should return a dictionary detailing specifics on the event stream
53    /// such as its type and scope.
54    pub router: Router<Dict>,
55    /// The filter that should be applied on the event stream initialized from the information
56    /// returned by the router.
57    pub filter: EventStreamFilter,
58}
59pub type EventStreamUseRouterFn<C> =
60    dyn Fn(&Arc<C>, Vec<EventStreamSourceRouter>) -> Router<Connector>;
61
62static NAMESPACE: LazyLock<Name> = LazyLock::new(|| "namespace".parse().unwrap());
63static NUMBERED_HANDLES: LazyLock<Name> = LazyLock::new(|| "numbered_handles".parse().unwrap());
64static RUNNER: LazyLock<Name> = LazyLock::new(|| "runner".parse().unwrap());
65static CONFIG: LazyLock<Name> = LazyLock::new(|| "config".parse().unwrap());
66
67/// All capabilities that are available to a component's program.
68#[derive(Debug, Clone)]
69pub struct ProgramInput {
70    // This will always have the following fields:
71    // - namespace: Dict
72    // - runner: Option<Router<Connector>>
73    // - config: Dict
74    // - numbered_handles: Dict
75    inner: Dict,
76}
77
78impl Default for ProgramInput {
79    fn default() -> Self {
80        Self::new(Dict::new(), None, Dict::new())
81    }
82}
83
84impl From<ProgramInput> for Dict {
85    fn from(program_input: ProgramInput) -> Self {
86        program_input.inner
87    }
88}
89
90impl ProgramInput {
91    pub fn new(namespace: Dict, runner: Option<Router<Connector>>, config: Dict) -> Self {
92        let inner = Dict::new();
93        inner.insert(NAMESPACE.clone(), namespace.into()).unwrap();
94        if let Some(runner) = runner {
95            inner.insert(RUNNER.clone(), runner.into()).unwrap();
96        }
97        inner.insert(NUMBERED_HANDLES.clone(), Dict::new().into()).unwrap();
98        inner.insert(CONFIG.clone(), config.into()).unwrap();
99        ProgramInput { inner }
100    }
101
102    /// All of the capabilities that appear in a program's namespace.
103    pub fn namespace(&self) -> Dict {
104        let cap = self.inner.get(&*NAMESPACE).expect("capabilities must be cloneable").unwrap();
105        let Capability::Dictionary(dict) = cap else {
106            unreachable!("namespace entry must be a dict: {cap:?}");
107        };
108        dict
109    }
110
111    /// All of the capabilities that appear in a program's set of numbered handles.
112    pub fn numbered_handles(&self) -> Dict {
113        let cap =
114            self.inner.get(&*NUMBERED_HANDLES).expect("capabilities must be cloneable").unwrap();
115        let Capability::Dictionary(dict) = cap else {
116            unreachable!("numbered_handles entry must be a dict: {cap:?}");
117        };
118        dict
119    }
120
121    /// A router for the runner that a component has used (if any).
122    pub fn runner(&self) -> Option<Router<Connector>> {
123        let cap = self.inner.get(&*RUNNER).expect("capabilities must be cloneable");
124        match cap {
125            None => None,
126            Some(Capability::ConnectorRouter(r)) => Some(r),
127            cap => unreachable!("runner entry must be a router: {cap:?}"),
128        }
129    }
130
131    fn set_runner(&self, capability: Capability) {
132        self.inner.insert(RUNNER.clone(), capability).unwrap()
133    }
134
135    /// All of the config capabilities that a program will use.
136    pub fn config(&self) -> Dict {
137        let cap = self.inner.get(&*CONFIG).expect("capabilities must be cloneable").unwrap();
138        let Capability::Dictionary(dict) = cap else {
139            unreachable!("config entry must be a dict: {cap:?}");
140        };
141        dict
142    }
143}
144
145/// A component's sandbox holds all the routing dictionaries that a component has once its been
146/// resolved.
147#[derive(Debug)]
148pub struct ComponentSandbox {
149    /// The dictionary containing all capabilities that a component's parent provided to it.
150    pub component_input: ComponentInput,
151
152    /// The dictionary containing all capabilities that a component makes available to its parent.
153    pub component_output: ComponentOutput,
154
155    /// The dictionary containing all capabilities that are available to a component's program.
156    pub program_input: ProgramInput,
157
158    /// The dictionary containing all capabilities that a component's program can provide.
159    pub program_output_dict: Dict,
160
161    /// Router that returns the dictionary of framework capabilities scoped to a component. This a
162    /// Router rather than the Dict itself to save memory.
163    ///
164    /// REQUIRES: This Router must never poll. This constraint exists `build_component_sandbox` is
165    /// not async.
166    // NOTE: This is wrapped in Mutex for interior mutability so that it is modifiable like the
167    // other parts of the sandbox. If this were a Dict this wouldn't be necessary because Dict
168    // already supports interior mutability, but since this is a singleton we don't need a Dict
169    // here. The Arc around the Mutex is needed for Sync.
170    framework_router: Mutex<Router<Dict>>,
171
172    /// The dictionary containing all capabilities that a component declares based on another
173    /// capability. Currently this is only the storage admin protocol.
174    pub capability_sourced_capabilities_dict: Dict,
175
176    /// The dictionary containing all dictionaries declared by this component.
177    pub declared_dictionaries: Dict,
178
179    /// This set holds a component input dictionary for each child of a component. Each dictionary
180    /// contains all capabilities the component has made available to a specific collection.
181    pub child_inputs: StructuredDictMap<ComponentInput>,
182
183    /// This set holds a component input dictionary for each collection declared by a component.
184    /// Each dictionary contains all capabilities the component has made available to a specific
185    /// collection.
186    pub collection_inputs: StructuredDictMap<ComponentInput>,
187}
188
189impl Default for ComponentSandbox {
190    fn default() -> Self {
191        static NULL_ROUTER: LazyLock<Router<Dict>> = LazyLock::new(|| Router::new(NullRouter {}));
192        struct NullRouter;
193        #[async_trait]
194        impl Routable<Dict> for NullRouter {
195            async fn route(
196                &self,
197                _request: Option<Request>,
198                _debug: bool,
199                _target: WeakInstanceToken,
200            ) -> Result<RouterResponse<Dict>, RouterError> {
201                panic!("null router invoked");
202            }
203        }
204        let framework_router = Mutex::new(NULL_ROUTER.clone());
205        Self {
206            framework_router,
207            component_input: Default::default(),
208            component_output: Default::default(),
209            program_input: Default::default(),
210            program_output_dict: Default::default(),
211            capability_sourced_capabilities_dict: Default::default(),
212            declared_dictionaries: Default::default(),
213            child_inputs: Default::default(),
214            collection_inputs: Default::default(),
215        }
216    }
217}
218
219impl From<ComponentSandbox> for Dict {
220    fn from(sandbox: ComponentSandbox) -> Dict {
221        let sandbox_dictionary = Dict::new();
222        sandbox_dictionary
223            .insert(Name::new("framework").unwrap(), sandbox.framework_router.lock().clone().into())
224            .unwrap();
225        sandbox_dictionary
226            .insert(
227                Name::new("component_input").unwrap(),
228                Capability::Dictionary(sandbox.component_input.into()),
229            )
230            .unwrap();
231        sandbox_dictionary
232            .insert(
233                Name::new("component_output").unwrap(),
234                Capability::Dictionary(sandbox.component_output.into()),
235            )
236            .unwrap();
237        sandbox_dictionary
238            .insert(
239                Name::new("program_input").unwrap(),
240                Capability::Dictionary(sandbox.program_input.into()),
241            )
242            .unwrap();
243        sandbox_dictionary
244            .insert(Name::new("program_output").unwrap(), sandbox.program_output_dict.into())
245            .unwrap();
246        sandbox_dictionary
247            .insert(
248                Name::new("capability_sourced").unwrap(),
249                sandbox.capability_sourced_capabilities_dict.into(),
250            )
251            .unwrap();
252        sandbox_dictionary
253            .insert(
254                Name::new("declared_dictionaries").unwrap(),
255                sandbox.declared_dictionaries.into(),
256            )
257            .unwrap();
258        sandbox_dictionary
259            .insert(
260                Name::new("child_inputs").unwrap(),
261                Capability::Dictionary(sandbox.child_inputs.into()),
262            )
263            .unwrap();
264        sandbox_dictionary
265            .insert(
266                Name::new("collection_inputs").unwrap(),
267                Capability::Dictionary(sandbox.collection_inputs.into()),
268            )
269            .unwrap();
270        sandbox_dictionary
271    }
272}
273
274impl Clone for ComponentSandbox {
275    fn clone(&self) -> Self {
276        let Self {
277            component_input,
278            component_output,
279            program_input,
280            program_output_dict,
281            framework_router,
282            capability_sourced_capabilities_dict,
283            declared_dictionaries,
284            child_inputs,
285            collection_inputs,
286        } = self;
287        Self {
288            component_input: component_input.clone(),
289            component_output: component_output.clone(),
290            program_input: program_input.clone(),
291            program_output_dict: program_output_dict.clone(),
292            framework_router: Mutex::new(framework_router.lock().clone()),
293            capability_sourced_capabilities_dict: capability_sourced_capabilities_dict.clone(),
294            declared_dictionaries: declared_dictionaries.clone(),
295            child_inputs: child_inputs.clone(),
296            collection_inputs: collection_inputs.clone(),
297        }
298    }
299}
300
301impl ComponentSandbox {
302    /// Copies all of the entries from the given sandbox into this one. Panics if the given sandbox
303    /// is holding any entries that cannot be copied. Panics if there are any duplicate entries.
304    pub fn append(&self, sandbox: &ComponentSandbox) {
305        // We destructure the sandbox here to ensure that this code is updated if the contents of
306        // the sandbox change.
307        let ComponentSandbox {
308            component_input,
309            component_output,
310            program_input,
311            program_output_dict,
312            framework_router,
313            capability_sourced_capabilities_dict,
314            declared_dictionaries,
315            child_inputs,
316            collection_inputs,
317        } = sandbox;
318        for (copy_from, copy_to) in [
319            (&component_input.capabilities(), &self.component_input.capabilities()),
320            (&component_input.environment().debug(), &self.component_input.environment().debug()),
321            (
322                &component_input.environment().runners(),
323                &self.component_input.environment().runners(),
324            ),
325            (
326                &component_input.environment().resolvers(),
327                &self.component_input.environment().resolvers(),
328            ),
329            (&component_output.capabilities(), &self.component_output.capabilities()),
330            (&component_output.framework(), &self.component_output.framework()),
331            (&program_input.namespace(), &self.program_input.namespace()),
332            (&program_input.numbered_handles(), &self.program_input.numbered_handles()),
333            (&program_input.config(), &self.program_input.config()),
334            (&program_output_dict, &self.program_output_dict),
335            (&capability_sourced_capabilities_dict, &self.capability_sourced_capabilities_dict),
336            (&declared_dictionaries, &self.declared_dictionaries),
337        ] {
338            copy_to.append(copy_from).expect("sandbox capability is not cloneable");
339        }
340        if let Some(timeout) = component_input.environment().stop_timeout() {
341            self.component_input.environment().set_stop_timeout(timeout as i64);
342        }
343        *self.framework_router.lock() = framework_router.lock().clone();
344        if let Some(runner_router) = program_input.runner() {
345            self.program_input.set_runner(runner_router.into());
346        }
347        self.child_inputs.append(child_inputs).unwrap();
348        self.collection_inputs.append(collection_inputs).unwrap();
349    }
350
351    pub fn framework_router(&self) -> Router<Dict> {
352        self.framework_router.lock().clone()
353    }
354}
355
356/// Once a component has been resolved and its manifest becomes known, this function produces the
357/// various dicts the component needs based on the contents of its manifest.
358pub fn build_component_sandbox<C: ComponentInstanceInterface + 'static>(
359    component: &Arc<C>,
360    child_component_output_dictionary_routers: HashMap<ChildName, Router<Dict>>,
361    decl: &cm_rust::ComponentDecl,
362    component_input: ComponentInput,
363    program_output_dict: Dict,
364    framework_router: Router<Dict>,
365    capability_sourced_capabilities_dict: Dict,
366    declared_dictionaries: Dict,
367    error_reporter: impl ErrorReporter,
368    aggregate_router_fn: &AggregateRouterFn<C>,
369    event_stream_use_router_fn: &EventStreamUseRouterFn<C>,
370) -> ComponentSandbox {
371    let component_output = ComponentOutput::new();
372    let program_input = ProgramInput::default();
373    let environments: StructuredDictMap<ComponentEnvironment> = Default::default();
374    let child_inputs: StructuredDictMap<ComponentInput> = Default::default();
375    let collection_inputs: StructuredDictMap<ComponentInput> = Default::default();
376
377    for environment_decl in &decl.environments {
378        environments
379            .insert(
380                environment_decl.name.clone(),
381                build_environment(
382                    component,
383                    &child_component_output_dictionary_routers,
384                    &component_input,
385                    environment_decl,
386                    &program_output_dict,
387                    &error_reporter,
388                ),
389            )
390            .ok();
391    }
392
393    for child in &decl.children {
394        let environment;
395        if let Some(environment_name) = child.environment.as_ref() {
396            environment = environments.get(environment_name).expect(
397                "child references nonexistent environment, \
398                    this should be prevented in manifest validation",
399            );
400        } else {
401            environment = component_input.environment();
402        }
403        let input = ComponentInput::new(environment);
404        let name = Name::new(child.name.as_str()).expect("child is static so name is not long");
405        child_inputs.insert(name, input).ok();
406    }
407
408    for collection in &decl.collections {
409        let environment;
410        if let Some(environment_name) = collection.environment.as_ref() {
411            environment = environments.get(environment_name).expect(
412                "collection references nonexistent environment, \
413                    this should be prevented in manifest validation",
414            )
415        } else {
416            environment = component_input.environment();
417        }
418        let input = ComponentInput::new(environment);
419        collection_inputs.insert(collection.name.clone(), input).ok();
420    }
421
422    let mut dictionary_use_bundles = Vec::with_capacity(decl.uses.len());
423    for use_bundle in group_use_aggregates(&decl.uses).into_iter() {
424        let first_use = *use_bundle.first().unwrap();
425        match first_use {
426            cm_rust::UseDecl::Service(_)
427                if matches!(first_use.source(), cm_rust::UseSource::Collection(_)) =>
428            {
429                let cm_rust::UseSource::Collection(collection_name) = first_use.source() else {
430                    unreachable!();
431                };
432                let availability = *first_use.availability();
433                let aggregate = (aggregate_router_fn)(
434                    component.clone(),
435                    vec![AggregateSource::Collection { collection_name: collection_name.clone() }],
436                    CapabilitySource::AnonymizedAggregate(AnonymizedAggregateSource {
437                        capability: AggregateCapability::Service(first_use.source_name().clone()),
438                        moniker: component.moniker().clone(),
439                        members: vec![AggregateMember::try_from(first_use).unwrap()],
440                        sources: Sources::new(cm_rust::CapabilityTypeName::Service),
441                        instances: vec![],
442                    }),
443                )
444                .with_porcelain_with_default(CapabilityTypeName::Service)
445                .availability(availability)
446                .target(component)
447                .error_info(first_use)
448                .error_reporter(error_reporter.clone())
449                .build();
450                if let Err(e) = program_input
451                    .namespace()
452                    .insert_capability(first_use.path().unwrap(), aggregate.into())
453                {
454                    warn!(
455                        "failed to insert {} in program input dict: {e:?}",
456                        first_use.path().unwrap()
457                    )
458                }
459            }
460            cm_rust::UseDecl::Service(_) => extend_dict_with_use::<DirConnector, _>(
461                component,
462                &child_component_output_dictionary_routers,
463                &component_input,
464                &program_input,
465                &program_output_dict,
466                &framework_router,
467                &capability_sourced_capabilities_dict,
468                first_use,
469                error_reporter.clone(),
470            ),
471            cm_rust::UseDecl::Directory(_) | cm_rust::UseDecl::Storage(_) => {
472                extend_dict_with_use::<DirConnector, C>(
473                    component,
474                    &child_component_output_dictionary_routers,
475                    &component_input,
476                    &program_input,
477                    &program_output_dict,
478                    &framework_router,
479                    &capability_sourced_capabilities_dict,
480                    first_use,
481                    error_reporter.clone(),
482                )
483            }
484            cm_rust::UseDecl::Protocol(_) | cm_rust::UseDecl::Runner(_) => {
485                extend_dict_with_use::<Connector, _>(
486                    component,
487                    &child_component_output_dictionary_routers,
488                    &component_input,
489                    &program_input,
490                    &program_output_dict,
491                    &framework_router,
492                    &capability_sourced_capabilities_dict,
493                    first_use,
494                    error_reporter.clone(),
495                )
496            }
497            cm_rust::UseDecl::Config(config) => extend_dict_with_config_use(
498                component,
499                &child_component_output_dictionary_routers,
500                &component_input,
501                &program_input,
502                &program_output_dict,
503                config,
504                error_reporter.clone(),
505            ),
506            cm_rust::UseDecl::EventStream(_) => extend_dict_with_event_stream_uses(
507                component,
508                &component_input,
509                &program_input,
510                use_bundle,
511                error_reporter.clone(),
512                event_stream_use_router_fn,
513            ),
514            cm_rust::UseDecl::Dictionary(_) => {
515                dictionary_use_bundles.push(use_bundle);
516            }
517        }
518    }
519
520    // The runner may be specified by either use declaration or in the program section of the
521    // manifest. If there's no use declaration for a runner and there is one set in the program
522    // section, then let's synthesize a use decl for it and add it to the sandbox.
523    if !decl.uses.iter().any(|u| matches!(u, cm_rust::UseDecl::Runner(_))) {
524        if let Some(runner_name) = decl.program.as_ref().and_then(|p| p.runner.as_ref()) {
525            extend_dict_with_use::<Connector, _>(
526                component,
527                &child_component_output_dictionary_routers,
528                &component_input,
529                &program_input,
530                &program_output_dict,
531                &framework_router,
532                &capability_sourced_capabilities_dict,
533                &cm_rust::UseDecl::Runner(cm_rust::UseRunnerDecl {
534                    source: cm_rust::UseSource::Environment,
535                    source_name: runner_name.clone(),
536                    source_dictionary: Default::default(),
537                }),
538                error_reporter.clone(),
539            )
540        }
541    }
542
543    // Dictionary uses are special: if any capabilities are used at a path that's a prefix of a
544    // dictionary use, then those capabilities are transparently added to the dictionary we
545    // assemble in the program input dictionary. In order to do this correctly, we want the program
546    // input dictionary to be complete (aside from used dictionaries) so that the dictionaries
547    // we're merging with the used dictionaries aren't missing entries. For this reason, we wait
548    // until after all other uses are processed before processing used dictionaries.
549    for dictionary_use_bundle in dictionary_use_bundles {
550        extend_dict_with_dictionary_use(
551            component,
552            &child_component_output_dictionary_routers,
553            &component_input,
554            &program_input,
555            &program_output_dict,
556            &framework_router,
557            &capability_sourced_capabilities_dict,
558            dictionary_use_bundle,
559            error_reporter.clone(),
560        )
561    }
562
563    for offer_bundle in group_offer_aggregates(&decl.offers) {
564        let first_offer = offer_bundle.first().unwrap();
565        let get_target_dict = || match first_offer.target() {
566            cm_rust::OfferTarget::Child(child_ref) => {
567                assert!(child_ref.collection.is_none(), "unexpected dynamic offer target");
568                let child_name = Name::new(child_ref.name.as_str())
569                    .expect("child is static so name is not long");
570                if child_inputs.get(&child_name).is_none() {
571                    child_inputs.insert(child_name.clone(), Default::default()).ok();
572                }
573                child_inputs
574                    .get(&child_name)
575                    .expect("component input was just added")
576                    .capabilities()
577            }
578            cm_rust::OfferTarget::Collection(name) => {
579                if collection_inputs.get(&name).is_none() {
580                    collection_inputs.insert(name.clone(), Default::default()).ok();
581                }
582                collection_inputs
583                    .get(&name)
584                    .expect("collection input was just added")
585                    .capabilities()
586            }
587            cm_rust::OfferTarget::Capability(name) => {
588                let dict = match declared_dictionaries
589                    .get(name)
590                    .expect("dictionaries must be cloneable")
591                {
592                    Some(dict) => dict,
593                    None => {
594                        let dict = Dict::new();
595                        declared_dictionaries
596                            .insert(name.clone(), Capability::Dictionary(dict.clone()))
597                            .ok();
598                        Capability::Dictionary(dict)
599                    }
600                };
601                let Capability::Dictionary(dict) = dict else {
602                    panic!("wrong type in dict");
603                };
604                dict
605            }
606        };
607        match first_offer {
608            cm_rust::OfferDecl::Service(_)
609                if offer_bundle.len() == 1
610                    && !matches!(first_offer.source(), cm_rust::OfferSource::Collection(_)) =>
611            {
612                extend_dict_with_offer::<DirConnector, _>(
613                    component,
614                    &child_component_output_dictionary_routers,
615                    &component_input,
616                    &program_output_dict,
617                    &framework_router,
618                    &capability_sourced_capabilities_dict,
619                    first_offer,
620                    &(get_target_dict)(),
621                    error_reporter.clone(),
622                )
623            }
624            cm_rust::OfferDecl::Service(_) => {
625                let aggregate_router = new_aggregate_router_from_service_offers(
626                    &offer_bundle,
627                    component,
628                    &child_component_output_dictionary_routers,
629                    &component_input,
630                    &program_output_dict,
631                    &framework_router,
632                    &capability_sourced_capabilities_dict,
633                    error_reporter.clone(),
634                    aggregate_router_fn,
635                );
636                (get_target_dict)()
637                    .insert(first_offer.target_name().clone(), aggregate_router.into())
638                    .expect("failed to insert capability into target dict");
639            }
640            cm_rust::OfferDecl::Config(_) => extend_dict_with_offer::<Data, _>(
641                component,
642                &child_component_output_dictionary_routers,
643                &component_input,
644                &program_output_dict,
645                &framework_router,
646                &capability_sourced_capabilities_dict,
647                first_offer,
648                &(get_target_dict)(),
649                error_reporter.clone(),
650            ),
651            cm_rust::OfferDecl::Directory(_) | cm_rust::OfferDecl::Storage(_) => {
652                extend_dict_with_offer::<DirConnector, _>(
653                    component,
654                    &child_component_output_dictionary_routers,
655                    &component_input,
656                    &program_output_dict,
657                    &framework_router,
658                    &capability_sourced_capabilities_dict,
659                    first_offer,
660                    &(get_target_dict)(),
661                    error_reporter.clone(),
662                )
663            }
664            cm_rust::OfferDecl::Dictionary(_) | cm_rust::OfferDecl::EventStream(_) => {
665                extend_dict_with_offer::<Dict, _>(
666                    component,
667                    &child_component_output_dictionary_routers,
668                    &component_input,
669                    &program_output_dict,
670                    &framework_router,
671                    &capability_sourced_capabilities_dict,
672                    first_offer,
673                    &(get_target_dict)(),
674                    error_reporter.clone(),
675                )
676            }
677            cm_rust::OfferDecl::Protocol(_)
678            | cm_rust::OfferDecl::Runner(_)
679            | cm_rust::OfferDecl::Resolver(_) => extend_dict_with_offer::<Connector, _>(
680                component,
681                &child_component_output_dictionary_routers,
682                &component_input,
683                &program_output_dict,
684                &framework_router,
685                &capability_sourced_capabilities_dict,
686                first_offer,
687                &(get_target_dict)(),
688                error_reporter.clone(),
689            ),
690        }
691    }
692
693    for expose_bundle in group_expose_aggregates(&decl.exposes) {
694        let first_expose = expose_bundle.first().unwrap();
695        match first_expose {
696            cm_rust::ExposeDecl::Service(_)
697                if expose_bundle.len() == 1
698                    && !matches!(first_expose.source(), cm_rust::ExposeSource::Collection(_)) =>
699            {
700                extend_dict_with_expose::<DirConnector, _>(
701                    component,
702                    &child_component_output_dictionary_routers,
703                    &program_output_dict,
704                    &framework_router,
705                    &capability_sourced_capabilities_dict,
706                    first_expose,
707                    &component_output,
708                    error_reporter.clone(),
709                )
710            }
711            cm_rust::ExposeDecl::Service(_) => {
712                let mut aggregate_sources = vec![];
713                let temp_component_output = ComponentOutput::new();
714                for expose in expose_bundle.iter() {
715                    extend_dict_with_expose::<DirConnector, _>(
716                        component,
717                        &child_component_output_dictionary_routers,
718                        &program_output_dict,
719                        &framework_router,
720                        &capability_sourced_capabilities_dict,
721                        expose,
722                        &temp_component_output,
723                        error_reporter.clone(),
724                    );
725                    match temp_component_output.capabilities().remove(first_expose.target_name()) {
726                        Some(Capability::DirConnectorRouter(router)) => {
727                            let source_instance = match expose.source() {
728                                cm_rust::ExposeSource::Self_ => AggregateInstance::Self_,
729                                cm_rust::ExposeSource::Child(name) => AggregateInstance::Child(
730                                    moniker::ChildName::new(name.clone().to_long(), None),
731                                ),
732                                other_source => {
733                                    warn!(
734                                        "unsupported source found in expose aggregate: {:?}",
735                                        other_source
736                                    );
737                                    continue;
738                                }
739                            };
740                            aggregate_sources
741                                .push(AggregateSource::DirectoryRouter { source_instance, router })
742                        }
743                        None => match expose.source() {
744                            cm_rust::ExposeSource::Collection(collection_name) => {
745                                aggregate_sources.push(AggregateSource::Collection {
746                                    collection_name: collection_name.clone(),
747                                });
748                            }
749                            _ => continue,
750                        },
751                        other_value => panic!("unexpected dictionary entry: {:?}", other_value),
752                    }
753                }
754                let availability = *first_expose.availability();
755                let aggregate = (aggregate_router_fn)(
756                    component.clone(),
757                    aggregate_sources,
758                    CapabilitySource::AnonymizedAggregate(AnonymizedAggregateSource {
759                        capability: AggregateCapability::Service(
760                            first_expose.target_name().clone(),
761                        ),
762                        moniker: component.moniker().clone(),
763                        members: expose_bundle
764                            .iter()
765                            .filter_map(|e| AggregateMember::try_from(*e).ok())
766                            .collect(),
767                        sources: Sources::new(cm_rust::CapabilityTypeName::Service),
768                        instances: vec![],
769                    }),
770                );
771                let router = aggregate
772                    .with_porcelain_with_default(CapabilityTypeName::Service)
773                    .availability(availability)
774                    .target(component)
775                    .error_info(*first_expose)
776                    .error_reporter(error_reporter.clone())
777                    .build();
778                component_output
779                    .capabilities()
780                    .insert(first_expose.target_name().clone(), router.into())
781                    .expect("failed to insert capability into target dict")
782            }
783            cm_rust::ExposeDecl::Config(_) => extend_dict_with_expose::<Data, _>(
784                component,
785                &child_component_output_dictionary_routers,
786                &program_output_dict,
787                &framework_router,
788                &capability_sourced_capabilities_dict,
789                first_expose,
790                &component_output,
791                error_reporter.clone(),
792            ),
793            cm_rust::ExposeDecl::Dictionary(_) => extend_dict_with_expose::<Dict, _>(
794                component,
795                &child_component_output_dictionary_routers,
796                &program_output_dict,
797                &framework_router,
798                &capability_sourced_capabilities_dict,
799                first_expose,
800                &component_output,
801                error_reporter.clone(),
802            ),
803            cm_rust::ExposeDecl::Directory(_) => extend_dict_with_expose::<DirConnector, _>(
804                component,
805                &child_component_output_dictionary_routers,
806                &program_output_dict,
807                &framework_router,
808                &capability_sourced_capabilities_dict,
809                first_expose,
810                &component_output,
811                error_reporter.clone(),
812            ),
813            cm_rust::ExposeDecl::Protocol(_)
814            | cm_rust::ExposeDecl::Runner(_)
815            | cm_rust::ExposeDecl::Resolver(_) => extend_dict_with_expose::<Connector, _>(
816                component,
817                &child_component_output_dictionary_routers,
818                &program_output_dict,
819                &framework_router,
820                &capability_sourced_capabilities_dict,
821                first_expose,
822                &component_output,
823                error_reporter.clone(),
824            ),
825        }
826    }
827
828    ComponentSandbox {
829        component_input,
830        component_output,
831        program_input,
832        program_output_dict,
833        framework_router: Mutex::new(framework_router),
834        capability_sourced_capabilities_dict,
835        declared_dictionaries,
836        child_inputs,
837        collection_inputs,
838    }
839}
840
841fn new_aggregate_router_from_service_offers<C: ComponentInstanceInterface + 'static>(
842    offer_bundle: &Vec<&cm_rust::OfferDecl>,
843    component: &Arc<C>,
844    child_component_output_dictionary_routers: &HashMap<ChildName, Router<Dict>>,
845    component_input: &ComponentInput,
846    program_output_dict: &Dict,
847    framework_router: &Router<Dict>,
848    capability_sourced_capabilities_dict: &Dict,
849    error_reporter: impl ErrorReporter,
850    aggregate_router_fn: &AggregateRouterFn<C>,
851) -> Router<DirConnector> {
852    let mut aggregate_sources = vec![];
853    let dict_for_source_router = Dict::new();
854    let source = new_aggregate_capability_source(component.moniker().clone(), offer_bundle.clone());
855    for offer in offer_bundle.iter() {
856        if matches!(&source, &CapabilitySource::FilteredAggregateProvider(_)) {
857            if let cm_rust::OfferDecl::Service(offer_service_decl) = offer {
858                if offer_service_decl
859                    .source_instance_filter
860                    .as_ref()
861                    .and_then(|v| v.first())
862                    .is_none()
863                    && offer_service_decl
864                        .renamed_instances
865                        .as_ref()
866                        .and_then(|v| v.first())
867                        .is_none()
868                {
869                    // If we're a filtering aggregate and no filter or renames have been
870                    // set, then all instances here are ignored, and there's no point in
871                    // including the router in the aggregate.
872                    continue;
873                }
874            }
875        }
876        extend_dict_with_offer::<DirConnector, _>(
877            component,
878            &child_component_output_dictionary_routers,
879            &component_input,
880            &program_output_dict,
881            framework_router,
882            &capability_sourced_capabilities_dict,
883            offer,
884            &dict_for_source_router,
885            error_reporter.clone(),
886        );
887        match dict_for_source_router.remove(offer.target_name()) {
888            Some(Capability::DirConnectorRouter(router)) => {
889                let source_instance = match offer.source() {
890                    cm_rust::OfferSource::Self_ => AggregateInstance::Self_,
891                    cm_rust::OfferSource::Parent => AggregateInstance::Parent,
892                    cm_rust::OfferSource::Child(child_ref) => {
893                        AggregateInstance::Child(moniker::ChildName::new(
894                            child_ref.name.clone(),
895                            child_ref.collection.clone(),
896                        ))
897                    }
898                    other_source => {
899                        warn!("unsupported source found in offer aggregate: {:?}", other_source);
900                        continue;
901                    }
902                };
903                aggregate_sources.push(AggregateSource::DirectoryRouter { source_instance, router })
904            }
905            None => match offer.source() {
906                // `extend_dict_with_offer` doesn't insert a capability for offers with a source of
907                // `OfferSource::Collection`. This is because at this stage there's nothing in the
908                // collection, and thus no routers to things in the collection.
909                cm_rust::OfferSource::Collection(collection_name) => {
910                    aggregate_sources.push(AggregateSource::Collection {
911                        collection_name: collection_name.clone(),
912                    });
913                }
914                _ => continue,
915            },
916            other => warn!("found unexpected entry in dictionary: {:?}", other),
917        }
918    }
919    (aggregate_router_fn)(component.clone(), aggregate_sources, source)
920}
921
922fn new_aggregate_capability_source(
923    moniker: Moniker,
924    offers: Vec<&cm_rust::OfferDecl>,
925) -> CapabilitySource {
926    let offer_service_decls = offers
927        .iter()
928        .map(|o| match o {
929            cm_rust::OfferDecl::Service(o) => o,
930            _ => panic!(
931                "cannot aggregate non-service capabilities, manifest validation should prevent this"
932            ),
933        })
934        .collect::<Vec<_>>();
935    // This is a filtered offer if any of the offers set a filter or rename mapping.
936    let is_filtered_offer = offer_service_decls.iter().any(|o| {
937        o.source_instance_filter.as_ref().map(|v| !v.is_empty()).unwrap_or(false)
938            || o.renamed_instances.as_ref().map(|v| !v.is_empty()).unwrap_or(false)
939    });
940    let capability =
941        AggregateCapability::Service(offer_service_decls.first().unwrap().target_name.clone());
942    if is_filtered_offer {
943        CapabilitySource::FilteredAggregateProvider(FilteredAggregateProviderSource {
944            capability,
945            moniker,
946            offer_service_decls: offer_service_decls.into_iter().cloned().collect(),
947            sources: Sources::new(cm_rust::CapabilityTypeName::Service).component().collection(),
948        })
949    } else {
950        let members = offers.iter().filter_map(|o| AggregateMember::try_from(*o).ok()).collect();
951        CapabilitySource::AnonymizedAggregate(AnonymizedAggregateSource {
952            capability,
953            moniker,
954            members,
955            sources: Sources::new(cm_rust::CapabilityTypeName::Service).component().collection(),
956            instances: vec![],
957        })
958    }
959}
960
961/// Groups together a set of offers into sub-sets of those that have the same target and target
962/// name. This is useful for identifying which offers are part of an aggregation of capabilities,
963/// and which are for standalone routes.
964fn group_use_aggregates<'a>(
965    uses: &'a [cm_rust::UseDecl],
966) -> impl Iterator<Item = Vec<&'a cm_rust::UseDecl>> + 'a {
967    let mut groupings = HashMap::with_capacity(uses.len());
968    let mut ungroupable_uses = Vec::new();
969    for use_ in uses.iter() {
970        if let Some(target_path) = use_.path() {
971            groupings.entry(target_path).or_insert_with(|| Vec::with_capacity(1)).push(use_);
972        } else {
973            ungroupable_uses.push(use_);
974        }
975    }
976    groupings.into_values().chain(ungroupable_uses.into_iter().map(|u| vec![u]))
977}
978
979/// Groups together a set of offers into sub-sets of those that have the same target and target
980/// name. This is useful for identifying which offers are part of an aggregation of capabilities,
981/// and which are for standalone routes.
982fn group_offer_aggregates<'a>(
983    offers: &'a [cm_rust::OfferDecl],
984) -> impl Iterator<Item = Vec<&'a cm_rust::OfferDecl>> + 'a {
985    let mut groupings = HashMap::with_capacity(offers.len());
986
987    for offer in offers {
988        groupings
989            .entry((offer.target(), offer.target_name()))
990            .or_insert_with(|| Vec::with_capacity(1))
991            .push(offer);
992    }
993    groupings.into_values()
994}
995
996/// Identical to `group_offer_aggregates`, but for exposes.
997fn group_expose_aggregates<'a>(
998    exposes: &'a [cm_rust::ExposeDecl],
999) -> impl Iterator<Item = Vec<&'a cm_rust::ExposeDecl>> + 'a {
1000    let mut groupings = HashMap::with_capacity(exposes.len());
1001    for expose in exposes {
1002        groupings
1003            .entry((expose.target(), expose.target_name()))
1004            .or_insert_with(|| Vec::with_capacity(1))
1005            .push(expose);
1006    }
1007    groupings.into_values()
1008}
1009
1010fn build_environment<C: ComponentInstanceInterface + 'static>(
1011    component: &Arc<C>,
1012    child_component_output_dictionary_routers: &HashMap<ChildName, Router<Dict>>,
1013    component_input: &ComponentInput,
1014    environment_decl: &cm_rust::EnvironmentDecl,
1015    program_output_dict: &Dict,
1016    error_reporter: &impl ErrorReporter,
1017) -> ComponentEnvironment {
1018    let mut environment = ComponentEnvironment::new();
1019    if environment_decl.extends == fdecl::EnvironmentExtends::Realm {
1020        if let Ok(e) = component_input.environment().shallow_copy() {
1021            environment = e;
1022        } else {
1023            warn!("failed to copy component_input.environment");
1024        }
1025    }
1026    environment.set_name(&environment_decl.name);
1027    if let Some(stop_timeout_ms) = environment_decl.stop_timeout_ms {
1028        environment.set_stop_timeout(stop_timeout_ms as i64);
1029    }
1030    let debug = environment_decl.debug_capabilities.iter().map(|debug_registration| {
1031        let cm_rust::DebugRegistration::Protocol(debug) = debug_registration;
1032        (
1033            &debug.source_name,
1034            debug.target_name.clone(),
1035            &debug.source,
1036            CapabilityTypeName::Protocol,
1037            RouteRequestErrorInfo::from(debug_registration),
1038        )
1039    });
1040    let runners = environment_decl.runners.iter().map(|runner| {
1041        (
1042            &runner.source_name,
1043            runner.target_name.clone(),
1044            &runner.source,
1045            CapabilityTypeName::Runner,
1046            RouteRequestErrorInfo::from(runner),
1047        )
1048    });
1049    let resolvers = environment_decl.resolvers.iter().map(|resolver| {
1050        (
1051            &resolver.resolver,
1052            Name::new(&resolver.scheme).unwrap(),
1053            &resolver.source,
1054            CapabilityTypeName::Resolver,
1055            RouteRequestErrorInfo::from(resolver),
1056        )
1057    });
1058    let moniker = component.moniker();
1059    for (source_name, target_name, source, porcelain_type, route_request) in
1060        debug.chain(runners).chain(resolvers)
1061    {
1062        let source_path =
1063            SeparatedPath { dirname: Default::default(), basename: source_name.clone() };
1064        let router: Router<Connector> = match &source {
1065            cm_rust::RegistrationSource::Parent => {
1066                use_from_parent_router::<Connector>(component_input, source_path, moniker)
1067            }
1068            cm_rust::RegistrationSource::Self_ => program_output_dict
1069                .get_router_or_not_found::<Connector>(
1070                    &source_path,
1071                    RoutingError::use_from_self_not_found(
1072                        moniker,
1073                        source_path.iter_segments().join("/"),
1074                    ),
1075                ),
1076            cm_rust::RegistrationSource::Child(child_name) => {
1077                let child_name = ChildName::parse(child_name).expect("invalid child name");
1078                let Some(child_component_output) =
1079                    child_component_output_dictionary_routers.get(&child_name)
1080                else {
1081                    continue;
1082                };
1083                child_component_output.clone().lazy_get(
1084                    source_path,
1085                    RoutingError::use_from_child_expose_not_found(
1086                        &child_name,
1087                        moniker,
1088                        source_name.clone(),
1089                    ),
1090                )
1091            }
1092        };
1093        let router = router
1094            .with_porcelain_no_default(porcelain_type)
1095            .availability(Availability::Required)
1096            .target(component)
1097            .error_info(route_request)
1098            .error_reporter(error_reporter.clone());
1099        let dict_to_insert_to = match porcelain_type {
1100            CapabilityTypeName::Protocol => environment.debug(),
1101            CapabilityTypeName::Runner => environment.runners(),
1102            CapabilityTypeName::Resolver => environment.resolvers(),
1103            c => panic!("unexpected capability type {}", c),
1104        };
1105        match dict_to_insert_to.insert_capability(&target_name, router.into()) {
1106            Ok(()) => (),
1107            Err(_e) => {
1108                // The only reason this will happen is if we're shadowing something else in the
1109                // environment. `insert_capability` will still insert the new capability when it
1110                // returns an error, so we can safely ignore this.
1111            }
1112        }
1113    }
1114    environment
1115}
1116
1117/// Extends the given `target_input` to contain the capabilities described in `dynamic_offers`.
1118pub fn extend_dict_with_offers<C: ComponentInstanceInterface + 'static>(
1119    component: &Arc<C>,
1120    static_offers: &[cm_rust::OfferDecl],
1121    child_component_output_dictionary_routers: &HashMap<ChildName, Router<Dict>>,
1122    component_input: &ComponentInput,
1123    dynamic_offers: &[cm_rust::OfferDecl],
1124    program_output_dict: &Dict,
1125    framework_router: &Router<Dict>,
1126    capability_sourced_capabilities_dict: &Dict,
1127    target_input: &ComponentInput,
1128    error_reporter: impl ErrorReporter,
1129    aggregate_router_fn: &AggregateRouterFn<C>,
1130) {
1131    for offer_bundle in group_offer_aggregates(dynamic_offers).into_iter() {
1132        let first_offer = offer_bundle.first().unwrap();
1133        match first_offer {
1134            cm_rust::OfferDecl::Service(_) => {
1135                let static_offer_bundles = group_offer_aggregates(static_offers);
1136                let maybe_static_offer_bundle = static_offer_bundles.into_iter().find(|bundle| {
1137                    bundle.first().unwrap().target_name() == first_offer.target_name()
1138                });
1139                let mut combined_offer_bundle = offer_bundle.clone();
1140                if let Some(mut static_offer_bundle) = maybe_static_offer_bundle {
1141                    // We are aggregating together dynamic and static offers, as there are static
1142                    // offers with the same target name as our current dynamic offers. We already
1143                    // populated a router for the static bundle in the target input, let's toss
1144                    // that and generate a new one with the expanded set of offers.
1145                    let _ = target_input.capabilities().remove(first_offer.target_name());
1146                    combined_offer_bundle.append(&mut static_offer_bundle);
1147                }
1148                if combined_offer_bundle.len() == 1
1149                    && !matches!(first_offer.source(), cm_rust::OfferSource::Collection(_))
1150                {
1151                    extend_dict_with_offer::<DirConnector, _>(
1152                        component,
1153                        &child_component_output_dictionary_routers,
1154                        &component_input,
1155                        &program_output_dict,
1156                        framework_router,
1157                        &capability_sourced_capabilities_dict,
1158                        first_offer,
1159                        &target_input.capabilities(),
1160                        error_reporter.clone(),
1161                    )
1162                } else {
1163                    let aggregate_router = new_aggregate_router_from_service_offers(
1164                        &combined_offer_bundle,
1165                        component,
1166                        &child_component_output_dictionary_routers,
1167                        &component_input,
1168                        &program_output_dict,
1169                        framework_router,
1170                        &capability_sourced_capabilities_dict,
1171                        error_reporter.clone(),
1172                        aggregate_router_fn,
1173                    );
1174                    target_input
1175                        .capabilities()
1176                        .insert(first_offer.target_name().clone(), aggregate_router.into())
1177                        .expect("failed to insert capability into target dict");
1178                }
1179            }
1180            cm_rust::OfferDecl::Config(_) => extend_dict_with_offer::<Data, _>(
1181                component,
1182                &child_component_output_dictionary_routers,
1183                component_input,
1184                program_output_dict,
1185                framework_router,
1186                capability_sourced_capabilities_dict,
1187                first_offer,
1188                &target_input.capabilities(),
1189                error_reporter.clone(),
1190            ),
1191            cm_rust::OfferDecl::Dictionary(_) => extend_dict_with_offer::<Dict, _>(
1192                component,
1193                &child_component_output_dictionary_routers,
1194                component_input,
1195                program_output_dict,
1196                framework_router,
1197                capability_sourced_capabilities_dict,
1198                first_offer,
1199                &target_input.capabilities(),
1200                error_reporter.clone(),
1201            ),
1202            cm_rust::OfferDecl::Directory(_) | cm_rust::OfferDecl::Storage(_) => {
1203                extend_dict_with_offer::<DirConnector, _>(
1204                    component,
1205                    &child_component_output_dictionary_routers,
1206                    component_input,
1207                    program_output_dict,
1208                    framework_router,
1209                    capability_sourced_capabilities_dict,
1210                    first_offer,
1211                    &target_input.capabilities(),
1212                    error_reporter.clone(),
1213                )
1214            }
1215            cm_rust::OfferDecl::Protocol(_)
1216            | cm_rust::OfferDecl::Runner(_)
1217            | cm_rust::OfferDecl::Resolver(_) => extend_dict_with_offer::<Connector, _>(
1218                component,
1219                &child_component_output_dictionary_routers,
1220                component_input,
1221                program_output_dict,
1222                framework_router,
1223                capability_sourced_capabilities_dict,
1224                first_offer,
1225                &target_input.capabilities(),
1226                error_reporter.clone(),
1227            ),
1228            _ => {}
1229        }
1230    }
1231}
1232
1233pub fn is_supported_use(use_: &cm_rust::UseDecl) -> bool {
1234    matches!(
1235        use_,
1236        cm_rust::UseDecl::Config(_)
1237            | cm_rust::UseDecl::Protocol(_)
1238            | cm_rust::UseDecl::Runner(_)
1239            | cm_rust::UseDecl::Service(_)
1240            | cm_rust::UseDecl::Directory(_)
1241            | cm_rust::UseDecl::EventStream(_)
1242            | cm_rust::UseDecl::Dictionary(_)
1243            | cm_rust::UseDecl::Storage(_)
1244    )
1245}
1246
1247// Add the `config_use` to the `program_input_dict`, so the component is able to
1248// access this configuration.
1249fn extend_dict_with_config_use<C: ComponentInstanceInterface + 'static>(
1250    component: &Arc<C>,
1251    child_component_output_dictionary_routers: &HashMap<ChildName, Router<Dict>>,
1252    component_input: &ComponentInput,
1253    program_input: &ProgramInput,
1254    program_output_dict: &Dict,
1255    config_use: &cm_rust::UseConfigurationDecl,
1256    error_reporter: impl ErrorReporter,
1257) {
1258    let moniker = component.moniker();
1259    let source_path = config_use.source_path();
1260    let porcelain_type = CapabilityTypeName::Config;
1261    let router: Router<Data> = match config_use.source() {
1262        cm_rust::UseSource::Parent => {
1263            use_from_parent_router::<Data>(component_input, source_path.to_owned(), moniker)
1264        }
1265        cm_rust::UseSource::Self_ => program_output_dict.get_router_or_not_found::<Data>(
1266            &source_path,
1267            RoutingError::use_from_self_not_found(moniker, source_path.iter_segments().join("/")),
1268        ),
1269        cm_rust::UseSource::Child(child_name) => {
1270            let child_name = ChildName::parse(child_name).expect("invalid child name");
1271            let Some(child_component_output) =
1272                child_component_output_dictionary_routers.get(&child_name)
1273            else {
1274                panic!(
1275                    "use declaration in manifest for component {} has a source of a nonexistent child {}, this should be prevented by manifest validation",
1276                    moniker, child_name
1277                );
1278            };
1279            child_component_output.clone().lazy_get(
1280                source_path.to_owned(),
1281                RoutingError::use_from_child_expose_not_found(
1282                    &child_name,
1283                    &moniker,
1284                    config_use.source_name().clone(),
1285                ),
1286            )
1287        }
1288        // The following are not used with config capabilities.
1289        cm_rust::UseSource::Environment => return,
1290        cm_rust::UseSource::Debug => return,
1291        cm_rust::UseSource::Capability(_) => return,
1292        cm_rust::UseSource::Framework => return,
1293        cm_rust::UseSource::Collection(_) => return,
1294    };
1295
1296    let availability = *config_use.availability();
1297    match program_input.config().insert_capability(
1298        &config_use.target_name,
1299        router
1300            .with_porcelain_with_default(porcelain_type)
1301            .availability(availability)
1302            .target(component)
1303            .error_info(config_use)
1304            .error_reporter(error_reporter)
1305            .into(),
1306    ) {
1307        Ok(()) => (),
1308        Err(e) => {
1309            warn!("failed to insert {} in program input dict: {e:?}", config_use.target_name)
1310        }
1311    }
1312}
1313
1314fn extend_dict_with_event_stream_uses<C: ComponentInstanceInterface + 'static>(
1315    component: &Arc<C>,
1316    component_input: &ComponentInput,
1317    program_input: &ProgramInput,
1318    uses: Vec<&cm_rust::UseDecl>,
1319    error_reporter: impl ErrorReporter,
1320    event_stream_use_router_fn: &EventStreamUseRouterFn<C>,
1321) {
1322    let use_event_stream_decls = uses.into_iter().map(|u| match u {
1323        cm_rust::UseDecl::EventStream(decl) => decl,
1324        _other_use => panic!("conflicting use types share target path, this should be prevented by manifest validation"),
1325    }).collect::<Vec<_>>();
1326    let moniker = component.moniker();
1327    let porcelain_type = CapabilityTypeName::EventStream;
1328    let target_path = use_event_stream_decls.first().unwrap().target_path.clone();
1329    for use_event_stream_decl in &use_event_stream_decls {
1330        assert_eq!(
1331            &use_event_stream_decl.source,
1332            &cm_rust::UseSource::Parent,
1333            "event streams can only be used from parent, anything else should be caught by \
1334            manifest validation",
1335        );
1336    }
1337    let routers = use_event_stream_decls
1338        .into_iter()
1339        .map(|use_event_stream_decl| {
1340            let mut route_metadata = finternal::EventStreamRouteMetadata::default();
1341            if let Some(scope) = &use_event_stream_decl.scope {
1342                route_metadata.scope_moniker = Some(component.moniker().to_string());
1343                route_metadata.scope = Some(scope.clone().native_into_fidl());
1344            }
1345
1346            let source_path = use_event_stream_decl.source_path().to_owned();
1347            let router = use_from_parent_router::<Dict>(component_input, source_path, &moniker)
1348                .with_porcelain_with_default(porcelain_type)
1349                .availability(use_event_stream_decl.availability)
1350                .event_stream_route_metadata(route_metadata)
1351                .target(component)
1352                .error_info(RouteRequestErrorInfo::from(use_event_stream_decl))
1353                .error_reporter(error_reporter.clone())
1354                .build();
1355            let filter = use_event_stream_decl.filter.clone();
1356            EventStreamSourceRouter { router, filter }
1357        })
1358        .collect::<Vec<_>>();
1359
1360    let router = event_stream_use_router_fn(component, routers);
1361    if let Err(e) = program_input.namespace().insert_capability(&target_path, router.into()) {
1362        warn!("failed to insert {} in program input dict: {e:?}", target_path)
1363    }
1364}
1365
1366fn extend_dict_with_use<T, C: ComponentInstanceInterface + 'static>(
1367    component: &Arc<C>,
1368    child_component_output_dictionary_routers: &HashMap<ChildName, Router<Dict>>,
1369    component_input: &ComponentInput,
1370    program_input: &ProgramInput,
1371    program_output_dict: &Dict,
1372    framework_router: &Router<Dict>,
1373    capability_sourced_capabilities_dict: &Dict,
1374    use_: &cm_rust::UseDecl,
1375    error_reporter: impl ErrorReporter,
1376) where
1377    T: CapabilityBound + Clone,
1378    Router<T>: TryFrom<Capability> + Into<Capability>,
1379{
1380    if !is_supported_use(use_) {
1381        return;
1382    }
1383    let moniker = component.moniker();
1384    if let cm_rust::UseDecl::Config(config) = use_ {
1385        return extend_dict_with_config_use(
1386            component,
1387            child_component_output_dictionary_routers,
1388            component_input,
1389            program_input,
1390            program_output_dict,
1391            config,
1392            error_reporter,
1393        );
1394    };
1395
1396    let source_path = use_.source_path();
1397    let porcelain_type = CapabilityTypeName::from(use_);
1398    let router: Router<T> = match use_.source() {
1399        cm_rust::UseSource::Parent => {
1400            use_from_parent_router::<T>(component_input, source_path.to_owned(), moniker)
1401        }
1402        cm_rust::UseSource::Self_ => program_output_dict.get_router_or_not_found::<T>(
1403            &source_path,
1404            RoutingError::use_from_self_not_found(moniker, source_path.iter_segments().join("/")),
1405        ),
1406        cm_rust::UseSource::Child(child_name) => {
1407            let child_name = ChildName::parse(child_name).expect("invalid child name");
1408            let Some(child_component_output) =
1409                child_component_output_dictionary_routers.get(&child_name)
1410            else {
1411                panic!(
1412                    "use declaration in manifest for component {} has a source of a nonexistent child {}, this should be prevented by manifest validation",
1413                    moniker, child_name
1414                );
1415            };
1416            child_component_output.clone().lazy_get(
1417                source_path.to_owned(),
1418                RoutingError::use_from_child_expose_not_found(
1419                    &child_name,
1420                    &moniker,
1421                    use_.source_name().clone(),
1422                ),
1423            )
1424        }
1425        cm_rust::UseSource::Framework if use_.is_from_dictionary() => {
1426            Router::<T>::new_error(RoutingError::capability_from_framework_not_found(
1427                moniker,
1428                source_path.iter_segments().join("/"),
1429            ))
1430        }
1431        cm_rust::UseSource::Framework => {
1432            query_framework_router_or_not_found(framework_router, &source_path, component)
1433        }
1434        cm_rust::UseSource::Capability(capability_name) => {
1435            let err = RoutingError::capability_from_capability_not_found(
1436                moniker,
1437                capability_name.as_str().to_string(),
1438            );
1439            if source_path.iter_segments().join("/") == fsys::StorageAdminMarker::PROTOCOL_NAME
1440                || source_path.iter_segments().join("/")
1441                    == fcomponent::StorageAdminMarker::PROTOCOL_NAME
1442            {
1443                capability_sourced_capabilities_dict.get_router_or_not_found(&capability_name, err)
1444            } else {
1445                Router::<T>::new_error(err)
1446            }
1447        }
1448        cm_rust::UseSource::Debug => {
1449            let cm_rust::UseDecl::Protocol(use_protocol) = use_ else {
1450                panic!(
1451                    "non-protocol capability used with a debug source, this should be prevented by manifest validation"
1452                );
1453            };
1454            component_input.environment().debug().get_router_or_not_found::<T>(
1455                &use_protocol.source_name,
1456                RoutingError::use_from_environment_not_found(
1457                    moniker,
1458                    "protocol",
1459                    &use_protocol.source_name,
1460                ),
1461            )
1462        }
1463        cm_rust::UseSource::Environment => {
1464            let cm_rust::UseDecl::Runner(use_runner) = use_ else {
1465                panic!(
1466                    "non-runner capability used with an environment source, this should be prevented by manifest validation"
1467                );
1468            };
1469            component_input.environment().runners().get_router_or_not_found::<T>(
1470                &use_runner.source_name,
1471                RoutingError::use_from_environment_not_found(
1472                    moniker,
1473                    "runner",
1474                    &use_runner.source_name,
1475                ),
1476            )
1477        }
1478        cm_rust::UseSource::Collection(_) => {
1479            // Collection sources are handled separately, in `build_component_sandbox`
1480            return;
1481        }
1482    };
1483
1484    let availability = *use_.availability();
1485    let mut router_builder = router
1486        .with_porcelain_with_default(porcelain_type)
1487        .availability(availability)
1488        .target(&component)
1489        .error_info(use_)
1490        .error_reporter(error_reporter);
1491    if let cm_rust::UseDecl::Directory(decl) = use_ {
1492        router_builder = router_builder
1493            .rights(Some(decl.rights.into()))
1494            .subdir(decl.subdir.clone().into())
1495            .inherit_rights(false);
1496    }
1497    if let cm_rust::UseDecl::Service(_) = use_ {
1498        router_builder = router_builder.rights(Some(fio::R_STAR_DIR.into())).inherit_rights(false);
1499    }
1500    if let cm_rust::UseDecl::Storage(_) = use_ {
1501        router_builder = router_builder
1502            .rights(Some(fidl_fuchsia_io::RW_STAR_DIR.into()))
1503            .subdir(cm_types::RelativePath::dot().into())
1504            .inherit_rights(false);
1505    }
1506    let router = router_builder.build();
1507
1508    match use_ {
1509        cm_rust::UseDecl::Protocol(cm_rust::UseProtocolDecl {
1510            numbered_handle: Some(numbered_handle),
1511            ..
1512        }) => {
1513            let numbered_handle = Name::from(*numbered_handle);
1514            if let Err(e) =
1515                program_input.numbered_handles().insert_capability(&numbered_handle, router.into())
1516            {
1517                warn!("failed to insert {} in program input dict: {e:?}", numbered_handle)
1518            }
1519        }
1520        cm_rust::UseDecl::Runner(_) => {
1521            assert!(program_input.runner().is_none(), "component can't use multiple runners");
1522            program_input.set_runner(router.into());
1523        }
1524        _ => {
1525            if let Err(e) =
1526                program_input.namespace().insert_capability(use_.path().unwrap(), router.into())
1527            {
1528                warn!("failed to insert {} in program input dict: {e:?}", use_.path().unwrap())
1529            }
1530        }
1531    }
1532}
1533
1534fn extend_dict_with_dictionary_use<C: ComponentInstanceInterface + 'static>(
1535    component: &Arc<C>,
1536    child_component_output_dictionary_routers: &HashMap<ChildName, Router<Dict>>,
1537    component_input: &ComponentInput,
1538    program_input: &ProgramInput,
1539    program_output_dict: &Dict,
1540    framework_router: &Router<Dict>,
1541    capability_sourced_capabilities_dict: &Dict,
1542    use_bundle: Vec<&cm_rust::UseDecl>,
1543    error_reporter: impl ErrorReporter,
1544) {
1545    let path = use_bundle[0].path().unwrap();
1546    let mut dictionary_routers = vec![];
1547    for use_ in use_bundle.iter() {
1548        let dict_for_used_router = ProgramInput::new(Dict::new(), None, Dict::new());
1549        extend_dict_with_use::<Dict, _>(
1550            component,
1551            child_component_output_dictionary_routers,
1552            component_input,
1553            &dict_for_used_router,
1554            program_output_dict,
1555            framework_router,
1556            capability_sourced_capabilities_dict,
1557            use_,
1558            error_reporter.clone(),
1559        );
1560        let dictionary_router = match dict_for_used_router.namespace().get_capability(path) {
1561            Some(Capability::DictionaryRouter(router)) => router,
1562            other_value => panic!("unexpected dictionary get result: {other_value:?}"),
1563        };
1564        dictionary_routers.push(dictionary_router);
1565    }
1566    let original_dictionary = match program_input.namespace().get_capability(path) {
1567        Some(Capability::Dictionary(dictionary)) => dictionary,
1568        _ => Dict::new(),
1569    };
1570    let router = UseDictionaryRouter::new(
1571        path.clone(),
1572        component.moniker().clone(),
1573        original_dictionary,
1574        dictionary_routers,
1575        CapabilitySource::Component(ComponentSource {
1576            capability: ComponentCapability::Use_((*use_bundle.first().unwrap()).clone()),
1577            moniker: component.moniker().clone(),
1578        }),
1579    );
1580    match program_input.namespace().insert_capability(path, router.into()) {
1581        Ok(()) => (),
1582        Err(_e) => {
1583            // The only reason this will happen is if we're shadowing something else.
1584            // `insert_capability` will still insert the new capability when it returns an error,
1585            // so we can safely ignore this.
1586        }
1587    }
1588}
1589
1590/// Builds a router that obtains a capability that the program uses from `parent`.
1591fn use_from_parent_router<T>(
1592    component_input: &ComponentInput,
1593    source_path: impl IterablePath + 'static + Debug,
1594    moniker: &Moniker,
1595) -> Router<T>
1596where
1597    T: CapabilityBound + Clone,
1598    Router<T>: TryFrom<Capability>,
1599{
1600    let err = if moniker == &Moniker::root() {
1601        RoutingError::register_from_component_manager_not_found(
1602            source_path.iter_segments().join("/"),
1603        )
1604    } else {
1605        RoutingError::use_from_parent_not_found(moniker, source_path.iter_segments().join("/"))
1606    };
1607    component_input.capabilities().get_router_or_not_found::<T>(&source_path, err)
1608}
1609
1610fn is_supported_offer(offer: &cm_rust::OfferDecl) -> bool {
1611    matches!(
1612        offer,
1613        cm_rust::OfferDecl::Config(_)
1614            | cm_rust::OfferDecl::Protocol(_)
1615            | cm_rust::OfferDecl::Dictionary(_)
1616            | cm_rust::OfferDecl::Directory(_)
1617            | cm_rust::OfferDecl::Runner(_)
1618            | cm_rust::OfferDecl::Resolver(_)
1619            | cm_rust::OfferDecl::Service(_)
1620            | cm_rust::OfferDecl::EventStream(_)
1621            | cm_rust::OfferDecl::Storage(_)
1622    )
1623}
1624
1625fn extend_dict_with_offer<T, C: ComponentInstanceInterface + 'static>(
1626    component: &Arc<C>,
1627    child_component_output_dictionary_routers: &HashMap<ChildName, Router<Dict>>,
1628    component_input: &ComponentInput,
1629    program_output_dict: &Dict,
1630    framework_router: &Router<Dict>,
1631    capability_sourced_capabilities_dict: &Dict,
1632    offer: &cm_rust::OfferDecl,
1633    target_dict: &Dict,
1634    error_reporter: impl ErrorReporter,
1635) where
1636    T: CapabilityBound + Clone,
1637    Router<T>: TryFrom<Capability> + Into<Capability> + WithServiceRenamesAndFilter,
1638{
1639    assert!(is_supported_offer(offer), "{offer:?}");
1640
1641    let source_path = offer.source_path();
1642    let target_name = offer.target_name();
1643    let porcelain_type = CapabilityTypeName::from(offer);
1644    let router: Router<T> = match offer.source() {
1645        cm_rust::OfferSource::Parent => {
1646            let err = if component.moniker() == &Moniker::root() {
1647                RoutingError::register_from_component_manager_not_found(
1648                    offer.source_name().to_string(),
1649                )
1650            } else {
1651                RoutingError::offer_from_parent_not_found(
1652                    &component.moniker(),
1653                    source_path.iter_segments().join("/"),
1654                )
1655            };
1656            component_input.capabilities().get_router_or_not_found::<T>(&source_path, err)
1657        }
1658        cm_rust::OfferSource::Self_ => program_output_dict.get_router_or_not_found::<T>(
1659            &source_path,
1660            RoutingError::offer_from_self_not_found(
1661                &component.moniker(),
1662                source_path.iter_segments().join("/"),
1663            ),
1664        ),
1665        cm_rust::OfferSource::Child(child_ref) => {
1666            let child_name: ChildName = child_ref.clone().try_into().expect("invalid child ref");
1667            match child_component_output_dictionary_routers.get(&child_name) {
1668                None => Router::<T>::new_error(RoutingError::offer_from_child_instance_not_found(
1669                    &child_name,
1670                    &component.moniker(),
1671                    source_path.iter_segments().join("/"),
1672                )),
1673                Some(child_component_output) => child_component_output.clone().lazy_get(
1674                    source_path.to_owned(),
1675                    RoutingError::offer_from_child_expose_not_found(
1676                        &child_name,
1677                        &component.moniker(),
1678                        offer.source_name().clone(),
1679                    ),
1680                ),
1681            }
1682        }
1683        cm_rust::OfferSource::Framework => {
1684            if offer.is_from_dictionary() {
1685                warn!(
1686                    "routing from framework with dictionary path is not supported: {source_path}"
1687                );
1688                return;
1689            }
1690            query_framework_router_or_not_found(framework_router, &source_path, component)
1691        }
1692        cm_rust::OfferSource::Capability(capability_name) => {
1693            let err = RoutingError::capability_from_capability_not_found(
1694                &component.moniker(),
1695                capability_name.as_str().to_string(),
1696            );
1697            if source_path.iter_segments().join("/") == fsys::StorageAdminMarker::PROTOCOL_NAME
1698                || source_path.iter_segments().join("/")
1699                    == fcomponent::StorageAdminMarker::PROTOCOL_NAME
1700            {
1701                capability_sourced_capabilities_dict.get_router_or_not_found(&capability_name, err)
1702            } else {
1703                Router::<T>::new_error(err)
1704            }
1705        }
1706        cm_rust::OfferSource::Void => UnavailableRouter::new_from_offer(offer, component),
1707        cm_rust::OfferSource::Collection(_collection_name) => {
1708            // There's nothing in a collection at this stage, and thus we can't get any routers to
1709            // things in the collection. What's more: the contents of the collection can change
1710            // over time, so it must be monitored. We don't handle collections here, they're
1711            // handled in a different way by whoever called `extend_dict_with_offer`.
1712            return;
1713        }
1714    };
1715
1716    let availability = *offer.availability();
1717    let mut router_builder = router
1718        .with_porcelain_with_default(porcelain_type)
1719        .availability(availability)
1720        .target(component)
1721        .error_info(offer)
1722        .error_reporter(error_reporter);
1723    if let cm_rust::OfferDecl::Directory(decl) = offer {
1724        // Offered capabilities need to support default requests in the case of
1725        // offer-to-dictionary. This is a corollary of the fact that program_input_dictionary and
1726        // component_output_dictionary support default requests, and we need this to cover the case
1727        // where the use or expose is from a dictionary.
1728        //
1729        // Technically, we could restrict this to the case of offer-to-dictionary, not offer in
1730        // general. However, supporting the general case simplifies the logic and establishes a
1731        // nice symmetry between program_input_dict, component_output_dict, and
1732        // {child,collection}_inputs.
1733        router_builder = router_builder
1734            .rights(decl.rights.clone().map(Into::into))
1735            .subdir(decl.subdir.clone().into())
1736            .inherit_rights(true);
1737    }
1738    if let cm_rust::OfferDecl::Storage(_) = offer {
1739        router_builder = router_builder
1740            .rights(Some(fio::RW_STAR_DIR.into()))
1741            .inherit_rights(false)
1742            .subdir(cm_types::RelativePath::dot().into());
1743    }
1744    if let cm_rust::OfferDecl::Service(_) = offer {
1745        router_builder = router_builder.rights(Some(fio::R_STAR_DIR.into())).inherit_rights(true);
1746    }
1747    if let cm_rust::OfferDecl::EventStream(offer_event_stream) = offer {
1748        if let Some(scope) = &offer_event_stream.scope {
1749            router_builder =
1750                router_builder.event_stream_route_metadata(finternal::EventStreamRouteMetadata {
1751                    scope_moniker: Some(component.moniker().to_string()),
1752                    scope: Some(scope.clone().native_into_fidl()),
1753                    ..Default::default()
1754                });
1755        }
1756    }
1757    let router = router_builder.build().with_service_renames_and_filter(offer.clone());
1758    match target_dict.insert_capability(target_name, router.into()) {
1759        Ok(()) => (),
1760        Err(e) => warn!("failed to insert {target_name} into target dict: {e:?}"),
1761    }
1762}
1763
1764fn query_framework_router_or_not_found<T, C>(
1765    router: &Router<Dict>,
1766    path: &BorrowedSeparatedPath<'_>,
1767    component: &Arc<C>,
1768) -> Router<T>
1769where
1770    T: CapabilityBound,
1771    Router<T>: TryFrom<Capability>,
1772    C: ComponentInstanceInterface + 'static,
1773{
1774    let request = Request { metadata: Dict::new() };
1775    let dict: Result<RouterResponse<Dict>, RouterError> = router
1776        .route(Some(request), false, component.as_weak().into())
1777        .now_or_never()
1778        .expect("failed to now_or_never");
1779    let dict = match dict {
1780        Ok(RouterResponse::Capability(dict)) => dict,
1781        // shouldn't happen, fallback
1782        _ => Dict::new(),
1783    };
1784    // `lazy_get` is not needed here as the framework dictionary does not contain
1785    // dictionary routers that have to be queried in turn.
1786    dict.get_router_or_not_found::<T>(
1787        path,
1788        RoutingError::capability_from_framework_not_found(
1789            &component.moniker(),
1790            path.iter_segments().join("/"),
1791        ),
1792    )
1793}
1794
1795pub fn is_supported_expose(expose: &cm_rust::ExposeDecl) -> bool {
1796    matches!(
1797        expose,
1798        cm_rust::ExposeDecl::Config(_)
1799            | cm_rust::ExposeDecl::Protocol(_)
1800            | cm_rust::ExposeDecl::Dictionary(_)
1801            | cm_rust::ExposeDecl::Directory(_)
1802            | cm_rust::ExposeDecl::Runner(_)
1803            | cm_rust::ExposeDecl::Resolver(_)
1804            | cm_rust::ExposeDecl::Service(_)
1805    )
1806}
1807
1808fn extend_dict_with_expose<T, C: ComponentInstanceInterface + 'static>(
1809    component: &Arc<C>,
1810    child_component_output_dictionary_routers: &HashMap<ChildName, Router<Dict>>,
1811    program_output_dict: &Dict,
1812    framework_router: &Router<Dict>,
1813    capability_sourced_capabilities_dict: &Dict,
1814    expose: &cm_rust::ExposeDecl,
1815    target_component_output: &ComponentOutput,
1816    error_reporter: impl ErrorReporter,
1817) where
1818    T: CapabilityBound + Clone,
1819    Router<T>: TryFrom<Capability> + Into<Capability>,
1820{
1821    assert!(is_supported_expose(expose), "{expose:?}");
1822
1823    let target_dict = match expose.target() {
1824        cm_rust::ExposeTarget::Parent => target_component_output.capabilities(),
1825        cm_rust::ExposeTarget::Framework => target_component_output.framework(),
1826    };
1827    let source_path = expose.source_path();
1828    let target_name = expose.target_name();
1829
1830    let porcelain_type = CapabilityTypeName::from(expose);
1831    let router: Router<T> = match expose.source() {
1832        cm_rust::ExposeSource::Self_ => program_output_dict.get_router_or_not_found::<T>(
1833            &source_path,
1834            RoutingError::expose_from_self_not_found(
1835                &component.moniker(),
1836                source_path.iter_segments().join("/"),
1837            ),
1838        ),
1839        cm_rust::ExposeSource::Child(child_name) => {
1840            let child_name = ChildName::parse(child_name).expect("invalid static child name");
1841            if let Some(child_component_output) =
1842                child_component_output_dictionary_routers.get(&child_name)
1843            {
1844                child_component_output.clone().lazy_get(
1845                    source_path.to_owned(),
1846                    RoutingError::expose_from_child_expose_not_found(
1847                        &child_name,
1848                        &component.moniker(),
1849                        expose.source_name().clone(),
1850                    ),
1851                )
1852            } else {
1853                Router::<T>::new_error(RoutingError::expose_from_child_instance_not_found(
1854                    &child_name,
1855                    &component.moniker(),
1856                    expose.source_name().clone(),
1857                ))
1858            }
1859        }
1860        cm_rust::ExposeSource::Framework => {
1861            if expose.is_from_dictionary() {
1862                warn!(
1863                    "routing from framework with dictionary path is not supported: {source_path}"
1864                );
1865                return;
1866            }
1867            query_framework_router_or_not_found(framework_router, &source_path, component)
1868        }
1869        cm_rust::ExposeSource::Capability(capability_name) => {
1870            let err = RoutingError::capability_from_capability_not_found(
1871                &component.moniker(),
1872                capability_name.as_str().to_string(),
1873            );
1874            if source_path.iter_segments().join("/") == fsys::StorageAdminMarker::PROTOCOL_NAME
1875                || source_path.iter_segments().join("/")
1876                    == fcomponent::StorageAdminMarker::PROTOCOL_NAME
1877            {
1878                capability_sourced_capabilities_dict
1879                    .clone()
1880                    .get_router_or_not_found::<T>(&capability_name, err)
1881            } else {
1882                Router::<T>::new_error(err)
1883            }
1884        }
1885        cm_rust::ExposeSource::Void => UnavailableRouter::new_from_expose(expose, component),
1886        // There's nothing in a collection at this stage, and thus we can't get any routers to
1887        // things in the collection. What's more: the contents of the collection can change over
1888        // time, so it must be monitored. We don't handle collections here, they're handled in a
1889        // different way by whoever called `extend_dict_with_expose`.
1890        cm_rust::ExposeSource::Collection(_name) => return,
1891    };
1892    let availability = *expose.availability();
1893    let mut router_builder = router
1894        .with_porcelain_with_default(porcelain_type)
1895        .availability(availability)
1896        .target(component)
1897        .error_info(expose)
1898        .error_reporter(error_reporter);
1899    if let cm_rust::ExposeDecl::Directory(decl) = expose {
1900        router_builder = router_builder
1901            .rights(decl.rights.clone().map(Into::into))
1902            .subdir(decl.subdir.clone().into())
1903            .inherit_rights(true);
1904    };
1905    if let cm_rust::ExposeDecl::Service(_) = expose {
1906        router_builder = router_builder.rights(Some(fio::R_STAR_DIR.into())).inherit_rights(true);
1907    };
1908    match target_dict.insert_capability(target_name, router_builder.build().into()) {
1909        Ok(()) => (),
1910        Err(e) => warn!("failed to insert {target_name} into target_dict: {e:?}"),
1911    }
1912}
1913
1914struct UnavailableRouter<C: ComponentInstanceInterface> {
1915    capability: InternalCapability,
1916    component: WeakComponentInstanceInterface<C>,
1917}
1918
1919impl<C: ComponentInstanceInterface + 'static> UnavailableRouter<C> {
1920    fn new<T: CapabilityBound>(capability: InternalCapability, component: &Arc<C>) -> Router<T> {
1921        Router::<T>::new(Self { capability, component: component.as_weak() })
1922    }
1923
1924    fn new_from_offer<T: CapabilityBound>(offer: &OfferDecl, component: &Arc<C>) -> Router<T> {
1925        let name = offer.source_name().clone();
1926        let capability = match offer {
1927            OfferDecl::Service(_) => InternalCapability::Service(name),
1928            OfferDecl::Protocol(_) => InternalCapability::Protocol(name),
1929            OfferDecl::Directory(_) => InternalCapability::Directory(name),
1930            OfferDecl::Storage(_) => InternalCapability::Storage(name),
1931            OfferDecl::Runner(_) => InternalCapability::Runner(name),
1932            OfferDecl::Resolver(_) => InternalCapability::Resolver(name),
1933            OfferDecl::EventStream(_) => InternalCapability::EventStream(name),
1934            OfferDecl::Dictionary(_) => InternalCapability::Dictionary(name),
1935            OfferDecl::Config(_) => InternalCapability::Config(name),
1936        };
1937        Self::new(capability, component)
1938    }
1939
1940    fn new_from_expose<T: CapabilityBound>(expose: &ExposeDecl, component: &Arc<C>) -> Router<T> {
1941        let name = expose.source_name().clone();
1942        let capability = match expose {
1943            ExposeDecl::Service(_) => InternalCapability::Service(name),
1944            ExposeDecl::Protocol(_) => InternalCapability::Protocol(name),
1945            ExposeDecl::Directory(_) => InternalCapability::Directory(name),
1946            ExposeDecl::Runner(_) => InternalCapability::Runner(name),
1947            ExposeDecl::Resolver(_) => InternalCapability::Resolver(name),
1948            ExposeDecl::Dictionary(_) => InternalCapability::Dictionary(name),
1949            ExposeDecl::Config(_) => InternalCapability::Config(name),
1950        };
1951        Self::new(capability, component)
1952    }
1953}
1954
1955#[async_trait]
1956impl<T: CapabilityBound, C: ComponentInstanceInterface + 'static> Routable<T>
1957    for UnavailableRouter<C>
1958{
1959    async fn route(
1960        &self,
1961        request: Option<Request>,
1962        debug: bool,
1963        _target: WeakInstanceToken,
1964    ) -> Result<RouterResponse<T>, RouterError> {
1965        if debug {
1966            let data = CapabilitySource::Void(VoidSource {
1967                capability: self.capability.clone(),
1968                moniker: self.component.moniker.clone(),
1969            })
1970            .try_into()
1971            .expect("failed to convert capability source to Data");
1972            return Ok(RouterResponse::<T>::Debug(data));
1973        }
1974        let request = request.ok_or_else(|| RouterError::InvalidArgs)?;
1975        let availability = request.metadata.get_metadata().ok_or(RouterError::InvalidArgs)?;
1976        match availability {
1977            cm_rust::Availability::Required | cm_rust::Availability::SameAsTarget => {
1978                Err(RoutingError::SourceCapabilityIsVoid {
1979                    moniker: self.component.moniker.clone(),
1980                }
1981                .into())
1982            }
1983            cm_rust::Availability::Optional | cm_rust::Availability::Transitional => {
1984                Ok(RouterResponse::Unavailable)
1985            }
1986        }
1987    }
1988}