/*
 * Copyright (c) 2017, Matias Fontanini
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following disclaimer
 *   in the documentation and/or other materials provided with the
 *   distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
 
#ifndef TINS_ICMPV6_H
#define TINS_ICMPV6_H

#include <algorithm>
#include <vector>
#include <tins/macros.h>
#include <tins/pdu.h>
#include <tins/ipv6_address.h>
#include <tins/pdu_option.h>
#include <tins/endianness.h>
#include <tins/small_uint.h>
#include <tins/hw_address.h>
#include <tins/small_uint.h>
#include <tins/icmp_extension.h>
#include <tins/cxxstd.h>

namespace Tins {
namespace Memory {

class InputMemoryStream;
class OutputMemoryStream;

} // memory

/**
 * \class ICMPv6
 * \brief Represents an ICMPv6 PDU.
 */
class TINS_API ICMPv6 : public PDU {
public:
    /**
     * \brief This PDU's flag.
     */
    static const PDU::PDUType pdu_flag = PDU::ICMPv6;
    
    /**
     * The types of ICMPv6 messages
     */
    enum Types {
        DEST_UNREACHABLE = 1,
        PACKET_TOOBIG = 2,
        TIME_EXCEEDED = 3,
        PARAM_PROBLEM = 4,
        ECHO_REQUEST = 128,
        ECHO_REPLY = 129,
        MGM_QUERY = 130,
        MGM_REPORT = 131,
        MGM_REDUCTION = 132,
        ROUTER_SOLICIT = 133,
        ROUTER_ADVERT = 134,
        NEIGHBOUR_SOLICIT = 135,
        NEIGHBOUR_ADVERT = 136,
        REDIRECT = 137,
        ROUTER_RENUMBER = 138,
        NI_QUERY = 139,
        NI_REPLY = 140,
        MLD2_REPORT = 143,
        DHAAD_REQUEST = 144,
        DHAAD_REPLY = 145,
        MOBILE_PREFIX_SOLICIT = 146,
        MOBILE_PREFIX_ADVERT = 147,
        CERT_PATH_SOLICIT = 148,
        CERT_PATH_ADVERT = 149,
        MULTICAST_ROUTER_ADVERT = 151,
        MULTICAST_ROUTER_SOLICIT = 152,
        MULTICAST_ROUTER_TERMINATE = 153,
        RPL_CONTROL_MSG = 155,
        EXTENDED_ECHO_REQUEST = 160,
        EXTENDED_ECHO_REPLY = 161
    };
    
    /**
     * The types of ICMPv6 options.
     */
    enum OptionTypes {
        SOURCE_ADDRESS = 1,
        TARGET_ADDRESS,
        PREFIX_INFO,
        REDIRECT_HEADER,
        MTU,
        NBMA_SHORT_LIMIT,
        ADVERT_INTERVAL,
        HOME_AGENT_INFO,
        S_ADDRESS_LIST,
        T_ADDRESS_LIST,
        CGA,
        RSA_SIGN,
        TIMESTAMP,
        NONCE,
        TRUST_ANCHOR,
        CERTIFICATE,
        IP_PREFIX,
        NEW_ROUTER_PREFIX,
        LINK_ADDRESS,
        NAACK,
        MAP = 23,
        ROUTE_INFO,
        RECURSIVE_DNS_SERV,
        RA_FLAGS_EXT,
        HANDOVER_KEY_REQ,
        HANDOVER_KEY_REPLY,
        HANDOVER_ASSIST_INFO,
        MOBILE_NODE_ID,
        DNS_SEARCH_LIST,
        PROXY_SIGNATURE,
        ADDRESS_REG,
        SIXLOWPAN_CONTEXT,
        AUTHORITATIVE_BORDER_ROUTER,
        CARD_REQUEST = 138,
        CARD_REPLY
    };
    
    /**
     * The type used to store addresses.
     */
    typedef IPv6Address ipaddress_type;
    
    /**
     * The type used to store addresses.
     */
    typedef HWAddress<6> hwaddress_type;
    
    /**
     * The type used to represent ICMPv6 options.
     */
    typedef PDUOption<uint8_t, ICMPv6> option;
    
    /**
     * The type used to store options.
     */
    typedef std::vector<option> options_type;
    
    /**
     * \brief The type used to store the new home agent information 
     * option data.
     */
    typedef std::vector<uint16_t> new_ha_info_type;
    
    /**
     * The type used to store the source/target address list options.
     */
    struct addr_list_type {
        typedef std::vector<ipaddress_type> addresses_type;
        
        uint8_t reserved[6];
        addresses_type addresses;
        
        addr_list_type(const addresses_type& addresses = addresses_type())
        : addresses(addresses) {
            std::fill(reserved, reserved + sizeof(reserved), static_cast<uint8_t>(0));
        }
        
        static addr_list_type from_option(const option& opt);
    };
    
    /**
     * The type used to store the nonce option data.
     */
    typedef std::vector<uint8_t> nonce_type;
    
    /**
     * The type used to store the MTU option.
     */
    typedef std::pair<uint16_t, uint32_t> mtu_type;
    
    /**
     * \brief The type used to store the neighbour advertisement 
     * acknowledgement option data.
     */
    struct naack_type {
        uint8_t code, status;
        uint8_t reserved[4];
        
