// Copyright (C) 2014-2025 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/.

#include <config.h>

#include <asiolink/io_service_mgr.h>
#include <asiolink/process_spawn.h>
#include <cc/command_interpreter.h>
#include <cc/data.h>
#include <config/command_mgr.h>
#include <config/http_command_mgr.h>
#include <config/unix_command_mgr.h>
#include <cryptolink/crypto_hash.h>
#include <database/dbaccess_parser.h>
#include <dhcp/libdhcp++.h>
#include <dhcp6/ctrl_dhcp6_srv.h>
#include <dhcp6/dhcp6_log.h>
#include <dhcp6/dhcp6to4_ipc.h>
#include <dhcp6/json_config_parser.h>
#include <dhcp6/parser_context.h>
#include <dhcpsrv/cfg_db_access.h>
#include <dhcpsrv/cfg_multi_threading.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/db_type.h>
#include <dhcpsrv/host_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/memfile_lease_mgr.h>
#include <hooks/hooks.h>
#include <hooks/hooks_manager.h>
#include <process/cfgrpt/config_report.h>
#include <stats/stats_mgr.h>
#include <util/encode/encode.h>
#include <util/multi_threading_mgr.h>

#include <signal.h>

#include <sstream>

using namespace isc::asiolink;
using namespace isc::config;
using namespace isc::data;
using namespace isc::db;
using namespace isc::dhcp;
using namespace isc::hooks;
using namespace isc::stats;
using namespace isc::util;
using namespace std;
namespace ph = std::placeholders;

namespace {

/// Structure that holds registered hook indexes.
struct CtrlDhcp6Hooks {
    int hooks_index_dhcp6_srv_configured_;

    /// Constructor that registers hook points for the DHCPv6 server.
    CtrlDhcp6Hooks() {
        hooks_index_dhcp6_srv_configured_ = HooksManager::registerHook("dhcp6_srv_configured");
    }

};

// Declare a Hooks object. As this is outside any function or method, it
// will be instantiated (and the constructor run) when the module is loaded.
// As a result, the hook indexes will be defined before any method in this
// module is called.
CtrlDhcp6Hooks Hooks;

// Name of the file holding server identifier.
static const char* SERVER_DUID_FILE = "kea-dhcp6-serverid";

/// @brief Signals handler for DHCPv6 server.
///
/// This signal handler handles the following signals received by the DHCPv6
/// server process:
/// - SIGHUP - triggers server's dynamic reconfiguration.
/// - SIGTERM - triggers server's shut down.
/// - SIGINT - triggers server's shut down.
///
/// @param signo Signal number received.
void signalHandler(int signo) {
    // SIGHUP signals a request to reconfigure the server.
    if (signo == SIGHUP) {
        CommandMgr::instance().processCommand(createCommand("config-reload"));
    } else if ((signo == SIGTERM) || (signo == SIGINT)) {
        CommandMgr::instance().processCommand(createCommand("shutdown"));
    }
}

}

namespace isc {
namespace dhcp {

ControlledDhcpv6Srv* ControlledDhcpv6Srv::server_ = NULL;

void
ControlledDhcpv6Srv::init(const std::string& file_name) {
    // Keep the call timestamp.
    start_ = boost::posix_time::second_clock::universal_time();

    // Configure the server using JSON file.
    ConstElementPtr result = loadConfigFile(file_name);

    int rcode;
    ConstElementPtr comment = isc::config::parseAnswer(rcode, result);
    if (rcode != CONTROL_RESULT_SUCCESS) {
        string reason = comment ? comment->stringValue() :
            "no details available";
        isc_throw(isc::BadValue, reason);
    }

    // Set signal handlers. When the SIGHUP is received by the process
    // the server reconfiguration will be triggered. When SIGTERM or
    // SIGINT will be received, the server will start shutting down.
    signal_set_.reset(new IOSignalSet(getIOService(), signalHandler));

    signal_set_->add(SIGINT);
    signal_set_->add(SIGHUP);
    signal_set_->add(SIGTERM);
}

void ControlledDhcpv6Srv::cleanup() {
    signal_set_.reset();
    getIOService()->poll();
}

ConstElementPtr
ControlledDhcpv6Srv::loadConfigFile(const std::string& file_name) {
    // This is a configuration backend implementation that reads the
    // configuration from a JSON file.

    isc::data::ConstElementPtr json;
    isc::data::ConstElementPtr result;

    // Basic sanity check: file name must not be empty.
    try {
        if (file_name.empty()) {
            // Basic sanity check: file name must not be empty.
            isc_throw(isc::BadValue, "JSON configuration file not specified."
                      " Please use -c command line option.");
        }

        // Read contents of the file and parse it as JSON
        Parser6Context parser;
        json = parser.parseFile(file_name, Parser6Context::PARSER_DHCP6);
        if (!json) {
            isc_throw(isc::BadValue, "no configuration found");
        }

        // Let's do sanity check before we call json->get() which
        // works only for map.
        if (json->getType() != isc::data::Element::map) {
            isc_throw(isc::BadValue, "Configuration file is expected to be "
                      "a map, i.e., start with { and end with } and contain "
                      "at least an entry called 'Dhcp6' that itself is a map. "
                      << file_name
                      << " is a valid JSON, but its top element is not a map."
                      " Did you forget to add { } around your configuration?");
        }

        // Use parsed JSON structures to configure the server
        result = CommandMgr::instance().processCommand(createCommand("config-set", json));
        if (!result) {
            // Undetermined status of the configuration. This should never
            // happen, but as the configureDhcp6Server returns a pointer, it is
            // theoretically possible that it will return NULL.
            isc_throw(isc::BadValue, "undefined result of "
                      "process command \"config-set\"");
        }

        // Now check is the returned result is successful (rcode=0) or not
        // (see @ref isc::config::parseAnswer).
        int rcode;
        ConstElementPtr comment = isc::config::parseAnswer(rcode, result);
        if (rcode != CONTROL_RESULT_SUCCESS) {
            string reason = comment ? comment->stringValue() :
                "no details available";
            isc_throw(isc::BadValue, reason);
        }
    } catch (const std::exception& ex) {
        // If configuration failed at any stage, we drop the staging
        // configuration and continue to use the previous one.
        CfgMgr::instance().clearStagingConfiguration();

        LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL)
            .arg(file_name).arg(ex.what());
        isc_throw(isc::BadValue, "configuration error using file '"
                  << file_name << "': " << ex.what());
    }

    LOG_INFO(dhcp6_logger, DHCP6_MULTI_THREADING_INFO)
        .arg(MultiThreadingMgr::instance().getMode() ? "yes" : "no")
        .arg(MultiThreadingMgr::instance().getThreadPoolSize())
        .arg(MultiThreadingMgr::instance().getPacketQueueSize());

