Kea 3.1.10
command_interpreter.cc
Go to the documentation of this file.
1// Copyright (C) 2009-2026 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8
11#include <cc/data.h>
12#include <string>
13#include <set>
14
15using namespace std;
16
21
22namespace isc {
23namespace config {
24
25const char *CONTROL_COMMAND = "command";
26const char *CONTROL_RESULT = "result";
27const char *CONTROL_TEXT = "text";
28const char *CONTROL_ARGUMENTS = "arguments";
29const char *CONTROL_SERVICE = "service";
30const char *CONTROL_REMOTE_ADDRESS = "remote-address";
31
32// Full version, with status, text and arguments
34createAnswer(const int status_code, const std::string& text,
35 const ConstElementPtr& arg) {
36 if (status_code != 0 && text.empty()) {
37 isc_throw(CtrlChannelError, "Text has to be provided for status_code != 0");
38 }
39
41 ElementPtr result = Element::create(status_code);
42 answer->set(CONTROL_RESULT, result);
43
44 if (!text.empty()) {
45 answer->set(CONTROL_TEXT, Element::create(text));
46 }
47 if (arg) {
48 answer->set(CONTROL_ARGUMENTS, arg);
49 }
50 return (answer);
51}
52
57
59createAnswer(const int status_code, const std::string& text) {
60 return (createAnswer(status_code, text, ElementPtr()));
61}
62
64createAnswer(const int status_code, const ConstElementPtr& arg) {
65 return (createAnswer(status_code, "", arg));
66}
67
69parseAnswerText(int &rcode, const ConstElementPtr& msg) {
70 if (!msg) {
71 isc_throw(CtrlChannelError, "invalid answer: no answer specified");
72 }
73 if (msg->getType() != Element::map) {
74 isc_throw(CtrlChannelError, "invalid answer: expected toplevel entry to be a map, got "
75 << Element::typeToName(msg->getType()) << " instead");
76 }
77 if (!msg->contains(CONTROL_RESULT)) {
79 "invalid answer: does not contain mandatory '" << CONTROL_RESULT << "'");
80 }
81
82 ConstElementPtr result = msg->get(CONTROL_RESULT);
83 if (result->getType() != Element::integer) {
84 isc_throw(CtrlChannelError, "invalid answer: expected '" << CONTROL_RESULT
85 << "' to be an integer, got "
86 << Element::typeToName(result->getType()) << " instead");
87 }
88
89 rcode = result->intValue();
90
91 // If there are arguments, return them.
92 return (msg->get(CONTROL_TEXT));
93}
94
96parseAnswer(int &rcode, const ConstElementPtr& msg) {
97 if (!msg) {
98 isc_throw(CtrlChannelError, "invalid answer: no answer specified");
99 }
100 if (msg->getType() != Element::map) {
101 isc_throw(CtrlChannelError, "invalid answer: expected toplevel entry to be a map, got "
102 << Element::typeToName(msg->getType()) << " instead");
103 }
104 if (!msg->contains(CONTROL_RESULT)) {
106 "invalid answer: does not contain mandatory '" << CONTROL_RESULT << "'");
107 }
108
109 ConstElementPtr result = msg->get(CONTROL_RESULT);
110 if (result->getType() != Element::integer) {
111 isc_throw(CtrlChannelError, "invalid answer: expected '" << CONTROL_RESULT
112 << "' to be an integer, got "
113 << Element::typeToName(result->getType()) << " instead");
114 }
115
116 rcode = result->intValue();
117
118 // If there are arguments, return them.
119 ConstElementPtr args = msg->get(CONTROL_ARGUMENTS);
120 if (args) {
121 return (args);
122 }
123
124 // There are no arguments, let's try to return just the text status
125 return (msg->get(CONTROL_TEXT));
126}
127
128std::string
130 if (!msg) {
131 isc_throw(CtrlChannelError, "invalid answer: no answer specified");
132 }
133 if (msg->getType() != Element::map) {
134 isc_throw(CtrlChannelError, "invalid answer: expected toplevel entry to be a map, got "
135 << Element::typeToName(msg->getType()) << " instead");
136 }
137 if (!msg->contains(CONTROL_RESULT)) {
139 "invalid answer: does not contain mandatory '" << CONTROL_RESULT << "'");
140 }
141
142 ConstElementPtr result = msg->get(CONTROL_RESULT);
143 if (result->getType() != Element::integer) {
144 isc_throw(CtrlChannelError, "invalid answer: expected '" << CONTROL_RESULT
145 << "' to be an integer, got " << Element::typeToName(result->getType())
146 << " instead");
147 }
148
149 stringstream txt;
150 int rcode = result->intValue();
151 if (rcode == 0) {
152 txt << "success(0)";
153 } else {
154 txt << "failure(" << rcode << ")";
155 }
156
157 // Was any text provided? If yes, include it.
158 ConstElementPtr txt_elem = msg->get(CONTROL_TEXT);
159 if (txt_elem) {
160 txt << ", text=" << txt_elem->stringValue();
161 }
162
163 return (txt.str());
164}
165
167createCommand(const std::string& command) {
168 return (createCommand(command, ElementPtr(), ""));
169}
170
172createCommand(const std::string& command, ConstElementPtr arg) {
173 return (createCommand(command, arg, ""));
174}
175
177createCommand(const std::string& command, const std::string& service) {
178 return (createCommand(command, ElementPtr(), service));
179}
180
182createCommand(const std::string& command,
183 ConstElementPtr arg,
184 const std::string& service) {
186 ElementPtr cmd = Element::create(command);
187 query->set(CONTROL_COMMAND, cmd);
188 if (arg) {
189 query->set(CONTROL_ARGUMENTS, arg);
190 }
191 if (!service.empty()) {
192 ElementPtr services = Element::createList();
193 services->add(Element::create(service));
194 query->set(CONTROL_SERVICE, services);
195 }
196 return (query);
197}
198
199std::string
201 if (!command) {
202 isc_throw(CtrlChannelError, "invalid command: no command specified");
203 }
204 if (command->getType() != Element::map) {
205 isc_throw(CtrlChannelError, "invalid command: expected toplevel entry to be a map, got "
206 << Element::typeToName(command->getType()) << " instead");
207 }
208 if (!command->contains(CONTROL_COMMAND)) {
210 "invalid command: does not contain mandatory '" << CONTROL_COMMAND << "'");
211 }
212
213 // Make sure that all specified parameters are supported.
214 auto const& command_params = command->mapValue();
215 for (auto const& param : command_params) {
216 if ((param.first != CONTROL_COMMAND) &&
217 (param.first != CONTROL_ARGUMENTS) &&
218 (param.first != CONTROL_SERVICE) &&
219 (param.first != CONTROL_REMOTE_ADDRESS)) {
221 "invalid command: unsupported parameter '" << param.first << "'");
222 }
223 }
224
225 ConstElementPtr cmd = command->get(CONTROL_COMMAND);
226 if (cmd->getType() != Element::string) {
227 isc_throw(CtrlChannelError, "invalid command: expected '"
228 << CONTROL_COMMAND << "' to be a string, got "
229 << Element::typeToName(command->getType()) << " instead");
230 }
231
232 arg = command->get(CONTROL_ARGUMENTS);
233
234 return (cmd->stringValue());
235}
236
237std::string
239 std::string command_name = parseCommand(arg, command);
240
241 // This function requires arguments within the command.
242 if (!arg) {
244 "invalid command '" << command_name << "': no arguments specified");
245 }
246
247 // Arguments must be a map.
248 if (arg->getType() != Element::map) {
250 "invalid command '" << command_name << "': expected '"
251 << CONTROL_ARGUMENTS << "' to be a map, got "
252 << Element::typeToName(arg->getType()) << " instead");
253 }
254
255 // At least one argument is required.
256 if (arg->size() == 0) {
258 "invalid command '" << command_name << "': '"
259 << CONTROL_ARGUMENTS << "' is empty");
260 }
261
262 return (command_name);
263}
264
267 const ConstElementPtr& response2) {
268 // Usually when this method is called there should be two non-null
269 // responses. If there is just a single response, return this
270 // response.
271 if (!response1 && response2) {
272 return (response2);
273
274 } else if (response1 && !response2) {
275 return (response1);
276
277 } else if (!response1 && !response2) {
278 return (ConstElementPtr());
279
280 } else {
281 // Both responses are non-null so we need to combine the lists
282 // of supported commands if the status codes are 0.
283 int status_code;
284 ConstElementPtr args1 = parseAnswer(status_code, response1);
285 if (status_code != 0) {
286 return (response1);
287 }
288
289 ConstElementPtr args2 = parseAnswer(status_code, response2);
290 if (status_code != 0) {
291 return (response2);
292 }
293
294 const std::vector<ElementPtr> vec1 = args1->listValue();
295 const std::vector<ElementPtr> vec2 = args2->listValue();
296
297 // Storing command names in a set guarantees that the non-unique
298 // command names are aggregated.
299 std::set<std::string> combined_set;
300 for (auto const& v : vec1) {
301 combined_set.insert(v->stringValue());
302 }
303 for (auto const& v : vec2) {
304 combined_set.insert(v->stringValue());
305 }
306
307 // Create a combined list of commands.
308 ElementPtr combined_list = Element::createList();
309 for (auto const& s : combined_set) {
310 combined_list->add(Element::create(s));
311 }
312 return (createAnswer(CONTROL_RESULT_SUCCESS, combined_list));
313 }
314}
315
316} // namespace config
317} // namespace isc
A standard control channel exception that is thrown if a function is there is a problem with one of t...
The Element class represents a piece of data, used by the command channel and configuration parts.
Definition data.h:73
static ElementPtr create(const Position &pos=ZERO_POSITION())
Create a NullElement.
Definition data.cc:299
static std::string typeToName(Element::types type)
Returns the name of the given type as a string.
Definition data.cc:715
static ElementPtr createMap(const Position &pos=ZERO_POSITION())
Creates an empty MapElement type ElementPtr.
Definition data.cc:354
static ElementPtr createList(const Position &pos=ZERO_POSITION())
Creates an empty ListElement type ElementPtr.
Definition data.cc:349
A standard Data module exception that is thrown if a parse error is encountered when constructing an ...
Definition data.h:50
This file contains several functions and constants that are used for handling commands and responses ...
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
std::string answerToText(const ConstElementPtr &msg)
Converts answer to printable text.
const char * CONTROL_ARGUMENTS
String used for arguments map ("arguments").
ConstElementPtr parseAnswerText(int &rcode, const ConstElementPtr &msg)
Parses a standard config/command level answer and returns text status.
const char * CONTROL_TEXT
String used for storing textual description ("text").
const char * CONTROL_COMMAND
String used for commands ("command").
ConstElementPtr parseAnswer(int &rcode, const ConstElementPtr &msg)
Parses a standard config/command level answer and returns arguments or text status code.
ConstElementPtr createCommand(const std::string &command)
Creates a standard command message with no argument (of the form { "command": "my_command" }...
ConstElementPtr combineCommandsLists(const ConstElementPtr &response1, const ConstElementPtr &response2)
Combines lists of commands carried in two responses.
std::string parseCommand(ConstElementPtr &arg, ConstElementPtr command)
Parses the given command into a string containing the actual command and an ElementPtr containing the...
const char * CONTROL_SERVICE
String used for service list ("service").
std::string parseCommandWithArgs(ConstElementPtr &arg, ConstElementPtr command)
Parses the given command into a string containing the command name and an ElementPtr containing the m...
ConstElementPtr createAnswer()
Creates a standard config/command level success answer message (i.e.
const char * CONTROL_REMOTE_ADDRESS
String used for remote address ("remote-address").
const char * CONTROL_RESULT
String used for result, i.e. integer status ("result").
const int CONTROL_RESULT_SUCCESS
Status code indicating a successful operation.
boost::shared_ptr< const Element > ConstElementPtr
Definition data.h:30
boost::shared_ptr< Element > ElementPtr
Definition data.h:29
Defines the logger used by the top-level component of kea-lfc.