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);
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);
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);
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);
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);
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);
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 [ + + ]: 28 : if (ts->endpoint.object->data.group_callback != NULL) {
236 : 2 : ts->endpoint.object->data.group_callback(THINGSET_CALLBACK_PRE_READ);
237 : : }
238 : :
239 : : const struct thingset_data_object *object;
240 : 60 : while ((err = ts->api->deserialize_child(ts, &object))
241 [ + + ]: 60 : != -THINGSET_ERR_DESERIALIZATION_FINISHED)
242 : : {
243 [ + + ]: 38 : if (err != 0) {
244 : 4 : return ts->api->serialize_response(ts, -err, NULL);
245 : : }
246 : :
247 [ + + + + ]: 34 : if (object->type == THINGSET_TYPE_GROUP && ts->endpoint.object->id != THINGSET_ID_PATHS
248 [ + + ]: 3 : && ts->endpoint.object->id != THINGSET_ID_METADATA)
249 : : {
250 : 2 : return ts->api->serialize_response(ts, THINGSET_ERR_BAD_REQUEST, "%s is a group",
251 : 2 : object->name);
252 : : }
253 : :
254 [ - + ]: 32 : if ((object->access & THINGSET_READ_MASK & ts->auth_flags) == 0) {
255 [ # # ]: 0 : if (object->access & THINGSET_READ_MASK) {
256 : 0 : return ts->api->serialize_response(ts, THINGSET_ERR_UNAUTHORIZED,
257 : : "Authentication required for %s",
258 : 0 : object->name);
259 : : }
260 : : else {
261 : 0 : return ts->api->serialize_response(ts, THINGSET_ERR_FORBIDDEN,
262 : 0 : "Reading %s forbidden", object->name);
263 : : }
264 : : }
265 : :
266 [ + + ]: 32 : if (ts->endpoint.object->id == THINGSET_ID_PATHS) {
267 : 4 : err = ts->api->serialize_path(ts, object);
268 : : }
269 : : #ifdef CONFIG_THINGSET_METADATA_ENDPOINT
270 [ + + ]: 28 : else if (ts->endpoint.object->id == THINGSET_ID_METADATA) {
271 : 8 : err = ts->api->serialize_metadata(ts, object);
272 : : }
273 : : #endif
274 : : else {
275 : 20 : err = ts->api->serialize_value(ts, object);
276 : : }
277 : :
278 [ - + ]: 32 : if (err != 0) {
279 : 0 : return ts->api->serialize_response(ts, -err, NULL);
280 : : }
281 : : }
282 : :
283 [ + + ]: 22 : if (ts->endpoint.object->data.group_callback != NULL) {
284 : 2 : ts->endpoint.object->data.group_callback(THINGSET_CALLBACK_POST_READ);
285 : : }
286 : : }
287 : : else {
288 : 0 : return ts->api->serialize_response(ts, THINGSET_ERR_BAD_REQUEST, "Invalid payload");
289 : : }
290 : :
291 : 28 : ts->api->serialize_list_end(ts);
292 : :
293 : 28 : return 0;
294 : : }
295 : :
296 : 23 : int thingset_common_update(struct thingset_context *ts)
297 : : {
298 : : const struct thingset_data_object *object;
299 : 23 : bool updated = false;
300 : : int err;
301 : :
302 : 23 : err = ts->api->deserialize_map_start(ts);
303 [ - + ]: 23 : if (err != 0) {
304 : 0 : return ts->api->serialize_response(ts, THINGSET_ERR_BAD_REQUEST, "Map with data required");
305 : : }
306 : :
307 : : /* loop through all elements to check if request is valid */
308 : 41 : while ((err = ts->api->deserialize_child(ts, &object))
309 [ + + ]: 41 : != -THINGSET_ERR_DESERIALIZATION_FINISHED)
310 : : {
311 [ + + ]: 24 : if (err != 0) {
312 : 6 : return ts->api->serialize_response(ts, -err, NULL);
313 : : }
314 : :
315 [ + + ]: 21 : if ((object->access & THINGSET_WRITE_MASK & ts->auth_flags) == 0) {
316 [ + + ]: 3 : if (object->access & THINGSET_WRITE_MASK) {
317 : 1 : return ts->api->serialize_response(ts, THINGSET_ERR_UNAUTHORIZED,
318 : 1 : "Authentication required for %s", object->name);
319 : : }
320 : : else {
321 : 2 : return ts->api->serialize_response(ts, THINGSET_ERR_FORBIDDEN,
322 : 2 : "Item %s is read-only", object->name);
323 : : }
324 : : }
325 : :
326 : : /*
327 : : * Test format of simple data types (up to 64-bit) by deserializing the value into a dummy
328 : : * object of the same type. For string and byte buffers only the size of the buffers is
329 : : * checked.
330 : : */
331 : : uint8_t dummy_data[8];
332 [ + + ]: 16 : uint8_t *data = object->type == THINGSET_TYPE_BYTES || object->type == THINGSET_TYPE_ARRAY
333 : 6 : ? object->data.u8
334 [ + + ]: 34 : : dummy_data;
335 : 18 : struct thingset_data_object dummy_object = {
336 : 18 : 0, 0, "Dummy", { .u8 = data }, object->type, object->detail
337 : : };
338 : :
339 : 18 : err = ts->api->deserialize_value(ts, &dummy_object, true);
340 [ - + ]: 18 : if (err != 0) {
341 : 0 : return ts->api->serialize_response(ts, -err, NULL);
342 : : }
343 : : }
344 : :
345 : 17 : ts->api->deserialize_payload_reset(ts);
346 : 17 : ts->api->deserialize_map_start(ts);
347 : :
348 [ + + ]: 17 : if (ts->endpoint.object->data.group_callback != NULL) {
349 : 5 : ts->endpoint.object->data.group_callback(THINGSET_CALLBACK_PRE_WRITE);
350 : : }
351 : :
352 : : /* actually write data */
353 : 35 : while ((err = ts->api->deserialize_child(ts, &object))
354 [ + + ]: 35 : != -THINGSET_ERR_DESERIALIZATION_FINISHED)
355 : : {
356 : 18 : err = ts->api->deserialize_value(ts, object, false);
357 [ - + ]: 18 : if (err != 0) {
358 : 0 : return ts->api->serialize_response(ts, -err, NULL);
359 : : }
360 : :
361 [ + + ]: 18 : if (ts->update_subsets & object->subsets) {
362 : 2 : updated = true;
363 : : }
364 : : }
365 : :
366 [ + + ]: 17 : if (ts->endpoint.object->data.group_callback != NULL) {
367 : 5 : ts->endpoint.object->data.group_callback(THINGSET_CALLBACK_POST_WRITE);
368 : : }
369 : :
370 : : /*
371 : : * The update callback should be invoked after the group callback. This allows to use the group
372 : : * callback for data processing and the update callback for finally storing the data in NVM.
373 : : */
374 [ + + + + ]: 17 : if (updated && ts->update_cb != NULL) {
375 : 1 : ts->update_cb();
376 : : }
377 : :
378 : 17 : return ts->api->serialize_response(ts, THINGSET_STATUS_CHANGED, NULL);
379 : : }
380 : :
381 : 24 : int thingset_common_exec(struct thingset_context *ts)
382 : : {
383 : : int err;
384 : :
385 : 24 : err = ts->api->deserialize_list_start(ts);
386 [ + + ]: 24 : if (err != 0) {
387 : 9 : err = ts->api->deserialize_finish(ts);
388 [ + + ]: 9 : if (err != 0) {
389 : 2 : return ts->api->serialize_response(ts, THINGSET_ERR_BAD_REQUEST, "Invalid parameters");
390 : : }
391 : : }
392 : :
393 [ + + ]: 22 : if ((ts->endpoint.object->access & THINGSET_WRITE_MASK)
394 [ + + ]: 20 : && (ts->endpoint.object->type == THINGSET_TYPE_FN_VOID
395 [ + - ]: 3 : || ts->endpoint.object->type == THINGSET_TYPE_FN_I32))
396 : : {
397 : : /* object is generally executable, but are we authorized? */
398 [ + + ]: 20 : if ((ts->endpoint.object->access & THINGSET_WRITE_MASK & ts->auth_flags) == 0) {
399 : 4 : return ts->api->serialize_response(ts, THINGSET_ERR_UNAUTHORIZED,
400 : : "Authentication required");
401 : : }
402 : : }
403 : : else {
404 : 2 : return ts->api->serialize_response(ts, THINGSET_ERR_FORBIDDEN, "%s is not executable",
405 : 2 : ts->endpoint.object->name);
406 : : }
407 : :
408 [ + + ]: 992 : for (unsigned int i = 0; i < ts->num_objects; i++) {
409 [ + + ]: 980 : if (ts->data_objects[i].parent_id == ts->endpoint.object->id) {
410 : 15 : err = ts->api->deserialize_value(ts, &ts->data_objects[i], false);
411 [ + + ]: 15 : if (err == -THINGSET_ERR_DESERIALIZATION_FINISHED) {
412 : : /* more child objects found than parameters were passed */
413 : 2 : return ts->api->serialize_response(ts, THINGSET_ERR_BAD_REQUEST,
414 : : "Not enough parameters");
415 : : }
416 [ + + ]: 13 : else if (err != 0) {
417 : : /* deserializing the value was not successful */
418 : 2 : return ts->api->serialize_response(ts, -err, NULL);
419 : : }
420 : : }
421 : : }
422 : :
423 : 12 : err = ts->api->deserialize_finish(ts);
424 [ + + ]: 12 : if (err != 0) {
425 : : /* more parameters passed than child objects found */
426 : 2 : return ts->api->serialize_response(ts, THINGSET_ERR_BAD_REQUEST, "Too many parameters");
427 : : }
428 : :
429 : 10 : ts->api->serialize_response(ts, THINGSET_STATUS_CHANGED, NULL);
430 : :
431 : : /* if we got here, finally create function pointer and call function */
432 [ + + ]: 10 : if (ts->endpoint.object->type == THINGSET_TYPE_FN_I32) {
433 : 3 : int32_t ret = ts->endpoint.object->data.i32_fn();
434 : 3 : struct thingset_data_object ret_object = THINGSET_ITEM_INT32(0, 0, "", &ret, 0, 0);
435 : 3 : err = ts->api->serialize_value(ts, &ret_object);
436 [ - + ]: 3 : if (err != 0) {
437 : 0 : return ts->api->serialize_response(ts, THINGSET_ERR_RESPONSE_TOO_LARGE, NULL);
438 : : }
439 : : }
440 : : else {
441 : 7 : ts->endpoint.object->data.void_fn();
442 : : }
443 : :
444 : 10 : return 0;
445 : : }
446 : :
447 : 10 : int thingset_common_create_delete(struct thingset_context *ts, bool create)
448 : : {
449 [ + + ]: 10 : if (ts->endpoint.object->id == 0) {
450 : 1 : return ts->api->serialize_response(ts, THINGSET_ERR_BAD_REQUEST, "Endpoint item required");
451 : : }
452 : :
453 [ + + ]: 9 : if (ts->endpoint.object->type == THINGSET_TYPE_ARRAY) {
454 : 1 : return ts->api->serialize_response(ts, THINGSET_ERR_NOT_IMPLEMENTED,
455 : : "Arrays not yet supported");
456 : : }
457 [ + + ]: 8 : else if (ts->endpoint.object->type == THINGSET_TYPE_SUBSET) {
458 : : #if CONFIG_THINGSET_IMMUTABLE_OBJECTS
459 : : return ts->api->serialize_response(ts, THINGSET_ERR_METHOD_NOT_ALLOWED,
460 : : "Subset is immutable");
461 : : #else
462 : : const char *str_start;
463 : : size_t str_len;
464 : 7 : int err = ts->api->deserialize_string(ts, &str_start, &str_len);
465 [ + + ]: 7 : if (err != 0) {
466 : 2 : return ts->api->serialize_response(ts, THINGSET_ERR_UNSUPPORTED_FORMAT, NULL);
467 : : }
468 : :
469 : : struct thingset_endpoint element;
470 : 5 : int ret = thingset_endpoint_by_path(ts, &element, str_start, str_len);
471 [ + + + - ]: 5 : if (ret >= 0 && element.index == THINGSET_ENDPOINT_INDEX_NONE) {
472 [ + + ]: 4 : if (create) {
473 : 2 : element.object->subsets |= ts->endpoint.object->data.subset;
474 : 2 : return ts->api->serialize_response(ts, THINGSET_STATUS_CREATED, NULL);
475 : : }
476 : : else {
477 : 2 : element.object->subsets &= ~ts->endpoint.object->data.subset;
478 : 2 : return ts->api->serialize_response(ts, THINGSET_STATUS_DELETED, NULL);
479 : : }
480 : : }
481 : 1 : return ts->api->serialize_response(ts, THINGSET_ERR_NOT_FOUND, NULL);
482 : : #endif /* CONFIG_THINGSET_IMMUTABLE_OBJECTS */
483 : : }
484 : :
485 : 1 : return ts->api->serialize_response(ts, THINGSET_ERR_METHOD_NOT_ALLOWED, NULL);
486 : : }
487 : :
488 : 8 : int thingset_common_create(struct thingset_context *ts)
489 : : {
490 : 8 : return thingset_common_create_delete(ts, true);
491 : : }
492 : :
493 : 2 : int thingset_common_delete(struct thingset_context *ts)
494 : : {
495 : 2 : return thingset_common_create_delete(ts, false);
496 : : }
|