/*
 * 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_SNIFFER_H
#define TINS_SNIFFER_H

#include <string>
#include <memory>
#include <iterator>
#include <tins/pdu.h>
#include <tins/packet.h>
#include <tins/cxxstd.h>
#include <tins/macros.h>
#include <tins/exceptions.h>
#include <tins/detail/type_traits.h>

#ifdef TINS_HAVE_PCAP

#include <pcap.h>


namespace Tins {
class SnifferIterator;
class SnifferConfiguration;

/**
 * \class BaseSniffer
 * \brief Base class for sniffers.
 *
 * This class implements the basic sniffing operations. Subclasses
 * should only initialize this object using a pcap_t pointer, which
 * will be used to extract packets.
 *
 * Initialization must be done using the BaseSniffer::init method.
 */
class TINS_API BaseSniffer {
public:
    /**
     * The iterator type.
     */
    typedef SnifferIterator iterator;

    #if TINS_IS_CXX11
        /**
         * \brief Move constructor.
         * This constructor is available only in C++11.
         */
        BaseSniffer(BaseSniffer &&rhs) TINS_NOEXCEPT
        : handle_(0), mask_(), extract_raw_(false),
          pcap_sniffing_method_(pcap_loop) {
            *this = std::move(rhs);
        }

        /**
         * \brief Move assignment operator.
         * This operator is available only in C++11.
         */
        BaseSniffer& operator=(BaseSniffer &&rhs) TINS_NOEXCEPT {
            using std::swap;
            swap(handle_, rhs.handle_);
            swap(mask_, rhs.mask_);
            swap(extract_raw_, rhs.extract_raw_);
            swap(pcap_sniffing_method_, rhs.pcap_sniffing_method_);
            return* this;
        }
    #endif

    /**
     * \brief Sniffer destructor.
     * This frees all memory used by the pcap handle.
     */
    virtual ~BaseSniffer();

    /**
     * \brief Compiles a filter and uses it to capture one packet.
     *
     * This method returns the first valid sniffed packet that matches the
     * sniffer's filter, or the first sniffed packet if no filter has
     * been set.
     *
     * The return type is a thin wrapper over a PDU* and a Timestamp
     * object. This wrapper can be both implicitly converted to a
     * PDU* and a Packet object. So doing this:
     *
     * \code
     * Sniffer s(...);
     * std::unique_ptr<PDU> pdu(s.next_packet());
     * // Packet takes care of the PDU*.
     * Packet packet(s.next_packet());
     * \endcode
     *
     * Is fine, but this:
     *
     * \code
     * // bad!!
     * PtrPacket p = s.next_packet();
     * \endcode
     *
     * Is not, since PtrPacket can't be copy constructed.
     *
     * \sa Packet::release_pdu
     *
     * \return A captured packet. If an error occured, PtrPacket::pdu
     * will return 0. Caller takes ownership of the PDU pointer stored in
     * the PtrPacket.
     */
    PtrPacket next_packet();

