// 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/.

#ifndef RRPARAMREGISTRY_H
#define RRPARAMREGISTRY_H

#include <string>

#include <stdint.h>

#include <boost/shared_ptr.hpp>

#include <dns/exceptions.h>

#include <dns/rdata.h>

namespace isc {
namespace dns {

// forward declarations
struct RRParamRegistryImpl;

///
/// \brief A standard DNS module exception that is thrown if a new RR type is
/// being registered with a different type string.
///
class RRTypeExists : public isc::dns::Exception {
public:
    RRTypeExists(const char* file, size_t line, const char* what) :
        isc::dns::Exception(file, line, what) {}
};

///
/// \brief A standard DNS module exception that is thrown if a new RR class is
/// being registered with a different type string.
///
class RRClassExists : public isc::dns::Exception {
public:
    RRClassExists(const char* file, size_t line, const char* what) :
        isc::dns::Exception(file, line, what) {}
};

namespace rdata {
/// \brief The \c AbstractRdataFactory class is an abstract base class to
/// encapsulate a set of Rdata factory methods in a polymorphic way.
///
/// An external developer who wants to introduce a new or experimental RR type
/// is expected to define a corresponding derived class of \c
/// AbstractRdataFactory and register it via \c RRParamRegistry.
///
/// Other users of this API normally do not have to care about this class
/// or its derived classes; this class is generally intended to be used
/// as an internal utility of the API implementation.
class AbstractRdataFactory {
    ///
    /// \name Constructors and Destructor
    ///
    //@{
protected:
    /// The default constructor
    ///
    /// This is intentionally defined as \c protected as this base class should
    /// never be instantiated (except as part of a derived class).
    AbstractRdataFactory() {}
public:
    /// The destructor.
    virtual ~AbstractRdataFactory() {};
    //@}

    ///
    /// \name Factory methods for polymorphic creation.
    ///
    //@{

    /// \brief Create RDATA from a string.
    ///
    /// This method creates from a string an \c Rdata object of specific class
    /// corresponding to the specific derived class of \c AbstractRdataFactory.
    ///
    /// \param rdata_str A string of textual representation of the \c Rdata.
    /// \return An \c RdataPtr object pointing to the created \c Rdata object.
    virtual RdataPtr create(const std::string& rdata_str) const = 0;

    /// \brief Create RDATA from wire-format data.
    ///
    /// This method creates from wire-format binary data an \c Rdata object
    /// of specific class corresponding to the specific derived class of
    /// \c AbstractRdataFactory.
    ///
    /// \param buff A reference to an \c InputBuffer object storing the
    /// \c Rdata to parse.
    /// \param rdata_len The length in buffer of the \c Rdata.  In bytes.
    /// \return An \c RdataPtr object pointing to the created \c Rdata object.
    virtual RdataPtr create(isc::util::InputBuffer& buff,
                            size_t rdata_len) const = 0;

    /// \brief Create RDATA from another \c Rdata object of the same type.
    ///
    /// This method creates an \c Rdata object of specific class corresponding
    /// to the specific derived class of \c AbstractRdataFactory, copying the
    /// content of the given \c Rdata, \c source.
    ///
    /// \c source must be an object of the concrete derived class corresponding
    /// to the specific derived class of \c AbstractRdataFactory;
    /// otherwise, an exception of class \c std::bad_cast will be thrown.
    ///
    /// \param source A reference to an \c Rdata object whose content is to
    /// be copied to the created \c Rdata object.
    /// \return An \c RdataPtr object pointing to the created \c Rdata object.
    virtual RdataPtr create(const rdata::Rdata& source) const = 0;

