// -*- c-basic-offset: 4; tab-width: 8; indent-tabs-mode: t -*-

// Copyright (c) 2001-2009 XORP, Inc.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, Version 2, June
// 1991 as published by the Free Software Foundation. Redistribution
// and/or modification of this program under the terms of any other
// version of the GNU General Public License is not permitted.
// 
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For more details,
// see the GNU General Public License, Version 2, a copy of which can be
// found in the XORP LICENSE.gpl file.
// 
// XORP Inc, 2953 Bunker Hill Lane, Suite 204, Santa Clara, CA 95054, USA;
// http://xorp.net



#include <set>

#include "rip_module.h"
#include "libxorp/xlog.h"

#include "libxorp/c_format.hh"
#include "libxorp/eventloop.hh"
#include "libxorp/ipv4.hh"
#include "libxorp/ipv4net.hh"

#include "auth.hh"
#include "port.hh"
#include "peer.hh"
#include "route_db.hh"
#include "system.hh"

#include "test_utils.hh"

#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif

///////////////////////////////////////////////////////////////////////////////
//
// Constants
//

static const char *program_name         = "test_request";
static const char *program_description  = "Test RIP handling route requests";
static const char *program_version_id   = "0.1";
static const char *program_date         = "July, 2003";
static const char *program_copyright    = "See file LICENSE";
static const char *program_return_value = "0 on success, 1 if test error, 2 if internal error";


// ----------------------------------------------------------------------------
// Spoof Port that supports just a single Peer
//

template <typename A>
class SpoofPort : public Port<A> {
public:
    SpoofPort(PortManagerBase<A>& pm, A addr) : Port<A>(pm)
    {
	this->_peers.push_back(new Peer<A>(*this, addr));
	verbose_log("Constructing SpoofPort instance\n");
	this->set_enabled(true);
    }
    ~SpoofPort()
    {
	verbose_log("Destructing SpoofPort instance\n");
	while (this->_peers.empty() == false) {
	    delete this->_peers.front();
	    this->_peers.pop_front();
	}
    }
};


// ----------------------------------------------------------------------------
// Type specific helpers

template <typename A>
struct DefaultPeer {
    static A get();
};

template <>
IPv4 DefaultPeer<IPv4>::get() { return IPv4("10.0.0.1"); }

template <>
IPv6 DefaultPeer<IPv6>::get() { return IPv6("10::1"); }

// ----------------------------------------------------------------------------
// Spoof Port Manager instance support a single Spoof Port which in turn
// contains a single Peer.
//

template <typename A>
class SpoofPortManager : public PortManagerBase<A> {
public:
    SpoofPortManager(System<A>& s, const IfMgrIfTree& iftree)
	: PortManagerBase<A>(s, iftree)
    {
	this->_ports.push_back(new SpoofPort<A>(*this, DefaultPeer<A>::get()));
    }

    ~SpoofPortManager()
    {
	while (!this->_ports.empty()) {
	    delete this->_ports.front();
	    this->_ports.pop_front();
	}
    }

    Port<A>* the_port()
    {
	XLOG_ASSERT(this->_ports.size() == 1);
	return this->_ports.front();
    }

    Peer<A>* the_peer()
    {
	XLOG_ASSERT(this->_ports.size() == 1);
	XLOG_ASSERT(this->_ports.front()->peers().size() == 1);
	return this->_ports.front()->peers().front();
    }
};

// ----------------------------------------------------------------------------
// Test PortIO class

template <typename A>
class SpoofPortIO : public PortIOBase<A>
{
public:
    typedef A			Addr;
    typedef PortIOUserBase<A>	PortIOUser;

public:
    SpoofPortIO(PortIOUserBase<A>& user)
	: PortIOBase<A>(user, "if0", "vif0", IPv4("10.0.0.1"))
    {
	last_rip_send_flush();
    }

    /**
     * Called by RIP Port instance.
     */
    bool send(const Addr&    addr,
	      uint16_t	     port,
	      const vector<uint8_t>& rip_packet)
    {
	_lo_addr = addr;
	_lo_port = port;
	_lo_data = rip_packet;
	_pending = true;
	return true;
    }

    bool pending() const
    {
	return _lo_port != 0;
    }

    const Addr&	last_rip_send_addr() const		{ return _lo_addr; }
    uint16_t	last_rip_send_port() const		{ return _lo_port; }
    const vector<uint8_t>& last_rip_send_data() const	{ return _lo_data; }