        naack_type(uint8_t code = 0, uint8_t status = 0)
        : code(code), status(status) {
            std::fill(reserved, reserved + 4, static_cast<uint8_t>(0));
        }
        
        static naack_type from_option(const option& opt);
    };
    
    /**
     * \brief The type used to store the link layer address option data.
     */
    struct lladdr_type {
        typedef std::vector<uint8_t> address_type;
        
        uint8_t option_code;
        address_type address;
        
        /**
         * Constructor taking an option code and an address.
         * 
         * \param option_code The option code.
         * \param address The address to be stored.
         */
        lladdr_type(uint8_t option_code = 0, 
                    const address_type& address = address_type())
        : option_code(option_code), address(address) {
            
        }
        
        /**
         * \brief Constructor taking an option code and hwaddress_type.
         * 
         * This is a helper constructor, since it'll be common to use
         * hwaddress_type as the link layer address.
         * 
         * \param option_code The option code.
         * \param address The address to be stored.
         */
        lladdr_type(uint8_t option_code, const hwaddress_type& address)
        : option_code(option_code), address(address.begin(), address.end()) {
            
        }
        
        static lladdr_type from_option(const option& opt);
    };
    
    /**
     * Type type used to store the prefix information option data.
     */
    struct prefix_info_type {
        uint8_t prefix_len;
        small_uint<1> A, L;
        uint32_t valid_lifetime,
                 preferred_lifetime,
                 reserved2;
        ipaddress_type prefix;
        
        prefix_info_type(uint8_t prefix_len = 0, 
                         small_uint<1> A = 0,
                         small_uint<1> L = 0,
                         uint32_t valid_lifetime = 0, 
                         uint32_t preferred_lifetime = 0,
                         const ipaddress_type& prefix = ipaddress_type())
        : prefix_len(prefix_len), A(A), L(L), valid_lifetime(valid_lifetime),
          preferred_lifetime(preferred_lifetime), reserved2(0), prefix(prefix) { }
          
        static prefix_info_type from_option(const option& opt);
    };
    
    /**
     * The type used to store the RSA signature option.
     */
    struct rsa_sign_type {
        typedef std::vector<uint8_t> signature_type;
        
        uint8_t key_hash[16];
        signature_type signature;
        
        /**
         * \brief Constructs a rsa_sign_type object.
         * 
         * The first parameter must be a random access iterator
         * which will be used to initialize the key_hash member. 
         * It is assumed that std::distance(hash, end_of_hash) >= 16.
         * 
         * The second and third arguments indicate the start and end of 
         * the sequence which will be used to initialize the signature
         * member.
         * 
         * \param hash A random access iterator used to initialize the
         * key_hash member.
         * \param start A forward iterator pointing to the start of the 
         * sequence which will be used to initialize the signature member.
         * \param end A forward iterator pointing to the end of the 
         * sequence used to initialize signature.
         */
        template <typename RAIterator, typename ForwardIterator>
        rsa_sign_type(RAIterator hash, ForwardIterator start, ForwardIterator end)
        : signature(start, end) {
            std::copy(hash, hash + sizeof(key_hash), key_hash);
        }
        
        /**
         * \brief Constructs a rsa_sign_type object.
         * 
         * The first parameter must be a random access iterator
         * which will be used to initialize the key_hash member. 
         * It is assumed that std::distance(hash, end_of_hash) >= 16.
         * 
         * 
         * \param hash A random access iterator used to initialize the
         * key_hash member.
         * \param sign The signature to be set.
         */
        template <typename RAIterator>
        rsa_sign_type(RAIterator hash, const signature_type& sign)
        : signature(sign) {
            std::copy(hash, hash + sizeof(key_hash), key_hash);
        }
        
        /**
         * \brief Default constructs a rsa_sign_type.
         * 
         * The key_hash member will be 0-initialized.
         */
        rsa_sign_type() {
            std::fill(key_hash, key_hash + sizeof(key_hash), static_cast<uint8_t>(0));
        }

        static rsa_sign_type from_option(const option& opt);
    };
    
    /**
     * The type used to store IP address/preffix option.
     */
    struct ip_prefix_type {
        uint8_t option_code, prefix_len;
        ipaddress_type address;
        
        ip_prefix_type(uint8_t option_code = 0,
                       uint8_t prefix_len = 0,
                       const ipaddress_type& address = ipaddress_type())
        : option_code(option_code), prefix_len(prefix_len), address(address)
        {}

        static ip_prefix_type from_option(const option& opt);
    };
    
    /**
     * The type used to store the map option.
     */
    struct map_type {
        small_uint<4> dist, pref;
        small_uint<1> r;
        uint32_t valid_lifetime;
        ipaddress_type address;
        
        map_type(small_uint<4> dist = 0,
                 small_uint<4> pref = 0, 
                 small_uint<1> r = 0, 
                 uint32_t valid_lifetime = 0, 
                 const ipaddress_type& address = ipaddress_type())
        : dist(dist), pref(pref), r(r), valid_lifetime(valid_lifetime),
          address(address) { }

        static map_type from_option(const option& opt);
    };
    
