// See the file "COPYING" in the main distribution directory for copyright.

/**
 * The central management unit for registering and instantiating analyzers.
 *
 * For each protocol that Zeek supports, there's one class derived from
 * analyzer::Analyzer. Once we have decided that a connection's payload is to
 * be parsed as a given protocol, we instantiate the corresponding
 * analyzer-derived class and add the new instance as a child node into the
 * connection's analyzer tree.
 *
 * In addition to the analyzer-derived class itself, for each protocol
 * there's also "meta-class" derived from analyzer::Component that describes
 * the analyzer, including status information on if that particular protocol
 * analysis is currently enabled.
 *
 * To identify an analyzer (or to be precise: a component), the manager
 * maintains mappings of (1) analyzer::Tag to component, and (2)
 * human-readable analyzer name to component.
 */
#pragma once

#include "zeek/zeek-config.h"

#include <queue>
#include <vector>

#include "zeek/IP.h"
#include "zeek/IPAddr.h"
#include "zeek/Tag.h"
#include "zeek/analyzer/Analyzer.h"
#include "zeek/analyzer/Component.h"
#include "zeek/net_util.h"
#include "zeek/plugin/ComponentManager.h"

namespace zeek {

namespace packet_analysis::IP {

class IPBasedAnalyzer;
class SessionAdapter;

} // namespace packet_analysis::IP

namespace analyzer {

/**
 * Class maintaining and scheduling available protocol analyzers.
 *
 * The manager maintains a registry of all available protocol analyzers,
 * including a mapping between their textual names and Tag. It
 * instantiates new analyzers on demand. For new connections, the manager
 * sets up their initial analyzer tree, including adding the right \c PIA,
 * respecting well-known ports, and tracking any analyzers specifically
 * scheduled for individual connections.
 */
class Manager : public plugin::ComponentManager<Component> {
public:
    /**
     * Constructor.
     */
    Manager();

    /**
     * Destructor.
     */
    ~Manager();

    /**
     * Second-stage initialization of the manager. This is called late
     * during Zeek's initialization after any scripts are processed.
     */
    void InitPostScript();

    /**
     * Finished the manager's operations.
     */
    void Done();

    /**
     * Dumps out the state of all registered analyzers to the \c analyzer
     * debug stream. Should be called only after any \c zeek_init events
     * have executed to ensure that any of their changes are applied.
     */
    void DumpDebug(); // Called after zeek_init() events.

    /**
     * Enables an analyzer type. Only enabled analyzers will be
     * instantiated for new connections.
     *
     * @param tag The analyzer's tag.
     *
     * @return True if successful.
     */
    bool EnableAnalyzer(const zeek::Tag& tag);

    /**
     * Enables an analyzer type. Only enabled analyzers will be
     * instantiated for new connections.
     *
     * @param tag The analyzer's tag as an enum of script type \c
     * Tag.
     *
     * @return True if successful.
     */
    bool EnableAnalyzer(EnumVal* tag);

    /**
     * Disables an analyzer type. Disabled analyzers will not be
     * instantiated for new connections.
     *
     * @param tag The analyzer's tag.
     *
     * @return True if successful.
     */
    bool DisableAnalyzer(const zeek::Tag& tag);

    /**
     * Disables an analyzer type. Disabled analyzers will not be
     * instantiated for new connections.
     *
     * @param tag The analyzer's tag as an enum of script type \c
     * Tag.
     *
     * @return True if successful.
     */
    bool DisableAnalyzer(EnumVal* tag);

    /**
     * Disables all currently registered analyzers.
     */
    void DisableAllAnalyzers();

    /**
     * Returns the tag associated with an analyzer name, or the tag
     * associated with an error if no such analyzer exists.
     *
     * @param name The canonical analyzer name to check.
     */
    zeek::Tag GetAnalyzerTag(const char* name);

    /**
     * Returns true if an analyzer is enabled.
     *
     * @param tag The analyzer's tag.
     */
    bool IsEnabled(const zeek::Tag& tag);

    /**
     * Returns true if an analyzer is enabled.
     *
     * @param tag The analyzer's tag as an enum of script type \c
     * Tag.
     */
    bool IsEnabled(EnumVal* tag);

    /**
     * Registers a well-known port for an analyzer. Once registered,
     * connection on that port will start with a corresponding analyzer
     * assigned.
     *
     * @param tag The analyzer's tag as an enum of script type \c
     * Tag.
     *
     * @param port The well-known port.
     *
     * @return True if successful.
     */
    bool RegisterAnalyzerForPort(EnumVal* tag, PortVal* port);