    void last_rip_send_flush()
    {
	_lo_addr = A::ZERO();
	_lo_port = 0;
	_lo_data.resize(0);
	_pending = false;
    }

private:
    vector<uint8_t>	_lo_data;
    Addr		_lo_addr;
    uint16_t		_lo_port;
    bool		_pending;
};


static const IPv4	REQUESTING_HOST = IPv4("10.0.100.1");
static const uint16_t	REQUESTING_PORT = 1092;
static const uint8_t	ROUTE_COST	= 5;

static const IfMgrIfTree ift_dummy = IfMgrIfTree();

class RequestPacketTester {
public:
    static const uint32_t INJECTED_ROUTES  = 10;
    static const uint32_t REQUESTED_ROUTES = 25;

public:
    RequestPacketTester() : _e(), _rip_system(_e), _pm(_rip_system, ift_dummy)
    {
	_portio	= new SpoofPortIO<IPv4>(*_pm.the_port());
	_pm.the_port()->set_io_handler(_portio, false);
	_portio->set_enabled(true);
    }

    ~RequestPacketTester()
    {
	_portio->set_enabled(false);
	delete _portio;
	RouteDB<IPv4>& rdb = _rip_system.route_db();
	rdb.flush_routes();
    }

    bool init_nets()
    {
	string ifname, vifname;		// XXX: not set, because not needed

	RouteDB<IPv4>& rdb = _rip_system.route_db();
	make_nets(_testnets, REQUESTED_ROUTES);

	set<IPv4Net>::const_iterator n = _testnets.begin();
	for (uint32_t i = 0; i < INJECTED_ROUTES; i++) {
	    if (rdb.update_route(*n, IPv4::ZERO(), ifname, vifname, ROUTE_COST,
				 0, _pm.the_peer(), PolicyTags(),
				 false) == false) {
		verbose_log("Failed to add route for %s\n",
			    n->str().c_str());
		return false;
	    }
	    n++;
	}
	return true;
    }

    bool
    send_rip_route_queries()
    {
	vector<uint8_t> buf;
	buf.resize(RipPacketHeader::SIZE +
		   REQUESTED_ROUTES * PacketRouteEntry<IPv4>::size());

	RipPacketHeaderWriter rph(&buf[0]);
	rph.initialize(RipPacketHeader::REQUEST, 2);

	set<IPv4Net>::const_iterator n = _testnets.begin();
	for (uint32_t i = 0; i < REQUESTED_ROUTES; i++) {
	    XLOG_ASSERT(n != _testnets.end());
	    uint32_t offset = RipPacketHeader::SIZE +
		i * PacketRouteEntry<IPv4>::size();

	    PacketRouteEntryWriter<IPv4> pre(&(buf[offset]));

	    pre.initialize(0, *n, IPv4::ZERO(), 0);
	    n++;
	}
	XLOG_ASSERT(_pm.the_port() != 0);
	_pm.the_port()->port_io_receive(REQUESTING_HOST, REQUESTING_PORT,
					&(buf[0]), buf.size());
	return true;
    }

