// Copyright 2018 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_fuchsia_net_dns::{DnsConfig, DnsPolicyRequest, DnsPolicyRequestStream, NetError, Status},
    fuchsia_async as fasync,
    fuchsia_syslog::fx_log_err,
    futures::prelude::*,
};

pub fn spawn_net_dns_fidl_server(
    resolver_admin: fidl_fuchsia_netstack::ResolverAdminProxy,
    stream: DnsPolicyRequestStream,
) {
    let fut = serve_fidl_requests(resolver_admin, stream)
        .unwrap_or_else(|e| fx_log_err!("failed to serve net dns FIDL call: {}", e));
    fasync::spawn(fut);
}

async fn serve_fidl_requests(
    resolver_admin: fidl_fuchsia_netstack::ResolverAdminProxy,
    stream: DnsPolicyRequestStream,
) -> Result<(), fidl::Error> {
    await!(stream.try_for_each(|req| handle_request(&resolver_admin, req)))
}

async fn handle_request(
    resolver_admin: &fidl_fuchsia_netstack::ResolverAdminProxy,
    req: DnsPolicyRequest,
) -> Result<(), fidl::Error> {
    match req {
        DnsPolicyRequest::SetDnsConfig { config, responder } => {
            let mut r = set_dns_config(resolver_admin, config);
            responder.send(&mut r)
        }
        DnsPolicyRequest::GetDnsConfig { responder } => {
            let mut r = get_dns_config();
            responder.send(&mut r)
        }
    }
}

fn set_dns_config(
    resolver_admin: &fidl_fuchsia_netstack::ResolverAdminProxy,
    config: DnsConfig,
) -> NetError {
    let mut parse_results: Vec<fidl_fuchsia_net::IpAddress> =
        config.dns_servers.into_iter().map(Into::into).collect();

    if let Err(e) = resolver_admin.set_name_servers(&mut parse_results.iter_mut()) {
        fx_log_err!("failed to set DNS server: {}", e);
        return NetError { status: Status::UnknownError };
    }

    NetError { status: Status::Ok }
}

fn get_dns_config() -> DnsConfig {
    // TODO(NET-1430): Add FIDL getters for current netcfg DNS server IP addresses.
    fx_log_err!("netdns: unimplemented get_dns_config called!");
    DnsConfig {
        dns_servers: vec![fidl_fuchsia_net::IpAddress::Ipv4(fidl_fuchsia_net::Ipv4Address {
            addr: [0, 0, 0, 0],
        })],
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use {
        fidl::endpoints::create_proxy, fidl_fuchsia_net_dns::DnsPolicyMarker, futures::task::Poll,
        pin_utils::pin_mut,
    };

    fn build_address() -> fidl_fuchsia_net::IpAddress {
        fidl_fuchsia_net::IpAddress::Ipv4(fidl_fuchsia_net::Ipv4Address { addr: [1, 1, 1, 1] })
    }

    #[test]
    fn set_dns_test() {
        let mut exec = fasync::Executor::new().expect("failed to create an executor");
        // Set up mock resolver_admin fidl server.
        let (resolver_admin_proxy, resolver_admin_server) =
            create_proxy::<fidl_fuchsia_netstack::ResolverAdminMarker>()
                .expect("failed to create resolver_admin fidl");
        let mut resolver_admin_stream =
            resolver_admin_server.into_stream().expect("failed to create a request stream.");

        // Set up real dns fidl server and client.
        let (dnspolicy_proxy, dnspolicy_server) =
            create_proxy::<DnsPolicyMarker>().expect("failed to create dnspolicy fidl");

        let dnspolicy_stream =
            dnspolicy_server.into_stream().expect("failed to create a request stream.");

        let dnspolicy_service_task = serve_fidl_requests(resolver_admin_proxy, dnspolicy_stream)
            .unwrap_or_else(|e| fx_log_err!("failed to serve dnspolicy FIDL call: {}", e));

        // Call dnspolicy FIDL call.
        let mut config = DnsConfig { dns_servers: vec![build_address()] };
        let client_fut = dnspolicy_proxy.set_dns_config(&mut config);

        // Let dnspolicy client run to stall.
        pin_mut!(client_fut);
        assert!(exec.run_until_stalled(&mut client_fut).is_pending());

        // Let dnspolicy server run to stall.
        pin_mut!(dnspolicy_service_task);
        assert!(exec.run_until_stalled(&mut dnspolicy_service_task).is_pending());

        // Let resolver_admin server run to stall and check that we got an appropriate FIDL call.
        let event = match exec.run_until_stalled(&mut resolver_admin_stream.next()) {
            Poll::Ready(Some(Ok(req))) => req,
            _ => panic!("Expected a Netstack fidl call, but there is none!"),
        };

        match event {
            fidl_fuchsia_netstack::ResolverAdminRequest::SetNameServers {
                servers,
                control_handle: _,
            } => {
                let expected_servers = vec![build_address()];
                assert_eq!(expected_servers, servers);
            }
        };

        // Let dnspolicy client run until ready, and check that we got a correct response code.
        let response = match exec.run_until_stalled(&mut client_fut) {
            Poll::Ready(Ok(req)) => req,
            _ => panic!("Expected a response from dnspolicy fidl call, but there is none!"),
        };

        assert_eq!(NetError { status: Status::Ok }, response);
    }
}
