/*

                          Firewall Builder

                 Copyright (C) 2010 NetCitadel, LLC

  Author:  Roman Bovsunivskiy     a2k0001@gmail.com

  This program is free software which we release under the GNU General Public
  License. You may redistribute and/or modify this program under the terms
  of that license as published by the Free Software Foundation; either
  version 2 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.

  To get a copy of the GNU General Public License, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/

#include "ImporterTest.h"

#include "config.h"
#include "global.h"

#include <fstream>
#include <iostream>
#include <algorithm>
#include <functional>
#include <stdexcept>

#include <assert.h>

#include "Importer.h"
#include "IOSImporter.h"
#include "IPTImporter.h"
#include "FWBTree.h"

#include "fwbuilder/Policy.h"
#include "fwbuilder/Rule.h"
#include "fwbuilder/TagService.h"
#include "fwbuilder/Constants.h"

#include <QDebug>
#include <QFile>
#include <QStringList>
#include <QString>
#include <QRegExp>


using namespace std;
using namespace libfwbuilder;

extern string platform;

const char* iptables_sample = 
"# Generated by iptables-save %VERSION% on Mon Apr 11 15:50:58 2011\n"
"*filter\n"
":INPUT ACCEPT [0:0]\n"
":FORWARD ACCEPT [0:0]\n"
":OUTPUT ACCEPT [0:0]\n"
":CHAIN-1-INPUT - [0:0]\n"
"-A INPUT -j CHAIN-1-INPUT \n"
"-A FORWARD -j CHAIN-1-INPUT \n"
"-A CHAIN-1-INPUT -j ACCEPT \n"
"COMMIT\n"
"# Completed on Mon Apr 11 15:50:58 2011\n";


extern QString findBestVersionMatch(const QString &platform,
                                    const QString &discovered_version);


class UpgradePredicate: public XMLTools::UpgradePredicate
{
    public:
    virtual bool operator()(const string &) const
    {
        return false;
    }
};

void ImporterTest::setUp()
{
    //init();

    FWBTree *tree = new FWBTree();

    /* create database */
    db = new FWObjectDatabase();

    /* load the data file */
    UpgradePredicate upgrade_predicate;

    db->setReadOnly( false );

    db->load( Constants::getStandardObjectsFilePath(),
              &upgrade_predicate, Constants::getDTDDirectory());

    db->setFileName("");
    lib = Library::cast(tree->createNewLibrary(db));
    lib->setName("User");

    logger = new QueueLogger();

    // this makes the test compile and link. There is a problem with
    // dependencies, the test depends on libimport.a and additionally,
    // PIXImporter.cpp depends on this function that is implemented in
    // platforms.cpp in libgui.a; however since libgui.a comes before
    // libimport.a in linker command line, this function does not get
    // pulled since it is not used anywhere except by this test module
    // and so linking fails. Making this call creates dependency and
    // pulls this function at linking time before libimport.a and its
    // dependencies are considered
    QString version = findBestVersionMatch("pix", "7.0");
}

void ImporterTest::compareResults(QueueLogger* logger,
                                  QString expected_result_file_name,
                                  QString obtained_result_file_name)
{
    QString result;
    QStringList obtained_result;

    while (logger->ready())
        result.append(logger->getLine().c_str());
    obtained_result = result.split("\n");

    QFile rw(obtained_result_file_name);
    rw.open(QFile::WriteOnly);
    rw.write(result.toAscii());
    rw.close();

    QFile rr(expected_result_file_name);
    rr.open(QFile::ReadOnly);
    QString result_file = rr.readAll();
    QStringList expected_result = result_file.split("\n");

    CPPUNIT_ASSERT_MESSAGE(
        QString(
            "Sizes of the generated importer output and test files are different.\n"
            "Expected: %1 (%2)\n"
            "Obtained: %3 (%4)\n"
            "diff -u  %1 %3 | less -S")
        .arg(expected_result_file_name).arg(expected_result.size())
        .arg(obtained_result_file_name).arg(obtained_result.size()).toStdString(),
        expected_result.size() == obtained_result.size());

    int max_idx = max(expected_result.size(), obtained_result.size());
    for (int i=0; i < max_idx; ++i)
    {
        QString err = QString("%1:%2:\nExpected: '%3'\nResult: '%4'\n")
            .arg(expected_result_file_name)
            .arg(i)
            .arg(expected_result[i])
            .arg(obtained_result[i]);
        CPPUNIT_ASSERT_MESSAGE(
            err.toStdString(), obtained_result[i] == expected_result[i]);
    }
}

