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