    return (result);
}

ConstElementPtr
ControlledDhcpv6Srv::commandShutdownHandler(const string&, ConstElementPtr args) {
    if (!ControlledDhcpv6Srv::getInstance()) {
        LOG_WARN(dhcp6_logger, DHCP6_NOT_RUNNING);
        return (createAnswer(CONTROL_RESULT_ERROR, "Shutdown failure."));
    }

    int exit_value = 0;
    if (args) {
        // @todo Should we go ahead and shutdown even if the args are invalid?
        if (args->getType() != Element::map) {
            return (createAnswer(CONTROL_RESULT_ERROR, "Argument must be a map"));
        }

        ConstElementPtr param = args->get("exit-value");
        if (param)  {
            if (param->getType() != Element::integer) {
                return (createAnswer(CONTROL_RESULT_ERROR,
                                     "parameter 'exit-value' is not an integer"));
            }

            exit_value = param->intValue();
        }
    }

    ControlledDhcpv6Srv::getInstance()->shutdownServer(exit_value);
    return (createAnswer(CONTROL_RESULT_SUCCESS, "Shutting down."));
}

ConstElementPtr
ControlledDhcpv6Srv::commandConfigReloadHandler(const string&,
                                                ConstElementPtr /*args*/) {
    // Get configuration file name.
    string file = ControlledDhcpv6Srv::getInstance()->getConfigFile();
    try {
        LOG_INFO(dhcp6_logger, DHCP6_DYNAMIC_RECONFIGURATION).arg(file);
        auto result = loadConfigFile(file);
        LOG_INFO(dhcp6_logger, DHCP6_DYNAMIC_RECONFIGURATION_SUCCESS).arg(file);
        return (result);
    } catch (const std::exception& ex) {
        // Log the unsuccessful reconfiguration. The reason for failure
        // should be already logged. Don't rethrow an exception so as
        // the server keeps working.
        LOG_FATAL(dhcp6_logger, DHCP6_DYNAMIC_RECONFIGURATION_FAIL)
            .arg(file);
        return (createAnswer(CONTROL_RESULT_ERROR,
                             "Config reload failed: " + string(ex.what())));
    }
}

ConstElementPtr
ControlledDhcpv6Srv::commandConfigGetHandler(const string&,
                                             ConstElementPtr /*args*/) {
    ElementPtr config = CfgMgr::instance().getCurrentCfg()->toElement();
    string hash = BaseCommandMgr::getHash(config);
    config->set("hash", Element::create(hash));

    return (createAnswer(CONTROL_RESULT_SUCCESS, config));
}

ConstElementPtr
ControlledDhcpv6Srv::commandConfigHashGetHandler(const string&,
                                                 ConstElementPtr /*args*/) {
    ConstElementPtr config = CfgMgr::instance().getCurrentCfg()->toElement();

    string hash = BaseCommandMgr::getHash(config);

    ElementPtr params = Element::createMap();
    params->set("hash", Element::create(hash));
    return (createAnswer(CONTROL_RESULT_SUCCESS, params));
}

ConstElementPtr
ControlledDhcpv6Srv::commandConfigWriteHandler(const string&,
                                               ConstElementPtr args) {
    string filename;

    if (args) {
        if (args->getType() != Element::map) {
            return (createAnswer(CONTROL_RESULT_ERROR, "Argument must be a map"));
        }
        ConstElementPtr filename_param = args->get("filename");
        if (filename_param) {
            if (filename_param->getType() != Element::string) {
                return (createAnswer(CONTROL_RESULT_ERROR,
                                     "passed parameter 'filename' is not a string"));
            }
            filename = filename_param->stringValue();
        }
    }

    if (filename.empty()) {
        // filename parameter was not specified, so let's use whatever we remember
        // from the command-line
        filename = getConfigFile();
        if (filename.empty()) {
            return (createAnswer(CONTROL_RESULT_ERROR, "Unable to determine filename."
                                 "Please specify filename explicitly."));
        }
    } else {
        try {
            checkWriteConfigFile(filename);
        } catch (const isc::Exception& ex) {
            std::ostringstream msg;
            msg << "not allowed to write config into " << filename
                << ": " << ex.what();
            return (createAnswer(CONTROL_RESULT_ERROR, msg.str()));
        }
    }

    // Ok, it's time to write the file.
    size_t size = 0;
    try {
        ConstElementPtr cfg = CfgMgr::instance().getCurrentCfg()->toElement();
        size = writeConfigFile(filename, cfg);
    } catch (const isc::Exception& ex) {
        return (createAnswer(CONTROL_RESULT_ERROR, string("Error during config-write: ")
                             + ex.what()));
    }
    if (size == 0) {
        return (createAnswer(CONTROL_RESULT_ERROR, "Error writing configuration to "
                             + filename));
    }

    // Ok, it's time to return the successful response.
    ElementPtr params = Element::createMap();
    params->set("size", Element::create(static_cast<long long>(size)));
    params->set("filename", Element::create(filename));

    return (createAnswer(CONTROL_RESULT_SUCCESS, "Configuration written to "
                         + filename + " successful", params));
}

ConstElementPtr
ControlledDhcpv6Srv::commandConfigSetHandler(const string&,
                                             ConstElementPtr args) {
    const int status_code = CONTROL_RESULT_ERROR;
    ConstElementPtr dhcp6;
    string message;

    // Command arguments are expected to be:
    // { "Dhcp6": { ... } }
    if (!args) {
        message = "Missing mandatory 'arguments' parameter.";
    } else {
        dhcp6 = args->get("Dhcp6");
        if (!dhcp6) {
            message = "Missing mandatory 'Dhcp6' parameter.";
        } else if (dhcp6->getType() != Element::map) {
            message = "'Dhcp6' parameter expected to be a map.";
        }
    }

    // Check unsupported objects.
    if (message.empty()) {
        for (auto const& obj : args->mapValue()) {
            const string& obj_name = obj.first;
            if (obj_name != "Dhcp6") {
                LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_UNSUPPORTED_OBJECT)
                    .arg(obj_name);
                if (message.empty()) {
                    message = "Unsupported '" + obj_name + "' parameter";
                } else {
                    message += " (and '" + obj_name + "')";
                }
            }
        }
        if (!message.empty()) {
            message += ".";
        }
    }

    if (!message.empty()) {
        // Something is amiss with arguments, return a failure response.
        ConstElementPtr result = isc::config::createAnswer(status_code,
                                                           message);
        return (result);
    }

    ConstElementPtr lease_database = dhcp6->get("lease-database");
    if (lease_database) {
        db::DbAccessParser parser;
        std::string access_string;
        parser.parse(access_string, lease_database);
        auto params = parser.getDbAccessParameters();
        if (params["type"] == "memfile") {
            string file_name = params["name"];
            if (Memfile_LeaseMgr::isLFCProcessRunning(file_name, Memfile_LeaseMgr::V6)) {
                return (isc::config::createAnswer(CONTROL_RESULT_ERROR,
                        "Can not update configuration while lease file cleanup process is running."));
            }
        }
    }

    // stop thread pool (if running)
    MultiThreadingCriticalSection cs;

    // We are starting the configuration process so we should remove any
    // staging configuration that has been created during previous
    // configuration attempts.
    CfgMgr::instance().clearStagingConfiguration();

    // Parse the logger configuration explicitly into the staging config.
    // Note this does not alter the current loggers, they remain in
    // effect until we apply the logging config below.  If no logging
    // is supplied logging will revert to default logging.
    Daemon::configureLogger(dhcp6, CfgMgr::instance().getStagingCfg());

    // Let's apply the new logging. We do it early, so we'll be able to print
    // out what exactly is wrong with the new config in case of problems.
    CfgMgr::instance().getStagingCfg()->applyLoggingCfg();

    // Now we configure the server proper.
    ConstElementPtr result = processConfig(dhcp6);