    /// \brief Create RDATA using MasterLexer.
    ///
    /// This version of the method defines the entry point of factory
    /// of a specific RR type and class for \c RRParamRegistry::createRdata()
    /// that uses \c MasterLexer.  See its description for the expected
    /// behavior and meaning of the parameters.
    virtual RdataPtr create(MasterLexer& lexer, const Name* origin,
                            MasterLoader::Options options,
                            MasterLoaderCallbacks& callbacks) const = 0;
    //@}
};

///
/// The \c RdataFactoryPtr type is a pointer-like type, pointing to an
/// object of some concrete derived class of \c AbstractRdataFactory.
///
typedef boost::shared_ptr<AbstractRdataFactory> RdataFactoryPtr;
} // end of namespace rdata

///
/// The \c RRParamRegistry class represents a registry of parameters to
/// manipulate DNS resource records (RRs).
///
/// A \c RRParamRegistry class object stores a mapping between RR types or
/// classes and their textual representations.  It will also have knowledge of
/// how to create an RDATA object for a specific pair of RR type and class
/// (not implemented in this version).
///
/// Normal applications that only handle standard DNS protocols won't have to
/// care about this class.  This is mostly an internal class to the DNS library
/// to manage standard parameters.  Some advanced applications may still need
/// to use this class explicitly.  For example, if an application wants to
/// define and use an experimental non-standard RR type, it may want to register
/// related protocol parameters for its convenience.  This class is designed to
/// allow such usage without modifying the library source code or rebuilding
/// the library.
///
/// It is assumed that at most one instance of this class can exist so that
/// the application uses the consistent set of registered parameters.  To ensure
/// this, this class is designed and implemented as a "singleton class": the
/// constructor is intentionally private, and applications must get access to
/// the single instance via the \c getRegistry() static member function.
///
/// In the current implementation, access to the singleton \c RRParamRegistry
/// object is not thread safe.
/// The application should ensure that multiple threads don't race in the
/// first invocation of \c getRegistry(), and, if the registry needs to
/// be changed dynamically, read and write operations are performed
/// exclusively.
/// Since this class should be static in common usage this restriction would
/// be acceptable in practice.
/// In the future, we may extend the implementation so that multiple threads can
/// get access to the registry fully concurrently without any restriction.
///
/// Note: the implementation of this class is incomplete: we should at least
/// add RDATA related parameters.  This will be done in a near future version,
/// at which point some of method signatures will be changed.
class RRParamRegistry {
    ///
    /// \name Constructors and Destructor
    ///
    /// These are intentionally hidden (see the class description).
    //@{
private:
    RRParamRegistry();
    RRParamRegistry(const RRParamRegistry& orig);
    ~RRParamRegistry();
    //@}
public:
    ///
    /// \brief Return the singleton instance of \c RRParamRegistry.
    ///
    /// This method is a unified access point to the singleton instance of
    /// the RR parameter registry (\c RRParamRegistry).
    /// On first invocation it internally constructs an instance of the
    /// \c RRParamRegistry class and returns a reference to it.
    /// This is a static object inside this method and will remain valid
    /// throughout the rest of the application lifetime.
    /// On subsequent calls this method simply returns a reference to the
    /// singleton object.
    ///
    /// If resource allocation fails in the first invocation,
    /// a corresponding standard exception will be thrown.
    /// This method never fails otherwise.  In particular, this method
    /// doesn't throw an exception once the singleton instance is constructed.
    ///
    /// \return A reference to the singleton instance of \c RRParamRegistry.
    static RRParamRegistry& getRegistry();

    ///
    /// \name Registry Update Methods
    ///
    //@{
    ///
    /// \brief Add a set of parameters for a pair of RR type and class.
    ///
    /// This method adds to the registry a specified set of RR parameters,
    /// including mappings between type/class codes and their textual
    /// representations.
    ///
    /// Regarding the mappings between textual representations and integer
    /// codes, this method behaves in the same way as \c addType() and
    /// \c addClass().  That is, it ignores any overriding attempt as
    /// long as the mapping is the same; otherwise the corresponding exception
    /// will be thrown.
    ///
    /// This method provides the strong exception guarantee: unless an
    /// exception is thrown the entire specified set of parameters must be
    /// stored in the registry; if this method throws an exception the
    /// registry will remain in the state before this method is called.
    ///
    /// \param type_string The textual representation of the RR type.
    /// \param type_code The integer code of the RR type.
    /// \param class_string The textual representation of the RR class.
    /// \param class_code The integer code of the RR class.
    /// \param rdata_factory An \c RdataFactoryPtr object pointing to a
    /// concrete RDATA factory.
    void add(const std::string& type_string, uint16_t type_code,
             const std::string& class_string, uint16_t class_code,
             rdata::RdataFactoryPtr rdata_factory);

