component_debug/
capability.rs

1// Copyright 2020 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::realm::{
6    get_all_instances, get_resolved_declaration, GetAllInstancesError, GetDeclarationError,
7};
8use cm_rust::{
9    CapabilityDecl, ComponentDecl, ExposeDecl, ExposeDeclCommon, OfferDecl, OfferDeclCommon,
10    SourceName, UseDecl, UseDeclCommon,
11};
12use fidl_fuchsia_sys2 as fsys;
13use moniker::Moniker;
14use thiserror::Error;
15
16#[derive(Debug, Error)]
17pub enum FindInstancesError {
18    #[error("failed to get all instances: {0}")]
19    GetAllInstancesError(#[from] GetAllInstancesError),
20
21    #[error("failed to get manifest for {moniker}: {err}")]
22    GetDeclarationError {
23        moniker: Moniker,
24        #[source]
25        err: GetDeclarationError,
26    },
27}
28
29pub enum RouteSegment {
30    /// The capability was used by a component instance in its manifest.
31    UseBy { moniker: Moniker, capability: UseDecl },
32
33    /// The capability was offered by a component instance in its manifest.
34    OfferBy { moniker: Moniker, capability: OfferDecl },
35
36    /// The capability was exposed by a component instance in its manifest.
37    ExposeBy { moniker: Moniker, capability: ExposeDecl },
38
39    /// The capability was declared by a component instance in its manifest.
40    DeclareBy { moniker: Moniker, capability: CapabilityDecl },
41}
42
43impl std::fmt::Display for RouteSegment {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        match self {
46            Self::UseBy { moniker, capability } => {
47                write!(
48                    f,
49                    "`{}` used `{}` from {}",
50                    moniker,
51                    capability.source_name(),
52                    capability.source()
53                )
54            }
55            Self::OfferBy { moniker, capability } => {
56                write!(
57                    f,
58                    "`{}` offered `{}` from {} to {}",
59                    moniker,
60                    capability.source_name(),
61                    capability.source(),
62                    capability.target()
63                )
64            }
65            Self::ExposeBy { moniker, capability } => {
66                write!(
67                    f,
68                    "`{}` exposed `{}` from {} to {}",
69                    moniker,
70                    capability.source_name(),
71                    capability.source(),
72                    capability.target()
73                )
74            }
75            Self::DeclareBy { moniker, capability } => {
76                write!(f, "`{}` declared capability `{}`", moniker, capability.name())
77            }
78        }
79    }
80}
81/// Find components that reference a capability matching the given |query|.
82pub async fn get_all_route_segments(
83    query: String,
84    realm_query: &fsys::RealmQueryProxy,
85) -> Result<Vec<RouteSegment>, FindInstancesError> {
86    let instances = get_all_instances(realm_query).await?;
87    let mut segments = vec![];
88
89    for instance in instances {
90        match get_resolved_declaration(&instance.moniker, realm_query).await {
91            Ok(decl) => {
92                let mut component_segments = get_segments(&instance.moniker, decl, &query);
93                segments.append(&mut component_segments)
94            }
95            // If the instance is not yet resolved, then we can't get its resolved declaration. If
96            // the component doesn't exist, then it's been destroyed since the `get_all_instances`
97            // call and we can't get its resolved declaration. Both of these things are expected,
98            // so ignore these errors.
99            Err(
100                GetDeclarationError::InstanceNotResolved(_)
101                | GetDeclarationError::InstanceNotFound(_),
102            ) => continue,
103            Err(err) => {
104                return Err(FindInstancesError::GetDeclarationError {
105                    moniker: instance.moniker.clone(),
106                    err,
107                })
108            }
109        }
110    }
111
112    Ok(segments)
113}
114
115/// Determine if a capability matching the |query| is declared, exposed, used or offered by
116/// this component.
117fn get_segments(moniker: &Moniker, manifest: ComponentDecl, query: &str) -> Vec<RouteSegment> {
118    let mut segments = vec![];
119
120    for capability in manifest.capabilities {
121        if capability.name().to_string().contains(query) {
122            segments.push(RouteSegment::DeclareBy { moniker: moniker.clone(), capability });
123        }
124    }
125
126    for expose in manifest.exposes {
127        if expose.source_name().to_string().contains(query) {
128            segments.push(RouteSegment::ExposeBy { moniker: moniker.clone(), capability: expose });
129        }
130    }
131
132    for use_ in manifest.uses {
133        if use_.source_name().to_string().contains(query) {
134            segments.push(RouteSegment::UseBy { moniker: moniker.clone(), capability: use_ });
135        }
136    }
137
138    for offer in manifest.offers {
139        if offer.source_name().to_string().contains(query) {
140            segments.push(RouteSegment::OfferBy { moniker: moniker.clone(), capability: offer });
141        }
142    }
143
144    segments
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150    use crate::test_utils::*;
151    use cm_rust::*;
152    use cm_rust_testing::*;
153    use std::collections::HashMap;
154
155    fn create_realm_query() -> fsys::RealmQueryProxy {
156        serve_realm_query(
157            vec![fsys::Instance {
158                moniker: Some("./my_foo".to_string()),
159                url: Some("fuchsia-pkg://fuchsia.com/foo#meta/foo.cm".to_string()),
160                instance_id: None,
161                resolved_info: Some(fsys::ResolvedInfo {
162                    resolved_url: Some("fuchsia-pkg://fuchsia.com/foo#meta/foo.cm".to_string()),
163                    execution_info: None,
164                    ..Default::default()
165                }),
166                ..Default::default()
167            }],
168            HashMap::from([(
169                "./my_foo".to_string(),
170                ComponentDeclBuilder::new()
171                    .child(
172                        ChildBuilder::new()
173                            .name("my_bar")
174                            .url("fuchsia-pkg://fuchsia.com/bar#meta/bar.cm"),
175                    )
176                    .protocol_default("fuchsia.foo.bar")
177                    .use_(UseBuilder::protocol().name("fuchsia.foo.bar"))
178                    .expose(
179                        ExposeBuilder::protocol()
180                            .name("fuchsia.foo.bar")
181                            .source(ExposeSource::Self_),
182                    )
183                    .offer(
184                        OfferBuilder::protocol()
185                            .name("fuchsia.foo.bar")
186                            .source(OfferSource::Self_)
187                            .target_static_child("my_bar"),
188                    )
189                    .build()
190                    .native_into_fidl(),
191            )]),
192            HashMap::new(),
193            HashMap::new(),
194        )
195    }
196
197    #[fuchsia::test]
198    async fn segments() {
199        let realm_query = create_realm_query();
200
201        let segments =
202            get_all_route_segments("fuchsia.foo.bar".to_string(), &realm_query).await.unwrap();
203
204        assert_eq!(segments.len(), 4);
205
206        let mut found_use = false;
207        let mut found_offer = false;
208        let mut found_expose = false;
209        let mut found_declaration = false;
210
211        for segment in segments {
212            match segment {
213                RouteSegment::UseBy { moniker, capability } => {
214                    found_use = true;
215                    assert_eq!(moniker, "/my_foo".try_into().unwrap());
216                    assert_eq!(
217                        capability,
218                        UseDecl::Protocol(UseProtocolDecl {
219                            source: UseSource::Parent,
220                            source_name: "fuchsia.foo.bar".parse().unwrap(),
221                            source_dictionary: Default::default(),
222                            target_path: "/svc/fuchsia.foo.bar".parse().unwrap(),
223                            dependency_type: DependencyType::Strong,
224                            availability: Availability::Required
225                        })
226                    );
227                }
228                RouteSegment::OfferBy { moniker, capability } => {
229                    found_offer = true;
230                    assert_eq!(moniker, "/my_foo".try_into().unwrap());
231                    assert_eq!(
232                        capability,
233                        OfferDecl::Protocol(OfferProtocolDecl {
234                            source: OfferSource::Self_,
235                            source_name: "fuchsia.foo.bar".parse().unwrap(),
236                            source_dictionary: Default::default(),
237                            target: OfferTarget::Child(ChildRef {
238                                name: "my_bar".parse().unwrap(),
239                                collection: None,
240                            }),
241                            target_name: "fuchsia.foo.bar".parse().unwrap(),
242                            dependency_type: DependencyType::Strong,
243                            availability: Availability::Required
244                        })
245                    );
246                }
247                RouteSegment::ExposeBy { moniker, capability } => {
248                    found_expose = true;
249                    assert_eq!(moniker, "/my_foo".try_into().unwrap());
250                    assert_eq!(
251                        capability,
252                        ExposeDecl::Protocol(ExposeProtocolDecl {
253                            source: ExposeSource::Self_,
254                            source_name: "fuchsia.foo.bar".parse().unwrap(),
255                            source_dictionary: Default::default(),
256                            target: ExposeTarget::Parent,
257                            target_name: "fuchsia.foo.bar".parse().unwrap(),
258                            availability: Availability::Required
259                        })
260                    );
261                }
262                RouteSegment::DeclareBy { moniker, capability } => {
263                    found_declaration = true;
264                    assert_eq!(moniker, "/my_foo".try_into().unwrap());
265                    assert_eq!(
266                        capability,
267                        CapabilityDecl::Protocol(ProtocolDecl {
268                            name: "fuchsia.foo.bar".parse().unwrap(),
269                            source_path: Some("/svc/fuchsia.foo.bar".parse().unwrap()),
270                            delivery: Default::default(),
271                        })
272                    );
273                }
274            }
275        }
276
277        assert!(found_use);
278        assert!(found_expose);
279        assert!(found_offer);
280        assert!(found_declaration);
281    }
282}