Line data Source code
1 : // Copyright (c) 2012-2022 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/eventdispatcher
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program; if not, write to the Free Software
18 : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 :
20 : /** \file
21 : * \brief Implementation of the Snap Communicator class.
22 : *
23 : * This class wraps the C poll() interface in a C++ object with many types
24 : * of objects:
25 : *
26 : * \li Server Connections; for software that want to offer a port to
27 : * which clients can connect to; the server will call accept()
28 : * once a new client connection is ready; this results in a
29 : * Server/Client connection object
30 : * \li Client Connections; for software that want to connect to
31 : * a server; these expect the IP address and port to connect to
32 : * \li Server/Client Connections; for the server when it accepts a new
33 : * connection; in this case the server gets a socket from accept()
34 : * and creates one of these objects to handle the connection
35 : *
36 : * Using the poll() function is the easiest and allows us to listen
37 : * on pretty much any number of sockets (on my server it is limited
38 : * at 16,768 and frankly over 1,000 we probably will start to have
39 : * real slowness issues on small VPN servers.)
40 : */
41 :
42 :
43 : // self
44 : //
45 : #include "eventdispatcher/message.h"
46 :
47 : #include "eventdispatcher/exception.h"
48 :
49 :
50 : // advgetopt lib
51 : //
52 : #include <advgetopt/validator_double.h>
53 : #include <advgetopt/validator_integer.h>
54 :
55 :
56 : // snaplogger lib
57 : //
58 : #include <snaplogger/message.h>
59 :
60 :
61 : // libutf8 lib
62 : //
63 : #include <libutf8/json_tokens.h>
64 :
65 :
66 : // snapdev lib
67 : //
68 : #include <snapdev/hexadecimal_string.h>
69 : #include <snapdev/string_replace_many.h>
70 : #include <snapdev/trim_string.h>
71 :
72 :
73 : // last include
74 : //
75 : #include <snapdev/poison.h>
76 :
77 :
78 :
79 : namespace ed
80 : {
81 :
82 :
83 :
84 : /** \brief Parse a message from the specified parameter.
85 : *
86 : * This function transformed the input string in a set of message
87 : * fields.
88 : *
89 : * The message formats supported are:
90 : *
91 : * \code
92 : * // String
93 : * ( '<' sent-from-server ':' sent-from-service ' ')?
94 : * ( ( server ':' )? service '/' )?
95 : * command
96 : * ' ' ( parameter_name '=' value ';' )*
97 : *
98 : * // JSON
99 : * {
100 : * "sent-from-server":"<name>", // optional
101 : * "sent-from-service":"<name>", // optional
102 : * "server":"<name>", // optional
103 : * "service":"<service>", // optional
104 : * "command":"<command>",
105 : * "parameters": // optional
106 : * {
107 : * "<name>":"<value>",
108 : * ...
109 : * }
110 : * }
111 : * \endcode
112 : *
113 : * Note that the String and JSON formats are written on a single line.
114 : * Here it is shown on multiple line to make it easier to read.
115 : * In a String message, the last ';' is optional.
116 : *
117 : * The sender "\<sent-from-server:sent-from-service" names are added by
118 : * snapcommunicator when it receives a message which is destined for
119 : * another service (i.e. not itself). This can be used by the receiver
120 : * to reply back to the exact same process if it is a requirement for that
121 : * message (i.e. a process that sends a LOCK message, for example,
122 : * expects to receive the LOCKED message back as an answer.) Note that
123 : * it is assumed that there cannot be more than one service named
124 : * 'service' per server. This is enforced by the snapcommunicator
125 : * REGISTER function.
126 : *
127 : * \code
128 : * // to reply to the exact message sender, one can use the
129 : * // following two lines of code:
130 : * //
131 : * reply.set_server(message.get_sent_from_server());
132 : * reply.set_service(message.get_sent_from_service());
133 : *
134 : * // or use the reply_to() helper function
135 : * //
136 : * reply.reply_to(message);
137 : * \endcode
138 : *
139 : * The space after the command cannot be there unless parameters follow.
140 : * Parameters must be separated by semi-colons.
141 : *
142 : * The value of a parameter gets quoted when it includes a ';'. Within
143 : * the quotes, a Double Quote can be escaped inside by adding a backslash
144 : * in front of it (\\"). Newline characters (as well as return carriage)
145 : * are also escaped using \\n and \\r respectively. Finally, we have to
146 : * escape backslashes themselves by doubling them, so \\ becomes \\\\.
147 : *
148 : * Note that only parameter values support absolutely any character.
149 : * All the other parameters are limited to the Latin alphabet, digits,
150 : * and underscores ([A-Za-z0-9_]+). Also all commands are limited
151 : * to uppercase letters only.
152 : *
153 : * \note
154 : * The input message is not saved as a cached version of the message
155 : * because we assume it may not be 100% optimized (canonicalized.)
156 : *
157 : * \param[in] original_message The string to convert into fields in this
158 : * message object.
159 : *
160 : * \return true if the message was successfully parsed; false when an
161 : * error occurs and in that case no fields get modified.
162 : *
163 : * \sa from_string()
164 : * \sa from_json()
165 : * \sa to_message()
166 : * \sa get_sent_from_server()
167 : * \sa get_sent_from_service()
168 : * \sa reply_to()
169 : */
170 9 : bool message::from_message(std::string const & original_message)
171 : {
172 18 : std::string const msg(snapdev::trim_string(original_message));
173 :
174 9 : if(msg.empty())
175 : {
176 0 : SNAP_LOG_ERROR
177 : << "message is empty or only composed of blanks ("
178 : << original_message
179 : << ")."
180 : << SNAP_LOG_SEND;
181 0 : return false;
182 : }
183 :
184 9 : if(msg[0] == '{')
185 : {
186 1 : return from_json(msg);
187 : }
188 : else
189 : {
190 8 : return from_string(msg);
191 : }
192 : }
193 :
194 :
195 : /** \brief Parse the message as a Snap! message string.
196 : *
197 : * This function parses the input as a string as defined by the Snap!
198 : * message string format.
199 : *
200 : * \note
201 : * You are expected to use the from_message() function instead of
202 : * directly calling this function. This means the message can either
203 : * be a Snap! String message or a JSON message.
204 : *
205 : * \param[in] original_message The message to be parsed.
206 : *
207 : * \return true if the message was successfully parsed.
208 : *
209 : * \sa from_message()
210 : * \sa from_json()
211 : */
212 9 : bool message::from_string(std::string const & original_message)
213 : {
214 18 : std::string sent_from_server;
215 18 : std::string sent_from_service;
216 18 : std::string server;
217 18 : std::string service;
218 18 : std::string command;
219 18 : parameters_t parameters;
220 :
221 : // someone using telnet to test sending messages will include a '\r'
222 : // so run a trim on the message in case it is there
223 : //
224 18 : std::string msg(snapdev::trim_string(original_message));
225 9 : char const * m(msg.c_str());
226 :
227 : // sent-from indicated?
228 : //
229 9 : if(*m != '\0' && *m == '<')
230 : {
231 : // the name of the server and server sending this message
232 : //
233 : // First ++m to skip the '<'
234 : //
235 0 : for(++m; *m != '\0' && *m != ':'; ++m)
236 : {
237 0 : if(*m == ' ')
238 : {
239 : // invalid syntax from input message
240 : //
241 0 : SNAP_LOG_ERROR
242 : << "a message with sent_from_server must not include a space in the server name ("
243 : << original_message
244 : << ")."
245 : << SNAP_LOG_SEND;
246 0 : return false;
247 : }
248 :
249 0 : sent_from_server += *m;
250 : }
251 0 : if(*m != '\0')
252 : {
253 : // First ++m to skip the ':'
254 0 : for(++m; *m != '\0' && *m != ' '; ++m)
255 : {
256 0 : sent_from_service += *m;
257 : }
258 : }
259 0 : if(*m == '\0')
260 : {
261 : // invalid syntax from input message
262 : //
263 0 : SNAP_LOG_ERROR
264 : << "a message cannot only include a 'sent from service' definition."
265 : << SNAP_LOG_SEND;
266 0 : return false;
267 : }
268 : // Skip the ' '
269 0 : ++m;
270 : }
271 :
272 9 : bool has_server(false);
273 9 : bool has_service(false);
274 129 : for(; *m != '\0' && *m != ' '; ++m)
275 : {
276 61 : if(*m == ':')
277 : {
278 2 : if(has_server
279 1 : || has_service
280 3 : || command.empty())
281 : {
282 : // we cannot have more than one ':'
283 : // and the name cannot be empty if ':' is used
284 : // we also cannot have a ':' after the '/'
285 : //
286 1 : SNAP_LOG_ERROR
287 : << "a server name cannot be empty when specified, also it cannot include two server names and a server name after a service name was specified."
288 : << SNAP_LOG_SEND;
289 1 : return false;
290 : }
291 1 : has_server = true;
292 1 : server = command;
293 1 : command.clear();
294 : }
295 59 : else if(*m == '/')
296 : {
297 0 : if(has_service
298 0 : || command.empty())
299 : {
300 : // we cannot have more than one '/'
301 : // and the name cannot be empty if '/' is used
302 : //
303 0 : SNAP_LOG_ERROR
304 : << "a service name is mandatory when the message includes a slash (/), also it cannot include two service names."
305 : << SNAP_LOG_SEND;
306 0 : return false;
307 : }
308 0 : has_service = true;
309 0 : service = command;
310 0 : command.clear();
311 : }
312 : else
313 : {
314 59 : command += *m;
315 : }
316 : }
317 :
318 8 : if(command.empty())
319 : {
320 : // command is mandatory
321 : //
322 0 : SNAP_LOG_ERROR
323 : << "a command is mandatory in a message."
324 : << SNAP_LOG_SEND;
325 0 : return false;
326 : }
327 :
328 : // if we have a space, we expect one or more parameters
329 : //
330 8 : if(*m == ' ')
331 : {
332 10 : for(++m; *m != '\0';)
333 : {
334 : // first we have to read the parameter name (up to the '=')
335 : //
336 8 : char const *s(m);
337 49 : for(; *m != '\0' && *m != '='; ++m);
338 16 : std::string const param_name(s, m - s);
339 8 : if(param_name.empty())
340 : {
341 : // parameters must have a name
342 : //
343 0 : SNAP_LOG_ERROR
344 : << "could not accept message because an empty parameter name is not valid."
345 : << SNAP_LOG_SEND;
346 0 : return false;
347 : }
348 : try
349 : {
350 8 : verify_message_name(param_name);
351 : }
352 0 : catch(invalid_message const & e)
353 : {
354 : // name is not empty, but it has invalid characters in it
355 : //
356 0 : SNAP_LOG_ERROR
357 : << "could not accept message because parameter name \""
358 : << param_name
359 : << "\" is not considered valid: "
360 0 : << e.what()
361 : << SNAP_LOG_SEND;
362 0 : return false;
363 : }
364 :
365 8 : if(*m == '\0'
366 8 : || *m != '=')
367 : {
368 : // ?!?
369 : //
370 0 : SNAP_LOG_ERROR
371 : << "message parameters must be followed by an equal (=) character."
372 : << SNAP_LOG_SEND;
373 0 : return false;
374 : }
375 8 : ++m; // skip '='
376 :
377 : // retrieve the parameter name at first
378 : //
379 16 : std::string param_value;
380 8 : if(*m == '"')
381 : {
382 : // quoted parameter
383 : //
384 0 : for(++m; *m != '"'; ++m)
385 : {
386 0 : if(*m == '\0')
387 : {
388 : // closing quote (") is missing
389 : //
390 0 : SNAP_LOG_ERROR
391 : << "a quoted message parameter must end with a quote (\")."
392 : << SNAP_LOG_SEND;
393 0 : return false;
394 : }
395 :
396 : // restored escaped double quotes
397 : // (note that we do not yet restore other backslashed
398 : // characters, that's done below)
399 : //
400 0 : if(*m == '\\' && m[1] != '\0' && m[1] == '"')
401 : {
402 0 : ++m;
403 : }
404 : // here the character may be ';'
405 : //
406 0 : param_value += *m;
407 : }
408 :
409 : // skip the quote
410 : //
411 0 : ++m;
412 : }
413 : else
414 : {
415 : // parameter value is found as is
416 : //
417 74 : for(; *m != '\0' && *m != ';'; ++m)
418 : {
419 33 : param_value += *m;
420 : }
421 : }
422 :
423 8 : if(*m != '\0')
424 : {
425 6 : if(*m != ';')
426 : {
427 : // this should never happen
428 : //
429 0 : SNAP_LOG_ERROR
430 : << "two parameters must be separated by a semicolon (;)."
431 : << SNAP_LOG_SEND;
432 0 : return false;
433 : }
434 :
435 : // skip the ';'
436 : //
437 6 : ++m;
438 : }
439 :
440 : // also restore new lines and backslashes if any
441 : //
442 8 : std::string const original_value(snapdev::string_replace_many(
443 : param_value,
444 : {
445 : { "\\\\", "\\" },
446 : { "\\n", "\n" },
447 : { "\\r", "\r" }
448 16 : }));
449 :
450 : // we got a valid parameter, add it
451 : //
452 8 : parameters[param_name] = original_value;
453 : }
454 : }
455 :
456 8 : f_sent_from_server = sent_from_server;
457 8 : f_sent_from_service = sent_from_service;
458 8 : f_server = server;
459 8 : f_service = service;
460 8 : f_command = command;
461 8 : f_parameters.swap(parameters);
462 8 : f_cached_message.clear();
463 :
464 8 : return true;
465 : }
466 :
467 :
468 : /** \brief Parse the JSON message.
469 : *
470 : * This function expects the input string to be a JSON message. It will
471 : * then parse that message and save the field information in this message
472 : * object.
473 : *
474 : * If the parsing fails (i.e. there is a missing comma, etc.), then the
475 : * function returns false and none of the message fields get modified.
476 : *
477 : * \todo
478 : * See if we have full access to the as2js in which case using that JSON
479 : * parser would make this a lot smaller than the libutf8 library parser.
480 : *
481 : * \param[in] msg The message to parse.
482 : *
483 : * \return true if the message was successfully parsed, false otherwise
484 : * and none of the fields will be modified.
485 : *
486 : * \sa from_string()
487 : * \sa from_message()
488 : */
489 2 : bool message::from_json(std::string const & msg)
490 : {
491 4 : std::string sent_from_server;
492 4 : std::string sent_from_service;
493 4 : std::string server;
494 4 : std::string service;
495 4 : std::string command;
496 4 : parameters_t parameters;
497 :
498 4 : libutf8::json_tokens tokens(msg);
499 2 : libutf8::token_t t(tokens.next_token());
500 2 : if(t == libutf8::token_t::TOKEN_END)
501 : {
502 0 : SNAP_LOG_ERROR
503 : << "JSON message is empty, which is not considered valid."
504 : << SNAP_LOG_SEND;
505 0 : return false;
506 : }
507 2 : if(t != libutf8::token_t::TOKEN_OPEN_OBJECT)
508 : {
509 0 : SNAP_LOG_ERROR
510 : << "JSON does not start with a '{' (an object definition)."
511 : << SNAP_LOG_SEND;
512 0 : return false;
513 : }
514 : for(;;)
515 : {
516 4 : t = tokens.next_token();
517 4 : if(t == libutf8::token_t::TOKEN_END)
518 : {
519 0 : SNAP_LOG_ERROR
520 : << "JSON message not properly closed, '}' missing."
521 : << SNAP_LOG_SEND;
522 0 : return false;
523 : }
524 4 : if(t != libutf8::token_t::TOKEN_STRING)
525 : {
526 0 : SNAP_LOG_ERROR
527 : << "JSON message expected a string defining the field name."
528 : << SNAP_LOG_SEND;
529 0 : return false;
530 : }
531 6 : std::string const name(tokens.string());
532 :
533 4 : t = tokens.next_token();
534 4 : if(t != libutf8::token_t::TOKEN_COLON)
535 : {
536 0 : SNAP_LOG_ERROR
537 : << "JSON message expected a colon after an object field name."
538 : << SNAP_LOG_SEND;
539 0 : return false;
540 : }
541 :
542 4 : t = tokens.next_token();
543 4 : if(t == libutf8::token_t::TOKEN_OPEN_OBJECT)
544 : {
545 : // this has to be the list of parameters, make sure the field
546 : // name is correct
547 : //
548 2 : if(name != "parameters")
549 : {
550 0 : SNAP_LOG_ERROR
551 : << "JSON message expected an object to define parameters."
552 : << SNAP_LOG_SEND;
553 0 : return false;
554 : }
555 :
556 2 : t = tokens.next_token();
557 2 : if(t != libutf8::token_t::TOKEN_CLOSE_OBJECT)
558 : {
559 : for(;;)
560 : {
561 8 : if(t != libutf8::token_t::TOKEN_STRING)
562 : {
563 0 : SNAP_LOG_ERROR
564 : << "JSON message expected a string defining the parameter name."
565 : << SNAP_LOG_SEND;
566 0 : return false;
567 : }
568 14 : std::string const parameter_name(tokens.string());
569 :
570 8 : t = tokens.next_token();
571 8 : if(t != libutf8::token_t::TOKEN_COLON)
572 : {
573 0 : SNAP_LOG_ERROR
574 : << "JSON message expected a colon after an object parameter name."
575 : << SNAP_LOG_SEND;
576 0 : return false;
577 : }
578 :
579 8 : t = tokens.next_token();
580 8 : switch(t)
581 : {
582 3 : case libutf8::token_t::TOKEN_STRING:
583 : // for strings, we may have encoded some characters
584 : //
585 18 : parameters[parameter_name] = snapdev::string_replace_many(
586 3 : tokens.string(),
587 : {
588 : { "\\\"", "\"" },
589 : { "\\\\", "\\" },
590 : { "\\n", "\n" },
591 : { "\\r", "\r" }
592 12 : });
593 3 : break;
594 :
595 3 : case libutf8::token_t::TOKEN_NUMBER:
596 : // TODO: we may want to make sure we do not lose precision here
597 : #pragma GCC diagnostic push
598 : #pragma GCC diagnostic ignored "-Wfloat-equal"
599 3 : if(tokens.number() == floor(tokens.number()))
600 : {
601 : // avoid the ".00000" for integers
602 : //
603 3 : parameters[parameter_name] = std::to_string(static_cast<std::int64_t>(tokens.number()));
604 : }
605 : else
606 : {
607 0 : parameters[parameter_name] = std::to_string(tokens.number());
608 : }
609 : #pragma GCC diagnostic pop
610 3 : break;
611 :
612 1 : case libutf8::token_t::TOKEN_TRUE:
613 1 : parameters[parameter_name] = "true";
614 1 : break;
615 :
616 1 : case libutf8::token_t::TOKEN_FALSE:
617 1 : parameters[parameter_name] = "false";
618 1 : break;
619 :
620 0 : case libutf8::token_t::TOKEN_NULL:
621 0 : parameters[parameter_name] = std::string();
622 0 : break;
623 :
624 0 : default:
625 0 : SNAP_LOG_ERROR
626 : << "JSON message expected a parameter value."
627 : << SNAP_LOG_SEND;
628 0 : return false;
629 :
630 : }
631 :
632 8 : t = tokens.next_token();
633 8 : if(t == libutf8::token_t::TOKEN_CLOSE_OBJECT)
634 : {
635 2 : break;
636 : }
637 6 : if(t != libutf8::token_t::TOKEN_COMMA)
638 : {
639 0 : SNAP_LOG_ERROR
640 : << "JSON message parameter expected to be followed by '}' or ','."
641 : << SNAP_LOG_SEND;
642 0 : return false;
643 : }
644 :
645 6 : t = tokens.next_token();
646 6 : }
647 : }
648 : }
649 : else
650 : {
651 2 : if(t != libutf8::token_t::TOKEN_STRING)
652 : {
653 0 : SNAP_LOG_ERROR
654 : << "JSON message expected a string as the field value."
655 : << SNAP_LOG_SEND;
656 0 : return false;
657 : }
658 :
659 2 : if(name == "sent-from-server")
660 : {
661 0 : sent_from_server = tokens.string();
662 : }
663 2 : else if(name == "sent-from-service")
664 : {
665 0 : sent_from_service = tokens.string();
666 : }
667 2 : else if(name == "server")
668 : {
669 0 : server = tokens.string();
670 : }
671 2 : else if(name == "service")
672 : {
673 0 : service = tokens.string();
674 : }
675 2 : else if(name == "command")
676 : {
677 2 : command = tokens.string();
678 : }
679 : else
680 : {
681 : // we just ignore unknown names for forward compatibility
682 : //
683 0 : SNAP_LOG_NOTICE
684 : << "JSON message expected a known field. \""
685 : << name
686 : << "\" was not recognized."
687 : << SNAP_LOG_SEND;
688 : }
689 : }
690 :
691 4 : t = tokens.next_token();
692 4 : if(t == libutf8::token_t::TOKEN_CLOSE_OBJECT)
693 : {
694 2 : t = tokens.next_token();
695 2 : if(t != libutf8::token_t::TOKEN_END)
696 : {
697 0 : SNAP_LOG_ERROR
698 : << "JSON message followed by more data after the last '}'."
699 : << SNAP_LOG_SEND;
700 0 : return false;
701 : }
702 2 : break;
703 : }
704 2 : if(t != libutf8::token_t::TOKEN_COMMA)
705 : {
706 0 : SNAP_LOG_ERROR
707 : << "JSON message parameter expected to be followed by '}' or ','."
708 : << SNAP_LOG_SEND;
709 0 : return false;
710 : }
711 2 : }
712 :
713 2 : f_sent_from_server = sent_from_server;
714 2 : f_sent_from_service = sent_from_service;
715 2 : f_server = server;
716 2 : f_service = service;
717 2 : f_command = command;
718 2 : f_parameters.swap(parameters);
719 2 : f_cached_message.clear();
720 :
721 2 : return true;
722 : }
723 :
724 :
725 : /** \brief Transform all the message parameters in a string.
726 : *
727 : * This function transforms all the message parameters in a string
728 : * and returns the result. The string is a message we can send over
729 : * TCP/IP (if you make sure to add a "\n", note that the
730 : * send_message() does that automatically) or over UDP/IP.
731 : *
732 : * \note
733 : * The function caches the result so calling the function many times
734 : * will return the same string and thus the function is very fast
735 : * after the first call (assuming you do not modify the message on
736 : * each call to to_message().)
737 : *
738 : * \note
739 : * The sent-from information gets saved in the message only if both,
740 : * the server name and service name it was sent from are defined.
741 : *
742 : * \exception invalid_message
743 : * This function raises an exception if the message command was not
744 : * defined since a command is always mandatory.
745 : *
746 : * \param[in] format The format the message will be defined as.
747 : *
748 : * \return The converted message as a string.
749 : *
750 : * \sa to_string()
751 : * \sa to_json()
752 : * \sa to_binary()
753 : * \sa get_sent_from_server()
754 : * \sa get_sent_from_service()
755 : * \sa set_reply_to_server()
756 : * \sa set_reply_to_service()
757 : */
758 15 : std::string message::to_message(format_t format) const
759 : {
760 15 : switch(format)
761 : {
762 14 : case format_t::MESSAGE_FORMAT_STRING:
763 14 : return to_string();
764 :
765 1 : case format_t::MESSAGE_FORMAT_JSON:
766 1 : return to_json();
767 :
768 : }
769 :
770 : throw invalid_parameter(
771 : "unsupported message format: "
772 0 : + std::to_string(static_cast<int>(format)));
773 : }
774 :
775 :
776 : /** \brief Transform all the message parameters in a string.
777 : *
778 : * This function transforms all the message parameters in a string
779 : * and returns the result. The string is a message we can send over
780 : * TCP/IP (if you make sure to add a "\n", note that the
781 : * send_message() does that automatically) or over UDP/IP.
782 : *
783 : * \note
784 : * The function caches the result so calling the function many times
785 : * will return the same string and thus the function is very fast
786 : * after the first call (assuming you do not modify the message on
787 : * each call to to_message().)
788 : *
789 : * \note
790 : * The sent-from information gets saved in the message only if both,
791 : * the server name and service name it was sent from are defined.
792 : *
793 : * \exception invalid_message
794 : * This function raises an exception if the message command was not
795 : * defined since a command is always mandatory.
796 : *
797 : * \return The converted message as a string.
798 : *
799 : * \sa get_sent_from_server()
800 : * \sa get_sent_from_service()
801 : * \sa set_reply_to_server()
802 : * \sa set_reply_to_service()
803 : */
804 14 : std::string message::to_string() const
805 : {
806 14 : if(f_cached_message.empty())
807 : {
808 14 : if(f_command.empty())
809 : {
810 0 : throw invalid_message("message::to_message(): cannot build a valid message without at least a command.");
811 : }
812 :
813 : // add info about the sender
814 : //
815 : // ['<' <sent-from-server> ':' <sent-from-service> ' ']
816 : //
817 28 : if(!f_sent_from_server.empty()
818 14 : || !f_sent_from_service.empty())
819 : {
820 0 : f_cached_message += '<';
821 0 : f_cached_message += f_sent_from_server;
822 0 : f_cached_message += ':';
823 0 : f_cached_message += f_sent_from_service;
824 0 : f_cached_message += ' ';
825 : }
826 :
827 : // add service and optionally the destination server name
828 : // if both are defined
829 : //
830 : // ['<' <sent-from-server> ':' <sent-from-service> ' ']
831 : // [[<server> ':'] <name> '/']
832 : //
833 14 : if(!f_service.empty())
834 : {
835 0 : if(!f_server.empty())
836 : {
837 0 : f_cached_message += f_server;
838 0 : f_cached_message += ':';
839 : }
840 0 : f_cached_message += f_service;
841 0 : f_cached_message += '/';
842 : }
843 :
844 : // ['<' <sent-from-server> ':' <sent-from-service> ' ']
845 : // [[<server> ':'] <name> '/'] <command>
846 : //
847 14 : f_cached_message += f_command;
848 :
849 : // add parameters if any
850 : //
851 : // ['<' <sent-from-server> ':' <sent-from-service> ' ']
852 : // [[<server> ':'] <name> '/'] <command>
853 : // [' ' <param1> '=' <value1>][';' <param2> '=' <value2>]...
854 : //
855 14 : char sep(' ');
856 22 : for(auto p : f_parameters)
857 : {
858 8 : f_cached_message += sep;
859 8 : f_cached_message += p.first;
860 8 : f_cached_message += '=';
861 :
862 8 : std::vector<std::pair<std::string, std::string>> search_and_replace{
863 : { "\\", "\\\\" },
864 : { "\n", "\\n" },
865 : { "\r", "\\r" }
866 16 : };
867 :
868 : // do we need quoting?
869 : //
870 8 : bool const quote(p.second.find(';') != std::string::npos
871 8 : || (!p.second.empty() && p.second[0] == '\"'));
872 8 : if(quote)
873 : {
874 0 : search_and_replace.push_back({ "\"", "\\\"" });
875 : }
876 :
877 8 : std::string const safe_value(snapdev::string_replace_many(
878 16 : p.second, search_and_replace));
879 :
880 8 : if(quote)
881 : {
882 : // quote the resulting parameter and save in f_cached_message
883 : //
884 0 : f_cached_message += '"';
885 0 : f_cached_message += safe_value;
886 0 : f_cached_message += '"';
887 : }
888 : else
889 : {
890 : // no special handling necessary
891 : //
892 8 : f_cached_message += safe_value;
893 : }
894 :
895 8 : sep = ';';
896 : }
897 : }
898 :
899 14 : return f_cached_message;
900 : }
901 :
902 :
903 : /** \brief Transform all the message parameters in a JSON string.
904 : *
905 : * This function transforms all the message parameters in a string
906 : * and returns the result. The string is a JSON object we can send over
907 : * TCP/IP (if you make sure to add a "\n", note that the
908 : * send_message() does that automatically) or over UDP/IP.
909 : *
910 : * \note
911 : * The function caches the result so calling the function many times
912 : * will return the same string and thus the function is very fast
913 : * after the first call (assuming you do not modify the message before
914 : * calling to_json() again.)
915 : *
916 : * \note
917 : * The sent-from information gets saved in the message only if both,
918 : * the server name and service name it was sent from are defined.
919 : *
920 : * \exception invalid_message
921 : * This function raises an exception if the message command was not
922 : * defined since a command is always mandatory.
923 : *
924 : * \return The converted message as a string.
925 : *
926 : * \sa get_sent_from_server()
927 : * \sa get_sent_from_service()
928 : * \sa set_reply_to_server()
929 : * \sa set_reply_to_service()
930 : */
931 2 : std::string message::to_json() const
932 : {
933 2 : if(f_cached_json.empty())
934 : {
935 2 : if(f_command.empty())
936 : {
937 0 : throw invalid_message("message::to_json(): cannot build a valid JSON message without at least a command.");
938 : }
939 :
940 2 : f_cached_json += '{';
941 :
942 : // add info about the sender
943 : //
944 2 : if(!f_sent_from_server.empty())
945 : {
946 0 : f_cached_json += "\"sent-from-server\":\"";
947 0 : f_cached_json += f_sent_from_server;
948 0 : f_cached_json += "\",";
949 : }
950 2 : if(!f_sent_from_service.empty())
951 : {
952 0 : f_cached_json += "\"sent-from-service\":\"";
953 0 : f_cached_json += f_sent_from_service;
954 0 : f_cached_json += "\",";
955 : }
956 :
957 : // add service and optionally the destination server name
958 : // if both are defined
959 : //
960 2 : if(!f_service.empty())
961 : {
962 0 : if(!f_server.empty())
963 : {
964 0 : f_cached_json += "\"server\":\"";
965 0 : f_cached_json += f_server;
966 0 : f_cached_json += "\",";
967 : }
968 0 : f_cached_json += "\"service\":\"";
969 0 : f_cached_json += f_service;
970 : }
971 :
972 : // command
973 : //
974 2 : f_cached_json += "\"command\":\"";
975 2 : f_cached_json += f_command;
976 2 : f_cached_json += '"';
977 :
978 : // add parameters if any
979 : //
980 2 : if(!f_parameters.empty())
981 : {
982 2 : f_cached_json += ",\"parameters\":{";
983 2 : bool first(true);
984 10 : for(auto p : f_parameters)
985 : {
986 8 : if(first)
987 : {
988 2 : first = false;
989 2 : f_cached_json += '"';
990 : }
991 : else
992 : {
993 6 : f_cached_json += ",\"";
994 : }
995 8 : f_cached_json += p.first;
996 8 : f_cached_json += "\":";
997 :
998 8 : if(p.second == "true")
999 : {
1000 1 : f_cached_json += "true";
1001 : }
1002 7 : else if(p.second == "false")
1003 : {
1004 1 : f_cached_json += "false";
1005 : }
1006 : else
1007 : {
1008 6 : double number(0.0);
1009 6 : if(advgetopt::validator_double::convert_string(p.second, number))
1010 : {
1011 : // +<number> is not allowed in JSON, so make sure we
1012 : // do not include the plus sign
1013 : //
1014 3 : if(p.second[0] == '+')
1015 : {
1016 1 : f_cached_json += p.second.substr(1);
1017 : }
1018 : else
1019 : {
1020 2 : f_cached_json += p.second;
1021 : }
1022 : }
1023 : else
1024 : {
1025 3 : f_cached_json += '"';
1026 18 : f_cached_json += snapdev::string_replace_many(
1027 : p.second,
1028 : {
1029 : { "\\", "\\\\" },
1030 : { "\"", "\\\"" },
1031 : { "\n", "\\n" },
1032 : { "\r", "\\r" },
1033 15 : });
1034 3 : f_cached_json += '"';
1035 : }
1036 : }
1037 :
1038 : }
1039 2 : f_cached_json += '}';
1040 : }
1041 :
1042 2 : f_cached_json += '}';
1043 : }
1044 :
1045 2 : return f_cached_json;
1046 : }
1047 :
1048 :
1049 : /** \brief Where this message came from.
1050 : *
1051 : * Some services send a message expecting an answer directly sent back
1052 : * to them. Yet, those services may have multiple instances in your cluster
1053 : * (i.e. snapcommunicator runs on all computers, snapwatchdog, snapfirewall,
1054 : * snaplock, snapdbproxy are likely to run on most computers, etc.)
1055 : * This parameter defines which computer the message came from. Thus,
1056 : * you can use that information to send the message back to that
1057 : * specific computer. The snapcommunicator on that computer will
1058 : * then forward the message to the specified service instance.
1059 : *
1060 : * If empty (the default,) then the normal snapcommunicator behavior is
1061 : * used (i.e. send to any instance of the service that is available.)
1062 : *
1063 : * \return The address and port of the specific service this message has to
1064 : * be sent to.
1065 : *
1066 : * \sa set_sent_from_server()
1067 : * \sa set_sent_from_service()
1068 : * \sa get_sent_from_service()
1069 : */
1070 6 : std::string const & message::get_sent_from_server() const
1071 : {
1072 6 : return f_sent_from_server;
1073 : }
1074 :
1075 :
1076 : /** \brief Set the name of the server that sent this message.
1077 : *
1078 : * This function saves the name of the server that was used to
1079 : * generate the message. This can be used later to send a reply
1080 : * to the service that sent this message.
1081 : *
1082 : * The snapcommunicator tool is actually in charge of setting this
1083 : * parameter and you should never have to do so from your tool.
1084 : * The set happens whenever the snapcommunicator receives a
1085 : * message from a client. If you are not using the snapcommunicator
1086 : * then you are welcome to use this function for your own needs.
1087 : *
1088 : * \param[in] sent_from_server The name of the source server.
1089 : *
1090 : * \sa get_sent_from_server()
1091 : * \sa get_sent_from_service()
1092 : * \sa set_sent_from_service()
1093 : */
1094 1 : void message::set_sent_from_server(std::string const & sent_from_server)
1095 : {
1096 1 : if(f_sent_from_server != sent_from_server)
1097 : {
1098 : // this name can be empty and it supports lowercase
1099 : //
1100 1 : verify_message_name(sent_from_server, true);
1101 :
1102 1 : f_sent_from_server = sent_from_server;
1103 1 : f_cached_message.clear();
1104 : }
1105 1 : }
1106 :
1107 :
1108 : /** \brief Who sent this message.
1109 : *
1110 : * Some services send messages expecting an answer sent right back to
1111 : * them. For example, the snaplock tool sends the message LOCKENTERING
1112 : * and expects the LOCKENTERED as a reply. The reply has to be sent
1113 : * to the exact same instance t hat sent the LOCKENTERING message.
1114 : *
1115 : * In order to do so, the system makes use of the server and service
1116 : * name the data was sent from. Since the name of each service
1117 : * registering with snapcommunicator must be unique, it 100% defines
1118 : * the sender of the that message.
1119 : *
1120 : * If empty (the default,) then the normal snapcommunicator behavior is
1121 : * used (i.e. send to any instance of the service that is available locally,
1122 : * if not available locally, try to send it to another snapcommunicator
1123 : * that knows about it.)
1124 : *
1125 : * \return The address and port of the specific service this message has to
1126 : * be sent to.
1127 : *
1128 : * \sa get_sent_from_server()
1129 : * \sa set_sent_from_server()
1130 : * \sa set_sent_from_service()
1131 : */
1132 6 : std::string const & message::get_sent_from_service() const
1133 : {
1134 6 : return f_sent_from_service;
1135 : }
1136 :
1137 :
1138 : /** \brief Set the name of the server that sent this message.
1139 : *
1140 : * This function saves the name of the service that sent this message
1141 : * to snapcommuncator. It is set by snapcommunicator whenever it receives
1142 : * a message from a service it manages so you do not have to specify this
1143 : * parameter yourselves.
1144 : *
1145 : * This can be used to provide the name of the service to reply to. This
1146 : * is useful when the receiver does not already know exactly who sends it
1147 : * certain messages.
1148 : *
1149 : * \param[in] sent_from_service The name of the service that sent this message.
1150 : *
1151 : * \sa get_sent_from_server()
1152 : * \sa set_sent_from_server()
1153 : * \sa get_sent_from_service()
1154 : */
1155 1 : void message::set_sent_from_service(std::string const & sent_from_service)
1156 : {
1157 1 : if(f_sent_from_service != sent_from_service)
1158 : {
1159 : // this name can be empty and it supports lowercase
1160 : //
1161 1 : verify_message_name(sent_from_service, true);
1162 :
1163 1 : f_sent_from_service = sent_from_service;
1164 1 : f_cached_message.clear();
1165 : }
1166 1 : }
1167 :
1168 :
1169 : /** \brief The server where this message has to be delivered.
1170 : *
1171 : * Some services need their messages to be delivered to a service
1172 : * running on a specific computer. This function returns the name
1173 : * of that server.
1174 : *
1175 : * If the function returns an empty string, then snapcommunicator is
1176 : * free to send the message to any server.
1177 : *
1178 : * \return The name of the server to send this message to or an empty string.
1179 : *
1180 : * \sa set_server()
1181 : * \sa get_service()
1182 : * \sa set_service()
1183 : */
1184 5 : std::string const & message::get_server() const
1185 : {
1186 5 : return f_server;
1187 : }
1188 :
1189 :
1190 : /** \brief Set the name of a specific server where to send this message.
1191 : *
1192 : * In some cases you may want to send a message to a service running
1193 : * on a specific server. This function can be used to specify the exact
1194 : * server where the message has to be delivered.
1195 : *
1196 : * This is particularly useful when you need to send a reply to a
1197 : * specific daemon that sent you a message.
1198 : *
1199 : * The name can be set to ".", which means send to a local service
1200 : * only, whether it is available or not. This option can be used
1201 : * to avoid/prevent sending a message to other computers.
1202 : *
1203 : * The name can be set to "*", which is useful to broadcast the message
1204 : * to all servers even if the destination service name is
1205 : * "snapcommunicator".
1206 : *
1207 : * \note
1208 : * This function works hand in hand with the "snapcommunicator". That is,
1209 : * the snapcommunicator is the one tool that makes use of the server name.
1210 : * It won't otherwise work with just the library (i.e. you have to know
1211 : * which client sent you a message to send it back to that client
1212 : * if you manage an array of clients).
1213 : *
1214 : * \param[in] server The name of the server to send this message to.
1215 : *
1216 : * \sa get_server()
1217 : * \sa get_service()
1218 : * \sa set_service()
1219 : */
1220 2 : void message::set_server(std::string const & server)
1221 : {
1222 2 : if(f_server != server)
1223 : {
1224 : // this name can be empty and it supports lowercase
1225 : //
1226 4 : if(server != "."
1227 2 : && server != "*")
1228 : {
1229 2 : verify_message_name(server, true);
1230 : }
1231 :
1232 2 : f_server = server;
1233 2 : f_cached_message.clear();
1234 : }
1235 2 : }
1236 :
1237 :
1238 : /** \brief Retrieve the name of the service the message is for.
1239 : *
1240 : * This function returns the name of the service this message is being
1241 : * sent to.
1242 : *
1243 : * \return Destination service.
1244 : *
1245 : * \sa get_server()
1246 : * \sa set_server()
1247 : * \sa set_service()
1248 : */
1249 5 : std::string const & message::get_service() const
1250 : {
1251 5 : return f_service;
1252 : }
1253 :
1254 :
1255 : /** \brief Set the name of the service this message is being sent to.
1256 : *
1257 : * This function specifies the name of the server this message is expected
1258 : * to be sent to.
1259 : *
1260 : * When a service wants to send a message to snapcommunicator, no service
1261 : * name is required.
1262 : *
1263 : * \param[in] service The name of the destination service.
1264 : *
1265 : * \sa get_server()
1266 : * \sa set_server()
1267 : * \sa get_service()
1268 : */
1269 2 : void message::set_service(std::string const & service)
1270 : {
1271 2 : if(f_service != service)
1272 : {
1273 : // broadcast is a special case that the verify_message_name() does not
1274 : // support
1275 : //
1276 4 : if(service != "*"
1277 2 : && service != "?"
1278 4 : && service != ".")
1279 : {
1280 : // this name can be empty and it supports lowercase
1281 : //
1282 2 : verify_message_name(service, true);
1283 : }
1284 :
1285 2 : f_service = service;
1286 2 : f_cached_message.clear();
1287 : }
1288 2 : }
1289 :
1290 :
1291 : /** \brief Copy sent information to this message.
1292 : *
1293 : * This function copies the sent information found in message
1294 : * to this message server and service names.
1295 : *
1296 : * This is an equivalent to the following two lines of code:
1297 : *
1298 : * \code
1299 : * reply.set_server(message.get_sent_from_server());
1300 : * reply.set_service(message.get_sent_from_service());
1301 : * \endcode
1302 : *
1303 : * \param[in] original_message The source message you want to reply to.
1304 : */
1305 1 : void message::reply_to(message const & original_message)
1306 : {
1307 1 : set_server(original_message.get_sent_from_server());
1308 1 : set_service(original_message.get_sent_from_service());
1309 1 : }
1310 :
1311 :
1312 : /** \brief Get the command being sent.
1313 : *
1314 : * Each message is an equivalent to an RPC command being send between
1315 : * services.
1316 : *
1317 : * The command is a string of text, generally one or more words
1318 : * concatenated (no space allowed) such as STOP and LOCKENTERING.
1319 : *
1320 : * \note
1321 : * The command string may still be empty if it was not yet assigned.
1322 : *
1323 : * \return The command of this message.
1324 : */
1325 36 : std::string const & message::get_command() const
1326 : {
1327 36 : return f_command;
1328 : }
1329 :
1330 :
1331 : /** \brief Set the message command.
1332 : *
1333 : * This function is used to define the RPC-like command of this message.
1334 : *
1335 : * The name of the command gets verified using the verify_message_name() function.
1336 : * It cannot be empty and all letters have to be uppercase.
1337 : *
1338 : * \param[in] command The command to send to a connection.
1339 : *
1340 : * \sa verify_message_name()
1341 : */
1342 9 : void message::set_command(std::string const & command)
1343 : {
1344 : // this name cannot be empty and it does not support lowercase
1345 : // characters either
1346 : //
1347 9 : verify_message_name(command, false, false);
1348 :
1349 9 : if(f_command != command)
1350 : {
1351 9 : f_command = command;
1352 9 : f_cached_message.clear();
1353 : }
1354 9 : }
1355 :
1356 :
1357 : /** \brief Retrieve the message version this library was compiled with.
1358 : *
1359 : * This function returns the MESSAGE_VERSION that this library was
1360 : * compiled with. Since we offer a shared object (.so) library, it
1361 : * could be different from the version your application was compiled
1362 : * with. If that's the case, your application may want to at least
1363 : * warn the user about the discrepancy.
1364 : *
1365 : * \return The MESSAGE_VERSION at the time this library was compiled.
1366 : */
1367 2 : message_version_t message::get_message_version() const
1368 : {
1369 2 : return MESSAGE_VERSION;
1370 : }
1371 :
1372 :
1373 : /** \brief Check the version parameter.
1374 : *
1375 : * This function retrieves the version parameter which has to exist and
1376 : * be an integer. If this is not the case, then an exception is raised.
1377 : *
1378 : * If the version is defined, it gets checked against this library's
1379 : * compile time MESSAGE_VERSION variable. If equal, then the function
1380 : * returns true. Otherwise, it returns false.
1381 : *
1382 : * In most cases, the very first message that you send with your service
1383 : * should be such that it includes the version the libeventdispatcher
1384 : * your program is linked against. In other words, you want to call
1385 : * the add_version_parameter() in your sender and call this function
1386 : * in your receiver.
1387 : *
1388 : * Make sure to design a way to disconnect cleanly so the other
1389 : * party knows that the communication is interrupted because the
1390 : * versions do not match. This allows your application to prevent
1391 : * re-connecting over and over again when it knows it will fail
1392 : * each time.
1393 : *
1394 : * \exception invalid_message
1395 : * If you call this function and no version parameter was added to
1396 : * the message, then this exception is raised.
1397 : *
1398 : * \return true if the version is present and valid.
1399 : */
1400 1 : bool message::check_version_parameter() const
1401 : {
1402 1 : return get_integer_parameter(MESSAGE_VERSION_NAME) == MESSAGE_VERSION;
1403 : }
1404 :
1405 :
1406 : /** \brief Add version parameter.
1407 : *
1408 : * Add a parameter named `"version"` with the current version of the
1409 : * message protocol.
1410 : *
1411 : * In the snapcommunicator tool, this is sent over with the CONNECT
1412 : * message. It allows the snapcommunicator to make sure they will
1413 : * properly understand each others.
1414 : */
1415 1 : void message::add_version_parameter()
1416 : {
1417 1 : add_parameter(MESSAGE_VERSION_NAME, MESSAGE_VERSION);
1418 1 : }
1419 :
1420 :
1421 : /** \brief Add a string parameter to the message.
1422 : *
1423 : * Messages can include parameters (variables) such as a URI or a word.
1424 : *
1425 : * The value is not limited, although it probably should be limited to
1426 : * standard text as these messages are sent as text. Especially, we
1427 : * manage the '\0' character as the end of the message.
1428 : *
1429 : * The parameter name is verified by the verify_message_name() function.
1430 : *
1431 : * \param[in] name The name of the parameter.
1432 : * \param[in] value The value of this parameter.
1433 : *
1434 : * \sa verify_message_name()
1435 : */
1436 7 : void message::add_parameter(std::string const & name, std::string const & value)
1437 : {
1438 7 : verify_message_name(name);
1439 :
1440 7 : f_parameters[name] = value;
1441 7 : f_cached_message.clear();
1442 7 : }
1443 :
1444 :
1445 : /** \brief Add an integer parameter to the message.
1446 : *
1447 : * Messages can include parameters (variables) such as a URI or a word.
1448 : *
1449 : * The value is not limited, although it probably should be limited to
1450 : * standard text as these messages are sent as text.
1451 : *
1452 : * The parameter name is verified by the verify_message_name() function.
1453 : *
1454 : * \param[in] name The name of the parameter.
1455 : * \param[in] value The value of this parameter.
1456 : *
1457 : * \sa verify_message_name()
1458 : */
1459 4 : void message::add_parameter(std::string const & name, std::int32_t value)
1460 : {
1461 4 : verify_message_name(name);
1462 :
1463 4 : f_parameters[name] = std::to_string(value);
1464 4 : f_cached_message.clear();
1465 4 : }
1466 :
1467 :
1468 : /** \brief Add an integer parameter to the message.
1469 : *
1470 : * Messages can include parameters (variables) such as a URI or a word.
1471 : *
1472 : * The value is not limited, although it probably should be limited to
1473 : * standard text as these messages are sent as text.
1474 : *
1475 : * The parameter name is verified by the verify_message_name() function.
1476 : *
1477 : * \param[in] name The name of the parameter.
1478 : * \param[in] value The value of this parameter.
1479 : *
1480 : * \sa verify_message_name()
1481 : */
1482 1 : void message::add_parameter(std::string const & name, std::uint32_t value)
1483 : {
1484 1 : verify_message_name(name);
1485 :
1486 1 : f_parameters[name] = std::to_string(value);
1487 1 : f_cached_message.clear();
1488 1 : }
1489 :
1490 :
1491 : /** \brief Add an integer parameter to the message.
1492 : *
1493 : * Messages can include parameters (variables) such as a URI or a word.
1494 : *
1495 : * The value is not limited, although it probably should be limited to
1496 : * standard text as these messages are sent as text.
1497 : *
1498 : * The parameter name is verified by the verify_message_name() function.
1499 : *
1500 : * \param[in] name The name of the parameter.
1501 : * \param[in] value The value of this parameter.
1502 : *
1503 : * \sa verify_message_name()
1504 : */
1505 1 : void message::add_parameter(std::string const & name, long long value)
1506 : {
1507 1 : verify_message_name(name);
1508 :
1509 1 : f_parameters[name] = std::to_string(value);
1510 1 : f_cached_message.clear();
1511 1 : }
1512 :
1513 :
1514 : /** \brief Add an integer parameter to the message.
1515 : *
1516 : * Messages can include parameters (variables) such as a URI or a word.
1517 : *
1518 : * The value is not limited, although it probably should be limited to
1519 : * standard text as these messages are sent as text.
1520 : *
1521 : * The parameter name is verified by the verify_message_name() function.
1522 : *
1523 : * \param[in] name The name of the parameter.
1524 : * \param[in] value The value of this parameter.
1525 : *
1526 : * \sa verify_message_name()
1527 : */
1528 1 : void message::add_parameter(std::string const & name, unsigned long long value)
1529 : {
1530 1 : verify_message_name(name);
1531 :
1532 1 : f_parameters[name] = std::to_string(value);
1533 1 : f_cached_message.clear();
1534 1 : }
1535 :
1536 :
1537 : /** \brief Add an integer parameter to the message.
1538 : *
1539 : * Messages can include parameters (variables) such as a URI or a word.
1540 : *
1541 : * The value is not limited, although it probably should be limited to
1542 : * standard text as these messages are sent as text.
1543 : *
1544 : * The parameter name is verified by the verify_message_name() function.
1545 : *
1546 : * \param[in] name The name of the parameter.
1547 : * \param[in] value The value of this parameter.
1548 : *
1549 : * \sa verify_message_name()
1550 : */
1551 1 : void message::add_parameter(std::string const & name, std::int64_t value)
1552 : {
1553 1 : verify_message_name(name);
1554 :
1555 1 : f_parameters[name] = std::to_string(value);
1556 1 : f_cached_message.clear();
1557 1 : }
1558 :
1559 :
1560 : /** \brief Add an integer parameter to the message.
1561 : *
1562 : * Messages can include parameters (variables) such as a URI or a word.
1563 : *
1564 : * The value is not limited, although it probably should be limited to
1565 : * standard text as these messages are sent as text.
1566 : *
1567 : * The parameter name is verified by the verify_message_name() function.
1568 : *
1569 : * \param[in] name The name of the parameter.
1570 : * \param[in] value The value of this parameter.
1571 : *
1572 : * \sa verify_message_name()
1573 : */
1574 1 : void message::add_parameter(std::string const & name, std::uint64_t value)
1575 : {
1576 1 : verify_message_name(name);
1577 :
1578 1 : f_parameters[name] = std::to_string(value);
1579 1 : f_cached_message.clear();
1580 1 : }
1581 :
1582 :
1583 : /** \brief Add an address parameter to the message.
1584 : *
1585 : * Messages can include parameters (variables) such as a URI or a word.
1586 : *
1587 : * This function transforms the input address in a string to be sent to
1588 : * the other end. The address IP and port are included. If the mask is
1589 : * also required, set the \p mask parameter to true.
1590 : *
1591 : * The parameter name is verified by the verify_message_name() function.
1592 : *
1593 : * \param[in] name The name of the parameter.
1594 : * \param[in] value The value of this parameter.
1595 : * \param[in] mask Set to true to include the mask.
1596 : *
1597 : * \sa verify_message_name()
1598 : */
1599 0 : void message::add_parameter(
1600 : std::string const & name
1601 : , addr::addr const & value
1602 : , bool mask)
1603 : {
1604 0 : verify_message_name(name);
1605 :
1606 0 : f_parameters[name] = value.to_ipv4or6_string(mask
1607 : ? addr::addr::string_ip_t::STRING_IP_ALL
1608 : : addr::addr::string_ip_t::STRING_IP_PORT);
1609 0 : f_cached_message.clear();
1610 0 : }
1611 :
1612 :
1613 : /** \brief Check whether a parameter is defined in this message.
1614 : *
1615 : * This function checks whether a parameter is defined in a message. If
1616 : * so it returns true. This is important because the get_parameter()
1617 : * functions throw if the parameter is not available (i.e. which is
1618 : * what is used for mandatory parameters.)
1619 : *
1620 : * The parameter name is verified by the verify_message_name() function.
1621 : *
1622 : * \param[in] name The name of the parameter.
1623 : *
1624 : * \return true if that parameter exists.
1625 : *
1626 : * \sa verify_message_name()
1627 : */
1628 64 : bool message::has_parameter(std::string const & name) const
1629 : {
1630 64 : verify_message_name(name);
1631 :
1632 64 : return f_parameters.find(name) != f_parameters.end();
1633 : }
1634 :
1635 :
1636 : /** \brief Retrieve a parameter as a string from this message.
1637 : *
1638 : * This function retrieves the named parameter from this message as a string,
1639 : * which is the default.
1640 : *
1641 : * The name must be valid as defined by the verify_message_name() function.
1642 : *
1643 : * \note
1644 : * This function returns a copy of the parameter so if you later change
1645 : * the value of that parameter, what has been returned does not change
1646 : * under your feet.
1647 : *
1648 : * \exception invalid_message
1649 : * This exception is raised whenever the parameter is not defined or
1650 : * if the parameter \p name is not considered valid.
1651 : *
1652 : * \param[in] name The name of the parameter.
1653 : *
1654 : * \return A copy of the parameter value.
1655 : *
1656 : * \sa verify_message_name()
1657 : */
1658 31 : std::string message::get_parameter(std::string const & name) const
1659 : {
1660 31 : verify_message_name(name);
1661 :
1662 31 : auto const it(f_parameters.find(name));
1663 31 : if(it != f_parameters.end())
1664 : {
1665 62 : return it->second;
1666 : }
1667 :
1668 : throw invalid_message(
1669 : "message::get_parameter(): parameter \""
1670 0 : + name
1671 0 : + "\" of command \""
1672 0 : + f_command
1673 0 : + "\" not defined, try has_parameter() before calling"
1674 0 : " the get_parameter() function.");
1675 : }
1676 :
1677 :
1678 : /** \brief Retrieve a parameter as an integer from this message.
1679 : *
1680 : * This function retrieves the named parameter from this message as a string,
1681 : * which is the default.
1682 : *
1683 : * The name must be valid as defined by the verify_message_name() function.
1684 : *
1685 : * \exception invalid_message
1686 : * This exception is raised whenever the parameter is not a valid integer,
1687 : * it is not set, or the parameter name is not considered valid.
1688 : *
1689 : * \param[in] name The name of the parameter.
1690 : *
1691 : * \return The parameter converted to an integer.
1692 : *
1693 : * \sa verify_message_name()
1694 : */
1695 16 : std::int64_t message::get_integer_parameter(std::string const & name) const
1696 : {
1697 16 : verify_message_name(name);
1698 :
1699 16 : auto const it(f_parameters.find(name));
1700 16 : if(it != f_parameters.end())
1701 : {
1702 16 : std::int64_t r;
1703 16 : if(!advgetopt::validator_integer::convert_string(it->second, r))
1704 : {
1705 : throw invalid_message(
1706 : "message::get_integer_parameter(): command \""
1707 0 : + f_command
1708 0 : + "\" expected integer for \""
1709 0 : + name
1710 0 : + "\" but \""
1711 0 : + it->second
1712 0 : + "\" could not be converted.");
1713 : }
1714 32 : return r;
1715 : }
1716 :
1717 : throw invalid_message(
1718 : "message::get_integer_parameter(): parameter \""
1719 0 : + name
1720 0 : + "\" of command \""
1721 0 : + f_command
1722 0 : + "\" not defined, try has_parameter() before calling"
1723 0 : " the get_integer_parameter() function.");
1724 : }
1725 :
1726 :
1727 : /** \brief Retrieve the list of parameters from this message.
1728 : *
1729 : * This function returns a constant reference to the list of parameters
1730 : * defined in this message.
1731 : *
1732 : * This can be useful if you allow for variable lists of parameters, but
1733 : * generally the get_parameter() and get_integer_parameter() are preferred.
1734 : *
1735 : * \warning
1736 : * This is a direct reference to the list of parameter. If you call the
1737 : * add_parameter() function, the new parameter will be visible in that
1738 : * new list and an iterator is likely not going to be valid on return
1739 : * from that call.
1740 : *
1741 : * \return A constant reference to the list of message parameters.
1742 : *
1743 : * \sa get_parameter()
1744 : * \sa get_integer_parameter()
1745 : */
1746 2 : message::parameters_t const & message::get_all_parameters() const
1747 : {
1748 2 : return f_parameters;
1749 : }
1750 :
1751 :
1752 : /** \brief Verify various names used with messages.
1753 : *
1754 : * The messages use names for:
1755 : *
1756 : * \li commands
1757 : * \li services
1758 : * \li parameters
1759 : *
1760 : * All those names must be valid as per this function. They are checked
1761 : * on read and on write (i.e. add_parameter() and get_parameter() both
1762 : * check the parameter name to make sure you did not mistype it.)
1763 : *
1764 : * A valid name must start with a letter or an underscore (although
1765 : * we suggest you do not start names with underscores; we want to
1766 : * have those reserved for low level system like messages,) and
1767 : * it can only include letters, digits, and underscores.
1768 : *
1769 : * The letters are limited to uppercase for commands. Also certain
1770 : * names may be empty (See concerned functions for details on that one.)
1771 : *
1772 : * \note
1773 : * The allowed letters are 'a' to 'z' and 'A' to 'Z' only. The allowed
1774 : * digits are '0' to '9' only. The underscore is '_' only.
1775 : *
1776 : * A few valid names:
1777 : *
1778 : * \li commands: PING, STOP, LOCK, LOCKED, QUITTING, UNKNOWN, LOCKEXITING
1779 : * \li services: snapcommunicator, snapserver, snaplock, MyOwnService
1780 : * \li parameters: URI, name, IP, TimeOut
1781 : *
1782 : * At this point all our services use lowercase, but this is not enforced.
1783 : * Actually, mixed case or uppercase service names are allowed.
1784 : *
1785 : * \exception invalid_message
1786 : * This exception is raised if the name includes characters considered
1787 : * invalid.
1788 : *
1789 : * \param[in] name The name of the parameter.
1790 : * \param[in] can_be_empty Whether the name can be empty.
1791 : * \param[in] can_be_lowercase Whether the name can include lowercase letters.
1792 : */
1793 150 : void verify_message_name(std::string const & name, bool can_be_empty, bool can_be_lowercase)
1794 : {
1795 300 : if(!can_be_empty
1796 150 : && name.empty())
1797 : {
1798 0 : std::string const err("a message name cannot be empty.");
1799 0 : SNAP_LOG_FATAL
1800 : << err
1801 : << SNAP_LOG_SEND;
1802 0 : throw invalid_message(err);
1803 : }
1804 :
1805 948 : for(auto const & c : name)
1806 : {
1807 798 : if((c < 'a' || c > 'z' || !can_be_lowercase)
1808 70 : && (c < 'A' || c > 'Z')
1809 33 : && (c < '0' || c > '9')
1810 3 : && c != '_')
1811 : {
1812 0 : std::string err("a ");
1813 0 : err += can_be_lowercase ? "parameter" : "command";
1814 0 : err += " name must be composed of ASCII ";
1815 0 : if(can_be_lowercase)
1816 : {
1817 0 : err += "'a'..'z', ";
1818 : }
1819 0 : err += "'A'..'Z', '0'..'9', or '_'"
1820 : " only (also a command must be uppercase only), \"";
1821 0 : for(auto const & x : name)
1822 : {
1823 0 : if(x < 0x20)
1824 : {
1825 0 : err += '^';
1826 0 : err += x + '@';
1827 : }
1828 0 : else if(c >= 0x7F)
1829 : {
1830 0 : err += "\\x";
1831 0 : err += snapdev::int_to_hex(x, true, 2);
1832 : }
1833 : else
1834 : {
1835 0 : err += x;
1836 : }
1837 : }
1838 0 : err += "\" is not valid.";
1839 0 : SNAP_LOG_FATAL
1840 : << err
1841 : << SNAP_LOG_SEND;
1842 0 : throw invalid_message(err);
1843 : }
1844 : }
1845 :
1846 300 : if(!name.empty()
1847 150 : && name[0] >= '0' && name[0] <= '9')
1848 : {
1849 0 : std::string err("parameter name cannot start with a digit, \"");
1850 0 : err += name;
1851 0 : err += "\" is not valid.";
1852 0 : SNAP_LOG_FATAL
1853 : << err
1854 : << SNAP_LOG_SEND;
1855 0 : throw invalid_message(err);
1856 : }
1857 150 : }
1858 :
1859 :
1860 :
1861 :
1862 6 : } // namespace ed
1863 : // vim: ts=4 sw=4 et
|