    /// \brief Add a set of parameters for a class-independent RR type.
    ///
    /// This method behaves as exactly same as the other \c add method except
    /// that it handles class-independent types (such as NS, CNAME, or SOA).
    ///
    /// \param type_string The textual representation of the RR type.
    /// \param type_code The integer code of the RR type.
    /// \param rdata_factory An \c RdataFactoryPtr object pointing to a
    /// concrete RDATA factory.
    void add(const std::string& type_string, uint16_t type_code,
             rdata::RdataFactoryPtr rdata_factory);

    /// \brief Add mappings between RR type code and textual representation.
    ///
    /// This method adds a mapping from the type code of an RR to its textual
    /// representation and the reverse mapping in the registry.
    ///
    /// If the given RR type is already registered with the same textual
    /// representation, this method simply ignores the duplicate mapping;
    /// if the given type is registered and a new pair with a different
    /// textual representation is being added,an exception of class
    /// \c RRTypeExist will be thrown.
    /// To replace an existing mapping with a different textual representation,
    /// the existing one must be removed by the \c removeType() method
    /// beforehand.
    ///
    /// In addition, if resource allocation for the new mapping entries fails,
    /// a corresponding standard exception will be thrown.
    ///
    /// This method provides the strong exception guarantee: unless an exception
    /// is thrown the specified mappings must be stored in the registry
    /// (although it may be an already existing one) on completion of the
    /// method; if this method throws an exception the registry will remain
    /// in the state before this method is called.
    ///
    /// \param type_string The textual representation of the RR type.
    /// \param type_code The integer code of the RR type.
    /// \return \c true if a new mapping is added to the repository; \c false
    /// if the same mapping is already registered.
    bool addType(const std::string& type_string, uint16_t type_code);

    /// \brief Remove mappings between RR type code and textual representation
    /// for a given type.
    ///
    /// This method can safely be called whether or not the specified mappings
    /// exist in the registry.  If not, this method simply ignores the attempt
    /// and returns \c false.
    ///
    /// This method never throws an exception.
    ///
    /// \param type_code The integer code of the RR type.
    /// \return \c true if mappings for the specified RR type exists and is
    /// removed; \c false if no such mapping is in the registry.
    bool removeType(uint16_t type_code);

    /// \brief Add mappings between RR class code and textual representation.
    ///
    /// This method adds a mapping from the class code of an RR to its textual
    /// representation and the reverse mapping in the registry.
    ///
    /// If the given RR class is already registered with the same textual
    /// representation, this method simply ignores the duplicate mapping;
    /// if the given class is registered and a new pair with a different
    /// textual representation is being added,an exception of class
    /// \c RRClassExist will be thrown.
    /// To replace an existing mapping with a different textual representation,
    /// the existing one must be removed by the \c removeClass() method
    /// beforehand.
    ///
    /// In addition, if resource allocation for the new mapping entries fails,
    /// a corresponding standard exception will be thrown.
    ///
    /// This method provides the strong exception guarantee: unless an exception
    /// is thrown the specified mappings must be stored in the registry
    /// (although it may be an already existing one) on completion of the
    /// method; if this method throws an exception the registry will remain
    /// in the state before this method is called.
    ///
    /// \param class_string The textual representation of the RR class.
    /// \param class_code The integer code of the RR class.
    /// \return \c true if a new mapping is added to the repository; \c false
    /// if the same mapping is already registered.
    bool addClass(const std::string& class_string, uint16_t class_code);

    /// \brief Remove mappings between RR class code and textual representation
    /// for a given class.
    ///
    /// This method can safely be called whether or not the specified mappings
    /// exist in the registry.  If not, this method simply ignores the attempt
    /// and returns \c false.
    ///
    /// This method never throws an exception.
    ///
    /// \param class_code The integer code of the RR class.
    /// \return \c true if mappings for the specified RR type exists and is
    /// removed; \c false if no such mapping is in the registry.
    bool removeClass(uint16_t class_code);

