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 : : #include <thingset.h>
8 : :
9 : : #include "thingset_internal.h"
10 : :
11 : : #include <errno.h>
12 : : #include <inttypes.h>
13 : : #include <math.h>
14 : : #include <stdarg.h>
15 : : #include <stdio.h>
16 : : #include <stdlib.h>
17 : : #include <string.h>
18 : :
19 : 13 : int thingset_common_serialize_group(struct thingset_context *ts,
20 : : const struct thingset_data_object *object)
21 : : {
22 : : int err;
23 : :
24 : 13 : err = ts->api->serialize_map_start(ts);
25 [ - + ]: 13 : if (err != 0) {
26 : 0 : return err;
27 : : }
28 : :
29 [ + + ]: 13 : if (object->data.group_callback != NULL) {
30 : 2 : object->data.group_callback(THINGSET_CALLBACK_PRE_READ, object);
31 : : }
32 : :
33 [ + + ]: 936 : for (unsigned int i = 0; i < ts->num_objects; i++) {
34 [ + + ]: 923 : if (ts->data_objects[i].parent_id == object->id
35 [ + - ]: 67 : && (ts->data_objects[i].access & THINGSET_READ_MASK))
36 : : {
37 : 67 : err = ts->api->serialize_key_value(ts, &ts->data_objects[i]);
38 [ - + ]: 67 : if (err != 0) {
39 : 0 : return err;
40 : : }
41 : : }
42 : : }
43 : :
44 [ + + ]: 13 : if (object->data.group_callback != NULL) {
45 : 2 : object->data.group_callback(THINGSET_CALLBACK_POST_READ, object);
46 : : }
47 : :
48 : 13 : return ts->api->serialize_map_end(ts);
49 : : }
50 : :
51 : 95 : int thingset_common_prepare_record_element(struct thingset_context *ts,
52 : : const struct thingset_data_object *item,
53 : : uint8_t *record_ptr,
54 : : thingset_common_record_element_action callback)
55 : : {
56 : 95 : int err = -EINVAL;
57 : :
58 [ + + + ]: 95 : switch (item->type)
59 : : {
60 : 7 : case THINGSET_TYPE_ARRAY:
61 : 7 : struct thingset_array *arr = item->data.array;
62 : 7 : struct thingset_array arr_offset = {
63 : 7 : { .u8 = record_ptr + arr->elements.offset },
64 : 7 : arr->element_type,
65 : 7 : arr->decimals,
66 : 7 : arr->max_elements,
67 : 7 : arr->num_elements,
68 : : };
69 : 7 : struct thingset_data_object array_item_offset = {
70 : 7 : item->parent_id, item->id, item->name,
71 : 7 : { .array = &arr_offset }, item->type, item->detail,
72 : : };
73 : 7 : err = callback(ts, &array_item_offset);
74 : 7 : break;
75 : :
76 : :
77 : 6 : case THINGSET_TYPE_RECORDS:
78 : 6 : struct thingset_records *rec = item->data.records;
79 : 6 : struct thingset_records rec_offset = {
80 : 6 : record_ptr + (size_t)rec->records,
81 : 6 : rec->record_size,
82 : 6 : rec->max_records,
83 : 6 : rec->num_records,
84 : 6 : rec->callback,
85 : : };
86 : 6 : struct thingset_data_object record_item_offset = {
87 : 6 : item->parent_id, item->id, item->name, { .records = &rec_offset },
88 : 6 : item->type, item->detail,
89 : : };
90 : 6 : err = callback(ts, &record_item_offset);
91 : 6 : break;
92 : :
93 : :
94 : 82 : default:
95 : 82 : struct thingset_data_object default_item_offset = {
96 : 82 : item->parent_id, item->id, item->name, { .u8 = record_ptr + item->data.offset },
97 : 82 : item->type, item->detail,
98 : : };
99 : 82 : err = callback(ts, &default_item_offset);
100 : 82 : break;
101 : :
102 : : }
103 : :
104 : 95 : return err;
105 : : }
106 : :
107 : 8 : int thingset_common_serialize_record(struct thingset_context *ts,
108 : : const struct thingset_data_object *object, int record_index)
109 : : {
110 : 8 : struct thingset_records *records = object->data.records;
111 : : size_t record_offset;
112 : : int err;
113 : :
114 [ - + ]: 8 : if (record_index >= records->num_records) {
115 : 0 : return -THINGSET_ERR_NOT_FOUND;
116 : : }
117 : :
118 : 8 : err = ts->api->serialize_map_start(ts);
119 [ - + ]: 8 : if (err != 0) {
120 : 0 : return err;
121 : : }
122 : :
123 [ + + ]: 8 : if (object->detail == THINGSET_DETAIL_DYN_RECORDS) {
124 : 2 : record_offset = 0;
125 : : }
126 : : else {
127 : 6 : record_offset = record_index * records->record_size;
128 : : }
129 : :
130 [ + + ]: 8 : if (records->callback != NULL) {
131 : 2 : records->callback(THINGSET_CALLBACK_PRE_READ, record_index, object);
132 : : }
133 : :
134 : 8 : const struct thingset_data_object *item = thingset_get_object_by_id(ts, object->id) + 1;
135 [ + + ]: 212 : while (item < &ts->data_objects[ts->num_objects]) {
136 [ + + ]: 204 : if (item->parent_id != object->id) {
137 : 112 : item++;
138 : 112 : continue;
139 : : }
140 : :
141 : : /* create new object with data pointer including offset */
142 : 92 : uint8_t *record_ptr = (uint8_t *)records->records + record_offset;
143 : 92 : err = thingset_common_prepare_record_element(ts, item, record_ptr,
144 : 92 : ts->api->serialize_key_value);
145 : :
146 [ - + ]: 92 : if (err != 0) {
147 : 0 : return err;
148 : : }
149 : :
150 : 92 : item++;
151 : : }
152 : :
153 [ + + ]: 8 : if (records->callback != NULL) {
154 : 2 : records->callback(THINGSET_CALLBACK_POST_READ, record_index, object);
155 : : }
156 : :
157 : 8 : return ts->api->serialize_map_end(ts);
158 : : }
159 : :
160 : 30 : int thingset_common_get(struct thingset_context *ts)
161 : : {
162 : : struct thingset_data_object *parent;
163 : : int err;
164 : :
165 : 30 : ts->api->serialize_response(ts, THINGSET_STATUS_CONTENT, NULL);
166 : :
167 [ + - + + ]: 30 : switch (ts->endpoint.object->type) {
168 : 11 : case THINGSET_TYPE_GROUP:
169 : 11 : err = thingset_common_serialize_group(ts, ts->endpoint.object);
170 : 11 : break;
171 : 0 : case THINGSET_TYPE_FN_VOID:
172 : : case THINGSET_TYPE_FN_I32:
173 : : /* bad request, as we can't read exec object's values */
174 : 0 : err = -THINGSET_ERR_BAD_REQUEST;
175 : 0 : break;
176 : 7 : case THINGSET_TYPE_RECORDS:
177 [ + + ]: 7 : if (ts->endpoint.index != THINGSET_ENDPOINT_INDEX_NONE) {
178 : 4 : err = thingset_common_serialize_record(ts, ts->endpoint.object, ts->endpoint.index);
179 : 4 : break;
180 : : }
181 : 3 : err = ts->api->serialize_value(ts, ts->endpoint.object);
182 : 3 : break;
183 : 12 : default:
184 : 12 : parent = thingset_get_object_by_id(ts, ts->endpoint.object->parent_id);
185 : :
186 [ + + + + ]: 12 : if (parent != NULL && parent->data.group_callback != NULL) {
187 : 2 : parent->data.group_callback(THINGSET_CALLBACK_PRE_READ, ts->endpoint.object);
188 : : }
189 : :
190 : 12 : err = ts->api->serialize_value(ts, ts->endpoint.object);
191 : :
192 [ + + + + ]: 12 : if (parent != NULL && parent->data.group_callback != NULL) {
193 : 2 : parent->data.group_callback(THINGSET_CALLBACK_POST_READ, ts->endpoint.object);
194 : : }
195 : 12 : break;
196 : : }
197 : :
198 [ + - ]: 30 : if (err == 0) {
199 : 30 : return ts->rsp_pos;
200 : : }
201 : : else {
202 : 0 : return ts->api->serialize_response(ts, -err, NULL);
203 : : }
204 : : }
205 : :
206 : 34 : int thingset_common_fetch(struct thingset_context *ts)
207 : : {
208 : : int err;
209 : :
210 : : /* initialize response with success message */
211 : 34 : ts->api->serialize_response(ts, THINGSET_STATUS_CONTENT, NULL);
212 : :
213 : 34 : ts->api->serialize_list_start(ts);
214 : :
215 [ + + ]: 34 : if (ts->api->deserialize_null(ts) == 0) {
216 : : /* fetch names */
217 [ + + ]: 432 : for (unsigned int i = 0; i < ts->num_objects; i++) {
218 [ + - ]: 426 : if ((ts->data_objects[i].access & THINGSET_READ_MASK)
219 [ + + ]: 426 : && (ts->data_objects[i].parent_id == ts->endpoint.object->id))
220 : : {
221 : 45 : err = ts->api->serialize_key(ts, &ts->data_objects[i]);
222 [ - + ]: 45 : if (err != 0) {
223 : 0 : return ts->api->serialize_response(ts, -err, NULL);
224 : : }
225 : : }
226 : : }
227 : : }
228 [ + - ]: 28 : else if (ts->api->deserialize_list_start(ts) == 0) {
229 [ - + ]: 28 : if (ts->endpoint.object->type != THINGSET_TYPE_GROUP) {
230 : 6 : return ts->api->serialize_response(ts, THINGSET_ERR_BAD_REQUEST, "%s is not a group",
231 : 0 : ts->endpoint.object->name);
232 : : }
233 : :
234 : : /* fetch values */
235 : :
236 : : const struct thingset_data_object *object;
237 : 60 : while ((err = ts->api->deserialize_child(ts, &object))
238 [ + + ]: 60 : != -THINGSET_ERR_DESERIALIZATION_FINISHED)
239 : : {
240 : :
241 [ + + ]: 38 : if (ts->endpoint.object->data.group_callback != NULL) {
242 : 2 : ts->endpoint.object->data.group_callback(THINGSET_CALLBACK_PRE_READ, object);
243 : : }
244 : :
245 [ + + ]: 38 : if (err != 0) {
246 : 4 : return ts->api->serialize_response(ts, -err, NULL);
247 : : }
248 : :
249 [ + + + + ]: 34 : if (object->type == THINGSET_TYPE_GROUP && ts->endpoint.object->id != THINGSET_ID_PATHS
250 [ + + ]: 3 : && ts->endpoint.object->id != THINGSET_ID_METADATA)
251 : : {
252 : 2 : return ts->api->serialize_response(ts, THINGSET_ERR_BAD_REQUEST, "%s is a group",
253 : 2 : object->name);
254 : : }
255 : :
256 [ - + ]: 32 : if ((object->access & THINGSET_READ_MASK & ts->auth_flags) == 0) {
257 [ # # ]: 0 : if (object->access & THINGSET_READ_MASK) {
258 : 0 : return ts->api->serialize_response(ts, THINGSET_ERR_UNAUTHORIZED,
259 : : "Authentication required for %s",
260 : 0 : object->name);
261 : : }
262 : : else {
263 : 0 : return ts->api->serialize_response(ts, THINGSET_ERR_FORBIDDEN,
264 : 0 : "Reading %s forbidden", object->name);
265 : : }
266 : : }
267 : :
268 [ + + ]: 32 : if (ts->endpoint.object->id == THINGSET_ID_PATHS) {
269 : 4 : err = ts->api->serialize_path(ts, object);
270 : : }
271 : : #ifdef CONFIG_THINGSET_METADATA_ENDPOINT
272 [ + + ]: 28 : else if (ts->endpoint.object->id == THINGSET_ID_METADATA) {
273 : 8 : err = ts->api->serialize_metadata(ts, object);
274 : : }
275 : : #endif
276 : : else {
277 : 20 : err = ts->api->serialize_value(ts, object);
278 : : }
279 : :
280 [ - + ]: 32 : if (err != 0) {
281 : 0 : return ts->api->serialize_response(ts, -err, NULL);
282 : : }
283 : : }
284 : :
285 [ + + ]: 22 : if (ts->endpoint.object->data.group_callback != NULL) {
286 : 2 : ts->endpoint.object->data.group_callback(THINGSET_CALLBACK_POST_READ, object);
287 : : }
288 : : }
289 : : else {
290 : 0 : return ts->api->serialize_response(ts, THINGSET_ERR_BAD_REQUEST, "Invalid payload");
291 : : }
292 : :
293 : 28 : ts->api->serialize_list_end(ts);
294 : :
295 : 28 : return 0;
296 : : }
297 : :
298 : 23 : int thingset_common_update(struct thingset_context *ts)
299 : : {
300 : : const struct thingset_data_object *object;
301 : 23 : bool updated = false;
302 : : int err;
303 : :
304 : 23 : err = ts->api->deserialize_map_start(ts);
305 [ - + ]: 23 : if (err != 0) {
306 : 0 : return ts->api->serialize_response(ts, THINGSET_ERR_BAD_REQUEST, "Map with data required");
307 : : }
308 : :
309 : : /* loop through all elements to check if request is valid */
310 : 41 : while ((err = ts->api->deserialize_child(ts, &object))
311 [ + + ]: 41 : != -THINGSET_ERR_DESERIALIZATION_FINISHED)
312 : : {
313 [ + + ]: 24 : if (err != 0) {
314 : 6 : return ts->api->serialize_response(ts, -err, NULL);
315 : : }
316 : :
317 [ + + ]: 21 : if ((object->access & THINGSET_WRITE_MASK & ts->auth_flags) == 0) {
318 [ + + ]: 3 : if (object->access & THINGSET_WRITE_MASK) {
319 : 1 : return ts->api->serialize_response(ts, THINGSET_ERR_UNAUTHORIZED,
320 : 1 : "Authentication required for %s", object->name);
321 : : }
322 : : else {
323 : 2 : return ts->api->serialize_response(ts, THINGSET_ERR_FORBIDDEN,
324 : 2 : "Item %s is read-only", object->name);
325 : : }
326 : : }
327 : :
328 : : /*
329 : : * Test format of simple data types (up to 64-bit) by deserializing the value into a dummy
330 : : * object of the same type. For string and byte buffers only the size of the buffers is
331 : : * checked.
332 : : */
333 : : uint8_t dummy_data[8];
334 [ + + ]: 16 : uint8_t *data = object->type == THINGSET_TYPE_BYTES || object->type == THINGSET_TYPE_ARRAY
335 : 6 : ? object->data.u8
336 [ + + ]: 34 : : dummy_data;
337 : 18 : struct thingset_data_object dummy_object = {
338 : 18 : 0, 0, "Dummy", { .u8 = data }, object->type, object->detail
339 : : };
340 : :
341 : 18 : err = ts->api->deserialize_value(ts, &dummy_object, true);
342 [ - + ]: 18 : if (err != 0) {
343 : 0 : return ts->api->serialize_response(ts, -err, NULL);
344 : : }
345 : : }
346 : :
347 : 17 : ts->api->deserialize_payload_reset(ts);
348 : 17 : ts->api->deserialize_map_start(ts);
349 : :
350 [ + + ]: 17 : if (ts->endpoint.object->data.group_callback != NULL) {
351 : 5 : int err = ts->endpoint.object->data.group_callback(THINGSET_CALLBACK_PRE_WRITE, object);
352 [ - + ]: 5 : if (err < 0) {
353 : 0 : return ts->api->serialize_response(ts, -err, NULL);
354 : : }
355 : : }
356 : :
357 : : /* actually write data */
358 : 35 : while ((err = ts->api->deserialize_child(ts, &object))
359 [ + + ]: 35 : != -THINGSET_ERR_DESERIALIZATION_FINISHED)
360 : : {
361 : 18 : err = ts->api->deserialize_value(ts, object, false);
362 [ - + ]: 18 : if (err != 0) {
363 : 0 : return ts->api->serialize_response(ts, -err, NULL);
364 : : }
365 : :
366 [ + + ]: 18 : if (ts->update_subsets & object->subsets) {
367 : 2 : updated = true;
368 : : }
369 : : }
370 : :
371 [ + + ]: 17 : if (ts->endpoint.object->data.group_callback != NULL) {
372 : 5 : int err = ts->endpoint.object->data.group_callback(THINGSET_CALLBACK_POST_WRITE, object);
373 [ - + ]: 5 : if (err < 0) {
374 : 0 : return ts->api->serialize_response(ts, -err, NULL);
375 : : }
376 : : }
377 : :
378 : : /*
379 : : * The update callback should be invoked after the group callback. This allows to use the group
380 : : * callback for data processing and the update callback for finally storing the data in NVM.
381 : : */
382 [ + + + + ]: 17 : if (updated && ts->update_cb != NULL) {
383 : 1 : ts->update_cb();
384 : : }
385 : :
386 : 17 : return ts->api->serialize_response(ts, THINGSET_STATUS_CHANGED, NULL);
387 : : }
388 : :
389 : 24 : int thingset_common_exec(struct thingset_context *ts)
390 : : {
391 : : int err;
392 : :
393 : 24 : err = ts->api->deserialize_list_start(ts);
394 [ + + ]: 24 : if (err != 0) {
395 : 9 : err = ts->api->deserialize_finish(ts);
396 [ + + ]: 9 : if (err != 0) {
397 : 2 : return ts->api->serialize_response(ts, THINGSET_ERR_BAD_REQUEST, "Invalid parameters");
398 : : }
399 : : }
400 : :
401 [ + + ]: 22 : if ((ts->endpoint.object->access & THINGSET_WRITE_MASK)
402 [ + + ]: 20 : && (ts->endpoint.object->type == THINGSET_TYPE_FN_VOID
403 [ + - ]: 3 : || ts->endpoint.object->type == THINGSET_TYPE_FN_I32))
404 : : {
405 : : /* object is generally executable, but are we authorized? */
406 [ + + ]: 20 : if ((ts->endpoint.object->access & THINGSET_WRITE_MASK & ts->auth_flags) == 0) {
407 : 4 : return ts->api->serialize_response(ts, THINGSET_ERR_UNAUTHORIZED,
408 : : "Authentication required");
409 : : }
410 : : }
411 : : else {
412 : 2 : return ts->api->serialize_response(ts, THINGSET_ERR_FORBIDDEN, "%s is not executable",
413 : 2 : ts->endpoint.object->name);
414 : : }
415 : :
416 [ + + ]: 992 : for (unsigned int i = 0; i < ts->num_objects; i++) {
417 [ + + ]: 980 : if (ts->data_objects[i].parent_id == ts->endpoint.object->id) {
418 : 15 : err = ts->api->deserialize_value(ts, &ts->data_objects[i], false);
419 [ + + ]: 15 : if (err == -THINGSET_ERR_DESERIALIZATION_FINISHED) {
420 : : /* more child objects found than parameters were passed */
421 : 2 : return ts->api->serialize_response(ts, THINGSET_ERR_BAD_REQUEST,
422 : : "Not enough parameters");
423 : : }
424 [ + + ]: 13 : else if (err != 0) {
425 : : /* deserializing the value was not successful */
426 : 2 : return ts->api->serialize_response(ts, -err, NULL);
427 : : }
428 : : }
429 : : }
430 : :
431 : 12 : err = ts->api->deserialize_finish(ts);
432 [ + + ]: 12 : if (err != 0) {
433 : : /* more parameters passed than child objects found */
434 : 2 : return ts->api->serialize_response(ts, THINGSET_ERR_BAD_REQUEST, "Too many parameters");
435 : : }
436 : :
437 : 10 : ts->api->serialize_response(ts, THINGSET_STATUS_CHANGED, NULL);
438 : :
439 : : /* if we got here, finally create function pointer and call function */
440 [ + + ]: 10 : if (ts->endpoint.object->type == THINGSET_TYPE_FN_I32) {
441 : 3 : int32_t ret = ts->endpoint.object->data.i32_fn();
442 : 3 : struct thingset_data_object ret_object = THINGSET_ITEM_INT32(0, 0, "", &ret, 0, 0);
443 : 3 : err = ts->api->serialize_value(ts, &ret_object);
444 [ - + ]: 3 : if (err != 0) {
445 : 0 : return ts->api->serialize_response(ts, THINGSET_ERR_RESPONSE_TOO_LARGE, NULL);
446 : : }
447 : : }
448 : : else {
449 : 7 : ts->endpoint.object->data.void_fn();
450 : : }
451 : :
452 : 10 : return 0;
453 : : }
454 : :
455 : 10 : int thingset_common_create_delete(struct thingset_context *ts, bool create)
456 : : {
457 [ + + ]: 10 : if (ts->endpoint.object->id == 0) {
458 : 1 : return ts->api->serialize_response(ts, THINGSET_ERR_BAD_REQUEST, "Endpoint item required");
459 : : }
460 : :
461 [ + + ]: 9 : if (ts->endpoint.object->type == THINGSET_TYPE_ARRAY) {
462 : 1 : return ts->api->serialize_response(ts, THINGSET_ERR_NOT_IMPLEMENTED,
463 : : "Arrays not yet supported");
464 : : }
465 [ + + ]: 8 : else if (ts->endpoint.object->type == THINGSET_TYPE_SUBSET) {
466 : : #if CONFIG_THINGSET_IMMUTABLE_OBJECTS
467 : : return ts->api->serialize_response(ts, THINGSET_ERR_METHOD_NOT_ALLOWED,
468 : : "Subset is immutable");
469 : : #else
470 : : const char *str_start;
471 : : size_t str_len;
472 : 7 : int err = ts->api->deserialize_string(ts, &str_start, &str_len);
473 [ + + ]: 7 : if (err != 0) {
474 : 2 : return ts->api->serialize_response(ts, THINGSET_ERR_UNSUPPORTED_FORMAT, NULL);
475 : : }
476 : :
477 : : struct thingset_endpoint element;
478 : 5 : int ret = thingset_endpoint_by_path(ts, &element, str_start, str_len);
479 [ + + + - ]: 5 : if (ret >= 0 && element.index == THINGSET_ENDPOINT_INDEX_NONE) {
480 [ + + ]: 4 : if (create) {
481 : 2 : element.object->subsets |= ts->endpoint.object->data.subset;
482 : 2 : return ts->api->serialize_response(ts, THINGSET_STATUS_CREATED, NULL);
483 : : }
484 : : else {
485 : 2 : element.object->subsets &= ~ts->endpoint.object->data.subset;
486 : 2 : return ts->api->serialize_response(ts, THINGSET_STATUS_DELETED, NULL);
487 : : }
488 : : }
489 : 1 : return ts->api->serialize_response(ts, THINGSET_ERR_NOT_FOUND, NULL);
490 : : #endif /* CONFIG_THINGSET_IMMUTABLE_OBJECTS */
491 : : }
492 : :
493 : 1 : return ts->api->serialize_response(ts, THINGSET_ERR_METHOD_NOT_ALLOWED, NULL);
494 : : }
495 : :
496 : 8 : int thingset_common_create(struct thingset_context *ts)
497 : : {
498 : 8 : return thingset_common_create_delete(ts, true);
499 : : }
500 : :
501 : 2 : int thingset_common_delete(struct thingset_context *ts)
502 : : {
503 : 2 : return thingset_common_create_delete(ts, false);
504 : : }
|