// Copyright (C) 2011-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/.

#ifndef TSIGRECORD_H
#define TSIGRECORD_H

#include <ostream>
#include <string>

#include <boost/shared_ptr.hpp>

#include <util/buffer.h>

#include <dns/name.h>
#include <dns/rdataclass.h>

namespace isc {
namespace dns {
class AbstractMessageRenderer;

/// TSIG resource record.
///
/// A \c TSIGRecord class object represents a TSIG resource record and is
/// responsible for conversion to and from wire format TSIG record based on
/// the protocol specification (RFC2845).
/// This class is provided so that other classes and applications can handle
/// TSIG without knowing protocol details of TSIG, such as that it uses a
/// fixed constant of TTL.
///
/// \todo So the plan is to eventually provide  the "from wire" constructor.
/// It's not yet provided in the current phase of development.
///
/// \note
/// This class could be a derived class of \c AbstractRRset.  That way
/// it would be able to be used in a polymorphic way; for example,
/// an application can construct a TSIG RR by itself and insert it to a
/// \c Message object as a generic RRset.  On the other hand, it would mean
/// this class would have to implement an \c RdataIterator (even though it
/// can be done via straightforward forwarding) while the iterator is mostly
/// redundant since there should be one and only one RDATA for a valid TSIG
/// RR.  Likewise, some methods such as \c setTTL() method wouldn't be well
/// defined due to such special rules for TSIG as using a fixed TTL.
/// Overall, TSIG is a very special RR type that simply uses the compatible
/// resource record format, and it will be unlikely that a user wants to
/// handle it through a generic interface in a polymorphic way.
/// We therefore chose to define it as a separate class.  This is also
/// similar to why \c EDNS is a separate class.
class TSIGRecord {
public:
    ///
    /// \name Constructors
    ///
    /// We use the default copy constructor, default copy assignment operator,
    /// (and default destructor) intentionally.
    //@{
    /// Constructor from TSIG key name and RDATA
    ///
    /// \exception std::bad_alloc Resource allocation for copying the name or
    /// RDATA fails
    TSIGRecord(const Name& key_name, const rdata::any::TSIG& tsig_rdata);

