mirror of
https://github.com/warmcat/libwebsockets.git
synced 2025-03-09 00:00:04 +01:00
jrpc: add support
This commit is contained in:
parent
e3544456d8
commit
057d03997f
13 changed files with 1143 additions and 2 deletions
|
@ -264,6 +264,7 @@ option(LWS_WITH_CBOR_FLOAT "Build floating point types if building CBOR LECP" ON
|
|||
option(LWS_WITH_SQLITE3 "Require SQLITE3 support" OFF)
|
||||
option(LWS_WITH_STRUCT_JSON "Generic struct serialization to and from JSON" OFF)
|
||||
option(LWS_WITH_STRUCT_SQLITE3 "Generic struct serialization to and from SQLITE3" OFF)
|
||||
option(LWS_WITH_JSONRPC "JSON RPC support" ON)
|
||||
# broken atm
|
||||
#option(LWS_WITH_SMTP "Provide SMTP support" OFF)
|
||||
if (LWS_WITH_ESP32)
|
||||
|
@ -960,7 +961,6 @@ list(APPEND LIB_LIST ${LIB_LIST_AT_END})
|
|||
#
|
||||
|
||||
include_directories("${PROJECT_SOURCE_DIR}/lib")
|
||||
|
||||
add_subdirectory(lib)
|
||||
|
||||
|
||||
|
|
|
@ -176,6 +176,7 @@
|
|||
#cmakedefine LWS_WITH_JOSE
|
||||
#cmakedefine LWS_WITH_CBOR
|
||||
#cmakedefine LWS_WITH_CBOR_FLOAT
|
||||
#cmakedefine LWS_WITH_JSONRPC
|
||||
#cmakedefine LWS_WITH_LEJP
|
||||
#cmakedefine LWS_WITH_LIBEV
|
||||
#cmakedefine LWS_WITH_LIBEVENT
|
||||
|
|
|
@ -670,6 +670,7 @@ struct lws;
|
|||
#include <libwebsockets/lws-secure-streams-policy.h>
|
||||
#include <libwebsockets/lws-secure-streams-client.h>
|
||||
#include <libwebsockets/lws-secure-streams-transport-proxy.h>
|
||||
#include <libwebsockets/lws-jrpc.h>
|
||||
|
||||
#if !defined(LWS_PLAT_FREERTOS)
|
||||
#include <libwebsockets/abstract/abstract.h>
|
||||
|
|
229
include/libwebsockets/lws-jrpc.h
Normal file
229
include/libwebsockets/lws-jrpc.h
Normal file
|
@ -0,0 +1,229 @@
|
|||
/*
|
||||
* libwebsockets - small server side websockets and web server implementation
|
||||
*
|
||||
* Copyright (C) 2010 - 2020 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* This is a JSON-RPC parser and state management implementation that's:
|
||||
*
|
||||
* - Lightweight, it uses lws LEJP JSON stream parser for requests, responses,
|
||||
* and user-defined parameter objects
|
||||
*
|
||||
* - Stateful... you can give it sequential input buffers randomly fragmented
|
||||
* and it will complete when it has enough
|
||||
*
|
||||
* - Asynchronous... response processing can return to the event loop both
|
||||
* while the RX is still coming and after it's all received before forming
|
||||
* the response, eg, because it's querying on a remote connection to get the
|
||||
* response data. Any number of RPCs can be either in flight or waiting for
|
||||
* response processing to complete before responding.
|
||||
*
|
||||
* - Supports "version" extension
|
||||
*
|
||||
* - allows binding different method names to different callbacks
|
||||
*
|
||||
* - Supports both client and server roles, eg, can parse both requests and
|
||||
* responses
|
||||
*
|
||||
* - No support for batch. Batching is not widely used because it doesn't
|
||||
* add anything for the vast bulk of cases compared to sending n requests.
|
||||
*
|
||||
* This handles client and server RX and transaction state, creating a callback
|
||||
* when parameters can be parsed and all of the request or notification is
|
||||
* done.
|
||||
*
|
||||
* Producing JSON is usually simpler and more compact than expressing it as an
|
||||
* object model, ie often a response can be completely formed in a single
|
||||
* lws_snprintf(). Response JSON must be buffered on heap until the method
|
||||
* callback is called with NULL / 0 buf len indicating that the incoming request
|
||||
* has completed parsing.
|
||||
*
|
||||
*/
|
||||
|
||||
/* these are opaque */
|
||||
|
||||
struct lws_jrpc_obj;
|
||||
struct lws_jrpc;
|
||||
|
||||
typedef enum {
|
||||
LJRPC_CBRET_CONTINUE,
|
||||
LJRPC_CBRET_WANT_TO_EMIT,
|
||||
LJRPC_CBRET_FINISHED,
|
||||
LJRPC_CBRET_FAILED
|
||||
} lws_jrpc_cb_return_t;
|
||||
|
||||
/*
|
||||
* method name to lejp parsing handler map
|
||||
*/
|
||||
|
||||
typedef struct lws_jrpc_method {
|
||||
const char *method_name;
|
||||
const char * const *paths;
|
||||
lejp_callback cb;
|
||||
int count_paths;
|
||||
} lws_jrpc_method_t;
|
||||
|
||||
/*
|
||||
* Boilerplate for forming correct requests
|
||||
*/
|
||||
|
||||
/* Boilerplate to start a request */
|
||||
#define LWSJRPCBP_REQ_START_S "{\"jsonrpc\":\"2.0\",\"method\":\"%s\""
|
||||
/* Boilerplate to start parameters (params are left freeform for user) */
|
||||
#define LWSJRPCBP_REQ_VERSION_S ",\"version\":\"%s\""
|
||||
/* Boilerplate to start parameters (params are left freeform for user) */
|
||||
#define LWSJRPCBP_REQ_PARAMS ",\"params\":"
|
||||
/* Boilerplate to complete the result object */
|
||||
#define LWSJRPCBP_REQ_NOTIF_END "}"
|
||||
/* Boilerplate to complete the result object */
|
||||
#define LWSJRPCBP_REQ_ID_END_S ",\"id\":%s}"
|
||||
|
||||
/*
|
||||
* Boilerplate for forming correct responses
|
||||
*/
|
||||
|
||||
/* Boilerplate to start a result */
|
||||
#define LWSJRPCBP_RESP_RESULT "{\"jsonrpc\":\"2.0\",\"result\":"
|
||||
/* Boilerplate to complete the result object */
|
||||
#define LWSJRPCBP_RESP_ID_END_S ",\"id\":%s}"
|
||||
|
||||
/* Boilerplate to form an error */
|
||||
#define LWSJRPCBP_RESP_ERROR_D "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":%d"
|
||||
/* optional */
|
||||
#define LWSJRPCBP_RESP_ERROR_MSG_S ",\"message\":\"%s\""
|
||||
/* optional */
|
||||
#define LWSJRPCBP_RESP_ERROR_DATA ",\"data\":"
|
||||
/* required */
|
||||
#define LWSJRPCBP_RESP_ERROR_END "}"
|
||||
|
||||
/*
|
||||
* JSONRPC Well-known Errors
|
||||
*/
|
||||
|
||||
enum {
|
||||
LWSJRPCE__NO_ERROR = 0,
|
||||
|
||||
LWSJRPCWKE__PARSE_ERROR = -32700, /* invalid JSON */
|
||||
LWSJRPCWKE__INVALID_REQUEST = -32600, /* not valid JSONRPC object */
|
||||
LWSJRPCWKE__METHOD_NOT_FOUND = -32601, /* method not supported */
|
||||
LWSJRPCWKE__INVALID_PARAMS = -32602, /* parameters are invalid */
|
||||
LWSJRPCWKE__INTERNAL_ERROR = -32603, /* internal JSONRPC error */
|
||||
LWSJRPCWKE__SERVER_ERROR_FIRST = -32000, /* implementation-defined...*/
|
||||
LWSJRPCWKE__SERVER_ERROR_LAST = -32099, /* ... server errors range */
|
||||
|
||||
LWSJRPCE__INVALID_MEMBERS = -31000, /* reponse membs in req, vv */
|
||||
};
|
||||
|
||||
enum {
|
||||
LWSJRPC_PARSE_REQUEST,
|
||||
LWSJRPC_PARSE_RESPONSE
|
||||
};
|
||||
|
||||
/*
|
||||
* APIs for the opaque JRPC request object
|
||||
*/
|
||||
|
||||
/**
|
||||
* lws_jrpc_obj_parse() - parse a request or response
|
||||
*
|
||||
* \param jrpc: the jrpc context this belongs to
|
||||
* \param type: LWSJRPC_PARSE_REQUEST or ..._RESPONSE
|
||||
* \param opaque: user-defined pointer bound to lws_jrpc, ignored by lws
|
||||
* \param buf: chunk of JSON-RPC
|
||||
* \param l: remaining length of JSON (may be under or oversize)
|
||||
* \param r: NULL to indicate starting new req, already set means continue parse
|
||||
*
|
||||
* If necessary creates an opaque req object and starts parsing len bytes of
|
||||
* buf. This may be undersize (more parts coming) in which case \p req will be
|
||||
* set on entry next time indicating a continuation.
|
||||
*
|
||||
* \p type and \p opaque are ignored if it it's not the first buffer that
|
||||
* creates the req object.
|
||||
*
|
||||
* Return code is >= 0 if completed, representing the amount of unused data in
|
||||
* the input buffer. -1 indicates more input data needed, <-1 indicates an
|
||||
* error from the LWSJRPCWKE_ set above
|
||||
*/
|
||||
|
||||
LWS_VISIBLE LWS_EXTERN int
|
||||
lws_jrpc_obj_parse(struct lws_jrpc *jrpc, int type, void *opaque,
|
||||
const char *buf, size_t l, struct lws_jrpc_obj **r);
|
||||
|
||||
/*
|
||||
* lws_jrpc_obj_destroy() - detach and destroy a JRPC request or response
|
||||
*
|
||||
* \param _r: pointer to pointer to JRPC request to detach and free
|
||||
*
|
||||
* Detaches the req from its JRPC context and frees it and any internal
|
||||
* allocations.
|
||||
*/
|
||||
LWS_VISIBLE LWS_EXTERN void
|
||||
lws_jrpc_obj_destroy(struct lws_jrpc_obj **_r);
|
||||
|
||||
/*
|
||||
* lws_jrpc_obj_get_opaque() - retreive the opaque pointer bound to the req
|
||||
*
|
||||
* \param r: pointer to pointer to JRPC request
|
||||
*
|
||||
* Returns the opaque pointer for a req given when it was parsed / created.
|
||||
*/
|
||||
LWS_VISIBLE LWS_EXTERN void *
|
||||
lws_jrpc_obj_get_opaque(const struct lws_jrpc_obj *r);
|
||||
|
||||
/*
|
||||
* lws_jrpc_obj_id() - retreive the object's id string
|
||||
*
|
||||
* \param r: pointer to pointer to JRPC object
|
||||
*
|
||||
* Returns a pointer to a correctly-typed id for use in a response; if a string,
|
||||
* then it is already quoted, if an int or null then it's provided without
|
||||
* quotes.
|
||||
*/
|
||||
LWS_VISIBLE LWS_EXTERN const char *
|
||||
lws_jrpc_obj_id(const struct lws_jrpc_obj *r);
|
||||
|
||||
|
||||
/*
|
||||
* APIs for the opaque JRPC context
|
||||
*/
|
||||
|
||||
/**
|
||||
* lws_jrpc_create() - Allocate and initialize a JRPC context
|
||||
*
|
||||
* \param methods: the method callbacks and names we can process
|
||||
* \param opaque: user-defined pointer bound to lws_jrpc ignored by lws
|
||||
*
|
||||
* Allocates an opaque lws_jrpc object and binds it to the given array of
|
||||
* method names and callbacks
|
||||
*/
|
||||
LWS_VISIBLE LWS_EXTERN struct lws_jrpc *
|
||||
lws_jrpc_create(const lws_jrpc_method_t *methods, void *opaque);
|
||||
|
||||
/*
|
||||
* lws_jrpc_destroy() - destroy an allocated JRPC context
|
||||
*
|
||||
* \param jrpc: pointer to pointer to jrpc to destroy
|
||||
*
|
||||
* Destroys any ongoing reqs in the JRPC and then destroys the JRPC and sets the
|
||||
* given pointer to NULL.
|
||||
*/
|
||||
LWS_VISIBLE LWS_EXTERN void
|
||||
lws_jrpc_destroy(struct lws_jrpc **jrpc);
|
|
@ -108,6 +108,8 @@ enum lejp_callbacks {
|
|||
|
||||
LEJPCB_OBJECT_START = 16,
|
||||
LEJPCB_OBJECT_END = 17,
|
||||
|
||||
LEJPCB_USER_START = 32,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -148,6 +148,7 @@ endif()
|
|||
if (LWS_WITH_JOSE)
|
||||
add_subdir_include_dirs(jose)
|
||||
endif()
|
||||
|
||||
if (LWS_WITH_COSE)
|
||||
add_subdir_include_dirs(cose)
|
||||
endif()
|
||||
|
|
|
@ -67,6 +67,12 @@ if (LWS_WITH_STRUCT_JSON)
|
|||
misc/lws-struct-lejp.c)
|
||||
endif()
|
||||
|
||||
if (LWS_WITH_JSONRPC)
|
||||
list(APPEND SOURCES
|
||||
misc/jrpc/jrpc.c)
|
||||
include_directories(misc/jrpc)
|
||||
endif()
|
||||
|
||||
if (LWS_WITH_STRUCT_SQLITE3)
|
||||
list(APPEND SOURCES
|
||||
misc/lws-struct-sqlite.c)
|
||||
|
|
386
lib/misc/jrpc/jrpc.c
Normal file
386
lib/misc/jrpc/jrpc.c
Normal file
|
@ -0,0 +1,386 @@
|
|||
/*
|
||||
* libwebsockets - small server side websockets and web server implementation
|
||||
*
|
||||
* Copyright (C) 2010 - 2020 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* We use the lejp parse stack to replace the callback context for JSON
|
||||
* subtrees.
|
||||
*
|
||||
* It's optionally done when we see we're in a [] batch of reqs, we pass each
|
||||
* unitary req to the internal req parser.
|
||||
*
|
||||
* Each req does it to hand off the parsing of the parameters section.
|
||||
*/
|
||||
|
||||
#include <private-lib-core.h>
|
||||
#include "private-lib-misc-jrpc.h"
|
||||
|
||||
static const char * const paths[] = {
|
||||
"jsonrpc",
|
||||
"method",
|
||||
"version",
|
||||
"params",
|
||||
"id",
|
||||
/* only for responses --> */
|
||||
"result",
|
||||
"error",
|
||||
"code",
|
||||
"message",
|
||||
"data",
|
||||
};
|
||||
|
||||
enum enum_paths {
|
||||
LEJPN_JSONRPC,
|
||||
LEJPN_METHOD,
|
||||
LEJPN_VERSION,
|
||||
LEJPN_PARAMS,
|
||||
LEJPN_ID,
|
||||
/* only for responses --> */
|
||||
LEJPN_RESULT,
|
||||
LEJPN_ERROR,
|
||||
LEJPN_E_CODE,
|
||||
LEJPN_E_MESSAGE,
|
||||
LEJPN_E_DATA,
|
||||
};
|
||||
|
||||
/*
|
||||
* Get the registered handler for a method name... a registered handler for
|
||||
* a NULL method name matches any other unmatched name.
|
||||
*/
|
||||
|
||||
static const lws_jrpc_method_t *
|
||||
lws_jrpc_method_lookup(lws_jrpc_t *jrpc, const char *method_name)
|
||||
{
|
||||
const lws_jrpc_method_t *m = jrpc->methods, *m_null = NULL;
|
||||
|
||||
while (1) {
|
||||
|
||||
if (!m->method_name)
|
||||
return m;
|
||||
|
||||
if (!strcmp(method_name, m->method_name))
|
||||
return m;
|
||||
|
||||
m++;
|
||||
}
|
||||
|
||||
return m_null;
|
||||
}
|
||||
|
||||
static signed char
|
||||
req_cb(struct lejp_ctx *ctx, char reason)
|
||||
{
|
||||
lws_jrpc_obj_t *r = (lws_jrpc_obj_t *)ctx->user;
|
||||
lws_jrpc_t *jrpc;
|
||||
char *p;
|
||||
|
||||
lwsl_warn("%s: %d '%s' %s (sp %d, pst_sp %d)\n", __func__, reason, ctx->path, ctx->buf, ctx->sp, ctx->pst_sp);
|
||||
|
||||
if (reason == LEJPCB_PAIR_NAME && ctx->path_match - 1 == LEJPN_PARAMS) {
|
||||
|
||||
if (r->response)
|
||||
goto fail_invalid_members;
|
||||
/*
|
||||
* Params are a wormhole to another LEJP parser context to deal
|
||||
* with, chosen based on the method name and the callbacks
|
||||
* associated with that at init time.
|
||||
*
|
||||
* Params may be provided in a toplevel array, called a "batch",
|
||||
* these are treated as n independent subrequests to be handled
|
||||
* sequentially, and if the request is parseable, the scope of
|
||||
* errors is only the current batch entry.
|
||||
*/
|
||||
|
||||
jrpc = lws_container_of(r->list.owner, lws_jrpc_t, req_owner);
|
||||
r->pmethod = lws_jrpc_method_lookup(jrpc, r->method);
|
||||
if (!r->pmethod || !r->pmethod->cb)
|
||||
/*
|
||||
* There's nothing we can do with no method binding, or
|
||||
* one that lacks a callback...
|
||||
*/
|
||||
goto fail_method_not_found;
|
||||
|
||||
r->inside_params = 1;
|
||||
|
||||
lwsl_notice("%s: params: entering subparser\n", __func__);
|
||||
lejp_parser_push(ctx, r, r->pmethod->paths,
|
||||
(uint8_t)r->pmethod->count_paths, r->pmethod->cb);
|
||||
}
|
||||
|
||||
if (reason == LEJPCB_COMPLETE && !r->response) {
|
||||
if (!r->has_jrpc_member)
|
||||
goto fail_invalid_request;
|
||||
if (r->method[0] && !r->pmethod) {
|
||||
jrpc = lws_container_of(r->list.owner, lws_jrpc_t,
|
||||
req_owner);
|
||||
r->pmethod = lws_jrpc_method_lookup(jrpc, r->method);
|
||||
if (!r->pmethod || !r->pmethod->cb)
|
||||
/*
|
||||
* There's nothing we can do with no method
|
||||
* binding, or one that lacks a callback...
|
||||
*/
|
||||
goto fail_method_not_found;
|
||||
}
|
||||
|
||||
/*
|
||||
* Indicate that the whole of the request has been parsed now
|
||||
* and the id is known, so the method can complete and finalize
|
||||
* its response
|
||||
*/
|
||||
r->pmethod->cb(ctx, LEJPCB_USER_START);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* we only match on the prepared path strings */
|
||||
if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
|
||||
return 0;
|
||||
|
||||
if (ctx->path_match - 1 >= LEJPN_RESULT && !r->response)
|
||||
goto fail_invalid_members;
|
||||
|
||||
switch (ctx->path_match - 1) {
|
||||
case LEJPN_JSONRPC:
|
||||
/*
|
||||
* A String specifying the version of the JSON-RPC protocol.
|
||||
* MUST be exactly "2.0".
|
||||
*/
|
||||
if (ctx->npos != 3 && strcmp(ctx->buf, "2.0")) {
|
||||
r->parse_result = LWSJRPCWKE__INVALID_REQUEST;
|
||||
return -1;
|
||||
}
|
||||
r->has_jrpc_member = 1;
|
||||
break;
|
||||
|
||||
case LEJPN_METHOD:
|
||||
if (r->response)
|
||||
goto fail_invalid_members;
|
||||
|
||||
/*
|
||||
* Method is defined to be a string... anything else is invalid
|
||||
*/
|
||||
|
||||
if (reason != LEJPCB_VAL_STR_END)
|
||||
goto fail_invalid_request;
|
||||
|
||||
/*
|
||||
* Restrict the method length to something sane
|
||||
*/
|
||||
if (ctx->npos > sizeof(r->method) - 1)
|
||||
goto fail_method_not_found;
|
||||
|
||||
lws_strnncpy(r->method, ctx->buf, ctx->npos, sizeof(r->method));
|
||||
|
||||
/* defer trying to use it so we catch parser errors */
|
||||
break;
|
||||
|
||||
|
||||
|
||||
case LEJPN_ID:
|
||||
/*
|
||||
* "An identifier established by the Client that MUST contain a
|
||||
* String, Number, or NULL value if included. If it is not
|
||||
* included it is assumed to be a notification. The value SHOULD
|
||||
* normally not be Null and Numbers SHOULD NOT contain
|
||||
* fractional parts."
|
||||
*
|
||||
* We defaulted the id to null, let's continue to store the id
|
||||
* exactly as it would be reissued, ie, if a string, then we'll
|
||||
* add the quotes around it now.
|
||||
*
|
||||
* Restrict the method length and type to something sane
|
||||
*/
|
||||
if (ctx->npos > sizeof(r->id) - 3 ||
|
||||
reason == LEJPCB_VAL_TRUE ||
|
||||
reason == LEJPCB_VAL_FALSE ||
|
||||
/* if float, has "fractional part" */
|
||||
reason == LEJPCB_VAL_NUM_FLOAT)
|
||||
goto fail_invalid_request;
|
||||
|
||||
r->seen_id = 1;
|
||||
if (reason == LEJPCB_VAL_NULL)
|
||||
/* it already defaults to null */
|
||||
break;
|
||||
|
||||
p = r->id;
|
||||
if (reason == LEJPCB_VAL_STR_END)
|
||||
*p++ = '\"';
|
||||
|
||||
lws_strnncpy(p, ctx->buf, ctx->npos, sizeof(r->id) - 2);
|
||||
|
||||
if (reason == LEJPCB_VAL_STR_END) {
|
||||
p += strlen(p);
|
||||
*p++ = '\"';
|
||||
*p = '\0';
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case LEJPN_VERSION:
|
||||
/*
|
||||
* Restrict the method length to something sane
|
||||
*/
|
||||
if (ctx->npos > sizeof(r->version) - 1)
|
||||
goto fail_invalid_request;
|
||||
lws_strnncpy(r->version, ctx->buf, ctx->npos, sizeof(r->version));
|
||||
break;
|
||||
|
||||
/*
|
||||
* Only for responses
|
||||
*/
|
||||
|
||||
case LEJPN_RESULT:
|
||||
break;
|
||||
|
||||
case LEJPN_ERROR:
|
||||
break;
|
||||
case LEJPN_E_CODE:
|
||||
break;
|
||||
case LEJPN_E_MESSAGE:
|
||||
break;
|
||||
case LEJPN_E_DATA:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail_invalid_members:
|
||||
r->parse_result = LWSJRPCE__INVALID_MEMBERS;
|
||||
|
||||
return -1;
|
||||
|
||||
fail_invalid_request:
|
||||
r->parse_result = LWSJRPCWKE__INVALID_REQUEST;
|
||||
|
||||
return -1;
|
||||
|
||||
fail_method_not_found:
|
||||
r->parse_result = LWSJRPCWKE__METHOD_NOT_FOUND;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *
|
||||
lws_jrpc_obj_id(const struct lws_jrpc_obj *r)
|
||||
{
|
||||
return r->id;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return code is >= 0 if completed, representing the amount of unused data in
|
||||
* the input buffer. -1 indicates more input data needed, <-1 indicates an
|
||||
* error from the LWSJRPCWKE_ set above
|
||||
*/
|
||||
int
|
||||
lws_jrpc_obj_parse(lws_jrpc_t *jrpc, int type, void *opaque,
|
||||
const char *buf, size_t l, lws_jrpc_obj_t **_r)
|
||||
{
|
||||
lws_jrpc_obj_t *r = *_r;
|
||||
int n;
|
||||
|
||||
if (!r) {
|
||||
/*
|
||||
* We need to init the request object
|
||||
*/
|
||||
r = *_r = malloc(sizeof(*r));
|
||||
if (!r)
|
||||
return 1; /* OOM */
|
||||
|
||||
memset(r, 0, sizeof *r);
|
||||
|
||||
lws_dll2_add_tail(&r->list, &jrpc->req_owner);
|
||||
r->opaque = opaque;
|
||||
r->response = type == LWSJRPC_PARSE_RESPONSE;
|
||||
lws_strncpy(r->id, "null", sizeof(r->id));
|
||||
lejp_construct(&r->lejp_ctx, req_cb, r, paths,
|
||||
LWS_ARRAY_SIZE(paths));
|
||||
}
|
||||
|
||||
n = lejp_parse(&r->lejp_ctx, (uint8_t *)buf, (int)l);
|
||||
lwsl_debug("%s: raw parse result %d\n", __func__, n);
|
||||
if (n == LEJP_REJECT_CALLBACK)
|
||||
return r->parse_result;
|
||||
|
||||
if (n < -1)
|
||||
return LWSJRPCWKE__PARSE_ERROR;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
void *
|
||||
lws_jrpc_obj_get_opaque(const struct lws_jrpc_obj * r)
|
||||
{
|
||||
return (void *)r->opaque;
|
||||
}
|
||||
|
||||
void
|
||||
lws_jrpc_obj_destroy(lws_jrpc_obj_t **_r)
|
||||
{
|
||||
lws_jrpc_obj_t *r = *_r;
|
||||
|
||||
if (!r)
|
||||
return;
|
||||
|
||||
lws_dll2_remove(&r->list);
|
||||
|
||||
free(r);
|
||||
*_r = NULL;
|
||||
}
|
||||
|
||||
struct lws_jrpc *
|
||||
lws_jrpc_create(const lws_jrpc_method_t *methods, void *opaque)
|
||||
{
|
||||
lws_jrpc_t *j = malloc(sizeof(*j));
|
||||
|
||||
if (!j)
|
||||
return NULL;
|
||||
|
||||
memset(j, 0, sizeof(*j));
|
||||
|
||||
j->opaque = opaque;
|
||||
j->methods = methods;
|
||||
|
||||
return j;
|
||||
}
|
||||
void *
|
||||
lws_jrpc_get_opaque(const struct lws_jrpc *jrpc)
|
||||
{
|
||||
return (void *)jrpc->opaque;
|
||||
}
|
||||
|
||||
void
|
||||
lws_jrpc_destroy(lws_jrpc_t **_jrpc)
|
||||
{
|
||||
struct lws_jrpc *jrpc = *_jrpc;
|
||||
|
||||
if (!jrpc)
|
||||
return;
|
||||
|
||||
lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1,
|
||||
jrpc->req_owner.head) {
|
||||
lws_jrpc_obj_t *r = lws_container_of(p, lws_jrpc_obj_t, list);
|
||||
|
||||
lws_jrpc_obj_destroy(&r);
|
||||
} lws_end_foreach_dll_safe(p, p1);
|
||||
|
||||
free(jrpc);
|
||||
*_jrpc = NULL;
|
||||
}
|
88
lib/misc/jrpc/private-lib-misc-jrpc.h
Normal file
88
lib/misc/jrpc/private-lib-misc-jrpc.h
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* libwebsockets - small server side websockets and web server implementation
|
||||
*
|
||||
* Copyright (C) 2010 - 2020 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* This written from scratch, but props to falk-werner for his earlier
|
||||
* work on top of lws for JRPC.
|
||||
*
|
||||
* https://github.com/falk-werner/jrpc
|
||||
*
|
||||
* https://www.jsonrpc.org/specification
|
||||
*
|
||||
* LWS JRPC takes the approach to stream-parse the incoming JRPC object in
|
||||
* place to maximize the flexibility and parameter sizes that can be handled.
|
||||
* Although "id" is often last, actually it has no users except to append the
|
||||
* same id to the response.
|
||||
*
|
||||
* Therefore we parse the outer JSON and treat params as a wormhole to be
|
||||
* parsed by a method-bound user callback.
|
||||
*
|
||||
* Streamed request processing must buffer its output before sending, since
|
||||
* it does not know until the end if it must replace the intended response
|
||||
* with an exception. It may not know that it wants to make an exception
|
||||
* until it really processes all the params either. Results must be held in
|
||||
* a side buffer until the response is able to complete or has errored.
|
||||
*
|
||||
* Types for id, method and params are ill-defined. They're all treated as
|
||||
* strings internally, so a "method": 1 is handled as the string "1". id
|
||||
* may be NULL, if so it's explicitly returned in the response with "id":null
|
||||
* Whether id came in as a non-quoted number is remembered and is reproduced
|
||||
* when giving the id.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Opaque object representing a request both at the sender and receiver
|
||||
*/
|
||||
|
||||
typedef struct lws_jrpc_obj {
|
||||
lws_dll2_t list;
|
||||
|
||||
struct lejp_ctx lejp_ctx;
|
||||
|
||||
void *opaque;
|
||||
const lws_jrpc_method_t *pmethod; /* only look up once if multi part */
|
||||
|
||||
char id[16]; /* includes quotes if was string */
|
||||
char method[48];
|
||||
/*
|
||||
* Eg Sony API "getCurrentExternalTerminalsStatus" (30 chars)
|
||||
* https://developer.sony.com/develop/audio-control-api/api-references/api-overview-2
|
||||
*/
|
||||
char version[4]; /* Eg for Sony, "2.0" */
|
||||
|
||||
int parse_result;
|
||||
|
||||
uint8_t count_batch_objects;
|
||||
|
||||
uint8_t seen_id :1;
|
||||
uint8_t inside_params :1;
|
||||
uint8_t has_jrpc_member :1;
|
||||
uint8_t response :1;
|
||||
|
||||
} lws_jrpc_obj_t;
|
||||
|
||||
|
||||
typedef struct lws_jrpc {
|
||||
lws_dll2_owner_t req_owner;
|
||||
const lws_jrpc_method_t *methods;
|
||||
void *opaque;
|
||||
} lws_jrpc_t;
|
|
@ -237,7 +237,7 @@ static const char tokens[] = "rue alse ull ";
|
|||
int
|
||||
lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
|
||||
{
|
||||
unsigned char c, n, s;
|
||||
unsigned char c, n, s, defer = 0;
|
||||
int ret = LEJP_REJECT_UNKNOWN;
|
||||
|
||||
if (!ctx->sp && !ctx->pst[ctx->pst_sp].ppos)
|
||||
|
@ -508,6 +508,8 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
|
|||
* smaller than the matching point
|
||||
*/
|
||||
ctx->path_match = 0;
|
||||
if (ctx->pst_sp && !ctx->sp)
|
||||
lejp_parser_pop(ctx);
|
||||
if (ctx->outer_array && !ctx->sp) { /* ended on ] */
|
||||
n = LEJPCB_ARRAY_END;
|
||||
goto completed;
|
||||
|
@ -700,6 +702,9 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
|
|||
goto completed;
|
||||
}
|
||||
|
||||
if (ctx->pst_sp && !ctx->sp)
|
||||
defer = 1;
|
||||
|
||||
/* do LEJP_MP_ARRAY_END processing */
|
||||
goto redo_character;
|
||||
}
|
||||
|
@ -740,6 +745,8 @@ pop_level:
|
|||
if (ctx->pst[ctx->pst_sp].callback(ctx,
|
||||
LEJPCB_OBJECT_END))
|
||||
goto reject_callback;
|
||||
if (ctx->pst_sp && !ctx->sp)
|
||||
lejp_parser_pop(ctx);
|
||||
break;
|
||||
|
||||
case LEJP_MP_ARRAY_END:
|
||||
|
@ -763,6 +770,10 @@ array_end:
|
|||
|
||||
ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END;
|
||||
ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_ARRAY_END);
|
||||
if (defer) {
|
||||
lejp_parser_pop(ctx);
|
||||
defer = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
project(lws-api-test-jrpc)
|
||||
cmake_minimum_required(VERSION 2.8.12)
|
||||
include(CheckCSourceCompiles)
|
||||
|
||||
set(SAMP lws-api-test-jrpc)
|
||||
set(SRCS main.c)
|
||||
|
||||
# If we are being built as part of lws, confirm current build config supports
|
||||
# reqconfig, else skip building ourselves.
|
||||
#
|
||||
# If we are being built externally, confirm installed lws was configured to
|
||||
# support reqconfig, else error out with a helpful message about the problem.
|
||||
#
|
||||
MACRO(require_lws_config reqconfig _val result)
|
||||
|
||||
if (DEFINED ${reqconfig})
|
||||
if (${reqconfig})
|
||||
set (rq 1)
|
||||
else()
|
||||
set (rq 0)
|
||||
endif()
|
||||
else()
|
||||
set(rq 0)
|
||||
endif()
|
||||
|
||||
if (${_val} EQUAL ${rq})
|
||||
set(SAME 1)
|
||||
else()
|
||||
set(SAME 0)
|
||||
endif()
|
||||
|
||||
if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
|
||||
if (${_val})
|
||||
message("${SAMP}: skipping as lws being built without ${reqconfig}")
|
||||
else()
|
||||
message("${SAMP}: skipping as lws built with ${reqconfig}")
|
||||
endif()
|
||||
set(${result} 0)
|
||||
else()
|
||||
if (LWS_WITH_MINIMAL_EXAMPLES)
|
||||
set(MET ${SAME})
|
||||
else()
|
||||
CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
|
||||
if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
|
||||
set(HAS_${reqconfig} 0)
|
||||
else()
|
||||
set(HAS_${reqconfig} 1)
|
||||
endif()
|
||||
if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
|
||||
set(MET 1)
|
||||
else()
|
||||
set(MET 0)
|
||||
endif()
|
||||
endif()
|
||||
if (NOT MET)
|
||||
if (${_val})
|
||||
message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
|
||||
else()
|
||||
message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
ENDMACRO()
|
||||
|
||||
set(requirements 1)
|
||||
require_lws_config(LWS_WITH_LEJP 1 requirements)
|
||||
require_lws_config(LWS_WITH_JSONRPC 1 requirements)
|
||||
|
||||
if (requirements)
|
||||
|
||||
add_executable(${SAMP} ${SRCS})
|
||||
add_test(NAME api-test-jrpc COMMAND lws-api-test-jrpc)
|
||||
|
||||
if (websockets_shared)
|
||||
target_link_libraries(${SAMP} websockets_shared)
|
||||
add_dependencies(${SAMP} websockets_shared)
|
||||
else()
|
||||
target_link_libraries(${SAMP} websockets)
|
||||
endif()
|
||||
endif()
|
56
minimal-examples-lowlevel/api-tests/api-test-jrpc/README.md
Normal file
56
minimal-examples-lowlevel/api-tests/api-test-jrpc/README.md
Normal file
|
@ -0,0 +1,56 @@
|
|||
# lws api test lws_struct JSON
|
||||
|
||||
Demonstrates how to use and performs selftests for lws_struct
|
||||
JSON serialization and deserialization
|
||||
|
||||
## build
|
||||
|
||||
```
|
||||
$ cmake . && make
|
||||
```
|
||||
|
||||
## usage
|
||||
|
||||
Commandline option|Meaning
|
||||
---|---
|
||||
-d <loglevel>|Debug verbosity in decimal, eg, -d15
|
||||
|
||||
```
|
||||
$ ./lws-api-test-lws_struct-json
|
||||
[2019/03/30 22:09:09:2529] USER: LWS API selftest: lws_struct JSON
|
||||
[2019/03/30 22:09:09:2625] NOTICE: main: ++++++++++++++++ test 1
|
||||
[2019/03/30 22:09:09:2812] NOTICE: builder.hostname = 'learn', timeout = 1800, targets (2)
|
||||
[2019/03/30 22:09:09:2822] NOTICE: target.name 'target1' (target 0x543a830)
|
||||
[2019/03/30 22:09:09:2824] NOTICE: target.name 'target2' (target 0x543a860)
|
||||
[2019/03/30 22:09:09:2826] NOTICE: main: .... strarting serialization of test 1
|
||||
[2019/03/30 22:09:09:2899] NOTICE: ser says 1
|
||||
{"schema":"com-warmcat-sai-builder","hostname":"learn","nspawn_timeout":1800,"targets":[{"name":"target1"},{"name":"target2"}]}
|
||||
[2019/03/30 22:09:09:2929] NOTICE: main: ++++++++++++++++ test 2
|
||||
[2019/03/30 22:09:09:2932] NOTICE: builder.hostname = 'learn', timeout = 0, targets (3)
|
||||
[2019/03/30 22:09:09:2932] NOTICE: target.name 'target1' (target 0x543b060)
|
||||
[2019/03/30 22:09:09:2933] NOTICE: target.name 'target2' (target 0x543b090)
|
||||
[2019/03/30 22:09:09:2933] NOTICE: target.name 'target3' (target 0x543b0c0)
|
||||
[2019/03/30 22:09:09:2934] NOTICE: main: .... strarting serialization of test 2
|
||||
[2019/03/30 22:09:09:2935] NOTICE: ser says 1
|
||||
{"schema":"com-warmcat-sai-builder","hostname":"learn","nspawn_timeout":0,"targets":[{"name":"target1"},{"name":"target2"},{"name":"target3"}]}
|
||||
[2019/03/30 22:09:09:2940] NOTICE: main: ++++++++++++++++ test 3
|
||||
[2019/03/30 22:09:09:2959] NOTICE: builder.hostname = 'learn', timeout = 1800, targets (2)
|
||||
[2019/03/30 22:09:09:2960] NOTICE: target.name 'target1' (target 0x543b450)
|
||||
[2019/03/30 22:09:09:2961] NOTICE: child 0x543b480, target.child.somename 'abc'
|
||||
[2019/03/30 22:09:09:2961] NOTICE: target.name 'target2' (target 0x543b490)
|
||||
[2019/03/30 22:09:09:2962] NOTICE: main: .... strarting serialization of test 3
|
||||
[2019/03/30 22:09:09:2969] NOTICE: ser says 1
|
||||
{"schema":"com-warmcat-sai-builder","hostname":"learn","nspawn_timeout":1800,"targets":[{"name":"target1","child":{"somename":"abc"}},{"name":"target2"}]}
|
||||
[2019/03/30 22:09:09:2970] NOTICE: main: ++++++++++++++++ test 4
|
||||
[2019/03/30 22:09:09:2971] NOTICE: builder.hostname = 'learn', timeout = 1800, targets (0)
|
||||
[2019/03/30 22:09:09:2971] NOTICE: main: .... strarting serialization of test 4
|
||||
[2019/03/30 22:09:09:2973] NOTICE: ser says 1
|
||||
{"schema":"com-warmcat-sai-builder","hostname":"learn","nspawn_timeout":1800}
|
||||
[2019/03/30 22:09:09:2974] NOTICE: main: ++++++++++++++++ test 5
|
||||
[2019/03/30 22:09:09:2978] NOTICE: builder.hostname = '', timeout = 0, targets (0)
|
||||
[2019/03/30 22:09:09:2979] NOTICE: main: .... strarting serialization of test 5
|
||||
[2019/03/30 22:09:09:2980] NOTICE: ser says 1
|
||||
{"schema":"com-warmcat-sai-builder","hostname":"","nspawn_timeout":0}
|
||||
[2019/03/30 22:09:09:2982] USER: Completed: PASS
|
||||
```
|
||||
|
280
minimal-examples-lowlevel/api-tests/api-test-jrpc/main.c
Normal file
280
minimal-examples-lowlevel/api-tests/api-test-jrpc/main.c
Normal file
|
@ -0,0 +1,280 @@
|
|||
/*
|
||||
* lws-api-test-jrpc
|
||||
*
|
||||
* Written in 2010-2020 by Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* This file is made available under the Creative Commons CC0 1.0
|
||||
* Universal Public Domain Dedication.
|
||||
*
|
||||
* sanity tests for jrpc
|
||||
*/
|
||||
|
||||
#include <libwebsockets.h>
|
||||
|
||||
/*
|
||||
* These came from https://www.jsonrpc.org/specification but amended since we
|
||||
* do not support batch
|
||||
*/
|
||||
|
||||
static const char * const jrpc_request_tests[] = {
|
||||
|
||||
"{" /* req 1 */
|
||||
"\"jsonrpc\":" "\"2.0\", "
|
||||
"\"method\":" "\"subtract\", "
|
||||
"\"params\":" "[42, 23], "
|
||||
"\"id\":" "1"
|
||||
"}",
|
||||
"{" /* req 2 */
|
||||
"\"jsonrpc\":" "\"2.0\", "
|
||||
"\"method\":" "\"subtract\", "
|
||||
"\"params\":" "[23, 42], "
|
||||
"\"id\":" "2"
|
||||
"}",
|
||||
"{" /* req 3 */
|
||||
"\"jsonrpc\":" "\"2.0\", "
|
||||
"\"method\":" "\"subtract\", "
|
||||
"\"params\":" "{"
|
||||
"\"subtrahend\":" "23, "
|
||||
"\"minuend\":" "42"
|
||||
"}, \"id\":" "3"
|
||||
"}",
|
||||
/* req 4 */
|
||||
"{\"jsonrpc\": \"2.0\","
|
||||
"\"method\": \"update\", "
|
||||
"\"params\": [1,2,3,4,5]}",
|
||||
|
||||
/* req 5 */
|
||||
"{\"jsonrpc\": \"2.0\", \"method\": \"foobar\"}",
|
||||
|
||||
/* req 6: unknown method: well-known error -32601 Method Not Found */
|
||||
"{\"jsonrpc\": \"2.0\", \"method\": \"noexist\", \"id\": \"1\"}",
|
||||
|
||||
/* req 7: Invalid JSON should yield well-known error -32700 Parse Error */
|
||||
"{\"jsonrpc\": \"2.0\", \"method\": \"foobar, \"params\": \"bar\", \"baz]",
|
||||
|
||||
/* req 8: Invalid req (method must be string): wke -32600 Invalid Request */
|
||||
"{\"jsonrpc\": \"2.0\", \"method\": 1, \"params\": \"bar\"}",
|
||||
|
||||
/* req 9: Incomplete JSON, just -32700 Parse Error */
|
||||
"{\"jsonrpc\": \"2.0\", \"method\"}",
|
||||
|
||||
/* req 10: OK */
|
||||
"{\"jsonrpc\": \"2.0\", \"method\": \"sum\", \"params\": [1,2,4], \"id\": \"1\"}",
|
||||
|
||||
/* req 11: OK (notify) */
|
||||
"{\"jsonrpc\": \"2.0\", \"method\": \"notify_hello\", \"params\": [7]}",
|
||||
|
||||
/* req 12: OK */
|
||||
"{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42,23], \"id\": \"2\"}",
|
||||
|
||||
/* req 13: -32600 */
|
||||
"{\"foo\": \"boo\"}",
|
||||
|
||||
/* req 14: -32601 */
|
||||
"{\"jsonrpc\": \"2.0\", \"method\": \"noexist\", \"params\": {\"name\": \"myself\"}, \"id\": \"5\"}",
|
||||
|
||||
/* req 15: OK */
|
||||
"{\"jsonrpc\": \"2.0\", \"method\": \"get_data\", \"id\": \"9\"}",
|
||||
|
||||
/* req 16: OK (notify) */
|
||||
"{\"jsonrpc\": \"2.0\", \"method\": \"notify_sum\", \"params\": [1,2,4]}",
|
||||
|
||||
/* req 17: OK (notify) */
|
||||
"{\"jsonrpc\": \"2.0\", \"method\": \"notify_hello\", \"params\": [7]}",
|
||||
};
|
||||
|
||||
static const char * const jrpc_response_tests[] = {
|
||||
|
||||
"{" /* req 1 */
|
||||
"\"jsonrpc\":" "\"2.0\","
|
||||
"\"id\":" "1, "
|
||||
"\"response\":" "\"string\""
|
||||
"}",
|
||||
"{" /* req 2 */
|
||||
"\"jsonrpc\":" "\"2.0\","
|
||||
"\"id\":" "2, "
|
||||
"\"response\":" "123"
|
||||
"}",
|
||||
"{" /* req 3 */
|
||||
"\"jsonrpc\":" "\"2.0\","
|
||||
"\"id\":" "3, "
|
||||
"\"response\":" "[1,2,3]"
|
||||
"}",
|
||||
"{" /* req 4 */
|
||||
"\"jsonrpc\":" "\"2.0\","
|
||||
"\"id\":" "4, "
|
||||
"\"response\":" "{\"a\": \"b\"}"
|
||||
"}",
|
||||
"{" /* req 5 */
|
||||
"\"jsonrpc\":" "\"2.0\","
|
||||
"\"error\": {"
|
||||
"\"code\": -32601,"
|
||||
"\"message\":" "\"Method not found\""
|
||||
"},"
|
||||
"\"id\": \"5\""
|
||||
"}",
|
||||
};
|
||||
|
||||
static int expected_parse_result[] = {
|
||||
/* 1 */ 0,
|
||||
/* 2 */ 0,
|
||||
/* 3 */ 0,
|
||||
/* 4 */ 0,
|
||||
/* 5 */ 0,
|
||||
/* 6 */ LWSJRPCWKE__METHOD_NOT_FOUND,
|
||||
/* 7 */ LWSJRPCWKE__PARSE_ERROR,
|
||||
/* 8 */ LWSJRPCWKE__INVALID_REQUEST,
|
||||
/* 9 */ LWSJRPCWKE__PARSE_ERROR,
|
||||
/* 10 */ 0,
|
||||
/* 11 */ 0,
|
||||
/* 12 */ 0,
|
||||
/* 13 */ LWSJRPCWKE__INVALID_REQUEST,
|
||||
/* 14 */ LWSJRPCWKE__METHOD_NOT_FOUND,
|
||||
/* 15 */ 0,
|
||||
/* 16 */ 0,
|
||||
/* 17 */ 0,
|
||||
};
|
||||
|
||||
static int expected_parse_result_response[] = {
|
||||
/* 1 */ 0,
|
||||
/* 2 */ 0,
|
||||
/* 3 */ 0,
|
||||
/* 4 */ 0,
|
||||
/* 5 */ 0,
|
||||
};
|
||||
|
||||
/*
|
||||
* The Method-specific parser is an lejp parser callback that only sees the
|
||||
* subtree in request "params":
|
||||
*/
|
||||
|
||||
static const char * const paths_s1[] = {
|
||||
"subtrahend",
|
||||
"minuend",
|
||||
"[]"
|
||||
};
|
||||
static const char * const paths_s2[] = {
|
||||
"subtrahend",
|
||||
"minuend",
|
||||
"[]"
|
||||
};
|
||||
|
||||
static signed char
|
||||
parse_s1(struct lejp_ctx *ctx, char reason)
|
||||
{
|
||||
// struct lws_jrpc_obj *r;
|
||||
|
||||
/*
|
||||
* In the canonical examples, this can take either an array like
|
||||
* [1,2]
|
||||
* or an object like
|
||||
* {"subtrahend":23, "minuend":42 }
|
||||
*/
|
||||
|
||||
lwsl_notice("%s: reason %d, path %s, buf %.*s sp %d, pst_sp %d\n",
|
||||
__func__, reason, ctx->path, ctx->npos, ctx->buf, ctx->sp,
|
||||
ctx->pst_sp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static signed char
|
||||
parse_s2(struct lejp_ctx *ctx, char reason)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const lws_jrpc_method_t methods[] = {
|
||||
/* list methods used by the tests that are expected to exist */
|
||||
{ "subtract", paths_s1, parse_s1, LWS_ARRAY_SIZE(paths_s1) },
|
||||
{ "foobar", paths_s2, parse_s2, LWS_ARRAY_SIZE(paths_s2) },
|
||||
{ "update", paths_s2, parse_s2, LWS_ARRAY_SIZE(paths_s2) },
|
||||
{ "sum", paths_s2, parse_s2, LWS_ARRAY_SIZE(paths_s2) },
|
||||
{ "get_data", paths_s2, parse_s2, LWS_ARRAY_SIZE(paths_s2) },
|
||||
{ "notify_hello", paths_s2, parse_s2, LWS_ARRAY_SIZE(paths_s2) },
|
||||
{ "notify_sum", paths_s2, parse_s2, LWS_ARRAY_SIZE(paths_s2) },
|
||||
{ NULL, NULL, NULL, 0 } /* sentinel */
|
||||
};
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
int n, m, e = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
|
||||
struct lws_jrpc_obj *req;
|
||||
struct lws_jrpc *jrpc;
|
||||
const char *p;
|
||||
|
||||
if ((p = lws_cmdline_option(argc, argv, "-d")))
|
||||
logs = atoi(p);
|
||||
|
||||
lws_set_log_level(logs, NULL);
|
||||
lwsl_user("LWS API selftest: JSON-RPC\n");
|
||||
|
||||
for (m = 0; m < (int)LWS_ARRAY_SIZE(jrpc_request_tests); m++) {
|
||||
|
||||
lwsl_notice("%s: ++++++++++++++++ request %d\n", __func__, m + 1);
|
||||
|
||||
jrpc = lws_jrpc_create(methods, NULL);
|
||||
if (!jrpc) {
|
||||
lwsl_err("%s: unable to create JRPC context\n", __func__);
|
||||
e++;
|
||||
continue;
|
||||
}
|
||||
|
||||
req = NULL;
|
||||
n = lws_jrpc_obj_parse(jrpc, LWSJRPC_PARSE_REQUEST, NULL,
|
||||
jrpc_request_tests[m],
|
||||
strlen(jrpc_request_tests[m]), &req);
|
||||
|
||||
lwsl_info("%s: %d\n", __func__, n);
|
||||
|
||||
if (n != expected_parse_result[m]) {
|
||||
lwsl_err("%s: got %d, expected %d\n", __func__,
|
||||
n, expected_parse_result[m]);
|
||||
e++;
|
||||
}
|
||||
|
||||
lws_jrpc_destroy(&jrpc);
|
||||
}
|
||||
|
||||
if (e)
|
||||
goto bail;
|
||||
|
||||
for (m = 0; m < (int)LWS_ARRAY_SIZE(jrpc_response_tests); m++) {
|
||||
|
||||
lwsl_notice("%s: ++++++++++++++++ response %d\n", __func__, m + 1);
|
||||
|
||||
jrpc = lws_jrpc_create(methods, NULL);
|
||||
if (!jrpc) {
|
||||
lwsl_err("%s: unable to create JRPC context\n", __func__);
|
||||
e++;
|
||||
continue;
|
||||
}
|
||||
|
||||
req = NULL;
|
||||
n = lws_jrpc_obj_parse(jrpc, LWSJRPC_PARSE_RESPONSE, NULL,
|
||||
jrpc_response_tests[m],
|
||||
strlen(jrpc_response_tests[m]), &req);
|
||||
|
||||
lwsl_info("%s: %d\n", __func__, n);
|
||||
|
||||
if (n != expected_parse_result_response[m]) {
|
||||
lwsl_err("%s: got %d, expected %d\n", __func__, n,
|
||||
expected_parse_result[m]);
|
||||
e++;
|
||||
}
|
||||
|
||||
lws_jrpc_destroy(&jrpc);
|
||||
}
|
||||
|
||||
if (e)
|
||||
goto bail;
|
||||
|
||||
lwsl_user("Completed: PASS\n");
|
||||
|
||||
return 0;
|
||||
|
||||
bail:
|
||||
lwsl_user("Completed: FAIL\n");
|
||||
|
||||
return 1;
|
||||
}
|
Loading…
Add table
Reference in a new issue