1/*
2 * lws-api-test-lws_struct-json
3 *
4 * Written in 2010-2020 by Andy Green <andy@warmcat.com>
5 *
6 * This file is made available under the Creative Commons CC0 1.0
7 * Universal Public Domain Dedication.
8 *
9 * lws_struct apis are used to serialize and deserialize your C structs and
10 * linked-lists in a standardized way that's very modest on memory but
11 * convenient and easy to maintain.
12 *
13 * This second test file shows a worked example for how to express a schema
14 * and both consume JSON -> struct and struct -> JSON for it.
15 */
16
17#include <libwebsockets.h>
18
19static const char * const test2_json =
20"{"
21	"\"config\":["
22		"{"
23			"\"id1\":"		"null,"
24			"\"creds\":{"
25				"\"key1\":"	"\"\\\"xxxxxxxxx\\\"\","
26				"\"key2\":"	"null"
27			"},"
28			"\"frequency\":"	"0,"
29			"\"arg1\":"		"\"val1\","
30			"\"arg2\":"		"0,"
31			"\"priority\":"		"1,"
32			"\"ssid\":"		"\"\\\"nw2\\\"\""
33		"}, {"
34			"\"id2\":"		"null,"
35			"\"creds\": {"
36				"\"key1\":"	"\"\\\"xxxxxxxxxxxxx\\\"\","
37				"\"key2\":"	"null"
38			"},"
39			"\"frequency\":"	"11,"
40			"\"arg1\":"		"\"val2\","
41			"\"arg2\":"		"1420887242594,"
42			"\"priority\":"		"3,"
43			"\"ssid\":"		"\"\\\"nw1\\\"\""
44		"}"
45	"]"
46"}";
47
48static const char * const test2_json_expected =
49	"{\"config\":[{\"creds\":{\"key1\":\"\\u0022xxxxxxxxx\\u0022\"},"
50	 "\"arg1\":\"val1\",\"ssid\":\"\\u0022nw2\\u0022\","
51	 "\"frequency\":0,\"arg2\":0,\"priority\":1},"
52	 "{\"creds\":{\"key1\":\"\\u0022xxxxxxxxxxxxx\\u0022\"},"
53	 "\"arg1\":\"val2\",\"ssid\":\"\\u0022nw1\\u0022\","
54	 "\"frequency\":11,\"arg2\":1420887242594,\"priority\":3}]}"
55;
56
57/*
58 * level 3: Credentials object
59 */
60
61typedef struct t2_cred {
62	const char				*key1;
63	const char				*key2;
64} t2_cred_t;
65
66static const lws_struct_map_t lsm_t2_cred[] = {
67	LSM_STRING_PTR	(t2_cred_t, key1, "key1"),
68	LSM_STRING_PTR	(t2_cred_t, key2, "key2"),
69};
70
71/*
72 * level 2: Configuration object, containing a child credentials object
73 */
74
75typedef struct t2_config {
76	lws_dll2_t				list;
77	t2_cred_t 				*creds;
78	const char				*id1;
79	const char				*arg1;
80	const char				*ssid;
81	unsigned int				frequency;
82	unsigned long long			arg2;
83	unsigned int				priority;
84} t2_config_t;
85
86static const lws_struct_map_t lsm_t2_config[] = {
87	LSM_CHILD_PTR	(t2_config_t,
88			 creds,			/* the child pointer member */
89			 t2_cred_t,		/* the child type */
90			 NULL, lsm_t2_cred,	/* map object for item type */
91			 "creds"),		/* outer json object name */
92	LSM_STRING_PTR	(t2_config_t, id1, 	 "id1"),
93	LSM_STRING_PTR	(t2_config_t, arg1,	 "arg1"),
94	LSM_STRING_PTR	(t2_config_t, ssid,	 "ssid"),
95
96	LSM_UNSIGNED	(t2_config_t, frequency, "frequency"),
97	LSM_UNSIGNED	(t2_config_t, arg2,	 "arg2"),
98	LSM_UNSIGNED	(t2_config_t, priority,	 "priority"),
99};
100
101/*
102 * level 1: list-of-configurations object
103 */
104
105typedef struct t2_configs {
106	lws_dll2_owner_t 			configs;
107} t2_configs_t;
108
109static const lws_struct_map_t lsm_t2_configs[] = {
110	LSM_LIST	(t2_configs_t, configs, /* the list owner type/member */
111			 t2_config_t,  list,	/* the list item type/member */
112			 NULL, lsm_t2_config,	/* map object for item type */
113			 "config"),		/* outer json object name */
114};
115
116/*
117 * For parsing, this lists the kind of object we expect to parse so the struct
118 * can be allocated polymorphically.
119 *
120 * Lws uses an explicit "schema" member so the type is known unambiguously.  If
121 * in the incoming JSON the first member is not "schema", it will scan the
122 * maps listed here and instantiate the first object that has a member of that
123 * name.
124 */
125
126static const lws_struct_map_t lsm_schema[] = {
127	LSM_SCHEMA	(t2_configs_t, NULL, lsm_t2_configs, "t2"),
128	/* other schemata that might need parsing... */
129};
130
131
132
133static int
134t2_config_dump(struct lws_dll2 *d, void *user)
135{
136#if !defined(LWS_WITH_NO_LOGS)
137	t2_config_t *c = lws_container_of(d, t2_config_t, list);
138
139	lwsl_notice("%s:   id1 '%s'\n", __func__, c->id1);
140	lwsl_notice("%s:   arg1 '%s'\n", __func__, c->arg1);
141	lwsl_notice("%s:   ssid '%s'\n", __func__, c->ssid);
142
143	lwsl_notice("%s:   freq %d\n", __func__, c->frequency);
144	lwsl_notice("%s:   arg2 %llu\n", __func__, c->arg2);
145	lwsl_notice("%s:   priority %d\n", __func__, c->priority);
146
147	lwsl_notice("%s:      key1: %s, key2: %s\n", __func__,
148			     c->creds->key1, c->creds->key2);
149#endif
150
151	return 0;
152}
153
154static int
155t2_configs_dump(t2_configs_t *t2cs)
156{
157	lwsl_notice("%s: number of configs: %d\n", __func__,
158		    t2cs->configs.count);
159
160	lws_dll2_foreach_safe(&t2cs->configs, NULL, t2_config_dump);
161
162	return 0;
163}
164
165
166int
167test2(void)
168{
169	lws_struct_serialize_t *ser;
170	struct lejp_ctx ctx;
171	lws_struct_args_t a;
172	t2_configs_t *top;
173	uint8_t buf[4096];
174	size_t written;
175	int n, bad = 1;
176
177	lwsl_notice("%s: start \n", __func__);
178
179	memset(&a, 0, sizeof(a));
180	a.map_st[0] = lsm_schema;
181	a.map_entries_st[0] = LWS_ARRAY_SIZE(lsm_schema);
182	a.ac_block_size = 512;
183	lws_struct_json_init_parse(&ctx, NULL, &a);
184
185	n = lejp_parse(&ctx, (uint8_t *)test2_json, (int)strlen(test2_json));
186	lwsl_notice("%s: lejp_parse %d\n", __func__, n);
187	if (n < 0) {
188		lwsl_err("%s: test2 JSON decode failed '%s'\n",
189				__func__, lejp_error_to_string(n));
190		goto bail;
191	}
192	lwsac_info(a.ac);
193
194	top = (t2_configs_t *)a.dest; /* the top level object */
195
196	if (!top) {
197		lwsl_err("%s: no top level object\n", __func__);
198		goto bail;
199	}
200	t2_configs_dump(top);
201
202	/* 2. Let's reserialize the top level object and see what comes out */
203
204	ser = lws_struct_json_serialize_create(&lsm_schema[0], 1,
205					       LSSERJ_FLAG_OMIT_SCHEMA, top);
206	if (!ser) {
207		lwsl_err("%s: unable to init serialization\n", __func__);
208		goto bail;
209	}
210
211	do {
212		n = (int)lws_struct_json_serialize(ser, buf, sizeof(buf), &written);
213		switch (n) {
214		case LSJS_RESULT_FINISH:
215			puts((const char *)buf);
216			break;
217		case LSJS_RESULT_CONTINUE:
218		case LSJS_RESULT_ERROR:
219			goto bail;
220		}
221	} while (n == LSJS_RESULT_CONTINUE);
222
223	if (strcmp(test2_json_expected, (char *)buf)) {
224		lwsl_err("%s: expected %s\n", __func__, test2_json_expected);
225		goto bail;
226	}
227
228	lws_struct_json_serialize_destroy(&ser);
229
230	bad = 0;
231
232bail:
233	lwsac_free(&a.ac);
234
235	return bad;
236}
237