    /// Constructor from resource record (RR) parameters.
    ///
    /// This constructor is intended to be used in the context of parsing
    /// an incoming DNS message that contains a TSIG.  The parser would
    /// first extract the owner name, RR type (which is TSIG) class, TTL and
    /// the TSIG RDATA from the message.  This constructor is expected to
    /// be given these RR parameters (except the RR type, because it must be
    /// TSIG).
    ///
    /// According to RFC2845, a TSIG RR uses fixed RR class (ANY) and TTL (0).
    /// If the RR class or TTL is different from the expected one, this
    /// implementation considers it an invalid record and throws an exception
    /// of class \c DNSMessageFORMERR.
    ///
    /// \note This behavior is not specified in the protocol specification,
    /// but this implementation rejects unexpected values for the following
    /// reasons (but in any case, this won't matter much in practice as
    /// RFC2848 clearly states these fields always have the fixed values and
    /// any sane implementation of TSIG signer will follow that):
    /// According to the RFC editor (in a private communication), the intended
    /// use of the TSIG TTL field is to signal protocol extensions (currently
    /// no such extension is defined), so this field may actually be
    /// validly non 0 in future.  However, until the implementation supports
    /// that extension it may not always be able to handle the extended
    /// TSIG as intended; the extension may even affect digest computation.
    /// There's a related issue on this point.  Different implementations
    /// interpret the RFC in different ways on the received TTL when
    /// digesting the message: BIND 9 uses the received value (even if
    /// it's not 0) as part of the TSIG variables; NLnet Labs' LDNS and NSD
    /// always use a fixed constant of 0 regardless of the received TTL value.
    /// This means if and when an extension with non 0 TTL is introduced
    /// there will be interoperability problems in the form of verification
    /// failure.  By explicitly rejecting it (and subsequently returning
    /// a response with a format error) we can indicate the source of the
    /// problem more clearly than a "bad signature" TSIG error, which can
    /// happen for various reasons.  On the other hand, rejecting unexpected
    /// RR classes is mostly for consistency; the RFC lists these two fields
    /// in the same way, so it makes more sense to handle them equally.
    /// (BIND 9 rejects unexpected RR classes for TSIG, but that is part of
    /// general check on RR classes on received RRs; it generally requests
    /// all classes are the same, and if the protocol specifies the use of
    /// a particular class for a particular type of RR, it requests that
    /// class be used).
    ///
    /// Likewise, if \c rdata is not of type \c any::TSIG, an exception of
    /// class DNSMessageFORMERR will be thrown.  When the caller is a
    /// DNS message parser and builds \c rdata from incoming wire format
    /// data as described above, this case happens when the RR class is
    /// different from ANY (in the implementation, the type check takes place
    /// before the explicit check against the RR class explained in the
    /// previous paragraph).
    ///
    /// The \c length parameter is intended to be the length of the TSIG RR
    /// (from the beginning of the owner name to the end of the RDATA) when
    /// the caller is a DNS message parser.  Note that it is the actual length
    /// for the RR in the format; if the owner name or the algorithm name
    /// (in the RDATA) is compressed (although the latter should not be
    /// compressed according to RFC3597), the length must be the size of the
    /// compressed data.  The length is recorded inside the class and will
    /// be returned via subsequent calls to \c getLength().  It's intended to
    /// be used in the context TSIG verification; in the verify process
    /// the MAC computation must be performed for the original data without
    /// TSIG, so, to avoid parsing the entire data in the verify process
    /// again, it's necessary to record information that can identify the
    /// length to be digested for the MAC.  This parameter serves for that
    /// purpose.
    ///
    /// \note Since the constructor doesn't take the wire format data per se,
    /// it doesn't (and cannot) check the validity of \c length, and simply
    /// accepts any given value.  It even accepts obviously invalid values
    /// such as 0.  It's caller's responsibility to provide a valid value of
    /// length, and, the verifier's responsibility to use the length safely.
    ///
    /// <b>DISCUSSION:</b> this design is fragile in that it introduces
    /// a tight coupling between message parsing and TSIG verification via
    /// the \c TSIGRecord class.  In terms of responsibility decoupling,
    /// the ideal way to have \c TSIGRecord remember the entire wire data
    /// along with the length of the TSIG.  Then in the TSIG verification
    /// we could refer to the necessary potion of data solely from a
    /// \c TSIGRecord object.  However, this approach would require expensive
    /// heavy copy of the original data or introduce another kind of coupling
    /// between the data holder and this class (if the original data is freed
    /// while a \c TSIGRecord object referencing the data still exists, the
    /// result will be catastrophic).  As a "best current compromise", we
    /// use the current design.  We may reconsider it if it turns out to
    /// cause a big problem or we come up with a better idea.
    ///
    /// \exception DNSMessageFORMERR Given RR parameters are invalid for TSIG.
    /// \exception std::bad_alloc Internal resource allocation fails.
    ///
    /// \param name The owner name of the TSIG RR
    /// \param rrclass The RR class of the RR.  Must be \c RRClass::ANY()
    /// (see above)
    /// \param ttl The TTL of the RR.  Must be 0 (see above)
    /// \param rdata The RDATA of the RR.  Must be of type \c any::TSIG.
    /// \param length The size of the RR (see above)
    TSIGRecord(const Name& name, const RRClass& rrclass, const RRTTL& ttl,
               const rdata::Rdata& rdata, size_t length);
    //@}

    /// Return the owner name of the TSIG RR, which is the TSIG key name
    ///
    /// \exception None
    const Name& getName() const {
        return (key_name_);
    }

    /// Return the RDATA of the TSIG RR
    ///
    /// \exception None
    const rdata::any::TSIG& getRdata() const {
        return (rdata_);
    }

    /// \name Protocol constants and defaults
    ///
    //@{
    /// Return the RR class of TSIG
    ///
    /// TSIG always uses the ANY RR class.  This static method returns it,
    /// when, though unlikely, an application wants to know which class TSIG
    /// is supposed to use.
    ///
    /// \exception None
    static const RRClass& getClass();

