1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-30 00:00:16 +01:00
libwebsockets/include/libwebsockets/lws-lejp.h
Andy Green dcaa0013b4 lecp: add CBOR stream parser LECP like JSON LEJP
This provides very memory-efficient CBOR stream parsing
and writing.

The parser  converts pieces of CBOR into callbacks that define
the structure and collate string and blobs into buffer chunks
for extensible and easy access.

It is fragementation-safe and does not need all the CBOR in
the same place at one time, chunks of CBOR are parsed and
discarded as provided.

It does not allocate and just needs a few hundred bytes of
stack for even huge CBOR objects.  Huge strings and blobs
are handled without needing memory to hold them atomically.

Includes ./minimal-examples/api-tests/api-test-lecp that
unit tests it against 82 official example CBORs and
26 additional test vectors from COSE (just checking the CBOR
parsing).

The writing apis allow printf style semantics with a variety
of CBOR-aware %-formats.  The apis write into a context that
manages output buffer usage, if the output buffer fills,
then the apis return with an AGAIN code that lets you issue
and reset the output buffer and repeat the api all to issue
more output.  The subsequent calls can occur much later or
from a different function context, so this is perfect for
WRITEABLE-mediated output from the network parts of lws.

See ./READMEs/README.cbor-lecp.md
2021-08-21 17:44:40 +01:00

301 lines
8.8 KiB
C

