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