    // If the configuration parsed successfully, apply the new logger
    // configuration and then commit the new configuration.  We apply
    // the logging first in case there's a configuration failure.
    int rcode = 0;
    isc::config::parseAnswer(rcode, result);
    if (rcode == CONTROL_RESULT_SUCCESS) {
        CfgMgr::instance().getStagingCfg()->applyLoggingCfg();

        // Use new configuration.
        CfgMgr::instance().commit();
    } else if (CfgMgr::instance().getCurrentCfg()->getSequence() != 0) {
        // Ok, we applied the logging from the upcoming configuration, but
        // there were problems with the config. As such, we need to back off
        // and revert to the previous logging configuration. This is not done if
        // sequence == 0, because that would mean always reverting to stdout by
        // default, and it is arguably more helpful to have the error in a
        // potential file or syslog configured in the upcoming configuration.
        CfgMgr::instance().getCurrentCfg()->applyLoggingCfg();

        // Not initial configuration so someone can believe we reverted
        // to the previous configuration. It is not the case so be clear
        // about this.
        LOG_FATAL(dhcp6_logger, DHCP6_CONFIG_UNRECOVERABLE_ERROR);
    }

    /// Let postponed hook initializations run.
    try {
        // Handle events registered by hooks using external IOService objects.
        IOServiceMgr::instance().pollIOServices();
    } catch (const std::exception& ex) {
        std::ostringstream err;
        err << "Error initializing hooks: "
            << ex.what();
        return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str()));
    }

    return (result);
}

ConstElementPtr
ControlledDhcpv6Srv::commandConfigTestHandler(const string&,
                                              ConstElementPtr args) {
    const int status_code = CONTROL_RESULT_ERROR; // 1 indicates an error
    ConstElementPtr dhcp6;
    string message;

    // Command arguments are expected to be:
    // { "Dhcp6": { ... } }
    if (!args) {
        message = "Missing mandatory 'arguments' parameter.";
    } else {
        dhcp6 = args->get("Dhcp6");
        if (!dhcp6) {
            message = "Missing mandatory 'Dhcp6' parameter.";
        } else if (dhcp6->getType() != Element::map) {
            message = "'Dhcp6' parameter expected to be a map.";
        }
    }

    // Check unsupported objects.
    if (message.empty()) {
        for (auto const& obj : args->mapValue()) {
            const string& obj_name = obj.first;
            if (obj_name != "Dhcp6") {
                LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_UNSUPPORTED_OBJECT)
                    .arg(obj_name);
                if (message.empty()) {
                    message = "Unsupported '" + obj_name + "' parameter";
                } else {
                    message += " (and '" + obj_name + "')";
                }
            }
        }
        if (!message.empty()) {
            message += ".";
        }
    }

    if (!message.empty()) {
        // Something is amiss with arguments, return a failure response.
        ConstElementPtr result = isc::config::createAnswer(status_code,
                                                           message);
        return (result);
    }

    // stop thread pool (if running)
    MultiThreadingCriticalSection cs;

    // We are starting the configuration process so we should remove any
    // staging configuration that has been created during previous
    // configuration attempts.
    CfgMgr::instance().clearStagingConfiguration();

    // Now we check the server proper.
    return (checkConfig(dhcp6));
}

ConstElementPtr
ControlledDhcpv6Srv::commandDhcpDisableHandler(const std::string&,
                                               ConstElementPtr args) {
    std::ostringstream message;
    int64_t max_period = 0;
    std::string origin;

    // If the args map does not contain 'origin' parameter, the default type
    // will be used (user command).
    auto type = NetworkState::USER_COMMAND;

    // Parse arguments to see if the 'max-period' or 'origin' parameters have
    // been specified.
    if (args) {
        // Arguments must be a map.
        if (args->getType() != Element::map) {
            message << "arguments for the 'dhcp-disable' command must be a map";

        } else {
            ConstElementPtr max_period_element = args->get("max-period");
            // max-period is optional.
            if (max_period_element) {
                // It must be an integer, if specified.
                if (max_period_element->getType() != Element::integer) {
                    message << "'max-period' argument must be a number";

                } else {
                    // It must be positive integer.
                    max_period = max_period_element->intValue();
                    if (max_period <= 0) {
                        message << "'max-period' must be positive integer";
                    }
                }
            }
            // 'origin-id' replaces the older parameter 'origin' since Kea 2.5.8
            // stable release. However, the 'origin' is kept for backward compatibility
            // with Kea versions before 2.5.8. It is common to receive both parameters
            // because HA hook library sends both in case the partner server hasn't been
            // upgraded to the new version. The 'origin-id' takes precedence over the
            // 'origin'.
            ConstElementPtr origin_id_element = args->get("origin-id");
            ConstElementPtr origin_element = args->get("origin");
            // The 'origin-id' and 'origin' arguments are optional.
            if (origin_id_element) {
                if (origin_id_element->getType() == Element::integer) {
                    type = origin_id_element->intValue();
                } else {
                    message << "'origin-id' argument must be a number";
                }
            } else if (origin_element) {
                switch (origin_element->getType()) {
                case Element::string:
                    origin = origin_element->stringValue();
                    if (origin == "ha-partner") {
                        type = NetworkState::HA_REMOTE_COMMAND;
                    } else if (origin != "user") {
                        if (origin.empty()) {
                            origin = "(empty string)";
                        }
                        message << "invalid value used for 'origin' parameter: "
                                << origin;
                    }
                    break;
                case Element::integer:
                    type = origin_element->intValue();
                    break;
                default:
                    // It must be a string or a number, if specified.
                    message << "'origin' argument must be a string or a number";
                }
            }
        }
    }

    // No error occurred, so let's disable the service.
    if (message.tellp() == 0) {
        message << "DHCPv6 service disabled";
        if (max_period > 0) {
            message << " for " << max_period << " seconds";

            // The user specified that the DHCP service should resume not
            // later than in max-period seconds. If the 'dhcp-enable' command
            // is not sent, the DHCP service will resume automatically.
            network_state_->delayedEnableService(static_cast<unsigned>(max_period),
                                                 type);
        }
        network_state_->disableService(type);

        // Success.
        return (config::createAnswer(CONTROL_RESULT_SUCCESS, message.str()));
    }

    // Failure.
    return (config::createAnswer(CONTROL_RESULT_ERROR, message.str()));
}

ConstElementPtr
ControlledDhcpv6Srv::commandDhcpEnableHandler(const std::string&,
                                              ConstElementPtr args) {
    std::ostringstream message;
    std::string origin;

    // If the args map does not contain 'origin' parameter, the default type
    // will be used (user command).
    auto type = NetworkState::USER_COMMAND;

    // Parse arguments to see if the 'origin' parameter has been specified.
    if (args) {
        // Arguments must be a map.
        if (args->getType() != Element::map) {
            message << "arguments for the 'dhcp-enable' command must be a map";

        } else {
            // 'origin-id' replaces the older parameter 'origin' since Kea 2.5.8
            // stable release. However, the 'origin' is kept for backward compatibility
            // with Kea versions before 2.5.8. It is common to receive both parameters
            // because HA hook library sends both in case the partner server hasn't been
            // upgraded to the new version. The 'origin-id' takes precedence over the
            // 'origin'.
            ConstElementPtr origin_id_element = args->get("origin-id");
            ConstElementPtr origin_element = args->get("origin");
            // The 'origin-id' and 'origin' arguments are optional.
            if (origin_id_element) {
                if (origin_id_element->getType() == Element::integer) {
                    type = origin_id_element->intValue();
                } else {
                    message << "'origin-id' argument must be a number";
                }
            } else if (origin_element) {
                switch (origin_element->getType()) {
                case Element::string:
                    origin = origin_element->stringValue();
                    if (origin == "ha-partner") {
                        type = NetworkState::HA_REMOTE_COMMAND;
                    } else if (origin != "user") {
                        if (origin.empty()) {
                            origin = "(empty string)";
                        }
                        message << "invalid value used for 'origin' parameter: "
                                << origin;
                    }
                    break;
                case Element::integer:
                    type = origin_element->intValue();
                    break;
                default:
                    // It must be a string or a number, if specified.
                    message << "'origin' argument must be a string or a number";
                }
            }
        }
    }

    // No error occurred, so let's enable the service.
    if (message.tellp() == 0) {
        network_state_->enableService(type);

        // Success.
        return (config::createAnswer(CONTROL_RESULT_SUCCESS,
                                     "DHCP service successfully enabled"));
    }

    // Failure.
    return (config::createAnswer(CONTROL_RESULT_ERROR, message.str()));
}

