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

Generated by: LCOV version 1.13