    /**
     * \brief Starts a sniffing loop, using a callback functor for every
     * sniffed packet.
     *
     * The functor must implement an operator with one of the
     * following signatures:
     *
     * \code
     * bool(PDU&);
     * bool(const PDU&);
     *
     * // These two are only allowed when compiling in C++11 mode
     * bool(Packet&);
     * bool(const Packet&);
     * \endcode
     *
     * This functor will be called using the each of the sniffed packets
     * as its argument. Using PDU member functions that modify the PDU,
     * such as PDU::release_inner_pdu, is perfectly valid.
     *
     * Note that if you're using a functor object, it will be copied using
     * its copy constructor, so it should be some kind of proxy to
     * another object which will process the packets(e.g. std::bind).
     *
     * Sniffing will stop when either max_packets are sniffed(if it is != 0),
     * or when the functor returns false.
     *
     * Note that the pcap handle stored in a BaseSniffer will always be the
     * same. This means that if you start sniffing using sniff_loop, then stop
     * and at some point in the future you call sniff_loop again, you will keep 
     * iterating over the same handle. If the handle points to a pcap file, then
     * you will continue processing packets from it. If the handle points to 
     * a network device, you will keep sniffing from it.
     *
     * This method catches both malformed_packet and pdu_not_found exceptions,
     * which allows writing much cleaner code, since you can call PDU::rfind_pdu
     * without worrying about catching the exception that can be thrown. This
     * allows writing code such as the following:
     *
    * \code
     * bool callback(const PDU& pdu) {
     *     // If either RawPDU is not found, or construction of the DNS
     *     // object fails, the BaseSniffer object will trap the exceptions,
     *     // so we don't need to worry about it.
     *     DNS dns = pdu.rfind_pdu<RawPDU>().to<DNS>();
     *     return true;
     * }
     * \endcode
     *
     * \param function The callback handler object which should process packets.
     * \param max_packets The maximum amount of packets to sniff. 0 == infinite.
     */
    template <typename Functor>
    void sniff_loop(Functor function, uint32_t max_packets = 0);

    /**
     * \brief Sets a filter on this sniffer.
     * \param filter The filter to be set.
     * \return True iif it was possible to apply the filter.
     */
    bool set_filter(const std::string& filter);

    /**
     * \brief Stops sniffing loops.
     *
     * This method must be called from the same thread from which
     * BaseSniffer::sniff_loop was called.
     */
    void stop_sniff();

    /**
     * \brief Gets the file descriptor associated with the sniffer.
     */
    int get_fd();

    /**
     * \brief Sets direction for the sniffer.
     *
     * This calls pcap_setdirection using the provided parameter.
     * \param d The direction for the sniffer.
     */
    bool set_direction(pcap_direction_t d);

    /**
     * \brief Sets the read timeout for this sniffer.
     *
     * This calls pcap_set_timeout using the provided parameter.
     * \param ms The amount of milliseconds.
     */
    void set_timeout(int ms);

    /**
     * \brief Sets whether to extract RawPDUs or fully parsed packets.
     *
     * By default, packets will be parsed starting from link layer.
     * However, if you're parsing a lot of traffic, then you might
     * want to extract packets and push them into a queue,
     * so a consumer can parse them when they're popped.
     *
     * This method allows doing that. If the parameter is true,
     * then packets taken from this BaseSniffer will only contain
     * a RawPDU which will have to entire contents of the packet.
     *
     * \param value Whether to extract RawPDUs or not.
     */
    void set_extract_raw_pdus(bool value);

    /**
     * \brief function pointer for the sniffing method
     *
     * By default, libtins uses `pcap_loop` to sniff packets. With
     * `set_pcap_sniffing_method` it is possible to specify an alternative
     * sniffing method, for example `pcap_dispatch`, or a custom function.
     * This function pointer has the same interface as `pcap_loop` and
     * `pcap_dispatch`.
     *
     * \sa set_pcap_sniffing_method
     */
    typedef int(*PcapSniffingMethod)(pcap_t*, int, pcap_handler, u_char*);

    /**
     * \brief set sniffing method to either pcap_loop or pcap_dispatch.
     *
     * By default, packets are sniffed with `pcap_loop`, which only returns if
     * a packet is received, thus ignoring timeout expiration, if any is set.
     * With this method it is possible to pass an alternative sniffer function,
     * e.g. `pcap_dispatch`, that honors timeouts, or a custom function with
     * the same signature.
     *
     * See the relevant manual pages for pcap_loop and pcap_dispatch for more
     * information on their behavior
     *
     * \sa PcapSniffingMethod
     */
    void set_pcap_sniffing_method(PcapSniffingMethod method);

    /**
     * \brief Retrieves this sniffer's link type.
     *
     * This calls pcap_datalink on the stored pcap handle and
     * returns its result.
     */
    int link_type() const;

