dhcpv6_client/
provider.rs

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
// Copyright 2020 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 fidl::endpoints::ServerEnd;
use fidl_fuchsia_net_dhcpv6::{ClientMarker, ClientProviderRequest, ClientProviderRequestStream};
use fidl_fuchsia_net_dhcpv6_ext::NewClientParams;

use futures::{Future, StreamExt as _};

use anyhow::Result;
use tracing::warn;

/// Handles client provider requests from the input stream.
pub(crate) async fn run_client_provider<Fut, F>(
    stream: ClientProviderRequestStream,
    serve_client: F,
) where
    Fut: Future<Output = Result<()>>,
    F: Fn(NewClientParams, ServerEnd<ClientMarker>) -> Fut,
{
    stream
        .for_each_concurrent(None, |request| async {
            match request {
                Ok(ClientProviderRequest::NewClient { params, request, control_handle: _ }) => {
                    let params: NewClientParams = match params.try_into() {
                        Ok(params) => params,
                        Err(e) => {
                            warn!("NewClientParams validation error: {}", e);
                            // All param fields are required.
                            request
                                .close_with_epitaph(zx::Status::INVALID_ARGS)
                                .unwrap_or_else(|e| warn!("closing NewClient request channel with epitaph INVALID_ARGS: {}", e));
                            return;
                        }
                    };
                    // `NewClientParams` does not implement `Clone`. It is also non-trivial to pass
                    // a reference of `params` to `serve_client` because that would require adding
                    // lifetimes in quite a few places.
                    let params_str = format!("{:?}", params);
                    let () =
                        serve_client(params, request).await.unwrap_or_else(|e: anyhow::Error| {
                            // TODO(https://fxbug.dev/42069288): Return error through
                            // a terminal event.
                            warn!("error running client with params {}: {:?}", params_str, e);
                        });
                }
                Err(e) => warn!("client provider request FIDL error: {}", e),
            }
        })
        .await
}

#[cfg(test)]
mod tests {
    use fidl::endpoints::create_endpoints;
    use fidl_fuchsia_net_dhcpv6::ClientProviderMarker;
    use fidl_fuchsia_net_dhcpv6_ext::ClientConfig;
    use fuchsia_async as fasync;
    use futures::join;

    use anyhow::{anyhow, Error};
    use assert_matches::assert_matches;
    use net_declare::fidl_socket_addr_v6;

    use super::*;

    async fn serve_client(
        _param: NewClientParams,
        _request: ServerEnd<ClientMarker>,
    ) -> Result<()> {
        Ok(())
    }

    async fn start_err_client(
        _param: NewClientParams,
        _request: ServerEnd<ClientMarker>,
    ) -> Result<()> {
        Err(anyhow!("fake test error"))
    }

    async fn test_client_provider<Fut, F>(serve_client: F)
    where
        Fut: Future<Output = Result<()>>,
        F: Fn(NewClientParams, ServerEnd<ClientMarker>) -> Fut,
    {
        let (client_end, server_end) = create_endpoints::<ClientProviderMarker>();
        let client_provider_proxy = client_end.into_proxy();
        let client_provider_stream = server_end.into_stream();

        let test_fut = async {
            for interface_id in 0..10 {
                let (_client_end, server_end) = create_endpoints::<ClientMarker>();
                client_provider_proxy
                    .new_client(
                        &NewClientParams {
                            interface_id: interface_id,
                            address: fidl_socket_addr_v6!("[fe01::1:2]:546"),
                            config: ClientConfig {
                                information_config: Default::default(),
                                non_temporary_address_config: Default::default(),
                                prefix_delegation_config: None,
                            },
                            duid: None,
                        }
                        .into(),
                        server_end,
                    )
                    .expect("failed to request new client");
            }
            drop(client_provider_proxy);
            Ok(())
        };
        let provider_fut = run_client_provider(client_provider_stream, serve_client);

        let (test_res, ()): (Result<_, Error>, ()) = join!(test_fut, provider_fut);
        assert_matches!(test_res, Ok(()));
    }

    #[fasync::run_singlethreaded(test)]
    async fn test_client_provider_serve_client_success() {
        let () = test_client_provider(serve_client).await;
    }

    #[fasync::run_singlethreaded(test)]
    async fn test_client_provider_should_keep_running_on_client_err() {
        let () = test_client_provider(start_err_client).await;
    }
}