Kea  2.3.8
command_interpreter.cc
Go to the documentation of this file.
1 // Copyright (C) 2009-2023 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 
10 #include <cc/command_interpreter.h>
11 #include <cc/data.h>
12 #include <string>
13 #include <set>
14 
15 using namespace std;
16 
17 using isc::data::Element;
21 
22 namespace isc {
23 namespace config {
24 
25 const char *CONTROL_COMMAND = "command";
26 const char *CONTROL_RESULT = "result";
27 const char *CONTROL_TEXT = "text";
28 const char *CONTROL_ARGUMENTS = "arguments";
29 const char *CONTROL_SERVICE = "service";
30 const char *CONTROL_REMOTE_ADDRESS = "remote-address";
31 
32 // Full version, with status, text and arguments
34 createAnswer(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 
40  ElementPtr answer = Element::createMap();
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 
55  return (createAnswer(CONTROL_RESULT_SUCCESS, string(""), ConstElementPtr()));
56 }
57 
59 createAnswer(const int status_code, const std::string& text) {
60  return (createAnswer(status_code, text, ElementPtr()));
61 }
62 
64 createAnswer(const int status_code, const ConstElementPtr& arg) {
65  return (createAnswer(status_code, "", arg));
66 }
67 
69 parseAnswer(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  ConstElementPtr args = msg->get(CONTROL_ARGUMENTS);
93  if (args) {
94  return (args);
95  }
96 
97  // There are no arguments, let's try to return just the text status
98  return (msg->get(CONTROL_TEXT));
99 }
100 
101 std::string
103  if (!msg) {
104  isc_throw(CtrlChannelError, "invalid answer: no answer specified");
105  }
106  if (msg->getType() != Element::map) {
107  isc_throw(CtrlChannelError, "invalid answer: expected toplevel entry to be a map, got "
108  << Element::typeToName(msg->getType()) << " instead");
109  }
110  if (!msg->contains(CONTROL_RESULT)) {
112  "invalid answer: does not contain mandatory '" << CONTROL_RESULT << "'");
113  }
114 
115  ConstElementPtr result = msg->get(CONTROL_RESULT);
116  if (result->getType() != Element::integer) {
117  isc_throw(CtrlChannelError, "invalid answer: expected '" << CONTROL_RESULT
118  << "' to be an integer, got " << Element::typeToName(result->getType())
119  << " instead");
120  }
121 
122  stringstream txt;
123  int rcode = result->intValue();
124  if (rcode == 0) {
125  txt << "success(0)";
126  } else {
127  txt << "failure(" << rcode << ")";
128  }
129 
130  // Was any text provided? If yes, include it.
131  ConstElementPtr txt_elem = msg->get(CONTROL_TEXT);
132  if (txt_elem) {
133  txt << ", text=" << txt_elem->stringValue();
134  }
135 
136  return (txt.str());
137 }
138 
140 createCommand(const std::string& command) {
141  return (createCommand(command, ElementPtr(), ""));
142 }
143 
145 createCommand(const std::string& command, ConstElementPtr arg) {
146  return (createCommand(command, arg, ""));
147 }
148 
150 createCommand(const std::string& command, const std::string& service) {
151  return (createCommand(command, ElementPtr(), service));
152 }
153 
155 createCommand(const std::string& command,
156  ConstElementPtr arg,
157  const std::string& service) {
158  ElementPtr query = Element::createMap();
159  ElementPtr cmd = Element::create(command);
160  query->set(CONTROL_COMMAND, cmd);
161  if (arg) {
162  query->set(CONTROL_ARGUMENTS, arg);
163  }
164  if (!service.empty()) {
165  ElementPtr services = Element::createList();
166  services->add(Element::create(service));
167  query->set(CONTROL_SERVICE, services);
168  }
169  return (query);
170 }
171 
172 std::string
174  if (!command) {
175  isc_throw(CtrlChannelError, "invalid command: no command specified");
176  }
177  if (command->getType() != Element::map) {
178  isc_throw(CtrlChannelError, "invalid command: expected toplevel entry to be a map, got "
179  << Element::typeToName(command->getType()) << " instead");
180  }
181  if (!command->contains(CONTROL_COMMAND)) {
183  "invalid command: does not contain mandatory '" << CONTROL_COMMAND << "'");
184  }
185 
186  // Make sure that all specified parameters are supported.
187  auto command_params = command->mapValue();
188  for (auto param : command_params) {
189  if ((param.first != CONTROL_COMMAND) &&
190  (param.first != CONTROL_ARGUMENTS) &&
191  (param.first != CONTROL_SERVICE) &&
192  (param.first != CONTROL_REMOTE_ADDRESS)) {
194  "invalid command: unsupported parameter '" << param.first << "'");
195  }
196  }
197 
198  ConstElementPtr cmd = command->get(CONTROL_COMMAND);
199  if (cmd->getType() != Element::string) {
200  isc_throw(CtrlChannelError, "invalid command: expected '"
201  << CONTROL_COMMAND << "' to be a string, got "
202  << Element::typeToName(command->getType()) << " instead");
203  }
204 
205  arg = command->get(CONTROL_ARGUMENTS);
206 
207  return (cmd->stringValue());
208 }
209 
210 std::string
212  std::string command_name = parseCommand(arg, command);
213 
214  // This function requires arguments within the command.
215  if (!arg) {
217  "invalid command '" << command_name << "': no arguments specified");
218  }
219 
220  // Arguments must be a map.
221  if (arg->getType() != Element::map) {
223  "invalid command '" << command_name << "': expected '"
224  << CONTROL_ARGUMENTS << "' to be a map, got "
225  << Element::typeToName(arg->getType()) << " instead");
226  }
227 
228  // At least one argument is required.
229  if (arg->size() == 0) {
231  "invalid command '" << command_name << "': '"
232  << CONTROL_ARGUMENTS << "' is empty");
233  }
234 
235  return (command_name);
236 }
237 
240  const ConstElementPtr& response2) {
241  // Usually when this method is called there should be two non-null
242  // responses. If there is just a single response, return this
243  // response.
244  if (!response1 && response2) {
245  return (response2);
246 
247  } else if (response1 && !response2) {
248  return (response1);
249 
250  } else if (!response1 && !response2) {
251  return (ConstElementPtr());
252 
253  } else {
254  // Both responses are non-null so we need to combine the lists
255  // of supported commands if the status codes are 0.
256  int status_code;
257  ConstElementPtr args1 = parseAnswer(status_code, response1);
258  if (status_code != 0) {
259  return (response1);
260  }
261 
262  ConstElementPtr args2 = parseAnswer(status_code, response2);
263  if (status_code != 0) {
264  return (response2);
265  }
266 
267  const std::vector<ElementPtr> vec1 = args1->listValue();
268  const std::vector<ElementPtr> vec2 = args2->listValue();
269 
270  // Storing command names in a set guarantees that the non-unique
271  // command names are aggregated.
272  std::set<std::string> combined_set;
273  for (auto v = vec1.cbegin(); v != vec1.cend(); ++v) {
274  combined_set.insert((*v)->stringValue());
275  }
276  for (auto v = vec2.cbegin(); v != vec2.cend(); ++v) {
277  combined_set.insert((*v)->stringValue());
278  }
279 
280  // Create a combined list of commands.
281  ElementPtr combined_list = Element::createList();
282  for (auto s = combined_set.cbegin(); s != combined_set.cend(); ++s) {
283  combined_list->add(Element::create(*s));
284  }
285  return (createAnswer(CONTROL_RESULT_SUCCESS, combined_list));
286  }
287 }
288 
289 } // namespace config
290 } // 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:72
A standard Data module exception that is thrown if a parse error is encountered when constructing an ...
Definition: data.h:49
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 parseCommandWithArgs(ConstElementPtr &arg, ConstElementPtr command)
const char * CONTROL_ARGUMENTS
String used for arguments map ("arguments")
const char * CONTROL_TEXT
String used for storing textual description ("text")
const char * CONTROL_COMMAND
String used for commands ("command")
ConstElementPtr createAnswer(const int status_code, const ConstElementPtr &arg)
const char * CONTROL_SERVICE
String used for service list ("service")
std::string parseCommand(ConstElementPtr &arg, ConstElementPtr command)
ConstElementPtr combineCommandsLists(const ConstElementPtr &response1, const ConstElementPtr &response2)
ConstElementPtr parseAnswer(int &rcode, const ConstElementPtr &msg)
const char * CONTROL_REMOTE_ADDRESS
String used for remote address ("remote-address")
ConstElementPtr createCommand(const std::string &command, ConstElementPtr arg, const std::string &service)
const char * CONTROL_RESULT
String used for result, i.e. integer status ("result")
const int CONTROL_RESULT_SUCCESS
Status code indicating a successful operation.
std::string answerToText(const ConstElementPtr &msg)
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:29
boost::shared_ptr< Element > ElementPtr
Definition: data.h:26
Defines the logger used by the top-level component of kea-lfc.