// Copyright (C) 2011-2021 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 PKT6_H
#define PKT6_H

#include <asiolink/io_address.h>
#include <dhcp/duid.h>
#include <dhcp/option.h>
#include <dhcp/pkt.h>

#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/shared_array.hpp>
#include <boost/shared_ptr.hpp>

#include <iostream>
#include <set>

#include <time.h>

namespace isc {

namespace dhcp {

class Pkt6;

/// @brief A pointer to Pkt6 packet
typedef boost::shared_ptr<Pkt6> Pkt6Ptr;

/// @brief Represents a DHCPv6 packet
///
/// This class represents a single DHCPv6 packet. It handles both incoming
/// and transmitted packets, parsing incoming options, options handling
/// (add, get, remove), on-wire assembly, sanity checks and other operations.
/// This specific class has several DHCPv6-specific methods, but it uses a lot
/// of common operations from its base @c Pkt class that is shared with Pkt4.
///
/// This class also handles relayed packets. For example, a RELAY-FORW message
/// with a SOLICIT inside will be represented as SOLICIT and the RELAY-FORW
/// layers will be stored in relay_info_ vector.
class Pkt6 : public Pkt {
public:
    /// specifies non-relayed DHCPv6 packet header length (over UDP)
    const static size_t DHCPV6_PKT_HDR_LEN = 4;

    /// specifies relay DHCPv6 packet header length (over UDP)
    const static size_t DHCPV6_RELAY_HDR_LEN = 34;

    /// DHCPv6 transport protocol
    enum DHCPv6Proto {
        UDP = 0, // most packets are UDP
        TCP = 1  // there are TCP DHCPv6 packets (bulk leasequery, failover)
    };

    /// @brief defines relay search pattern
    ///
    /// Defines order in which options are searched in a message that
    /// passed through multiple relays. RELAY_SEACH_FROM_CLIENT will
    /// start search from the relay that was the closest to the client
    /// (i.e. innermost in the encapsulated message, which also means
    /// this was the first relay that forwarded packet received by the
    /// server and this will be the last relay that will handle the
    /// response that server sent towards the client.).
    /// RELAY_SEARCH_FROM_SERVER is the opposite. This will be the
    /// relay closest to the server (i.e. outermost in the encapsulated
    /// message, which also means it was the last relay that relayed
    /// the received message and will be the first one to process
    /// server's response). RELAY_GET_FIRST will try to get option from
    /// the first relay only (closest to the client), RELAY_GET_LAST will
    /// try to get option form the last relay (closest to the server).
    enum RelaySearchOrder {
        RELAY_SEARCH_FROM_CLIENT = 1,
        RELAY_SEARCH_FROM_SERVER = 2,
        RELAY_GET_FIRST = 3,
        RELAY_GET_LAST = 4
    };

    /// @brief structure that describes a single relay information
    ///
    /// Client sends messages. Each relay along its way will encapsulate the message.
    /// This structure represents all information added by a single relay.
    struct RelayInfo {

        /// @brief default constructor
        RelayInfo();

        /// @brief Returns printable representation of the relay information.
        /// @return text representation of the structure (used in debug logging)
        std::string toText() const;

        uint8_t   msg_type_;               ///< message type (RELAY-FORW oro RELAY-REPL)
        uint8_t   hop_count_;              ///< number of traversed relays (up to 32)
        isc::asiolink::IOAddress linkaddr_;///< fixed field in relay-forw/relay-reply
        isc::asiolink::IOAddress peeraddr_;///< fixed field in relay-forw/relay-reply

        /// @brief length of the relay_msg_len
        /// Used when calculating length during pack/unpack
        uint16_t  relay_msg_len_;

        /// options received from a specified relay, except relay-msg option
        isc::dhcp::OptionCollection options_;
    };

    /// Constructor, used in replying to a message
    ///
    /// @param msg_type type of message (SOLICIT=1, ADVERTISE=2, ...)
    /// @param transid transaction-id
    /// @param proto protocol (TCP or UDP)
    Pkt6(uint8_t msg_type,
         uint32_t transid,
         DHCPv6Proto proto = UDP);

    /// Constructor, used in message transmission
    ///
    /// Creates new message. Transaction-id will randomized.
    ///
    /// @param buf pointer to a buffer of received packet content
    /// @param len size of buffer of received packet content
    /// @param proto protocol (usually UDP, but TCP will be supported eventually)
    Pkt6(const uint8_t* buf, uint32_t len, DHCPv6Proto proto = UDP);

