// Copyright (C) 2013-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 HOOKS_MANAGER_H
#define HOOKS_MANAGER_H

#include <hooks/server_hooks.h>
#include <hooks/libinfo.h>

#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>

#include <string>
#include <vector>

namespace isc {
namespace hooks {

/// @brief Libraries still opened.
///
/// Thrown if an attempt is made to load libraries when some are still
/// in memory likely because they were not unloaded (logic error in Kea)
/// or they have some visible dangling pointers (logic error in a hook
/// library).
class LibrariesStillOpened : public Exception {
public:
    LibrariesStillOpened(const char* file, size_t line, const char* what) :
        isc::Exception(file, line, what) {}
};

// Forward declarations
class CalloutHandle;
class CalloutManager;
class LibraryHandle;
class LibraryManagerCollection;

/// @brief Hooks Manager
///
/// This is the overall manager of the hooks framework and is the main class
/// used by a Kea module when handling hooks.  It is responsible for the
/// loading and unloading of user libraries, and for calling the callouts on
/// each hook point.
///
/// The class is a singleton, the single instance of the object being accessed
/// through the static getHooksManager() method.

class HooksManager : boost::noncopyable {
public:

    /// @brief Load and reload libraries
    ///
    /// Loads the list of libraries into the server address space.  For each
    /// library, the "standard" functions (ones with the same names as the
    /// hook points) are configured and the libraries' "load" function
    /// called.
    ///
    /// @note this method now requires the libraries are unloaded before
    ///       being called.
    ///
    /// If any library fails to load, an error message will be logged.  The
    /// remaining libraries will be loaded if possible.
    ///
    /// @param libraries List of libraries to be loaded.  The order is
    ///        important, as it determines the order that callouts on the same
    ///        hook will be called.
    ///
    /// @return true if all libraries loaded without a problem, false if one or
    ///        more libraries failed to load.  In the latter case, message will
    ///        be logged that give the reason.
    /// @throw LibrariesStillOpened when some libraries are already loaded.
    static bool loadLibraries(const HookLibsCollection& libraries);

    /// @brief Unload libraries
    ///
    /// Unloads the loaded libraries and leaves the hooks subsystem in the
    /// state it was after construction but before loadLibraries() is called.
    ///
    /// @note: This method should be called after @ref prepareUnloadLibraries
    ///        in order to destroy appropriate objects. See notes for
    ///        the class LibraryManager for pitfalls.
    /// @note: if even after @ref prepareUnloadLibraries there are still
    ///        visible pointers (i.e. callout handles owning the
    ///        library manager collection) the method will fail to close
    ///        libraries and returns false. It is a fatal error as there
    ///        is no possible recovery. It is a logic error in the hook
    ///        code too so the solution is to fix it and to restart
    ///        the server with a correct hook library binary.
    ///
    /// @return true if all libraries unloaded successfully, false if they
    ///         are still in memory.
    static bool unloadLibraries();

    /// @brief Prepare the unloading of libraries
    ///
    /// Calls the unload functions when they exist and removes callouts.
    ///
    /// @note: after the call to this method there should be no visible
    ///        dangling pointers (i.e. callout handles owning the library
    ///        manager collection) nor invisible dangling pointers.
    ///        In the first case it will be impossible to close libraries
    ///        so they will remain in memory, in the second case a crash
    ///        is possible in particular at exit time during global
    ///        object finalization. In both cases the hook library code
    ///        causing the problem is incorrect and must be fixed.
    /// @note: it is a logic error to not call this method before
    ///        @ref unloadLibraries even it hurts only with particular
    ///        hooks libraries.
    static void prepareUnloadLibraries();

    /// @brief Are callouts present?
    ///
    /// Checks loaded libraries and returns true if at lease one callout
    /// has been registered by them for the given hook.
    ///
    /// @param index Hooks index for which callouts are checked.
    ///
    /// @return true if callouts are present, false if not.
    /// @throw NoSuchHook Given index does not correspond to a valid hook.
    static bool calloutsPresent(int index);

    /// @brief Checks if control command handlers are present for the
    /// specified command.
    ///
    /// @param command_name Command name for which handlers' presence should
    ///        be checked.
    ///
    /// @return true if there is a hook point associated with the specified
    /// command and callouts/command handlers are installed for this hook
    /// point, false otherwise.
    static bool commandHandlersPresent(const std::string& command_name);

