// ndppd - NDP Proxy Daemon
// Copyright (C) 2011  Daniel Adolfsson <daniel@priv.nu>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// 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.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "ndppd.h"

#include "proxy.h"
#include "route.h"
#include "iface.h"
#include "rule.h"
#include "session.h"

NDPPD_NS_BEGIN

std::list<ptr<proxy> > proxy::_list;

proxy::proxy() :
    _router(true), _ttl(30000), _timeout(500)
{
}

ptr<proxy> proxy::create(const ptr<iface>& ifa)
{
    ptr<proxy> pr(new proxy());
    pr->_ptr = pr;
    pr->_ifa = ifa;

    _list.push_back(pr);

    ifa->pr(pr);

    logger::debug() << "proxy::create() if=" << ifa->name();

    return pr;
}

ptr<proxy> proxy::open(const std::string& ifname)
{
    ptr<iface> ifa = iface::open_pfd(ifname);

    if (!ifa) {
        return ptr<proxy>();
    }

    return create(ifa);
}

void proxy::handle_solicit(const address& saddr, const address& daddr,
    const address& taddr)
{
    logger::debug()
        << "proxy::handle_solicit() saddr=" << saddr.to_string()
        << ", taddr=" << taddr.to_string();

    // Let's check this proxy's list of sessions to see if we can
    // find one with the same target address.

    for (std::list<ptr<session> >::iterator sit = _sessions.begin();
            sit != _sessions.end(); sit++) {

        if ((*sit)->taddr() == taddr) {
            switch ((*sit)->status()) {
            case session::WAITING:
            case session::INVALID:
                break;

            case session::VALID:
                (*sit)->send_advert();
            }

            return;
        }
    }

    // Since we couldn't find a session that matched, we'll try to find
    // a matching rule instead, and then set up a new session.

    ptr<session> se;

    for (std::list<ptr<rule> >::iterator it = _rules.begin();
            it != _rules.end(); it++) {
        ptr<rule> ru = *it;

        logger::debug() << "checking " << ru->addr() << " against " << taddr;

        if (ru->addr() == taddr) {
            if (!se) {
                se = session::create(_ptr, saddr, daddr, taddr);
            }

            if (ru->is_auto()) {
                ptr<route> rt = route::find(taddr);

                if (rt->ifname() == _ifa->name()) {
                    logger::debug() << "skipping route since it's using interface " << rt->ifname();
                } else {
                    ptr<iface> ifa = rt->ifa();

                    if (ifa && (ifa != ru->ifa())) {
                        se->add_iface(ifa);
                    }
                }
            } else if (!ru->ifa()) {
                // This rule doesn't have an interface, and thus we'll consider
                // it "static" and immediately send the response.
                se->handle_advert();
                return;
            } else {
                se->add_iface((*it)->ifa());
            }
        }
    }

    if (se) {
        _sessions.push_back(se);
        se->send_solicit();
    }
}

ptr<rule> proxy::add_rule(const address& addr, const ptr<iface>& ifa)
{
    ptr<rule> ru(rule::create(_ptr, addr, ifa));
    _rules.push_back(ru);
    return ru;
}

ptr<rule> proxy::add_rule(const address& addr, bool aut)
{
    ptr<rule> ru(rule::create(_ptr, addr, aut));
    _rules.push_back(ru);
    return ru;
}

void proxy::remove_session(const ptr<session>& se)
{
    _sessions.remove(se);
}

const ptr<iface>& proxy::ifa() const
{
    return _ifa;
}

bool proxy::router() const
{
    return _router;
}

void proxy::router(bool val)
{
    _router = val;
}

int proxy::ttl() const
{
    return _ttl;
}

void proxy::ttl(int val)
{
    _ttl = (val >= 0) ? val : 30000;
}

int proxy::timeout() const
{
    return _timeout;
}

void proxy::timeout(int val)
{
    _timeout = (val >= 0) ? val : 500;
}

NDPPD_NS_END