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

// Copyright (c) 2001-2007 International Computer Science Institute
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software")
// to deal in the Software without restriction, subject to the conditions
// listed in the XORP LICENSE file. These conditions include: you must
// preserve this copyright notice, and you cannot mention the copyright
// holders in advertising related to the Software without their permission.
// The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
// notice is a summary of the XORP LICENSE file; the license in that file is
// legally binding.

#ident "$XORP: xorp/fea/xrl_fti.cc,v 1.22 2007/02/16 22:45:52 pavlin Exp $"

#include "fea_module.h"

#include "libxorp/xorp.h"
#include "libxorp/xlog.h"
#include "libxorp/debug.h"

#include "xrl_fti.hh"


static const char* FTI_MAX_OPS_HIT =
"Resource limit on number of operations in a transaction hit.";

static const char* FTI_MAX_TRANSACTIONS_HIT =
"Resource limit on number of pending transactions hit.";

static const char* FTI_BAD_ID =
"Expired or invalid transaction id presented.";

XrlCmdError
XrlFtiTransactionManager::start_transaction(uint32_t& tid)
{
    if (_ftm.start(tid))
	return XrlCmdError::OKAY();
    return XrlCmdError::COMMAND_FAILED(FTI_MAX_TRANSACTIONS_HIT);
}

XrlCmdError
XrlFtiTransactionManager::commit_transaction(uint32_t tid)
{
    if (_ftm.commit(tid)) {
	const string& error_msg = _ftm.error();
	if (error_msg.empty())
	    return XrlCmdError::OKAY();
	return XrlCmdError::COMMAND_FAILED(error_msg);
    }
    return XrlCmdError::COMMAND_FAILED(FTI_BAD_ID);
}

XrlCmdError
XrlFtiTransactionManager::abort_transaction(uint32_t tid)
{
    if (_ftm.abort(tid))
	return XrlCmdError::OKAY();
    return XrlCmdError::COMMAND_FAILED(FTI_BAD_ID);
}

XrlCmdError
XrlFtiTransactionManager::add(uint32_t tid,
			      const FtiTransactionManager::Operation& op)
{
    uint32_t n_ops;

    if (_ftm.retrieve_size(tid, n_ops) == false)
	return XrlCmdError::COMMAND_FAILED(FTI_BAD_ID);

    if (_max_ops <= n_ops)
	return XrlCmdError::COMMAND_FAILED(FTI_MAX_OPS_HIT);

    if (_ftm.add(tid, op))
	return XrlCmdError::OKAY();

    //
    // In theory, resource shortage is the only thing that could get us
    // here.
    //
    return XrlCmdError::COMMAND_FAILED("Unknown resource shortage");
}

/**
 * Process a list of IPv4 FIB route changes.
 * 
 * The FIB route changes come from the underlying system.
 * 
 * @param fte_list the list of Fte entries to add or delete.
 */
void
XrlFtiTransactionManager::process_fib_changes(const list<Fte4>& fte_list)
{
    map<string, FibClient4>::iterator iter;

    for (iter = _fib_clients4.begin(); iter != _fib_clients4.end(); ++iter) {
	FibClient4& fib_client = iter->second;
	fib_client.activate(fte_list);
    }
}

/**
 * Process a list of IPv6 FIB route changes.
 * 
 * The FIB route changes come from the underlying system.
 * 
 * @param fte_list the list of Fte entries to add or delete.
 */
void
XrlFtiTransactionManager::process_fib_changes(const list<Fte6>& fte_list)
{
    map<string, FibClient6>::iterator iter;

    for (iter = _fib_clients6.begin(); iter != _fib_clients6.end(); ++iter) {
	FibClient6& fib_client = iter->second;
	fib_client.activate(fte_list);
    }
}

