# Copyright 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import logging
import time

from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib.cros import avahi_utils
from autotest_lib.client.common_lib.cros.network import interface
from autotest_lib.client.common_lib.cros.network import iw_runner
from autotest_lib.client.common_lib.cros.network import netblock
from autotest_lib.client.common_lib.cros.network import ping_runner
from autotest_lib.client.common_lib.cros.network import xmlrpc_security_types
from autotest_lib.client.common_lib.cros.tendo import peerd_config
from autotest_lib.client.common_lib.cros.tendo import buffet_config
from autotest_lib.client.common_lib.cros.tendo import privet_helper
from autotest_lib.server import site_linux_router
from autotest_lib.server import test
from autotest_lib.server.cros.network import hostap_config
from autotest_lib.server.cros.network import wifi_client


PASSPHRASE = 'chromeos'

PRIVET_AP_STARTUP_TIMEOUT_SECONDS = 30
PRIVET_MDNS_RECORD_TIMEOUT_SECONDS = 10
PRIVET_CONNECT_TIMEOUT_SECONDS = 30

POLLING_PERIOD = 0.5


class buffet_PrivetSetupFlow(test.test):
    """This test validates the privet pairing/authentication/setup flow."""
    version = 1

    def warmup(self, host, router_hostname=None):
        self._router = None
        self._shill_xmlrpc_proxy = None
        config = buffet_config.BuffetConfig(
                log_verbosity=3,
                enable_ping=True,
                disable_pairing_security=True,
                device_whitelist='any',
                options={'wifi_bootstrap_mode': 'automatic'})
        config.restart_with_config(host=host)
        self._router = site_linux_router.build_router_proxy(
                test_name=self.__class__.__name__,
                client_hostname=host.hostname,
                router_addr=router_hostname,
                enable_avahi=True)
        self._shill_xmlrpc_proxy = wifi_client.get_xmlrpc_proxy(host)
        # Cleans up profiles, wifi credentials, sandboxes our new credentials.
        self._shill_xmlrpc_proxy.init_test_network_state()
        peerd_config.PeerdConfig(verbosity_level=3).restart_with_config(
                host=host)


    def cleanup(self, host):
        if self._shill_xmlrpc_proxy is not None:
            self._shill_xmlrpc_proxy.clean_profiles()
        if self._router is not None:
            self._router.close()
        buffet_config.naive_restart(host=host)


    def run_once(self, host):
        helper = privet_helper.PrivetHelper(host=host)
        logging.info('Looking for privet bootstrapping network from DUT.')
        scan_interface = self._router.get_wlanif(2437, 'managed')
        self._router.host.run('%s link set %s up' %
                              (self._router.cmd_ip, scan_interface))
        start_time = time.time()
        privet_bss = None
        while time.time() - start_time < PRIVET_AP_STARTUP_TIMEOUT_SECONDS:
            bss_list = self._router.iw_runner.scan(scan_interface)
            for bss in bss_list or []:
                if helper.is_softap_ssid(bss.ssid):
                    privet_bss = bss
        if privet_bss is None:
            raise error.TestFail('Device did not start soft AP in time.')
        self._router.release_interface(scan_interface)

        # Get the netblock of the interface running the AP.
        dut_iw_runner = iw_runner.IwRunner(remote_host=host)
        devs = dut_iw_runner.list_interfaces(desired_if_type='AP')
        if not devs:
            raise error.TestFail('No AP devices on DUT?')
        ap_interface = interface.Interface(devs[0].if_name, host=host)
        ap_netblock = netblock.from_addr(ap_interface.ipv4_address_and_prefix)

        # Set up an AP on the router in the 5Ghz range with WPA2 security.
        wpa_config = xmlrpc_security_types.WPAConfig(
                psk=PASSPHRASE,
                wpa_mode=xmlrpc_security_types.WPAConfig.MODE_PURE_WPA2,
                wpa2_ciphers=[xmlrpc_security_types.WPAConfig.CIPHER_CCMP])
        router_conf = hostap_config.HostapConfig(
                frequency=5240, security_config=wpa_config,
                mode=hostap_config.HostapConfig.MODE_11N_PURE)
        self._router.hostap_configure(router_conf)

        # Connect the other interface on the router to the AP on the client
        # at a hardcoded IP address.
        self._router.configure_managed_station(
                privet_bss.ssid, privet_bss.frequency,
                ap_netblock.get_addr_in_block(200))
        station_interface = self._router.get_station_interface(instance=0)
        logging.debug('Set up station on %s', station_interface)
        self._router.ping(ping_runner.PingConfig(ap_netblock.addr, count=3))

        logging.info('Looking for privet webserver in mDNS records.')
        start_time = time.time()
        while time.time() - start_time < PRIVET_MDNS_RECORD_TIMEOUT_SECONDS:
            all_records = avahi_utils.avahi_browse(host=self._router.host)
            records = [record for record in all_records
                       if (record.interface == station_interface and
                           record.record_type == '_privet._tcp')]
            if records:
                break
            time.sleep(POLLING_PERIOD)
        if not records:
            raise error.TestFail('Did not find privet mDNS records in time.')
        if len(records) > 1:
            raise error.TestFail('Should not see multiple privet records.')
        privet_record = records[0]
        # TODO(wiley) pull the HTTPs port number out of the /info API.
        helper = privet_helper.PrivetdHelper(
                host=self._router.host,
                hostname=privet_record.address,
                http_port=int(privet_record.port))
        helper.ping_server()

        # Now configure the client with WiFi credentials.
        auth_token = helper.privet_auth()
        ssid = self._router.get_ssid()
        data = helper.setup_add_wifi_credentials(ssid, PASSPHRASE)
        helper.setup_start(data, auth_token)

        logging.info('Waiting for DUT to connect to router network.')
        start_time = time.time()
        # Wait for the DUT to take down the AP.
        while time.time() - start_time < PRIVET_CONNECT_TIMEOUT_SECONDS:
            if not dut_iw_runner.list_interfaces(desired_if_type='AP'):
                break
            time.sleep(POLLING_PERIOD)
        else:
            raise error.TestFail('Timeout waiting for DUT to take down AP.')

        # But we should be able to ping the client from the router's AP.
        while time.time() - start_time < PRIVET_CONNECT_TIMEOUT_SECONDS:
            if dut_iw_runner.list_interfaces(desired_if_type='managed'):
                break
            time.sleep(POLLING_PERIOD)
        else:
            raise error.TestFail('Timeout waiting for DUT managerd interface.')

        while time.time() - start_time < PRIVET_CONNECT_TIMEOUT_SECONDS:
            devs = dut_iw_runner.list_interfaces(desired_if_type='managed')
            if devs:
                managed_interface = interface.Interface(devs[0].if_name,
                                                        host=host)
                # Check if we have an IP yet.
                if managed_interface.ipv4_address_and_prefix:
                    break
            time.sleep(POLLING_PERIOD)
        else:
            raise error.TestFail('Timeout waiting for DUT managerd interface.')

        managed_netblock = netblock.from_addr(
                managed_interface.ipv4_address_and_prefix)
        while time.time() - start_time < PRIVET_CONNECT_TIMEOUT_SECONDS:
            PING_COUNT = 3
            result = self._router.ping(
                    ping_runner.PingConfig(managed_netblock.addr,
                                           ignore_result=True,
                                           count=PING_COUNT))
            if result.received == PING_COUNT:
                break
            time.sleep(POLLING_PERIOD)
        else:
            raise error.TestFail('Timeout before ping was successful.')

        # And buffet should think it is online as well.
        helper = privet_helper.PrivetdHelper(
                host=host, hostname=managed_netblock.addr,
                http_port=int(privet_record.port))
        helper.ping_server()
        if not helper.wifi_setup_was_successful(ssid, auth_token):
            raise error.TestFail('Device claims to be offline, but is online.')