// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>

#include <exceptions/isc_assert.h>
#include <dns/messagerenderer.h>
#include <dns/name.h>
#include <dns/rrclass.h>
#include <dns/rrtype.h>
#include <dns/rrttl.h>
#include <dns/rrset.h>
#include <util/buffer.h>

#include <algorithm>
#include <string>
#include <vector>
#include <boost/shared_ptr.hpp>

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

using namespace std;

namespace isc {
namespace dns {
void
AbstractRRset::addRdata(const Rdata& rdata) {
    addRdata(createRdata(getType(), getClass(), rdata));
}

string
AbstractRRset::toText() const {
    string s;
    RdataIteratorPtr it = getRdataIterator();

    // In the case of an empty rrset, just print name, ttl, class, and
    // type
    if (it->isLast()) {
        // But only for class ANY or NONE
        if (getClass() != RRClass::ANY() &&
            getClass() != RRClass::NONE()) {
            isc_throw(EmptyRRset, "toText() is attempted for an empty RRset");
        }

        s += getName().toText() + " " + getTTL().toText() + " " +
             getClass().toText() + " " + getType().toText() + "\n";
        return (s);
    }

    do {
        s += getName().toText() + " " + getTTL().toText() + " " +
             getClass().toText() + " " + getType().toText() + " " +
             it->getCurrent().toText() + "\n";
        it->next();
    } while (!it->isLast());

    if (getRRsig()) {
        s += getRRsig()->toText();
    }

    return (s);
}

namespace { // unnamed namespace

// FIXME: This method's code should somehow be unified with
// BasicRRsetImpl::toWire() below to avoid duplication.
template <typename T>
inline uint32_t
rrsetToWire(const AbstractRRset& rrset, T& output, const size_t limit) {
    uint32_t n = 0;
    RdataIteratorPtr it = rrset.getRdataIterator();

    if (it->isLast()) {
        // empty rrsets are only allowed for classes ANY and NONE
        if (rrset.getClass() != RRClass::ANY() &&
            rrset.getClass() != RRClass::NONE()) {
            isc_throw(EmptyRRset, "toWire() is attempted for an empty RRset");
        }

        // For an empty RRset, write the name, type, class and TTL once,
        // followed by empty rdata.
        rrset.getName().toWire(output);
        rrset.getType().toWire(output);
        rrset.getClass().toWire(output);
        rrset.getTTL().toWire(output);
        output.writeUint16(0);
        // Still counts as 1 'rr'; it does show up in the message
        return (1);
    }

    // sort the set of Rdata based on rrset-order and sortlist, and possible
    // other options.  Details to be considered.
    do {
        const size_t pos0 = output.getLength();
        isc_throw_assert(pos0 < 65536);

        rrset.getName().toWire(output);
        rrset.getType().toWire(output);
        rrset.getClass().toWire(output);
        rrset.getTTL().toWire(output);

        const size_t pos = output.getLength();
        output.skip(sizeof(uint16_t)); // leave the space for RDLENGTH
        it->getCurrent().toWire(output);
        output.writeUint16At(output.getLength() - pos - sizeof(uint16_t), pos);

        if (limit > 0 && output.getLength() > limit) {
            // truncation is needed
            output.trim(output.getLength() - pos0);
            return (n);
        }

        it->next();
        ++n;
    } while (!it->isLast());

    return (n);
}

} // end of unnamed namespace

uint32_t
AbstractRRset::toWire(OutputBuffer& buffer) const {
    return (rrsetToWire<OutputBuffer>(*this, buffer, 0));
}

uint32_t
AbstractRRset::toWire(AbstractMessageRenderer& renderer) const {
    const uint32_t rrs_written = rrsetToWire<AbstractMessageRenderer>(
        *this, renderer, renderer.getLengthLimit());
    if (getRdataCount() > rrs_written) {
        renderer.setTruncated();
    }
    return (rrs_written);
}

bool
AbstractRRset::isSameKind(const AbstractRRset& other) const {
  // Compare classes last as they're likely to be identical. Compare
  // names late in the list too, as these are expensive. So we compare
  // types first, names second and classes last.
  return (getType() == other.getType() &&
          getName() == other.getName() &&
          getClass() == other.getClass());
}

ostream&
operator<<(ostream& os, const AbstractRRset& rrset) {
    os << rrset.toText();
    return (os);
}

/// \brief This encapsulates the actual implementation of the \c BasicRRset
/// class.  It's hidden from applications.
class BasicRRsetImpl {
public:
    BasicRRsetImpl(const Name& name, const RRClass& rrclass,
                   const RRType& rrtype, const RRTTL& ttl) :
        name_(name), rrclass_(rrclass), rrtype_(rrtype), ttl_(ttl) {}