    /**
     * Retrieves an iterator to the next packet in this sniffer.
     */
    iterator begin();

    /**
     * Retrieves an end iterator.
     */
    iterator end();

    /**
     * Retrieves the pcap handle used by this sniffer.
     */
    pcap_t* get_pcap_handle();

    /**
     * Retrieves the pcap handle used by this sniffer.
     */
    const pcap_t* get_pcap_handle() const;
protected:
    /**
     * Default constructor.
     */
    BaseSniffer();

    void set_pcap_handle(pcap_t* pcap_handle);

    void set_if_mask(bpf_u_int32 if_mask);

    bpf_u_int32 get_if_mask() const;
private:
    BaseSniffer(const BaseSniffer&);
    BaseSniffer& operator=(const BaseSniffer&);

    pcap_t* handle_;
    bpf_u_int32 mask_;
    bool extract_raw_;
    PcapSniffingMethod pcap_sniffing_method_;
};

/**
 * \class Sniffer
 * \brief Sniffs packets from a network interface.
 */
class TINS_API Sniffer : public BaseSniffer {
public:
    /**
     * \deprecated This enum is no longer necessary. You should use the
     * Sniffer(const std::string&, const SnifferConfiguration&) constructor.
     */
    enum promisc_type {
        NON_PROMISC,
        PROMISC
    };

    /**
     * \brief Constructs an instance of Sniffer
     *
     * \param device The device from which to capture packets
     */
    Sniffer(const std::string& device);

    /**
     * \brief Constructs an instance of Sniffer using the provided configuration.
     *
     * Use the SnifferConfiguration object to specify how you want to configure
     * the properties of this sniffer
     *
     * \sa SnifferConfiguration
     *
     * \param device The device which will be sniffed.
     * \param configuration The configuration object to use to setup the sniffer.
     */
    Sniffer(const std::string& device, const SnifferConfiguration& configuration);

    /**
     * \brief Constructs an instance of Sniffer.
     *
     * By default the interface won't be put into promiscuous mode, and won't
     * be put into monitor mode.
     *
     * \deprecated Use the Sniffer(const std::string&, const SnifferConfiguration&)
     * constructor.
     * \param device The device which will be sniffed.
     * \param max_packet_size The maximum packet size to be read.
     * \param promisc bool indicating whether to put the interface in promiscuous mode.(optional)
     * \param filter A capture filter to be used on the sniffing session.(optional);
     * \param rfmon Indicates if the interface should be put in monitor mode.(optional);
     */
    TINS_DEPRECATED(Sniffer(const std::string& device, unsigned max_packet_size,
                    bool promisc = false, const std::string& filter = "", bool rfmon = false));

    /**
     * \brief Constructs an instance of Sniffer.
     *
     * The maximum capture size is set to 65535. By default the interface won't
     * be put into promiscuous mode, and won't be put into monitor mode.
     *
     * \deprecated Use the Sniffer(const std::string&, const SnifferConfiguration&)
     * constructor.
     * \param device The device which will be sniffed.
     * \param promisc Indicates if the interface should be put in promiscuous mode.
     * \param filter A capture filter to be used on the sniffing session.(optional);
     * \param rfmon Indicates if the interface should be put in monitor mode.(optional);
     */
    TINS_DEPRECATED(Sniffer(const std::string& device, promisc_type promisc,
                    const std::string& filter = "", bool rfmon = false));

private:
    friend class SnifferConfiguration;

    void init(const std::string& device, const SnifferConfiguration& configuration);
    void set_snap_len(unsigned snap_len);
    void set_buffer_size(unsigned buffer_size);
    void set_promisc_mode(bool promisc_enabled);
    void set_rfmon(bool rfmon_enabled);
    void set_immediate_mode(bool enabled);
    void set_timestamp_precision(int value);
};