    /**
     * The type used to store the route information option.
     */
    struct route_info_type {
        typedef std::vector<uint8_t> prefix_type;
        
        uint8_t prefix_len;
        small_uint<2> pref;
        uint32_t route_lifetime;
        prefix_type prefix;
        
        route_info_type(uint8_t prefix_len = 0, 
                        small_uint<2> pref = 0, 
                        uint32_t route_lifetime = 0,
                        const prefix_type& prefix = prefix_type())
        : prefix_len(prefix_len), pref(pref), route_lifetime(route_lifetime),
          prefix(prefix) { }

        static route_info_type from_option(const option& opt);
    };
    
    /**
     * The type used to store the recursive DNS servers option.
     */
    struct recursive_dns_type {
        typedef std::vector<ipaddress_type> servers_type;
        
        uint32_t lifetime;
        servers_type servers;
        
        recursive_dns_type(uint32_t lifetime = 0, 
                           const servers_type& servers = servers_type())
        : lifetime(lifetime), servers(servers) {}

        static recursive_dns_type from_option(const option& opt);
    };
    
    /**
     * The type used to store the handover key request option.
     */
    struct handover_key_req_type {
        typedef std::vector<uint8_t> key_type;
        
        small_uint<4> AT;
        key_type key;
        
        handover_key_req_type(small_uint<4> AT = 0,
                              const key_type& key = key_type())
        : AT(AT), key(key) { }

        static handover_key_req_type from_option(const option& opt);
    };
    
    /**
     * The type used to store the handover key reply option.
     */
    struct handover_key_reply_type : handover_key_req_type {
        uint16_t lifetime;
        
        handover_key_reply_type(uint16_t lifetime = 0, 
                                small_uint<4> AT = 0,
                                const key_type& key = key_type())
        : handover_key_req_type(AT, key), lifetime(lifetime) { }

        static handover_key_reply_type from_option(const option& opt);
    };
    
    /**
     * The type used to store the handover assist information option.
     */
    struct handover_assist_info_type {
        typedef std::vector<uint8_t> hai_type;
        
        uint8_t option_code;
        hai_type hai;
        
        handover_assist_info_type(uint8_t option_code=0, 
                                  const hai_type& hai = hai_type())
        : option_code(option_code), hai(hai) { }

        static handover_assist_info_type from_option(const option& opt);
    };
    
    /**
     * The type used to store the mobile node identifier option.
     */
    struct mobile_node_id_type {
        typedef std::vector<uint8_t> mn_type;
        
        uint8_t option_code;
        mn_type mn;
        
        mobile_node_id_type(uint8_t option_code=0, 
                            const mn_type& mn = mn_type())
        : option_code(option_code), mn(mn) { }

        static mobile_node_id_type from_option(const option& opt);
    };
    
    /**
     * The type used to store the DNS search list option.
     */
    struct dns_search_list_type {
        typedef std::vector<std::string> domains_type;
        
        uint32_t lifetime;
        domains_type domains;
        
        dns_search_list_type(uint32_t lifetime = 0,
                             const domains_type& domains = domains_type())
        : lifetime(lifetime), domains(domains) { }

        static dns_search_list_type from_option(const option& opt);
    };

    /**
     * The type used to store the timestamp option.
     */
    struct timestamp_type {
        uint8_t reserved[6];
        uint64_t timestamp;

        timestamp_type(uint64_t timestamp = 0)
        : timestamp(timestamp) {
            std::fill(reserved, reserved + sizeof(reserved), static_cast<uint8_t>(0));
        }

        static timestamp_type from_option(const option& opt);
    };

    /**
     * The type used to store the shortcut limit option.
     */
    struct shortcut_limit_type {
        uint8_t limit, reserved1;
        uint32_t reserved2;

        shortcut_limit_type(uint8_t limit = 0)
        : limit(limit), reserved1(), reserved2() {

        }

        static shortcut_limit_type from_option(const option& opt);
    };

    /**
     * The type used to store new advertisement interval option.
     */
    struct new_advert_interval_type {
        uint16_t reserved;
        uint32_t interval;

        new_advert_interval_type(uint32_t interval = 0)
        : reserved(), interval(interval) {

        }

        static new_advert_interval_type from_option(const option& opt);
    };

    /**
     * The type used to represent a multicast address record
     */
    struct multicast_address_record {
        typedef std::vector<ipaddress_type> sources_type;
        typedef std::vector<uint8_t> aux_data_type;

        multicast_address_record(uint8_t type = 0) : type(type) { }

        multicast_address_record(const uint8_t* buffer, uint32_t total_sz);
        void serialize(uint8_t* buffer, uint32_t total_sz) const;
        uint32_t size() const;

        uint8_t type;
        ipaddress_type multicast_address;
        sources_type sources;
        aux_data_type aux_data;
    };

    /*
     * The type used to store all multicast address records in a packet
     */
    typedef std::vector<multicast_address_record> multicast_address_records_list;

    /*
     * The type used to store all source address (from Multicast 
     * Listener Query messages) in a packet 
     */
    typedef std::vector<ipaddress_type> sources_list;