    uint32_t toWire(AbstractMessageRenderer& renderer, size_t limit) const;

    Name name_;
    RRClass rrclass_;
    RRType rrtype_;
    RRTTL ttl_;
    // XXX: "list" is not a good name: It in fact isn't a list; more conceptual
    // name than a data structure name is generally better.  But since this
    // is only used in the internal implementation we'll live with it.
    vector<ConstRdataPtr> rdatalist_;
};

// FIXME: This method's code should somehow be unified with
// rrsetToWire() above to avoid duplication.
uint32_t
BasicRRsetImpl::toWire(AbstractMessageRenderer& renderer, size_t limit) const {
    if (rdatalist_.empty()) {
        // empty rrsets are only allowed for classes ANY and NONE
        if (rrclass_ != RRClass::ANY() &&
            rrclass_ != RRClass::NONE()) {
            isc_throw(EmptyRRset, "toWire() is attempted for an empty RRset");
        }

        // For an empty RRset, write the name, type, class and TTL once,
        // followed by empty rdata.
        name_.toWire(renderer);
        rrtype_.toWire(renderer);
        rrclass_.toWire(renderer);
        ttl_.toWire(renderer);
        renderer.writeUint16(0);
        // Still counts as 1 'rr'; it does show up in the message
        return (1);
    }

    uint32_t n = 0;

    // sort the set of Rdata based on rrset-order and sortlist, and possible
    // other options.  Details to be considered.
    for (auto const& rdata : rdatalist_) {
        const size_t pos0 = renderer.getLength();
        isc_throw_assert(pos0 < 65536);

        name_.toWire(renderer);
        rrtype_.toWire(renderer);
        rrclass_.toWire(renderer);
        ttl_.toWire(renderer);

        const size_t pos = renderer.getLength();
        renderer.skip(sizeof(uint16_t)); // leave the space for RDLENGTH
        rdata->toWire(renderer);
        renderer.writeUint16At(renderer.getLength() - pos - sizeof(uint16_t),
                               pos);

        if (limit > 0 && renderer.getLength() > limit) {
            // truncation is needed
            renderer.trim(renderer.getLength() - pos0);
            return (n);
        }
        ++n;
    }

    return (n);
}

BasicRRset::BasicRRset(const Name& name, const RRClass& rrclass,
                       const RRType& rrtype, const RRTTL& ttl) {
    impl_.reset(new BasicRRsetImpl(name, rrclass, rrtype, ttl));
}

BasicRRset::~BasicRRset() {
}

void
BasicRRset::addRdata(ConstRdataPtr rdata) {
    impl_->rdatalist_.push_back(rdata);
}

void
BasicRRset::addRdata(const Rdata& rdata) {
    AbstractRRset::addRdata(rdata);
}

void
BasicRRset::addRdata(const std::string& rdata_str) {
    addRdata(createRdata(getType(), getClass(), rdata_str));
}

uint32_t
BasicRRset::getRdataCount() const {
    return (impl_->rdatalist_.size());
}

const Name&
BasicRRset::getName() const {
    return (impl_->name_);
}

const RRClass&
BasicRRset::getClass() const {
    return (impl_->rrclass_);
}

const RRType&
BasicRRset::getType() const {
    return (impl_->rrtype_);
}

const RRTTL&
BasicRRset::getTTL() const {
    return (impl_->ttl_);
}

void
BasicRRset::setTTL(const RRTTL& ttl) {
    impl_->ttl_ = ttl;
}

string
BasicRRset::toText() const {
    return (AbstractRRset::toText());
}

uint16_t
BasicRRset::getLength() const {
    uint16_t length = 0;
    RdataIteratorPtr it = getRdataIterator();

    if (it->isLast()) {
        // empty rrsets are only allowed for classes ANY and NONE
        if (getClass() != RRClass::ANY() &&
            getClass() != RRClass::NONE()) {
            isc_throw(EmptyRRset, "getLength() is attempted for an empty RRset");
        }

        // For an empty RRset, write the name, type, class and TTL once,
        // followed by empty rdata.
        length += getName().getLength();
        length += 2; // TYPE field
        length += 2; // CLASS field
        length += 4; // TTL field
        length += 2; // RDLENGTH field (=0 in wire format)

        return (length);
    }

    do {
        // This is a size_t as some of the following additions may
        // overflow due to a programming mistake somewhere.
        size_t rrlen = 0;

        rrlen += getName().getLength();
        rrlen += 2; // TYPE field
        rrlen += 2; // CLASS field
        rrlen += 4; // TTL field
        rrlen += 2; // RDLENGTH field
        rrlen += it->getCurrent().getLength();

        isc_throw_assert(length + rrlen < 65536);
        length += rrlen;

        it->next();
    } while (!it->isLast());

    return (length);
}

uint32_t
BasicRRset::toWire(OutputBuffer& buffer) const {
    return (AbstractRRset::toWire(buffer));
}

uint32_t
BasicRRset::toWire(AbstractMessageRenderer& renderer) const {
    const uint32_t rrs_written = impl_->toWire(renderer,
                                                   renderer.getLengthLimit());
    if (impl_->rdatalist_.size() > rrs_written) {
        renderer.setTruncated();
    }
    return (rrs_written);
}

RRset::RRset(const Name& name, const RRClass& rrclass,
             const RRType& rrtype, const RRTTL& ttl) :
    BasicRRset(name, rrclass, rrtype, ttl) {
}

RRset::~RRset() {
}

uint32_t
RRset::getRRsigDataCount() const {
    if (rrsig_) {
        return (rrsig_->getRdataCount());
    } else {
        return (0);
    }
}

uint16_t
RRset::getLength() const {
    uint16_t length = BasicRRset::getLength();

    if (rrsig_) {
        const uint16_t rrsigs_length = rrsig_->getLength();
        // the uint16_ts are promoted to ints during addition below, so
        // it won't overflow a 16-bit register.
        isc_throw_assert(length + rrsigs_length < 65536);
        length += rrsigs_length;
    }

    return (length);
}

uint32_t
RRset::toWire(OutputBuffer& buffer) const {
    uint32_t rrs_written = BasicRRset::toWire(buffer);
    if (getRdataCount() > rrs_written) {
        return (rrs_written);
    }

    if (rrsig_) {
        rrs_written += rrsig_->toWire(buffer);
    }

    return (rrs_written);
}

uint32_t
RRset::toWire(AbstractMessageRenderer& renderer) const {
    uint32_t rrs_written = BasicRRset::toWire(renderer);
    if (getRdataCount() > rrs_written) {
        return (rrs_written);
    }

    if (rrsig_) {
        rrs_written += rrsig_->toWire(renderer);

        if (getRdataCount() + getRRsigDataCount() > rrs_written) {
            renderer.setTruncated();
        }
    }

    return (rrs_written);
}

namespace {

class BasicRdataIterator : public RdataIterator {
public:
    /// @brief Constructor.
    BasicRdataIterator(const std::vector<rdata::ConstRdataPtr>& datavector) :
        datavector_(&datavector), it_(datavector_->begin()) {
    }

    /// @brief Destructor.
    ~BasicRdataIterator() {
    }

    /// @brief Set iterator at first position.
    virtual void first() {
        it_ = datavector_->begin();
    }

    /// @brief Advance iterator.
    virtual void next() {
        ++it_;
    }

    /// @brief Get value at current iterator position.
    ///
    /// @return The value at current iterator position.
    virtual const rdata::Rdata& getCurrent() const {
        return (**it_);
    }

    /// @brief Check if iterator has reached the end.
    ///
    /// @return true if iterator has reached the end, false otherwise.
    virtual bool isLast() const {
        return (it_ == datavector_->end());
    }

private:
    /// @brief Vector containing data.
    const std::vector<rdata::ConstRdataPtr>* datavector_;

    /// @brief Iterator used to retrieve data.
    std::vector<rdata::ConstRdataPtr>::const_iterator it_;
};

}

RdataIteratorPtr
BasicRRset::getRdataIterator() const {
    return (RdataIteratorPtr(new BasicRdataIterator(impl_->rdatalist_)));
}

}
}