ConstElementPtr
ControlledDhcpv6Srv::commandVersionGetHandler(const string&, ConstElementPtr) {
    ElementPtr extended = Element::create(Dhcpv6Srv::getVersion(true));
    ElementPtr arguments = Element::createMap();
    arguments->set("extended", extended);
    ConstElementPtr answer = isc::config::createAnswer(CONTROL_RESULT_SUCCESS,
                                Dhcpv6Srv::getVersion(false),
                                arguments);
    return (answer);
}

ConstElementPtr
ControlledDhcpv6Srv::commandBuildReportHandler(const string&,
                                               ConstElementPtr) {
    ConstElementPtr answer =
        isc::config::createAnswer(CONTROL_RESULT_SUCCESS, isc::detail::getConfigReport());
    return (answer);
}

ConstElementPtr
ControlledDhcpv6Srv::commandLeasesReclaimHandler(const string&,
                                                 ConstElementPtr args) {
    int status_code = CONTROL_RESULT_ERROR;
    string message;

    // args must be { "remove": <bool> }
    if (!args) {
        message = "Missing mandatory 'remove' parameter.";
    } else {
        ConstElementPtr remove_name = args->get("remove");
        if (!remove_name) {
            message = "Missing mandatory 'remove' parameter.";
        } else if (remove_name->getType() != Element::boolean) {
            message = "'remove' parameter expected to be a boolean.";
        } else {
            bool remove_lease = remove_name->boolValue();
            server_->alloc_engine_->reclaimExpiredLeases6(0, 0, remove_lease);
            status_code = 0;
            message = "Reclamation of expired leases is complete.";
        }
    }
    ConstElementPtr answer = isc::config::createAnswer(status_code, message);
    return (answer);
}

ConstElementPtr
ControlledDhcpv6Srv::commandSubnet6SelectTestHandler(const string&,
                                                     ConstElementPtr args) {
    if (!args) {
        return (createAnswer(CONTROL_RESULT_ERROR, "empty arguments"));
    }
    if (args->getType() != Element::map) {
        return (createAnswer(CONTROL_RESULT_ERROR, "arguments must be a map"));
    }
    SubnetSelector selector;
    selector.remote_address_ = IOAddress::IPV6_ZERO_ADDRESS();
    for (auto const& entry : args->mapValue()) {
        ostringstream errmsg;
        if (entry.first == "interface") {
            if (entry.second->getType() != Element::string) {
                errmsg << "'interface' entry must be a string";
                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
            }
            selector.iface_name_ = entry.second->stringValue();
            continue;
        } if (entry.first == "interface-id") {
            if (entry.second->getType() != Element::string) {
                errmsg << "'interface-id' entry must be a string";
                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
            }
            try {
                string str = entry.second->stringValue();
                vector<uint8_t> id = util::str::quotedStringToBinary(str);
                if (id.empty()) {
                    util::str::decodeFormattedHexString(str, id);
                }
                if (id.empty()) {
                    errmsg << "'interface-id' must be not empty";
                    return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
                }
                selector.interface_id_ = OptionPtr(new Option(Option::V6,
                                                              D6O_INTERFACE_ID,
                                                              id));
                continue;
            } catch (...) {
                errmsg << "value of 'interface-id' was not recognized";
                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
            }
        } else if (entry.first == "remote") {
            if (entry.second->getType() != Element::string) {
                errmsg << "'remote' entry must be a string";
                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
            }
            try {
                IOAddress addr(entry.second->stringValue());
                if (!addr.isV6()) {
                    errmsg << "bad 'remote' entry: not IPv6";
                    return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
                }
                selector.remote_address_ = addr;
                continue;
            } catch (const exception& ex) {
                errmsg << "bad 'remote' entry: " << ex.what();
                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
            }
        } else if (entry.first == "link") {
            if (entry.second->getType() != Element::string) {
                errmsg << "'link' entry must be a string";
                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
            }
            try {
                IOAddress addr(entry.second->stringValue());
                if (!addr.isV6()) {
                    errmsg << "bad 'link' entry: not IPv6";
                    return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
                }
                selector.first_relay_linkaddr_ = addr;
                continue;
            } catch (const exception& ex) {
                errmsg << "bad 'link' entry: " << ex.what();
                return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
            }
        } else if (entry.first == "classes") {
            if (entry.second->getType() != Element::list) {
                return (createAnswer(CONTROL_RESULT_ERROR,
                                     "'classes' entry must be a list"));
            }
            for (auto const& item : entry.second->listValue()) {
                if (!item || (item->getType() != Element::string)) {
                    errmsg << "'classes' entry must be a list of strings";
                    return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
                }
                // Skip empty client classes.
                if (!item->stringValue().empty()) {
                    selector.client_classes_.insert(item->stringValue());
                }
            }
            continue;
        } else {
            errmsg << "unknown entry '" << entry.first << "'";
            return (createAnswer(CONTROL_RESULT_ERROR, errmsg.str()));
        }
    }
    ConstSubnet6Ptr subnet = CfgMgr::instance().getCurrentCfg()->
        getCfgSubnets6()->selectSubnet(selector);
    if (!subnet) {
        return (createAnswer(CONTROL_RESULT_EMPTY, "no subnet selected"));
    }
    SharedNetwork6Ptr network;
    subnet->getSharedNetwork(network);
    ostringstream msg;
    if (network) {
        msg << "selected shared network '" << network->getName()
            << "' starting with subnet '" << subnet->toText()
            << "' id " << subnet->getID();
    } else {
        msg << "selected subnet '" << subnet->toText()
            << "' id " << subnet->getID();
    }
    return (createAnswer(CONTROL_RESULT_SUCCESS, msg.str()));
}

ConstElementPtr
ControlledDhcpv6Srv::commandServerTagGetHandler(const std::string&,
                                                ConstElementPtr) {
    const std::string& tag =
        CfgMgr::instance().getCurrentCfg()->getServerTag();
    ElementPtr response = Element::createMap();
    response->set("server-tag", Element::create(tag));

    return (createAnswer(CONTROL_RESULT_SUCCESS, response));
}