/**
 * \class FileSniffer
 * \brief Reads pcap files and interprets the packets in it.
 *
 * This class acts exactly in the same way that Sniffer, but reads
 * packets from a pcap file instead of an interface.
 */
class TINS_API FileSniffer : public BaseSniffer {
public:
    /**
     * \brief Constructs an instance of FileSniffer.
     * \param file_name The pcap file which will be parsed.
     * \param filter A capture filter to be used on the file.(optional);
     */
    FileSniffer(const std::string& file_name, const SnifferConfiguration& configuration);

    /**
     * \deprecated Use the constructor that takes a SnifferConfiguration instead.
     *
     * \brief Constructs an instance of FileSniffer.
     * \param file_name The pcap file which will be parsed.
     * \param filter A capture filter to be used on the file.(optional);
     */
    FileSniffer(const std::string& file_name, const std::string& filter = "");
};

template <typename T>
class HandlerProxy {
public:
    typedef T* ptr_type;
    typedef bool (T::*fun_type)(PDU&) ;

    HandlerProxy(ptr_type ptr, fun_type function)
    : object_(ptr), fun_(function) {}

    bool operator()(PDU& pdu) {
        return (object_->*fun_)(pdu);
    }
private:
    ptr_type object_;
    fun_type fun_;
};

template <typename T>
HandlerProxy<T> make_sniffer_handler(T* ptr, 
                                     typename HandlerProxy<T>::fun_type function) {
    return HandlerProxy<T>(ptr, function);
}

/**
 * \brief Iterates over packets sniffed by a BaseSniffer.
 */
class SnifferIterator : public std::iterator<std::forward_iterator_tag, Packet> {
public:
    /**
     * Constructs a SnifferIterator.
     * \param sniffer The sniffer to iterate.
     */
    SnifferIterator(BaseSniffer* sniffer = 0)
    : sniffer_(sniffer) {
        if (sniffer_) {
            advance();
        }
    }

    /**
     * Advances the iterator.
     */
    SnifferIterator& operator++() {
        advance();
        return* this;
    }

    /**
     * Advances the iterator.
     */
    SnifferIterator operator++(int) {
        SnifferIterator other(*this);
        advance();
        return other;
    }

    /**
     * Dereferences the iterator.
     * \return reference to the current packet.
     */
    Packet& operator*() {
        return pkt_;
    }

    /**
     * Dereferences the iterator.
     * \return pointer to the current packet.
     */
    Packet* operator->() {
        return &(**this);
    }

    /**
     * Compares this iterator for equality.
     * \param rhs The iterator to be compared to.
     */
    bool operator==(const SnifferIterator& rhs) const {
        return sniffer_ == rhs.sniffer_;
    }

    /**
     * Compares this iterator for in-equality.
     * \param rhs The iterator to be compared to.
     */
    bool operator!=(const SnifferIterator& rhs) const {
        return !(*this == rhs);
    }
private:
    void advance() {
        pkt_ = sniffer_->next_packet();
        if (!pkt_) {
            sniffer_ = 0;
        }
    }

    BaseSniffer* sniffer_;
    Packet pkt_;
};

/**
 * \class SnifferConfiguration
 * \brief Represents the configuration of a BaseSniffer object.
 *
 * This class can be used as an easy way to configure a Sniffer
 * or FileSniffer object.
 *
 * It can be used by constructing an object of this type,
 * setting the desired values and then passing it to the
 * Sniffer or FileSniffer object's constructor. This sets
 * default values for some attributes:
 *
 * - Snapshot length: 65535 bytes (64 KB).
 * - Timeout: 1000 milliseconds.
 * - Promiscuous mode: false.
 *
 * For any of the attributes not listed above, the associated
 * pcap function which is used to set them on a pcap handle
 * won't be called at all.
 *
 * This class can be used to configure a Sniffer object,
 * like this:
 *
 * \code
 * // Initialize the configuration.
 * SnifferConfiguration config;
 * config.set_filter("ip and port 80");
 * config.set_promisc_mode(true);
 *
 * // Use it on a Sniffer object.
 * Sniffer sniffer("eth0", config);
 * \endcode
 */
