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