void ImporterTest::compareFwbFiles(QString expected_result_file_name,
                                   QString obtained_result_file_name)
{
    QString result;
    QStringList obtained_result;

    QFile rr(obtained_result_file_name);
    rr.open(QFile::ReadOnly);
    QString result_file = rr.readAll();
    rr.close();
    obtained_result = result_file.split("\n");
   
    QFile er(expected_result_file_name);
    er.open(QFile::ReadOnly);
    result_file = er.readAll();
    er.close();
    QStringList expected_result = result_file.split("\n");

    // find all lastModified attributes and replace them with identical values
    // because they are always going to be different

    QString err("Sizes of the generated .fwb and test files are different: \n"
                "Expected: %1 (%2)\n"
                "Obtained: %3 (%4)\n"
                "diff -u  %1 %3 | less -S");

    CPPUNIT_ASSERT_MESSAGE(
        err
        .arg(expected_result_file_name).arg(expected_result.size())
        .arg(obtained_result_file_name).arg(obtained_result.size())
        .toStdString(),
        expected_result.size() == obtained_result.size());

    QRegExp last_mod_re("lastModified=\"\\d+\"");
    int max_idx = max(expected_result.size(), obtained_result.size());
    for (int i=0; i < max_idx; ++i)
    {
        QString os = obtained_result[i];
        obtained_result[i] = os.replace(last_mod_re, "lastModified=\"0000000000\"");

        QString es = expected_result[i];
        expected_result[i] = es.replace(last_mod_re, "lastModified=\"0000000000\"");
    }

    for (int i=0; i < max_idx; ++i)
    {
        QString err = QString("%1:%2:\nExpected: '%3'\nResult: '%4'\n")
            .arg(expected_result_file_name)
            .arg(i)
            .arg(expected_result[i])
            .arg(obtained_result[i]);
        CPPUNIT_ASSERT_MESSAGE(
            err.toStdString(), obtained_result[i] == expected_result[i]);
    }
}

void ImporterTest::IOSImporterTest()
{
    platform = "iosacl";

    QFile f("test_data/ios.test");
    f.open(QFile::ReadOnly);

    string buffer = QString(f.readAll()).toStdString();
    f.close();

    std::istringstream instream(buffer);

    Importer* imp = new IOSImporter(lib, instream, logger, "test_fw");
    imp->setAddStandardCommentsFlag(true);
    CPPUNIT_ASSERT_NO_THROW( imp->run() );

    imp->finalize();

    db->setPredictableIds();
    db->saveFile("ios.fwb");

    compareFwbFiles("test_data/ios.fwb", "ios.fwb");
    compareResults(logger, "test_data/ios.output", "ios.output");
}

void ImporterTest::IPTImporterTest()
{
    platform = "iptables";

    QFile f("test_data/ipt.test");
    f.open(QFile::ReadOnly);

    string buffer = QString(f.readAll()).toStdString();
    f.close();

    std::istringstream instream(buffer);

    Importer* imp = new IPTImporter(lib, instream, logger, "test_fw");
    imp->setAddStandardCommentsFlag(true);
    CPPUNIT_ASSERT_NO_THROW( imp->run() );

    imp->finalize();

    db->setPredictableIds();
    db->saveFile("ipt.fwb");

    compareFwbFiles("test_data/ipt.fwb", "ipt.fwb");
    compareResults(logger, "test_data/ipt.output", "ipt.output");
}

void ImporterTest::IPTImporterNoNatTest()
{
    platform = "iptables";

    QFile f("test_data/ipt-no-nat.test");
    f.open(QFile::ReadOnly);

    string buffer = QString(f.readAll()).toStdString();
    f.close();

    std::istringstream instream(buffer);

    Importer* imp = new IPTImporter(lib, instream, logger, "test_fw");
    imp->setAddStandardCommentsFlag(true);
    CPPUNIT_ASSERT_NO_THROW( imp->run() );

    imp->finalize();

    db->setPredictableIds();
    db->saveFile("ipt-no-nat.fwb");

    compareFwbFiles("test_data/ipt-no-nat.fwb", "ipt-no-nat.fwb");
    compareResults(logger, "test_data/ipt-no-nat.output", "ipt-no-nat.output");
}

void ImporterTest::IPTImporterParseVersionsTest()
{
    platform = "iptables";

    QString iptables_save_file(iptables_sample);

    QStringList versions;

    versions << "v1.1.1" << "v1.1.1.1"
             << "v12.1.1" << "v12.1.1.1"
             << "v1.12.1" << "v1.12.1.1"
             << "v1.1.12" << "v1.1.12.1"
             << "v1.1.1.12" << "v1.2.1a";


    foreach (QString v, versions)
    {
        QString file_name = QString("ipt-%1").arg(v);

        QString actual_iptables_save = iptables_save_file;
        actual_iptables_save.replace("%VERSION%", v);
        std::istringstream instream(actual_iptables_save.toStdString());

        Importer* imp = new IPTImporter(lib, instream, logger, "test_fw");
        imp->setAddStandardCommentsFlag(true);
        CPPUNIT_ASSERT_NO_THROW( imp->run() );

        imp->finalize();

        // db->setPredictableIds();
        // db->saveFile(file_name.toStdString() + ".fwb");
        //
        // no need to compare .fwb files, we do not recognize these
        // test version numbers anyway so version will be set to "any"
        // in all tests anyway
        //
        // compareFwbFiles(QString("test_data/%1.fwb").arg(file_name),
        //                 QString("%1.fwb").arg(file_name));

        compareResults(logger,
                       QString("test_data/%1.output").arg(file_name),
                       QString("%1.output").arg(file_name));
    }
}
