1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use anyhow::{anyhow, Context as _, Result};
use std::collections::HashMap;
use {
    fidl_fuchsia_component as fcomponent, fidl_fuchsia_component_decl as fdecl,
    fidl_fuchsia_net_interfaces as fnet_interfaces,
    fidl_fuchsia_net_interfaces_ext as fnet_interfaces_ext,
};

/// Name of the collection that contains the hermetic network realm.
pub const HERMETIC_NETWORK_COLLECTION_NAME: &'static str = "enclosed-network";

/// Name of the realm that contains the hermetic network components.
pub const HERMETIC_NETWORK_REALM_NAME: &'static str = "hermetic-network";

/// Name of the collection that contains the test stub.
pub const STUB_COLLECTION_NAME: &'static str = "stubs";

/// Name of the component that corresponds to the test stub.
pub const STUB_COMPONENT_NAME: &'static str = "test-stub";

/// Returns true if the hermetic network realm exists.
///
/// The provided `realm_proxy` should correspond to the Network Test Realm
/// controller component.
///
/// # Errors
///
/// An error will be returned if the `realm_proxy` encounters an error while
/// attempting to list the children of the Network Test Realm.
pub async fn has_hermetic_network_realm(realm_proxy: &fcomponent::RealmProxy) -> Result<bool> {
    let child_ref = create_hermetic_network_realm_child_ref();
    has_running_child(
        fdecl::CollectionRef { name: HERMETIC_NETWORK_COLLECTION_NAME.to_string() },
        &child_ref,
        realm_proxy,
    )
    .await
}

/// Returns true if the hermetic-network realm contains a stub.
///
/// The provided `realm_proxy` should correspond to the hermetic-network realm.
///
/// # Errors
///
/// An error will be returned if the `realm_proxy` encounters an error while
/// attempting to list the children of the hermetic-network realm.
pub async fn has_stub(realm_proxy: &fcomponent::RealmProxy) -> Result<bool> {
    let child_ref = create_stub_child_ref();
    has_running_child(
        fdecl::CollectionRef { name: STUB_COLLECTION_NAME.to_string() },
        &child_ref,
        realm_proxy,
    )
    .await
}

/// Returns true if the `realm_proxy` contains the `expected_child_ref` within
/// the provided `collection_ref`.
async fn has_running_child(
    collection_ref: fdecl::CollectionRef,
    expected_child_ref: &fdecl::ChildRef,
    realm_proxy: &fcomponent::RealmProxy,
) -> Result<bool> {
    let (iterator_proxy, server_end) = fidl::endpoints::create_proxy().unwrap();
    let list_children_result = realm_proxy
        .list_children(&collection_ref, server_end)
        .await
        .context("failed to list_children")?;

    match list_children_result {
        Ok(()) => {
            let children =
                iterator_proxy.next().await.context("failed to iterate over children")?;

            Ok(children.iter().any(|child| child == expected_child_ref))
        }
        Err(error) => match error {
            // Variants that may be returned by the `ListChildren` method.
            // `CollectionNotFound` means that the hermetic network realm does
            // not exist. All other errors are propagated.
            fcomponent::Error::CollectionNotFound => Ok(false),
            fcomponent::Error::AccessDenied
            | fcomponent::Error::InstanceDied
            | fcomponent::Error::InvalidArguments
            // Variants that are not returned by the `ListChildren` method.
            | fcomponent::Error::InstanceAlreadyExists
            | fcomponent::Error::InstanceAlreadyStarted
            | fcomponent::Error::InstanceCannotResolve
            | fcomponent::Error::InstanceCannotUnresolve
            | fcomponent::Error::InstanceCannotStart
            | fcomponent::Error::InstanceNotFound
            | fcomponent::Error::Internal
            | fcomponent::Error::ResourceNotFound
            | fcomponent::Error::ResourceUnavailable
            | fcomponent::Error::Unsupported
            | fcomponent::ErrorUnknown!()
                => {
                Err(anyhow!("failed to list children: {:?}", error))
            }
        },
    }
}

/// Returns a `fdecl::ChildRef` that corresponds to the hermetic network realm.
pub fn create_hermetic_network_realm_child_ref() -> fdecl::ChildRef {
    fdecl::ChildRef {
        name: HERMETIC_NETWORK_REALM_NAME.to_string(),
        collection: Some(HERMETIC_NETWORK_COLLECTION_NAME.to_string()),
    }
}

/// Returns a `fdecl::ChildRef` that corresponds to the test stub.
pub fn create_stub_child_ref() -> fdecl::ChildRef {
    fdecl::ChildRef {
        name: STUB_COMPONENT_NAME.to_string(),
        collection: Some(STUB_COLLECTION_NAME.to_string()),
    }
}

/// Returns the id for the interface with `interface_name`.
///
/// If the interface is not found then, None is returned.
pub async fn get_interface_id<'a>(
    interface_name: &'a str,
    state_proxy: &'a fnet_interfaces::StateProxy,
) -> Result<Option<u64>> {
    let stream = fnet_interfaces_ext::event_stream_from_state(
        &state_proxy,
        fnet_interfaces_ext::IncludedAddresses::OnlyAssigned,
    )
    .context("failed to get interface stream")?;
    let interfaces = fnet_interfaces_ext::existing(
        stream,
        HashMap::<u64, fidl_fuchsia_net_interfaces_ext::PropertiesAndState<()>>::new(),
    )
    .await
    .context("failed to get existing interfaces")?;
    Ok(interfaces.values().find_map(
        |fidl_fuchsia_net_interfaces_ext::PropertiesAndState {
             properties: fidl_fuchsia_net_interfaces_ext::Properties { id, name, .. },
             state: _,
         }| {
            if name == interface_name {
                Some(id.get())
            } else {
                None
            }
        },
    ))
}