    /// Return the TTL value of TSIG
    ///
    /// TSIG always uses 0 TTL.  This static method returns it,
    /// when, though unlikely, an application wants to know the TTL TSIG
    /// is supposed to use.
    ///
    /// \exception None
    static const RRTTL& getTTL();
    //@}

    /// Return the length of the TSIG record
    ///
    /// When constructed from the key name and RDATA, it is the length of
    /// the record when it is rendered by the \c toWire() method.
    ///
    /// \note When constructed "from wire", that will mean the length of
    /// the wire format data for the TSIG RR.  The length will be necessary
    /// to verify the message once parse is completed.
    ///
    /// \exception None
    size_t getLength() const {
        return (length_);
    }

    /// \brief Render the \c TSIG RR in the wire format.
    ///
    /// This method renders the TSIG record as a form of a DNS TSIG RR
    /// via \c renderer, which encapsulates output buffer and other rendering
    /// contexts.
    ///
    /// Normally this version of \c toWire() method tries to compress the
    /// owner name of the RR whenever possible, but this method intentionally
    /// skips owner name compression.  This is due to a report that some
    /// Windows clients refuse a TSIG if its owner name is compressed
    /// (See http://marc.info/?l=bind-workers&m=126637138430819&w=2).
    /// Reportedly this seemed to be specific to GSS-TSIG, but this
    /// implementation skip compression regardless of the algorithm.
    ///
    /// If by adding the TSIG RR the message size would exceed the limit
    /// maintained in \c renderer, this method skips rendering the RR
    /// and returns 0 and mark \c renderer as "truncated" (so that a
    /// subsequent call to \c isTruncated() on \c renderer will result in
    /// \c true); otherwise it returns 1, which is the number of RR
    /// rendered.
    ///
    /// \note If the caller follows the specification of adding TSIG
    /// as described in RFC2845, this should not happen; the caller is
    /// generally expected to leave a sufficient room in the message for
    /// the TSIG.  But this method checks the unexpected case nevertheless.
    ///
    /// \exception std::bad_alloc Internal resource allocation fails (this
    /// should be rare).
    ///
    /// \param renderer DNS message rendering context that encapsulates the
    /// output buffer and name compression information.
    /// \return 1 if the TSIG RR fits in the message size limit; otherwise 0.
    uint32_t toWire(AbstractMessageRenderer& renderer) const;

    /// \brief Render the \c TSIG RR in the wire format.
    ///
    /// This method is same as \c toWire(AbstractMessageRenderer&)const
    /// except it renders the RR in an \c OutputBuffer and therefore
    /// does not care about message size limit.
    /// As a consequence it always returns 1.
    uint32_t toWire(isc::util::OutputBuffer& buffer) const;

    /// Convert the TSIG record to a string.
    ///
    /// The output format is the same as the result of \c toText() for
    /// other normal types of RRsets (with always using the same RR class
    /// and TTL).  It also ends with a newline.
    ///
    /// \exception std::bad_alloc Internal resource allocation fails (this
    /// should be rare).
    ///
    /// \return A string representation of \c TSIG record
    std::string toText() const;

    /// The TTL value to be used in TSIG RRs.
    static const uint32_t TSIG_TTL = 0;
    //@}

private:
    const Name key_name_;
    const rdata::any::TSIG rdata_;
    const size_t length_;
};

/// A pointer-like type pointing to a \c TSIGRecord object.
typedef boost::shared_ptr<TSIGRecord> TSIGRecordPtr;

/// A pointer-like type pointing to an immutable \c TSIGRecord object.
typedef boost::shared_ptr<const TSIGRecord> ConstTSIGRecordPtr;

/// Insert the \c TSIGRecord as a string into stream.
///
/// This method convert \c record into a string and inserts it into the
/// output stream \c os.
///
/// \param os A \c std::ostream object on which the insertion operation is
/// performed.
/// \param record A \c TSIGRecord object output by the operation.
/// \return A reference to the same \c std::ostream object referenced by
/// parameter \c os after the insertion operation.
std::ostream& operator<<(std::ostream& os, const TSIGRecord& record);
}
}

#endif  // TSIGRECORD_H