class TINS_API SnifferConfiguration {
public:
    /**
     * \brief The default snapshot length.
     *
     * This is 65535 by default.
     */
    static const unsigned DEFAULT_SNAP_LEN;

    /**
     * \brief The default timeout.
     *
     * This is 1000 by default.
     */
    static const unsigned DEFAULT_TIMEOUT;

    /**
     * Default constructs a SnifferConfiguration.
     */
    SnifferConfiguration();

    /**
     * Sets the snapshot length option.
     * \param snap_len The snapshot length to be set.
     */
    void set_snap_len(unsigned snap_len);

    /**
     * Sets the buffer size option.
     * \param buffer_size The buffer size to be set.
     */
    void set_buffer_size(unsigned buffer_size);

    /**
     * Sets the promiscuous mode option.
     * \param enabled The promiscuous mode value.
     */
    void set_promisc_mode(bool enabled);

    /**
     * Sets a pcap filter to use on the sniffer.
     * \param filter The pcap filter to be used.
     */
    void set_filter(const std::string& filter);

    /**
     * Sets the pcap sniffing method to use.
     * \param method The sniffing method to be used.
     */
    void set_pcap_sniffing_method(BaseSniffer::PcapSniffingMethod method);

    /**
     * Sets the rfmon option.
     * \param enabled The rfmon option value.
     */
    void set_rfmon(bool enabled);

    /**
     * Sets the timeout option.
     * \param timeout The timeout to be set.
     */
    void set_timeout(unsigned timeout);

    /**
     * Sets the direction option.
     * \param direction The direction to be set.
     */
    void set_direction(pcap_direction_t direction);

    /**
     * Sets the immediate mode option.
     * \param enabled The immediate mode option value.
     */
    void set_immediate_mode(bool enabled);

    /**
     * Sets the timestamp precision value
     * \param value The timestamp option value.
     */
    void set_timestamp_precision(int value);
protected:
    friend class Sniffer;
    friend class FileSniffer;

    enum Flags {
        BUFFER_SIZE = 1,
        PROMISCUOUS = 2,
        RFMON = 4,
        PACKET_FILTER = 8,
        IMMEDIATE_MODE = 16,
        DIRECTION = 32,
        TIMESTAMP_PRECISION = 64,
        PCAP_SNIFFING_METHOD = 128,
    };

    void configure_sniffer_pre_activation(Sniffer& sniffer) const;
    void configure_sniffer_pre_activation(FileSniffer& sniffer) const;

    void configure_sniffer_post_activation(Sniffer& sniffer) const;

    uint32_t flags_;
    unsigned snap_len_;
    unsigned buffer_size_;
    std::string filter_;
    BaseSniffer::PcapSniffingMethod pcap_sniffing_method_;
    unsigned timeout_;
    bool promisc_;
    bool rfmon_;
    bool immediate_mode_;
    pcap_direction_t direction_;
    int timestamp_precision_;
};

template <typename Functor>
void Tins::BaseSniffer::sniff_loop(Functor function, uint32_t max_packets) {
    for(iterator it = begin(); it != end(); ++it) {
        try {
            // If the functor returns false, we're done
            #if TINS_IS_CXX11 && !defined(_MSC_VER)
            if (!Tins::Internals::invoke_loop_cb(function, *it)) {
                return;
            }
            #else
            if (!function(*it->pdu())) {
                return;
            }
            #endif
        }
        catch(malformed_packet&) { }
        catch(pdu_not_found&) { }
        if (max_packets && --max_packets == 0) {
            return;
        }
    }
}

} // Tins

#endif // TINS_HAVE_PCAP

#endif // TINS_SNIFFER_H