ConstElementPtr
ControlledDhcpv6Srv::commandConfigBackendPullHandler(const std::string&,
                                                     ConstElementPtr) {
    auto ctl_info = CfgMgr::instance().getCurrentCfg()->getConfigControlInfo();
    if (!ctl_info) {
        return (createAnswer(CONTROL_RESULT_EMPTY, "No config backend."));
    }

    // stop thread pool (if running)
    MultiThreadingCriticalSection cs;

    // Reschedule the periodic CB fetch.
    if (TimerMgr::instance()->isTimerRegistered("Dhcp6CBFetchTimer")) {
        TimerMgr::instance()->cancel("Dhcp6CBFetchTimer");
        TimerMgr::instance()->setup("Dhcp6CBFetchTimer");
    }

    // Code from cbFetchUpdates.
    // The configuration to use is the current one because this is called
    // after the configuration manager commit.
    try {
        auto srv_cfg = CfgMgr::instance().getCurrentCfg();
        auto mode = CBControlDHCPv6::FetchMode::FETCH_UPDATE;
        server_->getCBControl()->databaseConfigFetch(srv_cfg, mode);
    } catch (const std::exception& ex) {
        LOG_ERROR(dhcp6_logger, DHCP6_CB_ON_DEMAND_FETCH_UPDATES_FAIL)
            .arg(ex.what());
        return (createAnswer(CONTROL_RESULT_ERROR,
                             "On demand configuration update failed: " +
                             string(ex.what())));
    }
    return (createAnswer(CONTROL_RESULT_SUCCESS,
                         "On demand configuration update successful."));
}

ConstElementPtr
ControlledDhcpv6Srv::commandStatusGetHandler(const string&,
                                             ConstElementPtr /*args*/) {
    ElementPtr status = Element::createMap();
    status->set("pid", Element::create(static_cast<int>(getpid())));

    auto now = boost::posix_time::second_clock::universal_time();
    // Sanity check: start_ is always initialized.
    if (!start_.is_not_a_date_time()) {
        auto uptime = now - start_;
        status->set("uptime", Element::create(uptime.total_seconds()));
    }

    auto last_commit = CfgMgr::instance().getCurrentCfg()->getLastCommitTime();
    if (!last_commit.is_not_a_date_time()) {
        auto reload = now - last_commit;
        status->set("reload", Element::create(reload.total_seconds()));
    }

    auto& mt_mgr = MultiThreadingMgr::instance();
    if (mt_mgr.getMode()) {
        status->set("multi-threading-enabled", Element::create(true));
        status->set("thread-pool-size", Element::create(static_cast<int32_t>(
                        MultiThreadingMgr::instance().getThreadPoolSize())));
        status->set("packet-queue-size", Element::create(static_cast<int32_t>(
                        MultiThreadingMgr::instance().getPacketQueueSize())));
        ElementPtr queue_stats = Element::createList();
        queue_stats->add(Element::create(mt_mgr.getThreadPool().getQueueStat(10)));
        queue_stats->add(Element::create(mt_mgr.getThreadPool().getQueueStat(100)));
        queue_stats->add(Element::create(mt_mgr.getThreadPool().getQueueStat(1000)));
        status->set("packet-queue-statistics", queue_stats);

    } else {
        status->set("multi-threading-enabled", Element::create(false));
    }

    // Merge lease manager status.
    ElementPtr lm_info;
    if (LeaseMgrFactory::haveInstance()) {
        lm_info = LeaseMgrFactory::instance().getStatus();
    }
    if (lm_info && (lm_info->getType() == Element::map)) {
        for (auto const& entry : lm_info->mapValue()) {
            status->set(entry.first, entry.second);
        }
    }

    status->set("extended-info-tables", Element::create(
                    CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->getExtendedInfoTablesEnabled()));

    // Iterate through the interfaces and get all the errors.
    ElementPtr socket_errors(Element::createList());
    for (IfacePtr const& interface : IfaceMgr::instance().getIfaces()) {
        for (std::string const& error : interface->getErrors()) {
            socket_errors->add(Element::create(error));
        }
    }

    // Abstract the information from all sockets into a single status.
    ElementPtr sockets(Element::createMap());
    if (socket_errors->empty()) {
        sockets->set("status", Element::create("ready"));
    } else {
        ReconnectCtlPtr const reconnect_ctl(
            CfgMgr::instance().getCurrentCfg()->getCfgIface()->getReconnectCtl());
        if (reconnect_ctl && reconnect_ctl->retriesLeft()) {
            sockets->set("status", Element::create("retrying"));
        } else {
            sockets->set("status", Element::create("failed"));
        }
        sockets->set("errors", socket_errors);
    }
    status->set("sockets", sockets);

    status->set("dhcp-state", network_state_->toElement());

    return (createAnswer(CONTROL_RESULT_SUCCESS, status));
}

ConstElementPtr
ControlledDhcpv6Srv::commandStatisticSetMaxSampleCountAllHandler(const string&,
                                                                 ConstElementPtr args) {
    StatsMgr& stats_mgr = StatsMgr::instance();
    ConstElementPtr answer = stats_mgr.statisticSetMaxSampleCountAllHandler(args);
    // Update the default parameter.
    long max_samples = stats_mgr.getMaxSampleCountDefault();
    CfgMgr::instance().getCurrentCfg()->addConfiguredGlobal(
        "statistic-default-sample-count", Element::create(max_samples));
    return (answer);
}

ConstElementPtr
ControlledDhcpv6Srv::commandStatisticSetMaxSampleAgeAllHandler(const string&,
                                                               ConstElementPtr args) {
    StatsMgr& stats_mgr = StatsMgr::instance();
    ConstElementPtr answer = stats_mgr.statisticSetMaxSampleAgeAllHandler(args);
    // Update the default parameter.
    auto duration = stats_mgr.getMaxSampleAgeDefault();
    long max_age = toSeconds(duration);
    CfgMgr::instance().getCurrentCfg()->addConfiguredGlobal(
        "statistic-default-sample-age", Element::create(max_age));
    return (answer);
}

ConstElementPtr
ControlledDhcpv6Srv::commandLfcStartHandler(const string&, ConstElementPtr) {
    if (LeaseMgrFactory::haveInstance()) {
        return (LeaseMgrFactory::instance().lfcStartHandler());
    }
    return (createAnswer(CONTROL_RESULT_COMMAND_UNSUPPORTED,
                         "no lease backend"));
}