    /**
     * Registers a well-known port for an analyzer. Once registered,
     * connection on that port will start with a corresponding analyzer
     * assigned.
     *
     * @param tag The analyzer's tag.
     *
     * @param proto The port's protocol.
     *
     * @param port The port's number.
     *
     * @return True if successful.
     */
    bool RegisterAnalyzerForPort(const zeek::Tag& tag, TransportProto proto, uint32_t port);

    /**
     * Unregisters a well-known port for an analyzers.
     *
     * @param tag The analyzer's tag as an enum of script type \c
     * Tag.
     *
     * @param port The well-known port.
     *
     * @return True if successful (incl. when the port wasn't actually
     * registered for the analyzer).
     *
     */
    bool UnregisterAnalyzerForPort(EnumVal* tag, PortVal* port);

    /**
     * Unregisters a well-known port for an analyzers.
     *
     * @param tag The analyzer's tag.
     *
     * @param proto The port's protocol.
     *
     * @param port The port's number.
     *
     * @param tag The analyzer's tag as an enum of script type \c
     * Tag.
     */
    bool UnregisterAnalyzerForPort(const zeek::Tag& tag, TransportProto proto, uint32_t port);

    /**
     * Instantiates a new analyzer instance for a connection.
     *
     * @param tag The analyzer's tag.
     *
     * @param conn The connection the analyzer is to be associated with.
     *
     * @return The new analyzer instance. Note that the analyzer will not
     * have been added to the connection's analyzer tree yet. Returns
     * null if tag is invalid, the requested analyzer is disabled, or the
     * analyzer can't be instantiated.
     */
    Analyzer* InstantiateAnalyzer(const zeek::Tag& tag, Connection* c);

    /**
     * Instantiates a new analyzer instance for a connection.
     *
     * @param name The name of the analyzer.
     *
     * @param conn The connection the analyzer is to be associated with.
     *
     * @return The new analyzer instance. Note that the analyzer will not
     * have been added to the connection's analyzer tree yet. Returns
     * null if the name is not known or if the requested analyzer that is
     * disabled.
     */
    Analyzer* InstantiateAnalyzer(const char* name, Connection* c);

    /**
     * Returns true if an analyzer's declared transport is compatible with the
     * given transport (i.e. matches it or the analyzer declares none). Used to
     * keep automatic attachment (port, DPD, scheduling) from binding an
     * analyzer to a transport it can't handle.
     */
    bool AnalyzerTransportMatches(const zeek::Tag& tag, TransportProto transport) const;

    /**
     * Schedules a particular analyzer for an upcoming connection. Once
     * the connection is seen, BuildInitAnalyzerTree() will add the
     * specified analyzer to its tree.
     *
     * @param orig The connection's anticipated originator address.
     * 0.0.0.0 can be used as a wildcard matching any originator.
     *
     * @param resp The connection's anticipated responder address (no
     * wildcard).
     *
     * @param resp_p The connection's anticipated responder port.
     *
     * @param proto The connection's anticipated transport protocol.
     *
     * @param analyzer The analyzer to use once the connection is seen.
     *
     * @param timeout An interval after which to timeout the request to
     * schedule this analyzer. Must be non-zero.
     */
    void ScheduleAnalyzer(const IPAddr& orig, const IPAddr& resp, uint16_t resp_p, TransportProto proto,
                          const zeek::Tag& analyzer, double timeout);

    /**
     * Schedules a particular analyzer for an upcoming connection. Once
     * the connection is seen, BuildInitAnalyzerTree() will add the
     * specified analyzer to its tree.
     *
     * @param orig The connection's anticipated originator address. 0 can
     * be used as a wildcard matching any originator.
     *
     * @param resp The connection's anticipated responder address (no
     * wildcard).
     *
     * @param resp_p The connection's anticipated responder port.
     *
     * @param proto The connection's anticipated transport protocol.
     *
     * @param analyzer The name of the analyzer to use once the
     * connection is seen.
     *
     * @param timeout An interval after which to timeout the request to
     * schedule this analyzer. Must be non-zero.
     */
    void ScheduleAnalyzer(const IPAddr& orig, const IPAddr& resp, uint16_t resp_p, TransportProto proto,
                          const char* analyzer, double timeout);