    /// \brief Remove registered RDATA factory for the given pair of \c RRType
    /// and \c RRClass.
    ///
    /// This method can safely be called whether or not the specified factory
    /// object exist in the registry.  If not, this method simply ignores the
    /// attempt and returns \c false.
    ///
    /// This method never throws an exception.
    ///
    /// \param rrtype An \c RRType object specifying the type/class pair.
    /// \param rrclass An \c RRClass object specifying the type/class pair.
    /// \return \c true if a factory object for the specified RR type/class
    /// pair exists and is removed; \c false if no such object is in the
    /// registry.
    bool removeRdataFactory(const RRType& rrtype, const RRClass& rrclass);

    /// \brief Remove registered RDATA factory for the given pair of \c RRType
    /// and \c RRClass.
    ///
    /// This method can safely be called whether or not the specified factory
    /// object exist in the registry.  If not, this method simply ignores the
    /// attempt and returns \c false.
    ///
    /// This method never throws an exception.
    ///
    /// \param rrtype An \c RRType object specifying the type/class pair.
    /// \return \c true if a factory object for the specified RR type/class
    /// pair exists and is removed; \c false if no such object is in the
    /// registry.
    bool removeRdataFactory(const RRType& rrtype);
    //@}

    ///
    /// \name Parameter Conversion Methods
    ///
    //@{
    /// \brief Convert a textual representation of an RR type to the
    /// corresponding 16-bit integer code.
    ///
    /// This method searches the \c RRParamRegistry for the mapping from
    /// the given textual representation of RR type to the corresponding
    /// integer code. If a mapping is found, it returns true with the
    /// associated type code in \c type_code; otherwise, if the given
    /// string is in the form of "TYPEnnnn", it returns true with the
    /// corresponding number as the type code in \c type_code;
    /// otherwise, it returns false and \c type_code is untouched.
    ///
    /// It returns \c false and avoids throwing an exception in the case
    /// of an error to avoid the exception overhead in some situations.
    ///
    /// \param type_string The textual representation of the RR type.
    /// \param type_code Returns the RR type code in this argument.
    /// \return true if conversion is successful, false otherwise.
    bool textToTypeCode(const std::string& type_string,
                        uint16_t& type_code) const;

    /// \brief Convert type code into its textual representation.
    ///
    /// This method searches the \c RRParamRegistry for the mapping from the
    /// given RR type code to its textual representation.
    /// If a mapping is found, it returns (a copy of) the associated string;
    /// otherwise, this method creates a new string in the form of "TYPEnnnn"
    /// where "nnnn" is the decimal representation of the type code, and
    /// returns the new string.
    ///
    /// If resource allocation for the returned string fails,
    /// a corresponding standard exception will be thrown.
    /// This method never fails otherwise.
    ///
    /// \param type_code The integer code of the RR type.
    /// \return A textual representation of the RR type for code \c type_code.
    std::string codeToTypeText(uint16_t type_code) const;

    /// \brief Convert a textual representation of an RR class to the
    /// corresponding 16-bit integer code.
    ///
    /// This method searches the \c RRParamRegistry for the mapping from
    /// the given textual representation of RR class to the
    /// corresponding integer code.  If a mapping is found, it returns
    /// true with the associated class code in \c class_code; otherwise,
    /// if the given string is in the form of "CLASSnnnn", it returns
    /// true with the corresponding number as the class code in
    /// \c class_code; otherwise, it returns false and \c class_code is
    /// untouched.
    ///
    /// It returns \c false and avoids throwing an exception in the case
    /// of an error to avoid the exception overhead in some situations.
    ///
    /// \param class_string The textual representation of the RR class.
    /// \param class_code Returns the RR class code in this argument.
    /// \return true if conversion is successful, false otherwise.
    bool textToClassCode(const std::string& class_string,
                         uint16_t& class_code) const;

    /// \brief Convert class code into its textual representation.
    ///
    /// This method searches the \c RRParamRegistry for the mapping from the
    /// given RR class code to its textual representation.
    /// If a mapping is found, it returns (a copy of) the associated string;
    /// otherwise, this method creates a new string in the form of "CLASSnnnn"
    /// where "nnnn" is the decimal representation of the class code, and
    /// returns the new string.
    ///
    /// If resource allocation for the returned string fails,
    /// a corresponding standard exception will be thrown.
    /// This method never fails otherwise.
    ///
    /// \param class_code The integer code of the RR class.
    /// \return A textual representation of the RR class for code \c class_code.
    std::string codeToClassText(uint16_t class_code) const;
    //@}