XrlCmdError
XrlFtiTransactionManager::add_fib_client4(const string& client_target_name,
	const bool send_updates, const bool send_resolves)
{
    // Test if we have this client already
    if (_fib_clients4.find(client_target_name) != _fib_clients4.end()) {
	string error_msg = c_format("Target %s is already an IPv4 FIB client",
				    client_target_name.c_str());
	return XrlCmdError::COMMAND_FAILED(error_msg);
    }

    // Add the client
    _fib_clients4.insert(make_pair(client_target_name,
				   FibClient4(client_target_name, *this)));
    FibClient4& fib_client = _fib_clients4.find(client_target_name)->second;

    fib_client.set_send_updates(send_updates);
    fib_client.set_send_resolves(send_resolves);

    // Activate the client
    list<Fte4> fte_list;
    if (ftic().get_table4(fte_list) != true) {
	string error_msg = "Cannot get the IPv4 FIB";
	return XrlCmdError::COMMAND_FAILED(error_msg);
    }
    fib_client.activate(fte_list);

    return XrlCmdError::OKAY();
}

XrlCmdError
XrlFtiTransactionManager::add_fib_client6(const string& client_target_name,
	const bool send_updates, const bool send_resolves)
{
    // Test if we have this client already
    if (_fib_clients6.find(client_target_name) != _fib_clients6.end()) {
	string error_msg = c_format("Target %s is already an IPv6 FIB client",
				    client_target_name.c_str());
	return XrlCmdError::COMMAND_FAILED(error_msg);
    }

    // Add the client
    _fib_clients6.insert(make_pair(client_target_name,
				   FibClient6(client_target_name, *this)));
    FibClient6& fib_client = _fib_clients6.find(client_target_name)->second;

    fib_client.set_send_updates(send_updates);
    fib_client.set_send_resolves(send_resolves);

    // Activate the client
    list<Fte6> fte_list;
    if (ftic().get_table6(fte_list) != true) {
	string error_msg = "Cannot get the IPv6 FIB";
	return XrlCmdError::COMMAND_FAILED(error_msg);
    }
    fib_client.activate(fte_list);

    return XrlCmdError::OKAY();
}

XrlCmdError
XrlFtiTransactionManager::delete_fib_client4(const string& client_target_name)
{
    map<string, FibClient4>::iterator iter;

    iter = _fib_clients4.find(client_target_name);
    if (iter == _fib_clients4.end()) {
	string error_msg = c_format("Target %s is not an IPv4 FIB client",
				    client_target_name.c_str());
	return XrlCmdError::COMMAND_FAILED(error_msg);
    }

    _fib_clients4.erase(iter);

    return XrlCmdError::OKAY();
}

XrlCmdError
XrlFtiTransactionManager::delete_fib_client6(const string& client_target_name)
{
    map<string, FibClient6>::iterator iter;

    iter = _fib_clients6.find(client_target_name);
    if (iter == _fib_clients6.end()) {
	string error_msg = c_format("Target %s is not an IPv6 FIB client",
				    client_target_name.c_str());
	return XrlCmdError::COMMAND_FAILED(error_msg);
    }

    _fib_clients6.erase(iter);

    return XrlCmdError::OKAY();
}

int
XrlFtiTransactionManager::send_fib_client_add_route(const string& target_name,
						    const Fte4& fte)
{
    bool success;

    success = _xrl_fea_fib_client.send_add_route4(
	target_name.c_str(),
	fte.net(),
	fte.nexthop(),
	fte.ifname(),
	fte.vifname(),
	fte.metric(),
	fte.admin_distance(),
	string("NOT_SUPPORTED"),
	fte.xorp_route(),
	callback(this,
		 &XrlFtiTransactionManager::send_fib_client_add_route4_cb,
		 target_name));

    if (success)
	return XORP_OK;
    else
	return XORP_ERROR;
}

int
XrlFtiTransactionManager::send_fib_client_add_route(const string& target_name,
						    const Fte6& fte)
{
    bool success;

    success = _xrl_fea_fib_client.send_add_route6(
	target_name.c_str(),
	fte.net(),
	fte.nexthop(),
	fte.ifname(),
	fte.vifname(),
	fte.metric(),
	fte.admin_distance(),
	string("NOT_SUPPORTED"),
	fte.xorp_route(),
	callback(this,
		 &XrlFtiTransactionManager::send_fib_client_add_route6_cb,
		 target_name));

    if (success)
	return XORP_OK;
    else
	return XORP_ERROR;
}

