1use crate::error::{ComponentInstanceError, PrettyPrintRef, RouteVerb, RoutingError};
6use crate::rights::{Rights, validate_rights};
7use async_trait::async_trait;
8use capability_source::CapabilitySource;
9use cm_rust::{CapabilityTypeName, FidlIntoNative, NativeIntoFidl};
10use cm_rust_derive::FidlDecl;
11use cm_types::RelativePath;
12use fidl_fuchsia_component_decl as fdecl;
13use fidl_fuchsia_component_runtime as fruntime;
14use fidl_fuchsia_io as fio;
15use moniker::{ChildName, Moniker};
16use router_error::RouterError;
17use runtime_capabilities::{
18 Capability, CapabilityBound, Connector, Data, Dictionary, DirConnector, Routable, Router,
19 RouterErrorInfo, WeakInstanceToken,
20};
21use std::sync::{Arc, Weak};
22
23#[cfg(target_os = "fuchsia")]
24use {cm_types::IterablePath, fuchsia_trace as trace, itertools::Itertools};
25
26pub enum WeakDictionaryOrRouter {
27 Dictionary(Weak<Dictionary>),
28 Router(Weak<Router<Dictionary>>),
29}
30
31impl From<Weak<Dictionary>> for WeakDictionaryOrRouter {
32 fn from(d: Weak<Dictionary>) -> Self {
33 Self::Dictionary(d)
34 }
35}
36
37impl From<Weak<Router<Dictionary>>> for WeakDictionaryOrRouter {
38 fn from(r: Weak<Router<Dictionary>>) -> Self {
39 Self::Router(r)
40 }
41}
42
43struct NotFoundErrorContext {
47 verb: RouteVerb,
48 source: PrettyPrintRef,
49 type_name: CapabilityTypeName,
50}
51
52impl NotFoundErrorContext {
53 fn to_router_error(&self, intermediate_router: &IntermediateRouter) -> RouterError {
54 RoutingError::RouteSourceNotFound {
55 moniker: intermediate_router.moniker.clone(),
56 verb: self.verb,
57 counter_verb: match &self.source {
58 PrettyPrintRef::Parent => RouteVerb::Offer,
59 PrettyPrintRef::Child(_)
60 | PrettyPrintRef::ChildInCollection(_, _)
61 | PrettyPrintRef::Collection(_) => RouteVerb::Expose,
62 PrettyPrintRef::Self_ | PrettyPrintRef::Capability(_) => RouteVerb::Declare,
63 PrettyPrintRef::Framework
64 | PrettyPrintRef::Debug
65 | PrettyPrintRef::Void
66 | PrettyPrintRef::Environment => RouteVerb::Contain,
67 },
68 source: self.source.clone(),
69 capability_type: self.type_name,
70 capability_name: intermediate_router.source_path.clone(),
71 }
72 .into()
73 }
74}
75
76#[derive(FidlDecl, Debug, Clone, PartialEq)]
77#[fidl_decl(fidl_table = "fruntime::RouteRequest")]
78pub struct RouteRequest {
79 pub build_type_name: cm_rust::CapabilityTypeName,
80 pub availability: Option<cm_rust::Availability>,
81 pub directory_rights: Option<fio::Flags>,
82 pub directory_intermediate_rights: Option<fio::Flags>,
83 pub inherit_rights: Option<bool>,
84 pub sub_directory_path: Option<RelativePath>,
85 pub isolated_storage_path: Option<RelativePath>,
86 pub storage_sub_directory_path: Option<RelativePath>,
87 pub storage_source_moniker: Option<Moniker>,
88 pub event_stream_scope_moniker: Option<Moniker>,
89 pub event_stream_scope: Option<Box<[cm_rust::EventScope]>>,
90 pub skip_policy_checks: Option<bool>,
91}
92
93impl Default for RouteRequest {
94 fn default() -> Self {
95 Self {
96 build_type_name: cm_rust::CapabilityTypeName::Protocol,
101 availability: None,
102 directory_rights: None,
103 directory_intermediate_rights: None,
104 inherit_rights: None,
105 sub_directory_path: None,
106 isolated_storage_path: None,
107 storage_sub_directory_path: None,
108 storage_source_moniker: None,
109 event_stream_scope_moniker: None,
110 event_stream_scope: None,
111 skip_policy_checks: None,
112 }
113 }
114}
115
116pub struct IntermediateRouter {
119 source_dictionary: WeakDictionaryOrRouter,
121
122 source_path: RelativePath,
124
125 default_request: RouteRequest,
131
132 default_token: Arc<WeakInstanceToken>,
135
136 moniker: Moniker,
138
139 not_found_context: NotFoundErrorContext,
142
143 #[allow(unused)]
146 enable_tracing: bool,
147}
148
149enum RouterCapabilityOrSource<C: CapabilityBound> {
150 Router(Arc<Router<C>>),
151 Capability(Arc<C>),
152 Source(Box<CapabilitySource>),
153}
154
155impl IntermediateRouter {
156 pub fn new(
159 source_dictionary: WeakDictionaryOrRouter,
160 source_path: RelativePath,
161 default_request: RouteRequest,
162 default_token: Arc<WeakInstanceToken>,
163 moniker: Moniker,
164 route_verb: RouteVerb,
165 source: fdecl::Ref,
166 ) -> Capability {
167 assert!(source_path.len() != 0);
168 let type_name = default_request.build_type_name;
169
170 let enable_tracing = route_verb == RouteVerb::Use;
171
172 let self_ = Self {
173 source_dictionary,
174 source_path,
175 default_request,
176 default_token,
177 moniker,
178 not_found_context: NotFoundErrorContext {
179 verb: route_verb,
180 source: source.into(),
181 type_name,
182 },
183 enable_tracing,
184 };
185
186 match type_name {
187 CapabilityTypeName::Protocol
188 | CapabilityTypeName::Runner
189 | CapabilityTypeName::Resolver => Router::<Connector>::new(self_).into(),
190 CapabilityTypeName::Service
191 | CapabilityTypeName::Directory
192 | CapabilityTypeName::Storage => Router::<DirConnector>::new(self_).into(),
193 CapabilityTypeName::EventStream | CapabilityTypeName::Dictionary => {
194 Router::<Dictionary>::new(self_).into()
195 }
196 CapabilityTypeName::Config => Router::<Data>::new(self_).into(),
197 }
198 }
199
200 fn get_upgrade_failure_moniker(&self) -> Moniker {
202 match &self.not_found_context.source {
203 PrettyPrintRef::Parent => self.moniker.parent().unwrap_or_else(|| Moniker::root()),
204
205 PrettyPrintRef::Child(name) => {
206 self.moniker.child(ChildName::new(name.clone().into(), None))
207 }
208 PrettyPrintRef::ChildInCollection(name, collection) => {
209 self.moniker.child(ChildName::new(name.clone(), Some(collection.clone())))
210 }
211
212 PrettyPrintRef::Collection(_)
213 | PrettyPrintRef::Capability(_)
214 | PrettyPrintRef::Debug
215 | PrettyPrintRef::Environment
216 | PrettyPrintRef::Framework
217 | PrettyPrintRef::Self_
218 | PrettyPrintRef::Void => self.moniker.clone(),
219 }
220 }
221
222 async fn upgrade_source_dictionary(
225 &self,
226 dictionary_request: &fruntime::RouteRequest,
227 ) -> Result<Arc<Dictionary>, RouterError> {
228 match &self.source_dictionary {
229 WeakDictionaryOrRouter::Dictionary(dictionary) => dictionary.upgrade().ok_or(
230 RoutingError::from(ComponentInstanceError::InstanceNotFound {
231 moniker: self.get_upgrade_failure_moniker(),
232 })
233 .into(),
234 ),
235 WeakDictionaryOrRouter::Router(router) => {
236 let router = router.upgrade().ok_or(RoutingError::from(
237 ComponentInstanceError::InstanceNotFound {
238 moniker: self.get_upgrade_failure_moniker(),
239 },
240 ))?;
241 let dictionary = router
242 .route(dictionary_request.clone(), self.default_token.clone())
243 .await?
244 .expect("routers for source dictionaries should never return None");
245 Ok(dictionary)
246 }
247 }
248 }
249
250 async fn get_source_router<C: CapabilityBound>(
254 &self,
255 request: &fruntime::RouteRequest,
256 debug: bool,
257 ) -> Result<RouterCapabilityOrSource<C>, RouterError>
258 where
259 Arc<Router<C>>: TryFrom<Capability>,
260 Arc<C>: TryFrom<Capability>,
261 Router<C>: CapabilityBound,
262 {
263 let dictionary_request = fruntime::RouteRequest {
264 build_type_name: Some(CapabilityTypeName::Dictionary.to_string()),
265 ..request.clone()
266 };
267 let mut source_dictionary = self.upgrade_source_dictionary(&dictionary_request).await?;
269
270 let mut path_to_walk = self.source_path.clone();
272 let mut most_recent_source = None;
273 while path_to_walk.len() > 1 {
274 let next_step = path_to_walk.pop_front().expect("we checked that this isn't empty");
275 match source_dictionary.get(&next_step) {
276 Some(Capability::Dictionary(d)) => source_dictionary = d,
277 Some(Capability::DictionaryRouter(r)) => {
278 match r.route(dictionary_request.clone(), self.default_token.clone()).await? {
279 Some(d) => {
280 if debug {
281 most_recent_source = Some(
282 r.route_debug(
283 dictionary_request.clone(),
284 self.default_token.clone(),
285 )
286 .await?,
287 );
288 }
289 source_dictionary = d;
290 }
291 None => {
292 let source = r
295 .route_debug(dictionary_request.clone(), self.default_token.clone())
296 .await?;
297 return Ok(RouterCapabilityOrSource::Source(Box::new(source)));
298 }
299 }
300 }
301 Some(capability) => {
302 return Err(RoutingError::BedrockWrongCapabilityType {
303 actual: capability.debug_typename().to_string(),
304 expected: Dictionary::debug_typename().to_string(),
305 moniker: self.moniker.clone().into(),
306 }
307 .into());
308 }
309 None => {
310 return Err(self.not_found_context.to_router_error(self));
311 }
312 }
313 }
314 let capability_name = path_to_walk
315 .pop_front()
316 .expect("we stopped the above loop before fully draining the path");
317 let maybe_source_router = source_dictionary
318 .get(&capability_name)
319 .ok_or_else(|| self.not_found_context.to_router_error(self))?;
320
321 let maybe_c: Option<Arc<C>> = maybe_source_router.clone().try_into().ok();
322 if let Some(c) = maybe_c {
323 if !debug {
324 return Ok(RouterCapabilityOrSource::Capability(c));
325 } else {
326 let source = most_recent_source.ok_or_else(|| RoutingError::SourceUnknown {
327 capability_id: capability_name.to_string(),
328 moniker: self.moniker.clone().into(),
329 })?;
330 return Ok(RouterCapabilityOrSource::Source(Box::new(source)));
331 }
332 }
333
334 let capability_type_name = maybe_source_router.debug_typename();
335 let router: Arc<Router<C>> = maybe_source_router.try_into().map_err(|_| {
336 RoutingError::BedrockWrongCapabilityType {
337 actual: capability_type_name.to_string(),
338 expected: Router::<C>::debug_typename().to_string(),
339 moniker: self.moniker.clone().into(),
340 }
341 })?;
342 Ok(RouterCapabilityOrSource::Router(router))
343 }
344
345 fn handle_new_request(&self, request: &mut fruntime::RouteRequest) -> Result<(), RouterError> {
348 if *request == fruntime::RouteRequest::default() {
349 *request = self.default_request.clone().native_into_fidl();
350 return Ok(());
351 }
352
353 self.check_build_type_name(request)?;
354 self.handle_availability(request)?;
355 self.handle_directory_rights(request)?;
356 self.handle_sub_directory(request)?;
357 self.handle_event_stream_scope(request);
358
359 Ok(())
360 }
361
362 fn check_build_type_name(
363 &self,
364 request: &mut fruntime::RouteRequest,
365 ) -> Result<(), RouterError> {
366 if request.build_type_name != Some(self.default_request.build_type_name.to_string()) {
367 Err(RoutingError::BedrockWrongCapabilityType {
368 moniker: self.moniker.clone().into(),
369 actual: request
370 .build_type_name
371 .as_ref()
372 .map(Clone::clone)
373 .unwrap_or_else(|| "".to_string()),
374 expected: self.default_request.build_type_name.to_string(),
375 })?;
376 }
377 Ok(())
378 }
379
380 fn handle_availability(&self, request: &mut fruntime::RouteRequest) -> Result<(), RouterError> {
381 if self.default_request.availability.is_none() {
382 return Ok(());
383 }
384 let request_availability: fidl_fuchsia_component_decl::Availability = *request
385 .availability
386 .as_ref()
387 .ok_or_else(|| RoutingError::RouteRequestMissingField {
388 moniker: self.moniker.clone().into(),
389 missing_field: "availability".to_string(),
390 })?;
391 let request_availability: cm_rust::Availability = request_availability.fidl_into_native();
392 let default_availability =
393 self.default_request.availability.expect("default request is missing availability");
394 let new_availability = crate::availability::advance(
395 &self.moniker.clone().into(),
396 request_availability,
397 default_availability,
398 )
399 .map_err(|e| RoutingError::from(e))?;
400 request.availability = Some(new_availability.native_into_fidl());
401 Ok(())
402 }
403
404 fn handle_directory_rights(
405 &self,
406 request: &mut fruntime::RouteRequest,
407 ) -> Result<(), RouterError> {
408 let Some(directory_rights) = self.default_request.directory_rights else {
409 return Ok(());
410 };
411 let rights = Rights::from(directory_rights);
412 validate_rights(self.moniker.clone().into(), rights.into(), request)?;
413 request.directory_intermediate_rights = Some(fio::Flags::from(rights));
414 Ok(())
415 }
416
417 fn handle_sub_directory(
418 &self,
419 request: &mut fruntime::RouteRequest,
420 ) -> Result<(), RouterError> {
421 let Some(mut new_subdir) = self.default_request.sub_directory_path.clone() else {
422 return Ok(());
423 };
424
425 let Some(current_subdir) = request.sub_directory_path.as_ref() else {
426 request.sub_directory_path =
427 self.default_request.sub_directory_path.clone().map(|p| p.to_string());
428 return Ok(());
429 };
430 let current_subdir = RelativePath::new(current_subdir).map_err(|e| {
431 RoutingError::RouteRequestFailedToParseField {
432 moniker: self.moniker.clone().into(),
433 field: "sub_directory_path".to_string(),
434 parse_error: format!("{e:?}"),
435 }
436 })?;
437
438 let success = new_subdir.extend(current_subdir);
439 if !success {
440 return Err(RoutingError::PathTooLong {
441 moniker: self.moniker.clone().into(),
442 path: self.default_request.sub_directory_path.clone().unwrap().to_string(),
443 keyword: request.sub_directory_path.clone().unwrap(),
444 }
445 .into());
446 }
447
448 request.sub_directory_path = Some(new_subdir.native_into_fidl());
449 Ok(())
450 }
451
452 fn handle_event_stream_scope(&self, request: &mut fruntime::RouteRequest) {
453 if request.event_stream_scope_moniker.is_some() {
454 return;
457 }
458 let Some(new_moniker) = self.default_request.event_stream_scope_moniker.as_ref() else {
459 return;
460 };
461 let Some(new_scope) = self.default_request.event_stream_scope.as_ref() else {
462 return;
463 };
464 request.event_stream_scope_moniker = Some(new_moniker.clone().native_into_fidl());
465 request.event_stream_scope = Some(new_scope.clone().native_into_fidl());
466 }
467}
468
469#[async_trait]
470impl<C: CapabilityBound> Routable<C> for IntermediateRouter
471where
472 Arc<Router<C>>: TryFrom<Capability>,
473 Arc<C>: TryFrom<Capability>,
474 Router<C>: CapabilityBound,
475 C: std::fmt::Debug,
476{
477 async fn route(
478 &self,
479 mut request: fruntime::RouteRequest,
480 target: Arc<WeakInstanceToken>,
481 ) -> Result<Option<Arc<C>>, RouterError> {
482 #[cfg(target_os = "fuchsia")]
483 if self.enable_tracing {
484 trace::duration_begin!(
485 "component_manager", "route_capability",
486 "target" => self.moniker.as_str(),
487 "type" => self.default_request.build_type_name.to_string().as_str(),
488 "capability" => self.source_path.iter_segments().join("/").as_str()
489 );
490 }
491
492 self.handle_new_request(&mut request)?;
493 let result = match self.get_source_router(&request, false).await? {
494 RouterCapabilityOrSource::Capability(c) => Ok(Some(c)),
495 RouterCapabilityOrSource::Source(source) => {
496 match &*source {
497 CapabilitySource::Void(_) => (),
498 other_source => {
499 panic!(
500 "should only return source for non-debug routes when source is void, but the source is {other_source:?}"
501 );
502 }
503 }
504 Err(RoutingError::SourceCapabilityIsVoid { moniker: source.source_moniker() }
505 .into())
506 }
507 RouterCapabilityOrSource::Router(router) => router.route(request, target).await,
508 };
509
510 #[cfg(target_os = "fuchsia")]
511 if self.enable_tracing {
512 trace::duration_end!(
513 "component_manager", "route_capability",
514 "target" => self.moniker.as_str(),
515 "type" => self.default_request.build_type_name.to_string().as_str(),
516 "capability" => self.source_path.iter_segments().join("/").as_str()
517 );
518 }
519
520 result
521 }
522
523 async fn route_debug(
524 &self,
525 mut request: fruntime::RouteRequest,
526 target: Arc<WeakInstanceToken>,
527 ) -> Result<CapabilitySource, RouterError> {
528 #[cfg(target_os = "fuchsia")]
529 if self.enable_tracing {
530 trace::duration_begin!(
531 "component_manager", "route_capability_debug",
532 "target" => self.moniker.as_str(),
533 "type" => self.default_request.build_type_name.to_string().as_str(),
534 "capability" => self.source_path.iter_segments().join("/").as_str()
535 );
536 }
537
538 self.handle_new_request(&mut request)?;
539 let result = match self.get_source_router(&request, true).await? {
540 RouterCapabilityOrSource::Capability(_) => {
541 panic!("returned capability for debug operation")
542 }
543 RouterCapabilityOrSource::Source(source) => Ok(*source),
544 RouterCapabilityOrSource::Router(router) => router.route_debug(request, target).await,
545 };
546
547 #[cfg(target_os = "fuchsia")]
548 if self.enable_tracing {
549 trace::duration_end!(
550 "component_manager", "route_capability_debug",
551 "target" => self.moniker.as_str(),
552 "type" => self.default_request.build_type_name.to_string().as_str(),
553 "capability" => self.source_path.iter_segments().join("/").as_str()
554 );
555 }
556
557 result
558 }
559
560 fn error_info(&self) -> Option<RouterErrorInfo> {
561 Some(RouterErrorInfo {
562 capability_type: self.not_found_context.type_name,
563 name: self.source_path.basename().unwrap().to_owned(),
564 availability: self.default_request.availability.unwrap(),
565 })
566 }
567}