Remote Procedure Call (RPC) over Websockets (uses MbedJSONValue)
Dependents: RPC_mbed_client RPC_Wifly_HelloWorld RPC_Ethernet_HelloWorld
Revision 0:a53d1c86196c, committed 2011-09-22
- Comitter:
- samux
- Date:
- Thu Sep 22 10:14:52 2011 +0000
- Child:
- 1:7b7230760e0c
- Commit message:
Changed in this revision
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MbedJSONRpc.cpp Thu Sep 22 10:14:52 2011 +0000 @@ -0,0 +1,164 @@ +#include "MbedJSONRpc.h" + +RPC_TYPE MbedJSONRpc::call(char * fn, char * dest, MbedRpcValue& val, MbedRpcValue& resp) { + + char json[150]; + RPC_TYPE t; + string str = val.serialize(); + int id = rand() % 100; + MbedRpcValue tmp; + + + sprintf(json, (const char *)MSG_CALL, my_id, dest, id, fn, str.c_str()); + webs->Send(json); + t = waitAnswer(tmp, id, json); + if (t != CALL_OK) + return t; + resp = tmp["res"]; + return CALL_OK; +} + +RPC_TYPE MbedJSONRpc::waitAnswer(MbedRpcValue& v, int id, char * json) { + Timer tmr; + + tmr.start(); + while (1) { + if (tmr.read() > 5.0) { + #ifdef DEBUG + printf("timeout\r\n"); + #endif + return SERVER_NOT_CONNECTED; + } + if (webs->read(json)) { + #ifdef DEBUG + printf("receive: %s\r\n", json); + #endif + parse(v, json); + return decodeMsg(v, id); + } + } +} + +int MbedJSONRpc::methodAlreadyRegistered(char * s) { + for (int i = 0; i < index; i++) + if (!strcmp(name[i], s)) + return i; + return -1; +} + + +int MbedJSONRpc::procAlreadyRegistered(char * s) { + for (int i = 0; i < index_proc; i++) + if (!strcmp(name_proc[i], s)) + return i; + return -1; +} + +void MbedJSONRpc::checkMethods(char * dest) { + char json[150]; + char name[5]; + MbedRpcValue tmp; + int id = rand() % 100; + + sprintf(json, (const char *)MSG_INFO_METHODS, my_id, dest, id); + webs->Send(json); + waitAnswer(tmp, id, json); + printf("methods available on %s: ", dest); + for (int i = 0; i < tmp.size() - 1; i++) { + sprintf(name, "fn%d", i); + printf("%s%c ", tmp[name].get<string>().c_str(), (i == tmp.size() - 2) ? ' ' : ','); + } + printf("\r\n"); +} + +RPC_TYPE MbedJSONRpc::decodeMsg(MbedRpcValue& v, int id) { + + if (v.hasMember("id_msg")) + if (v["id_msg"].get<int>() != -1 && id != v["id_msg"].get<int>()) { + #ifdef DEBUG + printf("bad id: %d\r\n",v["id_msg"].get<int>() ); + #endif + return ERR_ID; + } + + if (v.hasMember("msg")) { + std::string s = v["msg"].get<std::string>(); + if (!strcmp(s.c_str(), "RESULT")) { + return CALL_OK; + } + if (!strcmp(s.c_str(), "REGISTER_OK")) { + return REGISTER_OK; + } + } + + //there is an error + if (v.hasMember("cause")) { + std::string s = v["cause"].get<std::string>(); + if (!strcmp(s.c_str(), "JSON_PARSE_ERROR")) + return JSON_PARSE_ERROR; + if (!strcmp(s.c_str(), "JSON_RPC_ERROR")) + return RPC_PARSE_ERROR; + else if (!strcmp(s.c_str(), "METHOD_NOT_FOUND")) + return PROC_NOT_FOUND; + else if (!strcmp(s.c_str(), "CLIENT_NOT_CONNECTED")) + return CLIENT_NOT_CONNECTED; + } + return RPC_PARSE_ERROR; + +} + + +RPC_TYPE MbedJSONRpc::registerMethod(const char * public_name, void (*fn)(MbedRpcValue& val, MbedRpcValue& res) ) { + char json[100]; + int id = rand() % 100; + MbedRpcValue tmp; + RPC_TYPE t; + + sprintf(json, (const char *)MSG_REGISTER, my_id, id, public_name); + webs->Send(json); + t = waitAnswer(tmp, id, json); + if (t != REGISTER_OK) + return t; + if( index_proc == NB_METH ) + index_proc = NB_METH - 1; + proc[index_proc] = fn; + name_proc[index_proc++] = public_name; + return REGISTER_OK; +} + + +void MbedJSONRpc::work() { + char json_recv[150]; + DigitalOut led4(LED4); + MbedRpcValue v, r; + int i = -1; + while (1) { + wait(0.2); + if (webs->read(json_recv)) { + parse(v, json_recv); + if (v.hasMember("method") && v.hasMember("from") && v.hasMember("id_msg") && v.hasMember("params") && v.hasMember("msg") && !strcmp(v["msg"].get<std::string>().c_str(), "CALL")) { + + string s = v["method"].get<std::string>(); + + if ((i = methodAlreadyRegistered((char *)s.c_str())) != -1) { + + obj[i]->execute(v["params"], r); + sprintf(json_recv, (const char *)MSG_RESULT, my_id, + v["from"].get<std::string>().c_str(), v["id_msg"].get<int>(), r.serialize().c_str()); + webs->Send(json_recv); + + } else if ((i = procAlreadyRegistered((char *)s.c_str())) != -1) { + + proc[i](v["params"], r); + sprintf(json_recv, (const char *)MSG_RESULT, my_id, + v["from"].get<std::string>().c_str(), v["id_msg"].get<int>(), r.serialize().c_str()); + webs->Send(json_recv); + + } + } + + } + //show that we are alive + led4 = !led4; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MbedJSONRpc.h Thu Sep 22 10:14:52 2011 +0000 @@ -0,0 +1,377 @@ +/** +* @author Samuel Mokrani +* +* @section LICENSE +* +* Copyright (c) 2011 mbed +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +* +* @section DESCRIPTION +* Main part of MbedJSONRpc: you can register methods or procedures, call a registered method or procedure +* on a client over a websocket communication, see all methods available on a certain client, listen for +* CALL incoming messages to execute a method or procedure. +* +*/ + + +#ifndef _MbedJSONRPC_H_ +#define _MbedJSONRPC_H_ + +#include "Websocket.h" +#include "Wifly.h" +#include "mbed.h" +#include "MbedJSONRpcValue.h" +#include <string> + +#define NB_METH 30 + +#define MSG_CALL "{\"from\": \"%s\",\"to\": \"%s\",\"msg\": \"CALL\",\"id_msg\": %d,\"method\": \"%s\",\"params\":%s}" +/**< Message skeleton which will be use for a call */ +#define MSG_RESULT "{\"from\": \"%s\",\"to\": \"%s\",\"msg\": \"RESULT\",\"id_msg\": %d,\"res\":%s}" +/**< Message skeleton which will be use for a result */ +#define MSG_REGISTER "{\"from\": \"%s\",\"to\": \"gateway\",\"msg\": \"REGISTER\",\"id_msg\": %d,\"fn\":\"%s\"}" +/**< Message skeleton which will be use for register a method or a procedure */ +#define MSG_INFO_METHODS "{\"from\": \"%s\",\"to\": \"%s\",\"msg\": \"INFO_METHODS\",\"id_msg\": %d}" +/**< Message skeleton which will be send to see all methods available of a certain client */ + + +/** +* \enum RPC_TYPE +* \brief Type of an RPC transaction +*/ +enum RPC_TYPE { + JSON_PARSE_ERROR, /*!< Json parse error */ + RPC_PARSE_ERROR, /*!< rpc parse error (the message doesn't contain good identifiers in a TypeObject MbedRpcValue)*/ + PROC_NOT_FOUND, /*!< The user wants to call an inexisting method or procedure */ + CLIENT_NOT_CONNECTED, /*!< The user wants to call a method or procedure on a client not connected (no response from the client within 3s) */ + SERVER_NOT_CONNECTED, /*!< No response from the server within 5s */ + ERR_ID, /*!< Each messages have an id, if the id of the answer doesn't match with the id of the request, there is an id error */ + CALL_OK, /*!< A call has been successfully executed */ + REGISTER_OK /*!< A request to register a method or procedure is successful */ +}; + + + +class ObjBase { +public: + virtual void execute(MbedRpcValue& val, MbedRpcValue& res) = 0; +}; + +template <class T> +class Obj : public ObjBase { +public: + + Obj(T * ptr, void (T::*fn_ptr)(MbedRpcValue& , MbedRpcValue& )) { + obj_ptr = ptr; + fn = fn_ptr; + }; + + virtual void execute(MbedRpcValue& val, MbedRpcValue& res) { + ((*obj_ptr).*fn)(val, res); + } + + //obj + T * obj_ptr; + + //method + void (T::*fn)(MbedRpcValue& , MbedRpcValue& ); + +}; + + +/** MbedJSONRpc class + * + * Warning: you must use a wifi module (Wifly RN131-C) or an ethernet network to use this class + * + * Example (client which registers one method): + * @code + * #include "mbed.h" + * #include "MbedJSONRpc.h" + * #include "MbedJSONRpcValue.h" + * #include "Websocket.h" + * + * #define ETHERNET + * + * #ifdef WIFI + * #include "Wifly.h" + * #endif + * + * DigitalOut led1(LED1); + * Serial pc(USBTX, USBRX); + * + * + * class Test { + * public: + * Test() {}; + * void fn(JSONRpcValue& val, JSONRpcValue& res) { + * printf("first arg: %s\r\n", val[0].get<string>().c_str()); + * res[0] = "coucou"; + * led1 = 1; + * } + * }; + * + * + * #ifdef WIFI + * //wifi and websocket + * Wifly wifly(p9, p10, p30, "mbed", "mbedapm2011", true); + * Websocket webs("ws://sockets.mbed.org:888/ws/samux/server",&wifly); + * #endif + * + * #ifdef ETHERNET + * Websocket webs("ws://sockets.mbed.org:888/ws/samux/server"); + * #endif + * + * //RPC object identified with the name "server" on the network and attached to the websocket server + * MbedJSONRpc rpc("server", &webs); + * + * Test test; + * + * int main() { + * + * RPC_TYPE t; + * + * + * //connection to the router and to the websocket server + * #ifdef WIFI + * while (1) { + * + * while (!wifly.Join()) //we connect to the network + * wifly.reset(); + * + * if (!webs.connect()) //we connect to the server + * wifly.reset(); + * else + * break; + * } + * #endif + * + * #ifdef ETHERNET + * while(!webs.connect()) + * pc.printf("cannot connect websocket, retrying\r\n"); + * #endif + * + * //register the method Test::fn as "fn1" + * if((t = rpc.registerMethod("fn1", &test, &Test::fn)) == REGISTER_OK) + * printf("register ok\r\n"); + * else + * printType(t); + * + * //Listen CALL requests + * rpc.work(); + * } + * @endcode + * + * + * + * Example (client which calls the previous registered method): + * @code + * #include "mbed.h" + * #include "MbedJSONRpc.h" + * #include "MbedJSONRpcValue.h" + * #include "Websocket.h" + * + * #define WIFI + * + * #ifdef WIFI + * #include "Wifly.h" + * #endif + * + * Serial pc(USBTX, USBRX); + * + * #ifdef WIFI + * //wifi and websocket + * Wifly wifly(p9, p10, p21, "mbed", "mbedapm2011", true); + * Websocket webs("ws://sockets.mbed.org:888/ws/samux/client",&wifly); + * #endif + * + * #ifdef ETHERNET + * Websocket webs("ws://sockets.mbed.org:888/ws/samux/client"); + * #endif + * + * //RPC object identified by the name "client" on the network and attached to the websocket server + * MbedJSONRpc rpc("client", &webs); + * + * int main() { + * + * //connection to the router and to the websocket server + * #ifdef WIFI + * while (1) { + * + * while (!wifly.Join()) //we connect to the network + * wifly.reset(); + * + * if (!webs.connect()) //we connect to the server + * wifly.reset(); + * else + * break; + * } + * #endif + * + * #ifdef ETHERNET + * while(!webs.connect()) + * pc.printf("cannot connect websocket, retrying\r\n"); + * #endif + * + * RPC_TYPE t; + * MbedRpcValue arg, resp; + * arg[0] = "Hello"; + * + * // print all methods and procedure available on the client "server" + * rpc.checkMethods("server"); + * + * // Try to call the function "fn1" registered by server previously + * if((t = rpc.call("fn1", "server", arg, resp)) == CALL_OK) + * { + * printf("call success\r\n"); + * printf("res: %s\r\n", resp[0].get<string>().c_str()); + * } + * else + * printType(t); + * } + * @endcode + */ +class MbedJSONRpc { +public: + + /** + * Constructor + * + * @param id Name of the client on the network + * @param ws All communication between clients will be established over this websocket + */ + MbedJSONRpc(Websocket * webs) : webs(webs), index(0), index_proc(0) { + std::string path = webs->getPath(); + std::string token = "/"; + size_t found; + found = path.find(token, 3); + if (found != std::string::npos) + path = path.substr(found + 1, std::string::npos); + strcpy(my_id, path.c_str()); + }; + + /** + * Register a method of an object + * + * @param public_name the method will be seen and called by this name + * @param obj_ptr a pointeur on the object which contains the method to register + * @param fn the method to register (this method must have this signature: void fn(MbedRpcValue& val, MbedRpcValue& res) + * @return if REGISTER_OK, the method is registered, otherwise, there is an error + * + */ + template<typename T> RPC_TYPE registerMethod(const char * public_name, T * obj_ptr, void (T::*fn)(MbedRpcValue& val, MbedRpcValue& res)) { + char json[100]; + RPC_TYPE t; + Timer tmr; + int id = rand() % 100; + MbedRpcValue tmp; + + sprintf(json, (const char *)MSG_REGISTER, my_id, id, public_name); + webs->Send(json); + tmr.start(); + t = waitAnswer(tmp, id, json); + if (t != REGISTER_OK) + return t; + if( index == NB_METH ) + index = NB_METH - 1; + obj[index] = new Obj<T>(obj_ptr, fn); + name[index++] = public_name; + return REGISTER_OK; + } + + + /** + * Register a procedure + * + * @param public_name the method will be seen and called by this name + * @param fn the procedure to register (this procedure must have this signature: void fn(MbedRpcValue& val, MbedRpcValue& res) + * @return if REGISTER_OK, the procedure is registered, otherwise, there is an error + * + */ + RPC_TYPE registerMethod(const char * public_name, void (*fn)(MbedRpcValue& val, MbedRpcValue& res) ); + + + /** + * Call a method or procedure on a client + * + * @param fn name of the method or procedure to be called + * @param dest name of the client where will be executed the method or procedure + * @param val Input parameters of the method or procedure "fn" + * @param resp Once the method or procedure executed, the result will be stored in the "resp" variable + * @return If CALL_OK, the method has been executed and you can access the result with the "resp" variable. + * If CLIENT_NOT_CONNECTED, means that the client hasn't answered within 3s. + * If SERVER_NOT_CONNECTED, means that the server hasn't answered within 5s. + * Refer to the RPC_TYPE description + * + */ + RPC_TYPE call(char * fn, char * dest, MbedRpcValue& val, MbedRpcValue& resp); + + + /** + * Listen for CALL requests + * Warning: infinite loop + */ + void work(); + + /** + * Print by the usb serial port all methods or procedure available on "dest" client + */ + void checkMethods(char * dest); + +private: + + typedef void (*FNPTR)(MbedRpcValue& , MbedRpcValue& ); + + int methodAlreadyRegistered(char *); + int procAlreadyRegistered(char *); + RPC_TYPE decodeMsg(MbedRpcValue& , int); + RPC_TYPE waitAnswer(MbedRpcValue& , int, char *); + + Websocket * webs; + + ObjBase * obj[NB_METH]; + const char * name[NB_METH]; + int index; + + FNPTR proc[NB_METH]; + const char * name_proc[NB_METH]; + int index_proc; + + + char my_id[20]; + +}; + + +inline void printType(RPC_TYPE t) +{ + switch(t) + { + case JSON_PARSE_ERROR: printf("json parse error\r\n"); break; + case RPC_PARSE_ERROR: printf("rpc parse error\r\n"); break; + case PROC_NOT_FOUND: printf("proc or method not found\r\n"); break; + case CLIENT_NOT_CONNECTED: printf("client not connected\r\n"); break; + case SERVER_NOT_CONNECTED: printf("server not connected\r\n"); break; + case ERR_ID: printf("id error\r\n"); break; + case CALL_OK: printf("call ok\r\n"); break; + case REGISTER_OK: printf("register ok\r\n"); break; + } +} + +#endif \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MbedJSONRpcValue.cpp Thu Sep 22 10:14:52 2011 +0000 @@ -0,0 +1,245 @@ +#include "MbedJSONRpcValue.h" + +# include <stdlib.h> +# include <stdio.h> + +// Clean up +void MbedRpcValue::clean() { + switch (_type) { + case TypeString: + delete _value.asString; + break; + case TypeArray: + for (int i = 0; i < index_array; i++) + delete array[i]; + index_array = 0; + break; + case TypeObject: + for (int i = 0; i < index_token; i++) { + delete token[i]; + delete token_name[i]; + } + index_token = 0; + break; + default: + break; + } + _type = TypeNull; + _type = TypeNull; +} + +bool MbedRpcValue::hasMember(char * name) +{ + for(int i = 0; i < index_token; i++) + if( !strcmp(name, (*(token_name[i])).c_str() )) + return true; + return false; +} + + +void copy(const std::string& s, std::back_insert_iterator<std::string> oi) { + std::copy(s.begin(), s.end(), oi); +} + +void serialize_str(const std::string& s, std::back_insert_iterator<std::string> oi) { + *oi++ = '"'; + for (std::string::const_iterator i = s.begin(); i != s.end(); ++i) { + switch (*i) { +#define MAP(val, sym) case val: copy(sym, oi); break + MAP('"', "\\\""); + MAP('\\', "\\\\"); + MAP('/', "\\/"); + MAP('\b', "\\b"); + MAP('\f', "\\f"); + MAP('\n', "\\n"); + MAP('\r', "\\r"); + MAP('\t', "\\t"); +#undef MAP + default: + if ((unsigned char)*i < 0x20 || *i == 0x7f) { + char buf[7]; + sprintf(buf, "\\u%04x", *i & 0xff); + copy(buf, buf + 6, oi); + } else { + *oi++ = *i; + } + break; + } + } + *oi++ = '"'; +} + +std::string MbedRpcValue::serialize(){ + std::string s; + serialize(std::back_inserter(s)); + return s; +} + +std::string MbedRpcValue::to_str(){ + switch (_type) { + case TypeNull: + return "null"; + case TypeBoolean: + return _value.asBool ? "true" : "false"; + case TypeInt: { + char buf[10]; + sprintf(buf, "%d", _value.asInt); + return buf; + } + case TypeDouble: { + char buf[10]; + sprintf(buf, "%f", _value.asDouble); + return buf; + } + default: + break; + } + return NULL; +} + + + +void MbedRpcValue::serialize(std::back_insert_iterator<std::string> oi) { + switch (_type) { + case TypeString: + serialize_str(*_value.asString, oi); + break; + case TypeArray: { + *oi++ = '['; + for (int i = 0; i < index_array; i++) { + if (i) + *oi++ = ','; + (*this)[i].serialize(oi); + } + *oi++ = ']'; + break; + } + case TypeObject: { + *oi++ = '{'; + for (int i = 0; i < index_token; i++) { + if (i) + *oi++ = ','; + serialize_str(*(token_name[i]), oi); + *oi++ = ':'; + (*(token[i])).serialize(oi); + } + *oi++ = '}'; + break; + } + default: + copy(to_str(), oi); + break; + } +} + + + +MbedRpcValue& MbedRpcValue::operator[](int i) { + _type = TypeArray; + if (i < NB_TOKEN && index_array == i ) { +#ifdef DEBUG + printf("will add an element to the array\r\n"); +#endif + array[i] = new MbedRpcValue(); + index_array++; + return *(array[i]); + } + if (i < NB_TOKEN && index_array > i) + return *(array[i]); + + //if the user is not doing something wrong, this code is never executed!! + return *(new MbedRpcValue()); +} + +MbedRpcValue& MbedRpcValue::operator[](std::string k) { + _type = TypeObject; + for (int i = 0; i < index_token; i++) { +#ifdef DEBUG + printf("k: %s\r\n", k.c_str()); + printf("str: %s\r\n", token_name[i]->c_str()); +#endif + //existing token + if (!strcmp(k.c_str(), token_name[i]->c_str())) { +#ifdef DEBUG + printf("token found: %d\r\n", i); +#endif + return *(token[i]); + } + } + + if(index_token >= NB_TOKEN) + index_token = NB_TOKEN - 1; + //non existing token + token_name[index_token] = new std::string(k); + token[index_token] = new MbedRpcValue(); + index_token++; + return *(token[index_token - 1]); +} + +MbedRpcValue& MbedRpcValue::operator[](std::string k) const +{ + for (int i = 0; i < index_token; i++) { +#ifdef DEBUG + printf("k: %s\r\n", k.c_str()); + printf("str: %s\r\n", token_name[i]->c_str()); +#endif + if (!strcmp(k.c_str(), token_name[i]->c_str())) { +#ifdef DEBUG + printf("token found: %d\r\n", i); +#endif + return *(token[i]); + } + } + + //if the user is not doing something wrong, this code is never executed!! + return *(new MbedRpcValue()); +} + + +// Operators +MbedRpcValue& MbedRpcValue::operator=(MbedRpcValue const& rhs) { + if (this != &rhs) { + clean(); + _type = rhs._type; + switch (_type) { + case TypeBoolean: + _value.asBool = rhs._value.asBool; + break; + case TypeInt: + _value.asInt = rhs._value.asInt; + break; + case TypeDouble: + _value.asDouble = rhs._value.asDouble; + break; + case TypeString: + _value.asString = new std::string(*rhs._value.asString); + break; + case TypeArray: + for (int i = 0; i < rhs.index_array; i++) + (*this)[i] = rhs[i]; + case TypeObject: + for (int i = 0; i < rhs.index_token; i++) + (*this)[*(rhs.token_name[i])] = rhs[*(rhs.token_name[i])]; + default: + break; + } + } + return *this; +} + + +// Works for strings, arrays, and structs. +int MbedRpcValue::size() const { + switch (_type) { + case TypeString: + return int(_value.asString->size()); + case TypeArray: + return index_array; + case TypeObject: + return index_token; + default: + break; + } + return -1; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MbedJSONRpcValue.h Thu Sep 22 10:14:52 2011 +0000 @@ -0,0 +1,582 @@ +/** +* @author Samuel Mokrani +* +* @section LICENSE +* +* Copyright (c) 2011 mbed +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +* +* @section DESCRIPTION +* Types Abstraction for the RPC communication +* +*/ + +#ifndef _MbedJSON_RPC_VALUE_H_ +#define _MbedJSON_RPC_VALUE_H_ + +#define NB_TOKEN 20 +/*!< Number maximum of MbedRpcValue in an array or an object */ + +#include <string> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/** MbedRpcValue class + * + * Example: + * - creation of an MbedRpcValue of type TypeObject containing two others MbedRpcValue: + * -one array of one string and one integer identified by "my_array" + * -a boolean identified by "my_boolean" + * - serialization in JSON format of this object + * @code + * MbedRpcValue demo; + * std::string s; + * + * //fill the object + * demo["my_array"][0] = "demo_string"; + * demo["my_array"][1] = 10; + * demo["my_boolean"] = true; + * + * //serialize it into a JSON string + * s = demo.serialize(); + * + * + * @endcode + * + * Example: + * - creation of an MbedRpcValue from a JSON string + * - extraction of different values from this existing MbedRpcValue + * @code + * + * MbedRpcValue demo; + * + * const char * json = "{\"my_array\": [\"demo_string\", 10], \"my_boolean\": true}"; + * + * //parse the previous string and fill the object demo + * parse(demo, json); + * + * std::string my_str; + * int my_int; + * bool my_bool; + * + * my_str = demo["my_array"][0]; + * my_int = demo["my_array"][1]; + * my_bool = demo["my_boolean"]; + * + * @endcode + */ +class MbedRpcValue { +public: + + /** + * \enum Type + * \brief All types which can be used + */ + enum Type { + TypeNull, /*!< Null type */ + TypeBoolean, /*!< Boolean */ + TypeInt, /*!< Integer */ + TypeDouble, /*!< Double */ + TypeString, /*!< String */ + TypeArray, /*!< Array of Type */ + TypeObject /*!< Object */ + }; + + /** + * MbedRpcValue constructor of type TypeNull + */ + MbedRpcValue() : _type(TypeNull), index_array(0), index_token(0) {} + + /** + * MbedRpcValue constructor of type TypeBoolean + * + * @param value the object created will be initialized with this boolean + */ + MbedRpcValue(bool value) : _type(TypeBoolean), index_array(0), index_token(0) { + _value.asBool = value; + } + + /** + * MbedRpcValue constructor of type TypeInt + * + * @param value the object created will be initialized with this integer + */ + MbedRpcValue(int value) : _type(TypeInt), index_array(0), index_token(0) { + _value.asInt = value; + } + + /** + * MbedRpcValue constructor of type TypeDouble + * + * @param value the object created will be initialized with this double + */ + MbedRpcValue(double value) : _type(TypeDouble), index_array(0), index_token(0) { + _value.asDouble = value; + } + + /** + * MbedRpcValue constructor of type TypeString + * + * @param value the object created will be initialized with this string + */ + MbedRpcValue(std::string const& value) : _type(TypeString), index_array(0), index_token(0) { + _value.asString = new std::string(value); + } + + /** + * MbedRpcValue constructor of type TypeString + * + * @param value the object created will be initialized with this string + */ + MbedRpcValue(const char* value) : _type(TypeString), index_array(0), index_token(0) { + _value.asString = new std::string(value); + } + + /** + * Copy constructor + * + * @param rhs object which will be copied + */ + MbedRpcValue(MbedRpcValue const& rhs) : _type(TypeNull) { *this = rhs; } + + /** + * Destructor + */ + ~MbedRpcValue() { clean(); } + + /** + * = Operator overloading for an MbedRpcValue from an MbedRpcValue + * + * @param rhs object + * @return a reference on the MbedRpcValue affected + */ + MbedRpcValue& operator=(MbedRpcValue const & rhs); + + /** + * = Operator overloading for an MbedRpcValue from an int + * + * @param rhs + * @return a reference on the MbedRpcValue affected + */ + MbedRpcValue& operator=(int const& rhs) { return operator=(MbedRpcValue(rhs)); } + + /** + * = Operator overloading for an MbedRpcValue from a double + * + * @param rhs + * @return a reference on the MbedRpcValue affected + */ + MbedRpcValue& operator=(double const& rhs) { return operator=(MbedRpcValue(rhs)); } + + /** + * = Operator overloading for an MbedRpcValue from a string + * + * @param rhs + * @return a reference on the MbedRpcValue affected + */ + MbedRpcValue& operator=(const char* rhs) { return operator=(MbedRpcValue(std::string(rhs))); } + + + /** + * [] Operator overloading for an MbedRpcValue. + * Each TypeObject object can contain an array of NB_TOKEN MbedRpcvalue. + * This operator is useful to create an array or to retrieve an MbedRpcValue of an existing array. + * + * For instance, the following code creates an MbedRpcValue of type TypeArray and the first object + * of this array is an MbedRpcValue of type TypeString: + * MbedRpcValue my_mbed_rpc_value; + * my_mbed_rpc_value[0] = "hello"; + * + * Once an array is created, you access each elements by my_mbed_rpc_value[i] + * + * @param i index of the array + * @return a reference on the MbedRpcValue created or retrieved + */ + MbedRpcValue& operator[](int i); + + /** + * [] Operator overloading for an MbedRpcValue. + * Each TypeObject MbedRpcValue can contain NB_TOKEN MbedRpcValue IDENTIFIED BY A NAME + * This operator is useful to create a TypeObject MbedRpcValue or to retrieve an MbedRpcValue of an existing TypeObject. + * + * For instance, the following code creates an MbedRpcValue of type TypeObject and the first object + * of this "map" is an MbedRpcValue of type TypeString identified with the name "my_string": + * MbedRpcValue my_mbed_rpc_value; + * my_mbed_rpc_value["my_string"] = "hello"; + * + * Once an Object is created, you access each elements by my_mbed_rpc_value["my_identifier"] + * + * + * @param str identifier of the sub MbedRpcValue + * @return a reference on the MbedRpcValue created or retrieved + */ + MbedRpcValue& operator[](std::string str); + + /** + * Retrieve the value of an MbedRpcValue object. + * + * Let's suppose that we have an MbedRpcValue of type TypeString. + * To retrieve this string, we have to do: + * my_obj.get<std::string>(); + * + * @return A contant reference on the value of the object + */ + template <typename T> const T& get() const; + + /** + * Retrieve the value of an MbedRpcValue object. + * + * Let's suppose that we have an MbedRpcValue of type TypeInt. + * To retrieve this integer, we have to do: + * my_obj.get<int>(); + * + * @return A reference on the value of the object + */ + template <typename T> T& get(); + + + /** + * Return the type of the MbedRpcValue object + * + * @return type of the MbedRpcValue object + */ + Type const &getType() const { + return _type; + } + + /** + * Return the size of an MbedRpcValue object (works for TypeString, TypeArray or TypeObject) + * + * @param size + */ + int size() const; + + /** + * Check for the existence in a TypeObject object of member identified by name + * + * @param name Identifier + * @return true if the object is of type TypeObject AND contains a member named "name", false otherwise + */ + bool hasMember(char * name); + + /** + * Convert an MbedRpcValue in a JSON frame + * + * @return JSON string + */ + std::string serialize(); + +protected: + + // object type + Type _type; + + //indexes of TypeObject and TypeArray + int index_array; + int index_token; + + //an object can contain NB_TOKEN tokens. Each token have a name + MbedRpcValue * token[NB_TOKEN]; + std::string * token_name[NB_TOKEN]; + + //an object can contain an array of NB_TOKEN elements + MbedRpcValue * array[NB_TOKEN]; + + // Clean up + void clean(); + + union { + bool asBool; + int asInt; + double asDouble; + std::string* asString; + } _value; + + + MbedRpcValue& operator[](int i) const { return *(array[i]); } + MbedRpcValue& operator[](std::string k) const; + + std::string to_str(); + void serialize(std::back_insert_iterator<std::string> os); + +}; + + +#define GET(ctype, var) \ + template <> inline const ctype& MbedRpcValue::get<ctype>() const { \ + return var; \ + } \ + template <> inline ctype& MbedRpcValue::get<ctype>() { \ + return var; \ + } +GET(bool, _value.asBool) +GET(double, _value.asDouble) +GET(int, _value.asInt) +GET(std::string, *_value.asString) +#undef GET + + +//Input class for JSON parser +class input { +protected: + const char * cur_; + const char * end_; + int last_ch_; + bool ungot_; + int line_; +public: + input(const char * first, const char * last) : cur_(first), end_(last), last_ch_(-1), ungot_(false), line_(1) {}; + + int getc() { + if (ungot_) { + ungot_ = false; + return last_ch_; + } + if (cur_ == end_) { + last_ch_ = -1; + return -1; + } + if (last_ch_ == '\n') { + line_++; + } + last_ch_ = *cur_++ & 0xff; + return last_ch_; + } + + void ungetc() { + if (last_ch_ != -1) { + ungot_ = true; + } + } + + const char * cur() const { + return cur_; + } + int line() const { + return line_; + } + void skip_ws() { + while (1) { + int ch = getc(); + if (! (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r')) { + ungetc(); + break; + } + } + } + int expect(int expect) { + skip_ws(); + if (getc() != expect) { + ungetc(); + return false; + } + return true; + } + bool match(const std::string& pattern) { + for (std::string::const_iterator pi(pattern.begin()); + pi != pattern.end(); + ++pi) { + if (getc() != *pi) { + ungetc(); + return false; + } + } + return true; + } +}; + + + +inline const char * parse(MbedRpcValue& out, const char * first, const char * last, std::string* err); + +/** +* JSON string parser and creation of an MbedRpcValue +* +* @param out reference of an MbedRpcValue which will be filled according to the JSON string +* @param str JSON string +* @return A non empty string if there is a parsing error +* +*/ + +inline std::string parse(MbedRpcValue& out, const char * str); +inline bool _parse(MbedRpcValue& out, input& in); +inline bool _parse_number(MbedRpcValue& out, input& in); +inline bool _parse_string(MbedRpcValue& out, input& in); +inline bool _parse_array(MbedRpcValue& out, input& in); +inline bool _parse_object(MbedRpcValue& out, input& in); + + +inline bool _parse_string(MbedRpcValue& out, input& in) { +#ifdef DEBUG + printf("string detected\r\n"); +#endif + out = MbedRpcValue(std::string("")); + std::string& s = out.get<std::string>(); + while (1) { + int ch = in.getc(); + if (ch < ' ') { + in.ungetc(); + return false; + } else if (ch == '"') { + return true; + } else if (ch == '\\') { + if ((ch = in.getc()) == -1) { + return false; + } + switch (ch) { +#define MAP(sym, val) case sym: s.push_back(val); break + MAP('"', '\"'); + MAP('\\', '\\'); + MAP('/', '/'); + MAP('b', '\b'); + MAP('f', '\f'); + MAP('n', '\n'); + MAP('r', '\r'); + MAP('t', '\t'); +#undef MAP + default: + return false; + } + } else { + s.push_back(ch); + } + } +} + +inline bool _parse_array(MbedRpcValue& out, input& in) { +#ifdef DEBUG + printf("array detected\r\n"); +#endif + int i = 0; + if (in.expect(']')) { + return true; + } + do { + if (! _parse(out[i], in)) { + return false; + } + i++; + } while (in.expect(',')); + return in.expect(']'); +} + +inline bool _parse_object(MbedRpcValue& out, input& in) { +#ifdef DEBUG + printf("object detected\r\n"); +#endif + if (in.expect('}')) { + return true; + } + do { + MbedRpcValue key, val; + if (in.expect('"') && _parse_string(key, in) && in.expect(':') && _parse(val, in)) { + out[key.get<std::string>().c_str()] = val; +#ifdef DEBUG + printf("key: %s \r\n", key.get<std::string>().c_str()); +#endif + } else { + return false; + } + } while (in.expect(',')); + return in.expect('}'); +} + +inline bool _parse_number(MbedRpcValue& out, input& in) { +#ifdef DEBUG + printf("number detected\r\n"); +#endif + std::string num_str; + while (1) { + int ch = in.getc(); + if (('0' <= ch && ch <= '9') || ch == '+' || ch == '-' || ch == '.' + || ch == 'e' || ch == 'E') { + num_str.push_back(ch); + } else { + in.ungetc(); + break; + } + } + char* endp; + if (strchr(num_str.c_str(), '.') != NULL || strchr(num_str.c_str(), 'e') != NULL || strchr(num_str.c_str(), '+') != NULL) + out = MbedRpcValue(strtod(num_str.c_str(), &endp)); + else + out = MbedRpcValue((int)strtod(num_str.c_str(), &endp)); + return endp == num_str.c_str() + num_str.size(); +} + +inline bool _parse(MbedRpcValue& out, input& in) { + in.skip_ws(); + int ch = in.getc(); + switch (ch) { +#define IS(ch, text, val) case ch: \ + if (in.match(text)) { \ + out = val; \ + return true; \ + } else { \ + return false; \ + } + IS('n', "ull", MbedRpcValue()); + IS('f', "alse", MbedRpcValue(false)); + IS('t', "rue", MbedRpcValue(true)); +#undef IS + case '"': + return _parse_string(out, in); + case '[': + return _parse_array(out, in); + case '{': + return _parse_object(out, in); + default: + if (('0' <= ch && ch <= '9') || ch == '-') { + in.ungetc(); + return _parse_number(out, in); + } + break; + } + in.ungetc(); + return false; +} + +inline std::string parse(MbedRpcValue& out, const char * pos) { + const char * last = pos + strlen(pos); + std::string err; + pos = parse(out, pos, last, &err); + return err; +} + +inline const char * parse(MbedRpcValue& out, const char * first, const char * last, std::string* err) { + input in = input(first, last); + if (! _parse(out, in) && err != NULL) { + char buf[64]; + sprintf(buf, "syntax error at line %d near: ", in.line()); + *err = buf; + while (1) { + int ch = in.getc(); + if (ch == -1 || ch == '\n') { + break; + } else if (ch >= ' ') { + err->push_back(ch); + } + } + } + return in.cur(); +} + +#endif // _MbedMbedRpcValue_H_