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