Branch data Line data Source code
1 : : /*
2 : : * Copyright (c) The ThingSet Project Contributors
3 : : *
4 : : * SPDX-License-Identifier: Apache-2.0
5 : : */
6 : :
7 : : /* this must be included before thingset.h */
8 : : #include "jsmn.h"
9 : :
10 : : #include <thingset.h>
11 : :
12 : : #include "thingset_internal.h"
13 : :
14 : : #include <errno.h>
15 : : #include <inttypes.h>
16 : : #include <math.h>
17 : : #include <stdarg.h>
18 : : #include <stdio.h>
19 : : #include <stdlib.h>
20 : : #include <string.h>
21 : :
22 : : #if CONFIG_THINGSET_BYTES_TYPE_SUPPORT
23 : : #include <zephyr/sys/base64.h>
24 : : #endif
25 : :
26 : : #if CONFIG_THINGSET_JSON_STRING_ESCAPING
27 : : #include <zephyr/sys/util.h>
28 : : #endif
29 : :
30 : 24 : static inline int txt_serialize_start(struct thingset_context *ts, char c)
31 : : {
32 [ + - ]: 24 : if (ts->rsp_size > ts->rsp_pos + 2) {
33 : 24 : ts->rsp[ts->rsp_pos++] = c;
34 : 24 : return 0;
35 : : }
36 : : else {
37 : 0 : ts->rsp_pos = 0;
38 : 0 : return -THINGSET_ERR_RESPONSE_TOO_LARGE;
39 : : }
40 : : }
41 : :
42 : 21 : static inline int txt_serialize_end(struct thingset_context *ts, char c)
43 : : {
44 [ + - ]: 21 : if (ts->rsp_size > ts->rsp_pos + 3) {
45 [ + - ]: 21 : if (ts->rsp[ts->rsp_pos - 1] == ',') {
46 : 21 : ts->rsp_pos--;
47 : : }
48 : 21 : ts->rsp[ts->rsp_pos++] = c;
49 : 21 : ts->rsp[ts->rsp_pos++] = ',';
50 : 21 : return 0;
51 : : }
52 : : else {
53 : 0 : ts->rsp_pos = 0;
54 : 0 : return -THINGSET_ERR_RESPONSE_TOO_LARGE;
55 : : }
56 : : }
57 : :
58 : 10 : static int txt_serialize_map_start(struct thingset_context *ts)
59 : : {
60 : 10 : return txt_serialize_start(ts, '{');
61 : : }
62 : :
63 : 10 : static int txt_serialize_map_end(struct thingset_context *ts)
64 : : {
65 : 10 : return txt_serialize_end(ts, '}');
66 : : }
67 : :
68 : 14 : static int txt_serialize_list_start(struct thingset_context *ts)
69 : : {
70 : 14 : return txt_serialize_start(ts, '[');
71 : : }
72 : :
73 : 11 : static int txt_serialize_list_end(struct thingset_context *ts)
74 : : {
75 : 11 : return txt_serialize_end(ts, ']');
76 : : }
77 : :
78 : 67 : static int txt_serialize_response(struct thingset_context *ts, uint8_t code, const char *msg, ...)
79 : : {
80 : : va_list vargs;
81 : :
82 : 67 : ts->rsp_pos = snprintf((char *)ts->rsp, ts->rsp_size, ":%.2X ", code);
83 : :
84 [ + + + - ]: 67 : if (msg != NULL && ts->rsp_size > 7) {
85 : 16 : ts->rsp[ts->rsp_pos++] = '"';
86 : 16 : va_start(vargs, msg);
87 : 16 : ts->rsp_pos +=
88 : 16 : vsnprintf((char *)ts->rsp + ts->rsp_pos, ts->rsp_size - ts->rsp_pos, msg, vargs);
89 : 16 : va_end(vargs);
90 [ + - ]: 16 : if (ts->rsp_pos + 1 < ts->rsp_size) {
91 : 16 : ts->rsp[ts->rsp_pos++] = '"';
92 : : }
93 : : else {
94 : : /* message did not fit: keep minimum message with error code only */
95 : 0 : ts->rsp_pos = 3;
96 : : }
97 : 16 : ts->rsp[ts->rsp_pos++] = ' ';
98 : : }
99 : :
100 : 67 : return 0;
101 : : }
102 : :
103 : : /**
104 : : * @returns Number of serialized bytes or negative ThingSet reponse code in case of error
105 : : */
106 : 96 : static int json_serialize_simple_value(char *buf, size_t size, union thingset_data_pointer data,
107 : : int type, int detail)
108 : : {
109 : : int pos;
110 : :
111 [ + + + + : 96 : switch (type) {
+ + + + +
+ + + -
+ ]
112 : : #if CONFIG_THINGSET_64BIT_TYPES_SUPPORT
113 : 2 : case THINGSET_TYPE_U64:
114 : 2 : pos = snprintf(buf, size, "%" PRIu64 ",", *data.u64);
115 : 2 : break;
116 : 2 : case THINGSET_TYPE_I64:
117 : 2 : pos = snprintf(buf, size, "%" PRIi64 ",", *data.i64);
118 : 2 : break;
119 : : #endif
120 : 10 : case THINGSET_TYPE_U32:
121 : 10 : pos = snprintf(buf, size, "%" PRIu32 ",", *data.u32);
122 : 10 : break;
123 : 11 : case THINGSET_TYPE_I32:
124 : 11 : pos = snprintf(buf, size, "%" PRIi32 ",", *data.i32);
125 : 11 : break;
126 : 2 : case THINGSET_TYPE_U16:
127 : 2 : pos = snprintf(buf, size, "%" PRIu16 ",", *data.u16);
128 : 2 : break;
129 : 2 : case THINGSET_TYPE_I16:
130 : 2 : pos = snprintf(buf, size, "%" PRIi16 ",", *data.i16);
131 : 2 : break;
132 : 2 : case THINGSET_TYPE_U8:
133 : 2 : pos = snprintf(buf, size, "%" PRIu8 ",", *data.u8);
134 : 2 : break;
135 : 2 : case THINGSET_TYPE_I8:
136 : 2 : pos = snprintf(buf, size, "%" PRIi8 ",", *data.i8);
137 : 2 : break;
138 : 25 : case THINGSET_TYPE_F32:
139 [ + + + + ]: 25 : if (isnan(*data.f32) || isinf(*data.f32)) {
140 : : /* JSON spec does not support NaN and Inf, so we need to use null instead */
141 : 2 : pos = snprintf(buf, size, "null,");
142 : 2 : break;
143 : : }
144 : : else {
145 : : /* explicit double-conversion required to please compiler */
146 : 23 : pos = snprintf(buf, size, "%.*f,", detail, (double)*data.f32);
147 : 23 : break;
148 : : }
149 : : #if CONFIG_THINGSET_DECFRAC_TYPE_SUPPORT
150 : 2 : case THINGSET_TYPE_DECFRAC:
151 : 2 : pos = snprintf(buf, size, "%" PRIi32 "e%" PRIi16 ",", *data.decfrac, -detail);
152 : 2 : break;
153 : : #endif
154 : 5 : case THINGSET_TYPE_BOOL:
155 [ + - ]: 5 : pos = snprintf(buf, size, "%s,", *data.b == true ? "true" : "false");
156 : 5 : break;
157 : 5 : case THINGSET_TYPE_STRING:
158 : : #if CONFIG_THINGSET_JSON_STRING_ESCAPING
159 : 5 : buf[0] = '"';
160 : 5 : pos = 1;
161 [ + - ]: 36 : for (int data_pos = 0; data_pos < detail; data_pos++) {
162 : 36 : int remaining_chars = detail - data_pos;
163 [ - + ]: 36 : if (pos + remaining_chars + 2 >= size) {
164 : : /* indicate that the buffer is too small (similar to snprintf) and stop */
165 : 0 : pos += remaining_chars + 2;
166 : 0 : break;
167 : : }
168 : :
169 [ + + ]: 36 : if (data.str[data_pos] == '\0') {
170 : 5 : break;
171 : : }
172 : :
173 [ + + - - : 31 : switch (data.str[data_pos]) {
+ - - + ]
174 : 1 : case '\\':
175 : 1 : buf[pos++] = '\\';
176 : 1 : buf[pos++] = '\\';
177 : 1 : break;
178 : 1 : case '\"':
179 : 1 : buf[pos++] = '\\';
180 : 1 : buf[pos++] = '"';
181 : 1 : break;
182 : 0 : case '\b':
183 : 0 : buf[pos++] = '\\';
184 : 0 : buf[pos++] = 'b';
185 : 0 : break;
186 : 0 : case '\f':
187 : 0 : buf[pos++] = '\\';
188 : 0 : buf[pos++] = 'f';
189 : 0 : break;
190 : 1 : case '\n':
191 : 1 : buf[pos++] = '\\';
192 : 1 : buf[pos++] = 'n';
193 : 1 : break;
194 : 0 : case '\r':
195 : 0 : buf[pos++] = '\\';
196 : 0 : buf[pos++] = 'r';
197 : 0 : break;
198 : 0 : case '\t':
199 : 0 : buf[pos++] = '\\';
200 : 0 : buf[pos++] = 't';
201 : 0 : break;
202 : 28 : default:
203 : 28 : buf[pos++] = data.str[data_pos];
204 : : }
205 : : }
206 : 5 : buf[pos++] = '"';
207 : 5 : buf[pos++] = ',';
208 : : #else
209 : : pos = snprintf(buf, size, "\"%s\",", data.str);
210 : : #endif
211 : 5 : break;
212 : : #if CONFIG_THINGSET_BYTES_TYPE_SUPPORT
213 : 0 : case THINGSET_TYPE_BYTES: {
214 : : size_t strlen;
215 : 0 : int err = base64_encode((uint8_t *)buf + 1, size - 4, &strlen, data.bytes->bytes,
216 : 0 : data.bytes->num_bytes);
217 [ # # ]: 0 : if (err == 0) {
218 : 0 : buf[0] = '\"';
219 : 0 : buf[strlen + 1] = '\"';
220 : 0 : buf[strlen + 2] = ',';
221 : 0 : buf[strlen + 3] = '\0';
222 : 0 : pos = strlen + 3;
223 : : }
224 : : else {
225 : 0 : pos = snprintf(buf, size, "null,");
226 : : }
227 : 0 : break;
228 : : }
229 : : #endif
230 : 26 : default:
231 : 26 : return -THINGSET_ERR_UNSUPPORTED_FORMAT;
232 : : }
233 : :
234 [ + - + - ]: 70 : if (pos >= 0 && pos < size) {
235 : 70 : return pos;
236 : : }
237 : : else {
238 : 0 : return -THINGSET_ERR_RESPONSE_TOO_LARGE;
239 : : }
240 : : }
241 : :
242 : 84 : static int txt_serialize_value(struct thingset_context *ts,
243 : : const struct thingset_data_object *object)
244 : : {
245 : 84 : char *buf = ts->rsp + ts->rsp_pos;
246 : 84 : size_t size = ts->rsp_size - ts->rsp_pos;
247 : : int ret;
248 : :
249 : 84 : int pos = json_serialize_simple_value(buf, size, object->data, object->type, object->detail);
250 : :
251 [ + + ]: 84 : if (pos == -THINGSET_ERR_UNSUPPORTED_FORMAT) {
252 : : /* not a simple value */
253 [ + + ]: 26 : if (object->type == THINGSET_TYPE_GROUP) {
254 : 7 : pos = snprintf(buf, size, "null,");
255 : : }
256 [ + + ]: 19 : else if (object->type == THINGSET_TYPE_RECORDS) {
257 : : if (IS_ENABLED(CONFIG_THINGSET_REPORT_RECORD_SERIALIZATION)
258 : : && ts->rsp[0] == THINGSET_TXT_REPORT)
259 : : {
260 : : pos = snprintf(buf, size, "[");
261 : : ts->rsp_pos++;
262 : : for (unsigned int i = 0; i < object->data.records->num_records; i++) {
263 : : ret = thingset_common_serialize_record(ts, object, i);
264 : : pos = ts->rsp_pos;
265 : : buf[pos++] = ',';
266 : : }
267 : : /* thingset_common_serialize_record has already incremeneted rsp_pos, so we reset
268 : : the length of pos here, so that when we increment at the end, we are only
269 : : incrementing the small delta below */
270 : : buf = ts->rsp + ts->rsp_pos;
271 : : pos = 0;
272 : : if (object->data.records->num_records > 0) {
273 : : pos--; /* remove trailing comma */
274 : : }
275 : : pos += snprintf(buf + pos, size - pos, "],");
276 : : }
277 : : else {
278 : 7 : pos = snprintf(buf, size, "%d,", object->data.records->num_records);
279 : : }
280 : : }
281 [ + + + + ]: 12 : else if (object->type == THINGSET_TYPE_FN_VOID || object->type == THINGSET_TYPE_FN_I32) {
282 : 4 : pos = snprintf(buf, size, "[");
283 [ + + ]: 288 : for (unsigned int i = 0; i < ts->num_objects; i++) {
284 [ + + ]: 284 : if (ts->data_objects[i].parent_id == object->id) {
285 : 3 : pos += snprintf(buf + pos, size - pos, "\"%s\",", ts->data_objects[i].name);
286 : : }
287 : : }
288 [ + + ]: 4 : if (pos > 1) {
289 : 2 : pos--; /* remove trailing comma */
290 : : }
291 : 4 : pos += snprintf(buf + pos, size - pos, "],");
292 : : }
293 [ + + ]: 8 : else if (object->type == THINGSET_TYPE_SUBSET) {
294 : 4 : pos = snprintf(buf, size, "[");
295 [ + + ]: 288 : for (unsigned int i = 0; i < ts->num_objects; i++) {
296 [ + + ]: 284 : if (ts->data_objects[i].subsets & object->data.subset) {
297 : 19 : buf[pos++] = '"';
298 : 19 : ret = thingset_get_path(ts, buf + pos, size - pos, &ts->data_objects[i]);
299 [ - + ]: 19 : if (ret <= 0) {
300 : 0 : ts->rsp_pos = 0;
301 : 0 : return ret;
302 : : }
303 : 19 : pos += ret;
304 : 19 : buf[pos++] = '"';
305 : 19 : buf[pos++] = ',';
306 : : }
307 : : }
308 [ + - ]: 4 : if (pos > 1) {
309 : 4 : pos--; /* remove trailing comma */
310 : : }
311 : 4 : pos += snprintf(buf + pos, size - pos, "],");
312 : : }
313 [ + - + - ]: 4 : else if (object->type == THINGSET_TYPE_ARRAY && object->data.array != NULL) {
314 : 4 : struct thingset_array *array = object->data.array;
315 : 4 : pos = snprintf(buf, size, "[");
316 : 4 : size_t type_size = thingset_type_size(array->element_type);
317 [ + + ]: 16 : for (int i = 0; i < array->num_elements; i++) {
318 : : /* using uint8_t pointer for byte-wise pointer arithmetics */
319 : 12 : union thingset_data_pointer data = { .u8 = array->elements.u8 + i * type_size };
320 : 12 : pos += json_serialize_simple_value(buf + pos, size - pos, data, array->element_type,
321 : 12 : array->decimals);
322 : : }
323 [ + - ]: 4 : if (array->num_elements > 0) {
324 : 4 : pos--; /* remove trailing comma */
325 : : }
326 : 4 : pos += snprintf(buf + pos, size - pos, "],");
327 : : }
328 : : else {
329 : 0 : ts->rsp_pos = 0;
330 : 0 : return -THINGSET_ERR_UNSUPPORTED_FORMAT;
331 : : }
332 : : }
333 : :
334 [ + - + - ]: 84 : if (pos >= 0 && pos < size) {
335 : 84 : ts->rsp_pos += pos;
336 : 84 : return 0;
337 : : }
338 : : else {
339 : 0 : ts->rsp_pos = 0;
340 : 0 : return -THINGSET_ERR_RESPONSE_TOO_LARGE;
341 : : }
342 : : }
343 : :
344 : 0 : static int txt_serialize_path(struct thingset_context *ts,
345 : : const struct thingset_data_object *object)
346 : : {
347 : : /* not used for text mode */
348 : :
349 : 0 : return -THINGSET_ERR_INTERNAL_SERVER_ERR;
350 : : }
351 : :
352 : 85 : static int txt_serialize_string(struct thingset_context *ts, const char *buf, bool is_key)
353 : : {
354 [ + + ]: 85 : int len = snprintf(ts->rsp + ts->rsp_pos, ts->rsp_size - ts->rsp_pos, "\"%s\"%s", buf,
355 : : is_key ? ":" : ",");
356 [ + - + - ]: 85 : if (len >= 0 && len < ts->rsp_size - ts->rsp_pos) {
357 : 85 : ts->rsp_pos += len;
358 : 85 : return 0;
359 : : }
360 : : else {
361 : 0 : ts->rsp_pos = 0;
362 : 0 : return -THINGSET_ERR_RESPONSE_TOO_LARGE;
363 : : }
364 : : }
365 : :
366 : 16 : static int txt_serialize_name(struct thingset_context *ts,
367 : : const struct thingset_data_object *object)
368 : : {
369 : 16 : return txt_serialize_string(ts, object->name, false);
370 : : }
371 : :
372 : : #ifdef CONFIG_THINGSET_METADATA_ENDPOINT
373 : 1 : static int txt_serialize_metadata(struct thingset_context *ts,
374 : : const struct thingset_data_object *object)
375 : : {
376 : 1 : int err = txt_serialize_map_start(ts);
377 [ - + ]: 1 : if (err) {
378 : 0 : return err;
379 : : }
380 : :
381 [ - + ]: 1 : if ((err = txt_serialize_string(ts, "name", true))) {
382 : 0 : return -THINGSET_ERR_RESPONSE_TOO_LARGE;
383 : : }
384 : :
385 [ - + ]: 1 : if ((err = txt_serialize_name(ts, object))) {
386 : 0 : return -THINGSET_ERR_RESPONSE_TOO_LARGE;
387 : : }
388 : :
389 [ - + ]: 1 : if ((err = txt_serialize_string(ts, "type", true))) {
390 : 0 : return -THINGSET_ERR_RESPONSE_TOO_LARGE;
391 : : }
392 : :
393 : : char buf[128];
394 : 1 : int len = thingset_get_type_name(ts, object, (char *)&buf, sizeof(buf));
395 [ - + ]: 1 : if (len < 0) {
396 : 0 : return THINGSET_ERR_RESPONSE_TOO_LARGE;
397 : : }
398 : :
399 [ - + ]: 1 : if ((err = txt_serialize_string(ts, buf, false))) {
400 : 0 : return -THINGSET_ERR_RESPONSE_TOO_LARGE;
401 : : }
402 : :
403 [ - + ]: 1 : if ((err = txt_serialize_map_end(ts))) {
404 : 0 : return err;
405 : : }
406 : :
407 : 1 : return 0;
408 : : }
409 : : #endif /* CONFIG_THINGSET_METADATA_ENDPOINT */
410 : :
411 : 66 : static int txt_serialize_name_value(struct thingset_context *ts,
412 : : const struct thingset_data_object *object)
413 : : {
414 : : int err;
415 : :
416 : 66 : err = txt_serialize_string(ts, object->name, true);
417 [ - + ]: 66 : if (err != 0) {
418 : 0 : return err;
419 : : }
420 : :
421 : 66 : return ts->api->serialize_value(ts, object);
422 : : }
423 : :
424 : 69 : static void txt_serialize_finish(struct thingset_context *ts)
425 : : {
426 : : /* remove the trailing comma or space (in case of no payload) and terminate string */
427 : 69 : ts->rsp_pos--;
428 : 69 : ts->rsp[ts->rsp_pos] = '\0';
429 : 69 : }
430 : :
431 : : /**
432 : : * @returns 0 or negative ThingSet reponse code in case of error
433 : : */
434 : 65 : static int txt_parse_endpoint(struct thingset_context *ts)
435 : : {
436 : 65 : char *path_begin = (char *)ts->msg + 1;
437 : 65 : char *path_end = memchr(path_begin, ' ', ts->msg_len - 1);
438 : : int path_len;
439 : :
440 [ + + ]: 65 : if (path_end != NULL) {
441 : 45 : path_len = path_end - path_begin;
442 : : }
443 : : else {
444 : 20 : path_len = ts->msg_len - 1;
445 : : }
446 : :
447 : 65 : int err = thingset_endpoint_by_path(ts, &ts->endpoint, path_begin, path_len);
448 [ + + ]: 65 : if (err != 0) {
449 : 3 : return err;
450 : : }
451 : :
452 : 62 : ts->msg_pos += path_len + 1;
453 : :
454 : 62 : return 0;
455 : : }
456 : :
457 : : /**
458 : : * @returns 0 or negative ThingSet reponse code in case of error
459 : : */
460 : 73 : static int txt_parse_payload(struct thingset_context *ts)
461 : : {
462 : : struct jsmn_parser parser;
463 : : int ret;
464 : :
465 : 73 : ts->msg_payload = ts->msg + ts->msg_pos;
466 : 73 : ts->tok_pos = 0;
467 : :
468 : 73 : jsmn_init(&parser);
469 : :
470 : 73 : ret = jsmn_parse(&parser, ts->msg_payload, ts->msg_len - ts->msg_pos, ts->tokens,
471 : : sizeof(ts->tokens));
472 [ - + ]: 73 : if (ret == JSMN_ERROR_NOMEM) {
473 : 0 : ts->rsp_pos = 0;
474 : 0 : return -THINGSET_ERR_REQUEST_TOO_LARGE;
475 : : }
476 [ + + ]: 73 : else if (ret < 0) {
477 : : /* other parsing error */
478 : 2 : ts->rsp_pos = 0;
479 : 2 : return -THINGSET_ERR_BAD_REQUEST;
480 : : }
481 : :
482 : 71 : ts->tok_count = ret;
483 : 71 : return 0;
484 : : }
485 : :
486 : 27 : int thingset_txt_get_fetch(struct thingset_context *ts)
487 : : {
488 [ + + ]: 27 : if (ts->tok_count == 0) {
489 : 13 : return thingset_common_get(ts);
490 : : }
491 : : else {
492 : 14 : return thingset_common_fetch(ts);
493 : : }
494 : : }
495 : :
496 : 45 : static int txt_deserialize_simple_value(struct thingset_context *ts,
497 : : union thingset_data_pointer data, int type, int detail,
498 : : bool check_only)
499 : : {
500 [ + + ]: 45 : if (ts->tok_pos >= ts->tok_count) {
501 : 5 : return -THINGSET_ERR_DESERIALIZATION_FINISHED;
502 : : }
503 : :
504 : 40 : const char *buf = ts->msg_payload + ts->tokens[ts->tok_pos].start;
505 : 40 : size_t len = ts->tokens[ts->tok_pos].end - ts->tokens[ts->tok_pos].start;
506 : :
507 [ + + ]: 40 : if (ts->tokens[ts->tok_pos].type != JSMN_PRIMITIVE
508 [ + + ]: 10 : && ts->tokens[ts->tok_pos].type != JSMN_STRING)
509 : : {
510 : 4 : return -THINGSET_ERR_UNSUPPORTED_FORMAT;
511 : : }
512 : :
513 : 36 : errno = 0;
514 [ + - - - : 36 : switch (type) {
+ + - - -
- + + +
- ]
515 : 16 : case THINGSET_TYPE_F32:
516 : 16 : *data.f32 = strtod(buf, NULL);
517 : 16 : break;
518 : : #if CONFIG_THINGSET_DECFRAC_TYPE_SUPPORT
519 : 0 : case THINGSET_TYPE_DECFRAC: {
520 : 0 : float tmp = strtod(buf, NULL);
521 : : /* negative decimals and positive exponent */
522 [ # # ]: 0 : for (int16_t i = 0; i < -detail; i++) {
523 : 0 : tmp /= 10.0F;
524 : : }
525 : : /* positive decimals and negative exponent */
526 [ # # ]: 0 : for (int16_t i = 0; i > -detail; i--) {
527 : 0 : tmp *= 10.0F;
528 : : }
529 : 0 : *data.decfrac = (int32_t)tmp;
530 : 0 : break;
531 : : }
532 : : #endif
533 : : #if CONFIG_THINGSET_64BIT_TYPES_SUPPORT
534 : 0 : case THINGSET_TYPE_U64:
535 : 0 : *data.u64 = strtoull(buf, NULL, 0);
536 : 0 : break;
537 : 0 : case THINGSET_TYPE_I64:
538 : 0 : *data.i64 = strtoll(buf, NULL, 0);
539 : 0 : break;
540 : : #endif
541 : 2 : case THINGSET_TYPE_U32:
542 : 2 : *data.u32 = strtoul(buf, NULL, 0);
543 : 2 : break;
544 : 9 : case THINGSET_TYPE_I32:
545 : 9 : *data.i32 = strtol(buf, NULL, 0);
546 : 9 : break;
547 : 0 : case THINGSET_TYPE_U16:
548 : 0 : *data.u16 = strtoul(buf, NULL, 0);
549 : 0 : break;
550 : 0 : case THINGSET_TYPE_I16:
551 : 0 : *data.i16 = strtol(buf, NULL, 0);
552 : 0 : break;
553 : 0 : case THINGSET_TYPE_U8:
554 : 0 : *data.u8 = strtoul(buf, NULL, 0);
555 : 0 : break;
556 : 0 : case THINGSET_TYPE_I8:
557 : 0 : *data.i8 = strtol(buf, NULL, 0);
558 : 0 : break;
559 : 4 : case THINGSET_TYPE_BOOL:
560 [ + + - + ]: 4 : if (buf[0] == 't' || buf[0] == '1') {
561 : 2 : *data.b = true;
562 : : }
563 [ + + - + ]: 2 : else if (buf[0] == 'f' || buf[0] == '0') {
564 : 1 : *data.b = false;
565 : : }
566 : : else {
567 : 1 : return -THINGSET_ERR_UNSUPPORTED_FORMAT;
568 : : }
569 : 3 : break;
570 : 3 : case THINGSET_TYPE_STRING:
571 [ + - - + ]: 3 : if (ts->tokens[ts->tok_pos].type != JSMN_STRING || (unsigned int)detail <= len) {
572 : 0 : return -THINGSET_ERR_REQUEST_TOO_LARGE;
573 : : }
574 [ + + ]: 3 : if (!check_only) {
575 : : #if CONFIG_THINGSET_JSON_STRING_ESCAPING
576 : 2 : size_t data_pos = 0;
577 [ + + ]: 9 : for (int pos = 0; pos < len; pos++) {
578 [ + + ]: 7 : if (buf[pos] == '\\') {
579 : 3 : pos++;
580 [ + - - + : 3 : switch (buf[pos]) {
- - - - ]
581 : 2 : case '"':
582 : : case '/':
583 : : case '\\':
584 : 2 : data.str[data_pos++] = buf[pos];
585 : 2 : break;
586 : 0 : case 'b':
587 : 0 : data.str[data_pos++] = '\b';
588 : 0 : break;
589 : 0 : case 'f':
590 : 0 : data.str[data_pos++] = '\f';
591 : 0 : break;
592 : 1 : case 'n':
593 : 1 : data.str[data_pos++] = '\n';
594 : 1 : break;
595 : 0 : case 'r':
596 : 0 : data.str[data_pos++] = '\r';
597 : 0 : break;
598 : 0 : case 't':
599 : 0 : data.str[data_pos++] = '\t';
600 : 0 : break;
601 : 0 : case 'u':
602 : 0 : hex2bin(&buf[pos], 4, &data.str[data_pos++], 2);
603 : 0 : break;
604 : 0 : default:
605 : : /* this would be invalid JSON */
606 : 0 : return -THINGSET_ERR_UNSUPPORTED_FORMAT;
607 : : }
608 : : }
609 : : else {
610 : 4 : data.str[data_pos++] = buf[pos];
611 : : }
612 : : }
613 : : #else
614 : : strncpy(data.str, buf, len);
615 : : data.str[len] = '\0';
616 : : #endif
617 : : }
618 : 3 : break;
619 : : #if CONFIG_THINGSET_BYTES_TYPE_SUPPORT
620 : 2 : case THINGSET_TYPE_BYTES: {
621 [ + - - + ]: 2 : if (ts->tokens[ts->tok_pos].type != JSMN_STRING || data.bytes->max_bytes < len / 4 * 3)
622 : : {
623 : 0 : return -THINGSET_ERR_REQUEST_TOO_LARGE;
624 : : }
625 [ + + ]: 2 : if (!check_only) {
626 : 1 : struct thingset_bytes *bytes_buf = data.bytes;
627 : : size_t byteslen;
628 : 1 : int err = base64_decode(bytes_buf->bytes, bytes_buf->max_bytes, &byteslen,
629 : : (uint8_t *)buf, len);
630 : 1 : bytes_buf->num_bytes = byteslen;
631 [ - + ]: 1 : if (err != 0) {
632 : 0 : return -THINGSET_ERR_UNSUPPORTED_FORMAT;
633 : : }
634 : : }
635 : 2 : break;
636 : : }
637 : : #endif
638 : 0 : default:
639 : 0 : return -THINGSET_ERR_UNSUPPORTED_FORMAT;
640 : : }
641 : :
642 [ - + ]: 35 : if (errno == ERANGE) {
643 : 0 : return -THINGSET_ERR_UNSUPPORTED_FORMAT;
644 : : }
645 : :
646 : 35 : ts->tok_pos++;
647 : 35 : return 0;
648 : : }
649 : :
650 : 29 : static int txt_deserialize_value(struct thingset_context *ts,
651 : : const struct thingset_data_object *object, bool check_only)
652 : : {
653 : : int err =
654 : 29 : txt_deserialize_simple_value(ts, object->data, object->type, object->detail, check_only);
655 : :
656 [ + + + + ]: 29 : if (err == -THINGSET_ERR_UNSUPPORTED_FORMAT && object->type == THINGSET_TYPE_ARRAY) {
657 : 4 : struct thingset_array *array = object->data.array;
658 : :
659 : 4 : err = ts->api->deserialize_list_start(ts);
660 [ - + ]: 4 : if (err != 0) {
661 : 0 : return -THINGSET_ERR_UNSUPPORTED_FORMAT;
662 : : }
663 : :
664 : 4 : size_t type_size = thingset_type_size(array->element_type);
665 : 4 : int index = 0;
666 : : do {
667 : : /* using uint8_t pointer for byte-wise pointer arithmetics */
668 : 16 : union thingset_data_pointer data = { .u8 = array->elements.u8 + index * type_size };
669 : :
670 : 16 : err = txt_deserialize_simple_value(ts, data, array->element_type, array->decimals,
671 : : check_only);
672 [ + + ]: 16 : if (err != 0) {
673 : 4 : break;
674 : : }
675 : 12 : index++;
676 [ + - ]: 12 : } while (index < array->max_elements);
677 : :
678 [ + + ]: 4 : if (!check_only) {
679 : 2 : array->num_elements = index;
680 : : }
681 : :
682 [ + - ]: 4 : if (err == -THINGSET_ERR_DESERIALIZATION_FINISHED) {
683 : 4 : err = 0;
684 : : };
685 : : }
686 : :
687 : 29 : return err;
688 : : }
689 : :
690 : 1 : int thingset_txt_desire(struct thingset_context *ts)
691 : : {
692 : 1 : return -THINGSET_ERR_NOT_IMPLEMENTED;
693 : : }
694 : :
695 : : /* currently only supporting nesting of depth 2 (parent and grandparent != 0) */
696 : 2 : static int txt_serialize_subsets(struct thingset_context *ts, uint16_t subsets)
697 : : {
698 : : struct thingset_data_object *ancestors[2];
699 : 2 : int depth = 0;
700 : :
701 : 2 : ts->rsp[ts->rsp_pos++] = '{';
702 : :
703 [ + + ]: 144 : for (unsigned int i = 0; i < ts->num_objects; i++) {
704 [ + + ]: 142 : if (ts->data_objects[i].subsets & subsets) {
705 : 10 : const uint16_t parent_id = ts->data_objects[i].parent_id;
706 : :
707 : 10 : struct thingset_data_object *parent = NULL;
708 [ + + - + ]: 10 : if (depth > 0 && parent_id == ancestors[depth - 1]->id) {
709 : : /* same parent as previous item */
710 : 0 : parent = ancestors[depth - 1];
711 : : }
712 [ + + ]: 10 : else if (parent_id != 0) {
713 : : /* parent needs to be searched in the object database */
714 : 6 : parent = thingset_get_object_by_id(ts, parent_id);
715 : : }
716 : :
717 : : /* close object if previous object had different parent or grandparent */
718 [ + + + - ]: 10 : if (depth > 0 && parent_id != ancestors[depth - 1]->id
719 [ + + + - ]: 4 : && ((parent != NULL && parent->parent_id != ancestors[depth - 1]->id)
720 [ + + ]: 4 : || parent_id == 0)) /* return to root */
721 : : {
722 : 2 : ts->rsp[ts->rsp_pos - 1] = '}'; /* overwrite comma */
723 : 2 : ts->rsp[ts->rsp_pos++] = ',';
724 : 2 : depth--;
725 : : }
726 : :
727 [ + + + + ]: 10 : if (depth == 0 && parent != NULL) {
728 [ - + ]: 4 : if (parent->parent_id != 0) {
729 : : struct thingset_data_object *grandparent =
730 : 0 : thingset_get_object_by_id(ts, parent->parent_id);
731 [ # # ]: 0 : if (grandparent != NULL) {
732 : 0 : ts->rsp_pos += snprintf(ts->rsp + ts->rsp_pos, ts->rsp_size - ts->rsp_pos,
733 : : "\"%s\":{", grandparent->name);
734 : 0 : ancestors[depth++] = grandparent;
735 : : }
736 : : }
737 : 4 : ts->rsp_pos += snprintf(ts->rsp + ts->rsp_pos, ts->rsp_size - ts->rsp_pos,
738 : : "\"%s\":{", parent->name);
739 : 4 : ancestors[depth++] = parent;
740 : : }
741 [ + + + - ]: 6 : else if (depth > 0 && parent_id != ancestors[depth - 1]->id) {
742 [ + - ]: 2 : if (parent != NULL) {
743 : 2 : ts->rsp_pos += snprintf(ts->rsp + ts->rsp_pos, ts->rsp_size - ts->rsp_pos,
744 : : "\"%s\":{", parent->name);
745 : 2 : ancestors[depth++] = parent;
746 : : }
747 : : }
748 : 10 : ts->rsp_pos += ts->api->serialize_key_value(ts, &ts->data_objects[i]);
749 : : }
750 [ - + ]: 142 : if (ts->rsp_pos >= ts->rsp_size - 1 - depth) {
751 : 0 : return -THINGSET_ERR_RESPONSE_TOO_LARGE;
752 : : }
753 : : }
754 : :
755 : 2 : ts->rsp_pos--; /* overwrite internal comma */
756 : :
757 [ + + ]: 8 : while (depth >= 0) {
758 : 6 : ts->rsp[ts->rsp_pos++] = '}';
759 : 6 : depth--;
760 : : }
761 : :
762 : 2 : ts->rsp[ts->rsp_pos++] = ',';
763 : :
764 : 2 : return 0;
765 : : }
766 : :
767 : 4 : static int txt_serialize_report_header(struct thingset_context *ts, const char *path)
768 : : {
769 : 4 : ts->rsp_pos = snprintf(ts->rsp, ts->rsp_size, "#%s ", path);
770 [ - + ]: 4 : if (ts->rsp_pos < 0 || ts->rsp_pos > ts->rsp_size) {
771 : 0 : return -THINGSET_ERR_RESPONSE_TOO_LARGE;
772 : : }
773 : : else {
774 : 4 : return 0;
775 : : }
776 : : }
777 : :
778 : 11 : static void txt_deserialize_payload_reset(struct thingset_context *ts)
779 : : {
780 : 11 : ts->msg_pos = ts->msg_payload - ts->msg;
781 : :
782 : 11 : txt_parse_payload(ts);
783 : 11 : }
784 : :
785 : 5 : static int txt_deserialize_string(struct thingset_context *ts, const char **str_start,
786 : : size_t *str_len)
787 : : {
788 [ + - ]: 5 : if (ts->tok_pos < ts->tok_count) {
789 [ + + ]: 5 : if (ts->tokens[ts->tok_pos].type == JSMN_STRING) {
790 : 3 : *str_start = ts->msg_payload + ts->tokens[ts->tok_pos].start;
791 : 3 : *str_len = ts->tokens[ts->tok_pos].end - ts->tokens[ts->tok_pos].start;
792 : 3 : ts->tok_pos++;
793 : 3 : return 0;
794 : : }
795 : : else {
796 : 2 : return -THINGSET_ERR_UNSUPPORTED_FORMAT;
797 : : }
798 : : }
799 : : else {
800 : 0 : return -THINGSET_ERR_BAD_REQUEST;
801 : : }
802 : : }
803 : :
804 : 70 : static int txt_deserialize_child(struct thingset_context *ts,
805 : : const struct thingset_data_object **object)
806 : : {
807 [ + + ]: 70 : if (ts->tok_pos >= ts->tok_count) {
808 : 30 : return -THINGSET_ERR_DESERIALIZATION_FINISHED;
809 : : }
810 : :
811 [ + + ]: 40 : if (ts->tokens[ts->tok_pos].type != JSMN_STRING) {
812 : 1 : return -THINGSET_ERR_BAD_REQUEST;
813 : : }
814 : :
815 : 39 : const char *name = (char *)ts->msg_payload + ts->tokens[ts->tok_pos].start;
816 : 39 : size_t name_len = ts->tokens[ts->tok_pos].end - ts->tokens[ts->tok_pos].start;
817 : :
818 [ + + ]: 39 : if (ts->endpoint.object->id == THINGSET_ID_METADATA) {
819 : : int index;
820 : 1 : *object = thingset_get_object_by_path(ts, name, name_len, &index);
821 : : }
822 : : else {
823 : 38 : *object = thingset_get_child_by_name(ts, ts->endpoint.object->id, name, name_len);
824 : : }
825 : :
826 [ + + ]: 39 : if (*object == NULL) {
827 : 2 : return -THINGSET_ERR_NOT_FOUND;
828 : : }
829 : :
830 : 37 : ts->tok_pos++;
831 : 37 : return 0;
832 : : }
833 : :
834 : 14 : static int txt_deserialize_null(struct thingset_context *ts)
835 : : {
836 [ + - ]: 14 : if (ts->tok_pos < ts->tok_count) {
837 : 14 : jsmntok_t *token = &ts->tokens[ts->tok_pos];
838 [ + + ]: 14 : if (token->type == JSMN_PRIMITIVE
839 [ + - ]: 2 : && strncmp(ts->msg_payload + token->start, "null", token->end - token->start) == 0)
840 : : {
841 : 2 : ts->tok_pos++;
842 : 2 : return 0;
843 : : }
844 : : else {
845 : 12 : return -THINGSET_ERR_UNSUPPORTED_FORMAT;
846 : : }
847 : : }
848 : : else {
849 : 0 : return -THINGSET_ERR_BAD_REQUEST;
850 : : }
851 : : }
852 : :
853 : 27 : static int txt_deserialize_list_start(struct thingset_context *ts)
854 : : {
855 [ + + ]: 27 : if (ts->tok_pos < ts->tok_count) {
856 [ + + ]: 21 : if (ts->tokens[ts->tok_pos].type == JSMN_ARRAY) {
857 : 20 : ts->tok_pos++;
858 : 20 : return 0;
859 : : }
860 : : else {
861 : 1 : return -THINGSET_ERR_UNSUPPORTED_FORMAT;
862 : : }
863 : : }
864 : : else {
865 : 6 : return -THINGSET_ERR_BAD_REQUEST;
866 : : }
867 : : }
868 : :
869 : 24 : static int txt_deserialize_map_start(struct thingset_context *ts)
870 : : {
871 [ + - ]: 24 : if (ts->tok_pos < ts->tok_count) {
872 [ + - ]: 24 : if (ts->tokens[ts->tok_pos].type == JSMN_OBJECT) {
873 : 24 : ts->tok_pos++;
874 : 24 : return 0;
875 : : }
876 : : else {
877 : 0 : return -THINGSET_ERR_UNSUPPORTED_FORMAT;
878 : : }
879 : : }
880 : : else {
881 : 0 : return -THINGSET_ERR_BAD_REQUEST;
882 : : }
883 : : }
884 : :
885 : 0 : static int txt_deserialize_skip(struct thingset_context *ts)
886 : : {
887 [ # # ]: 0 : if (ts->tok_pos < ts->tok_count) {
888 : 0 : ts->tok_pos++;
889 : 0 : return 0;
890 : : }
891 : : else {
892 : 0 : return -THINGSET_ERR_BAD_REQUEST;
893 : : }
894 : : }
895 : :
896 : 12 : static int txt_deserialize_finish(struct thingset_context *ts)
897 : : {
898 [ + + ]: 12 : return ts->tok_count == ts->tok_pos ? 0 : -THINGSET_ERR_BAD_REQUEST;
899 : : }
900 : :
901 : : static struct thingset_api txt_api = {
902 : : .serialize_response = txt_serialize_response,
903 : : .serialize_key = txt_serialize_name,
904 : : .serialize_value = txt_serialize_value,
905 : : .serialize_key_value = txt_serialize_name_value,
906 : : .serialize_path = txt_serialize_path,
907 : : #ifdef CONFIG_THINGSET_METADATA_ENDPOINT
908 : : .serialize_metadata = txt_serialize_metadata,
909 : : #endif
910 : : .serialize_map_start = txt_serialize_map_start,
911 : : .serialize_map_end = txt_serialize_map_end,
912 : : .serialize_list_start = txt_serialize_list_start,
913 : : .serialize_list_end = txt_serialize_list_end,
914 : : .serialize_subsets = txt_serialize_subsets,
915 : : .serialize_report_header = txt_serialize_report_header,
916 : : .serialize_finish = txt_serialize_finish,
917 : : .deserialize_payload_reset = txt_deserialize_payload_reset,
918 : : .deserialize_string = txt_deserialize_string,
919 : : .deserialize_null = txt_deserialize_null,
920 : : .deserialize_list_start = txt_deserialize_list_start,
921 : : .deserialize_map_start = txt_deserialize_map_start,
922 : : .deserialize_child = txt_deserialize_child,
923 : : .deserialize_value = txt_deserialize_value,
924 : : .deserialize_skip = txt_deserialize_skip,
925 : : .deserialize_finish = txt_deserialize_finish,
926 : : };
927 : :
928 : 71 : inline void thingset_txt_setup(struct thingset_context *ts)
929 : : {
930 : 71 : ts->api = &txt_api;
931 : 71 : }
932 : :
933 : 65 : int thingset_txt_process(struct thingset_context *ts)
934 : : {
935 : : int ret;
936 : :
937 : 65 : thingset_txt_setup(ts);
938 : :
939 : : /* requests ordered with expected highest probability first */
940 : : int (*request_fn)(struct thingset_context *ts);
941 [ + + + + : 65 : switch (ts->msg[0]) {
+ + - ]
942 : 27 : case THINGSET_TXT_GET_FETCH:
943 : 27 : request_fn = thingset_txt_get_fetch;
944 : 27 : break;
945 : 16 : case THINGSET_TXT_UPDATE:
946 : 16 : request_fn = thingset_common_update;
947 : 16 : break;
948 : 12 : case THINGSET_TXT_EXEC:
949 : 12 : request_fn = thingset_common_exec;
950 : 12 : break;
951 : 8 : case THINGSET_TXT_CREATE:
952 : 8 : request_fn = thingset_common_create;
953 : 8 : break;
954 : 1 : case THINGSET_TXT_DELETE:
955 : 1 : request_fn = thingset_common_delete;
956 : 1 : break;
957 : 1 : case THINGSET_TXT_DESIRE:
958 : 1 : request_fn = thingset_txt_desire;
959 : 1 : break;
960 : 0 : default:
961 : 0 : return -THINGSET_ERR_BAD_REQUEST;
962 : : }
963 : :
964 : 65 : ret = txt_parse_endpoint(ts);
965 [ + + ]: 65 : if (ret != 0) {
966 : 3 : ts->api->serialize_response(ts, -ret, "Invalid endpoint");
967 : 3 : goto out;
968 : : }
969 : :
970 : 62 : ret = txt_parse_payload(ts);
971 [ + + ]: 62 : if (ret != 0) {
972 : 2 : ts->api->serialize_response(ts, -ret, "JSON parsing error");
973 : 2 : goto out;
974 : : }
975 : :
976 : 60 : ret = request_fn(ts);
977 : :
978 : 65 : out:
979 [ + + ]: 65 : if (ts->msg[0] != THINGSET_TXT_DESIRE) {
980 [ + - ]: 64 : if (ts->rsp_pos > 0) {
981 : 64 : ts->api->serialize_finish(ts);
982 : : }
983 : 64 : return ts->rsp_pos;
984 : : }
985 : : else {
986 : 1 : ts->rsp_pos = 0;
987 : 1 : return ret;
988 : : }
989 : : }
|