1use crate::WeakInstanceTokenExt;
6use crate::component_instance::{ComponentInstanceInterface, WeakExtendedInstanceInterface};
7use crate::error::{ErrorReporter, RouteRequestErrorInfo, RoutingError};
8use crate::rights::Rights;
9use crate::subdir::SubDir;
10use async_trait::async_trait;
11use capability_source::CapabilitySource;
12use cm_rust::{CapabilityTypeName, EventScope, FidlIntoNative, NativeIntoFidl};
13use cm_types::Availability;
14use fidl_fuchsia_component_runtime::RouteRequest;
15use fidl_fuchsia_component_sandbox as fsandbox;
16use fidl_fuchsia_io as fio;
17use moniker::{ExtendedMoniker, Moniker};
18use router_error::RouterError;
19use runtime_capabilities::{
20 Capability, CapabilityBound, Dictionary, Routable, Router, WeakInstanceToken,
21};
22use std::collections::HashMap;
23use std::sync::{Arc, LazyLock};
24use strum::IntoEnumIterator;
25
26#[cfg(target_os = "fuchsia")]
27use fuchsia_trace as trace;
28
29struct PorcelainRouter<T: CapabilityBound, R, C: ComponentInstanceInterface, const D: bool> {
30 router: Arc<Router<T>>,
31 porcelain_type: CapabilityTypeName,
32 availability: Availability,
33 rights: Option<Rights>,
34 subdir: Option<SubDir>,
35 inherit_rights: Option<bool>,
36 event_stream_scope: Option<(Moniker, Box<[EventScope]>)>,
37 target: WeakExtendedInstanceInterface<C>,
38 route_request: RouteRequestErrorInfo,
39 error_reporter: R,
40 should_log: bool,
41 #[allow(dead_code)]
42 tracing: bool,
43}
44
45#[async_trait]
46impl<T: CapabilityBound, R: ErrorReporter, C: ComponentInstanceInterface + 'static, const D: bool>
47 Routable<T> for PorcelainRouter<T, R, C, D>
48{
49 async fn route(
50 &self,
51 request: RouteRequest,
52 target: Arc<WeakInstanceToken>,
53 ) -> Result<Option<Arc<T>>, RouterError> {
54 #[allow(unused)]
55 let moniker: Option<ExtendedMoniker> = self
56 .tracing
57 .then(|| <Arc<WeakInstanceToken> as WeakInstanceTokenExt<C>>::moniker(&target));
58
59 #[cfg(target_os = "fuchsia")]
60 if self.tracing {
61 trace::duration_begin!("component_manager", "route_capability",
62 "target" => moniker.as_ref().unwrap().as_str(),
63 "type" => self.route_request.type_name().as_ref(),
64 "capability" => self.route_request.name().as_ref());
65 }
66
67 let result = match self.route_inner(request, D, target).await {
68 Err(err) if self.should_log => {
69 self.error_reporter
70 .report(&self.route_request, &err, self.target.clone().into())
71 .await;
72 Err(err)
73 }
74 other_result => other_result,
75 };
76
77 #[cfg(target_os = "fuchsia")]
78 if self.tracing {
79 trace::duration_end!("component_manager", "route_capability",
80 "target" => moniker.as_ref().unwrap().as_str(),
81 "type" => self.route_request.type_name().as_ref(),
82 "capability" => self.route_request.name().as_ref());
83 }
84
85 result
86 }
87
88 async fn route_debug(
89 &self,
90 request: RouteRequest,
91 target: Arc<WeakInstanceToken>,
92 ) -> Result<CapabilitySource, RouterError> {
93 #[allow(unused)]
94 let moniker: Option<ExtendedMoniker> = self
95 .tracing
96 .then(|| <Arc<WeakInstanceToken> as WeakInstanceTokenExt<C>>::moniker(&target));
97
98 #[cfg(target_os = "fuchsia")]
99 if self.tracing {
100 trace::duration_begin!("component_manager", "route_capability_debug",
101 "target" => moniker.as_ref().unwrap().as_str(),
102 "type" => self.route_request.type_name().as_ref(),
103 "capability" => self.route_request.name().as_ref());
104 }
105
106 let result = match self.route_debug_inner(request, D, target).await {
107 Err(err) if self.should_log => {
108 self.error_reporter
109 .report(&self.route_request, &err, self.target.clone().into())
110 .await;
111 Err(err)
112 }
113 other_result => other_result,
114 };
115
116 #[cfg(target_os = "fuchsia")]
117 if self.tracing {
118 trace::duration_end!("component_manager", "route_capability_debug",
119 "target" => moniker.as_ref().unwrap().as_str(),
120 "type" => self.route_request.type_name().as_ref(),
121 "capability" => self.route_request.name().as_ref());
122 }
123
124 result
125 }
126
127 fn error_info(&self) -> Option<runtime_capabilities::RouterErrorInfo> {
128 Some((&self.route_request).into())
129 }
130}
131
132impl<T: CapabilityBound, R: ErrorReporter, C: ComponentInstanceInterface + 'static, const D: bool>
133 PorcelainRouter<T, R, C, D>
134{
135 async fn route_inner(
136 &self,
137 request: RouteRequest,
138 supply_default: bool,
139 target: Arc<WeakInstanceToken>,
140 ) -> Result<Option<Arc<T>>, RouterError> {
141 let request = self.check_and_compute_request(request, supply_default)?;
142 self.router.route(request, target).await
143 }
144
145 async fn route_debug_inner(
146 &self,
147 request: RouteRequest,
148 supply_default: bool,
149 target: Arc<WeakInstanceToken>,
150 ) -> Result<CapabilitySource, RouterError> {
151 let request = self.check_and_compute_request(request, supply_default)?;
152 self.router.route_debug(request, target).await
153 }
154
155 fn check_and_compute_request(
156 &self,
157 request: RouteRequest,
158 supply_default: bool,
159 ) -> Result<RouteRequest, RouterError> {
160 let PorcelainRouter {
161 router: _,
162 porcelain_type,
163 availability,
164 rights,
165 subdir,
166 inherit_rights,
167 event_stream_scope,
168 target,
169 route_request: _,
170 error_reporter: _,
171 should_log: _,
172 tracing: _,
173 } = self;
174 let mut request = if request != RouteRequest::default() {
175 request
176 } else {
177 if !supply_default {
178 Err(RouterError::InvalidArgs)?;
179 }
180 let mut request = RouteRequest::default();
181 request.build_type_name = Some(porcelain_type.to_string());
182 request.availability = Some(availability.native_into_fidl());
183 if let Some(rights) = rights {
184 request.directory_rights = Some(fio::Flags::from(*rights));
185 }
186 if let Some(inherit_rights) = inherit_rights {
187 request.inherit_rights = Some(*inherit_rights);
188 }
189 if let Some((scope_moniker, scope)) = event_stream_scope.as_ref() {
190 request.event_stream_scope_moniker = Some(scope_moniker.to_string());
191 request.event_stream_scope = Some(scope.clone().native_into_fidl());
192 }
193 request
194 };
195
196 let moniker: ExtendedMoniker = match target {
197 WeakExtendedInstanceInterface::Component(t) => t.moniker.clone().into(),
198 WeakExtendedInstanceInterface::AboveRoot(_) => ExtendedMoniker::ComponentManager,
199 };
200 check_porcelain_type(&moniker, &request, *porcelain_type)?;
201 let updated_availability = check_availability(&moniker, &request, *availability)?;
202
203 check_and_compute_rights(&moniker, &mut request, &rights)?;
204 if let Some(new_subdir) = check_and_compute_subdir(&moniker, &request, &subdir)? {
205 request.sub_directory_path = Some(new_subdir.as_ref().clone().native_into_fidl());
206 }
207 if let Some((new_scope_moniker, new_scope)) = event_stream_scope.as_ref() {
208 if request.event_stream_scope_moniker.is_none() {
211 request.event_stream_scope_moniker =
212 Some(new_scope_moniker.clone().native_into_fidl());
213 request.event_stream_scope = Some(new_scope.clone().native_into_fidl());
214 }
215 }
216
217 request.availability = Some(updated_availability.native_into_fidl());
219 Ok(request)
220 }
221}
222
223fn check_porcelain_type(
224 moniker: &ExtendedMoniker,
225 request: &RouteRequest,
226 expected_type: CapabilityTypeName,
227) -> Result<(), RouterError> {
228 let capability_type: CapabilityTypeName = request
229 .build_type_name
230 .as_ref()
231 .ok_or_else(|| RoutingError::BedrockMissingCapabilityType {
232 type_name: expected_type.to_string(),
233 moniker: moniker.clone(),
234 })?
235 .parse()
236 .map_err(|_| RouterError::InvalidArgs)?;
237 if capability_type != expected_type {
238 Err(RoutingError::BedrockWrongCapabilityType {
239 moniker: moniker.clone(),
240 actual: capability_type.to_string(),
241 expected: expected_type.to_string(),
242 })?;
243 }
244 Ok(())
245}
246
247fn check_availability(
248 moniker: &ExtendedMoniker,
249 request: &RouteRequest,
250 availability: Availability,
251) -> Result<Availability, RouterError> {
252 let request_availability =
255 request.availability.ok_or(fsandbox::RouterError::InvalidArgs).inspect_err(|e| {
256 log::error!("request {:?} did not have availability metadata: {e:?}", request)
257 })?;
258 crate::availability::advance(&moniker, request_availability.fidl_into_native(), availability)
259 .map_err(|e| RoutingError::from(e).into())
260}
261
262fn check_and_compute_rights(
263 moniker: &ExtendedMoniker,
264 request: &mut RouteRequest,
265 rights: &Option<Rights>,
266) -> Result<(), RouterError> {
267 let Some(rights) = rights else {
268 return Ok(());
269 };
270 let inherit = request.inherit_rights.ok_or(RouterError::InvalidArgs)?;
271 let request_rights: Rights = match request.directory_rights {
272 Some(request_rights) => request_rights.into(),
273 None => {
274 if inherit {
275 request.directory_rights = Some(fio::Flags::from(*rights));
276 *rights
277 } else {
278 Err(RouterError::InvalidArgs)?
279 }
280 }
281 };
282 if let Some(intermediate_rights) = request.directory_intermediate_rights {
285 Rights::from(intermediate_rights)
286 .validate_next(&rights, moniker.clone().into())
287 .map_err(|e| router_error::RouterError::from(RoutingError::from(e)))?;
288 };
289 request.directory_intermediate_rights = Some(fio::Flags::from(*rights));
290 request_rights.validate_next(&rights, moniker.clone().into()).map_err(RoutingError::from)?;
293 Ok(())
294}
295
296fn check_and_compute_subdir(
297 moniker: &ExtendedMoniker,
298 request: &RouteRequest,
299 subdir: &Option<SubDir>,
300) -> Result<Option<SubDir>, RouterError> {
301 let Some(mut subdir_from_decl) = subdir.clone() else {
302 return Ok(None);
303 };
304
305 let request_subdir: Option<SubDir> =
306 request.sub_directory_path.as_ref().map(|s| SubDir::new(s).expect("invalid sub directory"));
307
308 if let Some(request_subdir) = request_subdir {
309 let success = subdir_from_decl.as_mut().extend(request_subdir.clone().into());
310 if !success {
311 return Err(RoutingError::PathTooLong {
312 moniker: moniker.clone(),
313 path: subdir_from_decl.to_string(),
314 keyword: request_subdir.to_string(),
315 }
316 .into());
317 }
318 }
319 Ok(Some(subdir_from_decl))
320}
321
322pub type DefaultMetadataFn = Arc<dyn Fn(Availability) -> Dictionary + Send + Sync + 'static>;
323
324pub struct PorcelainBuilder<
328 T: CapabilityBound,
329 R: ErrorReporter,
330 C: ComponentInstanceInterface + 'static,
331 const D: bool,
332> {
333 router: Arc<Router<T>>,
334 porcelain_type: CapabilityTypeName,
335 availability: Option<Availability>,
336 rights: Option<Rights>,
337 subdir: Option<SubDir>,
338 inherit_rights: Option<bool>,
339 event_stream_scope: Option<(Moniker, Box<[EventScope]>)>,
340 target: Option<WeakExtendedInstanceInterface<C>>,
341 error_info: Option<RouteRequestErrorInfo>,
342 error_reporter: Option<R>,
343 should_log: bool,
344 #[allow(dead_code)]
345 tracing: bool,
346}
347
348impl<T: CapabilityBound, R: ErrorReporter, C: ComponentInstanceInterface + 'static, const D: bool>
349 PorcelainBuilder<T, R, C, D>
350{
351 fn new(router: Arc<Router<T>>, porcelain_type: CapabilityTypeName) -> Self {
352 Self {
353 router,
354 porcelain_type,
355 availability: None,
356 rights: None,
357 subdir: None,
358 inherit_rights: None,
359 event_stream_scope: None,
360 target: None,
361 error_info: None,
362 error_reporter: None,
363 should_log: false,
364 tracing: false,
365 }
366 }
367
368 pub fn log_errors(mut self) -> Self {
369 self.should_log = true;
370 self
371 }
372
373 pub fn with_tracing(mut self) -> Self {
374 self.tracing = true;
375 self
376 }
377
378 pub fn availability(mut self, a: Availability) -> Self {
381 self.availability = Some(a);
382 self
383 }
384
385 pub fn rights(mut self, rights: Option<Rights>) -> Self {
386 self.rights = rights;
387 self
388 }
389
390 pub fn subdir(mut self, subdir: SubDir) -> Self {
391 self.subdir = Some(subdir);
392 self
393 }
394
395 pub fn inherit_rights(mut self, inherit_rights: bool) -> Self {
396 self.inherit_rights = Some(inherit_rights);
397 self
398 }
399
400 pub fn event_stream_scope(mut self, scope: (Moniker, Box<[EventScope]>)) -> Self {
401 self.event_stream_scope = Some(scope);
402 self
403 }
404
405 pub fn target(mut self, t: &Arc<C>) -> Self {
409 self.target = Some(WeakExtendedInstanceInterface::Component(t.as_weak()));
410 self
411 }
412
413 pub fn target_above_root(mut self, t: &Arc<C::TopInstance>) -> Self {
416 self.target = Some(WeakExtendedInstanceInterface::AboveRoot(Arc::downgrade(t)));
417 self
418 }
419
420 pub fn error_info<S>(mut self, r: S) -> Self
424 where
425 RouteRequestErrorInfo: From<S>,
426 {
427 self.error_info = Some(RouteRequestErrorInfo::from(r));
428 self
429 }
430
431 pub fn error_reporter(mut self, r: R) -> Self {
434 self.error_reporter = Some(r);
435 self
436 }
437
438 pub fn build(self) -> Arc<Router<T>> {
440 Router::new(PorcelainRouter::<T, R, C, D> {
441 router: self.router,
442 porcelain_type: self.porcelain_type,
443 availability: self.availability.expect("must set availability"),
444 rights: self.rights,
445 subdir: self.subdir,
446 inherit_rights: self.inherit_rights,
447 event_stream_scope: self.event_stream_scope,
448 target: self.target.expect("must set target"),
449 route_request: self.error_info.expect("must set route_request"),
450 error_reporter: self.error_reporter.expect("must set error_reporter"),
451 should_log: self.should_log,
452 tracing: self.tracing,
453 })
454 }
455}
456
457impl<R: ErrorReporter, T: CapabilityBound, C: ComponentInstanceInterface + 'static, const D: bool>
458 From<PorcelainBuilder<T, R, C, D>> for Capability
459where
460 Arc<Router<T>>: Into<Capability>,
461{
462 fn from(b: PorcelainBuilder<T, R, C, D>) -> Self {
463 b.build().into()
464 }
465}
466
467pub trait WithPorcelain<
469 T: CapabilityBound,
470 R: ErrorReporter,
471 C: ComponentInstanceInterface + 'static,
472>
473{
474 fn with_porcelain_with_default(
481 self,
482 type_: CapabilityTypeName,
483 ) -> PorcelainBuilder<T, R, C, true>;
484
485 fn with_porcelain_no_default(
492 self,
493 type_: CapabilityTypeName,
494 ) -> PorcelainBuilder<T, R, C, false>;
495}
496
497impl<T: CapabilityBound, R: ErrorReporter, C: ComponentInstanceInterface + 'static>
498 WithPorcelain<T, R, C> for Arc<Router<T>>
499{
500 fn with_porcelain_with_default(
501 self,
502 type_: CapabilityTypeName,
503 ) -> PorcelainBuilder<T, R, C, true> {
504 PorcelainBuilder::<T, R, C, true>::new(self, type_)
505 }
506
507 fn with_porcelain_no_default(
508 self,
509 type_: CapabilityTypeName,
510 ) -> PorcelainBuilder<T, R, C, false> {
511 PorcelainBuilder::<T, R, C, false>::new(self, type_)
512 }
513}
514
515pub fn metadata_for_porcelain_type(
516 typename: CapabilityTypeName,
517) -> Arc<dyn Fn(Availability) -> RouteRequest + Send + Sync + 'static> {
518 type MetadataMap = HashMap<
519 CapabilityTypeName,
520 Arc<dyn Fn(Availability) -> RouteRequest + Send + Sync + 'static>,
521 >;
522 static CLOSURES: LazyLock<MetadataMap> = LazyLock::new(|| {
523 fn entry_for_typename(
524 typename: CapabilityTypeName,
525 ) -> (CapabilityTypeName, Arc<dyn Fn(Availability) -> RouteRequest + Send + Sync + 'static>)
526 {
527 let v = Arc::new(move |availability: Availability| RouteRequest {
528 build_type_name: Some(typename.to_string()),
529 availability: Some(availability.native_into_fidl()),
530 ..Default::default()
531 });
532 (typename, v)
533 }
534 CapabilityTypeName::iter().map(entry_for_typename).collect()
535 });
536 CLOSURES.get(&typename).unwrap().clone()
537}
538
539#[cfg(test)]
540mod tests {
541 use super::*;
542 use crate::ResolvedInstanceInterface;
543 use crate::bedrock::sandbox_construction::ComponentSandbox;
544 use crate::component_instance::{ExtendedInstanceInterface, TopInstanceInterface};
545 use crate::error::ComponentInstanceError;
546 use crate::policy::GlobalPolicyChecker;
547 use assert_matches::assert_matches;
548 use capability_source::{BuiltinCapabilities, NamespaceCapabilities};
549 use cm_rust_testing::UseBuilder;
550 use cm_types::Url;
551 use fuchsia_sync::Mutex;
552 use moniker::Moniker;
553 use router_error::RouterError;
554 use runtime_capabilities::Data;
555 use std::sync::Arc;
556
557 #[derive(Debug)]
558 struct FakeComponent {
559 moniker: Moniker,
560 }
561
562 #[derive(Debug)]
563 struct FakeTopInstance {
564 ns: NamespaceCapabilities,
565 builtin: BuiltinCapabilities,
566 }
567
568 impl TopInstanceInterface for FakeTopInstance {
569 fn namespace_capabilities(&self) -> &NamespaceCapabilities {
570 &self.ns
571 }
572 fn builtin_capabilities(&self) -> &BuiltinCapabilities {
573 &self.builtin
574 }
575 }
576
577 #[async_trait]
578 impl ComponentInstanceInterface for FakeComponent {
579 type TopInstance = FakeTopInstance;
580
581 fn moniker(&self) -> &Moniker {
582 &self.moniker
583 }
584
585 fn url(&self) -> &Url {
586 panic!()
587 }
588
589 fn config_parent_overrides(&self) -> Option<&[cm_rust::ConfigOverride]> {
590 panic!()
591 }
592
593 fn policy_checker(&self) -> &GlobalPolicyChecker {
594 panic!()
595 }
596
597 fn component_id_index(&self) -> &component_id_index::Index {
598 panic!()
599 }
600
601 fn try_get_parent(
602 &self,
603 ) -> Result<ExtendedInstanceInterface<Self>, ComponentInstanceError> {
604 panic!()
605 }
606
607 async fn lock_resolved_state<'a>(
608 self: &'a Arc<Self>,
609 ) -> Result<Box<dyn ResolvedInstanceInterface<Component = Self> + 'a>, ComponentInstanceError>
610 {
611 panic!()
612 }
613
614 async fn component_sandbox(
615 self: &Arc<Self>,
616 ) -> Result<ComponentSandbox, ComponentInstanceError> {
617 panic!()
618 }
619 }
620
621 #[derive(Clone)]
622 struct TestErrorReporter {
623 reported: Arc<Mutex<bool>>,
624 }
625
626 impl TestErrorReporter {
627 fn new() -> Self {
628 Self { reported: Arc::new(Mutex::new(false)) }
629 }
630 }
631
632 #[async_trait]
633 impl ErrorReporter for TestErrorReporter {
634 async fn report(
635 &self,
636 _request: &RouteRequestErrorInfo,
637 _err: &RouterError,
638 _route_target: Arc<WeakInstanceToken>,
639 ) {
640 let mut reported = self.reported.lock();
641 if *reported {
642 panic!("report() was called twice");
643 }
644 *reported = true;
645 }
646 }
647
648 fn fake_component() -> Arc<FakeComponent> {
649 Arc::new(FakeComponent { moniker: Moniker::root() })
650 }
651
652 fn error_info() -> cm_rust::UseDecl {
653 UseBuilder::protocol().name("name").build()
654 }
655
656 #[fuchsia::test]
657 async fn success() {
658 let source = Arc::new(Data::String("hello".into()));
659 let base = Router::<Data>::new_ok(source);
660 let component = fake_component();
661 let proxy = base
662 .with_porcelain_with_default(CapabilityTypeName::Protocol)
663 .availability(Availability::Optional)
664 .target(&component)
665 .error_info(&error_info())
666 .error_reporter(TestErrorReporter::new())
667 .build();
668 let request = RouteRequest {
669 build_type_name: Some(CapabilityTypeName::Protocol.to_string()),
670 availability: Some(Availability::Optional.native_into_fidl()),
671 ..Default::default()
672 };
673
674 let capability = proxy.route(request, component.as_weak().into()).await.unwrap();
675 let capability = match capability {
676 Some(d) => d,
677 _ => panic!(),
678 };
679 assert_eq!(&*capability, &Data::String("hello".into()));
680 }
681
682 #[fuchsia::test]
683 async fn type_missing() {
684 let reporter = TestErrorReporter::new();
685 let reported = reporter.reported.clone();
686 let source = Data::String("hello".into());
687 let base = Router::<Data>::new_ok(source);
688 let component = fake_component();
689 let proxy = base
690 .with_porcelain_with_default(CapabilityTypeName::Protocol)
691 .availability(Availability::Optional)
692 .target(&component)
693 .error_info(&error_info())
694 .error_reporter(reporter)
695 .log_errors()
696 .build();
697 let request = RouteRequest {
698 availability: Some(Availability::Optional.native_into_fidl()),
699 ..Default::default()
700 };
701
702 let error = proxy.route(request, component.as_weak().into()).await.unwrap_err();
703 assert_matches!(
704 error,
705 RouterError::NotFound(err)
706 if matches!(
707 err.as_any().downcast_ref::<RoutingError>(),
708 Some(RoutingError::BedrockMissingCapabilityType {
709 moniker,
710 type_name,
711 }) if moniker == &Moniker::root().into() && type_name == "protocol"
712 )
713 );
714 assert!(*reported.lock());
715 }
716
717 #[fuchsia::test]
718 async fn type_mismatch() {
719 let reporter = TestErrorReporter::new();
720 let reported = reporter.reported.clone();
721 let source = Data::String("hello".into());
722 let base = Router::<Data>::new_ok(source);
723 let component = fake_component();
724 let proxy = base
725 .with_porcelain_with_default(CapabilityTypeName::Protocol)
726 .availability(Availability::Optional)
727 .target(&component)
728 .error_info(&error_info())
729 .error_reporter(reporter)
730 .log_errors()
731 .build();
732 let request = RouteRequest {
733 build_type_name: Some(CapabilityTypeName::Service.to_string()),
734 availability: Some(Availability::Optional.native_into_fidl()),
735 ..Default::default()
736 };
737
738 let error = proxy.route(request, component.as_weak().into()).await.unwrap_err();
739 assert_matches!(
740 error,
741 RouterError::NotFound(err)
742 if matches!(
743 err.as_any().downcast_ref::<RoutingError>(),
744 Some(RoutingError::BedrockWrongCapabilityType {
745 moniker,
746 expected,
747 actual
748 }) if moniker == &Moniker::root().into()
749 && expected == "protocol" && actual == "service"
750 )
751 );
752 assert!(*reported.lock());
753 }
754
755 #[fuchsia::test]
756 async fn availability_mismatch() {
757 let reporter = TestErrorReporter::new();
758 let reported = reporter.reported.clone();
759 let source = Data::String("hello".into());
760 let base = Router::<Data>::new_ok(source);
761 let component = fake_component();
762 let proxy = base
763 .with_porcelain_with_default(CapabilityTypeName::Protocol)
764 .availability(Availability::Optional)
765 .target(&component)
766 .error_info(&error_info())
767 .error_reporter(reporter)
768 .log_errors()
769 .build();
770 let request = RouteRequest {
771 build_type_name: Some(CapabilityTypeName::Protocol.to_string()),
772 availability: Some(Availability::Required.native_into_fidl()),
773 ..Default::default()
774 };
775
776 let error = proxy.route(request, component.as_weak().into()).await.unwrap_err();
777 assert_matches!(
778 error,
779 RouterError::NotFound(err)
780 if matches!(
781 err.as_any().downcast_ref::<RoutingError>(),
782 Some(RoutingError::AvailabilityRoutingError(
783 crate::error::AvailabilityRoutingError::TargetHasStrongerAvailability {
784 moniker
785 }
786 )) if moniker == &Moniker::root().into()
787 )
788 );
789 assert!(*reported.lock());
790 }
791}