LCOV - code coverage report
Current view: top level - eventdispatcher - message.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 315 451 69.8 %
Date: 2022-02-12 12:27:47 Functions: 34 34 100.0 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.13