isc::data::ConstElementPtr
ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) {
    ControlledDhcpv6Srv* srv = ControlledDhcpv6Srv::getInstance();

    // Allow DB reconnect on startup. The database connection parameters specify
    // respective details.
    DbConnectionInitWithRetry retry;

    // Single stream instance used in all error clauses
    std::ostringstream err;

    if (!srv) {
        err << "Server object not initialized, can't process config.";
        return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str()));
    }

    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_RECEIVED)
        .arg(srv->redactConfig(config)->str());

    // Destroy lease manager before hooks unload.
    LeaseMgrFactory::destroy();

    // Destroy host manager before hooks unload.
    HostMgr::create();

    ConstElementPtr answer = configureDhcp6Server(*srv, config);

    // Check that configuration was successful. If not, do not reopen sockets
    // and don't bother with DDNS stuff.
    try {
        int rcode = 0;
        isc::config::parseAnswer(rcode, answer);
        if (rcode != 0) {
            return (answer);
        }
    } catch (const std::exception& ex) {
        err << "Failed to process configuration:" << ex.what();
        return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str()));
    }

    // Re-open lease and host database with new parameters.
    try {
        DatabaseConnection::db_lost_callback_ =
            std::bind(&ControlledDhcpv6Srv::dbLostCallback, srv, ph::_1);

        DatabaseConnection::db_recovered_callback_ =
            std::bind(&ControlledDhcpv6Srv::dbRecoveredCallback, srv, ph::_1);

        DatabaseConnection::db_failed_callback_ =
            std::bind(&ControlledDhcpv6Srv::dbFailedCallback, srv, ph::_1);

        CfgDbAccessPtr cfg_db = CfgMgr::instance().getStagingCfg()->getCfgDbAccess();
        string params = "universe=6";
        if (cfg_db->getExtendedInfoTablesEnabled()) {
            params += " extended-info-tables=true";
        }
        cfg_db->setAppendedParameters(params);
        cfg_db->createManagers();
        // Reset counters related to connections as all managers have been recreated.
        srv->getNetworkState()->resetForDbConnection();
        srv->getNetworkState()->resetForLocalCommands();
        srv->getNetworkState()->resetForRemoteCommands();
    } catch (const std::exception& ex) {
        err << "Unable to open database: " << ex.what();
        return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str()));
    }

    // Regenerate server identifier if needed.
    try {
        const std::string duid_file =
            std::string(CfgMgr::instance().getDataDir()) + "/" +
            std::string(SERVER_DUID_FILE);
        DuidPtr duid = CfgMgr::instance().getStagingCfg()->getCfgDUID()->create(duid_file);
        server_->serverid_.reset(new Option(Option::V6, D6O_SERVERID, duid->getDuid()));
        if (duid) {
            LOG_INFO(dhcp6_logger, DHCP6_USING_SERVERID)
                .arg(duid->toText())
                .arg(duid_file);
        }

    } catch (const std::exception& ex) {
        err << "unable to configure server identifier: " << ex.what();
        return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str()));
    }

    // Server will start DDNS communications if its enabled.
    try {
        srv->startD2();
    } catch (const std::exception& ex) {
        err << "Error starting DHCP_DDNS client after server reconfiguration: "
            << ex.what();
        return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str()));
    }

    // Setup DHCPv4-over-DHCPv6 IPC
    try {
        Dhcp6to4Ipc::instance().open();
    } catch (const std::exception& ex) {
        err << "error starting DHCPv4-over-DHCPv6 IPC "
               " after server reconfiguration: " << ex.what();
        return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str()));
    }

    // Configure DHCP packet queueing
    try {
        data::ConstElementPtr qc;
        qc = CfgMgr::instance().getStagingCfg()->getDHCPQueueControl();
        if (IfaceMgr::instance().configureDHCPPacketQueue(AF_INET6, qc)) {
            LOG_INFO(dhcp6_logger, DHCP6_CONFIG_PACKET_QUEUE)
                     .arg(IfaceMgr::instance().getPacketQueue6()->getInfoStr());
        }

    } catch (const std::exception& ex) {
        err << "Error setting packet queue controls after server reconfiguration: "
            << ex.what();
        return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str()));
    }

    // Configure a callback to shut down the server when the bind socket
    // attempts exceeded.
    CfgIface::open_sockets_failed_callback_ =
        std::bind(&ControlledDhcpv6Srv::openSocketsFailedCallback, srv, ph::_1);

    // Configuration may change active interfaces. Therefore, we have to reopen
    // sockets according to new configuration. It is possible that this
    // operation will fail for some interfaces but the openSockets function
    // guards against exceptions and invokes a callback function to
    // log warnings. Since we allow that this fails for some interfaces there
    // is no need to rollback configuration if socket fails to open on any
    // of the interfaces.
    CfgMgr::instance().getStagingCfg()->getCfgIface()->
        openSockets(AF_INET6, srv->getServerPort());

    // Install the timers for handling leases reclamation.
    try {
        CfgMgr::instance().getStagingCfg()->getCfgExpiration()->
            setupTimers(&ControlledDhcpv6Srv::reclaimExpiredLeases,
                        &ControlledDhcpv6Srv::deleteExpiredReclaimedLeases,
                        server_);

    } catch (const std::exception& ex) {
        err << "unable to setup timers for periodically running the"
            " reclamation of the expired leases: "
            << ex.what() << ".";
        return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str()));
    }

    // Setup config backend polling, if configured for it.
    auto ctl_info = CfgMgr::instance().getStagingCfg()->getConfigControlInfo();
    if (ctl_info) {
        long fetch_time = static_cast<long>(ctl_info->getConfigFetchWaitTime());
        // Only schedule the CB fetch timer if the fetch wait time is greater
        // than 0.
        if (fetch_time > 0) {
            // When we run unit tests, we want to use milliseconds unit for the
            // specified interval. Otherwise, we use seconds. Note that using
            // milliseconds as a unit in unit tests prevents us from waiting 1
            // second on more before the timer goes off. Instead, we wait one
            // millisecond which significantly reduces the test time.
            if (!server_->inTestMode()) {
                fetch_time = 1000 * fetch_time;
            }

            boost::shared_ptr<unsigned> failure_count(new unsigned(0));
            TimerMgr::instance()->
                registerTimer("Dhcp6CBFetchTimer",
                              std::bind(&ControlledDhcpv6Srv::cbFetchUpdates,
                                        server_, CfgMgr::instance().getStagingCfg(),
                                        failure_count),
                              fetch_time,
                              asiolink::IntervalTimer::ONE_SHOT);
            TimerMgr::instance()->setup("Dhcp6CBFetchTimer");
        }
    }

    // Finally, we can commit runtime option definitions in libdhcp++. This is
    // exception free.
    LibDHCP::commitRuntimeOptionDefs();

    auto notify_libraries = ControlledDhcpv6Srv::finishConfigHookLibraries(config);
    if (notify_libraries) {
        return (notify_libraries);
    }

    // Initialize the allocators. If the user selected a Free Lease Queue Allocator
    // for any of the subnets, the server will now populate free leases to the queue.
    // It may take a while!
    try {
        CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->initAllocatorsAfterConfigure();

    } catch (const std::exception& ex) {
        err << "Error initializing the lease allocators: "
            << ex.what();
        return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str()));
    }

    // Apply multi threading settings.
    // @note These settings are applied/updated only if no errors occur while
    // applying the new configuration.
    // @todo This should be fixed.
    try {
        CfgMultiThreading::apply(CfgMgr::instance().getStagingCfg()->getDHCPMultiThreading());
    } catch (const std::exception& ex) {
        err << "Error applying multi threading settings: "
            << ex.what();
        return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str()));
    }

    return (answer);
}

isc::data::ConstElementPtr
ControlledDhcpv6Srv::finishConfigHookLibraries(isc::data::ConstElementPtr config) {
    ControlledDhcpv6Srv* srv = ControlledDhcpv6Srv::getInstance();
    // This hook point notifies hooks libraries that the configuration of the
    // DHCPv6 server has completed. It provides the hook library with the pointer
    // to the common IO service object, new server configuration in the JSON
    // format and with the pointer to the configuration storage where the
    // parsed configuration is stored.
    if (HooksManager::calloutsPresent(Hooks.hooks_index_dhcp6_srv_configured_)) {
        CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();

        callout_handle->setArgument("io_context", srv->getIOService());
        callout_handle->setArgument("network_state", srv->getNetworkState());
        callout_handle->setArgument("json_config", config);
        callout_handle->setArgument("server_config", CfgMgr::instance().getStagingCfg());

        HooksManager::callCallouts(Hooks.hooks_index_dhcp6_srv_configured_,
                                   *callout_handle);

        // If next step is DROP, report a configuration error.
        if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
            string error;
            try {
                callout_handle->getArgument("error", error);
            } catch (NoSuchArgument const& ex) {
                error = "unknown error";
            }
            return (isc::config::createAnswer(CONTROL_RESULT_ERROR, error));
        }
    }

    return (ConstElementPtr());
}