    /**
     * \brief Constructs an ICMPv6 object.
     * 
     * The type of the constructed object will be an echo request, unless
     * you provide another one in the tp parameter.
     * 
     * \param tp The message type of this ICMPv6 object.
     */
    ICMPv6(Types tp = ECHO_REQUEST);
    
    /**
     * \brief Constructs an ICMPv6 object from a buffer.
     * 
     * If there is not enough size for an ICMPv6 header, a
     * malformed_packet exception is thrown.
     * 
     * Any extra data is stored in a RawPDU.
     * 
     * \param buffer The buffer from which this PDU will be constructed.
     * \param total_sz The total size of the buffer.
     */
    ICMPv6(const uint8_t* buffer, uint32_t total_sz);
    
    // Getters

    /**
     *  \brief Getter for the type field.
     *  \return The stored type field value.
     */
    Types type() const {
        return static_cast<Types>(header_.type);
    }

    /**
     *  \brief Getter for the code field.
     *  \return The stored code field value.
     */
    uint8_t code() const {
        return header_.code;
    }

    /**
     *  \brief Getter for the cksum field.
     *  \return The stored cksum field value.
     */
    uint16_t checksum() const {
        return Endian::be_to_host(header_.cksum);
    }

    /**
     *  \brief Getter for the identifier field.
     *  \return The stored identifier field value.
     */
    uint16_t identifier() const {
        return Endian::be_to_host(header_.u_echo.identifier);
    }

    /**
     *  \brief Getter for the sequence field.
     *  \return The stored sequence field value.
     */
    uint16_t sequence() const {
        return Endian::be_to_host(header_.u_echo.sequence);
    }

    /**
     *  \brief Getter for the override field.
     *  \return The stored override field value.
     */
    small_uint<1> override() const {
        return header_.u_nd_advt.override;
    }

    /**
     *  \brief Getter for the solicited field.
     *  \return The stored solicited field value.
     */
    small_uint<1> solicited() const {
        return header_.u_nd_advt.solicited;
    }

    /**
     *  \brief Getter for the router field.
     *  \return The stored router field value.
     */
    small_uint<1> router() const {
        return header_.u_nd_advt.router;
    }

    /**
     *  \brief Getter for the hop limit field.
     *  \return The stored hop limit field value.
     */
    uint8_t hop_limit() const {
        return header_.u_nd_ra.hop_limit;
    }

    /**
     * \brief Getter for the maximum response code field.
     * \return The stored maximum response code field value.
     */
    uint16_t maximum_response_code() const {
        return Endian::be_to_host(header_.u_echo.identifier);
    }

    /**
     *  \brief Getter for the router_pref field.
     *  \return The stored router_pref field value.
     */
    small_uint<2> router_pref() const {
        return header_.u_nd_ra.router_pref;
    }

    /**
     *  \brief Getter for the home_agent field.
     *  \return The stored home_agent field value.
     */
    small_uint<1> home_agent() const {
        return header_.u_nd_ra.home_agent;
    }

    /**
     *  \brief Getter for the other field.
     *  \return The stored other field value.
     */
    small_uint<1> other() const {
        return header_.u_nd_ra.other;
    }

    /**
     *  \brief Getter for the managed field.
     *  \return The stored managed field value.
     */
    small_uint<1> managed() const {
        return header_.u_nd_ra.managed;
    }

    /**
     *  \brief Getter for the router_lifetime field.
     *  \return The stored router_lifetime field value.
     */
    uint16_t router_lifetime() const {
        return Endian::be_to_host(header_.u_nd_ra.router_lifetime);
    }
    
    /**
     *  \brief Getter for the reachable_time field.
     *  \return The stored reachable_time field value.
     */
    uint32_t reachable_time() const {
        return Endian::be_to_host(reach_time_);
    }
    
    /**
     *  \brief Getter for the retransmit_timer field.
     *  \return The stored retransmit_timer field value.
     */
    uint32_t retransmit_timer() const {
        return Endian::be_to_host(retrans_timer_);
    }
    
    /**
     *  \brief Getter for the target address field.
     *  \return The stored target address field value.
     */
    const ipaddress_type& target_addr() const {
        return target_address_;
    }
    
    /**
     *  \brief Getter for the destination address field.
     *  \return The stored destination address field value.
     */
    const ipaddress_type& dest_addr() const {
        return dest_address_;
    }

    /**
     * \brief Getter for the multicast address field.
     *
     * Note that this field is only valid for Multicast Listener Query
     * Message packets
     * \return The stored multicast address field value.
     */
    const ipaddress_type& multicast_addr() const {
        return multicast_address_;
    }

    /**
     *  \brief Getter for the ICMPv6 options.
     *  \return The stored options.
     */
    const options_type& options() const {
        return options_;
    }

    /**
     * \brief Getter for the length field.
     *
     * \return Returns the length field value.
     */
    uint8_t length() const { 
        return header_.rfc4884.length;
    }

    /**
     * \brief Getter for the multicast address records field
     */
    const multicast_address_records_list& multicast_address_records() const {
        return multicast_records_;
    }

    /**
     * \brief Getter for the multicast address records field.
     *
     * Note that this field is only valid for Multicast Listener Query Message
     * packets
     */
    const sources_list& sources() const {
        return sources_;
    }