int
XrlFtiTransactionManager::send_fib_client_delete_route(const string& target_name,
						       const Fte4& fte)
{
    bool success;

    success = _xrl_fea_fib_client.send_delete_route4(
	target_name.c_str(),
	fte.net(),
	fte.ifname(),
	fte.vifname(),
	callback(this,
		 &XrlFtiTransactionManager::send_fib_client_delete_route4_cb,
		 target_name));

    if (success)
	return XORP_OK;
    else
	return XORP_ERROR;
}

int
XrlFtiTransactionManager::send_fib_client_delete_route(const string& target_name,
						       const Fte6& fte)
{
    bool success;

    success = _xrl_fea_fib_client.send_delete_route6(
	target_name.c_str(),
	fte.net(),
	fte.ifname(),
	fte.vifname(),
	callback(this,
		 &XrlFtiTransactionManager::send_fib_client_delete_route6_cb,
		 target_name));

    if (success)
	return XORP_OK;
    else
	return XORP_ERROR;
}

int
XrlFtiTransactionManager::send_fib_client_resolve_route(const string& target_name,
						       const Fte4& fte)
{
    bool success;

    success = _xrl_fea_fib_client.send_resolve_route4(
	target_name.c_str(),
	fte.net(),
	callback(this,
		 &XrlFtiTransactionManager::send_fib_client_resolve_route4_cb,
		 target_name));

    if (success)
	return XORP_OK;
    else
	return XORP_ERROR;
}

int
XrlFtiTransactionManager::send_fib_client_resolve_route(const string& target_name,
						       const Fte6& fte)
{
    bool success;

    success = _xrl_fea_fib_client.send_resolve_route6(
	target_name.c_str(),
	fte.net(),
	callback(this,
		 &XrlFtiTransactionManager::send_fib_client_resolve_route6_cb,
		 target_name));

    if (success)
	return XORP_OK;
    else
	return XORP_ERROR;
}

void
XrlFtiTransactionManager::send_fib_client_add_route4_cb(
    const XrlError& xrl_error,
    string target_name)
{
    map<string, FibClient4>::iterator iter;

    iter = _fib_clients4.find(target_name);
    if (iter == _fib_clients4.end()) {
	// The client has probably gone. Silently ignore.
	return;
    }

    FibClient4& fib_client = iter->second;
    fib_client.send_fib_client_route_change_cb(xrl_error);
}

void
XrlFtiTransactionManager::send_fib_client_add_route6_cb(
    const XrlError& xrl_error,
    string target_name)
{
    map<string, FibClient6>::iterator iter;

    iter = _fib_clients6.find(target_name);
    if (iter == _fib_clients6.end()) {
	// The client has probably gone. Silently ignore.
	return;
    }

    FibClient6& fib_client = iter->second;
    fib_client.send_fib_client_route_change_cb(xrl_error);
}

void
XrlFtiTransactionManager::send_fib_client_delete_route4_cb(
    const XrlError& xrl_error,
    string target_name)
{
    map<string, FibClient4>::iterator iter;

    iter = _fib_clients4.find(target_name);
    if (iter == _fib_clients4.end()) {
	// The client has probably gone. Silently ignore.
	return;
    }

    FibClient4& fib_client = iter->second;
    fib_client.send_fib_client_route_change_cb(xrl_error);
}

void
XrlFtiTransactionManager::send_fib_client_delete_route6_cb(
    const XrlError& xrl_error,
    string target_name)
{
    map<string, FibClient6>::iterator iter;

    iter = _fib_clients6.find(target_name);
    if (iter == _fib_clients6.end()) {
	// The client has probably gone. Silently ignore.
	return;
    }

    FibClient6& fib_client = iter->second;
    fib_client.send_fib_client_route_change_cb(xrl_error);
}

void
XrlFtiTransactionManager::send_fib_client_resolve_route4_cb(
    const XrlError& xrl_error,
    string target_name)
{
    map<string, FibClient4>::iterator iter;

    iter = _fib_clients4.find(target_name);
    if (iter == _fib_clients4.end()) {
	// The client has probably gone. Silently ignore.
	return;
    }

    FibClient4& fib_client = iter->second;
    fib_client.send_fib_client_route_change_cb(xrl_error);
}