    /// @brief Calls the callouts for a given hook
    ///
    /// Iterates through the library handles and calls the callouts associated
    /// with the given hook index.
    ///
    /// @note This method invalidates the current library index set with
    ///       setLibraryIndex().
    ///
    /// @param index Index of the hook to call.
    /// @param handle Reference to the CalloutHandle object for the current
    ///        object being processed.
    static void callCallouts(int index, CalloutHandle& handle);

    /// @brief Calls the callouts/command handlers for a given command name.
    ///
    /// Iterates through the library handles and calls the command handlers
    /// associated with the given command. It expects that the hook point
    /// for this command exists (with a name being a command_name prefixed
    /// with a dollar sign and with hyphens replaced with underscores).
    ///
    /// @param command_name Command name for which handlers should be called.
    /// @param handle Reference to the CalloutHandle object for the current
    /// object being processed.
    ///
    /// @throw NoSuchHook if the hook point for the specified command does
    ///        not exist.
    static void callCommandHandlers(const std::string& command_name,
                                    CalloutHandle& handle);

    /// @brief Return pre-callouts library handle
    ///
    /// Returns a library handle that can be used by the server to register
    /// callouts on a hook that are called _before_ any callouts belonging
    /// to a library.
    ///
    /// @note Both the reference returned and the callouts registered with
    ///       this handle only remain valid until the next loadLibraries() or
    ///       unloadLibraries() call.  If the callouts are to remain registered
    ///       after this time, a new handle will need to be obtained and the
    ///       callouts re-registered.
    ///
    /// @return Reference to library handle associated with pre-library callout
    ///         registration.
    static LibraryHandle& preCalloutsLibraryHandle();

    /// @brief Return post-callouts library handle
    ///
    /// Returns a library handle that can be used by the server to register
    /// callouts on a hook that are called _after any callouts belonging
    /// to a library.
    ///
    /// @note Both the reference returned and the callouts registered with
    ///       this handle only remain valid until the next loadLibraries() or
    ///       unloadLibraries() call.  If the callouts are to remain registered
    ///       after this time, a new handle will need to be obtained and the
    ///       callouts re-registered.
    ///
    /// @return Reference to library handle associated with post-library callout
    ///         registration.
    static LibraryHandle& postCalloutsLibraryHandle();

    /// @brief Return callout handle
    ///
    /// Returns a callout handle to be associated with a request passed round
    /// the system.
    ///
    /// @note This handle is valid only after a loadLibraries() call and then
    ///       only up to the next loadLibraries() call.
    ///
    /// @return Shared pointer to a CalloutHandle object.
    static boost::shared_ptr<CalloutHandle> createCalloutHandle();

    /// @brief Register Hook
    ///
    /// This is just a convenience shell around the ServerHooks::registerHook()
    /// method.  It - along with the definitions of the two hook indexes for
    /// the context_create and context_destroy methods - means that server
    /// authors only need to deal with HooksManager and CalloutHandle, and not
    /// include any other hooks framework classes.
    ///
    /// @param name Name of the hook
    ///
    /// @return Index of the hook, to be used in subsequent hook-related calls.
    ///         This will be greater than or equal to zero (so allowing a
    ///         negative value to indicate an invalid index).
    ///
    /// @throws DuplicateHook A hook with the same name has already been
    ///         registered.
    static int registerHook(const std::string& name);

    /// @brief Return list of loaded libraries
    ///
    /// Returns the names of the loaded libraries.
    ///
    /// @return List of loaded library names.
    static std::vector<std::string> getLibraryNames();

    /// @brief Return list of loaded libraries with its parameters.
    ///
    /// Returns the names of the loaded libraries and their parameters.
    ///
    /// @return List of loaded libraries (names + parameters)
    static HookLibsCollection getLibraryInfo();

    /// @brief Validate library list
    ///
    /// For each library passed to it, checks that the library can be opened
    /// and that the "version" function is present and gives the right answer.
    /// Each library is closed afterwards.
    ///
    /// This is used during the configuration parsing - when the list of hooks
    /// libraries is changed, each of the new libraries is checked before the
    /// change is committed.
    ///
    /// @param libraries List of libraries to be validated.
    ///
    /// @return An empty vector if all libraries validated.  Otherwise it
    ///         holds the names of the libraries that failed validation.
    static std::vector<std::string> validateLibraries(
                       const std::vector<std::string>& libraries);

