diff options
Diffstat (limited to 'src/lib/cc/command_interpreter.cc')
-rw-r--r-- | src/lib/cc/command_interpreter.cc | 318 |
1 files changed, 318 insertions, 0 deletions
diff --git a/src/lib/cc/command_interpreter.cc b/src/lib/cc/command_interpreter.cc new file mode 100644 index 0000000..0ea03bc --- /dev/null +++ b/src/lib/cc/command_interpreter.cc @@ -0,0 +1,318 @@ +// Copyright (C) 2009-2023 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 <exceptions/exceptions.h> +#include <cc/command_interpreter.h> +#include <cc/data.h> +#include <string> +#include <set> + +using namespace std; + +using isc::data::Element; +using isc::data::ConstElementPtr; +using isc::data::ElementPtr; +using isc::data::JSONError; + +namespace isc { +namespace config { + +const char *CONTROL_COMMAND = "command"; +const char *CONTROL_RESULT = "result"; +const char *CONTROL_TEXT = "text"; +const char *CONTROL_ARGUMENTS = "arguments"; +const char *CONTROL_SERVICE = "service"; +const char *CONTROL_REMOTE_ADDRESS = "remote-address"; + +// Full version, with status, text and arguments +ConstElementPtr +createAnswer(const int status_code, const std::string& text, + const ConstElementPtr& arg) { + if (status_code != 0 && text.empty()) { + isc_throw(CtrlChannelError, "Text has to be provided for status_code != 0"); + } + + ElementPtr answer = Element::createMap(); + ElementPtr result = Element::create(status_code); + answer->set(CONTROL_RESULT, result); + + if (!text.empty()) { + answer->set(CONTROL_TEXT, Element::create(text)); + } + if (arg) { + answer->set(CONTROL_ARGUMENTS, arg); + } + return (answer); +} + +ConstElementPtr +createAnswer() { + return (createAnswer(CONTROL_RESULT_SUCCESS, string(""), ConstElementPtr())); +} + +ConstElementPtr +createAnswer(const int status_code, const std::string& text) { + return (createAnswer(status_code, text, ElementPtr())); +} + +ConstElementPtr +createAnswer(const int status_code, const ConstElementPtr& arg) { + return (createAnswer(status_code, "", arg)); +} + +ConstElementPtr +parseAnswerText(int &rcode, const ConstElementPtr& msg) { + if (!msg) { + isc_throw(CtrlChannelError, "invalid answer: no answer specified"); + } + if (msg->getType() != Element::map) { + isc_throw(CtrlChannelError, "invalid answer: expected toplevel entry to be a map, got " + << Element::typeToName(msg->getType()) << " instead"); + } + if (!msg->contains(CONTROL_RESULT)) { + isc_throw(CtrlChannelError, + "invalid answer: does not contain mandatory '" << CONTROL_RESULT << "'"); + } + + ConstElementPtr result = msg->get(CONTROL_RESULT); + if (result->getType() != Element::integer) { + isc_throw(CtrlChannelError, "invalid answer: expected '" << CONTROL_RESULT + << "' to be an integer, got " + << Element::typeToName(result->getType()) << " instead"); + } + + rcode = result->intValue(); + + // If there are arguments, return them. + return (msg->get(CONTROL_TEXT)); +} + +ConstElementPtr +parseAnswer(int &rcode, const ConstElementPtr& msg) { + if (!msg) { + isc_throw(CtrlChannelError, "invalid answer: no answer specified"); + } + if (msg->getType() != Element::map) { + isc_throw(CtrlChannelError, "invalid answer: expected toplevel entry to be a map, got " + << Element::typeToName(msg->getType()) << " instead"); + } + if (!msg->contains(CONTROL_RESULT)) { + isc_throw(CtrlChannelError, + "invalid answer: does not contain mandatory '" << CONTROL_RESULT << "'"); + } + + ConstElementPtr result = msg->get(CONTROL_RESULT); + if (result->getType() != Element::integer) { + isc_throw(CtrlChannelError, "invalid answer: expected '" << CONTROL_RESULT + << "' to be an integer, got " + << Element::typeToName(result->getType()) << " instead"); + } + + rcode = result->intValue(); + + // If there are arguments, return them. + ConstElementPtr args = msg->get(CONTROL_ARGUMENTS); + if (args) { + return (args); + } + + // There are no arguments, let's try to return just the text status + return (msg->get(CONTROL_TEXT)); +} + + +std::string +answerToText(const ConstElementPtr& msg) { + if (!msg) { + isc_throw(CtrlChannelError, "invalid answer: no answer specified"); + } + if (msg->getType() != Element::map) { + isc_throw(CtrlChannelError, "invalid answer: expected toplevel entry to be a map, got " + << Element::typeToName(msg->getType()) << " instead"); + } + if (!msg->contains(CONTROL_RESULT)) { + isc_throw(CtrlChannelError, + "invalid answer: does not contain mandatory '" << CONTROL_RESULT << "'"); + } + + ConstElementPtr result = msg->get(CONTROL_RESULT); + if (result->getType() != Element::integer) { + isc_throw(CtrlChannelError, "invalid answer: expected '" << CONTROL_RESULT + << "' to be an integer, got " << Element::typeToName(result->getType()) + << " instead"); + } + + stringstream txt; + int rcode = result->intValue(); + if (rcode == 0) { + txt << "success(0)"; + } else { + txt << "failure(" << rcode << ")"; + } + + // Was any text provided? If yes, include it. + ConstElementPtr txt_elem = msg->get(CONTROL_TEXT); + if (txt_elem) { + txt << ", text=" << txt_elem->stringValue(); + } + + return (txt.str()); +} + +ConstElementPtr +createCommand(const std::string& command) { + return (createCommand(command, ElementPtr(), "")); +} + +ConstElementPtr +createCommand(const std::string& command, ConstElementPtr arg) { + return (createCommand(command, arg, "")); +} + +ConstElementPtr +createCommand(const std::string& command, const std::string& service) { + return (createCommand(command, ElementPtr(), service)); +} + +ConstElementPtr +createCommand(const std::string& command, + ConstElementPtr arg, + const std::string& service) { + ElementPtr query = Element::createMap(); + ElementPtr cmd = Element::create(command); + query->set(CONTROL_COMMAND, cmd); + if (arg) { + query->set(CONTROL_ARGUMENTS, arg); + } + if (!service.empty()) { + ElementPtr services = Element::createList(); + services->add(Element::create(service)); + query->set(CONTROL_SERVICE, services); + } + return (query); +} + +std::string +parseCommand(ConstElementPtr& arg, ConstElementPtr command) { + if (!command) { + isc_throw(CtrlChannelError, "invalid command: no command specified"); + } + if (command->getType() != Element::map) { + isc_throw(CtrlChannelError, "invalid command: expected toplevel entry to be a map, got " + << Element::typeToName(command->getType()) << " instead"); + } + if (!command->contains(CONTROL_COMMAND)) { + isc_throw(CtrlChannelError, + "invalid command: does not contain mandatory '" << CONTROL_COMMAND << "'"); + } + + // Make sure that all specified parameters are supported. + auto command_params = command->mapValue(); + for (auto param : command_params) { + if ((param.first != CONTROL_COMMAND) && + (param.first != CONTROL_ARGUMENTS) && + (param.first != CONTROL_SERVICE) && + (param.first != CONTROL_REMOTE_ADDRESS)) { + isc_throw(CtrlChannelError, + "invalid command: unsupported parameter '" << param.first << "'"); + } + } + + ConstElementPtr cmd = command->get(CONTROL_COMMAND); + if (cmd->getType() != Element::string) { + isc_throw(CtrlChannelError, "invalid command: expected '" + << CONTROL_COMMAND << "' to be a string, got " + << Element::typeToName(command->getType()) << " instead"); + } + + arg = command->get(CONTROL_ARGUMENTS); + + return (cmd->stringValue()); +} + +std::string +parseCommandWithArgs(ConstElementPtr& arg, ConstElementPtr command) { + std::string command_name = parseCommand(arg, command); + + // This function requires arguments within the command. + if (!arg) { + isc_throw(CtrlChannelError, + "invalid command '" << command_name << "': no arguments specified"); + } + + // Arguments must be a map. + if (arg->getType() != Element::map) { + isc_throw(CtrlChannelError, + "invalid command '" << command_name << "': expected '" + << CONTROL_ARGUMENTS << "' to be a map, got " + << Element::typeToName(arg->getType()) << " instead"); + } + + // At least one argument is required. + if (arg->size() == 0) { + isc_throw(CtrlChannelError, + "invalid command '" << command_name << "': '" + << CONTROL_ARGUMENTS << "' is empty"); + } + + return (command_name); +} + +ConstElementPtr +combineCommandsLists(const ConstElementPtr& response1, + const ConstElementPtr& response2) { + // Usually when this method is called there should be two non-null + // responses. If there is just a single response, return this + // response. + if (!response1 && response2) { + return (response2); + + } else if (response1 && !response2) { + return (response1); + + } else if (!response1 && !response2) { + return (ConstElementPtr()); + + } else { + // Both responses are non-null so we need to combine the lists + // of supported commands if the status codes are 0. + int status_code; + ConstElementPtr args1 = parseAnswer(status_code, response1); + if (status_code != 0) { + return (response1); + } + + ConstElementPtr args2 = parseAnswer(status_code, response2); + if (status_code != 0) { + return (response2); + } + + const std::vector<ElementPtr> vec1 = args1->listValue(); + const std::vector<ElementPtr> vec2 = args2->listValue(); + + // Storing command names in a set guarantees that the non-unique + // command names are aggregated. + std::set<std::string> combined_set; + for (auto v = vec1.cbegin(); v != vec1.cend(); ++v) { + combined_set.insert((*v)->stringValue()); + } + for (auto v = vec2.cbegin(); v != vec2.cend(); ++v) { + combined_set.insert((*v)->stringValue()); + } + + // Create a combined list of commands. + ElementPtr combined_list = Element::createList(); + for (auto s = combined_set.cbegin(); s != combined_set.cend(); ++s) { + combined_list->add(Element::create(*s)); + } + return (createAnswer(CONTROL_RESULT_SUCCESS, combined_list)); + } +} + +} // namespace config +} // namespace isc |