void
XrlFtiTransactionManager::send_fib_client_resolve_route6_cb(
    const XrlError& xrl_error,
    string target_name)
{
    map<string, FibClient6>::iterator iter;

    iter = _fib_clients6.find(target_name);
    if (iter == _fib_clients6.end()) {
	// The client has probably gone. Silently ignore.
	return;
    }

    FibClient6& fib_client = iter->second;
    fib_client.send_fib_client_route_change_cb(xrl_error);
}

template<class F>
void
XrlFtiTransactionManager::FibClient<F>::activate(const list<F>& fte_list)
{
    bool queue_was_empty = _inform_fib_client_queue.empty();

    if (fte_list.empty())
	return;

    // Create the queue with the entries to add
    typename list<F>::const_iterator iter;
    for (iter = fte_list.begin(); iter != fte_list.end(); ++iter) {
	const F& fte = *iter;
	_inform_fib_client_queue.push_back(fte);
    }

    // If the queue was empty before, start sending the routes
    if (queue_was_empty)
	send_fib_client_route_change();
}

template<class F>
void
XrlFtiTransactionManager::FibClient<F>::send_fib_client_route_change()
{
    int success = XORP_ERROR;

    do {
	bool ignore_fte = true;

	if (_inform_fib_client_queue.empty())
	    return;		// No more route changes to send

	F& fte = _inform_fib_client_queue.front();

	//
	// If FIB route misses and resolution requests were requested to be
	// heard by the client, then send notifications of such events.
	//
	if (_send_resolves && fte.is_unresolved()) {
	    ignore_fte = false;
	    success = _xftm.send_fib_client_resolve_route(_target_name, fte);
	}

	//
	// If FIB updates were requested by the client, then send notification
	// of a route being added or deleted.
	//
	if (_send_updates && !fte.is_unresolved()) {
	    ignore_fte = false;
	    if (!fte.is_deleted()) {
		// Send notification of a route being added
		success = _xftm.send_fib_client_add_route(_target_name, fte);
	    } else {
		// Send notification of a route being deleted
		success = _xftm.send_fib_client_delete_route(_target_name,
							     fte);
	    }
	}

	if (ignore_fte) {
	    //
	    // This entry is not needed hence silently drop it and process the
	    // next one.
	    //
	    _inform_fib_client_queue.pop_front();
	    continue;
	}

	break;
    } while (true);

    if (success != XORP_OK) {
	//
	// If an error, then start a timer to try again
	// TODO: XXX: the timer value is hardcoded here!!
	//
	_inform_fib_client_queue_timer = eventloop().new_oneoff_after(
	    TimeVal(1, 0),
	    callback(this, &XrlFtiTransactionManager::FibClient<F>::send_fib_client_route_change));
    }
}

template<class F>
void
XrlFtiTransactionManager::FibClient<F>::send_fib_client_route_change_cb(
    const XrlError& xrl_error)
{
    // If success, then send the next route change
    if (xrl_error == XrlError::OKAY()) {
	_inform_fib_client_queue.pop_front();
	send_fib_client_route_change();
	return;
    }

    //
    // If command failed because the other side rejected it,
    // then send the next route change.
    //
    if (xrl_error == XrlError::COMMAND_FAILED()) {
	XLOG_ERROR("Error sending route change to %s: %s",
		   _target_name.c_str(), xrl_error.str().c_str());
	_inform_fib_client_queue.pop_front();
	send_fib_client_route_change();
	return;
    }

    //
    // If a transport error, then start a timer to try again
    // (unless the timer is already running).
    // TODO: XXX: the timer value is hardcoded here!!
    //
    if (_inform_fib_client_queue_timer.scheduled())
	return;
    _inform_fib_client_queue_timer = eventloop().new_oneoff_after(
	TimeVal(1, 0),
	callback(this, &XrlFtiTransactionManager::FibClient<F>::send_fib_client_route_change));
}

template class XrlFtiTransactionManager::FibClient<Fte4>;
template class XrlFtiTransactionManager::FibClient<Fte6>;


syntax highlighted by Code2HTML, v. 0.9.1