    /**
     * \brief Getter for the Suppress Router-Side Processing field.
     *
     * Note that this field is only valid for Multicast Listener Query Message
     * packets
     */
    small_uint<1> supress() const {
        return mlqm_.supress;
    }

    /**
     * \brief Getter for the Querier's Robustnes Variable field.
     *
     * Note that this field is only valid for Multicast Listener Query Message
     * packets
     */
    small_uint<3> qrv() const {
        return mlqm_.qrv;
    }

    /**
     * \brief Getter for the Querier's Query Interval Code field.
     *
     * Note that this field is only valid for Multicast Listener Query Message
     * packets
     */
    uint8_t qqic() const {
        return mlqm_.qqic;
    }

    // Setters

    /**
     *  \brief Setter for the type field.
     *  \param new_type The new type field value.
     */
    void type(Types new_type);

    /**
     *  \brief Setter for the code field.
     *  \param new_code The new code field value.
     */
    void code(uint8_t new_code);

    /**
     *  \brief Setter for the cksum field.
     *  \param new_cksum The new cksum field value.
     */
    void checksum(uint16_t new_cksum);

    /**
     *  \brief Setter for the identifier field.
     *  \param new_identifier The new identifier field value.
     */
    void identifier(uint16_t new_identifier);

    /**
     *  \brief Setter for the sequence field.
     *  \param new_sequence The new sequence field value.
     */
    void sequence(uint16_t new_sequence);

    /**
     *  \brief Setter for the override field.
     *  \param new_override The new override field value.
     */
    void override(small_uint<1> new_override);

    /**
     *  \brief Setter for the solicited field.
     *  \param new_solicited The new solicited field value.
     */
    void solicited(small_uint<1> new_solicited);

    /**
     *  \brief Setter for the router field.
     *  \param new_router The new router field value.
     */
    void router(small_uint<1> new_router);

    /**
     *  \brief Setter for the hop_limit field.
     *  \param new_hop_limit The new hop_limit field value.
     */
    void hop_limit(uint8_t new_hop_limit);

    /**
     *  \brief Setter for the maximum response code field.
     *  \param new_hop_limit The new maximum response code field value.
     */
    void maximum_response_code(uint16_t maximum_response_code);

    /**
     *  \brief Setter for the router_pref field.
     *  \param new_router_pref The new router_pref field value.
     */
    void router_pref(small_uint<2> new_router_pref);

    /**
     *  \brief Setter for the home_agent field.
     *  \param new_home_agent The new home_agent field value.
     */
    void home_agent(small_uint<1> new_home_agent);

    /**
     *  \brief Setter for the other field.
     *  \param new_other The new other field value.
     */
    void other(small_uint<1> new_other);

    /**
     *  \brief Setter for the managed field.
     *  \param new_managed The new managed field value.
     */
    void managed(small_uint<1> new_managed);

    /**
     *  \brief Setter for the router_lifetime field.
     *  \param new_router_lifetime The new router_lifetime field value.
     */
    void router_lifetime(uint16_t new_router_lifetime);
    
    /**
     *  \brief Setter for the target address field.
     *  \param new_target_addr The new target address field value.
     */
    void target_addr(const ipaddress_type& new_target_addr);
    
    /**
     *  \brief Setter for the destination address field.
     *  \param new_dest_addr The new destination address field value.
     */
    void dest_addr(const ipaddress_type& new_dest_addr);

    /**
     * \brief Setter for the multicast address field.
     *
     * Note that this field is only valid if the type is MGM_QUERY
     *
     * \param new_multicast_addr The new multicast address field value.
     */
    void multicast_addr(const ipaddress_type& new_multicast_addr);

    /**
     *  \brief Setter for the reachable_time field.
     *  \param new_reachable_time The new reachable_time field value.
     */
    void reachable_time(uint32_t new_reachable_time);
    
    /**
     *  \brief Setter for the retransmit_timer field.
     *  \param new_retrans_timer The new retrans_timer field value.
     */
    void retransmit_timer(uint32_t new_retrans_timer);

    /**
     *  \brief Setter for the multicast address records field.
     *
     * This field is only valid if the type of this PDU is MLD2_REPORT
     */
    void multicast_address_records(const multicast_address_records_list& records);

    /**
     * \brief Setter for the sources field.
     *
     * This field is only valid if the type of this PDU is MGM_QUERY
     */
    void sources(const sources_list& new_sources);

    /**
     * \brief Setter for the supress field.
     *
     * This field is only valid if the type of this PDU is MGM_QUERY
     */
    void supress(small_uint<1> value);

    /**
     * \brief Setter for the Querier's Robustness Variable field.
     *
     * This field is only valid if the type of this PDU is MGM_QUERY
     */
    void qrv(small_uint<3> value);

    /**
     * \brief Setter for the Querier's Query Interval Code field.
     *
     * This field is only valid if the type of this PDU is MGM_QUERY
     */
    void qqic(uint8_t value);

    /**
     * \brief Getter for the PDU's type.
     *
     * \sa PDU::pdu_type
     */
    PDUType pdu_type() const { return pdu_flag; }