    /// Index numbers for pre-defined hooks.
    static const int CONTEXT_CREATE = ServerHooks::CONTEXT_CREATE;
    static const int CONTEXT_DESTROY = ServerHooks::CONTEXT_DESTROY;

    /// @brief Park an object (packet).
    ///
    /// The typical use case for parking an object is when the server needs to
    /// suspend processing of a packet to perform an asynchronous operation,
    /// before the response is sent to a client. In this case, the object type
    /// is a pointer to the processed packet. Therefore, further in this
    /// description we're going to refer to the parked objects as "parked
    /// packets". However, any other object can be parked if necessary.
    ///
    /// The following is the typical flow when packets are parked. The callouts
    /// responsible for performing an asynchronous operation signal this need
    /// to the server by returning the status @c NEXT_STEP_PARK, which instructs
    /// the server to call this function. This function stops processing the
    /// packet and puts it in, so called, parking lot. In order to be able to
    /// resume the packet processing when instructed by the hooks library, the
    /// parked packet is associated with the callback which, when called, will
    /// resume packet processing.
    ///
    /// The hook library must increase a reference count on the parked object
    /// by calling @c ParkingLotHandle::reference prior to returning the
    /// @c NEXT_STEP_PARK status. This is important when multiple callouts
    /// are installed on the same hook point and each of them schedules an
    /// asynchronous operation. In this case, the packet must not be unparked
    /// until all hook libraries call @c ParkingLotHandle::unpark to mark
    /// that respective asynchronous operations are completed.
    ///
    /// @tparam Type of the parked object.
    /// @param hook_name name of the hook point for which the packet is parked.
    /// @param parked_object packet to be parked.
    /// @param unpark_callback callback invoked when the packet is unparked.
    template<typename T>
    static void park(const std::string& hook_name, T parked_object,
                     std::function<void()> unpark_callback) {
        ServerHooks::getServerHooks().
            getParkingLotPtr(hook_name)->park(parked_object, unpark_callback);
    }

    /// @brief Forces unparking the object (packet).
    ///
    /// This method unparks the object regardless of the reference counting
    /// value. This is used in the situations when the callouts fail to unpark
    /// the packet for some reason.
    ///
    /// @tparam T type of the parked object.
    /// @param hook_name name of the hook point for which the packet is parked.
    /// @param parked_object parked object to be unparked.
    /// @return true if the specified object has been found, false otherwise.
    template<typename T>
    static bool unpark(const std::string& hook_name, T parked_object) {
        return (ServerHooks::getServerHooks().
                getParkingLotPtr(hook_name)->unpark(parked_object, true));
    }

    /// @brief Removes parked object without calling a callback.
    ///
    /// @tparam T type of the parked object.
    /// @param hook_name name of the hook point for which the packet is parked.
    /// @param parked_object parked object to be removed.
    /// @return true if the specified object has been found false otherwise.
    template<typename T>
    static bool drop(const std::string& hook_name, T parked_object) {
        return (ServerHooks::getServerHooks().
                getParkingLotPtr(hook_name)->drop(parked_object));
    }

    /// @brief Increases reference counter for the parked object.
    ///
    /// Reference counter must be increased at least to 1 before the @c park()
    /// method can be called.
    ///
    /// @tparam Type of the parked object.
    /// @param hook_name name of the hook point for which the packet is parked.
    /// @param parked_object parked object for which reference counter should
    /// be increased.
    template<typename T>
    static void reference(const std::string& hook_name, T parked_object) {
        ServerHooks::getServerHooks().
            getParkingLotPtr(hook_name)->reference(parked_object);
    }

    /// @brief Clears any parking packets.
    ///
    /// This method should be called during reconfiguration to ensure there
    /// are no dangling pointers that could possibly prevent the library
    /// from being unloaded.
    static void clearParkingLots() {
        ServerHooks::getServerHooks().getParkingLotsPtr()->clear();
    }

    /// @brief Set test mode
    ///
    /// If enabled by unit tests will permit to register callouts before calling
    /// @ref loadLibraries which will return immediately without changing
    /// current internal state.
    ///
    /// @param mode the test mode flag which enables or disabled the
    /// functionality.
    static void setTestMode(bool mode);

    /// @brief Get test mode
    ///
    /// @return the test mode flag.
    static bool getTestMode();

private:

    /// @brief Constructor
    ///
    /// This is private as the object is a singleton and can only be addressed
    /// through the getHooksManager() static method.
    HooksManager();

    /// @brief Get singleton hooks manager
    ///
    /// @return Reference to the singleton hooks manager.
    static HooksManager& getHooksManager();

    //@{
    /// The following methods correspond to similarly-named static methods,
    /// but actually do the work on the singleton instance of the HooksManager.
    /// See the descriptions of the static methods for more details.

    /// @brief Validate library list
    ///
    /// @param List of libraries to be validated.
    ///
    /// @return An empty string if all libraries validated.  Otherwise it is
    ///         the name of the first library that failed validation.  The
    ///         configuration code can return this to bindctl as an indication
    ///         of the problem.
    std::string validateLibrariesInternal(
                       const std::vector<std::string>& libraries) const;

    /// @brief Load and reload libraries
    ///
    /// @param libraries List of libraries to be loaded.  The order is
    ///        important, as it determines the order that callouts on the same
    ///        hook will be called.
    ///
    /// @return true if all libraries loaded without a problem, false if one or
    ///        more libraries failed to load.  In the latter case, message will
    ///        be logged that give the reason.
    bool loadLibrariesInternal(const HookLibsCollection& libraries);

    /// @brief Unload libraries
    ///
    /// @return true if all libraries unloaded successfully, false on an error.
    ///         In the latter case, an error message will have been output.
    bool unloadLibrariesInternal();

    /// @brief Prepare the unloading of libraries
    void prepareUnloadLibrariesInternal();

    /// @brief Are callouts present?
    ///
    /// @param index Hooks index for which callouts are checked.
    ///
    /// @return true if callouts are present, false if not.
    /// @throw NoSuchHook Given index does not correspond to a valid hook.
    bool calloutsPresentInternal(int index);

    /// @brief Checks if control command handlers are present for the
    /// specified command.
    ///
    /// @param command_name Command name for which handlers' presence should
    ///        be checked.
    ///
    /// @return true if there is a hook point associated with the specified
    /// command and callouts/command handlers are installed for this hook
    /// point, false otherwise.
    bool commandHandlersPresentInternal(const std::string& command_name);

    /// @brief Calls the callouts for a given hook
    ///
    /// @param index Index of the hook to call.
    /// @param handle Reference to the CalloutHandle object for the current
    ///        object being processed.
    void callCalloutsInternal(int index, CalloutHandle& handle);

    /// @brief Calls the callouts/command handlers for a given command name.
    ///
    /// @param command_name Command name for which handlers should be called.
    /// @param handle Reference to the CalloutHandle object for the current
    /// object being processed.
    ///
    /// @throw NoSuchHook if the hook point for the specified command does
    ///        not exist.
    void callCommandHandlersInternal(const std::string& command_name,
                                     CalloutHandle& handle);

    /// @brief Return callout handle
    ///
    /// @return Shared pointer to a CalloutHandle object.
    boost::shared_ptr<CalloutHandle> createCalloutHandleInternal();

    /// @brief Return pre-callouts library handle
    ///
    /// @return Reference to library handle associated with pre-library callout
    ///         registration.
    LibraryHandle& preCalloutsLibraryHandleInternal();

    /// @brief Return post-callouts library handle
    ///
    /// @return Reference to library handle associated with post-library callout
    ///         registration.
    LibraryHandle& postCalloutsLibraryHandleInternal();

    /// @brief Return list of loaded libraries
    ///
    /// @return List of loaded library names.
    std::vector<std::string> getLibraryNamesInternal() const;

    /// @brief Return a collection of library names with parameters.
    HookLibsCollection getLibraryInfoInternal() const;

    //@}

    // Members

    /// Set of library managers.
    ///
    /// @note: This should never be null.
    boost::shared_ptr<LibraryManagerCollection> lm_collection_;

    /// Callout manager for the set of library managers.
    ///
    /// @note: This should never be null.
    boost::shared_ptr<CalloutManager> callout_manager_;

    /// Test flag to keep @ref callout_manager_ when calling @ref loadLibraries
    /// from unit tests (called by @ref configureDhcp[46]Server).
    ///
    /// @note: This will effectively make @ref loadLibraries return immediately.
    bool test_mode_;
};

} // namespace util
} // namespace hooks

#endif // HOOKS_MANAGER_H