    /// @brief Prepares on-wire format.
    ///
    /// Prepares on-wire format of message and all its options.
    /// Options must be stored in options_ field.
    /// Output buffer will be stored in data_. Length
    /// will be set in data_len_.
    /// The output buffer is cleared before new data is written to it.
    ///
    /// @throw BadValue if packet protocol is invalid, InvalidOperation
    /// if packing fails, or NotImplemented if protocol is TCP (IPv6 over TCP is
    /// not yet supported).
    virtual void pack();

    /// @brief Dispatch method that handles binary packet parsing.
    ///
    /// This method calls appropriate dispatch function (unpackUDP or
    /// unpackTCP).
    ///
    /// @throw tbd
    virtual void unpack();

    /// @brief Returns protocol of this packet (UDP or TCP).
    ///
    /// @return protocol type
    DHCPv6Proto getProto() {
        return (proto_);
    }

    /// @brief Sets protocol of this packet.
    ///
    /// @param proto protocol (UDP or TCP)
    void setProto(DHCPv6Proto proto = UDP) {
        proto_ = proto;
    }

    /// @brief Returns text representation of the given packet identifiers.
    ///
    /// @note The parameters are ordered from the one that should be available
    /// almost at all times, to the one that is optional. This allows for
    /// providing default values for the parameters that may not be available
    /// in some places in the code where @c Pkt6::makeLabel is called.
    ///
    /// @param duid Pointer to the client identifier or NULL.
    /// @param transid Numeric transaction id to include in the string.
    /// @param hwaddr Hardware address to include in the string or NULL.
    ///
    /// @return String with text representation of the packet identifiers.
    static std::string makeLabel(const DuidPtr duid, const uint32_t transid,
                                 const HWAddrPtr& hwaddr);

    /// @brief Returns text representation of the given packet identifiers.
    ///
    /// This variant of the method does not include transaction id.
    ///
    /// @param duid Pointer to the client identifier or NULL.
    /// @param hwaddr Hardware address to include in the string or NULL.
    ///
    /// @return String with text representation of the packet identifiers.
    static std::string makeLabel(const DuidPtr duid, const HWAddrPtr& hwaddr);

    /// @brief Returns text representation of the primary packet identifiers
    ///
    /// This method is intended to be used to provide a consistent way to
    /// identify packets within log statements.  It is an instance-level
    /// wrapper around static makeLabel(). See this method for string
    /// content.
    ///
    /// @note Currently this method doesn't include the HW address in the
    /// returned text.
    ///
    /// @return string with text representation
    virtual std::string getLabel() const;

    /// @brief Returns text representation of the packet.
    ///
    /// This function is useful mainly for debugging.
    ///
    /// @return string with text representation
    virtual std::string toText() const;

    /// @brief Returns length of the packet.
    ///
    /// This function returns size required to hold this packet.
    /// It includes DHCPv6 header and all options stored in
    /// options_ field.
    ///
    /// Note: It does not return proper length of incoming packets
    /// before they are unpacked.
    ///
    /// @return number of bytes required to assemble this packet
    virtual size_t len();

    /// @brief Returns message type (e.g. 1 = SOLICIT).
    ///
    /// @return message type
    virtual uint8_t getType() const { return (msg_type_); }

    /// @brief Sets message type (e.g. 1 = SOLICIT).
    ///
    /// @param type message type to be set
    virtual void setType(uint8_t type) { msg_type_=type; };

    /// @brief Retrieves the DUID from the Client Identifier option.
    ///
    /// This method is exception safe.
    ///
    /// @return Pointer to the DUID or NULL if the option doesn't exist.
    DuidPtr getClientId() const;


protected:

    /// @brief Returns pointer to an option inserted by relay agent.
    ///
    /// This is a variant of the @ref Pkt6::getRelayOption function which
    /// never copies an option returned. This method should be only used by
    /// the @ref Pkt6 class and derived classes. Any external callers should
    /// use @ref getRelayOption which copies the option before returning it
    /// when the @ref Pkt::copy_retrieved_options_ flag is set to true.
    ///
    /// @param opt_type Code of the requested option.
    /// @param relay_level Nesting level as described for
    /// @ref Pkt6::getRelayOption.
    ///
    /// @return Pointer to the option or NULL if such option doesn't exist.
    OptionPtr getNonCopiedRelayOption(const uint16_t opt_type,
                                      const uint8_t relay_level) const;

public:

    /// @brief Returns option inserted by relay
    ///
    /// Returns an option from specified relay scope (inserted by a given relay
    /// if this is received packet or to be decapsulated by a given relay if
    /// this is a transmitted packet). nesting_level specifies which relay
    /// scope is to be used. 0 is the outermost encapsulation (relay closest to
    /// the server). pkt->relay_info_.size() - 1 is the innermost encapsulation
    /// (relay closest to the client).
    ///
    /// @throw isc::OutOfRange if nesting level has invalid value.
    ///
    /// @param option_code code of the requested option
    /// @param nesting_level see description above
    ///
    /// @return pointer to the option (or NULL if there is no such option)
    OptionPtr getRelayOption(uint16_t option_code, uint8_t nesting_level);

private:

    /// @brief Prepares parameters for loop used in @ref getAnyRelayOption
    /// and @ref getNonCopiedAnyRelayOption.
    ///
    /// The methods retrieving "any" relay option iterate over the relay
    /// info structures to find the matching option. This method returns
    /// the index of the first and last relay info structure to be used
    /// for this iteration. It also returns the direction in which the
    /// iteration should be performed.
    ///
    /// @param order Option search order (see @ref RelaySearchOrder).
    /// @param [out] start Index of the relay information structure from
    /// which the search should be started.
    /// @param [out] end Index of the relay information structure on which
    /// the option searches should stop.
    /// @param [out] direction Equals to -1 for backwards searches, and
    /// equals to 1 for forward searches.
    void prepareGetAnyRelayOption(const RelaySearchOrder& order,
                                  int& start, int& end, int& direction) const;

protected:

    /// @brief Returns pointer to an instance of specified option.
    ///
    /// This is a variant of @ref getAnyRelayOption but it never copies
    /// an option returned. This method should be only used by
    /// the @ref Pkt6 class and derived classes. Any external callers should
    /// use @ref getAnyRelayOption which copies the option before returning it
    /// when the @ref Pkt::copy_retrieved_options_ flag is set to true.
    ///
    /// @param option_code Searched option.
    /// @param order Option search order (see @ref RelaySearchOrder).
    ///
    /// @return Option pointer or NULL, if no option matches specified criteria.
    OptionPtr getNonCopiedAnyRelayOption(const uint16_t option_code,
                                         const RelaySearchOrder& order) const;

public:

    /// @brief Return first instance of a specified option
    ///
    /// When a client's packet traverses multiple relays, each passing relay may
    /// insert extra options. This method allows the specific instance of a given
    /// option to be obtained (e.g. closest to the client, closest to the server,
    /// etc.) See @ref RelaySearchOrder for a detailed description.
    ///
    /// @param option_code searched option
    /// @param order option search order (see @ref RelaySearchOrder)
    /// @return option pointer (or NULL if no option matches specified criteria)
    OptionPtr getAnyRelayOption(const uint16_t option_code,
                                const RelaySearchOrder& order);

    /// @brief return the link address field from a relay option
    ///
    /// As with @c Pkt6::getRelayOption this returns information from the
    /// specified relay scope.  The relay_level specifies which relay
    /// scope is to be used.  0 is the outermost encapsulation (relay closest
    /// to the server).  pkt->relay_info_.size() -1 is the innermost encapsulation
    /// (relay closest to the client).
    ///
    /// @throw isc::OutOfRange if relay level has an invalid value.
    ///
    /// @param relay_level see description above
    ///
    /// @return pointer to the link address field
    const isc::asiolink::IOAddress&
    getRelay6LinkAddress(uint8_t relay_level) const;

    /// @brief return the peer address field from a relay option
    ///
    /// As with @c Pkt6::getRelayOption this returns information from the
    /// specified relay scope.  The relay_level specifies which relay
    /// scope is to be used.  0 is the outermost encapsulation (relay closest
    /// to the server).  pkt->relay_info_.size() -1 is the innermost encapsulation
    /// (relay closest to the client).
    ///
    /// @throw isc::OutOfRange if relay level has an invalid value.
    ///
    /// @param relay_level see description above
    ///
    /// @return pointer to the peer address field
    const isc::asiolink::IOAddress&
    getRelay6PeerAddress(uint8_t relay_level) const;

protected:

    /// @brief Returns all option instances of specified type without
    /// copying.
    ///
    /// This is a variant of @ref getOptions method, which returns a collection
    /// of options without copying them. This method should be only used by
    /// the @ref Pkt6 class and derived classes. Any external callers should
    /// use @ref getOptions which copies option instances before returning them
    /// when the @ref Pkt::copy_retrieved_options_ flag is set to true.
    ///
    /// @param opt_type Option code.
    ///
    /// @return Collection of options found.
    OptionCollection getNonCopiedOptions(const uint16_t opt_type) const;

public:

    /// @brief Returns all instances of specified type.
    ///
    /// Returns all instances of options of the specified type. DHCPv6 protocol
    /// allows (and uses frequently) multiple instances.
    ///
    /// @param type option type we are looking for
    /// @return instance of option collection with requested options
    isc::dhcp::OptionCollection getOptions(const uint16_t type);

    /// @brief add information about one traversed relay
    ///
    /// This adds information about one traversed relay, i.e.
    /// one relay-forw or relay-repl level of encapsulation.
    ///
    /// @param relay structure with necessary relay information
    void addRelayInfo(const RelayInfo& relay);

    /// @brief Returns name of the DHCPv6 message for a given type number.
    ///
    /// As the operation of the method does not depend on any server state, it
    /// is declared static. There is also non-static getName() method that
    /// works on Pkt6 objects.
    ///
    /// @param type DHCPv6 message type which name should be returned.
    ///
    /// @return Pointer to "const" string containing the message name. If
    /// the message type is unknown the "UNKNOWN" is returned. The caller
    /// must not release the returned pointer.
    static const char* getName(const uint8_t type);

    /// @brief Returns name of the DHCPv6 message.
    ///
    /// This method requires an object. There is also a static version, which
    /// requires one parameter (type).
    ///
    /// @return Pointer to "const" string containing the message name. If
    /// the message type is unknown the "UNKNOWN" is returned. The caller
    /// must not release the returned pointer.
    const char* getName() const;

    /// @brief copies relay information from client's packet to server's response
    ///
    /// This information is not simply copied over. Some parameter are
    /// removed, msg_type_is updated (RELAY-FORW => RELAY-REPL), etc.
    ///
    /// @param question client's packet
    void copyRelayInfo(const Pkt6Ptr& question);

    /// @brief Relay information.
    ///
    /// This is a public field. Otherwise we hit one of the two problems:
    /// we return reference to an internal field (and that reference could
    /// be potentially used past Pkt6 object lifetime causing badness) or
    /// we return a copy (which is inefficient and also causes any updates
    /// to be impossible). Therefore public field is considered the best
    /// (or least bad) solution.
    ///
    /// This vector is arranged in the order packet is encapsulated, i.e.
    /// relay[0] was the outermost encapsulation (relay closest to the server),
    /// relay[last] was the innermost encapsulation (relay closest to the
    /// client).
    std::vector<RelayInfo> relay_info_;

protected:

    /// @brief Attempts to generate MAC/Hardware address from IPv6 link-local
    ///        address.
    ///
    /// This method uses source IPv6 address for direct messages and the
    /// peeraddr or the first relay that saw that packet. It may fail if the
    /// address is not link-local or does not use EUI-64 identifier.
    ///
    /// @return Hardware address (or NULL)
    virtual HWAddrPtr getMACFromSrcLinkLocalAddr();

    /// @brief Extract MAC/Hardware address from client link-layer address
    //         option inserted by a relay agent (RFC6939).
    ///
    /// This method extracts the client's hardware address from the
    //  client-linklayer-addr option inserted by the relay agent closest to
    //  the client.
    ///
    /// @return Hardware address (or NULL)
    virtual HWAddrPtr getMACFromIPv6RelayOpt();

    /// @brief Extract MAC/Hardware address from client-id.
    ///
    /// This method attempts to extract MAC/Hardware address from DUID sent
    /// as client-id. This method may fail, as only DUID-LLT and DUID-LL are
    /// based on link-layer addresses. Client may use other valid DUID types
    /// and this method will fail.
    ///
    /// @return Hardware address (or NULL)
    virtual HWAddrPtr getMACFromDUID();