    /**
     * Searched for analyzers scheduled to be attached to a given connection
     * and then attaches them.
     *
     * @param conn The connection to which scheduled analyzers are attached.
     *
     * @param init True if the newly added analyzers should be
     * immediately initialized.
     *
     * @param root If given, the scheduled analyzers will become children
     * of this; if not given the connection's root analyzer is used
     * instead.
     *
     * @return True if at least one scheduled analyzer was found.
     */
    bool ApplyScheduledAnalyzers(Connection* conn, bool init_and_event = true,
                                 packet_analysis::IP::SessionAdapter* parent = nullptr);

    /**
     * Schedules a particular analyzer for an upcoming connection. Once
     * the connection is seen, BuildInitAnalyzerTree() will add the
     * specified analyzer to its tree.
     *
     * @param orig The connection's anticipated originator address. 0 can
     * be used as a wildcard matching any originator.
     *
     * @param resp The connection's anticipated responder address (no
     * wildcard).
     *
     * @param resp_p The connection's anticipated responder port.
     *
     * @param analyzer The analyzer to use once the connection is seen as
     * an enum value of script-type \c Tag.
     *
     * @param timeout An interval after which to timeout the request to
     * schedule this analyzer. Must be non-zero.
     */
    void ScheduleAnalyzer(const IPAddr& orig, const IPAddr& resp, PortVal* resp_p, Val* analyzer, double timeout);

    /**
     * @return the UDP port numbers to be associated with VXLAN traffic.
     */
    const std::vector<uint16_t>& GetVxlanPorts() const { return vxlan_ports; }

private:
    // Internal version that must be used only once InitPostScript has completed.
    bool RegisterAnalyzerForPort(const std::tuple<zeek::Tag, TransportProto, uint32_t>& p);

    friend class packet_analysis::IP::IPBasedAnalyzer;

    using tag_set = std::set<zeek::Tag>;

    tag_set GetScheduled(const Connection* conn);
    void ExpireScheduledAnalyzers();

    //// Data structures to track analyzed scheduled for future connections.

    // The index for a scheduled connection.
    struct ConnIndex {
        IPAddr orig;
        IPAddr resp;
        uint16_t resp_p;
        uint16_t proto;

        ConnIndex(const IPAddr& _orig, const IPAddr& _resp, uint16_t _resp_p, uint16_t _proto);
        ConnIndex();

        bool operator<(const ConnIndex& other) const;
    };

    // Information associated with a scheduled connection.
    struct ScheduledAnalyzer {
        ConnIndex conn;
        zeek::Tag analyzer;
        double timeout;       // The real timeout for the analyzer
        double queue_timeout; // The timeout for queueing, this might differ from
                              // timeout if the same analyzer is scheduled twice.

        struct Comparator {
            bool operator()(ScheduledAnalyzer* a, ScheduledAnalyzer* b) { return a->queue_timeout > b->queue_timeout; }
        };
    };

    using protocol_analyzers = std::set<std::tuple<zeek::Tag, TransportProto, uint32_t>>;
    using conns_map = std::multimap<ConnIndex, ScheduledAnalyzer*>;
    using conns_queue =
        std::priority_queue<ScheduledAnalyzer*, std::vector<ScheduledAnalyzer*>, ScheduledAnalyzer::Comparator>;

    bool initialized = false;
    protocol_analyzers pending_analyzers_for_ports;

    conns_map conns;
    conns_queue conns_by_timeout;
    std::vector<uint16_t> vxlan_ports;
};

} // namespace analyzer

ZEEK_EXTERN_DATA analyzer::Manager* analyzer_mgr;

} // namespace zeek

// Macros for analyzer debug logging which include the connection id into the
// message.
// NOLINTBEGIN(cppcoreguidelines-macro-usage)
#ifdef DEBUG
#define DBG_ANALYZER(conn, txt)                                                                                        \
    DBG_LOG(zeek::DBG_ANALYZER, "%s " txt,                                                                             \
            fmt_conn_id((conn)->OrigAddr(), ntohs((conn)->OrigPort()), (conn)->RespAddr(),                             \
                        ntohs((conn)->RespPort())));
#define DBG_ANALYZER_ARGS(conn, fmt, ...)                                                                              \
    DBG_LOG(zeek::DBG_ANALYZER, "%s " fmt,                                                                             \
            fmt_conn_id((conn)->OrigAddr(), ntohs((conn)->OrigPort()), (conn)->RespAddr(), ntohs((conn)->RespPort())), \
            ##__VA_ARGS__);
#else
#define DBG_ANALYZER(conn, txt)
#define DBG_ANALYZER_ARGS(conn, fmt, ...)
#endif
// NOLINTEND(cppcoreguidelines-macro-usage)