isc::data::ConstElementPtr
ControlledDhcpv6Srv::checkConfig(isc::data::ConstElementPtr config) {
    ControlledDhcpv6Srv* srv = ControlledDhcpv6Srv::getInstance();

    if (!srv) {
        ConstElementPtr no_srv = isc::config::createAnswer(CONTROL_RESULT_ERROR,
            "Server object not initialized, can't process config.");
        return (no_srv);
    }

    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_RECEIVED)
        .arg(srv->redactConfig(config)->str());

    return (configureDhcp6Server(*srv, config, true));
}

ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t server_port /*= DHCP6_SERVER_PORT*/,
                                         uint16_t client_port /*= 0*/)
    : Dhcpv6Srv(server_port, client_port), timer_mgr_(TimerMgr::instance()) {
    if (getInstance()) {
        isc_throw(InvalidOperation,
                  "There is another Dhcpv6Srv instance already.");
    }
    server_ = this; // remember this instance for later use in handlers

    // ProcessSpawn uses IO service to handle signal set events.
    ProcessSpawn::setIOService(getIOService());

    // TimerMgr uses IO service to run asynchronous timers.
    TimerMgr::instance()->setIOService(getIOService());

    // Command managers use IO service to run asynchronous socket operations.
    UnixCommandMgr::instance().setIOService(getIOService());
    HttpCommandMgr::instance().setIOService(getIOService());

    // Set the HTTP default socket address to the IPv6 (vs IPv4) loopback.
    HttpCommandConfig::DEFAULT_SOCKET_ADDRESS = IOAddress("::1");

    // Set the HTTP authentication default realm.
    HttpCommandConfig::DEFAULT_AUTHENTICATION_REALM = "kea-dhcpv6-server";

    // DatabaseConnection uses IO service to run asynchronous timers.
    DatabaseConnection::setIOService(getIOService());

    // These are the commands always supported by the DHCPv6 server.
    // Please keep the list in alphabetic order.
    CommandMgr::instance().registerCommand("build-report",
        std::bind(&ControlledDhcpv6Srv::commandBuildReportHandler, this, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("config-backend-pull",
        std::bind(&ControlledDhcpv6Srv::commandConfigBackendPullHandler, this, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("config-get",
        std::bind(&ControlledDhcpv6Srv::commandConfigGetHandler, this, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("config-hash-get",
        std::bind(&ControlledDhcpv6Srv::commandConfigHashGetHandler, this, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("config-reload",
        std::bind(&ControlledDhcpv6Srv::commandConfigReloadHandler, this, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("config-set",
        std::bind(&ControlledDhcpv6Srv::commandConfigSetHandler, this, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("config-test",
        std::bind(&ControlledDhcpv6Srv::commandConfigTestHandler, this, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("config-write",
        std::bind(&ControlledDhcpv6Srv::commandConfigWriteHandler, this, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("dhcp-enable",
        std::bind(&ControlledDhcpv6Srv::commandDhcpEnableHandler, this, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("dhcp-disable",
        std::bind(&ControlledDhcpv6Srv::commandDhcpDisableHandler, this, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("kea-lfc-start",
        std::bind(&ControlledDhcpv6Srv::commandLfcStartHandler, this, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("leases-reclaim",
        std::bind(&ControlledDhcpv6Srv::commandLeasesReclaimHandler, this, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("subnet6-select-test",
        std::bind(&ControlledDhcpv6Srv::commandSubnet6SelectTestHandler, this, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("server-tag-get",
        std::bind(&ControlledDhcpv6Srv::commandServerTagGetHandler, this, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("shutdown",
        std::bind(&ControlledDhcpv6Srv::commandShutdownHandler, this, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("status-get",
        std::bind(&ControlledDhcpv6Srv::commandStatusGetHandler, this, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("version-get",
        std::bind(&ControlledDhcpv6Srv::commandVersionGetHandler, this, ph::_1, ph::_2));

    // Register statistic related commands
    CommandMgr::instance().registerCommand("statistic-get",
        std::bind(&StatsMgr::statisticGetHandler, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("statistic-reset",
        std::bind(&StatsMgr::statisticResetHandler, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("statistic-remove",
        std::bind(&StatsMgr::statisticRemoveHandler, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("statistic-get-all",
        std::bind(&StatsMgr::statisticGetAllHandler, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("statistic-global-get-all",
        std::bind(&StatsMgr::statisticGlobalGetAllHandler, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("statistic-reset-all",
        std::bind(&StatsMgr::statisticResetAllHandler, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("statistic-remove-all",
        std::bind(&StatsMgr::statisticRemoveAllHandler, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("statistic-sample-age-set",
        std::bind(&StatsMgr::statisticSetMaxSampleAgeHandler, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("statistic-sample-age-set-all",
        std::bind(&ControlledDhcpv6Srv::commandStatisticSetMaxSampleAgeAllHandler, this, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("statistic-sample-count-set",
        std::bind(&StatsMgr::statisticSetMaxSampleCountHandler, ph::_1, ph::_2));

    CommandMgr::instance().registerCommand("statistic-sample-count-set-all",
        std::bind(&ControlledDhcpv6Srv::commandStatisticSetMaxSampleCountAllHandler, this, ph::_1, ph::_2));
}

void ControlledDhcpv6Srv::shutdownServer(int exit_value) {
    setExitValue(exit_value);
    getIOService()->stop();   // Stop ASIO transmissions
    shutdown();               // Initiate DHCPv6 shutdown procedure.
}

ControlledDhcpv6Srv::~ControlledDhcpv6Srv() {
    try {
        MultiThreadingMgr::instance().apply(false, 0, 0);
        LeaseMgrFactory::destroy();
        HostMgr::create();

        // The closure captures either a shared pointer (memory leak)
        // or a raw pointer (pointing to a deleted object).
        DatabaseConnection::db_lost_callback_ = 0;
        DatabaseConnection::db_recovered_callback_ = 0;
        DatabaseConnection::db_failed_callback_ = 0;

        timer_mgr_->unregisterTimers();

        cleanup();

        // Close command sockets.
        UnixCommandMgr::instance().closeCommandSockets();
        HttpCommandMgr::instance().closeCommandSockets();

        // Deregister any registered commands (please keep in alphabetic order)
        CommandMgr::instance().deregisterCommand("build-report");
        CommandMgr::instance().deregisterCommand("config-backend-pull");
        CommandMgr::instance().deregisterCommand("config-get");
        CommandMgr::instance().deregisterCommand("config-hash-get");
        CommandMgr::instance().deregisterCommand("config-reload");
        CommandMgr::instance().deregisterCommand("config-set");
        CommandMgr::instance().deregisterCommand("config-test");
        CommandMgr::instance().deregisterCommand("config-write");
        CommandMgr::instance().deregisterCommand("dhcp-disable");
        CommandMgr::instance().deregisterCommand("dhcp-enable");
        CommandMgr::instance().deregisterCommand("kea-lfc-start");
        CommandMgr::instance().deregisterCommand("leases-reclaim");
        CommandMgr::instance().deregisterCommand("subnet6-select-test");
        CommandMgr::instance().deregisterCommand("server-tag-get");
        CommandMgr::instance().deregisterCommand("shutdown");
        CommandMgr::instance().deregisterCommand("statistic-get");
        CommandMgr::instance().deregisterCommand("statistic-get-all");
        CommandMgr::instance().deregisterCommand("statistic-global-get-all");
        CommandMgr::instance().deregisterCommand("statistic-remove");
        CommandMgr::instance().deregisterCommand("statistic-remove-all");
        CommandMgr::instance().deregisterCommand("statistic-reset");
        CommandMgr::instance().deregisterCommand("statistic-reset-all");
        CommandMgr::instance().deregisterCommand("statistic-sample-age-set");
        CommandMgr::instance().deregisterCommand("statistic-sample-age-set-all");
        CommandMgr::instance().deregisterCommand("statistic-sample-count-set");
        CommandMgr::instance().deregisterCommand("statistic-sample-count-set-all");
        CommandMgr::instance().deregisterCommand("status-get");
        CommandMgr::instance().deregisterCommand("version-get");

        // Reset DatabaseConnection IO service.
        DatabaseConnection::setIOService(IOServicePtr());
    } catch (...) {
        // Don't want to throw exceptions from the destructor. The server
        // is shutting down anyway.
    }

    server_ = NULL; // forget this instance. There should be no callback anymore
                    // at this stage anyway.
}

void
ControlledDhcpv6Srv::reclaimExpiredLeases(const size_t max_leases,
                                          const uint16_t timeout,
                                          const bool remove_lease,
                                          const uint16_t max_unwarned_cycles) {
    try {
        if (network_state_->isServiceEnabled()) {
            server_->alloc_engine_->reclaimExpiredLeases6(max_leases, timeout,
                                                          remove_lease,
                                                          max_unwarned_cycles);
        } else {
            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_RECLAIM_EXPIRED_LEASES_SKIPPED)
                .arg(CfgMgr::instance().getCurrentCfg()->
                     getCfgExpiration()->getReclaimTimerWaitTime());
        }
    } catch (const std::exception& ex) {
        LOG_ERROR(dhcp6_logger, DHCP6_RECLAIM_EXPIRED_LEASES_FAIL)
            .arg(ex.what());
    }
    // We're using the ONE_SHOT timer so there is a need to re-schedule it.
    TimerMgr::instance()->setup(CfgExpiration::RECLAIM_EXPIRED_TIMER_NAME);
}

void
ControlledDhcpv6Srv::deleteExpiredReclaimedLeases(const uint32_t secs) {
    if (network_state_->isServiceEnabled()) {
        server_->alloc_engine_->deleteExpiredReclaimedLeases6(secs);
    }

    // We're using the ONE_SHOT timer so there is a need to re-schedule it.
    TimerMgr::instance()->setup(CfgExpiration::FLUSH_RECLAIMED_TIMER_NAME);
}

bool
ControlledDhcpv6Srv::dbLostCallback(ReconnectCtlPtr db_reconnect_ctl) {
    if (!db_reconnect_ctl) {
        // This should never happen
        LOG_ERROR(dhcp6_logger, DHCP6_DB_RECONNECT_NO_DB_CTL);
        return (false);
    }

    // Disable service until the connection is recovered.
    if (db_reconnect_ctl->retriesLeft() == db_reconnect_ctl->maxRetries() &&
        db_reconnect_ctl->alterServiceState()) {
        network_state_->disableService(NetworkState::DB_CONNECTION + db_reconnect_ctl->id());
    }

    LOG_INFO(dhcp6_logger, DHCP6_DB_RECONNECT_LOST_CONNECTION)
        .arg(db_reconnect_ctl->id())
        .arg(db_reconnect_ctl->timerName());

    // If reconnect isn't enabled log it, initiate a shutdown if needed and
    // return false.
    if (!db_reconnect_ctl->retriesLeft() ||
        !db_reconnect_ctl->retryInterval()) {
        LOG_INFO(dhcp6_logger, DHCP6_DB_RECONNECT_DISABLED)
            .arg(db_reconnect_ctl->retriesLeft())
            .arg(db_reconnect_ctl->retryInterval())
            .arg(db_reconnect_ctl->id())
            .arg(db_reconnect_ctl->timerName());
        if (db_reconnect_ctl->exitOnFailure()) {
            shutdownServer(EXIT_FAILURE);
        }
        return (false);
    }

    return (true);
}

bool
ControlledDhcpv6Srv::dbRecoveredCallback(ReconnectCtlPtr db_reconnect_ctl) {
    if (!db_reconnect_ctl) {
        // This should never happen
        LOG_ERROR(dhcp6_logger, DHCP6_DB_RECONNECT_NO_DB_CTL);
        return (false);
    }

    // Enable service after the connection is recovered.
    if (db_reconnect_ctl->retriesLeft() != db_reconnect_ctl->maxRetries() &&
        db_reconnect_ctl->alterServiceState()) {
        network_state_->enableService(NetworkState::DB_CONNECTION + db_reconnect_ctl->id());
    }

    LOG_INFO(dhcp6_logger, DHCP6_DB_RECONNECT_SUCCEEDED)
        .arg(db_reconnect_ctl->id())
        .arg(db_reconnect_ctl->timerName());

    db_reconnect_ctl->resetRetries();

    return (true);
}

bool
ControlledDhcpv6Srv::dbFailedCallback(ReconnectCtlPtr db_reconnect_ctl) {
    if (!db_reconnect_ctl) {
        // This should never happen
        LOG_ERROR(dhcp6_logger, DHCP6_DB_RECONNECT_NO_DB_CTL);
        return (false);
    }

    LOG_INFO(dhcp6_logger, DHCP6_DB_RECONNECT_FAILED)
        .arg(db_reconnect_ctl->maxRetries())
        .arg(db_reconnect_ctl->id())
        .arg(db_reconnect_ctl->timerName());

    if (db_reconnect_ctl->exitOnFailure()) {
        shutdownServer(EXIT_FAILURE);
    }

    return (true);
}

void
ControlledDhcpv6Srv::openSocketsFailedCallback(ReconnectCtlPtr reconnect_ctl) {
    if (!reconnect_ctl) {
        // This should never happen
        LOG_ERROR(dhcp6_logger, DHCP6_OPEN_SOCKETS_NO_RECONNECT_CTL);
        return;
    }

    LOG_INFO(dhcp6_logger, DHCP6_OPEN_SOCKETS_FAILED)
            .arg(reconnect_ctl->maxRetries());

    if (reconnect_ctl->exitOnFailure()) {
        shutdownServer(EXIT_FAILURE);
    }
}

void
ControlledDhcpv6Srv::cbFetchUpdates(const SrvConfigPtr& srv_cfg,
                                    boost::shared_ptr<unsigned> failure_count) {
    // stop thread pool (if running)
    MultiThreadingCriticalSection cs;

    try {
        // Fetch any configuration backend updates since our last fetch.
        server_->getCBControl()->databaseConfigFetch(srv_cfg,
                                                     CBControlDHCPv6::FetchMode::FETCH_UPDATE);
        (*failure_count) = 0;

    } catch (const std::exception& ex) {
        LOG_ERROR(dhcp6_logger, DHCP6_CB_PERIODIC_FETCH_UPDATES_FAIL)
            .arg(ex.what());

        // We allow at most 10 consecutive failures after which we stop
        // making further attempts to fetch the configuration updates.
        // Let's return without re-scheduling the timer.
        if (++(*failure_count) > 10) {
            LOG_ERROR(dhcp6_logger,
                      DHCP6_CB_PERIODIC_FETCH_UPDATES_RETRIES_EXHAUSTED);
            return;
        }
    }

    // Reschedule the timer to fetch new updates or re-try if
    // the previous attempt resulted in an error.
    if (TimerMgr::instance()->isTimerRegistered("Dhcp6CBFetchTimer")) {
        TimerMgr::instance()->setup("Dhcp6CBFetchTimer");
    }
}

}  // namespace dhcp
}  // namespace isc
