# Copyright (c) 2013 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 os
import sys
import tempfile
import time

from autotest_lib.client.bin import test
from autotest_lib.client.common_lib import error, utils
from autotest_lib.client.cros import p2p_utils
from autotest_lib.client.cros.netprotos import cros_p2p, zeroconf


class p2p_ServeFiles(test.test):
    """The P2P Server class tester.

    This class runs the p2p service (p2p-server and p2p-http-server) and checks
    that the DUT is serving the shared files on the network.
    """
    version = 1

    def setup(self):
        self.job.setup_dep(['lansim'])


    def initialize(self):
        dep = 'lansim'
        dep_dir = os.path.join(self.autodir, 'deps', dep)
        logging.info('lansim is at %s', dep_dir)
        self.job.install_pkg(dep, 'dep', dep_dir)

        # Import the lansim modules installed on lansim/build/
        sys.path.append(os.path.join(dep_dir, 'build'))

        self._p2p = p2p_utils.P2PServerOverTap()
        self._sim = None


    def cleanup(self):
        # Work around problem described in the chromium:364583 bug.
        time.sleep(1)
        self._join_simulator()
        self._p2p.cleanup()


    def _join_simulator(self):
        """Stops the simulator and logs any exception generated there."""
        if not self._sim:
            return
        self._sim.stop()
        self._sim.join()
        if self._sim.error:
            logging.error('SimulatorThread exception: %r', self._sim.error)
            logging.error(self._sim.traceback)


    def _dut_ready(self, p2pcli):
        # Lookup the DUT on the mDNS network.
        peers = p2pcli.get_peers()
        if not peers:
            return False
        peer_name, hostname, ips, port = peers[0]
        # Get the files shared by the DUT.
        files = p2pcli.get_peer_files(peer_name)
        if not files:
            return False
        return peer_name, hostname, ips, port, files


    def _p2p_fetch(self, host, port, filename):
        """Fetch a file from a p2p-http-server.

        @return: A str with the contents of the responde if the request
        succeeds or an integer value with the error code returned by curl
        otherwise.
        """
        fd, tempfn = tempfile.mkstemp(prefix='p2p-fetch')
        ret = utils.run(
                'curl', args=['http://%s:%s/%s' % (host, port, filename)],
                timeout=20., ignore_timeout=False, ignore_status=True,
                stdout_tee=open(tempfn, 'w'), stderr_tee=sys.stdout)
        with os.fdopen(fd) as f:
            output = f.read()
        os.unlink(tempfn)

        if ret is None:
            return None
        if ret.exit_status != 0:
            return ret.exit_status
        return output


    def run_once(self):
        from lansim import simulator, host

        # Setup the environment where avahi-daemon runs during the test.
        try:
            self._p2p.setup(dumpdir=self.job.resultdir)
        except:
            logging.exception('Failed to start tested services.')
            raise

        # Share a file on the DUT.
        content = open('/dev/urandom').read(16*1024)
        with open(os.path.join(p2p_utils.P2P_SHARE_PATH, 'file.p2p'), 'w') as f:
            f.write(content)

        self._sim = simulator.SimulatorThread(self._p2p.tap)
        # Create a single fake peer that will be sending the multicast requests.
        peer = host.SimpleHost(self._sim, '94:EB:2C:00:00:61', '169.254.10.55')

        # Run a userspace implementation of avahi + p2p-client on the fake
        # host. This will use the P2P services exported by the DUT.
        zero = zeroconf.ZeroconfDaemon(peer, 'peer')
        p2pcli = cros_p2p.CrosP2PClient(zero)

        self._sim.start()

        # Force a request from the client before waiting for the DUT's response.
        self._sim.run_on_simulator(lambda: p2pcli.start_query())

        # Wait up to 30 seconds until the DUT is ready sharing the files.
        res = self._sim.wait_for_condition(lambda: self._dut_ready(p2pcli),
                                           timeout=30.)
        self._sim.run_on_simulator(lambda: p2pcli.stop_query())

        if not res:
            raise error.TestFail('The DUT failed to announce the shared files '
                                 'after 30 seconds.')

        # DUT's p2p-http-server is running on hostname:port.
        peer_name, hostname, ips, port, files = res

        if len(files) != 1 or files[0] != ('file', len(content)) or (
                len(ips) != 1) or ips[0] != self._p2p.tap.addr:
            logging.error('peer_name = %r', peer_name)
            logging.error('hostname = %r', hostname)
            logging.error('ips = %r', ips)
            logging.error('port = %r', port)
            logging.error('files = %r', files)
            raise error.TestFail('The DUT announced an erroneous file.')

        # Check we can't download directly from localhost.
        for host_ip in (ips[0], '127.0.0.1'):
            ret = self._p2p_fetch(host_ip, port, 'file')
            if ret != 7: # curl's exit code 7 is "Failed to connect to host."
                logging.error('curl returned: %s', repr(ret)[:100])
                raise error.TestFail(
                        "The DUT didn't block a request from localhost using "
                        "the address %s." % host_ip)

        # Check we can download if the connection comes from a peer on the
        # network. To achieve this, we forward the tester's TCP traffic through
        # a fake host on lansim.
        self._sim.run_on_simulator(lambda: peer.tcp_forward(1234, ips[0], port))

        ret = self._p2p_fetch(peer.ip_addr, 1234, 'file')
        if ret != content:
            logging.error('curl returned: %s', repr(ret)[:100])
            raise error.TestFail(
                    "The DUT didn't serve the file request from %s " %
                    peer.id_addr)