    ///
    /// \name RDATA Factories
    ///
    /// This set of methods provide a unified interface to create an
    /// \c rdata::Rdata object in a parameterized polymorphic way,
    /// that is, these methods take a pair of \c RRType and \c RRClass
    /// objects and data specific to that pair, and create an object of
    /// the corresponding concrete derived class of \c rdata::Rdata.
    ///
    /// These methods first search the \c RRParamRegistry for a factory
    /// method (a member of a concrete derived class of
    /// \c AbstractRdataFactory) for the given RR type and class pair.
    /// If the search fails, they then search for a factory method for
    /// the given type ignoring the class, in case a RRClass independent
    /// factory method is registered.
    /// If it still fails, these methods assume the RDATA is of an "unknown"
    /// type, and creates a new object by calling a constructor of the
    /// \c rdata::generic::Generic class.
    ///
    //@{
    /// \brief Create RDATA of a given pair of RR type and class from a string.
    ///
    /// This method creates from a string an \c Rdata object of the given pair
    /// of RR type and class.
    ///
    /// \param rrtype An \c RRType object specifying the type/class pair.
    /// \param rrclass An \c RRClass object specifying the type/class pair.
    /// \param rdata_string A string of textual representation of the \c Rdata.
    /// \return An \c rdata::RdataPtr object pointing to the created \c Rdata
    /// object.
    rdata::RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
                                const std::string& rdata_string);
    /// \brief Create RDATA of a given pair of RR type and class from
    /// wire-format data.
    ///
    /// This method creates from wire-format binary data an \c Rdata object
    /// of the given pair of RR type and class.
    ///
    /// \param rrtype An \c RRType object specifying the type/class pair.
    /// \param rrclass An \c RRClass object specifying the type/class pair.
    /// \param buff A reference to an \c InputBuffer object storing the
    /// \c Rdata to parse.
    /// \param len The length in buffer of the \c Rdata.  In bytes.
    /// \return An \c rdata::RdataPtr object pointing to the created \c Rdata
    /// object.
    rdata::RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
                                isc::util::InputBuffer& buff, size_t len);
    /// \brief Create RDATA of a given pair of RR type and class, copying
    /// of another RDATA of same kind.
    ///
    /// This method creates an \c Rdata object of the given pair of
    /// RR type and class, copying the  content of the given \c Rdata,
    /// \c source.
    ///
    /// \c source must be an object of the concrete derived class of
    /// \c rdata::Rdata for the given pair of RR type and class;
    /// otherwise, an exception of class \c std::bad_cast will be thrown.
    /// In case the \c RRParamRegistry doesn't have a factory method for
    /// the given pair and it is assumed to be of an "unknown" type,
    /// \c source must reference an object of class
    /// \c rdata::generic::Generic; otherwise, an exception of class
    /// \c std::bad_cast will be thrown.
    ///
    /// \param rrtype An \c RRType object specifying the type/class pair.
    /// \param rrclass An \c RRClass object specifying the type/class pair.
    /// \param source A reference to an \c rdata::Rdata object whose content
    /// is to be copied to the created \c rdata::Rdata object.
    /// \return An \c rdata::RdataPtr object pointing to the created
    /// \c rdata::Rdata object.
    rdata::RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
                                const rdata::Rdata& source);

    /// \brief Create RDATA using MasterLexer
    ///
    /// This method is expected to be used as the underlying implementation
    /// of the same signature of \c rdata::createRdata().  One main
    /// difference is that this method is only responsible for constructing
    /// the Rdata; it doesn't update the lexer to reach the end of line or
    /// file or doesn't care about whether there's an extra (garbage) token
    /// after the textual RDATA representation.  Another difference is that
    /// this method can throw on error and never returns a null pointer.
    ///
    /// For other details and parameters, see the description of
    /// \c rdata::createRdata().
    rdata::RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
                                MasterLexer& lexer, const Name* origin,
                                MasterLoader::Options options,
                                MasterLoaderCallbacks& callbacks);
    //@}

private:
    boost::shared_ptr<RRParamRegistryImpl> impl_;
};

}
}
#endif  // RRPARAMREGISTRY_H
