routing_test_helpers/
availability.rs

1// Copyright 2022 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::{
6    CheckUse, ComponentEventRoute, ExpectedResult, RoutingTestModel, RoutingTestModelBuilder,
7    ServiceInstance,
8};
9use cm_rust::*;
10use cm_rust_testing::*;
11use moniker::Moniker;
12use std::marker::PhantomData;
13use std::path::{Path, PathBuf};
14use {fidl_fuchsia_io as fio, zx_status};
15
16pub struct CommonAvailabilityTest<T: RoutingTestModelBuilder> {
17    builder: PhantomData<T>,
18}
19
20#[derive(Debug)]
21struct TestCase {
22    /// The availability of either an `Offer` or `Expose` declaration.
23    provider_availability: Availability,
24    use_availability: Availability,
25}
26
27impl<T: RoutingTestModelBuilder> CommonAvailabilityTest<T> {
28    pub fn new() -> Self {
29        Self { builder: PhantomData }
30    }
31
32    const VALID_AVAILABILITY_PAIRS: &'static [TestCase] = &[
33        TestCase {
34            provider_availability: Availability::Required,
35            use_availability: Availability::Required,
36        },
37        TestCase {
38            provider_availability: Availability::Optional,
39            use_availability: Availability::Optional,
40        },
41        TestCase {
42            provider_availability: Availability::Required,
43            use_availability: Availability::Optional,
44        },
45        TestCase {
46            provider_availability: Availability::SameAsTarget,
47            use_availability: Availability::Required,
48        },
49        TestCase {
50            provider_availability: Availability::SameAsTarget,
51            use_availability: Availability::Optional,
52        },
53        TestCase {
54            provider_availability: Availability::Required,
55            use_availability: Availability::Transitional,
56        },
57        TestCase {
58            provider_availability: Availability::Optional,
59            use_availability: Availability::Transitional,
60        },
61        TestCase {
62            provider_availability: Availability::Transitional,
63            use_availability: Availability::Transitional,
64        },
65        TestCase {
66            provider_availability: Availability::SameAsTarget,
67            use_availability: Availability::Transitional,
68        },
69    ];
70
71    pub async fn test_offer_availability_successful_routes(&self) {
72        for test_case in Self::VALID_AVAILABILITY_PAIRS {
73            let components = vec![
74                (
75                    "a",
76                    ComponentDeclBuilder::new()
77                        .offer(
78                            OfferBuilder::service()
79                                .name("fuchsia.examples.EchoService")
80                                .source_static_child("b")
81                                .target_static_child("c")
82                                .availability(test_case.provider_availability),
83                        )
84                        .offer(
85                            OfferBuilder::protocol()
86                                .name("fuchsia.examples.Echo")
87                                .source_static_child("b")
88                                .target_static_child("c")
89                                .availability(test_case.provider_availability),
90                        )
91                        .offer(
92                            OfferBuilder::directory()
93                                .name("dir")
94                                .source_static_child("b")
95                                .target_static_child("c")
96                                .rights(fio::R_STAR_DIR)
97                                .availability(test_case.provider_availability),
98                        )
99                        .capability(
100                            CapabilityBuilder::directory()
101                                .name("data")
102                                .path("/data")
103                                .rights(fio::RW_STAR_DIR),
104                        )
105                        .capability(
106                            CapabilityBuilder::storage()
107                                .name("cache")
108                                .backing_dir("data")
109                                .source(StorageDirectorySource::Self_)
110                                .subdir("cache"),
111                        )
112                        .offer(
113                            OfferBuilder::storage()
114                                .name("cache")
115                                .source(OfferSource::Self_)
116                                .target_static_child("c")
117                                .availability(test_case.provider_availability),
118                        )
119                        .offer(
120                            OfferBuilder::event_stream()
121                                .name("started")
122                                .source(OfferSource::Parent)
123                                .target_static_child("c")
124                                .availability(test_case.provider_availability),
125                        )
126                        .child_default("b")
127                        .child_default("c")
128                        .build(),
129                ),
130                (
131                    "b",
132                    ComponentDeclBuilder::new()
133                        .capability(
134                            CapabilityBuilder::service()
135                                .name("fuchsia.examples.EchoService")
136                                .path("/svc/foo.service"),
137                        )
138                        .expose(
139                            ExposeBuilder::service()
140                                .name("fuchsia.examples.EchoService")
141                                .source(ExposeSource::Self_),
142                        )
143                        .capability(
144                            CapabilityBuilder::protocol()
145                                .name("fuchsia.examples.Echo")
146                                .path("/svc/foo"),
147                        )
148                        .expose(
149                            ExposeBuilder::protocol()
150                                .name("fuchsia.examples.Echo")
151                                .source(ExposeSource::Self_),
152                        )
153                        .capability(CapabilityBuilder::directory().name("dir").path("/data/dir"))
154                        .expose(ExposeBuilder::directory().name("dir").source(ExposeSource::Self_))
155                        .build(),
156                ),
157                (
158                    "c",
159                    ComponentDeclBuilder::new()
160                        .use_(
161                            UseBuilder::service()
162                                .name("fuchsia.examples.EchoService")
163                                .availability(test_case.use_availability),
164                        )
165                        .use_(
166                            UseBuilder::protocol()
167                                .name("fuchsia.examples.Echo")
168                                .availability(test_case.use_availability),
169                        )
170                        .use_(
171                            UseBuilder::directory()
172                                .name("dir")
173                                .path("/dir")
174                                .availability(test_case.use_availability),
175                        )
176                        .use_(
177                            UseBuilder::storage()
178                                .name("cache")
179                                .path("/storage")
180                                .availability(test_case.use_availability),
181                        )
182                        .use_(
183                            UseBuilder::event_stream()
184                                .name("started")
185                                .path("/event/stream")
186                                .availability(test_case.use_availability),
187                        )
188                        .build(),
189                ),
190            ];
191            let mut builder = T::new("a", components);
192            builder.set_builtin_capabilities(vec![CapabilityDecl::EventStream(EventStreamDecl {
193                name: "started".parse().unwrap(),
194            })]);
195            let model = builder.build().await;
196            model
197                .create_static_file(Path::new("dir/hippo"), "hello")
198                .await
199                .expect("failed to create file");
200            for check_use in vec![
201                CheckUse::Service {
202                    path: "/svc/fuchsia.examples.EchoService".parse().unwrap(),
203                    instance: ServiceInstance::Named("default".to_owned()),
204                    member: "echo".to_owned(),
205                    expected_res: ExpectedResult::Ok,
206                },
207                CheckUse::Protocol {
208                    path: "/svc/fuchsia.examples.Echo".parse().unwrap(),
209                    expected_res: ExpectedResult::Ok,
210                },
211                CheckUse::Directory {
212                    path: "/dir".parse().unwrap(),
213                    file: PathBuf::from("hippo"),
214                    expected_res: ExpectedResult::Ok,
215                },
216                CheckUse::Storage {
217                    path: "/storage".parse().unwrap(),
218                    storage_relation: Some(Moniker::try_from(vec!["c"]).unwrap()),
219                    from_cm_namespace: false,
220                    storage_subdir: Some("cache".to_string()),
221                    expected_res: ExpectedResult::Ok,
222                },
223                CheckUse::EventStream {
224                    expected_res: ExpectedResult::Ok,
225                    path: "/event/stream".parse().unwrap(),
226                    scope: vec![ComponentEventRoute { component: "/".to_string(), scope: None }],
227                    name: "started".parse().unwrap(),
228                },
229            ] {
230                model.check_use(vec!["c"].try_into().unwrap(), check_use).await;
231            }
232        }
233    }
234
235    pub async fn test_offer_availability_invalid_routes(&self) {
236        struct TestCase {
237            source: OfferSource,
238            storage_source: Option<OfferSource>,
239            offer_availability: Availability,
240            use_availability: Availability,
241        }
242        for test_case in &[
243            TestCase {
244                source: offer_source_static_child("b"),
245                storage_source: Some(OfferSource::Self_),
246                offer_availability: Availability::Optional,
247                use_availability: Availability::Required,
248            },
249            TestCase {
250                source: OfferSource::Void,
251                storage_source: None,
252                offer_availability: Availability::Optional,
253                use_availability: Availability::Required,
254            },
255            TestCase {
256                source: OfferSource::Void,
257                storage_source: None,
258                offer_availability: Availability::Optional,
259                use_availability: Availability::Optional,
260            },
261            TestCase {
262                source: OfferSource::Void,
263                storage_source: None,
264                offer_availability: Availability::Transitional,
265                use_availability: Availability::Optional,
266            },
267            TestCase {
268                source: OfferSource::Void,
269                storage_source: None,
270                offer_availability: Availability::Transitional,
271                use_availability: Availability::Required,
272            },
273        ] {
274            let components = vec![
275                (
276                    "a",
277                    ComponentDeclBuilder::new()
278                        .offer(
279                            OfferBuilder::service()
280                                .name("fuchsia.examples.EchoService")
281                                .source(test_case.source.clone())
282                                .target_static_child("c")
283                                .availability(test_case.offer_availability),
284                        )
285                        .offer(
286                            OfferBuilder::protocol()
287                                .name("fuchsia.examples.Echo")
288                                .source(test_case.source.clone())
289                                .target_static_child("c")
290                                .availability(test_case.offer_availability),
291                        )
292                        .offer(
293                            OfferBuilder::directory()
294                                .name("dir")
295                                .source(test_case.source.clone())
296                                .target_static_child("c")
297                                .rights(fio::Operations::CONNECT)
298                                .availability(test_case.offer_availability),
299                        )
300                        .offer(
301                            OfferBuilder::storage()
302                                .name("data")
303                                .source(
304                                    test_case
305                                        .storage_source
306                                        .as_ref()
307                                        .map(Clone::clone)
308                                        .unwrap_or(test_case.source.clone()),
309                                )
310                                .target_static_child("c")
311                                .availability(test_case.offer_availability),
312                        )
313                        .capability(
314                            CapabilityBuilder::storage()
315                                .name("data")
316                                .backing_dir("dir")
317                                .source(StorageDirectorySource::Child("b".into())),
318                        )
319                        .child_default("b")
320                        .child_default("c")
321                        .build(),
322                ),
323                (
324                    "b",
325                    ComponentDeclBuilder::new()
326                        .capability(
327                            CapabilityBuilder::service()
328                                .name("fuchsia.examples.EchoService")
329                                .path("/svc/foo.service"),
330                        )
331                        .expose(
332                            ExposeBuilder::service()
333                                .name("fuchsia.examples.EchoService")
334                                .source(ExposeSource::Self_),
335                        )
336                        .capability(
337                            CapabilityBuilder::protocol()
338                                .name("fuchsia.examples.Echo")
339                                .path("/svc/foo"),
340                        )
341                        .expose(
342                            ExposeBuilder::protocol()
343                                .name("fuchsia.examples.Echo")
344                                .source(ExposeSource::Self_),
345                        )
346                        .capability(
347                            CapabilityBuilder::directory()
348                                .name("dir")
349                                .path("/dir")
350                                .rights(fio::Operations::CONNECT),
351                        )
352                        .expose(ExposeBuilder::directory().name("dir").source(ExposeSource::Self_))
353                        .build(),
354                ),
355                (
356                    "c",
357                    ComponentDeclBuilder::new()
358                        .use_(
359                            UseBuilder::service()
360                                .name("fuchsia.examples.EchoService")
361                                .availability(test_case.use_availability),
362                        )
363                        .use_(
364                            UseBuilder::protocol()
365                                .name("fuchsia.examples.Echo")
366                                .availability(test_case.use_availability),
367                        )
368                        .use_(
369                            UseBuilder::directory()
370                                .name("dir")
371                                .path("/dir")
372                                .rights(fio::Operations::CONNECT)
373                                .availability(test_case.use_availability),
374                        )
375                        .use_(
376                            UseBuilder::storage()
377                                .name("data")
378                                .path("/data")
379                                .availability(test_case.use_availability),
380                        )
381                        .build(),
382                ),
383            ];
384            let model = T::new("a", components).build().await;
385            for check_use in vec![
386                CheckUse::Service {
387                    path: "/svc/fuchsia.examples.EchoService".parse().unwrap(),
388                    instance: ServiceInstance::Named("default".to_owned()),
389                    member: "echo".to_owned(),
390                    expected_res: ExpectedResult::Err(zx_status::Status::NOT_FOUND),
391                },
392                CheckUse::Protocol {
393                    path: "/svc/fuchsia.examples.Echo".parse().unwrap(),
394                    expected_res: ExpectedResult::Err(zx_status::Status::NOT_FOUND),
395                },
396                CheckUse::Directory {
397                    path: "/dir".parse().unwrap(),
398                    file: PathBuf::from("hippo"),
399                    expected_res: ExpectedResult::Err(zx_status::Status::NOT_FOUND),
400                },
401                CheckUse::Storage {
402                    path: "/data".parse().unwrap(),
403                    storage_relation: None,
404                    from_cm_namespace: false,
405                    storage_subdir: None,
406                    expected_res: ExpectedResult::Err(zx_status::Status::NOT_FOUND),
407                },
408            ] {
409                model.check_use(vec!["c"].try_into().unwrap(), check_use).await;
410            }
411        }
412    }
413
414    /// Creates the following topology:
415    ///
416    ///           a
417    ///          /
418    ///         /
419    ///        b
420    ///
421    /// And verifies exposing a variety of capabilities from `b`, testing the combination of
422    /// availability settings and capability types.
423    ///
424    /// Storage and event stream capabilities cannot be exposed, hence omitted.
425    pub async fn test_expose_availability_successful_routes(&self) {
426        for test_case in Self::VALID_AVAILABILITY_PAIRS {
427            let components = vec![
428                (
429                    "a",
430                    ComponentDeclBuilder::new()
431                        .use_(
432                            UseBuilder::service()
433                                .source_static_child("b")
434                                .name("fuchsia.examples.EchoService")
435                                .path("/svc/fuchsia.examples.EchoService_a")
436                                .availability(test_case.use_availability),
437                        )
438                        .use_(
439                            UseBuilder::protocol()
440                                .source_static_child("b")
441                                .name("fuchsia.examples.Echo")
442                                .path("/svc/fuchsia.examples.Echo_a")
443                                .availability(test_case.use_availability),
444                        )
445                        .use_(
446                            UseBuilder::directory()
447                                .source_static_child("b")
448                                .name("dir")
449                                .path("/dir_a")
450                                .availability(test_case.use_availability),
451                        )
452                        .child_default("b")
453                        .build(),
454                ),
455                (
456                    "b",
457                    ComponentDeclBuilder::new()
458                        .capability(
459                            CapabilityBuilder::service()
460                                .name("fuchsia.examples.EchoService")
461                                .path("/svc/foo.service"),
462                        )
463                        .expose(
464                            ExposeBuilder::service()
465                                .name("fuchsia.examples.EchoService")
466                                .source(ExposeSource::Self_)
467                                .availability(test_case.provider_availability),
468                        )
469                        .capability(
470                            CapabilityBuilder::protocol()
471                                .name("fuchsia.examples.Echo")
472                                .path("/svc/foo"),
473                        )
474                        .expose(
475                            ExposeBuilder::protocol()
476                                .name("fuchsia.examples.Echo")
477                                .source(ExposeSource::Self_)
478                                .availability(test_case.provider_availability),
479                        )
480                        .capability(CapabilityBuilder::directory().name("dir").path("/data/dir"))
481                        .expose(
482                            ExposeBuilder::directory()
483                                .name("dir")
484                                .source(ExposeSource::Self_)
485                                .availability(test_case.provider_availability),
486                        )
487                        .build(),
488                ),
489            ];
490            let builder = T::new("a", components);
491            let model = builder.build().await;
492
493            // Add a file to the directory capability in the component that declared it, so "b".
494            model
495                .create_static_file(Path::new("dir/hippo"), "hello")
496                .await
497                .expect("failed to create file");
498
499            for check_use in vec![
500                CheckUse::Service {
501                    path: "/svc/fuchsia.examples.EchoService_a".parse().unwrap(),
502                    instance: ServiceInstance::Named("default".to_owned()),
503                    member: "echo".to_owned(),
504                    expected_res: ExpectedResult::Ok,
505                },
506                CheckUse::Protocol {
507                    path: "/svc/fuchsia.examples.Echo_a".parse().unwrap(),
508                    expected_res: ExpectedResult::Ok,
509                },
510                CheckUse::Directory {
511                    path: "/dir_a".parse().unwrap(),
512                    file: PathBuf::from("hippo"),
513                    expected_res: ExpectedResult::Ok,
514                },
515            ] {
516                model.check_use(Moniker::root(), check_use).await;
517            }
518
519            for check_use in vec![
520                CheckUse::Service {
521                    path: "/fuchsia.examples.EchoService".parse().unwrap(),
522                    instance: ServiceInstance::Named("default".to_owned()),
523                    member: "echo".to_owned(),
524                    expected_res: ExpectedResult::Ok,
525                },
526                CheckUse::Protocol {
527                    path: "/fuchsia.examples.Echo".parse().unwrap(),
528                    expected_res: ExpectedResult::Ok,
529                },
530                CheckUse::Directory {
531                    path: "/dir".parse().unwrap(),
532                    file: PathBuf::from("hippo"),
533                    expected_res: ExpectedResult::Ok,
534                },
535            ] {
536                model.check_use_exposed_dir(vec!["b"].try_into().unwrap(), check_use).await;
537            }
538        }
539    }
540
541    /// Creates the following topology:
542    ///
543    ///           a
544    ///          /
545    ///         /
546    ///        b
547    ///
548    /// And verifies exposing a variety of capabilities from `b`. Except that either the route is
549    /// broken, or the rules around availability are broken.
550    pub async fn test_expose_availability_invalid_routes(&self) {
551        struct TestCase {
552            source: ExposeSource,
553            expose_availability: Availability,
554            use_availability: Availability,
555        }
556        for test_case in &[
557            TestCase {
558                source: ExposeSource::Self_,
559                expose_availability: Availability::Optional,
560                use_availability: Availability::Required,
561            },
562            TestCase {
563                source: ExposeSource::Void,
564                expose_availability: Availability::Optional,
565                use_availability: Availability::Required,
566            },
567            TestCase {
568                source: ExposeSource::Void,
569                expose_availability: Availability::Optional,
570                use_availability: Availability::Optional,
571            },
572            TestCase {
573                source: ExposeSource::Void,
574                expose_availability: Availability::Transitional,
575                use_availability: Availability::Optional,
576            },
577            TestCase {
578                source: ExposeSource::Void,
579                expose_availability: Availability::Transitional,
580                use_availability: Availability::Required,
581            },
582        ] {
583            let components = vec![
584                (
585                    "a",
586                    ComponentDeclBuilder::new()
587                        .use_(
588                            UseBuilder::service()
589                                .source_static_child("b")
590                                .name("fuchsia.examples.EchoService")
591                                .path("/svc/fuchsia.examples.EchoService_a")
592                                .availability(test_case.use_availability),
593                        )
594                        .use_(
595                            UseBuilder::protocol()
596                                .source_static_child("b")
597                                .name("fuchsia.examples.Echo")
598                                .path("/svc/fuchsia.examples.Echo_a")
599                                .availability(test_case.use_availability),
600                        )
601                        .use_(
602                            UseBuilder::directory()
603                                .source_static_child("b")
604                                .name("dir")
605                                .path("/dir_a")
606                                .availability(test_case.use_availability),
607                        )
608                        .child_default("b")
609                        .build(),
610                ),
611                (
612                    "b",
613                    ComponentDeclBuilder::new()
614                        .capability(
615                            CapabilityBuilder::service()
616                                .name("fuchsia.examples.EchoService")
617                                .path("/svc/foo.service"),
618                        )
619                        .expose(
620                            ExposeBuilder::service()
621                                .name("fuchsia.examples.EchoService")
622                                .source(test_case.source.clone())
623                                .availability(test_case.expose_availability),
624                        )
625                        .capability(
626                            CapabilityBuilder::protocol()
627                                .name("fuchsia.examples.Echo")
628                                .path("/svc/foo"),
629                        )
630                        .expose(
631                            ExposeBuilder::protocol()
632                                .name("fuchsia.examples.Echo")
633                                .source(test_case.source.clone())
634                                .availability(test_case.expose_availability),
635                        )
636                        .capability(CapabilityBuilder::directory().name("dir").path("/data/dir"))
637                        .expose(
638                            ExposeBuilder::directory()
639                                .name("dir")
640                                .source(test_case.source.clone())
641                                .availability(test_case.expose_availability),
642                        )
643                        .build(),
644                ),
645            ];
646            let builder = T::new("a", components);
647            let model = builder.build().await;
648
649            // Add a file to the directory capability in the component that declared it, so "b".
650            model
651                .create_static_file(Path::new("dir/hippo"), "hello")
652                .await
653                .expect("failed to create file");
654            for check_use in vec![
655                CheckUse::Service {
656                    path: "/svc/fuchsia.examples.EchoService_a".parse().unwrap(),
657                    instance: ServiceInstance::Named("default".to_owned()),
658                    member: "echo".to_owned(),
659                    expected_res: ExpectedResult::Err(zx_status::Status::NOT_FOUND),
660                },
661                CheckUse::Protocol {
662                    path: "/svc/fuchsia.examples.Echo_a".parse().unwrap(),
663                    expected_res: ExpectedResult::Err(zx_status::Status::NOT_FOUND),
664                },
665                CheckUse::Directory {
666                    path: "/dir_a".parse().unwrap(),
667                    file: PathBuf::from("hippo"),
668                    expected_res: ExpectedResult::Err(zx_status::Status::NOT_FOUND),
669                },
670            ] {
671                model.check_use(Moniker::root(), check_use).await;
672            }
673        }
674    }
675}