diff --git a/CMakeLists.txt b/CMakeLists.txt index db4e8c246..6367ddf4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,6 +101,8 @@ option(LWS_WITHOUT_DAEMONIZE "Don't build the daemonization api" ON) option(LWS_SSL_SERVER_WITH_ECDH_CERT "Include SSL server use ECDH certificate" OFF) option(LWS_WITH_LEJP "With the Lightweight JSON Parser" ON) option(LWS_WITH_SQLITE3 "Require SQLITE3 support" OFF) +option(LWS_WITH_STRUCT_JSON "Generic struct serialization to and from JSON" ON) +option(LWS_WITH_STRUCT_SQLITE3 "Generic struct serialization to and from SQLITE3" OFF) option(LWS_WITH_SMTP "Provide SMTP support" OFF) if (WIN32 OR LWS_WITH_ESP32) option(LWS_WITH_DIR "Directory scanning api support" OFF) @@ -189,6 +191,10 @@ if (NOT LWS_WITH_NETWORK) set(LWS_WITH_POLL 0) endif() +if (LWS_WITH_STRUCT_SQLITE3) + set(LWS_WITH_SQLITE3 1) +endif() + # do you care about this? Then send me a patch where it disables it on travis # but allows it on APPLE if (APPLE) @@ -898,7 +904,10 @@ set(HDR_PUBLIC set(SOURCES lib/core/alloc.c + lib/core/buflist.c lib/core/context.c + lib/core/lws_dll.c + lib/core/lws_dll2.c lib/core/libwebsockets.c lib/core/logs.c lib/misc/base64-decode.c @@ -1020,6 +1029,16 @@ if (LWS_WITH_DISKCACHE) lib/misc/diskcache.c) endif() +if (LWS_WITH_STRUCT_JSON) + list(APPEND SOURCES + lib/misc/lws-struct-lejp.c) +endif() + +if (LWS_WITH_STRUCT_SQLITE3) + list(APPEND SOURCES + lib/misc/lws-struct-sqlite.c) +endif() + if (NOT LWS_WITHOUT_CLIENT) list(APPEND SOURCES lib/core-net/connect.c diff --git a/READMEs/README.lws_struct.md b/READMEs/README.lws_struct.md new file mode 100644 index 000000000..00ca08abd --- /dev/null +++ b/READMEs/README.lws_struct.md @@ -0,0 +1,38 @@ +# lws_struct + +## Overview + +lws_struct provides a lightweight method for serializing and deserializing C +structs to and from JSON, and to and from sqlite3. + +![lws_struct overview](../doc-assets/lws_struct-overview.svg) + + - you provide a metadata array describing struct members one-time, then call + generic apis to serialize and deserialize + + - supports flat structs, single child struct pointers, and unbounded arrays / + linked-lists of child objects automatically using [lws_dll2 linked-lists](./README.lws_dll.md) + + - supports boolean and C types char, int, long, long long in explicitly signed + and unsigned forms + + - supports both char * type string members where the unbounded content is + separate and pointed to, and fixed length char array[] type members where + the content is part of the struct + + - huge linear strings are supported by storing to a temp lwsac of chained chunks, + which is written into a single linear chunk in the main lwsac once the + total string length is known + + - deserialization allocates into an [lwsac](../lib/misc/lwsac/README.md), so everything is inside as few + heap allocations as possible while still able to expand to handle arbitrary + array or strins sizes + + - when deserialized structs are finished with, a single call to free the + lwsac frees the whole thing without having to walk it + + - stateful serializaton and deserialization allows as-you-get packets incremental + parsing and production of chunks of as-you-can-send incremental serialization + output cleanly + +## Examples diff --git a/cmake/lws_config.h.in b/cmake/lws_config.h.in index 70cfa59bc..5b1be9b61 100644 --- a/cmake/lws_config.h.in +++ b/cmake/lws_config.h.in @@ -116,6 +116,8 @@ #cmakedefine LWS_WITH_SOCKS5 #cmakedefine LWS_WITH_STATEFUL_URLDECODE #cmakedefine LWS_WITH_STATS +#cmakedefine LWS_WITH_STRUCT_SQLITE3 +#cmakedefine LWS_WITH_SQLITE3 #cmakedefine LWS_WITH_THREADPOOL #cmakedefine LWS_WITH_TLS #cmakedefine LWS_WITH_UNIX_SOCK diff --git a/doc-assets/lws_struct-overview.svg b/doc-assets/lws_struct-overview.svg new file mode 100644 index 000000000..ba618c9a0 --- /dev/null +++ b/doc-assets/lws_struct-overview.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sqlite + + C structs + + + + JSON + transit + storage + processing + + + + + + + + + + + + + + + + + + + + lws_struct + + diff --git a/include/libwebsockets.h b/include/libwebsockets.h index 2f60a9e8e..31948abae 100644 --- a/include/libwebsockets.h +++ b/include/libwebsockets.h @@ -522,6 +522,7 @@ struct lws; #include #include #include +#include #include #include #include diff --git a/include/libwebsockets/lws-lejp.h b/include/libwebsockets/lws-lejp.h index 8e502377d..81a938313 100644 --- a/include/libwebsockets/lws-lejp.h +++ b/include/libwebsockets/lws-lejp.h @@ -173,6 +173,9 @@ 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 @@ -201,25 +204,36 @@ struct _lejp_stack { 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 */ - - signed char (*callback)(struct lejp_ctx *ctx, char reason); void *user; - const char * const *paths; /* 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; @@ -235,11 +249,11 @@ struct lejp_ctx { uint8_t f; uint8_t sp; /* stack head */ uint8_t ipos; /* index stack depth */ - uint8_t ppos; uint8_t count_paths; uint8_t path_match; uint8_t path_match_len; uint8_t wildcount; + uint8_t pst_sp; /* parsing stack head */ }; LWS_VISIBLE LWS_EXTERN void @@ -257,6 +271,21 @@ 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); diff --git a/include/libwebsockets/lws-misc.h b/include/libwebsockets/lws-misc.h index 7ed1490dd..971bfa042 100644 --- a/include/libwebsockets/lws-misc.h +++ b/include/libwebsockets/lws-misc.h @@ -241,18 +241,19 @@ lws_dll_foreach_safe(struct lws_dll *phead, void *user, struct lws_dll2; struct lws_dll2_owner; -struct lws_dll2 { +typedef struct lws_dll2 { struct lws_dll2 *prev; struct lws_dll2 *next; struct lws_dll2_owner *owner; -}; +} lws_dll2_t; -struct lws_dll2_owner { +typedef struct lws_dll2_owner { struct lws_dll2 *tail; struct lws_dll2 *head; uint32_t count; -}; +} lws_dll2_owner_t; + static LWS_INLINE int lws_dll2_is_detached(const struct lws_dll2 *d) { return !d->owner; } @@ -278,6 +279,12 @@ LWS_VISIBLE LWS_EXTERN int lws_dll2_foreach_safe(struct lws_dll2_owner *owner, void *user, int (*cb)(struct lws_dll2 *d, void *user)); +LWS_VISIBLE LWS_EXTERN void +lws_dll2_clear(struct lws_dll2 *d); + +LWS_VISIBLE LWS_EXTERN void +lws_dll2_owner_clear(struct lws_dll2_owner *d); + /* * these are safe against the current container object getting deleted, * since the hold his next in a temp and go to that next. ___tmp is @@ -332,6 +339,7 @@ lws_buflist_append_segment(struct lws_buflist **head, const uint8_t *buf, */ LWS_VISIBLE LWS_EXTERN size_t lws_buflist_next_segment_len(struct lws_buflist **head, uint8_t **buf); + /** * lws_buflist_use_segment(): remove len bytes from the current segment * @@ -349,6 +357,7 @@ lws_buflist_next_segment_len(struct lws_buflist **head, uint8_t **buf); */ LWS_VISIBLE LWS_EXTERN int lws_buflist_use_segment(struct lws_buflist **head, size_t len); + /** * lws_buflist_destroy_all_segments(): free all segments on the list * diff --git a/include/libwebsockets/lws-struct.h b/include/libwebsockets/lws-struct.h new file mode 100644 index 000000000..0d02f59e5 --- /dev/null +++ b/include/libwebsockets/lws-struct.h @@ -0,0 +1,258 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2019 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * included from libwebsockets.h + */ + +#if defined(LWS_WITH_STRUCT_SQLITE3) +#include +#endif + +typedef enum { + LSMT_SIGNED, + LSMT_UNSIGNED, + LSMT_BOOLEAN, + LSMT_STRING_CHAR_ARRAY, + LSMT_STRING_PTR, + LSMT_LIST, + LSMT_CHILD_PTR, + LSMT_SCHEMA, + +} lws_struct_map_type_eum; + +typedef struct lejp_collation { + struct lws_dll2 chunks; + int len; + char buf[LEJP_STRING_CHUNK + 1]; +} lejp_collation_t; + +typedef struct lws_struct_map { + const char *colname; + const struct lws_struct_map *child_map; + lejp_callback lejp_cb; + size_t ofs; /* child dll2; points to dll2_owner */ + size_t aux; + size_t ofs_clist; + size_t child_map_size; + lws_struct_map_type_eum type; +} lws_struct_map_t; + +typedef int (*lws_struct_args_cb)(void *obj, void *cb_arg); + +typedef struct lws_struct_args { + const lws_struct_map_t *map_st[LEJP_MAX_PARSING_STACK_DEPTH]; + lws_struct_args_cb cb; + struct lwsac *ac; + void *cb_arg; + void *dest; + + size_t dest_len; + size_t toplevel_dll2_ofs; + size_t map_entries_st[LEJP_MAX_PARSING_STACK_DEPTH]; + size_t ac_block_size; + int subtype; + + /* + * temp ac used to collate unknown possibly huge strings before final + * allocation and copy + */ + struct lwsac *ac_chunks; + struct lws_dll2_owner chunks_owner; + size_t chunks_length; +} lws_struct_args_t; + +#define LSM_SIGNED(type, name, qname) \ + { \ + qname, \ + NULL, \ + NULL, \ + offsetof(type, name), \ + sizeof ((type *)0)->name, \ + 0, \ + 0, \ + LSMT_SIGNED \ + } + +#define LSM_UNSIGNED(type, name, qname) \ + { \ + qname, \ + NULL, \ + NULL, \ + offsetof(type, name), \ + sizeof ((type *)0)->name, \ + 0, \ + 0, \ + LSMT_UNSIGNED \ + } + +#define LSM_BOOLEAN(type, name, qname) \ + { \ + qname, \ + NULL, \ + NULL, \ + offsetof(type, name), \ + sizeof ((type *)0)->name, \ + 0, \ + 0, \ + LSMT_BOOLEAN \ + } + +#define LSM_CARRAY(type, name, qname) \ + { \ + qname, \ + NULL, \ + NULL, \ + offsetof(type, name), \ + sizeof (((type *)0)->name), \ + 0, \ + 0, \ + LSMT_STRING_CHAR_ARRAY \ + } + +#define LSM_STRING_PTR(type, name, qname) \ + { \ + qname, \ + NULL, \ + NULL, \ + offsetof(type, name), \ + sizeof (((type *)0)->name), \ + 0, \ + 0, \ + LSMT_STRING_PTR \ + } + +#define LSM_LIST(ptype, pname, ctype, cname, lejp_cb, cmap, qname) \ + { \ + qname, \ + cmap, \ + lejp_cb, \ + offsetof(ptype, pname), \ + sizeof (ctype), \ + offsetof(ctype, cname), \ + LWS_ARRAY_SIZE(cmap), \ + LSMT_LIST \ + } + +#define LSM_CHILD_PTR(ptype, pname, ctype, lejp_cb, cmap, qname) \ + { \ + qname, \ + cmap, \ + lejp_cb, \ + offsetof(ptype, pname), \ + sizeof (ctype), \ + 0, \ + LWS_ARRAY_SIZE(cmap), \ + LSMT_CHILD_PTR \ + } + +#define LSM_SCHEMA(ctype, lejp_cb, map, schema_name) \ + { \ + schema_name, \ + map, \ + lejp_cb, \ + 0, \ + sizeof (ctype), \ + 0, \ + LWS_ARRAY_SIZE(map), \ + LSMT_SCHEMA \ + } + +#define LSM_SCHEMA_DLL2(ctype, cdll2mem, lejp_cb, map, schema_name) \ + { \ + schema_name, \ + map, \ + lejp_cb, \ + offsetof(ctype, cdll2mem), \ + sizeof (ctype), \ + 0, \ + LWS_ARRAY_SIZE(map), \ + LSMT_SCHEMA \ + } + +typedef struct lws_struct_serialize_st { + const struct lws_dll2 *dllpos; + const lws_struct_map_t *map; + const char *obj; + size_t map_entries; + size_t map_entry; + size_t size; + char subsequent; + char idt; +} lws_struct_serialize_st_t; + +enum { + LSSERJ_FLAG_PRETTY = 1 +}; + +typedef struct lws_struct_serialize { + lws_struct_serialize_st_t st[LEJP_MAX_PARSING_STACK_DEPTH]; + + size_t offset; + size_t remaining; + + int sp; + int flags; +} lws_struct_serialize_t; + +typedef enum { + LSJS_RESULT_CONTINUE, + LSJS_RESULT_FINISH, + LSJS_RESULT_ERROR +} lws_struct_json_serialize_result_t; + +LWS_VISIBLE LWS_EXTERN int +lws_struct_json_init_parse(struct lejp_ctx *ctx, lejp_callback cb, + void *user); + +LWS_VISIBLE LWS_EXTERN signed char +lws_struct_schema_only_lejp_cb(struct lejp_ctx *ctx, char reason); + +LWS_VISIBLE LWS_EXTERN signed char +lws_struct_default_lejp_cb(struct lejp_ctx *ctx, char reason); + +LWS_VISIBLE LWS_EXTERN lws_struct_serialize_t * +lws_struct_json_serialize_create(const lws_struct_map_t *map, + size_t map_entries, int flags, void *ptoplevel); + +LWS_VISIBLE LWS_EXTERN void +lws_struct_json_serialize_destroy(lws_struct_serialize_t **pjs); + +LWS_VISIBLE LWS_EXTERN lws_struct_json_serialize_result_t +lws_struct_json_serialize(lws_struct_serialize_t *js, uint8_t *buf, + size_t len, size_t *written); + +#if defined(LWS_WITH_STRUCT_SQLITE3) + +LWS_VISIBLE LWS_EXTERN int +lws_struct_sq3_deserialize(sqlite3 *pdb, const lws_struct_map_t *schema, + lws_dll2_owner_t *o, struct lwsac **ac, + uint64_t start, int limit); + +LWS_VISIBLE LWS_EXTERN int +lws_struct_sq3_create_table(sqlite3 *pdb, const lws_struct_map_t *schema); + +LWS_VISIBLE LWS_EXTERN int +lws_struct_sq3_open(struct lws_context *context, const char *sqlite3_path, + sqlite3 **pdb); + +LWS_VISIBLE LWS_EXTERN int +lws_struct_sq3_close(sqlite3 **pdb); + +#endif diff --git a/lib/core/buflist.c b/lib/core/buflist.c new file mode 100644 index 000000000..6d8190fac --- /dev/null +++ b/lib/core/buflist.c @@ -0,0 +1,170 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2019 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "core/private.h" + +#ifdef LWS_HAVE_SYS_TYPES_H +#include +#endif + +/* lws_buflist */ + +int +lws_buflist_append_segment(struct lws_buflist **head, const uint8_t *buf, + size_t len) +{ + struct lws_buflist *nbuf; + int first = !*head; + void *p = *head; + int sanity = 1024; + + assert(buf); + assert(len); + + /* append at the tail */ + while (*head) { + if (!--sanity) { + lwsl_err("%s: buflist reached sanity limit\n", __func__); + return -1; + } + if (*head == (*head)->next) { + lwsl_err("%s: corrupt list points to self\n", __func__); + return -1; + } + head = &((*head)->next); + } + + lwsl_info("%s: len %u first %d %p\n", __func__, (uint32_t)len, first, p); + + nbuf = (struct lws_buflist *)lws_malloc(sizeof(**head) + len, __func__); + if (!nbuf) { + lwsl_err("%s: OOM\n", __func__); + return -1; + } + + nbuf->len = len; + nbuf->pos = 0; + nbuf->next = NULL; + + p = (void *)nbuf->buf; + memcpy(p, buf, len); + + *head = nbuf; + + return first; /* returns 1 if first segment just created */ +} + +static int +lws_buflist_destroy_segment(struct lws_buflist **head) +{ + struct lws_buflist *old = *head; + + assert(*head); + *head = old->next; + old->next = NULL; + lws_free(old); + + return !*head; /* returns 1 if last segment just destroyed */ +} + +void +lws_buflist_destroy_all_segments(struct lws_buflist **head) +{ + struct lws_buflist *p = *head, *p1; + + while (p) { + p1 = p->next; + p->next = NULL; + lws_free(p); + p = p1; + } + + *head = NULL; +} + +size_t +lws_buflist_next_segment_len(struct lws_buflist **head, uint8_t **buf) +{ + if (!*head) { + if (buf) + *buf = NULL; + + return 0; + } + + if (!(*head)->len && (*head)->next) + lws_buflist_destroy_segment(head); + + if (!*head) { + if (buf) + *buf = NULL; + + return 0; + } + + assert((*head)->pos < (*head)->len); + + if (buf) + *buf = (*head)->buf + (*head)->pos; + + return (*head)->len - (*head)->pos; +} + +int +lws_buflist_use_segment(struct lws_buflist **head, size_t len) +{ + assert(*head); + assert(len); + assert((*head)->pos + len <= (*head)->len); + + (*head)->pos += len; + if ((*head)->pos == (*head)->len) + lws_buflist_destroy_segment(head); + + if (!*head) + return 0; + + return (int)((*head)->len - (*head)->pos); +} + +void +lws_buflist_describe(struct lws_buflist **head, void *id) +{ + struct lws_buflist *old; + int n = 0; + + if (*head == NULL) + lwsl_notice("%p: buflist empty\n", id); + + while (*head) { + lwsl_notice("%p: %d: %llu / %llu (%llu left)\n", id, n, + (unsigned long long)(*head)->pos, + (unsigned long long)(*head)->len, + (unsigned long long)(*head)->len - (*head)->pos); + old = *head; + head = &((*head)->next); + if (*head == old) { + lwsl_err("%s: next points to self\n", __func__); + break; + } + n++; + } +} diff --git a/lib/core/libwebsockets.c b/lib/core/libwebsockets.c index 6582566b5..9791ae194 100644 --- a/lib/core/libwebsockets.c +++ b/lib/core/libwebsockets.c @@ -94,321 +94,6 @@ int lws_open(const char *__file, int __oflag, ...) #endif -void -lws_dll_add_head(struct lws_dll *d, struct lws_dll *phead) -{ - if (!lws_dll_is_detached(d, phead)) { - assert(0); /* only wholly detached things can be added */ - return; - } - - /* our next guy is current first guy, if any */ - if (phead->next != d) - d->next = phead->next; - - /* if there is a next guy, set his prev ptr to our next ptr */ - if (d->next) - d->next->prev = d; - /* there is nobody previous to us, we are the head */ - d->prev = NULL; - - /* set the first guy to be us */ - phead->next = d; - - /* if there was nothing on the list before, we are also now the tail */ - if (!phead->prev) - phead->prev = d; - - assert(d->prev != d); - assert(d->next != d); -} - -void -lws_dll_add_tail(struct lws_dll *d, struct lws_dll *phead) -{ - if (!lws_dll_is_detached(d, phead)) { - assert(0); /* only wholly detached things can be added */ - return; - } - - /* our previous guy is current last guy */ - d->prev = phead->prev; - /* if there is a prev guy, set his next ptr to our prev ptr */ - if (d->prev) - d->prev->next = d; - /* our next ptr is NULL */ - d->next = NULL; - /* set the last guy to be us */ - phead->prev = d; - - /* list head is also us if we're the first */ - if (!phead->next) - phead->next = d; - - assert(d->prev != d); - assert(d->next != d); -} - -void -lws_dll_insert(struct lws_dll *n, struct lws_dll *target, - struct lws_dll *phead, int before) -{ - if (!lws_dll_is_detached(n, phead)) { - assert(0); /* only wholly detached things can be inserted */ - return; - } - if (!target) { - /* - * the case where there's no target identified degenerates to - * a simple add at head or tail - */ - if (before) { - lws_dll_add_head(n, phead); - return; - } - lws_dll_add_tail(n, phead); - return; - } - - /* - * in the case there's a target "cursor", we have to do the work to - * stitch the new guy in appropriately - */ - - if (before) { - /* - * we go before dd - * DDp <-> DD <-> DDn --> DDp <-> us <-> DD <-> DDn - */ - /* we point forward to dd */ - n->next = target; - /* we point back to what dd used to point back to */ - n->prev = target->prev; - /* DDp points forward to us now */ - if (target->prev) - target->prev->next = n; - /* DD points back to us now */ - target->prev = n; - - /* if target was the head, we are now the head */ - if (phead->next == target) - phead->next = n; - - /* since we are before another guy, we cannot become the tail */ - - } else { - /* - * we go after dd - * DDp <-> DD <-> DDn --> DDp <-> DD <-> us <-> DDn - */ - /* we point forward to what dd used to point forward to */ - n->next = target->next; - /* we point back to dd */ - n->prev = target; - /* DDn points back to us */ - if (target->next) - target->next->prev = n; - /* DD points forward to us */ - target->next = n; - - /* if target was the tail, we are now the tail */ - if (phead->prev == target) - phead->prev = n; - - /* since we go after another guy, we cannot become the head */ - } -} - -/* situation is: - * - * HEAD: struct lws_dll * = &entry1 - * - * Entry 1: struct lws_dll .pprev = &HEAD , .next = Entry 2 - * Entry 2: struct lws_dll .pprev = &entry1 , .next = &entry2 - * Entry 3: struct lws_dll .pprev = &entry2 , .next = NULL - * - * Delete Entry1: - * - * - HEAD = &entry2 - * - Entry2: .pprev = &HEAD, .next = &entry3 - * - Entry3: .pprev = &entry2, .next = NULL - * - * Delete Entry2: - * - * - HEAD = &entry1 - * - Entry1: .pprev = &HEAD, .next = &entry3 - * - Entry3: .pprev = &entry1, .next = NULL - * - * Delete Entry3: - * - * - HEAD = &entry1 - * - Entry1: .pprev = &HEAD, .next = &entry2 - * - Entry2: .pprev = &entry1, .next = NULL - * - */ - -void -lws_dll_remove(struct lws_dll *d) -{ - if (!d->prev && !d->next) - return; - - /* - * remove us - * - * USp <-> us <-> USn --> USp <-> USn - */ - - /* if we have a next guy, set his prev to our prev */ - if (d->next) - d->next->prev = d->prev; - - /* set our prev guy to our next guy instead of us */ - if (d->prev) - d->prev->next = d->next; - - /* we're out of the list, we should not point anywhere any more */ - d->prev = NULL; - d->next = NULL; -} - -void -lws_dll_remove_track_tail(struct lws_dll *d, struct lws_dll *phead) -{ - if (lws_dll_is_detached(d, phead)) { - assert(phead->prev != d); - assert(phead->next != d); - return; - } - - /* if we have a next guy, set his prev to our prev */ - if (d->next) - d->next->prev = d->prev; - - /* if we have a previous guy, set his next to our next */ - if (d->prev) - d->prev->next = d->next; - - if (phead->prev == d) - phead->prev = d->prev; - - if (phead->next == d) - phead->next = d->next; - - /* we're out of the list, we should not point anywhere any more */ - d->prev = NULL; - d->next = NULL; -} - - -int -lws_dll_foreach_safe(struct lws_dll *phead, void *user, - int (*cb)(struct lws_dll *d, void *user)) -{ - lws_start_foreach_dll_safe(struct lws_dll *, p, tp, phead->next) { - if (cb(p, user)) - return 1; - } lws_end_foreach_dll_safe(p, tp); - - return 0; -} - -int -lws_dll2_foreach_safe(struct lws_dll2_owner *owner, void *user, - int (*cb)(struct lws_dll2 *d, void *user)) -{ - lws_start_foreach_dll_safe(struct lws_dll2 *, p, tp, owner->head) { - if (cb(p, user)) - return 1; - } lws_end_foreach_dll_safe(p, tp); - - return 0; -} - -void -lws_dll2_add_head(struct lws_dll2 *d, struct lws_dll2_owner *owner) -{ - if (!lws_dll2_is_detached(d)) { - assert(0); /* only wholly detached things can be added */ - return; - } - - /* our next guy is current first guy, if any */ - if (owner->head != d) - d->next = owner->head; - - /* if there is a next guy, set his prev ptr to our next ptr */ - if (d->next) - d->next->prev = d; - /* there is nobody previous to us, we are the head */ - d->prev = NULL; - - /* set the first guy to be us */ - owner->head = d; - - if (!owner->tail) - owner->tail = d; - - d->owner = owner; - owner->count++; -} - -void -lws_dll2_add_tail(struct lws_dll2 *d, struct lws_dll2_owner *owner) -{ - if (!lws_dll2_is_detached(d)) { - assert(0); /* only wholly detached things can be added */ - return; - } - - /* our previous guy is current last guy */ - d->prev = owner->tail; - /* if there is a prev guy, set his next ptr to our prev ptr */ - if (d->prev) - d->prev->next = d; - /* our next ptr is NULL */ - d->next = NULL; - /* set the last guy to be us */ - owner->tail = d; - - /* list head is also us if we're the first */ - if (!owner->head) - owner->head = d; - - d->owner = owner; - owner->count++; -} - -void -lws_dll2_remove(struct lws_dll2 *d) -{ - if (lws_dll2_is_detached(d)) - return; - - /* if we have a next guy, set his prev to our prev */ - if (d->next) - d->next->prev = d->prev; - - /* if we have a previous guy, set his next to our next */ - if (d->prev) - d->prev->next = d->next; - - /* if we have phead, track the tail and head if it points to us... */ - - if (d->owner->tail == d) - d->owner->tail = d->prev; - - if (d->owner->head == d) - d->owner->head = d->next; - - d->owner->count--; - - /* we're out of the list, we should not point anywhere any more */ - d->owner = NULL; - d->prev = NULL; - d->next = NULL; -} - #if !(defined(LWS_PLAT_OPTEE) && !defined(LWS_WITH_NETWORK)) LWS_VISIBLE lws_usec_t @@ -441,151 +126,6 @@ lws_pthread_self_to_tsi(struct lws_context *context) #endif } - -/* lws_buflist */ - -int -lws_buflist_append_segment(struct lws_buflist **head, const uint8_t *buf, - size_t len) -{ - struct lws_buflist *nbuf; - int first = !*head; - void *p = *head; - int sanity = 1024; - - assert(buf); - assert(len); - - /* append at the tail */ - while (*head) { - if (!--sanity) { - lwsl_err("%s: buflist reached sanity limit\n", __func__); - return -1; - } - if (*head == (*head)->next) { - lwsl_err("%s: corrupt list points to self\n", __func__); - return -1; - } - head = &((*head)->next); - } - - lwsl_info("%s: len %u first %d %p\n", __func__, (uint32_t)len, first, p); - - nbuf = (struct lws_buflist *)lws_malloc(sizeof(**head) + len, __func__); - if (!nbuf) { - lwsl_err("%s: OOM\n", __func__); - return -1; - } - - nbuf->len = len; - nbuf->pos = 0; - nbuf->next = NULL; - - p = (void *)nbuf->buf; - memcpy(p, buf, len); - - *head = nbuf; - - return first; /* returns 1 if first segment just created */ -} - -static int -lws_buflist_destroy_segment(struct lws_buflist **head) -{ - struct lws_buflist *old = *head; - - assert(*head); - *head = old->next; - old->next = NULL; - lws_free(old); - - return !*head; /* returns 1 if last segment just destroyed */ -} - -void -lws_buflist_destroy_all_segments(struct lws_buflist **head) -{ - struct lws_buflist *p = *head, *p1; - - while (p) { - p1 = p->next; - p->next = NULL; - lws_free(p); - p = p1; - } - - *head = NULL; -} - -size_t -lws_buflist_next_segment_len(struct lws_buflist **head, uint8_t **buf) -{ - if (!*head) { - if (buf) - *buf = NULL; - - return 0; - } - - if (!(*head)->len && (*head)->next) - lws_buflist_destroy_segment(head); - - if (!*head) { - if (buf) - *buf = NULL; - - return 0; - } - - assert((*head)->pos < (*head)->len); - - if (buf) - *buf = (*head)->buf + (*head)->pos; - - return (*head)->len - (*head)->pos; -} - -int -lws_buflist_use_segment(struct lws_buflist **head, size_t len) -{ - assert(*head); - assert(len); - assert((*head)->pos + len <= (*head)->len); - - (*head)->pos += len; - if ((*head)->pos == (*head)->len) - lws_buflist_destroy_segment(head); - - if (!*head) - return 0; - - return (int)((*head)->len - (*head)->pos); -} - -void -lws_buflist_describe(struct lws_buflist **head, void *id) -{ - struct lws_buflist *old; - int n = 0; - - if (*head == NULL) - lwsl_notice("%p: buflist empty\n", id); - - while (*head) { - lwsl_notice("%p: %d: %llu / %llu (%llu left)\n", id, n, - (unsigned long long)(*head)->pos, - (unsigned long long)(*head)->len, - (unsigned long long)(*head)->len - (*head)->pos); - old = *head; - head = &((*head)->next); - if (*head == old) { - lwsl_err("%s: next points to self\n", __func__); - break; - } - n++; - } -} - LWS_EXTERN void * lws_context_user(struct lws_context *context) { diff --git a/lib/core/lws_dll.c b/lib/core/lws_dll.c new file mode 100644 index 000000000..bbb9c2b26 --- /dev/null +++ b/lib/core/lws_dll.c @@ -0,0 +1,246 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2019 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "core/private.h" + +#ifdef LWS_HAVE_SYS_TYPES_H +#include +#endif + + +void +lws_dll_add_head(struct lws_dll *d, struct lws_dll *phead) +{ + if (!lws_dll_is_detached(d, phead)) { + assert(0); /* only wholly detached things can be added */ + return; + } + + /* our next guy is current first guy, if any */ + if (phead->next != d) + d->next = phead->next; + + /* if there is a next guy, set his prev ptr to our next ptr */ + if (d->next) + d->next->prev = d; + /* there is nobody previous to us, we are the head */ + d->prev = NULL; + + /* set the first guy to be us */ + phead->next = d; + + /* if there was nothing on the list before, we are also now the tail */ + if (!phead->prev) + phead->prev = d; + + assert(d->prev != d); + assert(d->next != d); +} + +void +lws_dll_add_tail(struct lws_dll *d, struct lws_dll *phead) +{ + if (!lws_dll_is_detached(d, phead)) { + assert(0); /* only wholly detached things can be added */ + return; + } + + /* our previous guy is current last guy */ + d->prev = phead->prev; + /* if there is a prev guy, set his next ptr to our prev ptr */ + if (d->prev) + d->prev->next = d; + /* our next ptr is NULL */ + d->next = NULL; + /* set the last guy to be us */ + phead->prev = d; + + /* list head is also us if we're the first */ + if (!phead->next) + phead->next = d; + + assert(d->prev != d); + assert(d->next != d); +} + +void +lws_dll_insert(struct lws_dll *n, struct lws_dll *target, + struct lws_dll *phead, int before) +{ + if (!lws_dll_is_detached(n, phead)) { + assert(0); /* only wholly detached things can be inserted */ + return; + } + if (!target) { + /* + * the case where there's no target identified degenerates to + * a simple add at head or tail + */ + if (before) { + lws_dll_add_head(n, phead); + return; + } + lws_dll_add_tail(n, phead); + return; + } + + /* + * in the case there's a target "cursor", we have to do the work to + * stitch the new guy in appropriately + */ + + if (before) { + /* + * we go before dd + * DDp <-> DD <-> DDn --> DDp <-> us <-> DD <-> DDn + */ + /* we point forward to dd */ + n->next = target; + /* we point back to what dd used to point back to */ + n->prev = target->prev; + /* DDp points forward to us now */ + if (target->prev) + target->prev->next = n; + /* DD points back to us now */ + target->prev = n; + + /* if target was the head, we are now the head */ + if (phead->next == target) + phead->next = n; + + /* since we are before another guy, we cannot become the tail */ + + } else { + /* + * we go after dd + * DDp <-> DD <-> DDn --> DDp <-> DD <-> us <-> DDn + */ + /* we point forward to what dd used to point forward to */ + n->next = target->next; + /* we point back to dd */ + n->prev = target; + /* DDn points back to us */ + if (target->next) + target->next->prev = n; + /* DD points forward to us */ + target->next = n; + + /* if target was the tail, we are now the tail */ + if (phead->prev == target) + phead->prev = n; + + /* since we go after another guy, we cannot become the head */ + } +} + +/* situation is: + * + * HEAD: struct lws_dll * = &entry1 + * + * Entry 1: struct lws_dll .pprev = &HEAD , .next = Entry 2 + * Entry 2: struct lws_dll .pprev = &entry1 , .next = &entry2 + * Entry 3: struct lws_dll .pprev = &entry2 , .next = NULL + * + * Delete Entry1: + * + * - HEAD = &entry2 + * - Entry2: .pprev = &HEAD, .next = &entry3 + * - Entry3: .pprev = &entry2, .next = NULL + * + * Delete Entry2: + * + * - HEAD = &entry1 + * - Entry1: .pprev = &HEAD, .next = &entry3 + * - Entry3: .pprev = &entry1, .next = NULL + * + * Delete Entry3: + * + * - HEAD = &entry1 + * - Entry1: .pprev = &HEAD, .next = &entry2 + * - Entry2: .pprev = &entry1, .next = NULL + * + */ + +void +lws_dll_remove(struct lws_dll *d) +{ + if (!d->prev && !d->next) + return; + + /* + * remove us + * + * USp <-> us <-> USn --> USp <-> USn + */ + + /* if we have a next guy, set his prev to our prev */ + if (d->next) + d->next->prev = d->prev; + + /* set our prev guy to our next guy instead of us */ + if (d->prev) + d->prev->next = d->next; + + /* we're out of the list, we should not point anywhere any more */ + d->prev = NULL; + d->next = NULL; +} + +void +lws_dll_remove_track_tail(struct lws_dll *d, struct lws_dll *phead) +{ + if (lws_dll_is_detached(d, phead)) { + assert(phead->prev != d); + assert(phead->next != d); + return; + } + + /* if we have a next guy, set his prev to our prev */ + if (d->next) + d->next->prev = d->prev; + + /* if we have a previous guy, set his next to our next */ + if (d->prev) + d->prev->next = d->next; + + if (phead->prev == d) + phead->prev = d->prev; + + if (phead->next == d) + phead->next = d->next; + + /* we're out of the list, we should not point anywhere any more */ + d->prev = NULL; + d->next = NULL; +} + + +int +lws_dll_foreach_safe(struct lws_dll *phead, void *user, + int (*cb)(struct lws_dll *d, void *user)) +{ + lws_start_foreach_dll_safe(struct lws_dll *, p, tp, phead->next) { + if (cb(p, user)) + return 1; + } lws_end_foreach_dll_safe(p, tp); + + return 0; +} diff --git a/lib/core/lws_dll2.c b/lib/core/lws_dll2.c new file mode 100644 index 000000000..dad96bb38 --- /dev/null +++ b/lib/core/lws_dll2.c @@ -0,0 +1,138 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2019 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "core/private.h" + +#ifdef LWS_HAVE_SYS_TYPES_H +#include +#endif + +int +lws_dll2_foreach_safe(struct lws_dll2_owner *owner, void *user, + int (*cb)(struct lws_dll2 *d, void *user)) +{ + lws_start_foreach_dll_safe(struct lws_dll2 *, p, tp, owner->head) { + if (cb(p, user)) + return 1; + } lws_end_foreach_dll_safe(p, tp); + + return 0; +} + +void +lws_dll2_add_head(struct lws_dll2 *d, struct lws_dll2_owner *owner) +{ + if (!lws_dll2_is_detached(d)) { + assert(0); /* only wholly detached things can be added */ + return; + } + + /* our next guy is current first guy, if any */ + if (owner->head != d) + d->next = owner->head; + + /* if there is a next guy, set his prev ptr to our next ptr */ + if (d->next) + d->next->prev = d; + /* there is nobody previous to us, we are the head */ + d->prev = NULL; + + /* set the first guy to be us */ + owner->head = d; + + if (!owner->tail) + owner->tail = d; + + d->owner = owner; + owner->count++; +} + +void +lws_dll2_add_tail(struct lws_dll2 *d, struct lws_dll2_owner *owner) +{ + if (!lws_dll2_is_detached(d)) { + assert(0); /* only wholly detached things can be added */ + return; + } + + /* our previous guy is current last guy */ + d->prev = owner->tail; + /* if there is a prev guy, set his next ptr to our prev ptr */ + if (d->prev) + d->prev->next = d; + /* our next ptr is NULL */ + d->next = NULL; + /* set the last guy to be us */ + owner->tail = d; + + /* list head is also us if we're the first */ + if (!owner->head) + owner->head = d; + + d->owner = owner; + owner->count++; +} + +void +lws_dll2_remove(struct lws_dll2 *d) +{ + if (lws_dll2_is_detached(d)) + return; + + /* if we have a next guy, set his prev to our prev */ + if (d->next) + d->next->prev = d->prev; + + /* if we have a previous guy, set his next to our next */ + if (d->prev) + d->prev->next = d->next; + + /* if we have phead, track the tail and head if it points to us... */ + + if (d->owner->tail == d) + d->owner->tail = d->prev; + + if (d->owner->head == d) + d->owner->head = d->next; + + d->owner->count--; + + /* we're out of the list, we should not point anywhere any more */ + d->owner = NULL; + d->prev = NULL; + d->next = NULL; +} + +void +lws_dll2_clear(struct lws_dll2 *d) +{ + d->owner = NULL; + d->prev = NULL; + d->next = NULL; +} + +void +lws_dll2_owner_clear(struct lws_dll2_owner *d) +{ + d->head = NULL; + d->tail = NULL; + d->count = 0; +} diff --git a/lib/jose/jws/jose.c b/lib/jose/jws/jose.c index 2cb337be1..627fd237a 100644 --- a/lib/jose/jws/jose.c +++ b/lib/jose/jws/jose.c @@ -176,7 +176,8 @@ lws_jws_jose_cb(struct lejp_ctx *ctx, char reason) lejp_check_path_match(&args->jwk_jctx); if (args->jwk_jctx.path_match) - args->jwk_jctx.callback(&args->jwk_jctx, reason); + args->jwk_jctx.pst[args->jwk_jctx.pst_sp]. + callback(&args->jwk_jctx, reason); } // lwsl_notice("%s: %s %d (%d)\n", __func__, ctx->path, reason, ctx->sp); diff --git a/lib/misc/lejp.c b/lib/misc/lejp.c index 370126b6d..9f974479e 100644 --- a/lib/misc/lejp.c +++ b/lib/misc/lejp.c @@ -74,15 +74,20 @@ lejp_construct(struct lejp_ctx *ctx, ctx->st[0].b = 0; ctx->sp = 0; ctx->ipos = 0; - ctx->ppos = 0; ctx->path_match = 0; + ctx->path_stride = 0; ctx->path[0] = '\0'; - ctx->callback = callback; ctx->user = user; - ctx->paths = paths; - ctx->count_paths = count_paths; ctx->line = 1; - ctx->callback(ctx, LEJPCB_CONSTRUCTED); + + ctx->pst_sp = 0; + ctx->pst[0].callback = callback; + ctx->pst[0].paths = paths; + ctx->pst[0].count_paths = count_paths; + ctx->pst[0].user = NULL; + ctx->pst[0].ppos = 0; + + ctx->pst[0].callback(ctx, LEJPCB_CONSTRUCTED); } /** @@ -99,7 +104,7 @@ void lejp_destruct(struct lejp_ctx *ctx) { /* no allocations... just let callback know what it happening */ - ctx->callback(ctx, LEJPCB_DESTRUCTED); + ctx->pst[0].callback(ctx, LEJPCB_DESTRUCTED); } /** @@ -128,23 +133,29 @@ void lejp_change_callback(struct lejp_ctx *ctx, signed char (*callback)(struct lejp_ctx *ctx, char reason)) { - ctx->callback(ctx, LEJPCB_DESTRUCTED); - ctx->callback = callback; - ctx->callback(ctx, LEJPCB_CONSTRUCTED); - ctx->callback(ctx, LEJPCB_START); + ctx->pst[0].callback(ctx, LEJPCB_DESTRUCTED); + ctx->pst[0].callback = callback; + ctx->pst[0].callback(ctx, LEJPCB_CONSTRUCTED); + ctx->pst[0].callback(ctx, LEJPCB_START); } void lejp_check_path_match(struct lejp_ctx *ctx) { const char *p, *q; - int n; + int n, s = sizeof(char *); + + if (ctx->path_stride) + s = ctx->path_stride; /* we only need to check if a match is not active */ - for (n = 0; !ctx->path_match && n < ctx->count_paths; n++) { + for (n = 0; !ctx->path_match && + n < ctx->pst[ctx->pst_sp].count_paths; n++) { ctx->wildcount = 0; p = ctx->path; - q = ctx->paths[n]; + + q = *((char **)(((char *)ctx->pst[ctx->pst_sp].paths) + (n * s))); + while (*p && *q) { if (*q != '*') { if (*p != *q) @@ -170,7 +181,7 @@ lejp_check_path_match(struct lejp_ctx *ctx) continue; ctx->path_match = n + 1; - ctx->path_match_len = ctx->ppos; + ctx->path_match_len = ctx->pst[ctx->pst_sp].ppos; return; } @@ -188,7 +199,7 @@ lejp_get_wildcard(struct lejp_ctx *ctx, int wildcard, char *dest, int len) n = ctx->wild[wildcard]; - while (--len && n < ctx->ppos && + while (--len && n < ctx->pst[ctx->pst_sp].ppos && (n == ctx->wild[wildcard] || ctx->path[n] != '.')) *dest++ = ctx->path[n++]; @@ -222,8 +233,8 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) static const char esc_tran[] = "\"\\/\b\f\n\r\t"; static const char tokens[] = "rue alse ull "; - if (!ctx->sp && !ctx->ppos) - ctx->callback(ctx, LEJPCB_START); + if (!ctx->sp && !ctx->pst[ctx->pst_sp].ppos) + ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_START); while (len--) { c = *json++; @@ -252,7 +263,7 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) ret = LEJP_REJECT_IDLE_NO_BRACE; goto reject; } - if (ctx->callback(ctx, LEJPCB_OBJECT_START)) { + if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_OBJECT_START)) { ret = LEJP_REJECT_CALLBACK; goto reject; } @@ -284,7 +295,7 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) } if (ctx->st[ctx->sp - 1].s != LEJP_MP_DELIM) { ctx->buf[ctx->npos] = '\0'; - if (ctx->callback(ctx, + if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_VAL_STR_END) < 0) { ret = LEJP_REJECT_CALLBACK; goto reject; @@ -391,10 +402,10 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) goto reject; } ctx->st[ctx->sp].s = LEJP_MP_VALUE; - ctx->path[ctx->ppos] = '\0'; + ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0'; lejp_check_path_match(ctx); - if (ctx->callback(ctx, LEJPCB_PAIR_NAME)) { + if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_PAIR_NAME)) { ret = LEJP_REJECT_CALLBACK; goto reject; } @@ -415,7 +426,7 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) c = LEJP_MP_STRING; ctx->npos = 0; ctx->buf[0] = '\0'; - if (ctx->callback(ctx, LEJPCB_VAL_STR_START)) { + if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_VAL_STR_START)) { ret = LEJP_REJECT_CALLBACK; goto reject; } @@ -426,7 +437,7 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END; c = LEJP_MEMBERS; lejp_check_path_match(ctx); - if (ctx->callback(ctx, LEJPCB_OBJECT_START)) { + if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_OBJECT_START)) { ret = LEJP_REJECT_CALLBACK; goto reject; } @@ -437,10 +448,10 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) /* push */ ctx->st[ctx->sp].s = LEJP_MP_ARRAY_END; c = LEJP_MP_VALUE; - ctx->path[ctx->ppos++] = '['; - ctx->path[ctx->ppos++] = ']'; - ctx->path[ctx->ppos] = '\0'; - if (ctx->callback(ctx, LEJPCB_ARRAY_START)) { + ctx->path[ctx->pst[ctx->pst_sp].ppos++] = '['; + ctx->path[ctx->pst[ctx->pst_sp].ppos++] = ']'; + ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0'; + if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_ARRAY_START)) { ret = LEJP_REJECT_CALLBACK; goto reject; } @@ -464,12 +475,12 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) } /* drop the path [n] bit */ if (ctx->sp) { - ctx->ppos = ctx->st[ctx->sp - 1].p; + ctx->pst[ctx->pst_sp].ppos = ctx->st[ctx->sp - 1].p; ctx->ipos = ctx->st[ctx->sp - 1].i; } - ctx->path[ctx->ppos] = '\0'; + ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0'; if (ctx->path_match && - ctx->ppos <= ctx->path_match_len) + ctx->pst[ctx->pst_sp].ppos <= ctx->path_match_len) /* * we shrank the path to be * smaller than the matching point @@ -544,12 +555,12 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) ctx->buf[ctx->npos] = '\0'; if (ctx->f & LEJP_SEEN_POINT) { - if (ctx->callback(ctx, LEJPCB_VAL_NUM_FLOAT)) { + if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_VAL_NUM_FLOAT)) { ret = LEJP_REJECT_CALLBACK; goto reject; } } else { - if (ctx->callback(ctx, LEJPCB_VAL_NUM_INT)) { + if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_VAL_NUM_INT)) { ret = LEJP_REJECT_CALLBACK; goto reject; } @@ -580,7 +591,7 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) case 3: ctx->buf[0] = '1'; ctx->buf[1] = '\0'; - if (ctx->callback(ctx, LEJPCB_VAL_TRUE)) { + if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_VAL_TRUE)) { ret = LEJP_REJECT_CALLBACK; goto reject; } @@ -588,14 +599,14 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) case 8: ctx->buf[0] = '0'; ctx->buf[1] = '\0'; - if (ctx->callback(ctx, LEJPCB_VAL_FALSE)) { + if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_VAL_FALSE)) { ret = LEJP_REJECT_CALLBACK; goto reject; } break; case 12: ctx->buf[0] = '\0'; - if (ctx->callback(ctx, LEJPCB_VAL_NULL)) { + if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_VAL_NULL)) { ret = LEJP_REJECT_CALLBACK; goto reject; } @@ -605,12 +616,12 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) break; case LEJP_MP_COMMA_OR_END: - ctx->path[ctx->ppos] = '\0'; + ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0'; if (c == ',') { /* increment this stack level's index */ ctx->st[ctx->sp].s = LEJP_M_P; if (!ctx->sp) { - ctx->ppos = 0; + ctx->pst[ctx->pst_sp].ppos = 0; /* * since we came back to root level, * no path can still match @@ -618,10 +629,10 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) ctx->path_match = 0; break; } - ctx->ppos = ctx->st[ctx->sp - 1].p; - ctx->path[ctx->ppos] = '\0'; + ctx->pst[ctx->pst_sp].ppos = ctx->st[ctx->sp - 1].p; + ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0'; if (ctx->path_match && - ctx->ppos <= ctx->path_match_len) + ctx->pst[ctx->pst_sp].ppos <= ctx->path_match_len) /* * we shrank the path to be * smaller than the matching point @@ -649,12 +660,12 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) } /* drop the path [n] bit */ if (ctx->sp) { - ctx->ppos = ctx->st[ctx->sp - 1].p; + ctx->pst[ctx->pst_sp].ppos = ctx->st[ctx->sp - 1].p; ctx->ipos = ctx->st[ctx->sp - 1].i; } - ctx->path[ctx->ppos] = '\0'; + ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0'; if (ctx->path_match && - ctx->ppos <= ctx->path_match_len) + ctx->pst[ctx->pst_sp].ppos <= ctx->path_match_len) /* * we shrank the path to be * smaller than the matching point @@ -667,11 +678,11 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) if (c == '}') { if (!ctx->sp) { lejp_check_path_match(ctx); - if (ctx->callback(ctx, LEJPCB_OBJECT_END)) { + if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_OBJECT_END)) { ret = LEJP_REJECT_CALLBACK; goto reject; } - if (ctx->callback(ctx, LEJPCB_COMPLETE)) + if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_COMPLETE)) goto reject; else /* done, return unused amount */ @@ -680,19 +691,19 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) /* pop */ ctx->sp--; if (ctx->sp) { - ctx->ppos = ctx->st[ctx->sp - 1].p; + ctx->pst[ctx->pst_sp].ppos = ctx->st[ctx->sp - 1].p; ctx->ipos = ctx->st[ctx->sp - 1].i; } - ctx->path[ctx->ppos] = '\0'; + ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0'; if (ctx->path_match && - ctx->ppos <= ctx->path_match_len) + ctx->pst[ctx->pst_sp].ppos <= ctx->path_match_len) /* * we shrank the path to be * smaller than the matching point */ ctx->path_match = 0; lejp_check_path_match(ctx); - if (ctx->callback(ctx, LEJPCB_OBJECT_END)) { + if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_OBJECT_END)) { ret = LEJP_REJECT_CALLBACK; goto reject; } @@ -704,15 +715,15 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) case LEJP_MP_ARRAY_END: array_end: - ctx->path[ctx->ppos] = '\0'; + ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0'; if (c == ',') { /* increment this stack level's index */ if (ctx->ipos) ctx->i[ctx->ipos - 1]++; ctx->st[ctx->sp].s = LEJP_MP_VALUE; if (ctx->sp) - ctx->ppos = ctx->st[ctx->sp - 1].p; - ctx->path[ctx->ppos] = '\0'; + ctx->pst[ctx->pst_sp].ppos = ctx->st[ctx->sp - 1].p; + ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0'; break; } if (c != ']') { @@ -721,7 +732,7 @@ array_end: } ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END; - ctx->callback(ctx, LEJPCB_ARRAY_END); + ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_ARRAY_END); break; } @@ -732,7 +743,7 @@ emit_string_char: /* assemble the string value into chunks */ ctx->buf[ctx->npos++] = c; if (ctx->npos == sizeof(ctx->buf) - 1) { - if (ctx->callback(ctx, LEJPCB_VAL_STR_CHUNK)) { + if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_VAL_STR_CHUNK)) { ret = LEJP_REJECT_CALLBACK; goto reject; } @@ -741,22 +752,22 @@ emit_string_char: continue; } /* name part of name:value pair */ - ctx->path[ctx->ppos++] = c; + ctx->path[ctx->pst[ctx->pst_sp].ppos++] = c; continue; add_stack_level: /* push on to the object stack */ - if (ctx->ppos && ctx->st[ctx->sp].s != LEJP_MP_COMMA_OR_END && + if (ctx->pst[ctx->pst_sp].ppos && ctx->st[ctx->sp].s != LEJP_MP_COMMA_OR_END && ctx->st[ctx->sp].s != LEJP_MP_ARRAY_END) - ctx->path[ctx->ppos++] = '.'; + ctx->path[ctx->pst[ctx->pst_sp].ppos++] = '.'; - ctx->st[ctx->sp].p = ctx->ppos; + ctx->st[ctx->sp].p = ctx->pst[ctx->pst_sp].ppos; ctx->st[ctx->sp].i = ctx->ipos; if (++ctx->sp == LWS_ARRAY_SIZE(ctx->st)) { ret = LEJP_REJECT_STACK_OVERFLOW; goto reject; } - ctx->path[ctx->ppos] = '\0'; + ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0'; ctx->st[ctx->sp].s = c; ctx->st[ctx->sp].b = 0; continue; @@ -777,10 +788,55 @@ redo_character: return LEJP_CONTINUE; reject: - ctx->callback(ctx, LEJPCB_FAILED); + ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_FAILED); return ret; } +int +lejp_parser_push(struct lejp_ctx *ctx, void *user, const char * const *paths, + unsigned char paths_count, lejp_callback lejp_cb) +{ + struct _lejp_parsing_stack *p; + + if (ctx->pst_sp + 1 == LEJP_MAX_PARSING_STACK_DEPTH) + return -1; + + lejp_check_path_match(ctx); + + ctx->pst[ctx->pst_sp].path_match = ctx->path_match; + ctx->pst_sp++; + + p = &ctx->pst[ctx->pst_sp]; + p->user = user; + p->callback = lejp_cb; + p->paths = paths; + p->count_paths = paths_count; + p->ppos = 0; + + ctx->path_match = 0; + lejp_check_path_match(ctx); + + lwsl_debug("%s: pushed parser stack to %d (path %s)\n", __func__, + ctx->pst_sp, ctx->path); + + return 0; +} + +int +lejp_parser_pop(struct lejp_ctx *ctx) +{ + if (!ctx->pst_sp) + return -1; + + ctx->pst_sp--; + lwsl_debug("%s: popped parser stack to %d\n", __func__, ctx->pst_sp); + + ctx->path_match = 0; /* force it to check */ + lejp_check_path_match(ctx); + + return 0; +} + const char * lejp_error_to_string(int e) { diff --git a/lib/misc/lws-struct-lejp.c b/lib/misc/lws-struct-lejp.c new file mode 100644 index 000000000..af637cc3b --- /dev/null +++ b/lib/misc/lws-struct-lejp.c @@ -0,0 +1,762 @@ +/* + * libwebsockets - lws_struct JSON serialization helpers + * + * Copyright (C) 2019 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include +#include + +#include + +signed char +lws_struct_schema_only_lejp_cb(struct lejp_ctx *ctx, char reason) +{ + lws_struct_args_t *a = (lws_struct_args_t *)ctx->user; + const lws_struct_map_t *map = a->map_st[ctx->pst_sp]; + int n = a->map_entries_st[ctx->pst_sp]; + lejp_callback cb = map->lejp_cb; + + if (reason != LEJPCB_VAL_STR_END || ctx->path_match != 1) + return 0; + + while (n--) { + if (strcmp(ctx->buf, map->colname)) { + map++; + continue; + } + + a->dest = lwsac_use_zero(&a->ac, map->aux, a->ac_block_size); + if (!a->dest) { + lwsl_err("%s: OOT\n", __func__); + + return 1; + } + a->dest_len = map->aux; + + if (!cb) + cb = lws_struct_default_lejp_cb; + + lejp_parser_push(ctx, a->dest, &map->child_map[0].colname, + (uint8_t)map->child_map_size, cb); + a->map_st[ctx->pst_sp] = map->child_map; + a->map_entries_st[ctx->pst_sp] = map->child_map_size; + + return 0; + } + + lwsl_notice("%s: unknown schema %s\n", __func__, ctx->buf); + + return 1; +} + +static int +lws_struct_lejp_push(struct lejp_ctx *ctx, lws_struct_args_t *args, + const lws_struct_map_t *map, uint8_t *ch) +{ + lejp_callback cb = map->lejp_cb; + + if (!cb) + cb = lws_struct_default_lejp_cb; + + lejp_parser_push(ctx, ch, (const char * const*)map->child_map, + (uint8_t)map->child_map_size, cb); + + args->map_st[ctx->pst_sp] = map->child_map; + args->map_entries_st[ctx->pst_sp] = map->child_map_size; + + return 0; +} + +signed char +lws_struct_default_lejp_cb(struct lejp_ctx *ctx, char reason) +{ + lws_struct_args_t *args = (lws_struct_args_t *)ctx->user; + const lws_struct_map_t *map, *pmap = NULL; + uint8_t *ch; + char *u; + int n; + + if (reason == LEJPCB_ARRAY_END) { + lejp_parser_pop(ctx); + + return 0; + } + + if (reason == LEJPCB_ARRAY_START) { + map = &args->map_st[ctx->pst_sp][ctx->path_match - 1]; + n = args->map_entries_st[ctx->pst_sp]; + + if (map->type == LSMT_LIST) + lws_struct_lejp_push(ctx, args, map, NULL); + + return 0; + } + + if (ctx->pst_sp) + pmap = &args->map_st[ctx->pst_sp - 1] + [ctx->pst[ctx->pst_sp - 1].path_match - 1]; + map = &args->map_st[ctx->pst_sp][ctx->path_match - 1]; + n = args->map_entries_st[ctx->pst_sp]; + + if (reason == LEJPCB_OBJECT_START) { + + if (map->type != LSMT_CHILD_PTR) { + ctx->pst[ctx->pst_sp].user = NULL; + + return 0; + } + pmap = map; + + lws_struct_lejp_push(ctx, args, map, NULL); + map = &args->map_st[ctx->pst_sp][ctx->path_match - 1]; + n = args->map_entries_st[ctx->pst_sp]; + } + + if (reason == LEJPCB_OBJECT_END && pmap && pmap->type == LSMT_CHILD_PTR) + lejp_parser_pop(ctx); + + if (map->type == LSMT_SCHEMA) { + + while (n--) { + if (strcmp(map->colname, ctx->buf)) { + map++; + continue; + } + + /* instantiate the correct toplevel object */ + + ch = lwsac_use_zero(&args->ac, map->aux, + args->ac_block_size); + if (!ch) { + lwsl_err("OOM\n"); + + return 1; + } + + lws_struct_lejp_push(ctx, args, map, ch); + + return 0; + } + lwsl_notice("%s: unknown schema\n", __func__); + + goto cleanup; + } + + if (!ctx->pst[ctx->pst_sp].user) { + struct lws_dll2_owner *owner; + struct lws_dll2 *list; + + /* create list item object if none already */ + + if (!ctx->path_match || !pmap) + return 0; + + map = &args->map_st[ctx->pst_sp - 1][ctx->path_match - 1]; + n = args->map_entries_st[ctx->pst_sp - 1]; + + if (pmap->type != LSMT_LIST && pmap->type != LSMT_CHILD_PTR) + return 1; + + /* we need to create a child or array item object */ + + owner = (struct lws_dll2_owner *) + (((char *)ctx->pst[ctx->pst_sp - 1].user) + pmap->ofs); + + assert(pmap->aux); + + /* instantiate one of the child objects */ + + ctx->pst[ctx->pst_sp].user = lwsac_use_zero(&args->ac, + pmap->aux, args->ac_block_size); + if (!ctx->pst[ctx->pst_sp].user) { + lwsl_err("OOM\n"); + + return 1; + } + lwsl_notice("%s: created child object size %d\n", __func__, + (int)pmap->aux); + + if (pmap->type == LSMT_LIST) { + list = (struct lws_dll2 *)((char *)ctx->pst[ctx->pst_sp].user + + map->ofs_clist); + + lws_dll2_add_tail(list, owner); + } + } + + if (!ctx->path_match) + return 0; + + if (reason == LEJPCB_VAL_STR_CHUNK) { + lejp_collation_t *coll; + + /* don't cache stuff we are going to ignore */ + + if (map->type == LSMT_STRING_CHAR_ARRAY && + args->chunks_length >= map->aux) + return 0; + + coll = lwsac_use_zero(&args->ac_chunks, sizeof(*coll), + sizeof(*coll)); + if (!coll) { + lwsl_err("%s: OOT\n", __func__); + + return 1; + } + coll->chunks.prev = NULL; + coll->chunks.next = NULL; + coll->chunks.owner = NULL; + + coll->len = ctx->npos; + lws_dll2_add_tail(&coll->chunks, &args->chunks_owner); + + memcpy(coll->buf, ctx->buf, ctx->npos); + + args->chunks_length += ctx->npos; + + return 0; + } + + if (reason != LEJPCB_VAL_STR_END && reason != LEJPCB_VAL_NUM_INT && + reason != LEJPCB_VAL_TRUE && reason != LEJPCB_VAL_FALSE) + return 0; + + /* this is the end of the string */ + + if (ctx->pst[ctx->pst_sp].user && pmap && pmap->type == LSMT_CHILD_PTR) { + void **pp = (void **) + (((char *)ctx->pst[ctx->pst_sp - 1].user) + pmap->ofs); + + *pp = ctx->pst[ctx->pst_sp].user; + } + + u = (char *)ctx->pst[ctx->pst_sp].user; + if (!u) + u = (char *)ctx->pst[ctx->pst_sp - 1].user; + + { + char **pp, *s; + size_t lim, b; + long long li; + + switch (map->type) { + case LSMT_SIGNED: + if (map->aux == sizeof(signed char)) { + signed char *pc; + pc = (signed char *)(u + map->ofs); + *pc = atoi(ctx->buf); + break; + } + if (map->aux == sizeof(int)) { + int *pi; + pi = (int *)(u + map->ofs); + *pi = atoi(ctx->buf); + break; + } + if (map->aux == sizeof(long)) { + long *pl; + pl = (long *)(u + map->ofs); + *pl = atol(ctx->buf); + } else { + long long *pll; + pll = (long long *)(u + map->ofs); + *pll = atoll(ctx->buf); + } + break; + + case LSMT_UNSIGNED: + if (map->aux == sizeof(unsigned char)) { + unsigned char *pc; + pc = (unsigned char *)(u + map->ofs); + *pc = atoi(ctx->buf); + break; + } + if (map->aux == sizeof(unsigned int)) { + unsigned int *pi; + pi = (unsigned int *)(u + map->ofs); + *pi = atoi(ctx->buf); + break; + } + if (map->aux == sizeof(unsigned long)) { + unsigned long *pl; + pl = (unsigned long *)(u + map->ofs); + *pl = atol(ctx->buf); + } else { + unsigned long long *pll; + pll = (unsigned long long *)(u + map->ofs); + *pll = atoll(ctx->buf); + } + break; + + case LSMT_BOOLEAN: + li = reason == LEJPCB_VAL_TRUE; + if (map->aux == sizeof(char)) { + char *pc; + pc = (char *)(u + map->ofs); + *pc = (char)li; + break; + } + if (map->aux == sizeof(int)) { + int *pi; + pi = (int *)(u + map->ofs); + *pi = (int)li; + } else { + uint64_t *p64; + p64 = (uint64_t *)(u + map->ofs); + *p64 = li; + } + break; + + case LSMT_STRING_CHAR_ARRAY: + s = (char *)(u + map->ofs); + lim = map->aux - 1; + goto chunk_copy; + + case LSMT_STRING_PTR: + pp = (char **)(u + map->ofs); + lim = args->chunks_length + ctx->npos; + s = lwsac_use(&args->ac, lim + 1, args->ac_block_size); + if (!s) + goto cleanup; + *pp = s; + +chunk_copy: + s[lim] = '\0'; + /* copy up to lim from the string chunk ac first */ + lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1, + args->chunks_owner.head) { + lejp_collation_t *coll = (lejp_collation_t *)p; + + if (lim) { + b = coll->len; + if (b > lim) + b = lim; + memcpy(s, coll->buf, b); + s += b; + lim -= b; + } + } lws_end_foreach_dll_safe(p, p1); + + lwsac_free(&args->ac_chunks); + args->chunks_owner.count = 0; + args->chunks_owner.head = NULL; + args->chunks_owner.tail = NULL; + + if (lim) { + b = ctx->npos; + if (b > lim) + b = lim; + memcpy(s, ctx->buf, b); + } + break; + default: + break; + } + } + + if (args->cb) + args->cb(args->dest, args->cb_arg); + + return 0; + +cleanup: + lwsl_notice("%s: cleanup\n", __func__); + lwsac_free(&args->ac_chunks); + args->chunks_owner.count = 0; + args->chunks_owner.head = NULL; + args->chunks_owner.tail = NULL; + + return 1; +} + +static const char * schema[] = { "schema" }; + +int +lws_struct_json_init_parse(struct lejp_ctx *ctx, lejp_callback cb, void *user) +{ + if (!cb) + cb = lws_struct_schema_only_lejp_cb; + lejp_construct(ctx, cb, user, schema, 1); + + ctx->path_stride = sizeof(lws_struct_map_t); + + return 0; +} + +lws_struct_serialize_t * +lws_struct_json_serialize_create(const lws_struct_map_t *map, + size_t map_entries, int flags, + void *ptoplevel) +{ + lws_struct_serialize_t *js = lws_zalloc(sizeof(*js), __func__); + lws_struct_serialize_st_t *j; + + if (!js) + return NULL; + + js->flags = flags; + + j = &js->st[0]; + j->map = map; + j->map_entries = map_entries; + j->obj = ptoplevel; + j->idt = 0; + + return js; +} + +void +lws_struct_json_serialize_destroy(lws_struct_serialize_t **pjs) +{ + if (!*pjs) + return; + + lws_free(*pjs); + + *pjs = NULL; +} + +static void +lws_struct_pretty(lws_struct_serialize_t *js, uint8_t **pbuf, size_t *plen) +{ + if (js->flags & LSSERJ_FLAG_PRETTY) { + int n; + + *(*pbuf)++ = '\n'; + (*plen)--; + for (n = 0; n < js->st[js->sp].idt; n++) { + *(*pbuf)++ = ' '; + (*plen)--; + } + } +} + +lws_struct_json_serialize_result_t +lws_struct_json_serialize(lws_struct_serialize_t *js, uint8_t *buf, + size_t len, size_t *written) +{ + lws_struct_serialize_st_t *j; + const lws_struct_map_t *map; + size_t budget = 0, olen = len; + struct lws_dll2_owner *o; + unsigned long long uli; + const char *q; + const void *p; + char dbuf[72]; + long long li; + int n; + + *written = 0; + *buf = '\0'; + + while (len > sizeof(dbuf) + 20) { + j = &js->st[js->sp]; + map = &j->map[j->map_entry]; + q = j->obj + map->ofs; + + /* early check if the entry should be elided */ + + switch (map->type) { + case LSMT_STRING_PTR: + case LSMT_CHILD_PTR: + q = (char *)*(char **)q; + if (!q) + goto up; + break; + + case LSMT_LIST: + o = (struct lws_dll2_owner *)q; + p = j->dllpos = lws_dll2_get_head(o); + if (!p) + goto up; + break; + + default: + break; + } + + if (j->subsequent) { + *buf++ = ','; + len--; + lws_struct_pretty(js, &buf, &len); + } + j->subsequent = 1; + + if (map->type != LSMT_SCHEMA && !js->offset) { + n = lws_snprintf((char *)buf, len, "\"%s\":", + map->colname); + buf += n; + len -= n; + if (js->flags & LSSERJ_FLAG_PRETTY) { + *buf++ = ' '; + len--; + } + } + + switch (map->type) { + case LSMT_BOOLEAN: + case LSMT_UNSIGNED: + if (map->aux == sizeof(char)) { + uli = *(unsigned char *)q; + } else { + if (map->aux == sizeof(int)) { + uli = *(unsigned int *)q; + } else { + if (map->aux == sizeof(long)) + uli = *(unsigned long *)q; + else + uli = *(unsigned long long *)q; + } + } + q = dbuf; + + if (map->type == LSMT_BOOLEAN) { + budget = lws_snprintf(dbuf, sizeof(dbuf), + "%s", uli ? "true" : "false"); + } else + budget = lws_snprintf(dbuf, sizeof(dbuf), + "%llu", uli); + break; + + case LSMT_SIGNED: + if (map->aux == sizeof(signed char)) { + li = (long long)*(signed char *)q; + } else { + if (map->aux == sizeof(int)) { + li = (long long)*(int *)q; + } else { + if (map->aux == sizeof(long)) + li = (long long)*(long *)q; + else + li = *(long long *)q; + } + } + q = dbuf; + budget = lws_snprintf(dbuf, sizeof(dbuf), "%lld", li); + break; + + case LSMT_STRING_CHAR_ARRAY: + budget = strlen(q); + if (!js->offset) { + *buf++ = '\"'; + len--; + } + break; + + case LSMT_STRING_PTR: + budget = strlen(q); + if (!js->offset) { + *buf++ = '\"'; + len--; + } + break; + case LSMT_LIST: + *buf++ = '['; + len--; + if (js->sp + 1 == LEJP_MAX_PARSING_STACK_DEPTH) + return LSJS_RESULT_ERROR; + + /* add a stack level to handle parsing array members */ + + o = (struct lws_dll2_owner *)q; + p = j->dllpos = lws_dll2_get_head(o); + + if (!j->dllpos) { + *buf++ = ']'; + len--; + goto up; + } + + n = j->idt; + j = &js->st[++js->sp]; + j->idt = n + 2; + j->map = map->child_map; + j->map_entries = map->child_map_size; + j->size = map->aux; + j->subsequent = 0; + j->map_entry = 0; + lws_struct_pretty(js, &buf, &len); + *buf++ = '{'; + len--; + lws_struct_pretty(js, &buf, &len); + if (p) + j->obj = ((char *)p) - j->map->ofs_clist; + else + j->obj = NULL; + continue; + + case LSMT_CHILD_PTR: + + if (js->sp + 1 == LEJP_MAX_PARSING_STACK_DEPTH) + return LSJS_RESULT_ERROR; + + /* add a stack level tto handle parsing child members */ + + n = j->idt; + j = &js->st[++js->sp]; + j->idt = n + 2; + j->map = map->child_map; + j->map_entries = map->child_map_size; + j->size = map->aux; + j->subsequent = 0; + j->map_entry = 0; + *buf++ = '{'; + len--; + lws_struct_pretty(js, &buf, &len); + j->obj = q; + continue; + + case LSMT_SCHEMA: + q = dbuf; + *buf++ = '{'; + len--; + j = &js->st[++js->sp]; + lws_struct_pretty(js, &buf, &len); + budget = lws_snprintf(dbuf, 15, "\"schema\":"); + if (js->flags & LSSERJ_FLAG_PRETTY) + dbuf[budget++] = ' '; + + budget += lws_snprintf(dbuf + budget, + sizeof(dbuf) - budget, + "\"%s\"", map->colname); + + + if (js->sp != 1) + return LSJS_RESULT_ERROR; + j->map = map->child_map; + j->map_entries = map->child_map_size; + j->size = map->aux; + j->subsequent = 0; + j->map_entry = 0; + j->obj = js->st[js->sp - 1].obj; + j->dllpos = NULL; + /* we're actually at the same level */ + j->subsequent = 1; + j->idt = 1; + break; + } + + q += js->offset; + budget -= js->remaining; + + if (budget > len) { + js->remaining = budget - len; + js->offset = len; + budget = len; + } else { + js->remaining = 0; + js->offset = 0; + } + + memcpy(buf, q, budget); + buf += budget; + *buf = '\0'; + len -= budget; + + switch (map->type) { + case LSMT_STRING_CHAR_ARRAY: + case LSMT_STRING_PTR: + *buf++ = '\"'; + len--; + break; + case LSMT_SCHEMA: + continue; + default: + break; + } + + if (js->remaining) + continue; +up: + if (++j->map_entry < j->map_entries) + continue; + + if (!js->sp) + continue; + js->sp--; + if (!js->sp) { + lws_struct_pretty(js, &buf, &len); + *buf++ = '}'; + len--; + lws_struct_pretty(js, &buf, &len); + break; + } + js->offset = 0; + j = &js->st[js->sp]; + map = &j->map[j->map_entry]; + + if (map->type == LSMT_CHILD_PTR) { + lws_struct_pretty(js, &buf, &len); + *buf++ = '}'; + len--; + + /* we have done the singular child pointer */ + + js->offset = 0; + goto up; + } + + if (map->type != LSMT_LIST) + continue; + /* + * we are coming back up to an array map, it means we should + * advance to the next array member if there is one + */ + + lws_struct_pretty(js, &buf, &len); + *buf++ = '}'; + len--; + + p = j->dllpos = j->dllpos->next; + if (j->dllpos) { + /* + * there was another item in the array to do... let's + * move on to that nd do it + */ + *buf++ = ','; + len--; + lws_struct_pretty(js, &buf, &len); + js->offset = 0; + j = &js->st[++js->sp]; + j->map_entry = 0; + map = &j->map[j->map_entry]; + + *buf++ = '{'; + len--; + lws_struct_pretty(js, &buf, &len); + + j->subsequent = 0; + j->obj = ((char *)p) - j->map->ofs_clist; + continue; + } + + /* there are no further items in the array */ + + js->offset = 0; + lws_struct_pretty(js, &buf, &len); + *buf++ = ']'; + len--; + goto up; + } + + *written = olen - len; + *buf = '\0'; /* convenience, a NUL after the official end */ + + return LSJS_RESULT_FINISH; +} diff --git a/lib/misc/lws-struct-sqlite.c b/lib/misc/lws-struct-sqlite.c new file mode 100644 index 000000000..2ed2a4ef1 --- /dev/null +++ b/lib/misc/lws-struct-sqlite.c @@ -0,0 +1,275 @@ +/* + * libwebsockets - lws_struct JSON serialization helpers + * + * Copyright (C) 2019 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include +#include + +#include + +/* + * we get one of these per matching result from the query + */ + +static int +lws_struct_sq3_deser_cb(void *priv, int cols, char **cv, char **cn) +{ + lws_struct_args_t *a = (lws_struct_args_t *)priv; + const lws_struct_map_t *map = a->map_st[0]; + int n, mems = a->map_entries_st[0]; + lws_dll2_owner_t *o = (lws_dll2_owner_t *)a->cb_arg; + char *u = lwsac_use_zero(&a->ac, a->dest_len, a->ac_block_size); + long long li; + size_t lim; + char **pp; + char *s; + + if (!u) { + lwsl_err("OOM\n"); + + return 1; + } + + lws_dll2_add_tail((lws_dll2_t *)((char *)u + a->toplevel_dll2_ofs), o); + + while (mems--) { + for (n = 0; n < cols; n++) { + if (!cv[n] || strcmp(cn[n], map->colname)) + continue; + + switch (map->type) { + case LSMT_SIGNED: + if (map->aux == sizeof(signed char)) { + signed char *pc; + pc = (signed char *)(u + map->ofs); + *pc = atoi(cv[n]); + break; + } + if (map->aux == sizeof(int)) { + int *pi; + pi = (int *)(u + map->ofs); + *pi = atoi(cv[n]); + break; + } + if (map->aux == sizeof(long)) { + long *pl; + pl = (long *)(u + map->ofs); + *pl = atol(cv[n]); + break; + } + { + long long *pll; + pll = (long long *)(u + map->ofs); + *pll = atoll(cv[n]); + } + break; + + case LSMT_UNSIGNED: + if (map->aux == sizeof(unsigned char)) { + unsigned char *pc; + pc = (unsigned char *)(u + map->ofs); + *pc = atoi(cv[n]); + break; + } + if (map->aux == sizeof(unsigned int)) { + unsigned int *pi; + pi = (unsigned int *)(u + map->ofs); + *pi = atoi(cv[n]); + break; + } + if (map->aux == sizeof(unsigned long)) { + unsigned long *pl; + pl = (unsigned long *)(u + map->ofs); + *pl = atol(cv[n]); + break; + } + { + unsigned long long *pll; + pll = (unsigned long long *)(u + map->ofs); + *pll = atoll(cv[n]); + } + break; + + case LSMT_BOOLEAN: + li = 0; + if (!strcmp(cv[n], "true") || + !strcmp(cv[n], "TRUE") || cv[n][0] == '1') + li = 1; + if (map->aux == sizeof(char)) { + char *pc; + pc = (char *)(u + map->ofs); + *pc = (char)li; + break; + } + if (map->aux == sizeof(int)) { + int *pi; + pi = (int *)(u + map->ofs); + *pi = (int)li; + } else { + uint64_t *p64; + p64 = (uint64_t *)(u + map->ofs); + *p64 = li; + } + break; + + case LSMT_STRING_CHAR_ARRAY: + s = (char *)(u + map->ofs); + lim = map->aux - 1; + lws_strncpy(s, cv[n], lim); + break; + + case LSMT_STRING_PTR: + pp = (char **)(u + map->ofs); + lim = strlen(cv[n]); + s = lwsac_use(&a->ac, lim + 1, a->ac_block_size); + if (!s) + return 1; + *pp = s; + memcpy(s, cv[n], lim); + s[lim] = '\0'; + break; + default: + break; + } + } + map++; + } + + return 0; +} + +/* + * Call this with an LSM_SCHEMA map, its colname is the table name and its + * type information describes the toplevel type. Schema is dereferenced and + * put in args before the actual sq3 query, which is given the child map. + */ + +int +lws_struct_sq3_deserialize(sqlite3 *pdb, const lws_struct_map_t *schema, + lws_dll2_owner_t *o, struct lwsac **ac, + uint64_t start, int limit) +{ + char s[150], where[32]; + lws_struct_args_t a; + + memset(&a, 0, sizeof(a)); + a.cb_arg = o; /* lws_dll2_owner tracking query result objects */ + a.map_st[0] = schema->child_map; + a.map_entries_st[0] = schema->child_map_size; + a.dest_len = schema->aux; /* size of toplevel object to allocate */ + a.toplevel_dll2_ofs = schema->ofs; + + lws_dll2_owner_clear(o); + + where[0] = '\0'; + if (start) + lws_snprintf(where, sizeof(where), " where when < %llu ", + (unsigned long long)start); + + lws_snprintf(s, sizeof(s) - 1, "select * " + "from %s %s order by created desc limit %d;", + schema->colname, where, limit); + + if (sqlite3_exec(pdb, s, lws_struct_sq3_deser_cb, &a, NULL) != SQLITE_OK) { + lwsl_err("%s: fail\n", sqlite3_errmsg(pdb)); + lwsac_free(&a.ac); + return -1; + } + + *ac = a.ac; + + return 0; +} + +int +lws_struct_sq3_create_table(sqlite3 *pdb, const lws_struct_map_t *schema) +{ + const lws_struct_map_t *map = schema->child_map; + int map_size = schema->child_map_size, subsequent = 0; + char s[2048], *p = s, *end = &s[sizeof(s) - 1], *pri = "primary key"; + + p += lws_snprintf(p, end - p, "create table if not exists %s (", + schema->colname); + + while (map_size--) { + if (map->type > LSMT_STRING_PTR) { + map++; + continue; + } + if (subsequent && (end - p) > 3) + *p++ = ','; + subsequent = 1; + if (map->type < LSMT_STRING_CHAR_ARRAY) + p += lws_snprintf(p, end - p, "%s integer %s", + map->colname, pri); + else + p += lws_snprintf(p, end - p, "%s varchar %s", + map->colname, pri); + pri = ""; + map++; + } + + p += lws_snprintf(p, end - p, ");"); + + if (sqlite3_exec(pdb, s, NULL, NULL, NULL) != SQLITE_OK) { + lwsl_err("%s: %s: fail\n", __func__, sqlite3_errmsg(pdb)); + + return -1; + } + + return 0; +} + +int +lws_struct_sq3_open(struct lws_context *context, const char *sqlite3_path, + sqlite3 **pdb) +{ + int uid = 0, gid = 0; + + if (sqlite3_open_v2(sqlite3_path, pdb, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + NULL) != SQLITE_OK) { + lwsl_err("%s: Unable to open db %s: %s\n", + __func__, sqlite3_path, sqlite3_errmsg(*pdb)); + + return 1; + } + + lws_get_effective_uid_gid(context, &uid, &gid); + if (uid) + chown(sqlite3_path, uid, gid); + chmod(sqlite3_path, 0600); + + sqlite3_extended_result_codes(*pdb, 1); + + return 0; +} + +int +lws_struct_sq3_close(sqlite3 **pdb) +{ + if (!*pdb) + return 0; + + sqlite3_close(*pdb); + *pdb = NULL; + + return 0; +} diff --git a/lib/misc/lwsac/lwsac.c b/lib/misc/lwsac/lwsac.c index a401bbc7f..6471c1652 100644 --- a/lib/misc/lwsac/lwsac.c +++ b/lib/misc/lwsac/lwsac.c @@ -158,6 +158,7 @@ lwsac_free(struct lwsac **head) { struct lwsac *it = *head; + *head = NULL; lwsl_debug("%s: head %p\n", __func__, *head); while (it) { @@ -166,14 +167,15 @@ lwsac_free(struct lwsac **head) free(it); it = tmp; } - - *head = NULL; } void lwsac_info(struct lwsac *head) { - lwsl_debug("%s: lac %p: %dKiB in %d blocks\n", __func__, head, + if (!head) + lwsl_debug("%s: empty\n", __func__); + else + lwsl_debug("%s: lac %p: %dKiB in %d blocks\n", __func__, head, (int)(head->total_alloc_size >> 10), head->total_blocks); } diff --git a/minimal-examples/api-tests/README.md b/minimal-examples/api-tests/README.md index ff8d48ed3..8a1477d3b 100644 --- a/minimal-examples/api-tests/README.md +++ b/minimal-examples/api-tests/README.md @@ -3,6 +3,7 @@ These are buildable test apps that run in CI to confirm correct api operation. |name|tests| ---|--- api-test-lwsac|LWS Allocated Chunks api +api-test-lws_struct-json|Selftests for lws_struct JSON serialization and deserialization api-test-lws_tokenize|Generic secure string tokenizer api api-test-fts|LWS Full-text Search api api-test-gencrypto|LWS Generic Crypto apis diff --git a/minimal-examples/api-tests/api-test-lws_struct-json/CMakeLists.txt b/minimal-examples/api-tests/api-test-lws_struct-json/CMakeLists.txt new file mode 100644 index 000000000..a0900f866 --- /dev/null +++ b/minimal-examples/api-tests/api-test-lws_struct-json/CMakeLists.txt @@ -0,0 +1,73 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckCSourceCompiles) + +set(SAMP lws-api-test-lws_struct-json) +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 \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() + + + + add_executable(${SAMP} ${SRCS}) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets) + endif() diff --git a/minimal-examples/api-tests/api-test-lws_struct-json/README.md b/minimal-examples/api-tests/api-test-lws_struct-json/README.md new file mode 100644 index 000000000..ebe930d24 --- /dev/null +++ b/minimal-examples/api-tests/api-test-lws_struct-json/README.md @@ -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 |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 +``` + diff --git a/minimal-examples/api-tests/api-test-lws_struct-json/main.c b/minimal-examples/api-tests/api-test-lws_struct-json/main.c new file mode 100644 index 000000000..98c7d93ad --- /dev/null +++ b/minimal-examples/api-tests/api-test-lws_struct-json/main.c @@ -0,0 +1,365 @@ +/* + * lws-api-test-lws_struct-json + * + * Copyright (C) 2019 Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * lws_struct apis are used to serialize and deserialize your C structs and + * linked-lists in a standardized way that's very modest on memory but + * convenient and easy to maintain. + * + * The API test shows how to serialize and deserialize a struct with a linked- + * list of child structs in JSON using lws_struct APIs. + */ + +#include + +/* + * in this example, the JSON is for one "builder" object, which may specify + * a child list "targets" of zero or more "target" objects. + */ + +static const char * const json_tests[] = { + "{" /* test 1 */ + "\"schema\":\"com-warmcat-sai-builder\"," + + "\"hostname\":\"learn\"," + "\"nspawn_timeout\":1800," + "\"targets\":[" + "{" + "\"name\":\"target1\"," + "\"someflag\":true" + "}," + "{" + "\"name\":\"target2\"," + "\"someflag\":false" + "}" + "]" + "}", + "{" /* test 2 */ + "\"schema\":\"com-warmcat-sai-builder\"," + + "\"hostname\":\"learn\"," + "\"targets\":[" + "{" + "\"name\":\"target1\"" + "}," + "{" + "\"name\":\"target2\"" + "}," + "{" + "\"name\":\"target3\"" + "}" + "]" + "}", "{" /* test 3 */ + "\"schema\":\"com-warmcat-sai-builder\"," + + "\"hostname\":\"learn\"," + "\"nspawn_timeout\":1800," + "\"targets\":[" + "{" + "\"name\":\"target1\"," + "\"unrecognized\":\"xyz\"," + "\"child\": {" + "\"somename\": \"abc\"," + "\"junk\": { \"x\": \"y\" }" + "}" + "}," + "{" + "\"name\":\"target2\"" + "}" + "]" + "}", + "{" /* test 4 */ + "\"schema\":\"com-warmcat-sai-builder\"," + + "\"hostname\":\"learn\"," + "\"nspawn_timeout\":1800" + "}", + "{" /* test 5 */ + "\"schema\":\"com-warmcat-sai-builder\"" + "}", + "{" /* test 6 ... check huge strings into smaller fixed char array */ + "\"schema\":\"com-warmcat-sai-builder\"," + "\"hostname\":\"" + "PYvtan6kqppjnS0KpYTCaiOLsJkc7XecAr1kcE0aCIciewYB+JcLG82mO1Vb1mJtjDwUjBxy2I6A" + "zefzoWUWmqZbsv4MXR55j9bKlyz1liiSX63iO0x6JAwACMtE2MkgcLwR86TSWAD9D1QKIWqg5RJ/" + "CRuVsW0DKAUMD52ql4JmPFuJpJgTq28z6PhYNzN3yI3bmQt6bzhA+A/xAsFzSBnb3MHYWzGMprr5" + "3FAP1ISo5Ec9i+2ehV40sG6Q470sH3PGQZ0YRPO7Sh/SyrSQ/scONmxRc3AcXl7X/CSs417ii+CV" + "8sq3ZgcxKNB7tNfN7idNx3upZ00G2BZy9jSy03cLKKLNaNUt0TQsxXbH55uDHzSEeZWvxJgT6zB1" + "NoMhdC02w+oXim94M6z6COCnqT3rgkGk8PHMry9Bkh4yVpRmzIRfMmln/lEhdZgxky2+g5hhlSIG" + "JYDCrdynD9kCfvfy6KGOpNIi1X+mhbbWn4lnL9ZKihL/RrfOV+oV4R26IDq+KqUiJBENeo8/GXkG" + "LUH/87iPyzXKEMavr6fkrK0vTGto8yEYxmOyaVz8phG5rwf4jJgmYNoMbGo8gWvhqO7UAGy2g7MW" + "v+B/t1eZZ+1euLsNrWAsFJiFbQKgdFfQT3RjB14iU8knlQ8usoy+pXssY2ddGJGVcGC21oZvstK9" + "eu1eRZftda/wP+N5unT1Hw7kCoVzqxHieiYt47EGIOaaQ7XjZDK6qPN6O/grHnvJZm2vBkxuXgsY" + "VkRQ7AuTWIecphqFsq7Wbc1YNbMW47SVU5zMD0WaCqbaaI0t4uIzRvPlD8cpiiTzFTrEHlIBTf8/" + "uZjjEGGLhJR1jPqA9D1Ej3ChV+ye6F9JTUMlozRMsGuF8U4btDzH5xdnmvRS4Ar6LKEtAXGkj2yu" + "yJln+v4RIWj2xOGPJovOqiXwi0FyM61f8U8gj0OiNA2/QlvrqQVDF7sMXgjvaE7iQt5vMETteZlx" + "+z3f+jTFM/aon511W4+ZkRD+6AHwucvM9BEC\"" + "}", + "{" /* test 7 ... check huge strings into char * */ + "\"schema\":\"com-warmcat-sai-builder\"," + "\"targets\":[" + "{" + "\"name\":\"" + "PYvtan6kqppjnS0KpYTCaiOLsJkc7XecAr1kcE0aCIciewYB+JcLG82mO1Vb1mJtjDwUjBxy2I6A" + "zefzoWUWmqZbsv4MXR55j9bKlyz1liiSX63iO0x6JAwACMtE2MkgcLwR86TSWAD9D1QKIWqg5RJ/" + "CRuVsW0DKAUMD52ql4JmPFuJpJgTq28z6PhYNzN3yI3bmQt6bzhA+A/xAsFzSBnb3MHYWzGMprr5" + "3FAP1ISo5Ec9i+2ehV40sG6Q470sH3PGQZ0YRPO7Sh/SyrSQ/scONmxRc3AcXl7X/CSs417ii+CV" + "8sq3ZgcxKNB7tNfN7idNx3upZ00G2BZy9jSy03cLKKLNaNUt0TQsxXbH55uDHzSEeZWvxJgT6zB1" + "NoMhdC02w+oXim94M6z6COCnqT3rgkGk8PHMry9Bkh4yVpRmzIRfMmln/lEhdZgxky2+g5hhlSIG" + "JYDCrdynD9kCfvfy6KGOpNIi1X+mhbbWn4lnL9ZKihL/RrfOV+oV4R26IDq+KqUiJBENeo8/GXkG" + "LUH/87iPyzXKEMavr6fkrK0vTGto8yEYxmOyaVz8phG5rwf4jJgmYNoMbGo8gWvhqO7UAGy2g7MW" + "v+B/t1eZZ+1euLsNrWAsFJiFbQKgdFfQT3RjB14iU8knlQ8usoy+pXssY2ddGJGVcGC21oZvstK9" + "eu1eRZftda/wP+N5unT1Hw7kCoVzqxHieiYt47EGIOaaQ7XjZDK6qPN6O/grHnvJZm2vBkxuXgsY" + "VkRQ7AuTWIecphqFsq7Wbc1YNbMW47SVU5zMD0WaCqbaaI0t4uIzRvPlD8cpiiTzFTrEHlIBTf8/" + "uZjjEGGLhJR1jPqA9D1Ej3ChV+ye6F9JTUMlozRMsGuF8U4btDzH5xdnmvRS4Ar6LKEtAXGkj2yu" + "yJln+v4RIWj2xOGPJovOqiXwi0FyM61f8U8gj0OiNA2/QlvrqQVDF7sMXgjvaE7iQt5vMETteZlx" + "+z3f+jTFM/aon511W4+ZkRD+6AHwucvM9BEC\"}]}" + "}", +}; + +/* + * These are the expected outputs for each test, without pretty formatting. + * + * There are some differences to do with missing elements being rendered with + * default values. + */ + +static const char * const json_expected[] = { + "{\"schema\":\"com-warmcat-sai-builder\",\"hostname\":\"learn\"," + "\"nspawn_timeout\":1800,\"targets\":[{\"name\":\"target1\",\"someflag\":true}," + "{\"name\":\"target2\",\"someflag\":false}]}", + + "{\"schema\":\"com-warmcat-sai-builder\",\"hostname\":\"learn\"," + "\"nspawn_timeout\":0,\"targets\":[{\"name\":\"target1\",\"someflag\":false}," + "{\"name\":\"target2\",\"someflag\":false},{\"name\":\"target3\",\"someflag\":false}]}", + + "{\"schema\":\"com-warmcat-sai-builder\",\"hostname\":\"learn\"," + "\"nspawn_timeout\":1800,\"targets\":[{\"name\":\"target1\",\"someflag\":false," + "\"child\":{\"somename\":\"abc\"}},{\"name\":\"target2\",\"someflag\":false}]}", + + "{\"schema\":\"com-warmcat-sai-builder\"," + "\"hostname\":\"learn\",\"nspawn_timeout\":1800}", + + "{\"schema\":\"com-warmcat-sai-builder\",\"hostname\":\"\"," + "\"nspawn_timeout\":0}", + + "{\"schema\":\"com-warmcat-sai-builder\",\"hostname\":" + "\"PYvtan6kqppjnS0KpYTCaiOLsJkc7Xe\"," + "\"nspawn_timeout\":0}", + + "{\"schema\":\"com-warmcat-sai-builder\",\"hostname\":\"\"," + "\"nspawn_timeout\":0,\"targets\":[{\"name\":\"PYvtan6kqppjnS0KpYTC" + "aiOLsJkc7XecAr1kcE0aCIciewYB+JcLG82mO1Vb1mJtjDwUjBxy2I6Azefz" + "oWUWmqZbsv4MXR55j9bKlyz1liiSX63iO0x6JAwACMtE2MkgcLwR86TSWAD9" + "D1QKIWqg5RJ/CRuVsW0DKAUMD52ql4JmPFuJpJgTq28z6PhYNzN3yI3bmQt6" + "bzhA+A/xAsFzSBnb3MHYWzGMprr53FAP1ISo5Ec9i+2ehV40sG6Q470sH3PG" + "QZ0YRPO7Sh/SyrSQ/scONmxRc3AcXl7X/CSs417ii+CV8sq3ZgcxKNB7tNfN" + "7idNx3upZ00G2BZy9jSy03cLKKLNaNUt0TQsxXbH55uDHzSEeZWvxJgT6zB1" + "NoMhdC02w+oXim94M6z6COCnqT3rgkGk8PHMry9Bkh4yVpRmzIRfMmln/lEh" + "dZgxky2+g5hhlSIGJYDCrdynD9kCfvfy6KGOpNIi1X+mhbbWn4lnL9ZKihL/" + "RrfOV+oV4R26IDq+KqUiJBENeo8/GXkGLUH/87iPyzXKEMavr6fkrK0vTGto" + "8yEYxmOyaVz8phG5rwf4jJgmYNoMbGo8gWvhqO7UAGy2g7MWv+B/t1eZZ+1e" + "uLsNrWAsFJiFbQKgdFfQT3RjB14iU8knlQ8usoy+pXssY2ddGJGVcGC21oZv" + "stK9eu1eRZftda/wP+N5unT1Hw7kCoVzqxHieiYt47EGIOaaQ7XjZDK6qPN6" + "O/grHnvJZm2vBkxuXgsYVkRQ7AuTWIecphqFsq7Wbc1YNbMW47SVU5zMD0Wa" + "CqbaaI0t4uIzRvPlD8cpiiTzFTrEHlIBTf8/uZjjEGGLhJR1jPqA9D1Ej3Ch" + "V+ye6F9JTUMlozRMsGuF8U4btDzH5xdnmvRS4Ar6LKEtAXGkj2yuyJln+v4R" + "IWj2xOGPJovOqiXwi0FyM61f8U8gj0OiNA2/QlvrqQVDF7sMXgjvaE7iQt5v" + "METteZlx+z3f+jTFM/aon511W4+ZkRD+6AHwucvM9BEC\"" + ",\"someflag\":false}]}" +}; + +/* + * These annotate the members in the struct that will be serialized and + * deserialized with type and size information, as well as the name to use + * in the serialization format. + * + * Struct members that aren't annotated like this won't be serialized and + * when the struct is created during deserialiation, the will be set to 0 + * or NULL. + */ + +/* child object */ + +typedef struct sai_child { + const char * somename; +} sai_child_t; + +lws_struct_map_t lsm_child[] = { /* describes serializable members */ + LSM_STRING_PTR (sai_child_t, somename, "somename"), +}; + +/* target object */ + +typedef struct sai_target { + struct lws_dll2 target_list; + sai_child_t * child; + + const char * name; + char someflag; +} sai_target_t; + +static const lws_struct_map_t lsm_target[] = { + LSM_STRING_PTR (sai_target_t, name, "name"), + LSM_BOOLEAN (sai_target_t, someflag, "someflag"), + LSM_CHILD_PTR (sai_target_t, child, sai_child_t, + NULL, lsm_child, "child"), +}; + +/* builder object */ + +typedef struct sai_builder { + struct lws_dll2_owner targets; + + char hostname[32]; + unsigned int nspawn_timeout; +} sai_builder_t; + +static const lws_struct_map_t lsm_builder[] = { + LSM_CARRAY (sai_builder_t, hostname, "hostname"), + LSM_UNSIGNED (sai_builder_t, nspawn_timeout, "nspawn_timeout"), + LSM_LIST (sai_builder_t, targets, + sai_target_t, target_list, + NULL, lsm_target, "targets"), +}; + +/* Schema table + * + * Before we can understand the serialization top level format, we must read + * the schema, use the table below to create the right toplevel object for the + * schema name, and select the correct map tables to interpret the rest of the + * serialization. + * + * Therefore the schema tables below are the starting point for the + * JSON deserialization. + */ + +static const lws_struct_map_t lsm_schema_map[] = { + LSM_SCHEMA (sai_builder_t, NULL, + lsm_builder, "com-warmcat-sai-builder"), +}; + +static int +show_target(struct lws_dll2 *d, void *user) +{ + sai_target_t *t = lws_container_of(d, sai_target_t, target_list); + + lwsl_notice(" target.name '%s' (target %p)\n", t->name, t); + + if (t->child) + lwsl_notice(" child %p, target.child.somename '%s'\n", + t->child, t->child->somename); + + return 0; +} + + +int main(int argc, const char **argv) +{ + int n, m, e = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; +#if 1 + lws_struct_serialize_t *ser; + uint8_t buf[4096]; + size_t written; +#endif + struct lejp_ctx ctx; + lws_struct_args_t a; + sai_builder_t *b; + 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: lws_struct JSON\n"); + + for (m = 0; m < (int)LWS_ARRAY_SIZE(json_tests); m++) { + + /* 1. deserialize the canned JSON into structs */ + + lwsl_notice("%s: ++++++++++++++++ test %d\n", __func__, m + 1); + + memset(&a, 0, sizeof(a)); + a.map_st[0] = lsm_schema_map; + a.map_entries_st[0] = LWS_ARRAY_SIZE(lsm_schema_map); + a.ac_block_size = 512; + + lws_struct_json_init_parse(&ctx, NULL, &a); + n = (int)(signed char)lejp_parse(&ctx, (uint8_t *)json_tests[m], + strlen(json_tests[m])); + if (n < 0) { + lwsl_err("%s: notification JSON decode failed '%s'\n", + __func__, lejp_error_to_string(n)); + e++; + goto done; + } + lwsac_info(a.ac); + + b = a.dest; + if (!b) { + lwsl_err("%s: didn't produce any output\n", __func__); + e++; + goto done; + } + + lwsl_notice("builder.hostname = '%s', timeout = %d, targets (%d)\n", + b->hostname, b->nspawn_timeout, + b->targets.count); + + lws_dll2_foreach_safe(&b->targets, NULL, show_target); + + /* 2. serialize the structs into JSON and confirm */ + + lwsl_notice("%s: .... strarting serialization of test %d\n", + __func__, m + 1); + ser = lws_struct_json_serialize_create(lsm_schema_map, + LWS_ARRAY_SIZE(lsm_schema_map), + 0//LSSERJ_FLAG_PRETTY + , b); + if (!ser) { + lwsl_err("%s: unable to init serialization\n", __func__); + goto bail; + } + + do { + n = lws_struct_json_serialize(ser, buf, sizeof(buf), + &written); + lwsl_notice("ser says %d\n", n); + switch (n) { + case LSJS_RESULT_CONTINUE: + case LSJS_RESULT_FINISH: + puts((const char *)buf); + break; + case LSJS_RESULT_ERROR: + goto bail; + } + } while(n == LSJS_RESULT_CONTINUE); + + if (strcmp(json_expected[m], (char *)buf)) { + lwsl_err("%s: test %d: expected %s\n", __func__, m + 1, + json_expected[m]); + e++; + } + + lws_struct_json_serialize_destroy(&ser); + +done: + lwsac_free(&a.ac); + } + + if (e) + goto bail; + + lwsl_user("Completed: PASS\n"); + + return 0; + +bail: + lwsl_user("Completed: FAIL\n"); + + return 1; +} diff --git a/minimal-examples/api-tests/api-test-lws_struct-json/selftest.sh b/minimal-examples/api-tests/api-test-lws_struct-json/selftest.sh new file mode 100755 index 000000000..16d1e2e8e --- /dev/null +++ b/minimal-examples/api-tests/api-test-lws_struct-json/selftest.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# +# $1: path to minimal example binaries... +# if lws is built with -DLWS_WITH_MINIMAL_EXAMPLES=1 +# that will be ./bin from your build dir +# +# $2: path for logs and results. The results will go +# in a subdir named after the directory this script +# is in +# +# $3: offset for test index count +# +# $4: total test count +# +# $5: path to ./minimal-examples dir in lws +# +# Test return code 0: OK, 254: timed out, other: error indication + +. $5/selftests-library.sh + +COUNT_TESTS=1 + +dotest $1 $2 apiselftest +exit $FAILS