    /// @brief Attempts to extract MAC/Hardware address from DOCSIS options
    ///        inserted by the modem itself.
    ///
    /// The mechanism extracts that information from DOCSIS option
    /// (vendor-specific info, vendor-id=4491, suboption 36). Note that
    /// in a DOCSIS capable network, the MAC address information is provided
    /// several times. The first is specified by the modem itself. The second
    /// is added by the CMTS, which acts as a relay agent. This method
    /// attempts to extract the former. See @ref getMACFromDocsisCMTS
    /// for a similar method that extracts from the CMTS (relay) options.
    ///
    /// @return hardware address (if DOCSIS suboption 36 is present)
    virtual HWAddrPtr getMACFromDocsisModem();

    /// @brief Attempts to extract MAC/Hardware address from DOCSIS options.
    ///
    /// The DHCPv6 mechanism extracts that information from DOCSIS option
    /// (vendor-specific info, vendor-id=4491, suboption 1026). Note that
    /// in a DOCSIS capable network, the MAC address information is provided
    /// several times. The first is specified by the modem itself. The second
    /// is added by the CMTS, which acts as a relay agent. This method
    /// attempts to extract the latter. See @ref getMACFromDocsisModem
    /// for a similar method that extracts from the modem (client) options.
    ///
    /// @return hardware address (if DOCSIS suboption 1026 is present)
    virtual HWAddrPtr getMACFromDocsisCMTS();

    /// @brief Attempts to obtain MAC address from remote-id relay option.
    ///
    /// This method is called from getMAC(HWADDR_SOURCE_REMOTE_ID) and should not be
    /// called directly. It will attempt to extract MAC address information
    /// from remote-id option inserted by a relay agent closest to the client.
    /// If this method fails, it will return NULL.
    ///
    /// @return hardware address (or NULL)
    virtual HWAddrPtr getMACFromRemoteIdRelayOption();

    /// @brief Builds on wire packet for TCP transmission.
    ///
    /// @todo This function is not implemented yet.
    ///
    /// @throw NotImplemented, IPv6 over TCP is not yet supported.
    void packTCP();

    /// @brief Builds on wire packet for UDP transmission.
    ///
    /// @throw InvalidOperation if packing fails
    void packUDP();

    /// @brief Parses on-wire form of TCP DHCPv6 packet.
    ///
    /// Parses received packet, stored in on-wire format in data_.
    /// data_len_ must be set to indicate data length.
    /// Will create a collection of option objects that will
    /// be stored in options_ container.
    ///
    /// @todo This function is not implemented yet.
    ///
    /// @throw tbd
    void unpackTCP();

    /// @brief Parses on-wire form of UDP DHCPv6 packet.
    ///
    /// Parses received packet, stored in on-wire format in data_.
    /// data_len_ must be set to indicate data length.
    /// Will create a collection of option objects that will
    /// be stored in options_ container.
    ///
    /// @throw tbd
    void unpackUDP();

    /// @brief Unpacks direct (non-relayed) message.
    ///
    /// This method unpacks specified buffer range as a direct
    /// (e.g. solicit or request) message. This method is called from
    /// unpackUDP() when received message is detected to be direct.
    ///
    /// @param begin start of the buffer
    /// @param end end of the buffer
    /// @throw tbd
    void unpackMsg(OptionBuffer::const_iterator begin,
                   OptionBuffer::const_iterator end);

    /// @brief Unpacks relayed message (RELAY-FORW or RELAY-REPL).
    ///
    /// This method is called from unpackUDP() when received message
    /// is detected to be relay-message. It goes iteratively over
    /// all relays (if there are multiple encapsulation levels).
    ///
    /// @throw tbd
    void unpackRelayMsg();

    /// @brief Calculates overhead introduced in specified relay.
    ///
    /// It is used when calculating message size and packing message
    /// @param relay RelayInfo structure that holds information about relay
    /// @return number of bytes needed to store relay information
    uint16_t getRelayOverhead(const RelayInfo& relay) const;

    /// @brief Calculates overhead for all relays defined for this message.
    /// @return number of bytes needed to store all relay information
    uint16_t calculateRelaySizes();

    /// @brief Calculates size of the message as if it was not relayed at all.
    ///
    /// This is equal to len() if the message was not relayed.
    /// @return number of bytes required to store the message
    uint16_t directLen() const;

    /// UDP (usually) or TCP (bulk leasequery or failover)
    DHCPv6Proto proto_;

    /// DHCPv6 message type
    uint8_t msg_type_;

}; // Pkt6 class

} // isc::dhcp namespace

} // isc namespace

#endif