    /**
     * \brief Checks whether this ICMPv6 object has a target_addr field.
     * 
     * This depends on the type field.
     */
    bool has_target_addr() const {
        return type() == NEIGHBOUR_SOLICIT || 
               type() == NEIGHBOUR_ADVERT || 
               type() == REDIRECT;
    }
    
    /**
     * \brief Checks whether this ICMPv6 object has a target_addr field.
     * 
     * This depends on the type field.
     */
    bool has_dest_addr() const {
        return type() == REDIRECT;
    }
    
    /**
     * \brief Adds an ICMPv6 option.
     * 
     * The option is added after the last option in the option 
     * fields.
     * 
     * \param option The option to be added
     */
    void add_option(const option& option);
    
    #if TINS_IS_CXX11
        /**
         * \brief Adds an ICMPv6 option.
         * 
         * The option is move-constructed.
         * 
         * \param option The option to be added.
         */
        void add_option(option &&option) {
            internal_add_option(option);
            options_.push_back(std::move(option));
        }
    #endif

    /**
     * \brief Removes an ICMPv6 option.
     * 
     * If there are multiple options of the given type, only the first one
     * will be removed.
     *
     * \param type The type of the option to be removed.
     * \return true if the option was removed, false otherwise.
     */
    bool remove_option(OptionTypes type);

    /**
     * \brief Returns the header size.
     *
     * This method overrides PDU::header_size. This size includes the
     * payload and options size. \sa PDU::header_size
     */
    uint32_t header_size() const;

    /**
     * \brief Returns the trailer size.
     *
     * This method overrides PDU::trailer_size. This size will hold the extensions size
     *
     * \sa PDU::header_size
     */
    uint32_t trailer_size() const;
    
    /** 
     * \brief Getter for the extensions field.
     *
     * \return The extensions field
     */
    const ICMPExtensionsStructure& extensions() const {
        return extensions_;
    }

    /** 
     * \brief Getter for the extensions field.
     *
     * \return The extensions field
     */
    ICMPExtensionsStructure& extensions() {
        return extensions_;
    }

    /**
     * \brief Indicates whether this object contains ICMP extensions
     */
    bool has_extensions() const {
        return !extensions_.extensions().empty();
    }

    /**
     * \brief Sets whether the length field will be set for packets that use it
     *
     * As defined in RFC 4884, some ICMP packet types can have a length field. This
     * method controlers whether the length field is set or not.
     *
     * Note that this only indicates that the packet should use this field. The 
     * actual value will be set during the packet's serialization.
     *
     * Note that, in order to br RFC compliant, if the size of the encapsulated
     * PDU is greater than 128, the length field will always be set, regardless
     * of whether this method was called or not.
     *
     * /param value true iff the length field should be set appropriately
     */
    void use_length_field(bool value);

    /** 
     * \brief Check whether ptr points to a valid response for this PDU.
     *
     * \sa PDU::matches_response
     * \param ptr The pointer to the buffer.
     * \param total_sz The size of the buffer.
     */
    bool matches_response(const uint8_t* ptr, uint32_t total_sz) const;

    /**
     * \brief Searchs for an option that matchs the given flag.
     * 
     * If the header is not found, a null pointer is returned. 
     * Deleting the returned pointer will result in <b>undefined 
     * behaviour</b>.
     * 
     * \param type The option identifier to be searched.
     */
    const option* search_option(OptionTypes type) const;

    /**
     * \sa PDU::clone
     */
    ICMPv6* clone() const {
        return new ICMPv6(*this);
    }

    /** 
     * \brief Indicates whether to use MLDv2
     *
     * If this is set to true, then MLDv2 will be used rather than MLDv1 when
     * serializing Multicast Listener Discovery messages. By default,
     * MLDv2 will be used.
     *
     * \param value The value to set
     */
    void use_mldv2(bool value);
    
    // ****************************************************************
    //                          Option setters
    // ****************************************************************
    
    /**
     * \brief Setter for the source link layer address option.
     * 
     * \param addr The source link layer address.
     */
    void source_link_layer_addr(const hwaddress_type& addr);
    
    /**
     * \brief Setter for the target link layer address option.
     * 
     * \param addr The target link layer address.
     */
    void target_link_layer_addr(const hwaddress_type& addr);
    
    /**
     * \brief Setter for the prefix information option.
     * 
     * \param info The prefix information.
     */
    void prefix_info(prefix_info_type info);
    
    /**
     * \brief Setter for the redirect header option.
     * 
     * \param data The redirect header option data.
     */
    void redirect_header(const byte_array& data);
    
    /**
     * \brief Setter for the MTU option.
     * 
     * \param value The MTU option data.
     */
    void mtu(const mtu_type& value);
    
    /**
     * \brief Setter for the shortcut limit option.
     * 
     * \param value The shortcut limit option data.
     */
    void shortcut_limit(const shortcut_limit_type& value);
    
    /**
     * \brief Setter for the new advertisement interval option.
     * 
     * \param value The new advertisement interval option data.
     */
    void new_advert_interval(const new_advert_interval_type& value);
    
    /**
     * \brief Setter for the new home agent information option.
     * 
     * \param value The new home agent information option data.
     */
    void new_home_agent_info(const new_ha_info_type& value);
    
    /**
     * \brief Setter for the new source address list option.
     * 
     * \param value The new source address list option data.
     */
    void source_addr_list(const addr_list_type& value);
    