/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 2019 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.
*/
/** \defgroup lejp JSON parser
* ##JSON parsing related functions
* \ingroup lwsapi
*
* LEJP is an extremely lightweight JSON stream parser included in lws.
*/
//@{
struct lejp_ctx;
#if !defined(LWS_ARRAY_SIZE)
#define LWS_ARRAY_SIZE(_x) (sizeof(_x) / sizeof(_x[0]))
#endif
#define LEJP_FLAG_WS_KEEP 64
#define LEJP_FLAG_WS_COMMENTLINE 32
enum lejp_states {
LEJP_IDLE = 0,
LEJP_MEMBERS = 1,
LEJP_M_P = 2,
LEJP_MP_STRING = LEJP_FLAG_WS_KEEP | 3,
LEJP_MP_STRING_ESC = LEJP_FLAG_WS_KEEP | 4,
LEJP_MP_STRING_ESC_U1 = LEJP_FLAG_WS_KEEP | 5,
LEJP_MP_STRING_ESC_U2 = LEJP_FLAG_WS_KEEP | 6,
LEJP_MP_STRING_ESC_U3 = LEJP_FLAG_WS_KEEP | 7,
LEJP_MP_STRING_ESC_U4 = LEJP_FLAG_WS_KEEP | 8,
LEJP_MP_DELIM = 9,
LEJP_MP_VALUE = 10,
LEJP_MP_VALUE_NUM_INT = LEJP_FLAG_WS_KEEP | 11,
LEJP_MP_VALUE_NUM_EXP = LEJP_FLAG_WS_KEEP | 12,
LEJP_MP_VALUE_TOK = LEJP_FLAG_WS_KEEP | 13,
LEJP_MP_COMMA_OR_END = 14,
LEJP_MP_ARRAY_END = 15,
};
enum lejp_reasons {
LEJP_CONTINUE = -1,
LEJP_REJECT_IDLE_NO_BRACE = -2,
LEJP_REJECT_MEMBERS_NO_CLOSE = -3,
LEJP_REJECT_MP_NO_OPEN_QUOTE = -4,
LEJP_REJECT_MP_STRING_UNDERRUN = -5,
LEJP_REJECT_MP_ILLEGAL_CTRL = -6,
LEJP_REJECT_MP_STRING_ESC_ILLEGAL_ESC = -7,
LEJP_REJECT_ILLEGAL_HEX = -8,
LEJP_REJECT_MP_DELIM_MISSING_COLON = -9,
LEJP_REJECT_MP_DELIM_BAD_VALUE_START = -10,
LEJP_REJECT_MP_VAL_NUM_INT_NO_FRAC = -11,
LEJP_REJECT_MP_VAL_NUM_FORMAT = -12,
LEJP_REJECT_MP_VAL_NUM_EXP_BAD_EXP = -13,
LEJP_REJECT_MP_VAL_TOK_UNKNOWN = -14,
LEJP_REJECT_MP_C_OR_E_UNDERF = -15,
LEJP_REJECT_MP_C_OR_E_NOTARRAY = -16,
LEJP_REJECT_MP_ARRAY_END_MISSING = -17,
LEJP_REJECT_STACK_OVERFLOW = -18,
LEJP_REJECT_MP_DELIM_ISTACK = -19,
LEJP_REJECT_NUM_TOO_LONG = -20,
LEJP_REJECT_MP_C_OR_E_NEITHER = -21,
LEJP_REJECT_UNKNOWN = -22,
LEJP_REJECT_CALLBACK = -23
};
#define LEJP_FLAG_CB_IS_VALUE 64
enum lejp_callbacks {
LEJPCB_CONSTRUCTED = 0,
LEJPCB_DESTRUCTED = 1,
LEJPCB_START = 2,
LEJPCB_COMPLETE = 3,
LEJPCB_FAILED = 4,
LEJPCB_PAIR_NAME = 5,
LEJPCB_VAL_TRUE = LEJP_FLAG_CB_IS_VALUE | 6,
LEJPCB_VAL_FALSE = LEJP_FLAG_CB_IS_VALUE | 7,
LEJPCB_VAL_NULL = LEJP_FLAG_CB_IS_VALUE | 8,
LEJPCB_VAL_NUM_INT = LEJP_FLAG_CB_IS_VALUE | 9,
LEJPCB_VAL_NUM_FLOAT = LEJP_FLAG_CB_IS_VALUE | 10,
LEJPCB_VAL_STR_START = 11, /* notice handle separately */
LEJPCB_VAL_STR_CHUNK = LEJP_FLAG_CB_IS_VALUE | 12,
LEJPCB_VAL_STR_END = LEJP_FLAG_CB_IS_VALUE | 13,
LEJPCB_ARRAY_START = 14,
LEJPCB_ARRAY_END = 15,
LEJPCB_OBJECT_START = 16,
LEJPCB_OBJECT_END = 17,
};
/**
* _lejp_callback() - User parser actions
* \param ctx: LEJP context
* \param reason: Callback reason
*
* Your user callback is associated with the context at construction time,
* and receives calls as the parsing progresses.
*
* All of the callbacks may be ignored and just return 0.
*
* The reasons it might get called, found in @reason, are:
*
* LEJPCB_CONSTRUCTED: The context was just constructed... you might want to
* perform one-time allocation for the life of the context.
*
* LEJPCB_DESTRUCTED: The context is being destructed... if you made any
* allocations at construction-time, you can free them now
*
* LEJPCB_START: Parsing is beginning at the first byte of input
*
* LEJPCB_COMPLETE: Parsing has completed successfully. You'll get a 0 or
* positive return code from lejp_parse indicating the
* amount of unused bytes left in the input buffer
*
* LEJPCB_FAILED: Parsing failed. You'll get a negative error code
* returned from lejp_parse
*
* LEJPCB_PAIR_NAME: When a "name":"value" pair has had the name parsed,
* this callback occurs. You can find the new name at
* the end of ctx->path[]
*
* LEJPCB_VAL_TRUE: The "true" value appeared
*
* LEJPCB_VAL_FALSE: The "false" value appeared
*
* LEJPCB_VAL_NULL: The "null" value appeared
*
* LEJPCB_VAL_NUM_INT: A string representing an integer is in ctx->buf
*
* LEJPCB_VAL_NUM_FLOAT: A string representing a float is in ctx->buf
*
* LEJPCB_VAL_STR_START: We are starting to parse a string, no data yet
*
* LEJPCB_VAL_STR_CHUNK: We filled the string buffer in the ctx, but it's not
* the end of the string. We produce this to spill the
* intermediate buffer to the user code, so we can handle
* huge JSON strings using only the small buffer in the
* ctx. If the whole JSON string fits in the ctx buffer,
* you won't get these callbacks.
*
* LEJPCB_VAL_STR_END: String parsing has completed, the last chunk of the
* string is in ctx->buf.
*
* LEJPCB_ARRAY_START: An array started
*
* LEJPCB_ARRAY_END: An array ended
*
* LEJPCB_OBJECT_START: An object started
*
* LEJPCB_OBJECT_END: An object ended
*/
LWS_EXTERN signed char _lejp_callback(struct lejp_ctx *ctx, char reason);
typedef signed char (*lejp_callback)(struct lejp_ctx *ctx, char reason);
#ifndef LEJP_MAX_PARSING_STACK_DEPTH
#define LEJP_MAX_PARSING_STACK_DEPTH 5
#endif
#ifndef LEJP_MAX_DEPTH
#define LEJP_MAX_DEPTH 12
#endif
#ifndef LEJP_MAX_INDEX_DEPTH
#define LEJP_MAX_INDEX_DEPTH 8
#endif
#ifndef LEJP_MAX_PATH
#define LEJP_MAX_PATH 128
#endif
#ifndef LEJP_STRING_CHUNK
/* must be >= 30 to assemble floats */
#define LEJP_STRING_CHUNK 254
#endif
enum num_flags {
LEJP_SEEN_MINUS = (1 << 0),
LEJP_SEEN_POINT = (1 << 1),
LEJP_SEEN_POST_POINT = (1 << 2),
LEJP_SEEN_EXP = (1 << 3)
};
struct _lejp_stack {
char s; /* lejp_state stack*/
char p; /* path length */
char i; /* index array length */
char b; /* user bitfield */
};
struct _lejp_parsing_stack {
void *user; /* private to the stack level */
signed char (*callback)(struct lejp_ctx *ctx, char reason);
const char * const *paths;
uint8_t count_paths;
uint8_t ppos;
uint8_t path_match;
};
struct lejp_ctx {
/* sorted by type for most compact alignment
*
* pointers
*/
void *user;
/* arrays */
struct _lejp_parsing_stack pst[LEJP_MAX_PARSING_STACK_DEPTH];
struct _lejp_stack st[LEJP_MAX_DEPTH];
uint16_t i[LEJP_MAX_INDEX_DEPTH]; /* index array */
uint16_t wild[LEJP_MAX_INDEX_DEPTH]; /* index array */
char path[LEJP_MAX_PATH];
char buf[LEJP_STRING_CHUNK + 1];
/* size_t */
size_t path_stride; /* 0 means default ptr size, else stride */
/* int */
uint32_t line;
/* short */
uint16_t uni;
/* char */
uint8_t npos;
uint8_t dcount;
uint8_t f;
uint8_t sp; /* stack head */
uint8_t ipos; /* index stack depth */
uint8_t count_paths;
uint8_t path_match;
uint8_t path_match_len;
uint8_t wildcount;
uint8_t pst_sp; /* parsing stack head */
uint8_t outer_array;
};
LWS_VISIBLE LWS_EXTERN void
lejp_construct(struct lejp_ctx *ctx,
signed char (*callback)(struct lejp_ctx *ctx, char reason),
void *user, const char * const *paths, unsigned char paths_count);
LWS_VISIBLE LWS_EXTERN void
lejp_destruct(struct lejp_ctx *ctx);
LWS_VISIBLE LWS_EXTERN int
lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len);
LWS_VISIBLE LWS_EXTERN void
lejp_change_callback(struct lejp_ctx *ctx,
signed char (*callback)(struct lejp_ctx *ctx, char reason));
/*
* push the current paths / paths_count and lejp_cb to a stack in the ctx, and
* start using the new ones
*/
LWS_VISIBLE LWS_EXTERN int
lejp_parser_push(struct lejp_ctx *ctx, void *user, const char * const *paths,
unsigned char paths_count, lejp_callback lejp_cb);
/*
* pop the previously used paths / paths_count and lejp_cb, and continue
* parsing using those as before
*/
LWS_VISIBLE LWS_EXTERN int
lejp_parser_pop(struct lejp_ctx *ctx);
/* exported for use when reevaluating a path for use with a subcontext */
LWS_VISIBLE LWS_EXTERN void
lejp_check_path_match(struct lejp_ctx *ctx);
LWS_VISIBLE LWS_EXTERN int
lejp_get_wildcard(struct lejp_ctx *ctx, int wildcard, char *dest, int len);
LWS_VISIBLE LWS_EXTERN const char *
lejp_error_to_string(int e);
//@}