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