    /**
     * \brief Setter for the new target address list option.
     * 
     * \param value The new target address list option data.
     */
    void target_addr_list(const addr_list_type& value);
    
    /**
     * \brief Setter for the new RSA signature option.
     * 
     * \param value The new RSA signature option data.
     */
    void rsa_signature(const rsa_sign_type& value);
    
    /**
     * \brief Setter for the new timestamp option.
     * 
     * \param value The new timestamp option data.
     */
    void timestamp(const timestamp_type& value);
    
    /**
     * \brief Setter for the new nonce option.
     * 
     * \param value The new nonce option data.
     */
    void nonce(const nonce_type& value);
    
    /**
     * \brief Setter for the new IP address/prefix option.
     * 
     * \param value The new IP address/prefix option data.
     */
    void ip_prefix(const ip_prefix_type& value);

    /**
     * \brief Setter for the new link layer address option.
     * 
     * \param value The new link layer address option data.
     */
    void link_layer_addr(lladdr_type value);

    /**
     * \brief Setter for the neighbour advertisement acknowledgement option.
     * 
     * \param value The new naack option data.
     */
    void naack(const naack_type& value);
    
    /**
     * \brief Setter for the map option.
     * 
     * \param value The new map option data.
     */
    void map(const map_type& value);
    
    /**
     * \brief Setter for the route information option.
     * 
     * \param value The new route information option data.
     */
    void route_info(const route_info_type& value);
    
    /**
     * \brief Setter for the recursive DNS servers option.
     * 
     * \param value The new recursive DNS servers option data.
     */
    void recursive_dns_servers(const recursive_dns_type& value);
    
    /**
     * \brief Setter for the handover key request option.
     * 
     * \param value The new handover key request option data.
     */
    void handover_key_request(const handover_key_req_type& value);
    
    /**
     * \brief Setter for the handover key reply option.
     * 
     * \param value The new handover key reply option data.
     */
    void handover_key_reply(const handover_key_reply_type& value);
    
    /**
     * \brief Setter for the handover assist info option.
     * 
     * \param value The new handover assist info option data.
     */
    void handover_assist_info(const handover_assist_info_type& value);
    
    /**
     * \brief Setter for the mobile node identifier option.
     * 
     * \param value The new mobile node identifier option data.
     */
    void mobile_node_identifier(const mobile_node_id_type& value);
    
    /**
     * \brief Setter for the DNS search list option.
     * 
     * \param value The new DNS search list option data.
     */
    void dns_search_list(const dns_search_list_type& value);
    
    // ****************************************************************
    //                          Option getters
    // ****************************************************************
    
    /**
     * \brief Getter for the source link layer address option.
     * 
     * This method will throw an option_not_found exception if the
     * option is not found.
     */
    hwaddress_type source_link_layer_addr() const;
    
    /**
     * \brief Getter for the target link layer address option.
     * 
     * This method will throw an option_not_found exception if the
     * option is not found.
     */
    hwaddress_type target_link_layer_addr() const;
        
    /**
     * \brief Getter for the prefix information option.
     * 
     * This method will throw an option_not_found exception if the
     * option is not found.
     */
    prefix_info_type prefix_info() const;
    
    /**
     * \brief Getter for the redirect header option.
     * 
     * This method will throw an option_not_found exception if the
     * option is not found.
     */
    byte_array redirect_header() const;
    
    /**
     * \brief Getter for the MTU option.
     * 
     * This method will throw an option_not_found exception if the
     * option is not found.
     */
    mtu_type mtu() const;
    
    /**
     * \brief Getter for the shortcut limit option.
     * 
     * This method will throw an option_not_found exception if the
     * option is not found.
     */
    shortcut_limit_type shortcut_limit() const;
    
    /**
     * \brief Getter for the new advertisement interval option.
     * 
     * This method will throw an option_not_found exception if the
     * option is not found.
     */
    new_advert_interval_type new_advert_interval() const;
    
    /**
     * \brief Getter for the new home agent information option.
     * 
     * This method will throw an option_not_found exception if the
     * option is not found.
     */
    new_ha_info_type new_home_agent_info() const;
    
    /**
     * \brief Getter for the source address list option.
     * 
     * This method will throw an option_not_found exception if the
     * option is not found.
     */
    addr_list_type source_addr_list() const;
    
    /**
     * \brief Getter for the target address list option.
     * 
     * This method will throw an option_not_found exception if the
     * option is not found.
     */
    addr_list_type target_addr_list() const;
    
    /**
     * \brief Getter for the RSA signature option.
     * 
     * This method will throw an option_not_found exception if the
     * option is not found.
     */
    rsa_sign_type rsa_signature() const;
    
    /**
     * \brief Getter for the timestamp option.
     * 
     * This method will throw an option_not_found exception if the
     * option is not found.
     */
    timestamp_type timestamp() const;
    
    /**
     * \brief Getter for the nonce option.
     * 
     * This method will throw an option_not_found exception if the
     * option is not found.
     */
    nonce_type nonce() const;
    
    /**
     * \brief Getter for the IP address/prefix option.
     * 
     * This method will throw an option_not_found exception if the
     * option is not found.
     */
    ip_prefix_type ip_prefix() const;
    
