// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

// This is the common code for TXT and SPF tests.

#include <util/buffer.h>
#include <dns/exceptions.h>
#include <dns/rdataclass.h>
#include <gtest/gtest.h>

#include <dns/tests/unittest_util.h>
#include <dns/tests/rdata_unittest.h>

using isc::UnitTestUtil;
using namespace std;
using namespace isc::dns;
using namespace isc::util;
using namespace isc::dns::rdata;


template<class T>
class RRTYPE : public RRType {
public:
    RRTYPE();
};

template<> RRTYPE<generic::TXT>::RRTYPE() : RRType(RRType::TXT()) {}
template<> RRTYPE<generic::SPF>::RRTYPE() : RRType(RRType::SPF()) {}

namespace {
const uint8_t wiredata_txt_like[] = {
    sizeof("Test String") - 1,
    'T', 'e', 's', 't', ' ', 'S', 't', 'r', 'i', 'n', 'g'
};

const uint8_t wiredata_nulltxt[] = { 0 };
vector<uint8_t> wiredata_longesttxt(256, 'a');

template<class TXT_LIKE>
class Rdata_TXT_LIKE_Test : public RdataTest {
protected:
    Rdata_TXT_LIKE_Test() {
        wiredata_longesttxt[0] = 255; // adjust length
    }

    static const TXT_LIKE rdata_txt_like;
    static const TXT_LIKE rdata_txt_like_empty;
    static const TXT_LIKE rdata_txt_like_quoted;
};

template<class TXT_LIKE>
const TXT_LIKE Rdata_TXT_LIKE_Test<TXT_LIKE>::rdata_txt_like("Test String");

template<class TXT_LIKE>
const TXT_LIKE Rdata_TXT_LIKE_Test<TXT_LIKE>::rdata_txt_like_empty("");

template<class TXT_LIKE>
const TXT_LIKE Rdata_TXT_LIKE_Test<TXT_LIKE>::rdata_txt_like_quoted
                                                          ("\"Test String\"");

// The list of types we want to test.
typedef testing::Types<generic::TXT, generic::SPF> Implementations;

TYPED_TEST_CASE(Rdata_TXT_LIKE_Test, Implementations);

TYPED_TEST(Rdata_TXT_LIKE_Test, createFromText) {
    // normal case is covered in toWireBuffer.

    // surrounding double-quotes shouldn't change the result.
    EXPECT_EQ(0, this->rdata_txt_like.compare(this->rdata_txt_like_quoted));

    // Null character-string.
    this->obuffer.clear();
    TypeParam(string("")).toWire(this->obuffer);
    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
                        this->obuffer.getData(),
                        this->obuffer.getLength(),
                        wiredata_nulltxt, sizeof(wiredata_nulltxt));

    // Longest possible character-string.
    this->obuffer.clear();
    TypeParam(string(255, 'a')).toWire(this->obuffer);
    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
                        this->obuffer.getData(),
                        this->obuffer.getLength(),
                        &wiredata_longesttxt[0], wiredata_longesttxt.size());

    // Too long text for a valid character-string.
    EXPECT_THROW(TypeParam(string(256, 'a')), CharStringTooLong);

    // The escape character makes the double quote a part of character-string,
    // so this is invalid input and should be rejected.
    EXPECT_THROW(TypeParam("\"Test String\\\""), InvalidRdataText);

    // Terminating double-quote is provided, so this is valid, but in this
    // version of implementation we reject escaped characters.
    EXPECT_THROW(TypeParam("\"Test String\\\"\""), InvalidRdataText);
}

void
makeLargest(vector<uint8_t>& data) {
    uint8_t ch = 0;

    // create 255 sets of character-strings, each of which has the longest
    // length (255bytes string + 1-byte length field)
    for (int i = 0; i < 255; ++i, ++ch) {
        data.push_back(255);
        data.insert(data.end(), 255, ch);
    }
    // the last character-string should be 255 bytes (including the one-byte
    // length field) in length so that the total length should be in the range
    // of 16-bit integers.
    data.push_back(254);
    data.insert(data.end(), 254, ch);

    assert(data.size() == 65535);
}