    bool
    check_response()
    {
	if (_portio->pending() == false) {
	    verbose_log("No response packet sent by rip\n");
	    return false;
	}
	if (_portio->last_rip_send_addr() != REQUESTING_HOST) {
	    verbose_log("Response was not sent to originator\n");
	    return false;
	}
	if (_portio->last_rip_send_port() != REQUESTING_PORT) {
	    verbose_log("Response was not sent to originators port\n");
	    return false;
	}

	const vector<uint8_t> buf = _portio->last_rip_send_data();

	// Validate RIP packet header
	const RipPacketHeader rph(&buf[0]);

	if (rph.valid_command() == false) {
	    verbose_log("Invalid command\n");
	    return false;
	}

	if (rph.valid_version(RipPacketHeader::IPv4_VERSION) == false) {
	    verbose_log("Invalid version\n");
	    return false;
	}

	if (rph.valid_padding() == false) {
	    verbose_log("Invalid padding\n");
	    return false;
	}

	if (rph.command() != RipPacketHeader::RESPONSE) {
	    verbose_log("Not a response packet\n");
	    return false;
	}

	// Validate entries
	const uint8_t* pre_ptr = &buf[0] + RipPacketHeader::size();
	uint32_t n_entries = buf.size() / PacketRouteEntry<IPv4>::size() - 1;

	if (n_entries > _testnets.size()) {
	    verbose_log("Got more routes than requested (%u > %u).\n",
			XORP_UINT_CAST(n_entries),
			XORP_UINT_CAST(_testnets.size()));
	    return false;
	}

	set<IPv4Net>::const_iterator ni = _testnets.begin();

	for (uint32_t i = 0; i < n_entries;
	     i++, pre_ptr += PacketRouteEntry<IPv4>::size()) {
	    const PacketRouteEntry<IPv4> pre(pre_ptr);
	    verbose_log("%s %s %u %u\n",
			pre.net().str().c_str(),
			pre.nexthop().str().c_str(),
			XORP_UINT_CAST(pre.metric()),
			pre.tag());
	    if (pre.addr_family() != PacketRouteEntry<IPv4>::ADDR_FAMILY) {
		verbose_log("Invalid address family in route entry %u\n",
			    XORP_UINT_CAST(i));
		return false;
	    }
	    if (*ni != pre.net()) {
		verbose_log("Mismatched net in route entry %u\n",
			    XORP_UINT_CAST(i));
		return false;
	    }
	    if (i < INJECTED_ROUTES) {
		if (pre.metric() != ROUTE_COST) {
		    verbose_log("Metric changed.\n");
		    return false;
		}
	    } else {
		if (pre.metric() != RIP_INFINITY) {
		    verbose_log("Non-existant route with finite metric??\n");
		}
	    }
	    ni++;
	}
	return true;
    }

    int run_test()
    {
	if (init_nets() == false) {
	    return -1;
	}

	bool start_test = false;
	XorpTimer d = _e.set_flag_after_ms(1 * 1000, &start_test);
	while (start_test == false) {
	    _e.run();
	}

	if (send_rip_route_queries() == false) {
	    return -1;
	}

	if (check_response() == false) {
	    return -1;
	}
	return 0;
    }

protected:
    EventLoop			_e;
    System<IPv4>		_rip_system;
    SpoofPortManager<IPv4>	_pm;
    SpoofPortIO<IPv4>*		_portio;
    set<IPv4Net>		_testnets;
};


/**
 * Print program info to output stream.
 *
 * @param stream the output stream the print the program info to.
 */
static void
print_program_info(FILE *stream)
{
    fprintf(stream, "Name:          %s\n", program_name);
    fprintf(stream, "Description:   %s\n", program_description);
    fprintf(stream, "Version:       %s\n", program_version_id);
    fprintf(stream, "Date:          %s\n", program_date);
    fprintf(stream, "Copyright:     %s\n", program_copyright);
    fprintf(stream, "Return:        %s\n", program_return_value);
}

/*
 * Print program usage information to the stderr.
 *
 * @param progname the name of the program.
 */
static void
usage(const char* progname)
{
    print_program_info(stderr);
    fprintf(stderr, "usage: %s [-v] [-h]\n", progname);
    fprintf(stderr, "       -h          : usage (this message)\n");
    fprintf(stderr, "       -v          : verbose output\n");
}

int
main(int argc, char* const argv[])
{
    //
    // Initialize and start xlog
    //
    xlog_init(argv[0], NULL);
    xlog_set_verbose(XLOG_VERBOSE_LOW);         // Least verbose messages
    // XXX: verbosity of the error messages temporary increased
    xlog_level_set_verbose(XLOG_LEVEL_ERROR, XLOG_VERBOSE_HIGH);
    xlog_add_default_output();
    xlog_start();

    int ch;
    while ((ch = getopt(argc, argv, "hv")) != -1) {
        switch (ch) {
        case 'v':
            set_verbose(true);
            break;
        case 'h':
        case '?':
        default:
            usage(argv[0]);
            xlog_stop();
            xlog_exit();
            if (ch == 'h')
                return (0);
            else
                return (1);
        }
    }
    argc -= optind;
    argv += optind;

    int rval = 0;
    XorpUnexpectedHandler x(xorp_unexpected_handler);
    try {
	RequestPacketTester rpt;
	rval = rpt.run_test();
    } catch (...) {
        // Internal error
        xorp_print_standard_exceptions();
        rval = 2;
    }

    //
    // Gracefully stop and exit xlog
    //
    xlog_stop();
    xlog_exit();

    return rval;
}