    /**
     * \brief Getter for the link layer address option.
     * 
     * This method will throw an option_not_found exception if the
     * option is not found.
     */
    lladdr_type link_layer_addr() const;
    
    /**
     * \brief Getter for the neighbour advertisement acknowledgement
     * option.
     * 
     * This method will throw an option_not_found exception if the
     * option is not found.
     */
    naack_type naack() const;
    
    /**
     * \brief Getter for the map option.
     * 
     * This method will throw an option_not_found exception if the
     * option is not found.
     */
    map_type map() const;
    
    /**
     * \brief Getter for the route information option.
     * 
     * This method will throw an option_not_found exception if the
     * option is not found.
     */
    route_info_type route_info() const;
    
    /**
     * \brief Getter for the recursive dns servers option.
     * 
     * This method will throw an option_not_found exception if the
     * option is not found.
     */
    recursive_dns_type recursive_dns_servers() const;
    
    /**
     * \brief Getter for the handover key request option.
     * 
     * This method will throw an option_not_found exception if the
     * option is not found.
     */
    handover_key_req_type handover_key_request() const;
    
    /**
     * \brief Getter for the handover key reply option.
     * 
     * This method will throw an option_not_found exception if the
     * option is not found.
     */
    handover_key_reply_type handover_key_reply() const;
    
    /**
     * \brief Getter for the handover key reply option.
     * 
     * This method will throw an option_not_found exception if the
     * option is not found.
     */
    handover_assist_info_type handover_assist_info() const;
    
    /**
     * \brief Getter for the mobile node identifier option.
     * 
     * This method will throw an option_not_found exception if the
     * option is not found.
     */
    mobile_node_id_type mobile_node_identifier() const;
    
    /**
     * \brief Getter for the mobile node identifier option.
     * 
     * This method will throw an option_not_found exception if the
     * option is not found.
     */
    dns_search_list_type dns_search_list() const;
private:
    TINS_BEGIN_PACK
    struct icmp6_header {
        uint8_t	type;
        uint8_t code;
        uint16_t cksum;
        union {
            struct {
                uint16_t identifier;
                uint16_t sequence;
            } u_echo;
            
            struct {
        #if TINS_IS_LITTLE_ENDIAN
            uint32_t reserved:5,
                     override:1,
                     solicited:1,
                     router:1,
                     reserved2:24;
        #else
            uint32_t router:1,
                     solicited:1,
                     override:1,
                     reserved:29;
        #endif						
            } u_nd_advt;
            struct {
                uint8_t	hop_limit;
        #if TINS_IS_LITTLE_ENDIAN
                uint8_t reserved:3,
                        router_pref:2,
                        home_agent:1,
                        other:1,
                        managed:1;
        #else
                uint8_t managed:1,
                        other:1,
                        home_agent:1,
                        router_pref:2,
                        reserved:3;
        #endif
                uint16_t router_lifetime;
            } u_nd_ra;
            struct {
                uint8_t length;
                uint8_t unused[3];
            } rfc4884;
            // Multicast Listener Report Message (mld2)
            struct {
                uint16_t reserved;
                uint16_t record_count;
            } mlrm2;
        };
    } TINS_END_PACK;

    TINS_BEGIN_PACK
    struct multicast_listener_query_message_fields {
        uint8_t reserved:4,
                supress:1,
                qrv:3;
        uint8_t qqic;
    } TINS_END_PACK;
    
    void internal_add_option(const option& option);
    void write_serialization(uint8_t* buffer, uint32_t total_sz);
    bool has_options() const;
    void write_option(const option& opt, Memory::OutputMemoryStream& stream);
    void parse_options(Memory::InputMemoryStream& stream);
    void add_addr_list(uint8_t type, const addr_list_type& value);
    addr_list_type search_addr_list(OptionTypes type) const;
    options_type::const_iterator search_option_iterator(OptionTypes type) const;
    options_type::iterator search_option_iterator(OptionTypes type);
    void try_parse_extensions(Memory::InputMemoryStream& stream);
    bool are_extensions_allowed() const;
    uint32_t get_adjusted_inner_pdu_size() const;
    uint8_t get_option_padding(uint32_t data_size);

    template <template <typename> class Functor>
    const option* safe_search_option(OptionTypes opt, uint32_t size) const {
        const option* option = search_option(opt);
        if (!option || Functor<uint32_t>()(option->data_size(), size)) {
            throw option_not_found();
        }
        return option;
    }

    template <typename T>
    T search_and_convert(OptionTypes type) const {
        const option* opt = search_option(type);
        if (!opt) {
            throw option_not_found();
        }
        return opt->to<T>();
    }

    icmp6_header header_;
    ipaddress_type target_address_;
    ipaddress_type dest_address_;
    ipaddress_type multicast_address_;
    options_type options_;
    uint32_t options_size_;
    uint32_t reach_time_, retrans_timer_;
    multicast_address_records_list multicast_records_;
    multicast_listener_query_message_fields mlqm_;
    sources_list sources_;
    ICMPExtensionsStructure extensions_;
    bool use_mldv2_;
};

} // Tins

#endif // TINS_ICMPV6_H
