Skip to main content

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