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
// 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 fuchsia_zircon as zx;
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().expect("failed to create test client proxy");
        let client_provider_stream =
            server_end.into_stream().expect("failed to create test request 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,
                            },
                        }
                        .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;
    }
}