TYPED_TEST(Rdata_TXT_LIKE_Test, createFromWire) {
    EXPECT_EQ(0, this->rdata_txt_like.compare(
                  *this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
                                        "rdata_txt_fromWire1")));

    // Empty character string
    EXPECT_EQ(0, this->rdata_txt_like_empty.compare(
                  *this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
                                        "rdata_txt_fromWire2.wire")));

    // Multiple character strings
    this->obuffer.clear();
    this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
                         "rdata_txt_fromWire3.wire")->toWire(this->obuffer);
    // the result should be 'wiredata_txt' repeated twice
    vector<uint8_t> expected_data(wiredata_txt_like, wiredata_txt_like +
                                  sizeof(wiredata_txt_like));
    expected_data.insert(expected_data.end(), wiredata_txt_like,
                         wiredata_txt_like + sizeof(wiredata_txt_like));
    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
                        this->obuffer.getData(),
                        this->obuffer.getLength(),
                        &expected_data[0], expected_data.size());

    // Largest length of data.  There's nothing special, but should be
    // constructed safely, and the content should be identical to the original
    // data.
    vector<uint8_t> largest_txt_like_data;
    makeLargest(largest_txt_like_data);
    InputBuffer ibuffer(&largest_txt_like_data[0],
                        largest_txt_like_data.size());
    TypeParam largest_txt_like(ibuffer, largest_txt_like_data.size());
    this->obuffer.clear();
    largest_txt_like.toWire(this->obuffer);
    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
                        this->obuffer.getData(),
                        this->obuffer.getLength(),
                        &largest_txt_like_data[0],
                        largest_txt_like_data.size());

    // rdlen parameter is out of range.  This is a rare event because we'd
    // normally call the constructor via a polymorphic wrapper, where the
    // length is validated.  But this should be checked explicitly.
    InputBuffer ibuffer2(&largest_txt_like_data[0],
                         largest_txt_like_data.size());
    EXPECT_THROW(TypeParam(ibuffer2, 65536), InvalidRdataLength);

    // RDATA is empty, which is invalid for TXT_LIKE.
    EXPECT_THROW(this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
                                      "rdata_txt_fromWire4.wire"),
                 DNSMessageFORMERR);

    // character-string length is too large, which could cause overrun.
    EXPECT_THROW(this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
                                      "rdata_txt_fromWire5.wire"),
                 DNSMessageFORMERR);
}

TYPED_TEST(Rdata_TXT_LIKE_Test, toWireBuffer) {
    this->rdata_txt_like.toWire(this->obuffer);
    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
                        this->obuffer.getData(),
                        this->obuffer.getLength(),
                        wiredata_txt_like, sizeof(wiredata_txt_like));
}

TYPED_TEST(Rdata_TXT_LIKE_Test, toWireRenderer) {
    this->rdata_txt_like.toWire(this->renderer);
    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
                        this->renderer.getData(),
                        this->renderer.getLength(),
                        wiredata_txt_like, sizeof(wiredata_txt_like));
}

TYPED_TEST(Rdata_TXT_LIKE_Test, toText) {
    EXPECT_EQ("\"Test String\"", this->rdata_txt_like.toText());
}

TYPED_TEST(Rdata_TXT_LIKE_Test, assignment) {
    TypeParam rdata1("assignment1");
    TypeParam rdata2("assignment2");
    rdata1 = rdata2;
    EXPECT_EQ(0, rdata2.compare(rdata1));

    // Check if the copied data is valid even after the original is deleted
    TypeParam* rdata3 = new TypeParam(rdata1);
    TypeParam rdata4("assignment3");
    rdata4 = *rdata3;
    delete rdata3;
    EXPECT_EQ(0, rdata4.compare(rdata1));

    // Self assignment
    rdata2 = rdata2;
    EXPECT_EQ(0, rdata2.compare(rdata1));
}

TYPED_TEST(Rdata_TXT_LIKE_Test, compare) {
    string const txt1("aaaaaaaa");
    string const txt2("aaaaaaaaaa");
    string const txt3("bbbbbbbb");
    string const txt4(129, 'a');
    string const txt5(128, 'b');

    EXPECT_EQ(TypeParam(txt1).compare(TypeParam(txt1)), 0);

    EXPECT_LT(TypeParam("").compare(TypeParam(txt1)), 0);
    EXPECT_GT(TypeParam(txt1).compare(TypeParam("")), 0);

    EXPECT_LT(TypeParam(txt1).compare(TypeParam(txt2)), 0);
    EXPECT_GT(TypeParam(txt2).compare(TypeParam(txt1)), 0);

    EXPECT_LT(TypeParam(txt1).compare(TypeParam(txt3)), 0);
    EXPECT_GT(TypeParam(txt3).compare(TypeParam(txt1)), 0);

    // we're comparing the data raw, starting at the length octet, so a shorter
    // string sorts before a longer one no matter the lexicopraphical order
    EXPECT_LT(TypeParam(txt3).compare(TypeParam(txt2)), 0);
    EXPECT_GT(TypeParam(txt2).compare(TypeParam(txt3)), 0);

    // to make sure the length octet compares unsigned
    EXPECT_LT(TypeParam(txt1).compare(TypeParam(txt4)), 0);
    EXPECT_GT(TypeParam(txt4).compare(TypeParam(txt1)), 0);

    EXPECT_LT(TypeParam(txt5).compare(TypeParam(txt4)), 0);
    EXPECT_GT(TypeParam(txt4).compare(TypeParam(txt5)), 0);

    // comparison attempt between incompatible RR types should be rejected
    EXPECT_THROW(TypeParam(txt1).compare(*this->rdata_nomatch),
                 bad_cast);
}

}
