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.
+
+
+
+ - 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 @@
+
+
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