Skip to main content

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