From dd6f51e5b6f73e88811fd157e1bccab66e524616 Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Tue, 5 Oct 2021 12:17:42 +0200 Subject: [PATCH 01/37] COM prepare - add TinyCBOR as a submodule Prepare for ThingSet Communication Framework: - Add tinycbor as a submodule for the native environment Other environments (e.g. Zephyr, FreeRTOS) usually support TinyCBOR by themselves. TinyCBOR is from: https://github.com/intel/tinycbor The documentation is at: https://intel.github.io/tinycbor/current/ TinyCBOR is MIT licensed. Signed-off-by: Bobby Noelte --- .gitmodules | 3 +++ native/tinycbor | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 native/tinycbor diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..8b315d6 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "native/tinycbor"] + path = native/tinycbor + url = https://github.com/intel/tinycbor diff --git a/native/tinycbor b/native/tinycbor new file mode 160000 index 0000000..6176e0a --- /dev/null +++ b/native/tinycbor @@ -0,0 +1 @@ +Subproject commit 6176e0a28d7c5ef3a5e9cbd02521999c412de72c From ed238c5e0a5f1d82c3f487a0422de9a29928a31b Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Wed, 12 Jan 2022 08:19:34 +0100 Subject: [PATCH 02/37] COM prepare - add Unity Test as a submodule Prepare for ThingSet Communication Framework: - Add Unity Test as a submodule for the native environment Other environments (e.g. Zephyr, FreeRTOS) either have their own unit test framework or provide Unity Test by themselves. Unity Test is from: https://github.com/ThrowTheSwitch/Unity The documentation is at: https://github.com/ThrowTheSwitch/Unity/blob/master/docs/UnityGettingStartedGuide.md Unity Test is MIT licensed. Signed-off-by: Bobby Noelte --- .gitmodules | 3 +++ native/unity | 1 + 2 files changed, 4 insertions(+) create mode 160000 native/unity diff --git a/.gitmodules b/.gitmodules index 8b315d6..79b7b7f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "native/tinycbor"] path = native/tinycbor url = https://github.com/intel/tinycbor +[submodule "native/unity"] + path = native/unity + url = https://github.com/ThrowTheSwitch/Unity diff --git a/native/unity b/native/unity new file mode 160000 index 0000000..8ba0138 --- /dev/null +++ b/native/unity @@ -0,0 +1 @@ +Subproject commit 8ba01386008196a92ef4fdbdb0b00f2434c79563 From 16f7f22df53e2b73165d34a05ec08487e1ccd107 Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Sat, 18 Dec 2021 13:18:07 +0100 Subject: [PATCH 03/37] COM prepare - add ThingSet OSAL for complex macros Prepare for ThingSet Communication Context: - Add operating system abstraction for complex macro functions. - Add native (default) implementation of complex macros. - Add Zephyr implementation of complex macros. - Add some simpler macros for convenience (TS_CAT, TS_EXPAND, ...) Complex macros are: - TS_ARRAY_SIZE - TS_FOR_EACH - TS_NUM_VA_ARGS_LESS_1 These macros are in preparation to allow complex data structure definitions using macros. Signed-off-by: Bobby Noelte --- native/thingset/ts_impl_macro.h | 1118 +++++++++++++++++++++++++++++++ src/ts_macro.h | 207 ++++++ zephyr/thingset/ts_impl_macro.h | 22 + 3 files changed, 1347 insertions(+) create mode 100644 native/thingset/ts_impl_macro.h create mode 100644 src/ts_macro.h create mode 100644 zephyr/thingset/ts_impl_macro.h diff --git a/native/thingset/ts_impl_macro.h b/native/thingset/ts_impl_macro.h new file mode 100644 index 0000000..6129e86 --- /dev/null +++ b/native/thingset/ts_impl_macro.h @@ -0,0 +1,1118 @@ +/* + * Copyright (c) 2021, Nordic Semiconductor ASA + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Native implementation of ThingSet utility macros. + */ + +#ifndef TS_IMPL_MACRO_H_ +#define TS_IMPL_MACRO_H_ + +/* + * TS_IMPL_ARRAY_SIZE + * ------------------- + */ +#ifdef ARRAY_SIZE +#define TS_IMPL_ARRAY_SIZE(array) ARRAY_SIZE(array) +#else +#define TS_IMPL_ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) +#endif + +/* + * TS_IMPL_FOR_EACH + * ---------------- + */ +#define __DEBRACKET(...) __VA_ARGS__ + +#define Z_FOR_LOOP_GET_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \ + _12, _13, _14, _15, _16, _17, _18, _19, _20, \ + _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, \ + _39, _40, _41, _42, _43, _44, _45, _46, _47, \ + _48, _49, _50, _51, _52, _53, _54, _55, _56, \ + _57, _58, _59, _60, _61, _62, _63, _64, N, ...) N + +#define Z_FOR_LOOP_0(z_call, sep, fixed_arg0, fixed_arg1, ...) + +#define Z_FOR_LOOP_1(z_call, sep, fixed_arg0, fixed_arg1, x) \ + z_call(0, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_2(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_1(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(1, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_3(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_2(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(2, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_4(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_3(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(3, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_5(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_4(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(4, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_6(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_5(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(5, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_7(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_6(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(6, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_8(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_7(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(7, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_9(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_8(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(8, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_10(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_9(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(9, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_11(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_10(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(10, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_12(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_11(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(11, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_13(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_12(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(12, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_14(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_13(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(13, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_15(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_14(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(14, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_16(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_15(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(15, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_17(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_16(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(16, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_18(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_17(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(17, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_19(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_18(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(18, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_20(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_19(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(19, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_21(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_20(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(20, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_22(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_21(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(21, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_23(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_22(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(22, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_24(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_23(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(23, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_25(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_24(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(24, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_26(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_25(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(25, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_27(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_26(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(26, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_28(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_27(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(27, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_29(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_28(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(28, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_30(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_29(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(29, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_31(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_30(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(30, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_32(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_31(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(31, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_33(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_32(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(32, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_34(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_33(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(33, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_35(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_34(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(34, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_36(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_35(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(35, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_37(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_36(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(36, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_38(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_37(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(37, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_39(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_38(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(38, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_40(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_39(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(39, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_41(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_40(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(40, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_42(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_41(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(41, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_43(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_42(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(42, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_44(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_43(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(43, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_45(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_44(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(44, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_46(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_45(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(45, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_47(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_46(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(46, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_48(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_47(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(47, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_49(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_48(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(48, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_50(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_49(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(49, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_51(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_50(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(50, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_52(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_51(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(51, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_53(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_52(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(52, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_54(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_53(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(53, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_55(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_54(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(54, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_56(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_55(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(55, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_57(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_56(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(56, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_58(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_57(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(57, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_59(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_58(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(58, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_60(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_59(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(59, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_61(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_60(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(60, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_62(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_61(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(61, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_63(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_62(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(62, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_LOOP_64(z_call, sep, fixed_arg0, fixed_arg1, x, ...) \ + Z_FOR_LOOP_63(z_call, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) \ + __DEBRACKET sep \ + z_call(63, x, fixed_arg0, fixed_arg1) + +#define Z_FOR_EACH_ENGINE(x, sep, fixed_arg0, fixed_arg1, ...) \ + Z_FOR_LOOP_GET_ARG(__VA_ARGS__, \ + Z_FOR_LOOP_64, \ + Z_FOR_LOOP_63, \ + Z_FOR_LOOP_62, \ + Z_FOR_LOOP_61, \ + Z_FOR_LOOP_60, \ + Z_FOR_LOOP_59, \ + Z_FOR_LOOP_58, \ + Z_FOR_LOOP_57, \ + Z_FOR_LOOP_56, \ + Z_FOR_LOOP_55, \ + Z_FOR_LOOP_54, \ + Z_FOR_LOOP_53, \ + Z_FOR_LOOP_52, \ + Z_FOR_LOOP_51, \ + Z_FOR_LOOP_50, \ + Z_FOR_LOOP_49, \ + Z_FOR_LOOP_48, \ + Z_FOR_LOOP_47, \ + Z_FOR_LOOP_46, \ + Z_FOR_LOOP_45, \ + Z_FOR_LOOP_44, \ + Z_FOR_LOOP_43, \ + Z_FOR_LOOP_42, \ + Z_FOR_LOOP_41, \ + Z_FOR_LOOP_40, \ + Z_FOR_LOOP_39, \ + Z_FOR_LOOP_38, \ + Z_FOR_LOOP_37, \ + Z_FOR_LOOP_36, \ + Z_FOR_LOOP_35, \ + Z_FOR_LOOP_34, \ + Z_FOR_LOOP_33, \ + Z_FOR_LOOP_32, \ + Z_FOR_LOOP_31, \ + Z_FOR_LOOP_30, \ + Z_FOR_LOOP_29, \ + Z_FOR_LOOP_28, \ + Z_FOR_LOOP_27, \ + Z_FOR_LOOP_26, \ + Z_FOR_LOOP_25, \ + Z_FOR_LOOP_24, \ + Z_FOR_LOOP_23, \ + Z_FOR_LOOP_22, \ + Z_FOR_LOOP_21, \ + Z_FOR_LOOP_20, \ + Z_FOR_LOOP_19, \ + Z_FOR_LOOP_18, \ + Z_FOR_LOOP_17, \ + Z_FOR_LOOP_16, \ + Z_FOR_LOOP_15, \ + Z_FOR_LOOP_14, \ + Z_FOR_LOOP_13, \ + Z_FOR_LOOP_12, \ + Z_FOR_LOOP_11, \ + Z_FOR_LOOP_10, \ + Z_FOR_LOOP_9, \ + Z_FOR_LOOP_8, \ + Z_FOR_LOOP_7, \ + Z_FOR_LOOP_6, \ + Z_FOR_LOOP_5, \ + Z_FOR_LOOP_4, \ + Z_FOR_LOOP_3, \ + Z_FOR_LOOP_2, \ + Z_FOR_LOOP_1, \ + Z_FOR_LOOP_0)(x, sep, fixed_arg0, fixed_arg1, ##__VA_ARGS__) + +#define Z_GET_ARG_1(_0, ...) _0 + +#define Z_GET_ARG_2(_0, _1, ...) _1 + +#define Z_GET_ARG_3(_0, _1, _2, ...) _2 + +#define Z_GET_ARG_4(_0, _1, _2, _3, ...) _3 + +#define Z_GET_ARG_5(_0, _1, _2, _3, _4, ...) _4 + +#define Z_GET_ARG_6(_0, _1, _2, _3, _4, _5, ...) _5 + +#define Z_GET_ARG_7(_0, _1, _2, _3, _4, _5, _6, ...) _6 + +#define Z_GET_ARG_8(_0, _1, _2, _3, _4, _5, _6, _7, ...) _7 + +#define Z_GET_ARG_9(_0, _1, _2, _3, _4, _5, _6, _7, _8, ...) _8 + +#define Z_GET_ARG_10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, ...) _9 + +#define Z_GET_ARG_11(_0, _1, _2, _3, _4, _5, \ + _6, _7, _8, _9, _10, ...) _10 + +#define Z_GET_ARG_12(_0, _1, _2, _3, _4, _5, _6,\ + _7, _8, _9, _10, _11, ...) _11 + +#define Z_GET_ARG_13(_0, _1, _2, _3, _4, _5, _6, \ + _7, _8, _9, _10, _11, _12, ...) _12 + +#define Z_GET_ARG_14(_0, _1, _2, _3, _4, _5, _6, \ + _7, _8, _9, _10, _11, _12, _13, ...) _13 + +#define Z_GET_ARG_15(_0, _1, _2, _3, _4, _5, _6, _7, \ + _8, _9, _10, _11, _12, _13, _14, ...) _14 + +#define Z_GET_ARG_16(_0, _1, _2, _3, _4, _5, _6, _7, \ + _8, _9, _10, _11, _12, _13, _14, _15, ...) _15 + +#define Z_GET_ARG_17(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, ...) _16 + +#define Z_GET_ARG_18(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, ...) _17 + +#define Z_GET_ARG_19(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, ...) _18 + +#define Z_GET_ARG_20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + ...) _19 + +#define Z_GET_ARG_21(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, ...) _20 + +#define Z_GET_ARG_22(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, ...) _21 + +#define Z_GET_ARG_23(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, ...) _22 + +#define Z_GET_ARG_24(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, ...) _23 + +#define Z_GET_ARG_25(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, ...) _24 + +#define Z_GET_ARG_26(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, ...) _25 + +#define Z_GET_ARG_27(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, ...) _26 + +#define Z_GET_ARG_28(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, ...) _27 + +#define Z_GET_ARG_29(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + ...) _28 + +#define Z_GET_ARG_30(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, ...) _29 + +#define Z_GET_ARG_31(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, ...) _30 + +#define Z_GET_ARG_32(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, ...) _31 + +#define Z_GET_ARG_33(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, ...) _32 + +#define Z_GET_ARG_34(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, ...) _33 + +#define Z_GET_ARG_35(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, ...) _34 + +#define Z_GET_ARG_36(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, ...) _35 + +#define Z_GET_ARG_37(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, ...) _36 + +#define Z_GET_ARG_38(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, ...) _37 + +#define Z_GET_ARG_39(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, ...) _38 + +#define Z_GET_ARG_40(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, ...) _39 + +#define Z_GET_ARG_41(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, ...) _40 + +#define Z_GET_ARG_42(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, ...) _41 + +#define Z_GET_ARG_43(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, ...) _42 + +#define Z_GET_ARG_44(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, ...) _43 + +#define Z_GET_ARG_45(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, ...) _44 + +#define Z_GET_ARG_46(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, ...) _45 + +#define Z_GET_ARG_47(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, ...) _46 + +#define Z_GET_ARG_48(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, ...) _47 + +#define Z_GET_ARG_49(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, ...) _48 + +#define Z_GET_ARG_50(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, ...) _49 + +#define Z_GET_ARG_51(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, ...) _50 + +#define Z_GET_ARG_52(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, ...) _51 + +#define Z_GET_ARG_53(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, _52, ...) _52 + +#define Z_GET_ARG_54(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, _52, _53, ...) _53 + +#define Z_GET_ARG_55(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, _52, _53, _54, ...) _54 + +#define Z_GET_ARG_56(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, _52, _53, _54, _55, ...) _55 + +#define Z_GET_ARG_57(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, _52, _53, _54, _55, \ + _56, ...) _56 + +#define Z_GET_ARG_58(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, _52, _53, _54, _55, \ + _56, _57, ...) _57 + +#define Z_GET_ARG_59(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, _52, _53, _54, _55, \ + _56, _57, _58, ...) _58 + +#define Z_GET_ARG_60(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, _52, _53, _54, _55, \ + _56, _57, _58, _59, ...) _59 + +#define Z_GET_ARG_61(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, _52, _53, _54, _55, \ + _56, _57, _58, _59, _60, ...) _60 + +#define Z_GET_ARG_62(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, _52, _53, _54, _55, \ + _56, _57, _58, _59, _60, _61, ...) _61 + +#define Z_GET_ARG_63(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, _52, _53, _54, _55, \ + _56, _57, _58, _59, _60, _61, _62, ...) _62 + +#define Z_GET_ARG_64(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, _52, _53, _54, _55, \ + _56, _57, _58, _59, _60, _61, _62, _63, ...) _63 + +#define Z_GET_ARGS_LESS_0(...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_1(_0, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_2(_0, _1, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_3(_0, _1, _2, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_4(_0, _1, _2, _3, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_5(_0, _1, _2, _3, _4, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_6(_0, _1, _2, _3, _4, _5, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_7(_0, _1, _2, _3, _4, _5, _6, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_8(_0, _1, _2, _3, _4, _5, \ + _6, _7, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_9(_0, _1, _2, _3, _4, _5, \ + _6, _7, _8, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_10(_0, _1, _2, _3, _4, _5, \ + _6, _7, _8, _9, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_11(_0, _1, _2, _3, _4, _5, \ + _6, _7, _8, _9, _10, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_12(_0, _1, _2, _3, _4, _5, _6,\ + _7, _8, _9, _10, _11, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_13(_0, _1, _2, _3, _4, _5, _6, \ + _7, _8, _9, _10, _11, _12, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_14(_0, _1, _2, _3, _4, _5, _6, \ + _7, _8, _9, _10, _11, _12, _13, \ + ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_15(_0, _1, _2, _3, _4, _5, _6, _7, \ + _8, _9, _10, _11, _12, _13, _14, \ + ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_16(_0, _1, _2, _3, _4, _5, _6, _7, \ + _8, _9, _10, _11, _12, _13, _14, _15, ...) \ + __VA_ARGS__ + +#define Z_GET_ARGS_LESS_17(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_18(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, ...) \ + __VA_ARGS__ + +#define Z_GET_ARGS_LESS_19(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, ...) \ + __VA_ARGS__ + +#define Z_GET_ARGS_LESS_20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_21(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_22(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_23(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_24(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_25(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_26(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_27(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, ...) \ + __VA_ARGS__ + +#define Z_GET_ARGS_LESS_28(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, \ + ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_29(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_30(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_31(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_32(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_33(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_34(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_35(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_36(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_37(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_38(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_39(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_40(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_41(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_42(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_43(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_44(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_45(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_46(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_47(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_48(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_49(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_50(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_51(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_52(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_53(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, _52, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_54(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, _52, _53, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_55(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, _52, _53, _54, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_56(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, _52, _53, _54, _55, \ + ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_57(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, _52, _53, _54, _55, \ + _56, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_58(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, _52, _53, _54, _55, \ + _56, _57, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_59(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, _52, _53, _54, _55, \ + _56, _57, _58, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_60(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, _52, _53, _54, _55, \ + _56, _57, _58, _59, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_61(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, _52, _53, _54, _55, \ + _56, _57, _58, _59, _60, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_62(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, _52, _53, _54, _55, \ + _56, _57, _58, _59, _60, _61, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_63(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, _52, _53, _54, _55, \ + _56, _57, _58, _59, _60, _61, _62, ...) __VA_ARGS__ + +#define Z_GET_ARGS_LESS_64(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, \ + _29, _30, _31, _32, _33, _34, _35, _36, _37, \ + _38, _39, _40, _41, _42, _43, _44, _45, _46, \ + _47, _48, _49, _50, _51, _52, _53, _54, _55, \ + _56, _57, _58, _59, _60, _61, _62, _63, ...) __VA_ARGS__ + +#define Z_FOR_EACH_EXEC(idx, x, fixed_arg0, fixed_arg1) \ + fixed_arg0(x) + +#define Z_FOR_EACH(F, sep, ...) \ + Z_FOR_EACH_ENGINE(Z_FOR_EACH_EXEC, sep, F, _, __VA_ARGS__) + +#define Z_BYPASS(x) x + +#define TS_IMPL_FOR_EACH(F, sep, ...) \ + Z_FOR_EACH(F, sep, TS_IMPL_REVERSE_ARGS(__VA_ARGS__)) + +#define TS_IMPL_REVERSE_ARGS(...) \ + Z_FOR_EACH_ENGINE(Z_FOR_EACH_EXEC, (,), Z_BYPASS, _, __VA_ARGS__) + +/* + * TS_IMPL_NUM_VA_ARGS_LESS_1 + * -------------------------- + */ +#ifdef _MSC_VER // Microsoft compilers +# define TS_IMPL_NUM_VA_ARGS_LESS_1(...) INTERNAL_EXPAND_ARGS_PRIVATE(INTERNAL_ARGS_AUGMENTER(__VA_ARGS__)) +# define INTERNAL_ARGS_AUGMENTER(...) unused, __VA_ARGS__ +# define INTERNAL_EXPAND(x) x +# define INTERNAL_EXPAND_ARGS_PRIVATE(...) INTERNAL_EXPAND( \ + Z_NUM_VA_ARGS_LESS_1(__VA_ARGS__, 63, 62, 61, \ + 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, \ + 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, \ + 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, \ + 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, \ + 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, \ + 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ~)) +#else // Non-Microsoft compilers +#define TS_IMPL_NUM_VA_ARGS_LESS_1(...) \ + Z_NUM_VA_ARGS_LESS_1(__VA_ARGS__, 63, 62, 61, \ + 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, \ + 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, \ + 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, \ + 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, \ + 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, \ + 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ~) +#endif +#define Z_NUM_VA_ARGS_LESS_1( \ + _ignored, \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, \ + _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, \ + _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, \ + _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, \ + _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, \ + _61, _62, N, ...) N + +#endif /* TS_IMPL_MACRO_H_ */ diff --git a/src/ts_macro.h b/src/ts_macro.h new file mode 100644 index 0000000..1ea1a75 --- /dev/null +++ b/src/ts_macro.h @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet utility macros (private interface) + */ + +#ifndef TS_MACRO_H_ +#define TS_MACRO_H_ + +/* ThingSet environment implementation interface */ + +/** + * @addtogroup ts_impl_api + * @{ + */ + +#if TS_DOXYGEN + +/** + * @brief Implementation for @ref TS_ASSERT. + */ +#define TS_IMPL_ASSERT(test, fmt, ...) + +/** + * @brief Implementation for @ref TS_ARRAY_SIZE. + */ +#define TS_IMPL_ARRAY_SIZE(array) + +/** + * @brief Implementation for @ref TS_FOR_EACH. + */ +#define TS_IMPL_FOR_EACH(F, sep, ...) + +/** + * @brief Implementation for @ref TS_NUM_VA_ARGS_LESS_1. + */ +#define TS_IMPL_NUM_VA_ARGS_LESS_1(...) + +#endif /* TS_DOXYGEN */ + +/** + * @} + */ + +/** + * @brief ThingSet utility macros. + * + * @defgroup ts_macro_api_priv ThingSet utility macros (private interface) + * @{ + */ + +/** + * @def TS_ASSERT + * + * @brief Evaluate an expression and generate a debug report when the result is False. + * + * @note TS_IMPL_ASSERT to be provided by the implementation + * + * @param test Expression to be evaluated + * @param fmt Format string for debug report. + * @param ... Variable number of argumentes for the format string. + */ +#define TS_ASSERT(test, fmt, ...) TS_IMPL_ASSERT(test, fmt, ##__VA_ARGS__) + +/** + * @def TS_STATIC_ASSERT + * + * @brief Check boolean condition at compile time. + * + * Assertion that is checked at compile time. + * + * @param bool_cond Boolean condition. + * @param message Message to be displayed on assertion fail. + */ +#if ((__STDC__VERSION__ - 0) >= 201112L) || defined(static_assert) +#define TS_STATIC_ASSERT(bool_cond, message) static_assert(bool_cond, message) +#else +#define TS_STATIC_ASSERT(bool_cond, message) \ + static char const static_assertion##__COUNTER__[ (bool_cond) ? 1 : -1 ] = { message } +#endif + +/** + * @def TS_EXPAND + * + * @brief Expand arguments. + * + * @param ... Variable number of arguments to be expanded. + */ +#define TS_EXPAND(...) __VA_ARGS__ + +/** + * @def TS_STRINGIFY + * + * @brief Stringify the argument after it has been expanded. + * + * @param x The largument to be stringified. + */ +#define TS_STRINGIFY(x) TS_PRIMITIVE_STRINGIFY(x) + +/** + * @def TS_PRIMITIVE_STRINGIFY + * + * @brief Stringify the argument before it is expanded. + * + * @param x The largument to be stringified. + */ +#define TS_PRIMITIVE_STRINGIFY(x) #x + +/** + * @def TS_CAT + * + * @brief Concatenate arguments after they have been expanded. + * + * @param a The left operator of the concatenation. + * @param ... The right operator of the concatenation. + */ +#define TS_CAT(a, ...) TS_PRIMITIVE_CAT(a, __VA_ARGS__) + +/** + * @def TS_PRIMITIVE_CAT + * + * @brief Concatenate arguments before they are expanded. + * + * @param a The left operator of the concatenation. + * @param ... The right operator of the concatenation. + */ +#define TS_PRIMITIVE_CAT(a, ...) a##__VA_ARGS__ + +/** + * @def TS_CALL + * + * @brief Call a macro @p F with given arguments. + * + * Example: + * + * #define MY_ARGS (my_var, 4) + * #define F(x, y) int x = y; + * TS_CALL(F, MY_ARGS) + * + * This expands to: + * + * int my_var = 4; + * + * @param F Macro to invoke + * @param args Arguments. Must be in parentheses! + */ +#define TS_CALL(F, args) TS_EXPAND(F)args + +/** + * @def TS_ARRAY_SIZE + * + * @brief Number of elements in the given @p array + * + * @note TS_IMPL_ARRAY_SIZE to be provided by the implementation + * + * @param array Array + */ +#define TS_ARRAY_SIZE(array) TS_IMPL_ARRAY_SIZE(array) + +/** + * @def TS_FOR_EACH + * + * @brief Call a macro @p F on each provided argument with a given + * separator between each call. + * + * Example: + * + * #define F(x) int a##x + * TS_FOR_EACH(F, (;), 4, 5, 6); + * + * This expands to: + * + * int a4; + * int a5; + * int a6; + * + * @note TS_IMPL_FOR_EACH to be provided by the implementation + * + * @param F Macro to invoke + * @param sep Separator (e.g. comma or semicolon). Must be in parentheses; + * this is required to enable providing a comma as separator. + * @param ... Variable argument list. The macro @p F is invoked as + * F(element) for each element in the list. + */ +#define TS_FOR_EACH(F, sep, ...) TS_IMPL_FOR_EACH(F, sep, __VA_ARGS__) + +/** + * @def TS_NUM_VA_ARGS_LESS_1 + * + * @brief Number of arguments in the variable arguments list minus one. + * + * @note TS_IMPL_NUM_VA_ARGS_LESS_1 to be provided by the implementation + * + * @param ... List of arguments + * @return Number of variadic arguments in the argument list, minus one + */ +#define TS_NUM_VA_ARGS_LESS_1(...) TS_IMPL_NUM_VA_ARGS_LESS_1(__VA_ARGS__) + +/** + * @} + */ + +#endif /* TS_MACRO_H_ */ diff --git a/zephyr/thingset/ts_impl_macro.h b/zephyr/thingset/ts_impl_macro.h new file mode 100644 index 0000000..848935a --- /dev/null +++ b/zephyr/thingset/ts_impl_macro.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Zephyr implementation of ThingSet utility macros. + */ + +#ifndef TS_IMPL_MACRO_H_ +#define TS_IMPL_MACRO_H_ + +#include + +#define TS_IMPL_ARRAY_SIZE(array) ARRAY_SIZE(array) + +#define TS_IMPL_FOR_EACH(F, sep, ...) FOR_EACH(F, sep, __VA_ARGS__) + +#define TS_IMPL_NUM_VA_ARGS_LESS_1(...) NUM_VA_ARGS_LESS_1(__VA_ARGS__) + +#endif /* TS_IMPL_MACRO_H_ */ From 9db2eef954e1efd96b4a89bb7ecb6ab131ae486b Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Mon, 7 Feb 2022 10:37:57 +0100 Subject: [PATCH 04/37] COM prepare - add ThingSet OSAL for endian conversion functions Prepare for ThingSet Communication Context: - Add operating system abstraction for endian conversion functions. - Add Native implementation of endian conversion functions. - Add Zephyr implementation of endian conversion functions. Signed-off-by: Bobby Noelte --- native/thingset/ts_impl_endian.h | 44 ++++++++++ src/ts_endian.h | 133 +++++++++++++++++++++++++++++++ zephyr/thingset/ts_impl_endian.h | 44 ++++++++++ 3 files changed, 221 insertions(+) create mode 100644 native/thingset/ts_impl_endian.h create mode 100644 src/ts_endian.h create mode 100644 zephyr/thingset/ts_impl_endian.h diff --git a/native/thingset/ts_impl_endian.h b/native/thingset/ts_impl_endian.h new file mode 100644 index 0000000..050ba5d --- /dev/null +++ b/native/thingset/ts_impl_endian.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Native implementation of ThingSet endian conversion. + */ + +#ifndef TS_IMPL_ENDIAN_H_ +#define TS_IMPL_ENDIAN_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static inline uint16_t ts_impl_endian_get_be16(const uint8_t src[2]) +{ + return ntohs(*(uint16_t *)src); +} + +static inline uint32_t ts_impl_endian_get_be32(const uint8_t src[4]) +{ + return ntohl(*(uint32_t *)src); +} + +static inline void ts_impl_endian_put_be16(uint16_t val, uint8_t dst[2]) +{ + *(uint16_t *)dst = htons(val); +} + +static inline void ts_impl_endian_put_be32(uint32_t val, uint8_t dst[4]) +{ + *(uint32_t *)dst = htonl(val); +} + +#ifdef __cplusplus +} +#endif + +#endif /* TS_IMPL_ENDIAN_H_ */ diff --git a/src/ts_endian.h b/src/ts_endian.h new file mode 100644 index 0000000..7bb72a3 --- /dev/null +++ b/src/ts_endian.h @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet endian conversion (private interface) + */ + +#ifndef TS_ENDIAN_H_ +#define TS_ENDIAN_H_ + +/** + * @brief ThingSet endian conversion. + * + * @defgroup ts_endian_api_priv ThingSet endian conversion (private interface) + * @{ + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** + * @} + * @addtogroup ts_impl_api + * @{ + */ + +/** + * @brief Implementation for @ref ts_endian_get_be16. + */ +uint16_t ts_impl_endian_get_be16(const uint8_t src[2]); + +/** + * @brief Implementation for @ref ts_endian_get_be32. + */ +uint32_t ts_impl_endian_get_be32(const uint8_t src[4]); + +/** + * @brief Implementation for @ref ts_endian_put_be16. + */ +void ts_impl_endian_put_be16(uint16_t val, uint8_t dst[2]); + +/** + * @brief Implementation for @ref ts_endian_put_be32. + */ +void ts_impl_endian_put_be32(uint32_t val, uint8_t dst[4]); + +/** + * @} + * @addtogroup ts_endian_api_priv + * @{ + */ + + +/** + * @brief Get a 16-bit integer stored in big-endian format. + * + * Get a 16-bit integer, stored in big-endian format in a potentially unaligned memory location, + * and convert it to the host endianness. + * + * @note ts_impl_endian_get_be16() to be provided by the implementation + * + * @param[in] src Pointer to the big-endian 16-bit integer to get. + * @return 16-bit integer in host endianness. + */ +static inline uint16_t ts_endian_get_be16(const uint8_t src[2]) +{ + return ts_impl_endian_get_be16(src); +} + +/** + * @brief Get a 32-bit integer stored in big-endian format. + * + * Get a 32-bit integer, stored in big-endian format in a potentially unaligned memory location, + * and convert it to the host endianness. + * + * @note ts_impl_endian_get_be32() to be provided by the implementation + * + * @param[in] src Pointer to the big-endian 32-bit integer to get. + * @return 16-bit integer in host endianness. + */ +static inline uint32_t ts_endian_get_be32(const uint8_t src[4]) +{ + return ts_impl_endian_get_be32(src); +} + +/** + * @brief Put a 16-bit integer as big-endian to arbitrary location. + * + * Put a 16-bit integer, originally in host endianness, to a potentially unaligned memory location + * in big-endian format. + * + * @note ts_impl_endian_put_be16() to be provided by the implementation + * + * @param[in] val 16-bit integer in host endianness. + * @oaram[out] dst Destination memory address to store the result. + */ +static inline void ts_endian_put_be16(uint16_t val, uint8_t dst[2]) +{ + ts_impl_endian_put_be16(val, dst); +} + +/** + * @brief Put a 32-bit integer as big-endian to arbitrary location. + * + * Put a 32-bit integer, originally in host endianness, to a potentially unaligned memory location + * in big-endian format. + * + * @note ts_impl_endian_put_be32() to be provided by the implementation + * + * @param[in] val 32-bit integer in host endianness. + * @oaram[out] dst Destination memory address to store the result. + */ +static inline void ts_endian_put_be32(uint32_t val, uint8_t dst[4]) +{ + ts_impl_endian_put_be32(val, dst); +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +/** + * @} + */ + + +#endif /* TS_ENDIAN_H_ */ diff --git a/zephyr/thingset/ts_impl_endian.h b/zephyr/thingset/ts_impl_endian.h new file mode 100644 index 0000000..4e1739c --- /dev/null +++ b/zephyr/thingset/ts_impl_endian.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Zephyr implementation of ThingSet endian conversion. + */ + +#ifndef TS_IMPL_ENDIAN_H_ +#define TS_IMPL_ENDIAN_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static inline uint16_t ts_impl_endian_get_be16(const uint8_t src[2]) +{ + return sys_get_be16(src); +} + +static inline uint32_t ts_impl_endian_get_be32(const uint8_t src[4]) +{ + return sys_get_be32(src); +} + +static inline void ts_impl_endian_put_be16(uint16_t val, uint8_t dst[2]) +{ + sys_put_be16(val, dst); +} + +static inline void ts_impl_endian_put_be32(uint32_t val, uint8_t dst[4]) +{ + sys_put_be32(val, dst); +} + +#ifdef __cplusplus +} +#endif + +#endif /* TS_IMPL_ENDIAN_H_ */ From 0b5130d9033f06eb5a6da637cba5520b50ac8b30 Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Wed, 26 Jan 2022 08:00:25 +0100 Subject: [PATCH 05/37] COM prepare - add ThingSet OSAL for logging Prepare for ThingSet Communication Context: - Add operating system abstraction for logging. - Add Native implementation of logging - Add Zephyr implementation of logging Signed-off-by: Bobby Noelte --- native/thingset/ts_impl_log.h | 52 ++++++++++++++ src/ts_log.h | 124 ++++++++++++++++++++++++++++++++++ zephyr/thingset/ts_impl_log.c | 10 +++ zephyr/thingset/ts_impl_log.h | 44 ++++++++++++ 4 files changed, 230 insertions(+) create mode 100644 native/thingset/ts_impl_log.h create mode 100644 src/ts_log.h create mode 100644 zephyr/thingset/ts_impl_log.c create mode 100644 zephyr/thingset/ts_impl_log.h diff --git a/native/thingset/ts_impl_log.h b/native/thingset/ts_impl_log.h new file mode 100644 index 0000000..49f6118 --- /dev/null +++ b/native/thingset/ts_impl_log.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Native implementation of ThingSet logging. + */ + +#ifndef TS_IMPL_LOG_H_ +#define TS_IMPL_LOG_H_ + +#if TS_CONFIG_LOG && TS_CONFIG_LOG_LEVEL > 0 +#define TS_IMPL_LOGE(fmt, ...) printf(fmt "\n", ##__VA_ARGS__) +#else +#define TS_IMPL_LOGE(...) +#endif + +#if TS_CONFIG_LOG && TS_CONFIG_LOG_LEVEL > 1 +#define TS_IMPL_LOGW(fmt, ...) printf(fmt "\n", ##__VA_ARGS__) +#else +#define TS_IMPL_LOGW(...) +#endif + +#if TS_CONFIG_LOG && TS_CONFIG_LOG_LEVEL > 2 +#define TS_IMPL_LOGI(fmt, ...) printf(fmt "\n", ##__VA_ARGS__) +#else +#define TS_IMPL_LOGI(...) +#endif + +#if TS_CONFIG_LOG && TS_CONFIG_LOG_LEVEL > 3 +#define TS_IMPL_LOGD(fmt, ...) printf(fmt "\n", ##__VA_ARGS__) +#else +#define TS_IMPL_LOGD(...) +#endif + + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* None */ + +#ifdef __cplusplus +} +#endif + +#endif /* TS_IMPL_LOG_H_ */ diff --git a/src/ts_log.h b/src/ts_log.h new file mode 100644 index 0000000..240bf0c --- /dev/null +++ b/src/ts_log.h @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet logging (private interface) + */ + +#ifndef TS_LOG_H_ +#define TS_LOG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* ThingSet environment implementation interface */ + +/** + * @addtogroup ts_impl_api + * @{ + */ + +#if TS_DOXYGEN + +/** + * @brief Implementation for @ref TS_LOGE. + */ +#define TS_IMPL_LOGE(...) + +/** + * @brief Implementation for @ref TS_LOGW. + */ +#define TS_IMPL_LOGW(...) + +/** + * @brief Implementation for @ref TS_LOGI. + */ +#define TS_IMPL_LOGI(...) + +/** + * @brief Implementation for @ref TS_LOGD. + */ +#define TS_IMPL_LOGD(...) + +#endif /* TS_DOXYGEN */ + +/** + * @} + */ + +/** + * @brief ThingSet logging. + * + * @defgroup ts_log_api_priv ThingSet logging (private interface) + * @{ + */ + +/** + * @def TS_LOGE + * + * @brief Writes an ERROR level message to the log. + * + * It’s meant to report severe errors, such as those from which it’s not possible to recover. + * + * @note To be provided by the implementation. + * + * @param[in] ... A string optionally containing printf valid conversion specifier, + * followed by as many values as specifiers. + */ +#define TS_LOGE(...) TS_IMPL_LOGE(__VA_ARGS__) + +/** + * @def TS_LOGW + * + * @brief Writes an WARNING level message to the log. + * + * It’s meant to report messages related to unusual situations that are not necessarily errors. + * + * @note To be provided by the implementation. + * + * @param[in] ... A string optionally containing printf valid conversion specifier, + * followed by as many values as specifiers. + */ +#define TS_LOGW(...) TS_IMPL_LOGW(__VA_ARGS__) + +/** + * @def TS_LOGI + * + * @brief Writes an INFO level message to the log. + * + * It’s meant to write generic user oriented messages. + * + * @note To be provided by the implementation. + * + * @param[in] ... A string optionally containing printf valid conversion specifier, + * followed by as many values as specifiers. + */ +#define TS_LOGI(...) TS_IMPL_LOGI(__VA_ARGS__) + +/** + * @def TS_LOGD + * + * @brief Writes an DEBUG level message to the log. + * + * It’s meant to write developer oriented information. + * + * @note To be provided by the implementation. + * + * @param[in] ... A string optionally containing printf valid conversion specifier, + * followed by as many values as specifiers. + */ +#define TS_LOGD(...) TS_IMPL_LOGD(__VA_ARGS__) + +/** + * @} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* TS_LOG_H_ */ diff --git a/zephyr/thingset/ts_impl_log.c b/zephyr/thingset/ts_impl_log.c new file mode 100644 index 0000000..d5791b0 --- /dev/null +++ b/zephyr/thingset/ts_impl_log.c @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Setup logging environment for the ThingSet library */ +#define TS_IMPL_LOG_REGISTER + +#include "../../src/thingset_env.h" + diff --git a/zephyr/thingset/ts_impl_log.h b/zephyr/thingset/ts_impl_log.h new file mode 100644 index 0000000..d533ee5 --- /dev/null +++ b/zephyr/thingset/ts_impl_log.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Zephyr implementation of ThingSet logging. + */ + +#ifndef TS_IMPL_LOG_H_ +#define TS_IMPL_LOG_H_ + +#ifndef LOG_MODULE_NAME +#define LOG_MODULE_NAME thingset +#define LOG_LEVEL CONFIG_THINGSET_LOG_LEVEL +#endif +#include + +#ifdef TS_IMPL_LOG_REGISTER +LOG_MODULE_REGISTER(LOG_MODULE_NAME); +#else +LOG_MODULE_DECLARE(LOG_MODULE_NAME); +#endif + +#define LOG_ALLOC_STR(str) ((str == NULL) ? log_strdup("null") : \ + log_strdup(str)) + +#define TS_IMPL_LOGE(...) LOG_ERR(__VA_ARGS__) +#define TS_IMPL_LOGW(...) LOG_WRN(__VA_ARGS__) +#define TS_IMPL_LOGI(...) LOG_INF(__VA_ARGS__) +#define TS_IMPL_LOGD(...) LOG_DBG(__VA_ARGS__) + +#ifdef __cplusplus +extern "C" { +#endif + +/* None */ + +#ifdef __cplusplus +} +#endif + +#endif /* TS_IMPL_LOG_H_ */ From 7c611a5d31b70a809009eebaba5d4bd5608adbcd Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Sat, 18 Dec 2021 13:20:48 +0100 Subject: [PATCH 06/37] COM prepare - add ThingSet OSAL for time functions Prepare for ThingSet Communication Context: - Add operating system abstraction for time functions. - Add Native implementation of time functions. - Add Zephyr implementation of time functions. Signed-off-by: Bobby Noelte --- native/thingset/ts_impl_time.h | 16 +++++++ src/thingset_time.h | 88 ++++++++++++++++++++++++++++++++++ src/ts_time.c | 63 ++++++++++++++++++++++++ zephyr/thingset/ts_impl_time.h | 25 ++++++++++ 4 files changed, 192 insertions(+) create mode 100644 native/thingset/ts_impl_time.h create mode 100644 src/thingset_time.h create mode 100644 src/ts_time.c create mode 100644 zephyr/thingset/ts_impl_time.h diff --git a/native/thingset/ts_impl_time.h b/native/thingset/ts_impl_time.h new file mode 100644 index 0000000..d1764c4 --- /dev/null +++ b/native/thingset/ts_impl_time.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Native implementation of ThingSet time support. + */ + +#ifndef TS_IMPL_TIME_H_ +#define TS_IMPL_TIME_H_ + +/* Nothing to do here for the moment */ + +#endif /* TS_IMPL_TIME_H_ */ diff --git a/src/thingset_time.h b/src/thingset_time.h new file mode 100644 index 0000000..2309679 --- /dev/null +++ b/src/thingset_time.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2021..2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet time support (public interface). + */ + +#ifndef THINGSET_TIME_H_ +#define THINGSET_TIME_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief ThingSet time support. + * + * Thingset time support is based on posix time. + * + * @defgroup ts_time_api_pub ThingSet time support (public interface) + * @{ + */ + +/** + * @brief Mark infinite timeout delay, + * + * This macro generates a timeout delay that instructs a ThingSet API functions to wait as long + * as necessary to perform the requested operation. + */ +#define THINGSET_TIMEOUT_FOREVER UINT32_MAX + +/** + * @brief Mark immediate timeout delay. + * + * This macro generates a timeout delay that instructs a ThingSet API functions to not wait to + * perform the requested operation. + */ +#define THINGSET_TIMEOUT_IMMEDIATE 0 + +/** + * @brief ThingSet maximum system time value in milliseconds. + * + * This is the last value before roll over. + */ +#define THINGSET_TIME_MS_MAX UINT32_MAX + +/** + * @brief Type of ThingSet system time in milliseconds. + */ +typedef uint32_t thingset_time_ms_t; + +/** + * @brief Get system time in milliseconds. + * + * @return system time in milliseconds + */ +thingset_time_ms_t thingset_time_ms(void); + +/** + * @brief Get elapsed system time in milliseconds. + * + * @param[in] reftime Reference time in milliseconds. + * @return delta in milliseconds + */ +thingset_time_ms_t thingset_time_ms_delta(thingset_time_ms_t reftime); + +/** + * @brief Absolute timeout time as posix timespec. + * + * @param[in] timeout_ms Timeout in milliseconds. + * @return Absolute timeout time as posix timespec. + */ +struct timespec thingset_time_timeout_spec(thingset_time_ms_t timeout_ms); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +/** + * @} + */ + +#endif /* THINGSET_TIME_H_ */ diff --git a/src/ts_time.c b/src/ts_time.c new file mode 100644 index 0000000..996a46d --- /dev/null +++ b/src/ts_time.c @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "thingset_env.h" +#include "thingset_time.h" + +#include + +thingset_time_ms_t thingset_time_ms(void) +{ + long ms; // Milliseconds + time_t s; // Seconds + struct timespec spec; + + clock_gettime(CLOCK_REALTIME, &spec); + + s = spec.tv_sec; + ms = round(spec.tv_nsec / 1.0e6); // Convert nanoseconds to milliseconds + if (ms > 999) { + s++; + ms = 0; + } + return (thingset_time_ms_t)((s * 1000) + ms); +} + +thingset_time_ms_t thingset_time_ms_delta(thingset_time_ms_t reftime) +{ + thingset_time_ms_t curtime = thingset_time_ms(); + + if (curtime >= reftime) { + return curtime - reftime; + } + return (THINGSET_TIME_MS_MAX - reftime) + curtime; +} + +struct timespec thingset_time_timeout_spec(thingset_time_ms_t timeout_ms) +{ + struct timespec timeout; + + if (timeout_ms == THINGSET_TIMEOUT_FOREVER) { + timeout.tv_sec = THINGSET_TIMEOUT_FOREVER; + timeout.tv_nsec = 1e9L - 1; + } + else if (timeout_ms == THINGSET_TIMEOUT_IMMEDIATE) { + /* now */ + clock_gettime(CLOCK_REALTIME, &timeout); + } + else { + clock_gettime(CLOCK_REALTIME, &timeout); + + time_t timeout_sec = timeout_ms / 1000; + timeout.tv_sec += timeout_sec; + timeout.tv_nsec += (timeout_ms - (timeout_sec * 1000)) * 1000; + if (timeout.tv_nsec >= 1000000000L) { + timeout.tv_sec += 1; + timeout.tv_sec -= 1000000000L; + } + } + + return timeout; +} diff --git a/zephyr/thingset/ts_impl_time.h b/zephyr/thingset/ts_impl_time.h new file mode 100644 index 0000000..47a1c1e --- /dev/null +++ b/zephyr/thingset/ts_impl_time.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Zephyr implementation of ThingSet time support. + */ + +#ifndef TS_IMPL_TIME_H_ +#define TS_IMPL_TIME_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define TS_IMPL_ZEPHYR_TIMEOUT_MS(timeout_ms) \ + (timeout_ms == THINGSET_TIMEOUT_FOREVER ? K_FOREVER : K_MSEC(timeout_ms)) + +#ifdef __cplusplus +} +#endif + +#endif /* TS_IMPL_TIME_H_ */ From 0dbdeee424de491a0a670436f06e84dc43eefe3a Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Wed, 29 Dec 2021 15:11:27 +0100 Subject: [PATCH 07/37] COM prepare - add ThingSet OSAL for memory pool Prepare for ThingSet Communication Context: - Add operating system abstraction for memory pool. - Add Native implementation of time memory pool functions. - Add Zephyr implementation of time memory pool functions. Signed-off-by: Bobby Noelte --- native/thingset/ts_impl_mem.h | 99 ++++++++++++++++++++++ src/ts_mem.h | 152 ++++++++++++++++++++++++++++++++++ zephyr/thingset/ts_impl_mem.h | 56 +++++++++++++ 3 files changed, 307 insertions(+) create mode 100644 native/thingset/ts_impl_mem.h create mode 100644 src/ts_mem.h create mode 100644 zephyr/thingset/ts_impl_mem.h diff --git a/native/thingset/ts_impl_mem.h b/native/thingset/ts_impl_mem.h new file mode 100644 index 0000000..9bc405b --- /dev/null +++ b/native/thingset/ts_impl_mem.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Native implementation of ThingSet memory blocks. + */ + +#ifndef TS_IMPL_MEM_H_ +#define TS_IMPL_MEM_H_ + +#include +#include +#include + +#if defined(__cplusplus) && __cplusplus <= 201703L +#ifndef _Atomic +#define _Atomic +#endif +#endif +#include + +#include "../../src/thingset_time.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct ts_impl_mem_block { + size_t size; + uint8_t data[]; +}; + +struct ts_impl_mem { + size_t size; + atomic_size_t alloc; +}; + +#define TS_IMPL_MEM_DEFINE(name, mem_size) struct ts_impl_mem name = { \ + .size = mem_size, \ + .alloc = 0 \ + } + +#define TS_IMPL_MEM_DECLARE(name) extern struct ts_impl_mem name + +static inline int ts_impl_mem_alloc(struct ts_impl_mem *mem_pool, size_t block_size, + thingset_time_ms_t timeout_ms, void **mem_block) +{ + /* Allocate block_size block */ + size_t alloc = atomic_fetch_add(&mem_pool->alloc, block_size); + if ((mem_pool->size - alloc) < block_size) { + /* We do not have enough space */ + (void)atomic_fetch_sub(&mem_pool->alloc, block_size); + return -ENOMEM; + } + + struct ts_impl_mem_block *block = + (struct ts_impl_mem_block *)malloc(sizeof(struct ts_impl_mem_block) + block_size); + if (block == NULL) { + /* We do not have enough space */ + (void)atomic_fetch_sub(&mem_pool->alloc, block_size); + return -ENOMEM; + } + block->size = block_size; + *mem_block = &block->data; + return 0; +} + +static inline int ts_impl_mem_free(struct ts_impl_mem *mem_pool, const void *mem_block) +{ + if (mem_block == 0) { + return 0; + } + + /* + * Cast away const from block. + * This is acceptable here as const defines the potential usage of the block for pseudo + * constant data - but not the characteristic of the block. Blocks are always mutable. + */ + union { + const void *pseudo_immutable_block; + void *mutable_block; + } blk = { .pseudo_immutable_block = mem_block}; + + struct ts_impl_mem_block *block = (struct ts_impl_mem_block *)((uint8_t *)blk.mutable_block - + offsetof(struct ts_impl_mem_block, data)); + (void)atomic_fetch_sub(&mem_pool->alloc, block->size); + free(block); + + return 0; +} + +#ifdef __cplusplus +} +#endif + +#endif /* TS_IMPL_MEM_H_ */ diff --git a/src/ts_mem.h b/src/ts_mem.h new file mode 100644 index 0000000..2597690 --- /dev/null +++ b/src/ts_mem.h @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet memory blocks (private interface) + */ + +#ifndef TS_MEM_H_ +#define TS_MEM_H_ + +#include "thingset_env.h" +#include "thingset_time.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* ThingSet environment implementation interface */ + +/** + * @addtogroup ts_impl_api + * @{ + */ + +#if TS_DOXYGEN + +/** + * @brief Implementation for @ref ts_mem. + */ +#define ts_impl_mem + +/** + * @brief Implementation for @ref TS_MEM_DEFINE. + */ +#define TS_IMPL_MEM_DEFINE(name, mem_size) + +/** + * @brief Implementation for @ref TS_MEM_DECLARE. + */ +#define TS_IMPL_MEM_DECLARE(name) + +#endif /* TS_DOXYGEN */ + +/** + * @brief Implementation for @ref ts_mem_alloc. + */ +int ts_impl_mem_alloc(struct ts_impl_mem *mem_pool, size_t block_size, thingset_time_ms_t timeout_ms, + void **mem_block); + +/** + * @brief Implementation for @ref ts_mem_free. + */ +int ts_impl_mem_free(struct ts_impl_mem *mem_pool, const void *mem_block); + +/** + * @} + */ + +/** + * @brief ThingSet memory blocks. + * + * Memory blocks can be dynamically allocated from a designated memory pool. + * + * A memory pool can be defined and initialized at compile time by calling @ref TS_MEM_DEFINE. + * + * @defgroup ts_mem_api_priv ThingSet memory blocks (private interface) + * @{ + */ + +/** + * @def TS_MEM_DEFINE + * + * @brief Statically define and initialize a memory pool in a public (non-static) scope. + * + * @note To be provided by the implementation. + * + * @param[in] name Name of the memory pool. + * @param[in] mem_size Size of memory region (in bytes). + */ +#define TS_MEM_DEFINE(name, mem_size) TS_IMPL_MEM_DEFINE(name, mem_size) + +/** + * @def TS_MEM_DECLARE + * + * @brief Declare a memory pool in a public (non-static) scope. + * + * @note To be provided by the implementation. + * + * @param[in] name Name of the memory pool. + */ +#define TS_MEM_DECLARE(name) TS_IMPL_MEM_DECLARE(name) + +/** + * @brief ThingSet memory pool structure name. + * + * @note To be provided by the implementation. + */ +#define ts_mem ts_impl_mem + +/** + * @brief Allocate memory block from a memory pool. + * + * Allocates and returns a memory block from the memory region owned by the memory pool. If no + * memory is available immediately, the call will block for the specified timeout (constructed via + * the standard timeout API, or TS_NO_WAIT or TS_FOREVER) waiting for memory to be freed. If the + * allocation cannot be performed by the expiration of the timeout, an error will be returned. + * + * @note To be provided by the implementation. + * + * @param[in] mem_pool Address of the memory pool. + * @param[in] block_size Size of memory block (in bytes). + * @param[in] timeout_ms Maximum time to wait in milliseconds. + * @param[out] mem_block Pointer to memory block. + * @return 0 on success, <0 otherwise. + * @retval -ENOMEM Returned without waiting on no memory available. + * @retval -EAGAIN Waiting period timed out. + * @retval -EINVAL Invalid data supplied. + */ +static inline int ts_mem_alloc(struct ts_mem *mem_pool, size_t block_size, + thingset_time_ms_t timeout_ms, void **mem_block) +{ + return ts_impl_mem_alloc(mem_pool, block_size, timeout_ms, mem_block); +} + +/** + * @brief Free memory block allocated from a memory pool. + * + * Release a previously allocated memory block back to its associated memory pool. + * + * @note To be provided by the implementation. + * + * @param[in] mem_pool Address of the memory pool. + * @param[in] mem_block Pointer to memory block. + * @return 0 on success, <0 otherwise + */ +static inline int ts_mem_free(struct ts_mem *mem_pool, const void *mem_block) +{ + return ts_impl_mem_free(mem_pool, mem_block); +} + +/** + * @} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* TS_MEM_H_ */ diff --git a/zephyr/thingset/ts_impl_mem.h b/zephyr/thingset/ts_impl_mem.h new file mode 100644 index 0000000..781a9dd --- /dev/null +++ b/zephyr/thingset/ts_impl_mem.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Zephyr implementation of ThingSet memory blocks. + */ + +#ifndef TS_IMPL_MEM_H_ +#define TS_IMPL_MEM_H_ + +#include "../../src/thingset_time.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ts_impl_mem k_heap + +#define TS_IMPL_MEM_DEFINE(name, mem_size) K_HEAP_DEFINE(name, Z_HEAP_MIN_SIZE + mem_size) + +#define TS_IMPL_MEM_DECLARE(name) extern struct ts_impl_mem name + +static inline int ts_impl_mem_alloc(struct ts_impl_mem *mem_pool, size_t block_size, + thingset_time_ms_t timeout_ms, void **mem_block) +{ + *mem_block = k_heap_aligned_alloc(mem_pool, 4, block_size, + TS_IMPL_ZEPHYR_TIMEOUT_MS(timeout_ms)); + return (*mem_block == NULL) ? -ENOMEM : 0; +} + +static inline int ts_impl_mem_free(struct ts_impl_mem *mem_pool, const void *mem_block) +{ + /* + * Cast away const from block. + * This is acceptable here as const defines the potential usage of the block for pseudo + * constant data - but not the characteristic of the block. Blocks are always mutable. + */ + union { + const void *pseudo_immutable_block; + void *mutable_block; + } block = { .pseudo_immutable_block = mem_block}; + + k_heap_free(mem_pool, block.mutable_block); + return 0; +} + +#ifdef __cplusplus +} +#endif + +#endif /* TS_IMPL_MEM_H_ */ From ebed1a32f8d7847c66712724baf6789a2094ff5b Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Sat, 18 Dec 2021 18:22:43 +0100 Subject: [PATCH 08/37] COM prepare - add ThingSet OSAL for communication buffers Prepare for ThingSet Communication Context: - Add operating system abstraction for communication buffers. - Add Native implementation of communication buffers - Add Zephyr implementation of communication buffers Communication buffers are a core concept of how ThingSet passes communication data around. Every device holds a pool of buffers that can be utilized on the device. The buffers and the buffers pool are modeled after (or uses) the Zephyr network buffers. By using a single buffer pool the buffers can easily be transfered to and from any ThingSet context. Signed-off-by: Bobby Noelte --- native/thingset/ts_impl_buf.c | 316 ++++++++++++++++++++++ native/thingset/ts_impl_buf.h | 130 +++++++++ src/ts_buf.h | 489 ++++++++++++++++++++++++++++++++++ zephyr/thingset/ts_impl_buf.c | 82 ++++++ zephyr/thingset/ts_impl_buf.h | 117 ++++++++ 5 files changed, 1134 insertions(+) create mode 100644 native/thingset/ts_impl_buf.c create mode 100644 native/thingset/ts_impl_buf.h create mode 100644 src/ts_buf.h create mode 100644 zephyr/thingset/ts_impl_buf.c create mode 100644 zephyr/thingset/ts_impl_buf.h diff --git a/native/thingset/ts_impl_buf.c b/native/thingset/ts_impl_buf.c new file mode 100644 index 0000000..a974eab --- /dev/null +++ b/native/thingset/ts_impl_buf.c @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2015-2019 Intel Corporation + * Copyright (c) 2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @file + * @brief Native implementation of ThingSet communication buffers. + */ + +#include + +#include "../../src/thingset_env.h" +#include "../../src/thingset_time.h" + +#include "../../src/ts_macro.h" +#include "../../src/ts_log.h" + +#include "ts_impl_buf.h" + +struct ts_impl_buf ts_impl_bufs[TS_CONFIG_BUF_COUNT]; + +int ts_impl_buf_alloc(uint16_t size, thingset_time_ms_t timeout_ms, struct ts_impl_buf **buffer) +{ + struct ts_impl_buf *buf = NULL; + + for (unsigned int i = 0; i < TS_CONFIG_BUF_COUNT; i++) { + int_least8_t free_ref = 0; /* Only 0 indicates free, < 0 indicates concurrent free */ + if (atomic_compare_exchange_weak(&ts_impl_bufs[i].ref, &free_ref, (int_least8_t)(1))) { + buf = &ts_impl_bufs[i]; + break; + } + } + + if (!buf) { + TS_LOGE("%s():%d: Failed to get free buffer", __func__, __LINE__); + return -ENOMEM; + } + + TS_LOGD("buf %p allocated", buf); + + if (size) { + buf->__buf = malloc(size); + if (!buf->__buf) { + TS_LOGE("%s():%d: Failed to allocate data", __func__, __LINE__); + (void)atomic_fetch_sub(&buf->ref, (int_least8_t)(1U)); + return -ENOMEM; + } + } + else { + buf->__buf = NULL; + } + + buf->flags = 0; + buf->size = size; + + ts_impl_buf_reset(buf); + + *buffer = buf; + return 0; +} + +int ts_impl_buf_alloc_with_data(void *data, uint16_t size, thingset_time_ms_t timeout_ms, + struct ts_impl_buf **buffer) +{ + struct ts_impl_buf *buf = NULL; + int_least8_t free_ref = 0; /* Only 0 indicates free, < 0 indicates concurrent free */ + + for (unsigned int i = 0; i < TS_CONFIG_BUF_COUNT; i++) { + if (atomic_compare_exchange_weak(&ts_impl_bufs[i].ref, &free_ref, (int_least8_t)(1))) { + buf = &ts_impl_bufs[i]; + break; + } + } + + if (!buf) { + TS_LOGE("%s():%d: Failed to get free buffer", __func__, __LINE__); + return -ENOMEM; + } + + TS_LOGD("buf %p allocated", buf); + + if (size) { + buf->__buf = data; + if (!buf->__buf) { + TS_LOGE("%s():%d: Failed to provide data", __func__, __LINE__); + (void)atomic_fetch_sub(&buf->ref, (int_least8_t)(1U)); + return -ENOMEM; + } + } + else { + buf->__buf = NULL; + } + + buf->next = NULL; + buf->flags = TS_IMPL_BUF_EXTERNAL_DATA; + buf->size = size; + + ts_impl_buf_reset(buf); + + *buffer = buf; + return 0; +} + +int ts_impl_buf_unref(struct ts_impl_buf *buffer) +{ + TS_LOGD("buf %p unref (%d)", buffer, (int)buffer->ref); + + TS_ASSERT(buffer != NULL, "NATIVE BUF: %s on invalid buffer pointer NULL", __func__); + + void *free_buf = NULL; + if (!(buffer->flags & TS_IMPL_BUF_EXTERNAL_DATA)) { + free_buf = buffer->__buf; + } + + int_least8_t ref = atomic_fetch_sub(&buffer->ref, (int_least8_t)(1U)); + if (ref == 0) { + (void)atomic_fetch_add(&buffer->ref, (int_least8_t)(1)); + TS_LOGE("%s():%d: buf %p double free", __func__, __LINE__, buffer); + return -EINVAL; + } + + if ((ref == 1) && (free_buf != NULL)) { + free(free_buf); + } + + return 0; +} + +int ts_impl_buf_ref(struct ts_impl_buf *buffer) +{ + TS_LOGD("buf %p ref (%d)", buffer, (int)buffer->ref); + + TS_ASSERT(buffer != NULL, "NATIVE BUF: %s on invalid buffer pointer NULL", __func__); + + int_least8_t ref = atomic_fetch_add(&buffer->ref, (int_least8_t)(1)); + if (ref == 0) { + (void)atomic_fetch_sub(&buffer->ref, (int_least8_t)(1)); + TS_LOGE("%s():%d: buf %p ref on freed buffer", __func__, __LINE__, buffer); + return -ENOMEM; + } + + return 0; +} + +uint8_t *ts_impl_buf_tail(struct ts_impl_buf *buffer) +{ + TS_ASSERT(buffer != NULL, "NATIVE BUF: %s on invalid buffer pointer NULL", __func__); + + return &buffer->data[buffer->len]; +} + +void ts_impl_buf_reserve(struct ts_impl_buf *buffer, uint16_t reserve) +{ + TS_LOGD("buf %p reserve %u", buffer, (unsigned int)reserve); + + TS_ASSERT(buffer != NULL, "NATIVE BUF: %s on invalid buffer pointer NULL", __func__); + + buffer->data = buffer->__buf + reserve; +} + +void ts_impl_buf_reset(struct ts_impl_buf *buffer) +{ + TS_LOGD("buf %p reset", buffer); + + TS_ASSERT(buffer != NULL, "NATIVE BUF: %s on invalid buffer pointer NULL", __func__); + + buffer->data = buffer->__buf; + buffer->len = 0; +} + +int ts_impl_buf_clone(struct ts_impl_buf *buffer, thingset_time_ms_t timeout_ms, + struct ts_impl_buf **clone) +{ + TS_LOGD("buf %p clone", buffer); + + TS_ASSERT(buffer != NULL, "NATIVE BUF: %s on invalid buffer pointer NULL", __func__); + + struct ts_impl_buf *buf; + + buf = malloc(sizeof(struct ts_impl_buf)); + if (!buf) { + return -ENOMEM; + } + + buf->size = buffer->size; + buf->len = buffer->len; + buf->flags = buffer->flags; + + /* Make a copy. */ + if (buf->flags & TS_IMPL_BUF_EXTERNAL_DATA) { + buf->__buf = buffer->__buf; + buf->data = buffer->data; + } + else if (buf->size) { + buf->__buf = malloc(buf->size); + if (!buf->__buf) { + TS_LOGE("%s():%d: Failed to allocate data", __func__, __LINE__); + free(buf); + return -ENOMEM; + } + memcpy(buf->__buf, buffer->__buf, buf->size); + buf->data = buf->__buf + ts_impl_buf_headroom(buffer); + } + else { + buf->__buf = NULL; + buf->data = NULL; + } + + *clone = buf; + return 0; +} + +uint8_t *ts_impl_buf_add(struct ts_impl_buf *buffer, uint16_t len) +{ + TS_ASSERT(buffer != NULL, "NATIVE BUF: %s on invalid buffer pointer NULL", __func__); + + uint8_t *tail = ts_impl_buf_tail(buffer); + + TS_LOGD("buf %p len %u", buffer, (unsigned int)len); + + TS_ASSERT(ts_impl_buf_tailroom(buffer) >= len, "NATIVE BUF: %s not enough tailroom (%u > %u)", + __func__, (unsigned int)len, (unsigned int)ts_impl_buf_tailroom(buffer)); + + buffer->len += len; + return tail; +} + +uint8_t *ts_impl_buf_add_mem(struct ts_impl_buf *buffer, const uint8_t *data, uint16_t len) +{ + TS_ASSERT(buffer != NULL, "NATIVE BUF: %s on invalid buffer pointer NULL", __func__); + + uint8_t *tail = ts_impl_buf_add(buffer, len); + + if (tail != NULL) { + memcpy(tail, data, len); + } + return tail; +} + +uint8_t *ts_impl_buf_add_u8(struct ts_impl_buf *buffer, uint8_t val) +{ + TS_ASSERT(buffer != NULL, "NATIVE BUF: %s on invalid buffer pointer NULL", __func__); + + uint8_t *u8; + + TS_LOGD("buf %p val 0x%02x", buffer, val); + + u8 = ts_impl_buf_add(buffer, 1); + *u8 = val; + + return u8; +} + +uint8_t *ts_impl_buf_remove(struct ts_impl_buf *buffer, uint16_t len) +{ + TS_ASSERT(buffer != NULL, "NATIVE BUF: %s on invalid buffer pointer NULL", __func__); + + TS_LOGD("buf %p len %u", buffer, (unsigned int)len); + + TS_ASSERT(buffer->len >= len, "NATIVE BUF: %s not enough length (%u > %u)", + __func__, (unsigned int)len, (unsigned int)buffer->len); + + buffer->len -= len; + return ts_impl_buf_tail(buffer); +} + +uint8_t *ts_impl_buf_push(struct ts_impl_buf *buffer, uint16_t len) +{ + TS_ASSERT(buffer != NULL, "NATIVE BUF: %s on invalid buffer pointer NULL", __func__); + + TS_LOGD("buf %p len %u", buffer, (unsigned int)len); + + TS_ASSERT(ts_impl_buf_headroom(buffer) >= len, "NATIVE BUF: %s not enough headroom (%u > %u)", + __func__, (unsigned int)len, (unsigned int)ts_impl_buf_headroom(buffer)); + + buffer->data -= len; + buffer->len += len; + return buffer->data; +} + +uint8_t *ts_impl_buf_pull(struct ts_impl_buf *buffer, uint16_t len) +{ + TS_ASSERT(buffer != NULL, "NATIVE BUF: %s on invalid buffer pointer NULL", __func__); + + TS_LOGD("buf %p len %u", buffer, (unsigned int)len); + + TS_ASSERT(buffer->len >= len, "NATIVE BUF: %s not enough length (%u > %u)", + __func__, (unsigned int)len, (unsigned int)buffer->len); + + buffer->len -= len; + return buffer->data += len; +} + +int ts_impl_buf_free_all(void) +{ + for (unsigned int i = 0; i < TS_CONFIG_BUF_COUNT; i++) { + struct ts_impl_buf *buffer = &ts_impl_bufs[i]; + /* Block buffer */ + int_least8_t ref = atomic_exchange(&buffer->ref, (int_least8_t)(INT8_MIN)); + + if ((ref > 0) && (buffer->__buf != NULL) && !(buffer->flags & TS_IMPL_BUF_EXTERNAL_DATA)) { + free(buffer->__buf); + } + + buffer->next = NULL; + buffer->flags = 0; + buffer->size = 0; + buffer->len = 0; + buffer->data = NULL; + buffer->__buf = NULL; + + atomic_exchange(&buffer->ref, (int_least8_t)(0)); + } + return 0; +} diff --git a/native/thingset/ts_impl_buf.h b/native/thingset/ts_impl_buf.h new file mode 100644 index 0000000..cf3a1b1 --- /dev/null +++ b/native/thingset/ts_impl_buf.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Native implementation of ThingSet communication buffers. + */ + +#ifndef TS_IMPL_BUF_H_ +#define TS_IMPL_BUF_H_ + +#include +#include + +#include "../../src/thingset_time.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Flag indicating that the buffer's associated data pointer, points to + * externally allocated memory. + * + * Therefore once ref goes down to zero, the + * pointed data will not need to be deallocated. This never needs to be + * explicitly set or unset by the net_buf API user. Such net_buf is + * exclusively instantiated via net_buf_alloc_with_data() function. + * Reference count mechanism however will behave the same way, and ref + * count going to 0 will free the net_buf but not the data pointer in it. + */ +#define TS_IMPL_BUF_EXTERNAL_DATA (0x02U) + +/** + * @brief Native communication buffer representation. + */ +struct ts_impl_buf { + /** @brief Next buffer pointer to be used for buffer queues */ + struct ts_impl_buf *next; + + /** @brief Reference count. */ + atomic_int_least8_t ref; + + /** @brief Bit-field of buffer flags. */ + uint8_t flags; + + /** @brief Pointer to the start of data in the buffer. */ + uint8_t *data; + + /** + * @brief Length of the data behind the data pointer. + * + * To determine the max length, use ts_impl_buf_max_len(), not #size! + */ + uint16_t len; + + /** @brief Amount of data that ts_impl_buf#__buf can store. */ + uint16_t size; + + /** + * @brief Start of the data storage. Not to be accessed directly + * (the data pointer should be used instead). + */ + uint8_t *__buf; +}; + +int ts_impl_buf_alloc(uint16_t size, thingset_time_ms_t timeout_ms, struct ts_impl_buf **buffer); + +int ts_impl_buf_alloc_with_data(void *data, uint16_t size, thingset_time_ms_t timeout_ms, + struct ts_impl_buf **buffer); + +int ts_impl_buf_unref(struct ts_impl_buf *buffer); + +int ts_impl_buf_ref(struct ts_impl_buf *buffer); + +static inline uint16_t ts_impl_buf_size(struct ts_impl_buf *buffer) +{ + return buffer->size; +} + +static inline uint16_t ts_impl_buf_len(struct ts_impl_buf *buffer) +{ + return buffer->len; +} + +static uint16_t ts_impl_buf_headroom(struct ts_impl_buf *buffer) +{ + return buffer->data - buffer->__buf; +} + +static inline uint16_t ts_impl_buf_tailroom(struct ts_impl_buf *buffer) +{ + return buffer->size - ts_impl_buf_headroom(buffer) - buffer->len; +} + +static inline uint8_t *ts_impl_buf_data(struct ts_impl_buf *buffer) +{ + return buffer->data; +} + +uint8_t *ts_impl_buf_tail(struct ts_impl_buf *buffer); + +void ts_impl_buf_reserve(struct ts_impl_buf *buffer, uint16_t reserve); + +void ts_impl_buf_reset(struct ts_impl_buf *buffer); + +int ts_impl_buf_clone(struct ts_impl_buf *buffer, thingset_time_ms_t timeout_ms, + struct ts_impl_buf **clone); + +uint8_t *ts_impl_buf_add(struct ts_impl_buf *buffer, uint16_t len); + +uint8_t *ts_impl_buf_add_mem(struct ts_impl_buf *buffer, const uint8_t *data, uint16_t len); + +uint8_t *ts_impl_buf_add_u8(struct ts_impl_buf *buffer, uint8_t val); + +uint8_t *ts_impl_buf_remove(struct ts_impl_buf *buffer, uint16_t len); + +uint8_t *ts_impl_buf_push(struct ts_impl_buf *buffer, uint16_t len); + +uint8_t *ts_impl_buf_pull(struct ts_impl_buf *buffer, uint16_t len); + +int ts_impl_buf_free_all(void); + +#ifdef __cplusplus +} +#endif + +#endif /* TS_IMPL_BUF_H_ */ diff --git a/src/ts_buf.h b/src/ts_buf.h new file mode 100644 index 0000000..3eeb001 --- /dev/null +++ b/src/ts_buf.h @@ -0,0 +1,489 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet communication buffers (private interface) + */ + +#ifndef TS_BUF_H_ +#define TS_BUF_H_ + +#include "thingset_env.h" +#include "thingset_time.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* ThingSet environment implementation interface */ + +/** + * @addtogroup ts_impl_api + * @{ + */ + +#if TS_DOXYGEN + +/** + * @brief Implementation for @ref ts_buf. + */ +#define ts_impl_buf + +#endif /* TS_DOXYGEN */ + +/** + * @brief Implementation for @ref ts_buf_alloc. + */ +int ts_impl_buf_alloc(uint16_t size, thingset_time_ms_t timeout_ms, struct ts_impl_buf **buffer); + +/** + * @brief Implementation for @ref ts_buf_alloc_with_data. + */ +int ts_impl_buf_alloc_with_data(void *data, uint16_t size, thingset_time_ms_t timeout_ms, + struct ts_impl_buf **buffer); + +/** + * @brief Implementation for @ref ts_buf_unref. + */ +int ts_impl_buf_unref(struct ts_impl_buf *buffer); + +/** + * @brief Implementation for @ref ts_buf_ref. + */ +int ts_impl_buf_ref(struct ts_impl_buf *buffer); + +/** + * @brief Implementation for @ref ts_buf_size. + */ +uint16_t ts_impl_buf_size(struct ts_impl_buf *buffer); + +/** + * @brief Implementation for @ref ts_buf_len. + */ +uint16_t ts_impl_buf_len(struct ts_impl_buf *buffer); + +/** + * @brief Implementation for @ref ts_buf_headroom. + */ +uint16_t ts_impl_buf_headroom(struct ts_impl_buf *buffer); + +/** + * @brief Implementation for @ref ts_buf_tailroom. + */ +uint16_t ts_impl_buf_tailroom(struct ts_impl_buf *buffer); + +/** + * @brief Implementation for @ref ts_buf_data. + */ +uint8_t *ts_impl_buf_data(struct ts_impl_buf *buffer); + +/** + * @brief Implementation for @ref ts_buf_unref. + */ +uint8_t *ts_impl_buf_tail(struct ts_impl_buf *buffer); + +/** + * @brief Implementation for @ref ts_buf_reserve. + */ +void ts_impl_buf_reserve(struct ts_impl_buf *buffer, uint16_t reserve); + +/** + * @brief Implementation for @ref ts_buf_reset. + */ +void ts_impl_buf_reset(struct ts_impl_buf *buffer); + +/** + * @brief Implementation for @ref ts_buf_clone. + */ +int ts_impl_buf_clone(struct ts_impl_buf *buffer, thingset_time_ms_t timeout_ms, struct ts_impl_buf **clone); + +/** + * @brief Implementation for @ref ts_buf_add. + */ +uint8_t *ts_impl_buf_add(struct ts_impl_buf *buffer, uint16_t len); + +/** + * @brief Implementation for @ref ts_buf_add_mem. + */ +uint8_t *ts_impl_buf_add_mem(struct ts_impl_buf *buffer, const uint8_t *data, uint16_t len); + +/** + * @brief Implementation for @ref ts_buf_add_u8. + */ +uint8_t *ts_impl_buf_add_u8(struct ts_impl_buf *buffer, uint8_t val); + +/** + * @brief Implementation for @ref ts_buf_remove. + */ +uint8_t *ts_impl_buf_remove(struct ts_impl_buf *buffer, uint16_t len); + +/** + * @brief Implementation for @ref ts_buf_pull. + */ +uint8_t *ts_impl_buf_push(struct ts_impl_buf *buffer, uint16_t len); + +/** + * @brief Implementation for @ref ts_buf_unref. + */ +uint8_t *ts_impl_buf_pull(struct ts_impl_buf *buffer, uint16_t len); + +/** + * @brief Implementation for @ref ts_buf_free_all. + */ +int ts_impl_buf_free_all(void); + +/** + * @} + */ + +/** + * @brief ThingSet communication buffers. + * + * Buffers are divided into three buffer parts: + * - headroom + * - data + * - tailroom + * + * All of the parts may be of zero size. + * + * @defgroup ts_buf_api_priv ThingSet communication buffers (private interface) + * @{ + */ + +/** + * @brief ThingSet communication buffer structure name. + * + * @note To be provided by the implementation. + */ +#define ts_buf ts_impl_buf + +/** + * @brief Allocate a ThingSet communication buffer from the buffer pool. + * + * The communication buffer is allocated with reference count set to 1. + * + * @note To be provided by the implementation. + * + * @param[in] size The size of the buffer + * @param[in] timeout_ms maximum time to wait in milliseconds. + * @param[out] buffer Pointer to buffer + * @return 0 on success, <0 otherwise + */ +static inline int ts_buf_alloc(uint16_t size, thingset_time_ms_t timeout_ms, struct ts_buf **buffer) +{ + return ts_impl_buf_alloc(size, timeout_ms, buffer); +} + + +/** + * @brief Allocate a ThingSet communication buffer from the buffer pool with external data pointer. + * + * Allocate a new communication buffer from a pool, where the data pointer comes from the user and + * not from the pool. + * + * The communication buffer is allocated with reference count set to 1. + * + * @note To be provided by the implementation. + * + * @param[in] data Pointer to external data. + * @param[in] size The size of the buffer. + * @param[in] timeout_ms maximum time to wait in milliseconds. + * @param[out] buffer Pointer to buffer. + * @return 0 on success, <0 otherwise + */ +static inline int ts_buf_alloc_with_data(void *data, uint16_t size, thingset_time_ms_t timeout_ms, + struct ts_buf **buffer) +{ + return ts_impl_buf_alloc_with_data(data, size, timeout_ms, buffer); +} + +/** + * @brief Mark ThingSet communication buffer unused. + * + * Decrement the reference count of a buffer. The buffer is put back into the + * pool if the reference count reaches zero. + * + * @note The buffer shall not be accessed after it is marked unused. + * + * @note To be provided by the implementation. + * + * @param[in] buffer Pointer to the buffer. + * @return 0 on success, <0 otherwise + */ +static inline int ts_buf_unref(struct ts_buf *buffer) +{ + return ts_impl_buf_unref(buffer); +} + +/** + * @brief Mark ThingSet communication buffer used. + * + * Increment the reference count of a buffer. + * + * @note To be provided by the implementation. + * + * @param[in] buffer Pointer to the buffer. + * @return 0 on success, <0 otherwise + */ +static inline int ts_buf_ref(struct ts_buf *buffer) +{ + return ts_impl_buf_ref(buffer); +} + +/** + * @brief Amount of data that this ThingSet communication buffer can store. + * + * @note To be provided by the implementation. + * + * @param[in] buffer Pointer to the buffer. + * @return Amount of data that this buffer can store. + */ +static inline uint16_t ts_buf_size(struct ts_buf *buffer) +{ + return ts_impl_buf_size(buffer); +} + +/** + * @brief Amount of data that is stored in this ThingSet communication buffer. + * + * @note To be provided by the implementation. + * + * @param[in] buffer Pointer to the buffer. + * @return Amount of data that is stored in this buffer. + */ +static inline uint16_t ts_buf_len(struct ts_buf *buffer) +{ + return ts_impl_buf_len(buffer); +} + +/** + * @brief Size of buffer headroom. + * + * Free space at the beginning of the buffer. + * + * @note To be provided by the implementation. + * + * @param[in] buffer Pointer to the buffer. + * @return Number of bytes available at the beginning of the buffer. + */ +static inline uint16_t ts_buf_headroom(struct ts_buf *buffer) +{ + return ts_impl_buf_headroom(buffer); +} + +/** + * @brief Size of buffer tailroom. + * + * Free space at the end of the buffer. + * + * @note To be provided by the implementation. + * + * @param[in] buffer Pointer to the buffer. + * @return Number of bytes available at the end of the buffer. + */ +static inline uint16_t ts_buf_tailroom(struct ts_buf *buffer) +{ + return ts_impl_buf_tailroom(buffer); +} + +/** + * @brief Get the data pointer for a ThingSet communication buffer. + * + * Data pointer points to the first data stored in buffer. + * + * @note To be provided by the implementation. + * + * @param[in] buffer Pointer to the buffer. + * @return data pointer of buffer. + */ +static inline uint8_t *ts_buf_data(struct ts_buf *buffer) +{ + return ts_impl_buf_data(buffer); +} + +/** + * @brief Get the tail pointer for a ThingSet communication buffer. + * + * Tail pointer points after the last data stored in buffer. + * + * @note To be provided by the implementation. + * + * @param[in] buffer Pointer to the buffer. + * @return tail pointer of buffer. + */ +static inline uint8_t *ts_buf_tail(struct ts_buf *buffer) +{ + return ts_impl_buf_tail(buffer); +} + +/** + * @brief Initialize buffer with the given headroom. + * + * @note The buffer is not expected to contain any data when this API is called. + * + * @note To be provided by the implementation. + * + * @param[in] buffer Pointer to the buffer. + * @return tail pointer of buffer. + */ +static inline void ts_buf_reserve(struct ts_buf *buffer, uint16_t reserve) +{ + ts_impl_buf_reserve(buffer, reserve); +} + +/** + * @brief Reset buffer. + * + * Reset buffer data so the buffer can be reused for other purposes. + * + * @note To be provided by the implementation. + * + * @param[in] buffer Pointer to the buffer. + */ +static inline void ts_buf_reset(struct ts_buf *buffer) +{ + ts_impl_buf_reset(buffer); +} + +/** + * @brief Clone buffer. + * + * Duplicate given buffer including any data currently stored. + * + * @param[in] buffer Pointer to the source buffer. + * @param[in] timeout_ms maximum time to wait in milliseconds. + * @param[out] clone Pointer to the cloned buffer. + * @return The original tail of the destinatio buffer, before incremented by new data. + */ +static inline int ts_buf_clone(struct ts_buf *buffer, thingset_time_ms_t timeout_ms, + struct ts_buf **clone) +{ + return ts_impl_buf_clone(buffer, timeout_ms, clone); +} + +/** + * @brief Prepare data to be added at the end of the buffer. + * + * Increments the data length of a buffer to account for more data at the end. + * + * @note To be provided by the implementation. + * + * @param[in] buffer Pointer to the buffer. + * @param[in] len Number of bytes to increment the length with. + * @return The original tail of the buffer, before incremented by len. + */ +static inline uint8_t *ts_buf_add(struct ts_buf *buffer, uint16_t len) +{ + return ts_impl_buf_add(buffer, len); +} + +/** + * @brief Copy the given number of bytes to the end of the buffer. + * + * Increments the data length of the buffer to account for more data at the end. + * + * @note To be provided by the implementation. + * + * @param[in] buffer Pointer to the buffer. + * @param[in] data Location of data to be added. + * @param[in] len Number of bytes to by added. + * @return The original tail of the buffer, before incremented by len. + */ +static inline uint8_t *ts_buf_add_mem(struct ts_buf *buffer, const uint8_t *data, uint16_t len) +{ + return ts_impl_buf_add_mem(buffer, data, len); +} + +/** + * @brief Add (8-bit) byte at the end of the buffer. + * + * Increments the data length of the buffer to account for more data at the end. + * + * @note ts_impl_buf_add_u8 to be provided by the implementation. + * + * @param[in] buffer Pointer to the buffer. + * @param[in] val Byte value to be added. + * @return Pointer to the value added. + */ +static inline uint8_t *ts_buf_add_u8(struct ts_buf *buffer, uint8_t val) +{ + return ts_impl_buf_add_u8(buffer, val); +} + +/** + * @brief Remove data from the end of the buffer. + * + * Removes data from the end of the buffer by modifying the buffer length (not the size). + * + * @note To be provided by the implementation. + * + * @param[in] buffer Pointer to the buffer. + * @param[in] len Number of bytes to remove. + * @return The new tail of the buffer. + */ +static inline uint8_t *ts_buf_remove(struct ts_buf *buffer, uint16_t len) +{ + return ts_impl_buf_remove(buffer, len); +} + +/** + * @brief Prepare data to be added at start of the buffer. + * + * Modifies the data pointer and buffer length to account for more data in the beginning of the + * buffer. + * + * @note To be provided by the implementation. + * + * @param[in] buffer Pointer to the buffer. + * @param[in] len Number of bytes to add to the beginning. + * @return The new beginning of the buffer data. + */ +static inline uint8_t *ts_buf_push(struct ts_buf *buffer, uint16_t len) +{ + return ts_impl_buf_push(buffer, len); +} + +/** + * @brief Remove data from the beginning of the buffer. + * + * Removes data from the beginning of the buffer by modifying the data pointer and buffer length. + * + * @note To be provided by the implementation. + * + * @param[in] buffer Pointer to the buffer. + * @param[in] len Number of bytes to remove. + * @return New beginning of the buffer data. + */ +static inline uint8_t *ts_buf_pull(struct ts_buf *buffer, uint16_t len) +{ + return ts_impl_buf_pull(buffer, len); +} + +/** + * @brief Mark all ThingSet communication buffers unused. + * + * Set reference count of all buffers to zero. Put back all buffers into the pool. + * + * @note Any buffers shall not be accessed after this function was executed. + * + * @note To be provided by the implementation. + * + * @return 0 on success, <0 otherwise + */ +static inline int ts_buf_free_all(void) +{ + return ts_impl_buf_free_all(); +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +/** + * @} + */ + +#endif /* TS_BUF_H_ */ diff --git a/zephyr/thingset/ts_impl_buf.c b/zephyr/thingset/ts_impl_buf.c new file mode 100644 index 0000000..7f8f3ec --- /dev/null +++ b/zephyr/thingset/ts_impl_buf.c @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "../../src/thingset_env.h" + +#include +#include + +#include "../../src/ts_buf.h" +#include "../../src/ts_log.h" + +/** + * @brief Device's communication buffers pool. + * + * Pool of ThingSet communication buffers used by (all) ThingSet communication of the device. + */ +NET_BUF_POOL_VAR_DEFINE(ts_buf_pool, TS_CONFIG_BUF_COUNT, TS_CONFIG_BUF_DATA_SIZE, 0, NULL); + + +int ts_impl_buf_alloc(uint16_t size, thingset_time_ms_t timeout_ms, struct ts_impl_buf **buffer) +{ + struct net_buf *buf = net_buf_alloc_len(&ts_buf_pool, (size_t)size, + TS_IMPL_ZEPHYR_TIMEOUT_MS(timeout_ms)); + if (buf == NULL) { + return -ENOMEM; + } + *buffer = buf; + return 0; +} + +int ts_impl_buf_alloc_with_data(void *data, uint16_t size, thingset_time_ms_t timeout_ms, + struct ts_impl_buf **buffer) +{ + struct net_buf *buf = net_buf_alloc_with_data(&ts_buf_pool, data, (size_t)size, + TS_IMPL_ZEPHYR_TIMEOUT_MS(timeout_ms)); + if (buf == NULL) { + return -ENOMEM; + } + *buffer = buf; + return 0; +} + +int ts_impl_buf_unref(struct ts_impl_buf *buffer) +{ + if (buffer->ref == 0) { + /* + * Add some extra safety here as it is not tracked by Zephyr. + * Will not work in all cases (aka. re-allocation of same message + * in between). + */ + TS_LOGE("ThingSet marks already unused buffer unused."); + return -EALREADY; + } + + net_buf_unref(buffer); + + return 0; +} + +int ts_impl_buf_clone(struct ts_impl_buf *buffer, thingset_time_ms_t timeout_ms, + struct ts_impl_buf **clone) +{ + struct net_buf *cbuf = net_buf_clone(buffer, TS_IMPL_ZEPHYR_TIMEOUT_MS(timeout_ms)); + if (cbuf == NULL) { + return -ETIMEDOUT; + } + *clone = cbuf; + return 0; +} + +int ts_impl_buf_free_all(void) +{ + for (int buf_idx = 0; buf_idx < (ts_buf_pool.buf_count - ts_buf_pool.uninit_count); buf_idx++) { + struct net_buf *buf = &ts_buf_pool.__bufs[buf_idx]; + while(buf->ref > 0) { + net_buf_unref(buf); + } + } + return 0; +} diff --git a/zephyr/thingset/ts_impl_buf.h b/zephyr/thingset/ts_impl_buf.h new file mode 100644 index 0000000..8c57608 --- /dev/null +++ b/zephyr/thingset/ts_impl_buf.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Zephyr implementation of ThingSet communication buffers. + */ + +#ifndef TS_IMPL_BUF_H_ +#define TS_IMPL_BUF_H_ + +#include "../../src/thingset_time.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ts_impl_buf net_buf + +int ts_impl_buf_alloc(uint16_t size, thingset_time_ms_t timeout_ms, struct ts_impl_buf **buffer); + +int ts_impl_buf_alloc_with_data(void *data, uint16_t size, thingset_time_ms_t timeout_ms, + struct ts_impl_buf **buffer); + +int ts_impl_buf_unref(struct ts_impl_buf *buffer); + +static inline int ts_impl_buf_ref(struct ts_impl_buf *buffer) +{ + if (net_buf_ref(buffer) != buffer) { + return -ENOMEM; + } + return 0; +} + +static inline uint16_t ts_impl_buf_size(struct ts_impl_buf *buffer) +{ + return buffer->size; +} + +static inline uint16_t ts_impl_buf_len(struct ts_impl_buf *buffer) +{ + return buffer->len; +} + +static inline uint16_t ts_impl_buf_headroom(struct ts_impl_buf *buffer) +{ + return net_buf_headroom(buffer); +} + +static inline uint16_t ts_impl_buf_tailroom(struct ts_impl_buf *buffer) +{ + return net_buf_tailroom(buffer); +} + +static inline uint8_t *ts_impl_buf_data(struct ts_impl_buf *buffer) +{ + return buffer->data; +} + +static inline uint8_t *ts_impl_buf_tail(struct ts_impl_buf *buffer) +{ + return net_buf_tail(buffer); +} + +static inline void ts_impl_buf_reserve(struct ts_impl_buf *buffer, uint16_t reserve) +{ + net_buf_reserve(buffer, reserve); +} + +static inline void ts_impl_buf_reset(struct ts_impl_buf *buffer) +{ + net_buf_reset(buffer); +} + +int ts_impl_buf_clone(struct ts_impl_buf *buffer, thingset_time_ms_t timeout_ms, struct ts_impl_buf **clone); + +static inline uint8_t *ts_impl_buf_add(struct ts_impl_buf *buffer, uint16_t len) +{ + return (uint8_t *)net_buf_add(buffer, (size_t)len); +} + +static inline uint8_t *ts_impl_buf_add_mem(struct ts_impl_buf *buffer, const uint8_t *data, uint16_t len) +{ + return (uint8_t *)net_buf_add_mem(buffer, (const void *)data, (size_t)len); +} + +static inline uint8_t *ts_impl_buf_add_u8(struct ts_impl_buf *buffer, uint8_t val) +{ + return (uint8_t *)net_buf_add_u8(buffer, val); +} + +static inline uint8_t *ts_impl_buf_remove(struct ts_impl_buf *buffer, uint16_t len) +{ + return (uint8_t *)net_buf_remove_mem(buffer, (size_t)len); +} + +static inline uint8_t *ts_impl_buf_push(struct ts_impl_buf *buffer, uint16_t len) +{ + return (uint8_t *)net_buf_push(buffer, (size_t)len); +} + +static inline uint8_t *ts_impl_buf_pull(struct ts_impl_buf *buffer, uint16_t len) +{ + return (uint8_t *)net_buf_pull(buffer, (size_t)len); +} + +int ts_impl_buf_free_all(void); + +#ifdef __cplusplus +} +#endif + +#endif /* TS_IMPL_BUF_H_ */ From f4971c12cdca2f7d2c1e6d0bd1e199d1a8768b02 Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Sat, 18 Dec 2021 18:25:42 +0100 Subject: [PATCH 09/37] COM prepare - add ThingSet OSAL for buffer queue Prepare for ThingSet Communication Context: - Add operating system abstraction for buffer queue. - Add Native implementation of buffer queue. - Add Zephyr implementation of buffer queue. Signed-off-by: Bobby Noelte --- native/thingset/ts_impl_bufq.c | 173 +++++++++++++++++++++++++++++++++ native/thingset/ts_impl_bufq.h | 41 ++++++++ src/ts_bufq.h | 170 ++++++++++++++++++++++++++++++++ zephyr/thingset/ts_impl_bufq.h | 58 +++++++++++ 4 files changed, 442 insertions(+) create mode 100644 native/thingset/ts_impl_bufq.c create mode 100644 native/thingset/ts_impl_bufq.h create mode 100644 src/ts_bufq.h create mode 100644 zephyr/thingset/ts_impl_bufq.h diff --git a/native/thingset/ts_impl_bufq.c b/native/thingset/ts_impl_bufq.c new file mode 100644 index 0000000..eb2f192 --- /dev/null +++ b/native/thingset/ts_impl_bufq.c @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2019 chrismerck@gmail.com + * Copyright (c) 2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + * + * Modified from https://github.com/chrismerck/rpa_queue + */ + +/** + * @file + * @brief Native implementation of ThingSet communication buffer queue. + */ + +#include "../../src/thingset_env.h" +#include "../../src/thingset_time.h" + +#include "../../src/ts_macro.h" +#include "../../src/ts_log.h" + +#include "ts_impl_bufq.h" + +#define TS_IMPL_BUFQ_LOGD(msg, bufq) \ + TS_LOGD("BUFQ: %s on queue 0x%" PRIXPTR " %s", __func__, (uintptr_t)bufq, msg) + +/** + * @brief Detects when the buffer queue is empty. + * + * This function is expected to be called from within critical sections, and is not threadsafe. + */ +#define ts_impl_bufq_empty(queue) ((queue)->first == NULL) + +/** + * Initialize the struct ts_impl_bufq. + */ +int ts_impl_bufq_init(struct ts_impl_bufq *queue) +{ + memset(queue, 0, sizeof(struct ts_impl_bufq)); + + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + int ret = pthread_mutex_init(&queue->one_big_mutex, &attr); + if (ret != 0) { + TS_IMPL_BUFQ_LOGD("pthread_mutex_init failed", queue); + return ret; + } + + ret = pthread_cond_init(&queue->not_empty, NULL); + if (ret != 0) { + TS_IMPL_BUFQ_LOGD("pthread_cond_init not_empty failed", queue); + return ret; + } + + /* Set all the data in the queue to NULL */ + queue->first = NULL; + queue->empty_waiters = 0; + + return 0; +} + +/** + * @brief Last buffer in buffer queue. + * + * @return Pointer to last buffer or NULL. + */ +struct ts_impl_buf *ts_impl_bufq_last(struct ts_impl_bufq *bufq) +{ + struct ts_impl_buf *last = NULL; + struct ts_impl_buf *next = bufq->first; + + while (next != NULL) { + last = next; + next = last->next; + } + + return last; +} + +/** + * Push new buffer onto the queue. If the queue is full, return RPA_EAGAIN. If + * the push operation completes successfully, it signals other threads + * waiting in rpa_queue_pop() that they may continue consuming sockets. + */ +int ts_impl_bufq_put(struct ts_impl_bufq *queue, struct ts_impl_buf *buf) +{ + int ret = pthread_mutex_lock(&queue->one_big_mutex); + if (ret != 0) { + return ret; + } + + /* Put buffer as last element into queue */ + if (queue->first == NULL) { + queue->first = buf; + } + else { + struct ts_impl_buf *last = ts_impl_bufq_last(queue); + last->next = buf; + } + buf->next = NULL; + + /* Signal not empty */ + if (queue->empty_waiters) { + TS_IMPL_BUFQ_LOGD("sig !empty", queue); + ret = pthread_cond_signal(&queue->not_empty); + if (ret != 0) { + pthread_mutex_unlock(&queue->one_big_mutex); + return ret; + } + } + + pthread_mutex_unlock(&queue->one_big_mutex); + return 0; +} + +int ts_impl_bufq_get(struct ts_impl_bufq *queue, thingset_time_ms_t timeout_ms, + struct ts_impl_buf **buf) +{ + int ret = pthread_mutex_lock(&queue->one_big_mutex); + if (ret != 0) { + return ret; + } + + /* Keep waiting until we wake up and find that the queue is not empty. */ + if (ts_impl_bufq_empty(queue)) { + if (timeout_ms == THINGSET_TIMEOUT_IMMEDIATE) { + return -EAGAIN; + } + + queue->empty_waiters++; + if (timeout_ms == THINGSET_TIMEOUT_FOREVER) { + ret = pthread_cond_wait(&queue->not_empty, &queue->one_big_mutex); + } else { + struct timespec abstime = thingset_time_timeout_spec(timeout_ms); + ret = pthread_cond_timedwait(&queue->not_empty, &queue->one_big_mutex, &abstime); + } + + queue->empty_waiters--; + if (ret != 0) { + pthread_mutex_unlock(&queue->one_big_mutex); + return ret; + } + + /* If we wake up and it's still empty, then we were interrupted */ + if (ts_impl_bufq_empty(queue)) { + TS_IMPL_BUFQ_LOGD("queue empty (intr)", queue); + ret = pthread_mutex_unlock(&queue->one_big_mutex); + if (ret == 0) { + ret = -EINTR; + } + return ret; + } + } + + /* Get buffer from first element in queue */ + *buf = queue->first; + queue->first = queue->first->next; + + pthread_mutex_unlock(&queue->one_big_mutex); + return 0; +} + +int ts_impl_bufq_is_empty(struct ts_impl_bufq *bufq) +{ + int ret = pthread_mutex_lock(&bufq->one_big_mutex); + if (ret != 0) { + return ret; + } + + ret = ts_impl_bufq_empty(bufq) ? -ENOTEMPTY : 0; + + pthread_mutex_unlock(&bufq->one_big_mutex); + return ret; +} diff --git a/native/thingset/ts_impl_bufq.h b/native/thingset/ts_impl_bufq.h new file mode 100644 index 0000000..7d4c3aa --- /dev/null +++ b/native/thingset/ts_impl_bufq.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Native implementation of ThingSet communication buffer queue. + */ + +#ifndef TS_IMPL_BUFQ_H_ +#define TS_IMPL_BUFQ_H_ + +#include "ts_impl_buf.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct ts_impl_bufq { + struct ts_impl_buf *first; /**< first buffer put into queue */ + uint32_t empty_waiters; + pthread_mutex_t one_big_mutex; + pthread_cond_t not_empty; +}; + +#define TS_IMPL_BUFQ_DEFINE(bufq_name) struct ts_impl_bufq bufq_name + +#define TS_IMPL_BUFQ_DECLARE(bufq_name) extern struct ts_impl_bufq bufq_name + +int ts_impl_bufq_init(struct ts_impl_bufq *bufq); +int ts_impl_bufq_put(struct ts_impl_bufq *bufq, struct ts_impl_buf *buffer); +int ts_impl_bufq_get(struct ts_impl_bufq *bufq, thingset_time_ms_t timeout_ms, + struct ts_impl_buf **buffer); +int ts_impl_bufq_is_empty(struct ts_impl_bufq *bufq); + +#ifdef __cplusplus +} +#endif + +#endif /* TS_IMPL_BUFQ_H_ */ diff --git a/src/ts_bufq.h b/src/ts_bufq.h new file mode 100644 index 0000000..8819102 --- /dev/null +++ b/src/ts_bufq.h @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet communication buffer queue (private interface). + */ + +#ifndef TS_BUFQ_H_ +#define TS_BUFQ_H_ + +#include "thingset_env.h" +#include "thingset_time.h" + +#include "ts_log.h" +#include "ts_buf.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* ThingSet environment implementation interface */ + +/** + * @addtogroup ts_impl_api + * @{ + */ + +#if TS_DOXYGEN + +/** + * @brief Implementation for @ref TS_BUFQ_DEFINE. + */ +#define TS_IMPL_BUFQ_DEFINE(bufq_name) + +/** + * @brief Implementation for @ref TS_BUFQ_DECLARE. + */ +#define TS_IMPL_BUFQ_DECLARE(bufq_name) + +/** + * @brief Implementation for @ref ts_bufq. + */ +#define ts_impl_bufq + +#endif /* TS_DOXYGEN */ + +/** + * @brief Implementation for @ref ts_bufq_init. + */ +int ts_impl_bufq_init(struct ts_impl_bufq *bufq); + +/** + * @brief Implementation for @ref ts_bufq_put. + */ +int ts_impl_bufq_put(struct ts_impl_bufq *bufq, struct ts_impl_buf *buffer); + +/** + * @brief Implementation for @ref ts_bufq_get. + */ +int ts_impl_bufq_get(struct ts_impl_bufq *bufq, thingset_time_ms_t timeout_ms, + struct ts_impl_buf **buffer); + +/** + * @brief Implementation for @ref ts_bufq_is_empty. + */ +int ts_impl_bufq_is_empty(struct ts_impl_bufq *bufq); + +/** + * @} + */ + +/** + * @brief ThingSet communication buffer queue. + * + * @defgroup ts_bufq_api_priv ThingSet communication buffer queue (private interface) + * @{ + */ + +/** + * @def TS_BUFQ_DEFINE Statically define and initialize a buffer queue. + * + * @note TS_IMPL_BUFQ_DEFINE to be provided by the implementation. + */ +#define TS_BUFQ_DEFINE(bufq_name) TS_IMPL_BUFQ_DEFINE(bufq_name) + +/** + * @def TS_BUFQ_DECLARE Declare a buffer queue. + * + * @note TS_IMPL_BUFQ_DECLARE to be provided by the implementation. + */ +#define TS_BUFQ_DECLARE(bufq_name) TS_IMPL_BUFQ_DECLARE(bufq_name) + +/** + * @brief ThingSet communication buffer queue structure name. + * + * @note To be provided by the implementation. + */ +#define ts_bufq ts_impl_bufq + +/** + * @brief Initialize a buffer queue. + * + * @note ts_impl_bufq_init to be provided by the implementation. + * + * @return 0 on success, < 0 otherwise. + */ +static inline int ts_bufq_init(struct ts_bufq *bufq) +{ + int ret = ts_impl_bufq_init(bufq); + TS_LOGD("BUFQ: %s initialized queue 0x%" PRIXPTR " returning %d", __func__, (uintptr_t)bufq, + ret); + return ret; +} + +/** + * @brief Add a buffer to the queue. + * + * @note ts_impl_bufq_put to be provided by the implementation. + * + * @return 0 on success, < 0 otherwise. + */ +static inline int ts_bufq_put(struct ts_bufq *bufq, struct ts_buf *buffer) +{ + int ret = ts_impl_bufq_put(bufq, buffer); + TS_LOGD("BUFQ: %s put buffer 0x%" PRIXPTR " to queue 0x%" PRIXPTR " returning %d", __func__, + (uintptr_t)buffer, (uintptr_t)bufq, ret); + return ret; +} + +/** + * @brief Get a buffer from the queue. + * + * @note ts_impl_bufq_get to be provided by the implementation. + * + * @return 0 on success, < 0 otherwise. + */ +static inline int ts_bufq_get(struct ts_bufq *bufq, thingset_time_ms_t timeout_ms, struct ts_buf **buffer) +{ + int ret = ts_impl_bufq_get(bufq, timeout_ms, buffer); + TS_LOGD("BUFQ: %s got buffer 0x%" PRIXPTR " from queue 0x%" PRIXPTR " returning %d", __func__, + (uintptr_t)*buffer, (uintptr_t)bufq, ret); + return ret; +} + +/** + * @brief Query a queue to see if it has buffers available. + * + * @note ts_impl_bufq_is_empty to be provided by the implementation. + * + * @return Non-zero if the queue is empty, 0 if buffers are available. + */ +static inline int ts_bufq_is_empty(struct ts_bufq *bufq) +{ + int ret = ts_impl_bufq_is_empty(bufq); + TS_LOGD("BUFQ: %s on queue 0x%" PRIXPTR " returning %d", __func__, (uintptr_t)bufq, ret); + return ret; +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +/** + * @} + */ + +#endif /* TS_BUFQ_H_ */ diff --git a/zephyr/thingset/ts_impl_bufq.h b/zephyr/thingset/ts_impl_bufq.h new file mode 100644 index 0000000..b0bfba4 --- /dev/null +++ b/zephyr/thingset/ts_impl_bufq.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Zephyr implementation of ThingSet communication buffer queue. + */ + +#ifndef TS_IMPL_BUFQ_H_ +#define TS_IMPL_BUFQ_H_ + +#include "ts_impl_buf.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ts_impl_bufq k_fifo + +#define TS_IMPL_BUFQ_DEFINE(bufq_name) K_FIFO_DEFINE(bufq_name) + +#define TS_IMPL_BUFQ_DECLARE(bufq_name) extern struct k_fifo bufq_name + +static inline int ts_impl_bufq_init(struct ts_impl_bufq *bufq) +{ + k_fifo_init(bufq); + return 0; +} + +static inline int ts_impl_bufq_put(struct ts_impl_bufq *bufq, struct ts_impl_buf *buffer) +{ + net_buf_put(bufq, buffer); + return 0; +} + +static inline int ts_impl_bufq_get(struct ts_impl_bufq *bufq, thingset_time_ms_t timeout_ms, + struct ts_impl_buf **buffer) +{ + struct net_buf *got = net_buf_get(bufq, TS_IMPL_ZEPHYR_TIMEOUT_MS(timeout_ms)); + if (got == NULL) { + return -ETIMEDOUT; + } + *buffer = got; + return 0; +} + +static inline int ts_impl_bufq_is_empty(struct ts_impl_bufq *bufq) +{ + return k_fifo_is_empty(bufq); +} + +#ifdef __cplusplus +} +#endif + +#endif /* TS_IMPL_BUFQ_H_ */ From d2689f3615f7d8bb1eea745861ff51bad7decf1a Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Sun, 6 Feb 2022 20:05:46 +0100 Subject: [PATCH 10/37] COM prepare - add ThingSet OSAL for shell application support Prepare for ThingSet Communication Context: - Add Native implementation of shell application support. - Add Zephyr implementation of shell application support. Signed-off-by: Bobby Noelte --- native/thingset/ts_impl_shell.c | 48 + native/thingset/ts_impl_shell.h | 56 + native/thingset/ts_impl_shell_linenoise.c | 1277 +++++++++++++++++++++ native/thingset/ts_impl_shell_linenoise.h | 56 + zephyr/thingset/ts_impl_shell.c | 50 + zephyr/thingset/ts_impl_shell.h | 60 + 6 files changed, 1547 insertions(+) create mode 100644 native/thingset/ts_impl_shell.c create mode 100644 native/thingset/ts_impl_shell.h create mode 100644 native/thingset/ts_impl_shell_linenoise.c create mode 100644 native/thingset/ts_impl_shell_linenoise.h create mode 100644 zephyr/thingset/ts_impl_shell.c create mode 100644 zephyr/thingset/ts_impl_shell.h diff --git a/native/thingset/ts_impl_shell.c b/native/thingset/ts_impl_shell.c new file mode 100644 index 0000000..edcfe48 --- /dev/null +++ b/native/thingset/ts_impl_shell.c @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Native implementation of ThingSet shell app. + */ + +#include "../../src/thingset_env.h" + +#include "../../apps/shell/ts_shell_g.h" + +#include "ts_impl_shell_linenoise.h" + +int ts_impl_shell_init(const struct thingset_app *app) +{ + linenoiseHistoryLoad(".thingset-shell-history.txt"); /* Load the history at startup */ + //linenoiseClearScreen(); + + return 0; +} + +int ts_impl_shell_get_line(const struct ts_impl_shell *shell, const char *prompt, char **line) +{ + char *input = linenoise(prompt); + + if (input == NULL) { + return -ENAVAIL; + } + + *line = input; + return 0; +} + +int ts_impl_shell_execute_cmd(const char* cmd) +{ + if ((cmd == NULL) || (cmd[0] == '\0')) { + return 0; + } + + linenoiseHistoryAdd(cmd); + linenoiseHistorySave(".thingset-shell-history.txt"); + + /* Let generic shell function ts_shell_execute_cmd_g() do the rest */ + return ts_shell_execute_cmd_g(cmd); +} diff --git a/native/thingset/ts_impl_shell.h b/native/thingset/ts_impl_shell.h new file mode 100644 index 0000000..70d3cac --- /dev/null +++ b/native/thingset/ts_impl_shell.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Native implementation of ThingSet shell app. + */ + +#ifndef TS_IMPL_SHELL_H_ +#define TS_IMPL_SHELL_H_ + +/* Use generic implementation of shell */ +#include "../../apps/shell/ts_shell_g.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define TS_IMPL_SHELL_CMD_HELP_PRINTED 1 + +/* forward declaration */ +struct thingset_app; +struct ts_impl_shell; + +#define ts_impl_shell_print(shell, format, ...) ts_shell_printf_g(format "\n", ##__VA_ARGS__) + +#define ts_impl_shell_error(shell, format, ...) ts_shell_printf_g(format "\n", ##__VA_ARGS__) + +int ts_impl_shell_init(const struct thingset_app *app); + +static inline int ts_impl_shell_run(const struct thingset_app *app) +{ + return ts_shell_run_g(); +} + +int ts_impl_shell_get_line(const struct ts_impl_shell *shell, const char *promt, char **line); + +int ts_impl_shell_execute_cmd(const char* cmd); + +static inline int ts_impl_shell_execute_output(size_t *sizep, const char **output) +{ + return ts_shell_execute_output_g(sizep, output); +} + +static inline int ts_impl_shell_join(void) +{ + return ts_shell_join_g(); +} + +#ifdef __cplusplus +} +#endif + +#endif /* TS_IMPL_SHELL_H_ */ diff --git a/native/thingset/ts_impl_shell_linenoise.c b/native/thingset/ts_impl_shell_linenoise.c new file mode 100644 index 0000000..97282dd --- /dev/null +++ b/native/thingset/ts_impl_shell_linenoise.c @@ -0,0 +1,1277 @@ +/* + * Copyright (c) 2010-2016, Salvatore Sanfilippo + * Copyright (c) 2010-2013, Pieter Noordhuis + * Copyright (c) 2022, Bobby Noelte + * SPDX-License-Identifier: BSD-2-Clause + */ + +/** + * @file + * @brief Linenoise for native implementation of ThingSet shell app. + */ + +/* linenoise.c -- guerrilla line editing library against the idea that a + * line editing lib needs to be 20,000 lines of C code. + * + * You can find the latest source code at: + * + * http://github.com/antirez/linenoise + * + * Does a number of crazy assumptions that happen to be true in 99.9999% of + * the 2010 UNIX computers around. + * + * References: + * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html + * + * Todo list: + * - Filter bogus Ctrl+ combinations. + * - Win32 support + * + * Bloat: + * - History search like Ctrl+r in readline? + * + * List of escape sequences used by this program, we do everything just + * with three sequences. In order to be so cheap we may have some + * flickering effect with some slow terminal, but the lesser sequences + * the more compatible. + * + * EL (Erase Line) + * Sequence: ESC [ n K + * Effect: if n is 0 or missing, clear from cursor to end of line + * Effect: if n is 1, clear from beginning of line to cursor + * Effect: if n is 2, clear entire line + * + * CUF (CUrsor Forward) + * Sequence: ESC [ n C + * Effect: moves cursor forward n chars + * + * CUB (CUrsor Backward) + * Sequence: ESC [ n D + * Effect: moves cursor backward n chars + * + * The following is used to get the terminal width if getting + * the width with the TIOCGWINSZ ioctl fails + * + * DSR (Device Status Report) + * Sequence: ESC [ 6 n + * Effect: reports the current cusor position as ESC [ n ; m R + * where n is the row and m is the column + * + * When multi line mode is enabled, we also use an additional escape + * sequence. However multi line editing is disabled by default. + * + * CUU (Cursor Up) + * Sequence: ESC [ n A + * Effect: moves cursor up of n chars. + * + * CUD (Cursor Down) + * Sequence: ESC [ n B + * Effect: moves cursor down of n chars. + * + * When linenoiseClearScreen() is called, two additional escape sequences + * are used in order to clear the screen and position the cursor at home + * position. + * + * CUP (Cursor position) + * Sequence: ESC [ H + * Effect: moves the cursor to upper left corner + * + * ED (Erase display) + * Sequence: ESC [ 2 J + * Effect: clear the whole screen + * + */ + +#include "../../src/thingset_env.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Use ts_shell_alloc()/ ts_shell_free() instead of malloc */ +#include "../../apps/shell/ts_shell.h" +/* Append buffers were made a shell element - use them and replace linenoise originals */ +#include "../../apps/shell/ts_shell_abuf.h" + +#include "ts_impl_shell_linenoise.h" + +#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 +#define LINENOISE_DEFAULT_MAX_LINE 4096 +#define LINENOISE_MINIMAL_MAX_LINE 64 +#define LINENOISE_COMMAND_MAX_LEN 32 +#define LINENOISE_COMPLETION_MAX_COUNT 32 +#define LINENOISE_PASTE_KEY_DELAY 30 /* Delay, in milliseconds, between two characters being pasted from clipboard */ + +typedef struct linenoiseCompletions { + size_t len; + char *cvec[LINENOISE_COMPLETION_MAX_COUNT]; +} linenoiseCompletions; + +static linenoiseCompletionCallback *completionCallback = NULL; +static linenoiseHintsCallback *hintsCallback = NULL; +static linenoiseFreeHintsCallback *freeHintsCallback = NULL; + +static size_t max_cmdline_length = LINENOISE_DEFAULT_MAX_LINE; +static int mlmode = 0; /* Multi line mode. Default is single line. */ +static int dumbmode = 0; /* Dumb mode where line editing is disabled. Off by default */ +static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; +static int history_len = 0; +static char **history = NULL; +static bool allow_empty = true; + +/* The linenoiseState structure represents the state during line editing. + * We pass this state to functions implementing specific editing + * functionalities. */ +struct linenoiseState { + char *buf; /* Edited line buffer. */ + size_t buflen; /* Edited line buffer size. */ + const char *prompt; /* Prompt to display. */ + size_t plen; /* Prompt length. */ + size_t pos; /* Current cursor position. */ + size_t oldpos; /* Previous refresh cursor position. */ + size_t len; /* Current edited line length. */ + size_t cols; /* Number of columns in terminal. */ + size_t maxrows; /* Maximum num of rows used so far (multiline mode) */ + int history_index; /* The history index we are currently editing. */ +}; + +enum KEY_ACTION{ + KEY_NULL = 0, /* NULL */ + CTRL_A = 1, /* Ctrl+a */ + CTRL_B = 2, /* Ctrl-b */ + CTRL_C = 3, /* Ctrl-c */ + CTRL_D = 4, /* Ctrl-d */ + CTRL_E = 5, /* Ctrl-e */ + CTRL_F = 6, /* Ctrl-f */ + CTRL_H = 8, /* Ctrl-h */ + TAB = 9, /* Tab */ + CTRL_K = 11, /* Ctrl+k */ + CTRL_L = 12, /* Ctrl+l */ + ENTER = 10, /* Enter */ + CTRL_N = 14, /* Ctrl-n */ + CTRL_P = 16, /* Ctrl-p */ + CTRL_T = 20, /* Ctrl-t */ + CTRL_U = 21, /* Ctrl+u */ + CTRL_W = 23, /* Ctrl+w */ + ESC = 27, /* Escape */ + BACKSPACE = 127 /* Backspace */ +}; + +int linenoiseHistoryAdd(const char *line); +static void refreshLine(struct linenoiseState *l); + +/* Debugging macro. */ +#if 0 +FILE *lndebug_fp = NULL; +#define lndebug(...) \ + do { \ + if (lndebug_fp == NULL) { \ + lndebug_fp = fopen("/tmp/lndebug.txt","a"); \ + fprintf(lndebug_fp, \ + "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \ + (int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \ + (int)l->maxrows,old_rows); \ + } \ + fprintf(lndebug_fp, ", " __VA_ARGS__); \ + fflush(lndebug_fp); \ + } while (0) +#else +#define lndebug(fmt, ...) +#endif + +/* ======================= Low level terminal handling ====================== */ + +/* Set if to use or not the multi line mode. */ +void linenoiseSetMultiLine(int ml) { + mlmode = ml; +} + +/* Set if terminal does not recognize escape sequences */ +void linenoiseSetDumbMode(int set) { + dumbmode = set; +} + +/* Returns whether the current mode is dumbmode or not. */ +bool linenoiseIsDumbMode(void) { + return dumbmode; +} + +static void flushWrite(void) { + if (__fbufsize(stdout) > 0) { + fflush(stdout); + } + fsync(fileno(stdout)); +} + +/* Use the ESC [6n escape sequence to query the horizontal cursor position + * and return it. On error -1 is returned, on success the position of the + * cursor. */ +static int getCursorPosition(void) { + char buf[LINENOISE_COMMAND_MAX_LEN] = { 0 }; + int cols = 0; + int rows = 0; + int i = 0; + const int out_fd = fileno(stdout); + const int in_fd = fileno(stdin); + /* The following ANSI escape sequence is used to get from the TTY the + * cursor position. */ + const char get_cursor_cmd[] = "\x1b[6n"; + + /* Send the command to the TTY on the other end of the UART. + * Let's use unistd's write function. Thus, data sent through it are raw + * reducing the overhead compared to using fputs, fprintf, etc... */ + write(out_fd, get_cursor_cmd, sizeof(get_cursor_cmd)); + + /* For USB CDC, it is required to flush the output. */ + flushWrite(); + + /* The other end will send its response which format is ESC [ rows ; cols R + * We don't know exactly how many bytes we have to read, thus, perform a + * read for each byte. + * Stop right before the last character of the buffer, to be able to NULL + * terminate it. */ + while (i < sizeof(buf)-1) { + /* Keep using unistd's functions. Here, using `read` instead of `fgets` + * or `fgets` guarantees us that we we can read a byte regardless on + * whether the sender sent end of line character(s) (CR, CRLF, LF). */ + if (read(in_fd, buf + i, 1) != 1 || buf[i] == 'R') { + /* If we couldn't read a byte from STDIN or if 'R' was received, + * the transmission is finished. */ + break; + } + + /* For some reasons, it is possible that we receive new line character + * after querying the cursor position on some UART. Let's ignore them, + * this will not affect the rest of the program. */ + if (buf[i] != '\n') { + i++; + } + } + + /* NULL-terminate the buffer, this is required by `sscanf`. */ + buf[i] = '\0'; + + /* Parse the received data to get the position of the cursor. */ + if (buf[0] != ESC || buf[1] != '[' || sscanf(buf+2,"%d;%d",&rows,&cols) != 2) { + return -1; + } + return cols; +} + +/* Try to get the number of columns in the current terminal, or assume 80 + * if it fails. */ +static int getColumns(void) { + int start = 0; + int cols = 0; + int written = 0; + char seq[LINENOISE_COMMAND_MAX_LEN] = { 0 }; + const int fd = fileno(stdout); + + /* The following ANSI escape sequence is used to tell the TTY to move + * the cursor to the most-right position. */ + const char move_cursor_right[] = "\x1b[999C"; + const size_t cmd_len = sizeof(move_cursor_right); + + /* This one is used to set the cursor position. */ + const char set_cursor_pos[] = "\x1b[%dD"; + + /* Get the initial position so we can restore it later. */ + start = getCursorPosition(); + if (start == -1) { + goto failed; + } + + /* Send the command to go to right margin. Use `write` function instead of + * `fwrite` for the same reasons explained in `getCursorPosition()` */ + if (write(fd, move_cursor_right, cmd_len) != cmd_len) { + goto failed; + } + flushWrite(); + + /* After sending this command, we can get the new position of the cursor, + * we'd get the size, in columns, of the opened TTY. */ + cols = getCursorPosition(); + if (cols == -1) { + goto failed; + } + + /* Restore the position of the cursor back. */ + if (cols > start) { + /* Generate the move cursor command. */ + written = snprintf(seq, LINENOISE_COMMAND_MAX_LEN, set_cursor_pos, cols-start); + + /* If `written` is equal or bigger than LINENOISE_COMMAND_MAX_LEN, it + * means that the output has been truncated because the size provided + * is too small. */ + assert (written < LINENOISE_COMMAND_MAX_LEN); + + /* Send the command with `write`, which is not buffered. */ + if (write(fd, seq, written) == -1) { + /* Can't recover... */ + } + flushWrite(); + } + return cols; + +failed: + return 80; +} + +/* Clear the screen. Used to handle ctrl+l */ +void linenoiseClearScreen(void) { + fprintf(stdout,"\x1b[H\x1b[2J"); + flushWrite(); +} + +/* Beep, used for completion when there is nothing to complete or when all + * the choices were already shown. */ +static void linenoiseBeep(void) { + fprintf(stdout, "\x7"); + flushWrite(); +} + +/* ============================== Completion ================================ */ + +/* Free a list of completion option populated by linenoiseAddCompletion(). */ +static void freeCompletions(linenoiseCompletions *lc) { + size_t i; + for (i = 0; i < lc->len; i++) { + free(lc->cvec[i]); + } +} + +/* This is an helper function for linenoiseEdit() and is called when the + * user types the key in order to complete the string currently in the + * input. + * + * The state of the editing is encapsulated into the pointed linenoiseState + * structure as described in the structure definition. */ +static int completeLine(struct linenoiseState *ls) { + linenoiseCompletions lc = { .len = 0 }; + int nread, nwritten; + char c = 0; + int in_fd = fileno(stdin); + + completionCallback(ls->buf,&lc); + if (lc.len == 0) { + linenoiseBeep(); + } else { + size_t stop = 0, i = 0; + + while(!stop) { + /* Show completion or original buffer */ + if (i < lc.len) { + struct linenoiseState saved = *ls; + + ls->len = ls->pos = strlen(lc.cvec[i]); + ls->buf = lc.cvec[i]; + refreshLine(ls); + ls->len = saved.len; + ls->pos = saved.pos; + ls->buf = saved.buf; + } else { + refreshLine(ls); + } + + nread = read(in_fd, &c, 1); + if (nread <= 0) { + freeCompletions(&lc); + return -1; + } + + switch(c) { + case TAB: /* tab */ + i = (i+1) % (lc.len+1); + if (i == lc.len) linenoiseBeep(); + break; + case ESC: /* escape */ + /* Re-show original buffer */ + if (i < lc.len) refreshLine(ls); + stop = 1; + break; + default: + /* Update buffer and return */ + if (i < lc.len) { + nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]); + ls->len = ls->pos = nwritten; + } + stop = 1; + break; + } + } + } + + freeCompletions(&lc); + return c; /* Return last read character */ +} + +/* Register a callback function to be called for tab-completion. */ +void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) { + completionCallback = fn; +} + +/* Register a hits function to be called to show hits to the user at the + * right of the prompt. */ +void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) { + hintsCallback = fn; +} + +/* Register a function to free the hints returned by the hints callback + * registered with linenoiseSetHintsCallback(). */ +void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) { + freeHintsCallback = fn; +} + +/* This function is used by the callback function registered by the user + * in order to add completion options given the input string when the + * user typed . See the example.c source code for a very easy to + * understand example. */ +void linenoiseAddCompletion(struct linenoiseCompletions *lc, const char *str) +{ + if (lc->len >= LINENOISE_COMPLETION_MAX_COUNT) { + return; + } + + char *copy; + size_t len = strlen(str); + int ret = ts_shell_alloc(len + 1, 10, (uint8_t **)©); + if (ret != 0) { + return; + } + + memcpy(copy, str, len + 1); + lc->cvec[lc->len] = copy; + lc->len++; +} + +/* =========================== Line editing ================================= */ + +/* Helper of refreshSingleLine() and refreshMultiLine() to show hints + * to the right of the prompt. */ +void refreshShowHints(struct ts_shell_abuf *ab, struct linenoiseState *l, int plen) { + char seq[64]; + if (hintsCallback && plen+l->len < l->cols) { + int color = -1, bold = 0; + char *hint = hintsCallback(l->buf,&color,&bold); + if (hint) { + int hintlen = strlen(hint); + int hintmaxlen = l->cols-(plen+l->len); + if (hintlen > hintmaxlen) hintlen = hintmaxlen; + if (bold == 1 && color == -1) color = 37; + if (color != -1 || bold != 0) + snprintf(seq,64,"\033[%d;%d;49m",bold,color); + ts_shell_abuf_append(ab,seq,strlen(seq)); + ts_shell_abuf_append(ab,hint,hintlen); + if (color != -1 || bold != 0) + ts_shell_abuf_append(ab,"\033[0m",4); + /* Call the function to free the hint returned. */ + if (freeHintsCallback) freeHintsCallback(hint); + } + } +} + +/* Single line low level line refresh. + * + * Rewrite the currently edited line accordingly to the buffer content, + * cursor position, and number of columns of the terminal. */ +static void refreshSingleLine(struct linenoiseState *l) { + char seq[64]; + size_t plen = l->plen; + int fd = fileno(stdout); + char *buf = l->buf; + size_t len = l->len; + size_t pos = l->pos; + struct ts_shell_abuf ab; + + while((plen+pos) >= l->cols) { + buf++; + len--; + pos--; + } + while (plen+len > l->cols) { + len--; + } + + ts_shell_abuf_init(&ab); + /* Cursor to left edge */ + snprintf(seq,64,"\r"); + ts_shell_abuf_append(&ab,seq,strlen(seq)); + /* Write the prompt and the current buffer content */ + ts_shell_abuf_append(&ab,l->prompt,strlen(l->prompt)); + ts_shell_abuf_append(&ab,buf,len); + /* Show hits if any. */ + refreshShowHints(&ab,l,plen); + /* Erase to right */ + snprintf(seq,64,"\x1b[0K"); + ts_shell_abuf_append(&ab,seq,strlen(seq)); + /* Move cursor to original position. */ + snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen)); + ts_shell_abuf_append(&ab,seq,strlen(seq)); + if (write(fd, ab.b, ab.len) == -1) {} /* Can't recover from write error. */ + flushWrite(); + ts_shell_abuf_free(&ab); +} + +/* Multi line low level line refresh. + * + * Rewrite the currently edited line accordingly to the buffer content, + * cursor position, and number of columns of the terminal. */ +static void refreshMultiLine(struct linenoiseState *l) { + char seq[64]; + int plen = l->plen; + int rows = (plen+l->len+l->cols-1)/l->cols; /* rows used by current buf. */ + int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */ + int rpos2; /* rpos after refresh. */ + int col; /* colum position, zero-based. */ + int old_rows = l->maxrows; + int j; + int fd = fileno(stdout); + struct ts_shell_abuf ab; + + /* Update maxrows if needed. */ + if (rows > (int)l->maxrows) l->maxrows = rows; + + /* First step: clear all the lines used before. To do so start by + * going to the last row. */ + ts_shell_abuf_init(&ab); + if (old_rows-rpos > 0) { + lndebug("go down %d", old_rows-rpos); + snprintf(seq,64,"\x1b[%dB", old_rows-rpos); + ts_shell_abuf_append(&ab,seq,strlen(seq)); + } + + /* Now for every row clear it, go up. */ + for (j = 0; j < old_rows-1; j++) { + lndebug("clear+up"); + snprintf(seq,64,"\r\x1b[0K\x1b[1A"); + ts_shell_abuf_append(&ab,seq,strlen(seq)); + } + + /* Clean the top line. */ + lndebug("clear"); + snprintf(seq,64,"\r\x1b[0K"); + ts_shell_abuf_append(&ab,seq,strlen(seq)); + + /* Write the prompt and the current buffer content */ + ts_shell_abuf_append(&ab,l->prompt,strlen(l->prompt)); + ts_shell_abuf_append(&ab,l->buf,l->len); + + /* Show hits if any. */ + refreshShowHints(&ab,l,plen); + + /* If we are at the very end of the screen with our prompt, we need to + * emit a newline and move the prompt to the first column. */ + if (l->pos && + l->pos == l->len && + (l->pos+plen) % l->cols == 0) + { + lndebug(""); + ts_shell_abuf_append(&ab,"\n",1); + snprintf(seq,64,"\r"); + ts_shell_abuf_append(&ab,seq,strlen(seq)); + rows++; + if (rows > (int)l->maxrows) l->maxrows = rows; + } + + /* Move cursor to right position. */ + rpos2 = (plen+l->pos+l->cols)/l->cols; /* current cursor relative row. */ + lndebug("rpos2 %d", rpos2); + + /* Go up till we reach the expected positon. */ + if (rows-rpos2 > 0) { + lndebug("go-up %d", rows-rpos2); + snprintf(seq,64,"\x1b[%dA", rows-rpos2); + ts_shell_abuf_append(&ab,seq,strlen(seq)); + } + + /* Set column. */ + col = (plen+(int)l->pos) % (int)l->cols; + lndebug("set col %d", 1+col); + if (col) + snprintf(seq,64,"\r\x1b[%dC", col); + else + snprintf(seq,64,"\r"); + ts_shell_abuf_append(&ab,seq,strlen(seq)); + + lndebug("\n"); + l->oldpos = l->pos; + + if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ + flushWrite(); + ts_shell_abuf_free(&ab); +} + +/* Calls the two low level functions refreshSingleLine() or + * refreshMultiLine() according to the selected mode. */ +static void refreshLine(struct linenoiseState *l) { + if (mlmode) + refreshMultiLine(l); + else + refreshSingleLine(l); +} + +/* Insert the character 'c' at cursor current position. + * + * On error writing to the terminal -1 is returned, otherwise 0. */ +int linenoiseEditInsert(struct linenoiseState *l, char c) { + int fd = fileno(stdout); + if (l->len < l->buflen) { + if (l->len == l->pos) { + l->buf[l->pos] = c; + l->pos++; + l->len++; + l->buf[l->len] = '\0'; + if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) { + /* Avoid a full update of the line in the + * trivial case. */ + if (write(fd, &c,1) == -1) { + return -1; + } + flushWrite(); + } else { + refreshLine(l); + } + } else { + memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos); + l->buf[l->pos] = c; + l->len++; + l->pos++; + l->buf[l->len] = '\0'; + refreshLine(l); + } + } + return 0; +} + +int linenoiseInsertPastedChar(struct linenoiseState *l, char c) { + int fd = fileno(stdout); + if (l->len < l->buflen && l->len == l->pos) { + l->buf[l->pos] = c; + l->pos++; + l->len++; + l->buf[l->len] = '\0'; + if (write(fd, &c,1) == -1) { + return -1; + } + flushWrite(); + } + return 0; +} + +/* Move cursor on the left. */ +void linenoiseEditMoveLeft(struct linenoiseState *l) { + if (l->pos > 0) { + l->pos--; + refreshLine(l); + } +} + +/* Move cursor on the right. */ +void linenoiseEditMoveRight(struct linenoiseState *l) { + if (l->pos != l->len) { + l->pos++; + refreshLine(l); + } +} + +/* Move cursor to the start of the line. */ +void linenoiseEditMoveHome(struct linenoiseState *l) { + if (l->pos != 0) { + l->pos = 0; + refreshLine(l); + } +} + +/* Move cursor to the end of the line. */ +void linenoiseEditMoveEnd(struct linenoiseState *l) { + if (l->pos != l->len) { + l->pos = l->len; + refreshLine(l); + } +} + +/* Substitute the currently edited line with the next or previous history + * entry as specified by 'dir'. */ +#define LINENOISE_HISTORY_NEXT 0 +#define LINENOISE_HISTORY_PREV 1 +void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) { + if (history_len > 1) { + /* Update the current history entry before to + * overwrite it with the next one. */ + free(history[history_len - 1 - l->history_index]); + history[history_len - 1 - l->history_index] = strdup(l->buf); + /* Show the new entry */ + l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; + if (l->history_index < 0) { + l->history_index = 0; + return; + } else if (l->history_index >= history_len) { + l->history_index = history_len-1; + return; + } + strncpy(l->buf,history[history_len - 1 - l->history_index],l->buflen); + l->buf[l->buflen-1] = '\0'; + l->len = l->pos = strlen(l->buf); + refreshLine(l); + } +} + +/* Delete the character at the right of the cursor without altering the cursor + * position. Basically this is what happens with the "Delete" keyboard key. */ +void linenoiseEditDelete(struct linenoiseState *l) { + if (l->len > 0 && l->pos < l->len) { + memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1); + l->len--; + l->buf[l->len] = '\0'; + refreshLine(l); + } +} + +/* Backspace implementation. */ +void linenoiseEditBackspace(struct linenoiseState *l) { + if (l->pos > 0 && l->len > 0) { + memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos); + l->pos--; + l->len--; + l->buf[l->len] = '\0'; + refreshLine(l); + } +} + +/* Delete the previosu word, maintaining the cursor at the start of the + * current word. */ +void linenoiseEditDeletePrevWord(struct linenoiseState *l) { + size_t old_pos = l->pos; + size_t diff; + + while (l->pos > 0 && l->buf[l->pos-1] == ' ') + l->pos--; + while (l->pos > 0 && l->buf[l->pos-1] != ' ') + l->pos--; + diff = old_pos - l->pos; + memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1); + l->len -= diff; + refreshLine(l); +} + +uint32_t getMillis(void) { + long ms; // Milliseconds + time_t s; // Seconds + struct timespec spec; + + clock_gettime(CLOCK_REALTIME, &spec); + + s = spec.tv_sec; + ms = round(spec.tv_nsec / 1.0e6); // Convert nanoseconds to milliseconds + if (ms > 999) { + s++; + ms = 0; + } + return ((s * 1000) + ms); +} + +/* This function is the core of the line editing capability of linenoise. + * It expects 'fd' to be already in "raw mode" so that every key pressed + * will be returned ASAP to read(). + * + * The resulting string is put into 'buf' when the user type enter, or + * when ctrl+d is typed. + * + * The function returns the length of the current buffer. */ +static int linenoiseEdit(char *buf, size_t buflen, const char *prompt) +{ + uint32_t t1 = 0; + struct linenoiseState l; + int out_fd = fileno(stdout); + int in_fd = fileno(stdin); + + /* Populate the linenoise state that we pass to functions implementing + * specific editing functionalities. */ + l.buf = buf; + l.buflen = buflen; + l.prompt = prompt; + l.plen = strlen(prompt); + l.oldpos = l.pos = 0; + l.len = 0; + l.cols = getColumns(); + l.maxrows = 0; + l.history_index = 0; + + /* Buffer starts empty. */ + l.buf[0] = '\0'; + l.buflen--; /* Make sure there is always space for the nulterm */ + + /* The latest history entry is always our current buffer, that + * initially is just an empty string. */ + linenoiseHistoryAdd(""); + + int pos1 = getCursorPosition(); + if (write(out_fd, prompt,l.plen) == -1) { + return -1; + } + flushWrite(); + int pos2 = getCursorPosition(); + if (pos1 >= 0 && pos2 >= 0) { + l.plen = pos2 - pos1; + } + while(1) { + char c; + int nread; + char seq[3]; + + /** + * To determine whether the user is pasting data or typing itself, we + * need to calculate how many milliseconds elapsed between two key + * presses. Indeed, if there is less than LINENOISE_PASTE_KEY_DELAY + * (typically 30-40ms), then a paste is being performed, else, the + * user is typing. + * NOTE: pressing a key down without releasing it will also spend + * about 40ms (or even more) + */ + t1 = getMillis(); + nread = read(in_fd, &c, 1); + if (nread <= 0) return l.len; + + if ( (getMillis() - t1) < LINENOISE_PASTE_KEY_DELAY && c != ENTER) { + /* Pasting data, insert characters without formatting. + * This can only be performed when the cursor is at the end of the + * line. */ + if (linenoiseInsertPastedChar(&l,c)) { + return -1; + } + continue; + } + + /* Only autocomplete when the callback is set. It returns < 0 when + * there was an error reading from fd. Otherwise it will return the + * character that should be handled next. */ + if (c == 9 && completionCallback != NULL) { + int c2 = completeLine(&l); + /* Return on errors */ + if (c2 < 0) return l.len; + /* Read next character when 0 */ + if (c2 == 0) continue; + c = c2; + } + + switch(c) { + case ENTER: /* enter */ + history_len--; + free(history[history_len]); + if (mlmode) linenoiseEditMoveEnd(&l); + if (hintsCallback) { + /* Force a refresh without hints to leave the previous + * line as the user typed it after a newline. */ + linenoiseHintsCallback *hc = hintsCallback; + hintsCallback = NULL; + refreshLine(&l); + hintsCallback = hc; + } + return (int)l.len; + case CTRL_C: /* ctrl-c */ + errno = EAGAIN; + return -1; + case BACKSPACE: /* backspace */ + case 8: /* ctrl-h */ + linenoiseEditBackspace(&l); + break; + case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the + line is empty, act as end-of-file. */ + if (l.len > 0) { + linenoiseEditDelete(&l); + } else { + history_len--; + free(history[history_len]); + return -1; + } + break; + case CTRL_T: /* ctrl-t, swaps current character with previous. */ + if (l.pos > 0 && l.pos < l.len) { + int aux = buf[l.pos-1]; + buf[l.pos-1] = buf[l.pos]; + buf[l.pos] = aux; + if (l.pos != l.len-1) l.pos++; + refreshLine(&l); + } + break; + case CTRL_B: /* ctrl-b */ + linenoiseEditMoveLeft(&l); + break; + case CTRL_F: /* ctrl-f */ + linenoiseEditMoveRight(&l); + break; + case CTRL_P: /* ctrl-p */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); + break; + case CTRL_N: /* ctrl-n */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); + break; + case ESC: /* escape sequence */ + /* Read the next two bytes representing the escape sequence. */ + if (read(in_fd, seq, 2) < 2) { + break; + } + + /* ESC [ sequences. */ + if (seq[0] == '[') { + if (seq[1] >= '0' && seq[1] <= '9') { + /* Extended escape, read additional byte. */ + if (read(in_fd, seq+2, 1) == -1) { + break; + } + if (seq[2] == '~') { + switch(seq[1]) { + case '3': /* Delete key. */ + linenoiseEditDelete(&l); + break; + } + } + } else { + switch(seq[1]) { + case 'A': /* Up */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); + break; + case 'B': /* Down */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); + break; + case 'C': /* Right */ + linenoiseEditMoveRight(&l); + break; + case 'D': /* Left */ + linenoiseEditMoveLeft(&l); + break; + case 'H': /* Home */ + linenoiseEditMoveHome(&l); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(&l); + break; + } + } + } + + /* ESC O sequences. */ + else if (seq[0] == 'O') { + switch(seq[1]) { + case 'H': /* Home */ + linenoiseEditMoveHome(&l); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(&l); + break; + } + } + break; + default: + if (linenoiseEditInsert(&l,c)) return -1; + break; + case CTRL_U: /* Ctrl+u, delete the whole line. */ + buf[0] = '\0'; + l.pos = l.len = 0; + refreshLine(&l); + break; + case CTRL_K: /* Ctrl+k, delete from current to end of line. */ + buf[l.pos] = '\0'; + l.len = l.pos; + refreshLine(&l); + break; + case CTRL_A: /* Ctrl+a, go to the start of the line */ + linenoiseEditMoveHome(&l); + break; + case CTRL_E: /* ctrl+e, go to the end of the line */ + linenoiseEditMoveEnd(&l); + break; + case CTRL_L: /* ctrl+l, clear screen */ + linenoiseClearScreen(); + refreshLine(&l); + break; + case CTRL_W: /* ctrl+w, delete previous word */ + linenoiseEditDeletePrevWord(&l); + break; + } + flushWrite(); + } + return l.len; +} + +void linenoiseAllowEmpty(bool val) { + allow_empty = val; +} + +int linenoiseProbe(void) { + /* Switch to non-blocking mode */ + int stdin_fileno = fileno(stdin); + int flags = fcntl(stdin_fileno, F_GETFL); + flags |= O_NONBLOCK; + int res = fcntl(stdin_fileno, F_SETFL, flags); + if (res != 0) { + return -1; + } + /* Device status request */ + fprintf(stdout, "\x1b[5n"); + flushWrite(); + + /* Try to read response */ + int timeout_ms = 200; + const int retry_ms = 10; + size_t read_bytes = 0; + while (timeout_ms > 0 && read_bytes < 4) { // response is ESC[0n or ESC[3n + usleep(retry_ms * 1000); + timeout_ms -= retry_ms; + char c; + int cb = read(stdin_fileno, &c, 1); + if (cb < 0) { + continue; + } + read_bytes += cb; + } + /* Restore old mode */ + flags &= ~O_NONBLOCK; + res = fcntl(stdin_fileno, F_SETFL, flags); + if (res != 0) { + return -1; + } + if (read_bytes < 4) { + return -2; + } + return 0; +} + +static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) { + int count; + + if (buflen == 0) { + errno = EINVAL; + return -1; + } + + count = linenoiseEdit(buf, buflen, prompt); + fputc('\n', stdout); + flushWrite(); + return count; +} + +static int linenoiseDumb(char* buf, size_t buflen, const char* prompt) { + /* dumb terminal, fall back to fgets */ + fputs(prompt, stdout); + size_t count = 0; + while (count < buflen) { + int c = fgetc(stdin); + if (c == '\n') { + break; + } else if (c >= 0x1c && c <= 0x1f){ + continue; /* consume arrow keys */ + } else if (c == BACKSPACE || c == 0x8) { + if (count > 0) { + buf[count - 1] = 0; + count --; + } + fputs("\x08 ", stdout); /* Windows CMD: erase symbol under cursor */ + } else { + buf[count] = c; + ++count; + } + fputc(c, stdout); /* echo */ + } + fputc('\n', stdout); + flushWrite(); + return count; +} + +static void sanitize(char* src) { + char* dst = src; + for (int c = *src; c != 0; src++, c = *src) { + if (isprint(c)) { + *dst = c; + ++dst; + } + } + *dst = 0; +} + +/* The high level function that is the main API of the linenoise library. */ +char *linenoise(const char *prompt) { + char *buf; + int ret = ts_shell_alloc(max_cmdline_length, 10, (uint8_t **)&buf); + if (ret != 0) { + return NULL; + } + int count = 0; + if (buf == NULL) { + return NULL; + } + if (!dumbmode) { + count = linenoiseRaw(buf, max_cmdline_length, prompt); + } else { + count = linenoiseDumb(buf, max_cmdline_length, prompt); + } + if (count > 0) { + sanitize(buf); + count = strlen(buf); + } else if (count == 0 && allow_empty) { + /* will return an empty (0-length) string */ + } else { + ts_shell_free((uint8_t *)buf); + return NULL; + } + return buf; +} + +/* This is just a wrapper the user may want to call in order to make sure + * the linenoise returned buffer is freed with the same allocator it was + * created with. Useful when the main program is using an alternative + * allocator. */ +void linenoiseFree(void *ptr) { + ts_shell_free(ptr); +} + +/* ================================ History ================================= */ + +void linenoiseHistoryFree(void) { + if (history) { + for (int j = 0; j < history_len; j++) { + free(history[j]); + } + free(history); + } + history = NULL; +} + +/* This is the API call to add a new entry in the linenoise history. + * It uses a fixed array of char pointers that are shifted (memmoved) + * when the history max length is reached in order to remove the older + * entry and make room for the new one, so it is not exactly suitable for huge + * histories, but will work well for a few hundred of entries. + * + * Using a circular buffer is smarter, but a bit more complex to handle. */ +int linenoiseHistoryAdd(const char *line) { + char *linecopy; + + if (history_max_len == 0) return 0; + + /* Initialization on first call. */ + if (history == NULL) { + history = malloc(sizeof(char*)*history_max_len); + if (history == NULL) return 0; + memset(history,0,(sizeof(char*)*history_max_len)); + } + + /* Don't add duplicated lines. */ + if (history_len && !strcmp(history[history_len-1], line)) return 0; + + /* Add an heap allocated copy of the line in the history. + * If we reached the max length, remove the older line. */ + linecopy = strdup(line); + if (!linecopy) return 0; + if (history_len == history_max_len) { + free(history[0]); + memmove(history, history + 1, sizeof(char*)*(history_max_len-1)); + history_len--; + } + history[history_len] = linecopy; + history_len++; + return 1; +} + +/* Set the maximum length for the history. This function can be called even + * if there is already some history, the function will make sure to retain + * just the latest 'len' elements if the new history length value is smaller + * than the amount of items already inside the history. */ +int linenoiseHistorySetMaxLen(int len) { + char **new; + + if (len < 1) return 0; + if (history) { + int tocopy = history_len; + + new = malloc(sizeof(char*)*len); + if (new == NULL) return 0; + + /* If we can't copy everything, free the elements we'll not use. */ + if (len < tocopy) { + int j; + + for (j = 0; j < tocopy-len; j++) free(history[j]); + tocopy = len; + } + memset(new,0,sizeof(char*)*len); + memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy); + free(history); + history = new; + } + history_max_len = len; + if (history_len > history_max_len) + history_len = history_max_len; + return 1; +} + +/* Save the history in the specified file. On success 0 is returned + * otherwise -1 is returned. */ +int linenoiseHistorySave(const char *filename) { + FILE *fp; + int j; + + fp = fopen(filename,"w"); + if (fp == NULL) return -1; + for (j = 0; j < history_len; j++) + fprintf(fp,"%s\n",history[j]); + fclose(fp); + return 0; +} + +/* Load the history from the specified file. If the file does not exist + * zero is returned and no operation is performed. + * + * If the file exists and the operation succeeded 0 is returned, otherwise + * on error -1 is returned. */ +int linenoiseHistoryLoad(const char *filename) { + FILE *fp = fopen(filename, "r"); + if (fp == NULL) { + return -1; + } + + char *buf = calloc(1, max_cmdline_length); + if (buf == NULL) { + fclose(fp); + return -1; + } + + while (fgets(buf, max_cmdline_length, fp) != NULL) { + char *p; + + p = strchr(buf,'\r'); + if (!p) p = strchr(buf,'\n'); + if (p) *p = '\0'; + linenoiseHistoryAdd(buf); + } + + free(buf); + fclose(fp); + + return 0; +} + +/* Set line maximum length. If len parameter is smaller than + * LINENOISE_MINIMAL_MAX_LINE, -1 is returned + * otherwise 0 is returned. */ +int linenoiseSetMaxLineLen(size_t len) { + if (len < LINENOISE_MINIMAL_MAX_LINE) { + return -1; + } + max_cmdline_length = len; + return 0; +} diff --git a/native/thingset/ts_impl_shell_linenoise.h b/native/thingset/ts_impl_shell_linenoise.h new file mode 100644 index 0000000..0336aed --- /dev/null +++ b/native/thingset/ts_impl_shell_linenoise.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2010-2014, Salvatore Sanfilippo + * Copyright (c) 2010-2013, Pieter Noordhuis + * SPDX-License-Identifier: BSD-2-Clause + */ + +/** + * @file + * @brief Linenoise for native implementation of ThingSet shell app. + */ + +/* linenoise.h -- VERSION 1.0 + * + * Guerrilla line editing library against the idea that a line editing lib + * needs to be 20,000 lines of C code. + * + * See ts_impl_shell_linenoise.c for more. + */ + +#ifndef TS_IMPL_SHELL_LINENOISE_H_ +#define TS_IMPL_SHELL_LINENOISE_H_ + +#include + +/* Forward declaration */ +struct linenoiseCompletions; + +typedef void(linenoiseCompletionCallback)(const char *, struct linenoiseCompletions *); +typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold); +typedef void(linenoiseFreeHintsCallback)(void *); +void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); +void linenoiseSetHintsCallback(linenoiseHintsCallback *); +void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *); +void linenoiseAddCompletion(struct linenoiseCompletions *, const char *); + +int linenoiseProbe(void); +char *linenoise(const char *prompt); +void linenoiseFree(void *ptr); +int linenoiseHistoryAdd(const char *line); +int linenoiseHistorySetMaxLen(int len); +int linenoiseHistorySave(const char *filename); +int linenoiseHistoryLoad(const char *filename); +void linenoiseHistoryFree(void); +void linenoiseClearScreen(void); +void linenoiseSetMultiLine(int ml); +void linenoiseSetDumbMode(int set); +bool linenoiseIsDumbMode(void); +void linenoisePrintKeyCodes(void); +void linenoiseAllowEmpty(bool); +int linenoiseSetMaxLineLen(size_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* TS_IMPL_SHELL_LINENOISE_H_ */ diff --git a/zephyr/thingset/ts_impl_shell.c b/zephyr/thingset/ts_impl_shell.c new file mode 100644 index 0000000..7994651 --- /dev/null +++ b/zephyr/thingset/ts_impl_shell.c @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Zephyr implementation of ThingSet shell app. + */ + +#include "../../apps/shell/ts_shell.h" + +#include + +int ts_impl_shell_execute_cmd(const char* cmd) +{ + const struct shell *exe_shell = shell_backend_dummy_get_ptr(); + + return shell_execute_cmd(exe_shell, cmd); +} + +int ts_impl_shell_execute_output(size_t *sizep, const char **output) +{ + const struct shell *exe_shell = shell_backend_dummy_get_ptr(); + + *output = shell_backend_dummy_get_output(exe_shell, sizep); + + return (*output == NULL) ? -ENAVAIL : 0; +} + +int ts_impl_shell_cmd(const struct ts_shell *shell, size_t argc, char **argv) +{ + int ts_argc; + char **ts_argv; + int ret = ts_shell_split_cmd(argv[1], &ts_argc, &ts_argv); + if (ret != 0) { + return ret; + } + + ret = ts_shell_cmd_ts(shell, ts_argc, ts_argv); + ts_shell_free((const uint8_t *)ts_argv); + + return ret; +} + +/* + * Use own subcommands handler to prevent wildcard expansion (?, .) on chars used in ThingSet. + * Use SHELL_OPT_ARG_RAW to prevent argument split on (", ') used by ThingSet. + */ +SHELL_CMD_ARG_REGISTER(ts, NULL, "ThingSet commands", ts_impl_shell_cmd, 1, SHELL_OPT_ARG_RAW); diff --git a/zephyr/thingset/ts_impl_shell.h b/zephyr/thingset/ts_impl_shell.h new file mode 100644 index 0000000..d5ad78a --- /dev/null +++ b/zephyr/thingset/ts_impl_shell.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Zephyr implementation of ThingSet shell app. + */ + +#ifndef TS_IMPL_SHELL_H_ +#define TS_IMPL_SHELL_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define TS_IMPL_SHELL_CMD_HELP_PRINTED SHELL_CMD_HELP_PRINTED + +/* forward declaration */ +struct thingset_app; + +#define ts_impl_shell shell + +#define ts_impl_shell_print(shell, format, ...) shell_print(shell, format, ##__VA_ARGS__) + +#define ts_impl_shell_error(shell, format, ...) shell_error(shell, format, ##__VA_ARGS__) + +static inline int ts_impl_shell_init(const struct thingset_app *app) +{ + return 0; +} + +static inline int ts_impl_shell_run(const struct thingset_app *app) +{ + return 0; +} + +static inline int ts_impl_shell_get_line(const struct ts_impl_shell *shell, const char *prompt, + char **line) +{ + return -ENOTSUP; +} + +int ts_impl_shell_execute_cmd(const char* cmd); + +int ts_impl_shell_execute_output(size_t *sizep, const char **output); + +static inline int ts_impl_shell_join(void) +{ + return -ENOTSUP; +} + +#ifdef __cplusplus +} +#endif + +#endif /* TS_IMPL_SHELL_H_ */ From b01032b1f800338e046934cb80cf844391b2d2c1 Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Mon, 17 Jan 2022 07:00:42 +0100 Subject: [PATCH 11/37] COM prepare - add ThingSet OSAL for Unity Test Prepare for ThingSet Communication Context: - Add Zephyr implementation of Unity Test framework. Signed-off-by: Bobby Noelte --- zephyr/thingset/ts_impl_unity.h | 476 ++++++++++++++++++++++++++++++++ 1 file changed, 476 insertions(+) create mode 100644 zephyr/thingset/ts_impl_unity.h diff --git a/zephyr/thingset/ts_impl_unity.h b/zephyr/thingset/ts_impl_unity.h new file mode 100644 index 0000000..8b3b59a --- /dev/null +++ b/zephyr/thingset/ts_impl_unity.h @@ -0,0 +1,476 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Zephyr implementation of Unity Test support. + */ + +/* + * ThingSet unit tests are basically made for the Unity test framework. + * Provide some adaptations to make them run under the ztest framework. + */ + +#ifndef ZTEST_UNITY_H_ +#define ZTEST_UNITY_H_ + +#if !CONFIG_ZTEST +#error "You need to define CONFIG_ZTEST." +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include + +#define TEST_MAIN() void test_main(void) + +#define UNITY_BEGIN() + +#define UNITY_END() + +#define RUN_TEST(test_function) \ + ztest_test_suite(test_function, \ + ztest_unit_test_setup_teardown(test_function, setUp, tearDown) \ + ); \ + ztest_run_test_suite(test_function) + +#define TEST_ASSERT(condition) \ + TEST_ASSERT_EXEC( \ + zassert_true(condition, "expect: >true<\nactual: >%d<", (int)(condition)) \ + ) + +#define TEST_ASSERT_TRUE(condition) \ + TEST_ASSERT_EXEC( \ + zassert_true(condition, "expect: >true<\nactual: >%d<", (int)(condition)) \ + ) + +#define TEST_ASSERT_TRUE_MESSAGE(condition, msg) \ + TEST_ASSERT_EXEC( \ + zassert_true(condition, "expect: >true<\nactual: >%d<\n%s", (int)(condition), msg) \ + ) + +#define TEST_ASSERT_FALSE(condition) \ + TEST_ASSERT_EXEC( \ + zassert_false(condition, "expect: >false<\nactual: >%d<", (int)(condition)) \ + ) + +#define TEST_ASSERT_FALSE_MESSAGE(condition, msg) \ + TEST_ASSERT_EXEC( \ + zassert_false(condition, "expect: >false<\nactual: >%d<\n%s", (int)(condition), msg) \ + ) + +#define TEST_ASSERT_NULL(pointer) \ + TEST_ASSERT_EXEC( \ + zassert_is_null(pointer, "expect: >NULL<\nactual: >%d<", (int)(pointer)) \ + ) + +#define TEST_ASSERT_NOT_NULL(pointer) \ + TEST_ASSERT_EXEC( \ + zassert_not_null(pointer, "expect: >!NULL<\nactual: >%d<", (int)(pointer)) \ + ) + +#define TEST_ASSERT_NOT_NULL_MESSAGE(pointer, msg) \ + TEST_ASSERT_EXEC( \ + zassert_not_null(pointer, "expect: >!NULL<\nactual: >%d<\n%s", (int)(pointer), msg) \ + ) + +#define TEST_ASSERT_EQUAL(expected, actual) \ + TEST_ASSERT_EXEC( \ + zassert_equal((int)(expected), (int)(actual), "expect: >%d<\nactual: >%d<", \ + (int)(expected), (int)(actual)) \ + ) + +#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, msg) \ + TEST_ASSERT_EXEC( \ + zassert_equal((int)(expected), (int)(actual), "expect: >%d<\nactual: >%d<\n%s", \ + (int)(expected), (int)(actual), msg) \ + ) + +#define TEST_ASSERT_EQUAL_FLOAT(expected, actual) \ + TEST_ASSERT_EXEC( \ + do { \ + (void)snprintf(&test_assert_buffer[0], TEST_ASSERT_BUFFER_SIZE, \ + "expect: >%g<\nactual: >%g<", (float)(expected), (float)(actual)); \ + if (isinf((float)(expected))) { \ + zassert_true(isinf((float)(actual)), "%s", &test_assert_buffer[0]); \ + } else if (isnan((float)(expected))) { \ + zassert_true(isnan((float)(actual)), "%s", &test_assert_buffer[0]); \ + } else { \ + zassert_within((float)(expected), (float)(actual), 0.0000001, "%s", \ + &test_assert_buffer[0]); \ + } \ + } while(false) \ + ) + +#define TEST_ASSERT_EQUAL_HEX(expected, actual) \ + TEST_ASSERT_EXEC( \ + zassert_equal((unsigned)(expected), (unsigned)(actual), "expect: >0x%x<\nactual: >0x%x<", \ + (unsigned)(expected), (unsigned)(actual)) \ + ) + +#define TEST_ASSERT_EQUAL_HEX8(expected, actual) \ + TEST_ASSERT_EXEC( \ + zassert_equal((uint8_t)(expected), (uint8_t)(actual), "expect: >0x%x<\nactual: >0x%x<", \ + (unsigned)(expected), (unsigned)(actual)) \ + ) + +#define TEST_ASSERT_EQUAL_HEX8_MESSAGE(expected, actual, msg) \ + TEST_ASSERT_EXEC( \ + zassert_equal((uint8_t)(expected), (uint8_t)(actual), "expect: >0x%x<\nactual: >0x%x<\n%s",\ + (unsigned)(expected), (unsigned)(actual), msg) \ + ) + +#define TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, actual, num_elements) \ + TEST_ASSERT_EXEC( \ + do { \ + int len = 0; \ + int ret; \ + ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, \ + "expect: >0x"); \ + len += ret; \ + for (int i = 0; (i < num_elements) && (ret >= 0); i++) { \ + ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, "%02x", \ + (unsigned int)(((const uint8_t*)(expected))[i])); \ + len += ret; \ + } \ + if (ret >= 0) { \ + ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, \ + "<\nactual: >0x"); \ + len += ret; \ + } \ + for (int i = 0; (i < num_elements) && (ret >= 0); i++) { \ + ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, "%02x", \ + (unsigned int)(((const uint8_t*)(actual))[i])); \ + len += ret; \ + } \ + if (ret >= 0) { \ + ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, "<"); \ + } \ + if (ret < 0) { \ + (void)snprintf(&test_assert_buffer[0], TEST_ASSERT_BUFFER_SIZE, \ + "expect/ actual conversion error %d", ret); \ + } \ + test_assert_buffer[TEST_ASSERT_BUFFER_SIZE - 1] = '\0'; \ + } while(false); \ + zassert_equal(memcmp(expected, actual, num_elements), 0, &test_assert_buffer[0]) \ + ) + +#define TEST_ASSERT_EQUAL_HEX8_ARRAY_MESSAGE(expected, actual, num_elements, msg) \ + TEST_ASSERT_EXEC( \ + do { \ + int len = 0; \ + int ret; \ + ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, \ + "expect: >0x"); \ + len += ret; \ + for (int i = 0; (i < num_elements) && (ret >= 0); i++) { \ + ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, "%02x", \ + (unsigned int)(((const uint8_t*)(expected))[i])); \ + len += ret; \ + } \ + if (ret >= 0) { \ + ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, \ + "<\nactual: >0x"); \ + len += ret; \ + } \ + for (int i = 0; (i < num_elements) && (ret >= 0); i++) { \ + ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, "%02x", \ + (unsigned int)(((const uint8_t*)(actual))[i])); \ + len += ret; \ + } \ + if (ret >= 0) { \ + ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, \ + "<\n%s", msg); \ + } \ + if (ret < 0) { \ + (void)snprintf(&test_assert_buffer[0], TEST_ASSERT_BUFFER_SIZE, \ + "expect/ actual conversion error %d\n%s", ret, msg); \ + } \ + test_assert_buffer[TEST_ASSERT_BUFFER_SIZE - 1] = '\0'; \ + } while(false); \ + zassert_equal(memcmp(expected, actual, num_elements), 0, &test_assert_buffer[0]) \ + ) + +#define TEST_ASSERT_EQUAL_INT(expected, actual) \ + TEST_ASSERT_EXEC( \ + zassert_equal((int)(expected), (int)(actual), "expect: >%d<\nactual: >%d<", \ + (int)(expected), (int)(actual)) \ + ) + +#define TEST_ASSERT_EQUAL_INT8(expected, actual) \ + TEST_ASSERT_EXEC( \ + zassert_equal((int8_t)(expected), (int8_t)(actual), "expect: >%u<\nactual: >%u<", \ + (int)(int8_t)(expected), (int)(int8_t)(actual)) \ + ) + +#define TEST_ASSERT_EQUAL_INT16(expected, actual) \ + TEST_ASSERT_EXEC( \ + zassert_equal((int16_t)(expected), (int16_t)(actual), "expect: >%d<\nactual: >0xd<", \ + (int)(int16_t)(expected), (int)(int16_t)(actual)) \ + ) + +#define TEST_ASSERT_EQUAL_INT32(expected, actual) \ + TEST_ASSERT_EXEC( \ + zassert_equal((int32_t)(expected), (int32_t)(actual), "expect: >%d<\nactual: >0xd<", \ + (int)(int32_t)(expected), (int)(int32_t)(actual)) \ + ) + +#define TEST_ASSERT_EQUAL_INT64(expected, actual) \ + TEST_ASSERT_EXEC( \ + zassert_equal((int64_t)(expected), (int64_t)(actual), \ + "expect: >%" PRIi64 "<\nactual: >%" PRIi64 "<", \ + (int64_t)(expected), (int64_t)(actual)) \ + ) + +#define TEST_ASSERT_EQUAL_UINT(expected, actual) \ + TEST_ASSERT_EXEC( \ + zassert_equal((unsigned)(expected), (unsigned)(actual), "expect: >0x%x<\nactual: >0x%x<", \ + (unsigned)(expected), (unsigned)(actual)) \ + ) + +#define TEST_ASSERT_EQUAL_UINT8(expected, actual) \ + TEST_ASSERT_EXEC( \ + zassert_equal((uint8_t)(expected), (uint8_t)(actual), "expect: >%u<\nactual: >%u<", \ + (unsigned)(uint8_t)(expected), (unsigned)(uint8_t)(actual)) \ + ) + +#define TEST_ASSERT_EQUAL_UINT16(expected, actual) \ + TEST_ASSERT_EXEC( \ + zassert_equal((uint16_t)(expected), (uint16_t)(actual), "expect: >%u<\nactual: >%u<", \ + (unsigned)(uint16_t)(expected), (unsigned)(uint16_t)(actual)) \ + ) + +#define TEST_ASSERT_EQUAL_UINT16_MESSAGE(expected, actual, msg) \ + TEST_ASSERT_EXEC( \ + zassert_equal((uint16_t)(expected), (uint16_t)(actual), "expect: >%u<\nactual: >%u<\n%s", \ + (unsigned)(uint16_t)(expected), (unsigned)(uint16_t)(actual), msg) + +#define TEST_ASSERT_EQUAL_UINT32(expected, actual) \ + TEST_ASSERT_EXEC( \ + zassert_equal((uint32_t)(expected), (uint32_t)(actual), "expect: >%u<\nactual: >%u<", \ + (unsigned)(uint32_t)(expected), (unsigned)(uint32_t)(actual)) \ + ) + +#define TEST_ASSERT_EQUAL_UINT64(expected, actual) \ + TEST_ASSERT_EXEC( \ + zassert_equal((uint64_t)(expected), (uint64_t)(actual), \ + "expect: >%" PRIu64 "<\nactual: >%" PRIu64 "<", \ + (uint64_t)(expected), (uint64_t)(actual)) \ + ) + +#define TEST_ASSERT_EQUAL_PTR(expected, actual) \ + TEST_ASSERT_EXEC( \ + zassert_equal_ptr(expected, actual, "expect: >%d<\nactual: >%d<", \ + (int)(expected), (int)(actual)) \ + ) + +#define TEST_ASSERT_EQUAL_PTR_MESSAGE(expected, actual, msg) \ + TEST_ASSERT_EXEC( \ + zassert_equal_ptr(expected, actual, "expect: >%d<\nactual: >%d<\n%s", \ + (int)(expected), (int)(actual), msg) \ + ) + +#define TEST_ASSERT_EQUAL_STRING(expected, actual) \ + TEST_ASSERT_EXEC( \ + zassert_equal(strcmp(expected, actual), 0, "expect: >%s<\nactual: >%s<", expected, actual) \ + ) + +#define TEST_ASSERT_EQUAL_STRING_LEN(expected, actual, len) \ + TEST_ASSERT_EXEC( \ + zassert_equal(strncmp(expected, actual, len), 0, "expect: >%.*s<\nactual: >%.*s<", \ + (size_t)len, expected, (size_t)len, actual) \ + ) + +#define TEST_ASSERT_EQUAL_STRING_LEN_MESSAGE(expected, actual, len, msg) \ + TEST_ASSERT_EXEC( \ + zassert_equal(strncmp(expected, actual, len), 0, "expect: >%.*s<\nactual: >%.*s<\n%s", \ + (size_t)len, expected, (size_t)len, actual, msg) \ + ) + +#define TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, actual, msg) \ + TEST_ASSERT_EXEC( \ + zassert_equal(strcmp(expected, actual), 0, "expect: >%s<\nactual: >%s<\n%s", \ + expected, actual, msg) \ + ) + +#define TEST_ASSERT_EQUAL_size_t(expected, actual) \ + TEST_ASSERT_EXEC( \ + zassert_equal((size_t)(expected),(size_t)(actual), "expect: >%d<\nactual: >%d<", \ + (int)(expected), (int)(actual)) \ + ) + +#define TEST_ASSERT_GREATER_OR_EQUAL_INT(threshold, actual) \ + TEST_ASSERT_EXEC( \ + zassert_true((int)(threshold) <= (int)(actual), "expect: >= >%d<\nactual: >%d<", \ + (int)(threshold), (int)(actual)) \ + ) + +#define TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(threshold, actual, msg) \ + TEST_ASSERT_EXEC( \ + zassert_true((int)(threshold) <= (int)(actual), "expect: >= >%d<\nactual: >%d<\n%s", \ + (int)(threshold), (int)(actual), msg) \ + ) + +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT(threshold, actual) \ + TEST_ASSERT_EXEC( \ + zassert_true((unsigned)(threshold) <= (unsigned)(actual), "expect: >= >%u<\nactual: >%u<", \ + (unsigned int)(threshold), (unsigned int)(actual)) \ + ) + +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT16(threshold, actual) \ + TEST_ASSERT_EXEC( \ + zassert_true((uint16_t)(threshold) <= (uint16_t)(actual), "expect: >= >%u<\nactual: >%u<", \ + (unsigned int)(uint16_t)(threshold), (unsigned int)(uint16_t)(actual)) \ + ) + +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT32(threshold, actual) \ + TEST_ASSERT_EXEC( \ + zassert_true((uint32_t)(threshold) <= (uint32_t)(actual), "expect: >= >%u<\nactual: >%u<", \ + (unsigned int)(uint32_t)(threshold), (unsigned int)(uint32_t)(actual)) \ + ) + +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT64(threshold, actual) \ + TEST_ASSERT_EXEC( \ + zassert_true((uint64_t)(threshold) <= (uint64_t)(actual), \ + "expect: >= >%" PRIu64 "<\nactual: >%" PRIu64 "<", \ + (unsigned int)(uint32_t)(threshold), (unsigned int)(uint32_t)(actual)) \ + ) + +#define TEST_ASSERT_GREATER_OR_EQUAL_size_t(threshold, actual) \ + TEST_ASSERT_EXEC( \ + zassert_true((size_t)(threshold) <= (size_t)(actual), "expect: >= >%d<\nactual: >%d<", \ + (int)(threshold), (int)(actual)) \ + ) + +#define TEST_ASSERT_GREATER_THAN_UINT16(threshold, actual) \ + TEST_ASSERT_EXEC( \ + zassert_true((uint16_t)(threshold) < (uint16_t)(actual), "expect: > >%u<\nactual: >%u<", \ + (unsigned int)(uint16_t)(threshold), (unsigned int)(uint16_t)(actual)) \ + ) + +#define TEST_ASSERT_LESS_THAN_INT(threshold, actual) \ + TEST_ASSERT_EXEC( \ + zassert_true((int)(threshold) > (int)(actual), "expect: < >%d<\nactual: >%d<", \ + (int)(threshold), (int)(actual)) \ + ) + +#define TEST_ASSERT_LESS_THAN_INT_MESSAGE(threshold, actual, msg) \ + TEST_ASSERT_EXEC( \ + zassert_true((int)(threshold) > (int)(actual), "expect: < >%d<\nactual: >%d<\n%s", \ + (int)(threshold), (int)(actual), msg) \ + ) + +#define TEST_ASSERT_LESS_THAN_size_t(threshold, actual) \ + TEST_ASSERT_EXEC( \ + zassert_true((size_t)(threshold) > (size_t)(actual), "expect: < >%d<\nactual: >%d<", \ + (int)(threshold), (int)(actual)) \ + ) + +#define TEST_ASSERT_LESS_THAN_size_t_MESSAGE(threshold, actual, msg) \ + TEST_ASSERT_EXEC( \ + zassert_true((size_t)(threshold) > (size_t)(actual), "expect: < >%d<\nactual: >%d<\n%s", \ + (int)(threshold), (int)(actual), msg) \ + ) + +#define TEST_ASSERT_LESS_THAN_UINT(threshold, actual) \ + TEST_ASSERT_EXEC( \ + zassert_true((unsigned)(threshold) > (unsigned)(actual), "expect: < >%u<\nactual: >%u<", \ + (unsigned int)(threshold), (unsigned int)(actual)) \ + ) + +#define TEST_ASSERT_LESS_THAN_UINT16(threshold, actual) \ + TEST_ASSERT_EXEC( \ + zassert_true((uint16_t)(threshold) > (uint16_t)(actual), "expect: < >%u<\nactual: >%u<", \ + (unsigned)(uint16_t)(threshold), (unsigned)(uint16_t)(actual)) \ + ) + +#define TEST_ASSERT_LESS_THAN_UINT64(threshold, actual) \ + TEST_ASSERT_EXEC( \ + zassert_true((uint64_t)(threshold) > (uint64_t)(actual), \ + "expect: < >%" PRIu64 "<\nactual: >%" PRIu64 "<", \ + (uint64_t)(threshold), (uint64_t)(actual)) \ + ) + +#define TEST_ASSERT_LESS_OR_EQUAL_UINT8(threshold, actual) \ + TEST_ASSERT_EXEC( \ + zassert_true((uint8_t)(threshold) >= (uint8_t)(actual), "expect: <= >%u<\nactual: >%u<", \ + (unsigned)(uint8_t)(threshold), (unsigned)(uint8_t)(actual)) \ + ) + +#define TEST_ASSERT_LESS_OR_EQUAL_size_t(threshold, actual) \ + TEST_ASSERT_EXEC( \ + zassert_true((size_t)(threshold) >= (size_t)(actual), "expect: <= >%d<\nactual: >%d<", \ + (int)(threshold), (int)(actual)) \ + ) + +#define TEST_ASSERT_LESS_OR_EQUAL_size_t_MESSAGE(threshold, actual, msg) \ + TEST_ASSERT_EXEC( \ + zassert_true((size_t)(threshold) >= (size_t)(actual), "expect: <= >%d<\nactual: >%d<\n%s", \ + (int)(threshold), (int)(actual), msg) \ + ) + +#define TEST_ASSERT_NOT_EQUAL(expected, actual) \ + TEST_ASSERT_EXEC( \ + zassert_not_equal(expected, actual, "expect: ! >%d<\nactual: >%d<", \ + (int)(expected), (int)(actual)) \ + ) + +#define TEST_ASSERT_NOT_EQUAL_UINT16(expected, actual) \ + TEST_ASSERT_EXEC( \ + zassert_not_equal((uint16_t)(expected), (uint16_t)(actual), \ + "expect: ! >%u<\nactual: >%u<", \ + (unsigned int)(uint16_t)(expected), (unsigned int)(uint16_t)(actual)) \ + ) + +#define TEST_ASSERT_NOT_EQUAL_PTR(expected, actual) \ + TEST_ASSERT_EXEC( \ + zassert_not_equal((void *)expected, (void *)actual, "expect: ! >%d<\nactual: >", \ + (int)(expected), (int)(actual)) \ + ) + +#define TEST_ASSERT_NOT_EQUAL_PTR_MESSAGE(expected, actual, msg) \ + TEST_ASSERT_EXEC( \ + zassert_not_equal((void *)expected, (void *)actual, "expect: ! >%d<\nactual: >%d<\n%s", \ + (int)(expected), (int)(actual), msg) \ + ) + +#define UNITY_TEST_ASSERT_EQUAL_INT(expected, actual, line, msg) \ + z_zassert((int)(expected) == (int)(actual), #expected " not equal to " #actual, \ + test_file_current, line, test_func_current, msg) + +#define UNITY_TEST_ASSERT_EQUAL_UINT8(expected, actual, line, msg) \ + z_zassert((uint8_t)(expected) == (uint8_t)(actual), #expected " not equal to " #actual, \ + test_file_current, line, test_func_current, msg) + +#define UNITY_TEST_ASSERT_EQUAL_UINT16(expected, actual, line, msg) \ + z_zassert((uint16_t)(expected) == (uint16_t)(actual), #expected " not equal to " #actual, \ + test_file_current, line, test_func_current, msg) + +#define UNITY_TEST_ASSERT_EQUAL_STRING(expected, actual, line, msg) \ + z_zassert(strcmp(expected, actual) == 0, #expected " not equal to " #actual, \ + test_file_current, line, test_func_current, msg) + +#define UNITY_TEST_ASSERT_EQUAL_STRING_LEN(expected, actual, len, line, msg) \ + z_zassert(strncmp(expected, actual, len) == 0, #expected " not equal to " #actual, \ + test_file_current, line, test_func_current, msg) + +#define UNITY_TEST_ASSERT(condition, line, msg) \ + z_zassert((bool)(condition), #condition " not true ", \ + test_file_current, line, test_func_current, msg) + +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT(threshold, actual, line, msg) \ + z_zassert((unsigned int)(threshold) >= (unsigned int)(actual), \ + #actual " not less or equal to " #threshold, \ + test_file_current, line, test_func_current, msg) + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* ZTEST_UNITY_H_ */ From 313f590312857ea51a1b3b9e576e5be353273bae Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Wed, 26 Jan 2022 16:37:28 +0100 Subject: [PATCH 12/37] COM prepare - add ThingSet libc Prepare for ThingSet Communication Context: - Add libc strnstr function missing in Native/ Zephyr implmentation. - Add libc functions missing in Zephyr's minimal libc. Signed-off-by: Bobby Noelte --- native/thingset/ts_impl_libc.c | 27 +++ zephyr/thingset/ts_impl_libc_minimal.c | 241 +++++++++++++++++++++++++ 2 files changed, 268 insertions(+) create mode 100644 native/thingset/ts_impl_libc.c create mode 100644 zephyr/thingset/ts_impl_libc_minimal.c diff --git a/native/thingset/ts_impl_libc.c b/native/thingset/ts_impl_libc.c new file mode 100644 index 0000000..e409e40 --- /dev/null +++ b/native/thingset/ts_impl_libc.c @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021..2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +char *ts_strnstr(const char *haystack, const char *needle, size_t len) +{ + int i; + size_t needle_len; + + if (0 == (needle_len = strnlen(needle, len))) { + return (char *)haystack; + } + + for (i=0; i<=(int)(len-needle_len); i++) { + if ((haystack[0] == needle[0]) && + (0 == strncmp(haystack, needle, needle_len))) { + return (char *)haystack; + } + + haystack++; + } + return NULL; +} diff --git a/zephyr/thingset/ts_impl_libc_minimal.c b/zephyr/thingset/ts_impl_libc_minimal.c new file mode 100644 index 0000000..9918ad5 --- /dev/null +++ b/zephyr/thingset/ts_impl_libc_minimal.c @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2021..2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +/* + * strtod.c -- + * + * Source code for the "strtod" library procedure. + * + * Copyright (c) 1988-1993 The Regents of the University of California. + * Copyright (c) 1994 Sun Microsystems, Inc. + * + * Permission to use, copy, modify, and distribute this + * software and its documentation for any purpose and without + * fee is hereby granted, provided that the above copyright + * notice appear in all copies. The University of California + * makes no representations about the suitability of this + * software for any purpose. It is provided "as is" without + * express or implied warranty. + * + * RCS: @(#) $Id$ + */ + +/** + * @brief Convert string to double. + * + * @param string A decimal ASCII floating-point number, + * optionally preceded by white space. + * Must have form "-I.FE-X", where I is the + * integer part of the mantissa, F is the + * fractional part of the mantissa, and X + * is the exponent. Either of the signs + * may be "+", "-", or omitted. Either I + * or F may be omitted, or both. The decimal + * point isn't necessary unless F is present. + * The "E" may actually be an "e". E and X + * may both be omitted (but not just one). + * @param endPtr If non-NULL, store terminating character's + * address here. + * @return double + */ +double ts_strtod(const char * string, char **endPtr) +{ + + static int maxExponent = 511; /* Largest possible base 10 exponent. Any + * exponent larger than this will already + * produce underflow or overflow, so there's + * no need to worry about additional digits. + */ + static double powersOf10[] = { /* Table giving binary powers of 10. Entry */ + 10., /* is 10^2^i. Used to convert decimal */ + 100., /* exponents into floating-point numbers. */ + 1.0e4, + 1.0e8, + 1.0e16, + 1.0e32, + 1.0e64, + 1.0e128, + 1.0e256 + }; + + int sign, expSign = false; + double fraction, dblExp, *d; + register const char *p; + register int c; + int exp = 0; /* Exponent read from "EX" field. */ + int fracExp = 0; /* Exponent that derives from the fractional + * part. Under normal circumstatnces, it is + * the negative of the number of digits in F. + * However, if I is very long, the last digits + * of I get dropped (otherwise a long I with a + * large negative exponent could cause an + * unnecessary overflow on I alone). In this + * case, fracExp is incremented one for each + * dropped digit. */ + int mantSize; /* Number of digits in mantissa. */ + int decPt; /* Number of mantissa digits BEFORE decimal + * point. */ + const char *pExp; /* Temporarily holds location of exponent + * in string. */ + + /* + * Strip off leading blanks and check for a sign. + */ + + p = string; + while (isspace(*p)) { + p += 1; + } + if (*p == '-') { + sign = true; + p += 1; + } else { + if (*p == '+') { + p += 1; + } + sign = false; + } + + /* + * Count the number of digits in the mantissa (including the decimal + * point), and also locate the decimal point. + */ + + decPt = -1; + for (mantSize = 0; ; mantSize += 1) + { + c = *p; + if (!isdigit(c)) { + if ((c != '.') || (decPt >= 0)) { + break; + } + decPt = mantSize; + } + p += 1; + } + + /* + * Now suck up the digits in the mantissa. Use two integers to + * collect 9 digits each (this is faster than using floating-point). + * If the mantissa has more than 18 digits, ignore the extras, since + * they can't affect the value anyway. + */ + + pExp = p; + p -= mantSize; + if (decPt < 0) { + decPt = mantSize; + } else { + mantSize -= 1; /* One of the digits was the point. */ + } + if (mantSize > 18) { + fracExp = decPt - 18; + mantSize = 18; + } else { + fracExp = decPt - mantSize; + } + if (mantSize == 0) { + fraction = 0.0; + p = string; + goto done; + } else { + int frac1, frac2; + frac1 = 0; + for ( ; mantSize > 9; mantSize -= 1) + { + c = *p; + p += 1; + if (c == '.') { + c = *p; + p += 1; + } + frac1 = 10*frac1 + (c - '0'); + } + frac2 = 0; + for (; mantSize > 0; mantSize -= 1) + { + c = *p; + p += 1; + if (c == '.') { + c = *p; + p += 1; + } + frac2 = 10*frac2 + (c - '0'); + } + fraction = (1.0e9 * frac1) + frac2; + } + + /* + * Skim off the exponent. + */ + + p = pExp; + if ((*p == 'E') || (*p == 'e')) { + p += 1; + if (*p == '-') { + expSign = true; + p += 1; + } else { + if (*p == '+') { + p += 1; + } + expSign = false; + } + while (isdigit(*p)) { + exp = exp * 10 + (*p - '0'); + p += 1; + } + } + if (expSign) { + exp = fracExp - exp; + } else { + exp = fracExp + exp; + } + + /* + * Generate a floating-point number that represents the exponent. + * Do this by processing the exponent one bit at a time to combine + * many powers of 2 of 10. Then combine the exponent with the + * fraction. + */ + + if (exp < 0) { + expSign = true; + exp = -exp; + } else { + expSign = false; + } + if (exp > maxExponent) { + exp = maxExponent; + errno = ERANGE; + } + dblExp = 1.0; + for (d = powersOf10; exp != 0; exp >>= 1, d += 1) { + if (exp & 01) { + dblExp *= *d; + } + } + if (expSign) { + fraction /= dblExp; + } else { + fraction *= dblExp; + } + + done: + if (endPtr != NULL) { + *endPtr = (char *) p; + } + + if (sign) { + return -fraction; + } + return fraction; +}; From 655447c8d3826e1a5af770666ccf4b59cb950efc Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Sun, 19 Dec 2021 07:52:02 +0100 Subject: [PATCH 13/37] COM prepare - add ThingSet environment Prepare for ThingSet Communication Context: - Make the environment a first level information for applications (instead of config info). The environment may change the configuration information. Thus ts_config.h has to be include by the specific environments. - Single out environemnt specific information to specific headers - /ts_impl_env.h - Improve ztest/ unity test support - Provide POSIX.1-2017 support - to be used as an OSAL for different OSes. - Add tinyCBOR support Signed-off-by: Bobby Noelte --- native/thingset/ts_impl_env.h | 166 +++++++++++++++++ src/thingset_env.h | 36 ++++ zephyr/thingset/ts_impl_env.h | 202 ++++++++++++++++++++ zephyr/ztest/ztest_unity.h | 337 ++++++++++++++++++++++++++++------ 4 files changed, 680 insertions(+), 61 deletions(-) create mode 100644 native/thingset/ts_impl_env.h create mode 100644 src/thingset_env.h create mode 100644 zephyr/thingset/ts_impl_env.h diff --git a/native/thingset/ts_impl_env.h b/native/thingset/ts_impl_env.h new file mode 100644 index 0000000..c91409f --- /dev/null +++ b/native/thingset/ts_impl_env.h @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Native implementation of ThingSet environment. + */ + +#ifndef TS_IMPL_ENV_H_ +#define TS_IMPL_ENV_H_ + +/* + * ThingSet configuration (& overrides) + * ==================================== + */ + +#include "../../src/ts_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ThingSet logging + * ================ + */ +#include "ts_impl_log.h" + +/* + * ThingSet Operating System Abstraction Layer (OSAL). + * =================================================== + */ + +/* + * ThingSet unit test + * ================== + */ + +#if TS_CONFIG_UNIT_TEST + +#define TEST_MAIN() int main(void) + +#include "../unity/src/unity.h" + +#endif + +/* + * ThingSet assert + * --------------- + */ +#if TS_CONFIG_ASSERT && TS_CONFIG_UNIT_TEST + +/** + * @def TEST_ASSERT_BUFFER_SIZE + * + * @brief Size of buffer for assertion message build up. + */ +#define TEST_ASSERT_BUFFER_SIZE 300 + +/** + * @brief Buffer for assertion message build up. + * + * @note Buffer must be provided by the test application. + */ +extern char test_assert_buffer[TEST_ASSERT_BUFFER_SIZE]; + +#define TS_IMPL_ASSERT(test, fmt, ...) \ + if (!(test)) { \ + snprintf(&test_assert_buffer[0], TEST_ASSERT_BUFFER_SIZE, fmt, ##__VA_ARGS__); \ + TEST_FAIL_MESSAGE(&test_assert_buffer[0]); \ + } + +#elif TS_CONFIG_ASSERT +#include +#define TS_IMPL_ASSERT(test, fmt, ...) assert(test && fmt) +#else +#define TS_IMPL_ASSERT(test, fmt, ...) +#endif + +/* + * Libc support + * ------------ + */ +#define _POSIX_C_SOURCE 200809L + +/* - atomic support */ +#ifdef __STDC_NO_ATOMICS__ +#error "ThingSet device library needs " +#endif +#if defined(__cplusplus) && __cplusplus <= 201703L +#ifndef _Atomic +#define _Atomic +#endif +#endif +#include +#include + +#include + +char *ts_strnstr(const char *haystack, const char *needle, size_t len); +static inline char *strnstr(const char *haystack, const char *needle, size_t len) { + return ts_strnstr(haystack, needle, len); +} + +/* + * POSIX.1-2017 support + * -------------------- + */ +#include +#include + +/* + * TinyCBOR support + * ---------------- + */ +#include "../tinycbor/src/cbor.h" + +/* + * ts_endian.h + * ---------- + */ +#include "ts_impl_endian.h" + +/* + * ts_macro.h + * ---------- + */ +#include "ts_impl_macro.h" + +/* + * thingset_time.h + * --------------- + */ +#include "ts_impl_time.h" + +/* + * ts_mem.h + * -------- + */ +#include "ts_impl_mem.h" + +/* + * ts_buf.h + * -------- + */ +#include "ts_impl_buf.h" + +/* + * ts_bufq.h + * -------- + */ +#include "ts_impl_bufq.h" + +/* + * Shell application support + * ------------------------- + */ +#include "ts_impl_shell.h" + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* TS_IMPL_ENV_H_ */ diff --git a/src/thingset_env.h b/src/thingset_env.h new file mode 100644 index 0000000..d3d85ab --- /dev/null +++ b/src/thingset_env.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet environment. + */ + + +#ifndef THINGSET_ENV_H_ +#define THINGSET_ENV_H_ + +/** + * @brief ThingSet environment. + * + * The ThingSet device library expects a certain environment (functions, macros) to be available. + * This environment has to be provided by a specific underlying implementation. + * + * @defgroup ts_impl_api ThingSet environment (implementation interface) + * @{ + */ + +#if CONFIG_THINGSET_ZEPHYR +#include "../zephyr/thingset/ts_impl_env.h" +#else +/* Default environment */ +#include "../native/thingset/ts_impl_env.h" +#endif + +/** + * @} + */ + +#endif /* THINGSET_ENV_H_ */ diff --git a/zephyr/thingset/ts_impl_env.h b/zephyr/thingset/ts_impl_env.h new file mode 100644 index 0000000..087c437 --- /dev/null +++ b/zephyr/thingset/ts_impl_env.h @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Zephyr implementation of ThingSet environment. + */ + +#ifndef TS_IMPL_ENV_H_ +#define TS_IMPL_ENV_H_ + +/* + * ThingSet configuration (& overrides) + * ==================================== + */ + +#include "../../src/ts_config.h" + +#if TS_CONFIG_COM && !CONFIG_ARCH_POSIX && !CONFIG_POSIX_API +#error "You need to define CONFIG_POSIX_API." +#endif + +/* Make Zephyr CONFIG_LOG overwrite ThingSet logging support configuration */ +#if TS_CONFIG_LOG != CONFIG_LOG +#warning "ThingSet TS_CONFIG_LOG does not match Zephyr CONFIG_LOG - CONFIG_LOG will prevail!" +#undef TS_CONFIG_LOG +#define TS_CONFIG_LOG CONFIG_LOG +#endif + +/* + * ThingSet logging + * ================ + */ +#include "ts_impl_log.h" + +/* + * ThingSet Operating System Abstraction Layer (OSAL). + * =================================================== + */ + +#include + +/* + * ThingSet unit test + * ================== + */ + +#if TS_CONFIG_UNIT_TEST +#include "ts_impl_unity.h" +#endif + +/* + * ThingSet assert + * --------------- + */ +#if TS_CONFIG_ASSERT && TS_CONFIG_UNIT_TEST +#define TS_IMPL_ASSERT(test, fmt, ...) zassert_true(test, fmt, ##__VA_ARGS__) +#elif TS_CONFIG_ASSERT +#include +#define TS_IMPL_ASSERT(test, fmt, ...) __ASSERT(test, fmt, ##__VA_ARGS__) +#elif TS_CONFIG_LOG +#define TS_IMPL_ASSERT(test, fmt, ...) if (!(test)) { TS_IMPL_LOGE(fmt, ##__VA_ARGS__); } +#else +#define TS_IMPL_ASSERT(test, fmt, ...) +#endif + +/* + * Libc support + * ------------ + */ + +/* - standard C atomic support */ +#ifdef __STDC_NO_ATOMICS__ +#error "ThingSet device library needs " +#endif +#if defined(__cplusplus) && __cplusplus <= 201703L +#ifndef _Atomic +#define _Atomic +#endif +#endif +#include + +#include + +/* - Zephyr minimal c library support */ +#if CONFIG_MINIMAL_LIBC + +#if !CONFIG_MINIMAL_LIBC_MALLOC +#error "You need to define CONFIG_MINIMAL_LIBC_MALLOC if using CONFIG_MINIMAL_LIBC." +#endif + +#if CONFIG_MINIMAL_LIBC_MALLOC_ARENA_SIZE == 0 +#error "You need to define CONFIG_MINIMAL_LIBC_MALLOC_ARENA_SIZE > 0 to allow malloc() to work." +#endif + +/* + * Zephyr's minimal libc is missing some functions. + * Provide !!sufficient!! replacements here. + */ +#include +#include +#include + +#define isnan(value) __builtin_isnan(value) +#define isinf(value) __builtin_isinf(value) + +static inline long long int llroundf(float x) +{ + return __builtin_llroundf(x); +}; + +double ts_strtod(const char * string, char **endPtr); +static inline double strtod(const char * string, char **endPtr) +{ + return ts_strtod(string, endPtr); +}; + +static inline long long strtoll(const char *str, char **endptr, int base) +{ + /* XXX good enough for thingset uses ?*/ + return (long long)strtol(str, endptr, base); +}; + +static inline unsigned long long strtoull(const char *str, char **endptr, int base) +{ + /* XXX good enough for thingset uses ?*/ + return (unsigned long long)strtoul(str, endptr, base); +}; + +#endif /* CONFIG_MINIMAL_LIBC */ + +char *ts_strnstr(const char *haystack, const char *needle, size_t len); +static inline char *strnstr(const char *haystack, const char *needle, size_t len) { + return ts_strnstr(haystack, needle, len); +} + +/* + * POSIX.1-2017 support + * -------------------- + */ +#if CONFIG_ARCH_POSIX +#include +#include +#elif CONFIG_POSIX_API +#include +#include +#endif + +/* + * TinyCBOR support + * ---------------- + */ +#if !CONFIG_TINYCBOR +#include "../../native/tinycbor/src/cbor.h" +#endif + +/* + * ts_endian.h + * ---------- + */ +#include "ts_impl_endian.h" + +/* + * ts_macro.h + * ---------- + */ +#include "ts_impl_macro.h" + +/* + * thingset_time.h + * --------------- + */ +#include "ts_impl_time.h" + +/* + * ts_mem.h + * -------- + */ +#include "ts_impl_mem.h" + +/* + * ts_buf.h + * -------- + */ +#include "ts_impl_buf.h" + +/* + * ts_bufq.h + * ---------- + */ +#include "ts_impl_bufq.h" + +/* + * Shell application support + * ------------------------- + */ +#include "ts_impl_shell.h" + + +#endif /* TS_IMPL_ENV_H_ */ diff --git a/zephyr/ztest/ztest_unity.h b/zephyr/ztest/ztest_unity.h index cafb142..0da05bf 100644 --- a/zephyr/ztest/ztest_unity.h +++ b/zephyr/ztest/ztest_unity.h @@ -16,68 +16,283 @@ extern "C" { #include -#define TEST_ASSERT(condition) \ - zassert_true(condition, "exp: true: actual: %d", (int)(condition)) -#define TEST_ASSERT_TRUE(condition) \ - zassert_true(condition, "exp: true: actual: %d", (int)(condition)) -#define TEST_ASSERT_TRUE_MESSAGE(condition, msg) \ - zassert_true(condition, "exp: true: actual: %d\n%s", (int)(condition)) -#define TEST_ASSERT_FALSE(condition) \ - zassert_false(condition, "exp: false: actual: %d", (int)(condition)) -#define TEST_ASSERT_NULL(pointer) \ - zassert_is_null(pointer, "exp: NULL: actual: %d", (int)(pointer)) -#define TEST_ASSERT_NOT_NULL(pointer) \ - zassert_not_null(pointer, "exp: !NULL: actual: %d", (int)(pointer)) -#define TEST_ASSERT_EQUAL(expected, actual) \ - zassert_equal((int)(expected), (int)(actual), "exp: %d: actual: %d", (int)(expected), (int)(actual)) -#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, msg) \ - zassert_equal((int)(expected), (int)(actual), "exp: %d: actual: %d\n%s", (int)(expected), (int)(actual), msg) -#define TEST_ASSERT_EQUAL_FLOAT(expected, actual) \ - do { \ - char buffer[50]; \ - (void)snprintf(buffer, sizeof(buffer), "exp: %g: actual: %g", (float)(expected), (float)(actual)); \ - zassert_within((float)(expected), (float)(actual), 0.0000001, "%s", &buffer[0]); \ +#include + +/** + * @def TEST_ASSERT_BUFFER_SIZE + * + * @brief Size of buffer for assertion message build up. + */ +#define TEST_ASSERT_BUFFER_SIZE 300 + +/** + * @brief Buffer for assertion message build up. + * + * @note Buffer must be provided by the test application. + */ +extern char test_assert_buffer[TEST_ASSERT_BUFFER_SIZE]; + +#define TEST_ASSERT(condition) \ + zassert_true(condition, "expect: >true<\nactual: >%d<", (int)(condition)) + +#define TEST_ASSERT_TRUE(condition) \ + zassert_true(condition, "expect: >true<\nactual: >%d<", (int)(condition)) + +#define TEST_ASSERT_TRUE_MESSAGE(condition, msg) \ + zassert_true(condition, "expect: >true<\nactual: >%d<\n%s", (int)(condition), msg) + +#define TEST_ASSERT_FALSE(condition) \ + zassert_false(condition, "expect: >false<\nactual: >%d<", (int)(condition)) + +#define TEST_ASSERT_FALSE_MESSAGE(condition, msg) \ + zassert_false(condition, "expect: >false<\nactual: >%d<\n%s", (int)(condition), msg) + +#define TEST_ASSERT_NULL(pointer) \ + zassert_is_null(pointer, "expect: >NULL<\nactual: >%d<", (int)(pointer)) + +#define TEST_ASSERT_NOT_NULL(pointer) \ + zassert_not_null(pointer, "expect: >!NULL<\nactual: >%d<", (int)(pointer)) + +#define TEST_ASSERT_NOT_NULL_MESSAGE(pointer, msg) \ + zassert_not_null(pointer, "expect: >!NULL<\nactual: >%d<\n%s", (int)(pointer), msg) + +#define TEST_ASSERT_EQUAL(expected, actual) \ + zassert_equal((int)(expected), (int)(actual), "expect: >%d<\nactual: >%d<", \ + (int)(expected), (int)(actual)) + +#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, msg) \ + zassert_equal((int)(expected), (int)(actual), "expect: >%d<\nactual: >%d<\n%s", \ + (int)(expected), (int)(actual), msg) + +#define TEST_ASSERT_EQUAL_FLOAT(expected, actual) \ + do { \ + (void)snprintf(&test_assert_buffer[0], TEST_ASSERT_BUFFER_SIZE, \ + "expect: >%g<\nactual: >%g<", (float)(expected), (float)(actual)); \ + if (isinf((float)(expected))) { \ + zassert_true(isinf((float)(actual)), "%s", &test_assert_buffer[0]); \ + } else if (isnan((float)(expected))) { \ + zassert_true(isnan((float)(actual)), "%s", &test_assert_buffer[0]); \ + } else { \ + zassert_within((float)(expected), (float)(actual), 0.0000001, "%s", \ + &test_assert_buffer[0]); \ + } \ } while(false) -#define TEST_ASSERT_EQUAL_HEX(expected, actual) \ - zassert_equal((unsigned)(expected), (unsigned)(actual), "exp: 0x%x: actual: 0x%x", (unsigned)(expected), (unsigned)(actual)) -#define TEST_ASSERT_EQUAL_HEX8(expected, actual) \ - zassert_equal((uint8_t)(expected), (uint8_t)(actual), "exp: 0x%x: actual: 0x%x", (unsigned)(expected), (unsigned)(actual)) -#define TEST_ASSERT_EQUAL_HEX8_MESSAGE(expected, actual, msg) \ - zassert_equal((uint8_t)(expected), (uint8_t)(actual), "exp: 0x%x: actual: 0x%x\n%s", (unsigned)(expected), (unsigned)(actual), msg) -#define TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, actual, num_elements) \ - zassert_equal(memcmp(expected, actual, num_elements), 0, "exp: TBD: actual: TBD") -#define TEST_ASSERT_EQUAL_HEX8_ARRAY_MESSAGE(expected, actual, num_elements, msg) \ - zassert_equal(memcmp(expected, actual, num_elements), 0, "exp: TBD: actual: TBD\n%s", msg) -#define TEST_ASSERT_EQUAL_INT(expected, actual) \ - zassert_equal((int)(expected), (int)(actual), "exp: %d: actual: %d", (int)(expected), (int)(actual)) -#define TEST_ASSERT_EQUAL_INT32(expected, actual) \ - zassert_equal((int32_t)(expected), (int32_t)(actual), "exp: %d: actual: 0xd", (int)(int32_t)(expected), (int)(int32_t)(actual)) -#define TEST_ASSERT_EQUAL_UINT(expected, actual) \ - zassert_equal((unsigned)(expected), (unsigned)(actual), "exp: 0x%x: actual: 0x%x", (unsigned)(expected), (unsigned)(actual)) -#define TEST_ASSERT_EQUAL_UINT8(expected, actual) \ - zassert_equal((uint8_t)(expected), (uint8_t)(actual), "exp: %u: actual: %u", (unsigned)(uint8_t)(expected), (unsigned)(uint8_t)(actual)) -#define TEST_ASSERT_EQUAL_UINT16(expected, actual) \ - zassert_equal((uint16_t)(expected), (uint16_t)(actual), "exp: %u: actual: %u", (unsigned)(uint16_t)(expected), (unsigned)(uint16_t)(actual)) -#define TEST_ASSERT_EQUAL_UINT32(expected, actual) \ - zassert_equal((uint32_t)(expected), (uint32_t)(actual), "exp: %u: actual: %u", (unsigned)(uint32_t)(expected), (unsigned)(uint32_t)(actual)) -#define TEST_ASSERT_EQUAL_PTR(expected, actual) \ - zassert_equal_ptr(expected, actual, "exp: %d: actual: %d", (int)(expected), (int)(actual)) -#define TEST_ASSERT_EQUAL_STRING(expected, actual) \ - zassert_equal(strcmp(expected, actual), 0, "exp: %s: actual: %s", expected, actual) -#define TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, actual, msg) \ - zassert_equal(strcmp(expected, actual), 0, "exp: %s: actual: %s\n%s", expected, actual, msg) -#define TEST_ASSERT_GREATER_OR_EQUAL_size_t(threshold, actual) \ - zassert_true((size_t)(threshold) <= (size_t)(actual), "exp: >= %d: actual: %d", (int)(threshold), (int)(actual)) -#define TEST_ASSERT_LESS_THAN_size_t(threshold, actual) \ - zassert_true((size_t)(threshold) > (size_t)(actual), "exp: < %d: actual: %d", (int)(threshold), (int)(actual)) -#define TEST_ASSERT_LESS_THAN_size_t_MESSAGE(threshold, actual, msg) \ - zassert_true((size_t)(threshold) > (size_t)(actual), "exp: < %d: actual: %d\n%s", (int)(threshold), (int)(actual), msg) -#define TEST_ASSERT_LESS_OR_EQUAL_size_t(threshold, actual) \ - zassert_true((size_t)(threshold) >= (size_t)(actual), "exp: <= %d: actual: %d", (int)(threshold), (int)(actual)) -#define TEST_ASSERT_LESS_OR_EQUAL_size_t_MESSAGE(threshold, actual, msg) \ - zassert_true((size_t)(threshold) >= (size_t)(actual), "exp: <= %d: actual: %d\n%s", (int)(threshold), (int)(actual), msg) -#define TEST_ASSERT_NOT_EQUAL(expected, actual) \ - zassert_not_equal(expected, actual, "exp: %d: actual: %d", (int)(expected), (int)(actual)) + +#define TEST_ASSERT_EQUAL_HEX(expected, actual) \ + zassert_equal((unsigned)(expected), (unsigned)(actual), "expect: >0x%x<\nactual: >0x%x<", \ + (unsigned)(expected), (unsigned)(actual)) + +#define TEST_ASSERT_EQUAL_HEX8(expected, actual) \ + zassert_equal((uint8_t)(expected), (uint8_t)(actual), "expect: >0x%x<\nactual: >0x%x<", \ + (unsigned)(expected), (unsigned)(actual)) + +#define TEST_ASSERT_EQUAL_HEX8_MESSAGE(expected, actual, msg) \ + zassert_equal((uint8_t)(expected), (uint8_t)(actual), "expect: >0x%x<\nactual: >0x%x<\n%s",\ + (unsigned)(expected), (unsigned)(actual), msg) + +#define TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, actual, num_elements) \ + do { \ + int len = 0; \ + int ret; \ + ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, \ + "expect: >0x"); \ + len += ret; \ + for (int i = 0; (i < num_elements) && (ret >= 0); i++) { \ + ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, "%02x", \ + (unsigned int)(((const uint8_t*)(expected))[i])); \ + len += ret; \ + } \ + if (ret >= 0) { \ + ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, \ + "<\nactual: >0x"); \ + len += ret; \ + } \ + for (int i = 0; (i < num_elements) && (ret >= 0); i++) { \ + ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, "%02x", \ + (unsigned int)(((const uint8_t*)(actual))[i])); \ + len += ret; \ + } \ + if (ret >= 0) { \ + ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, "<"); \ + } \ + if (ret < 0) { \ + (void)snprintf(&test_assert_buffer[0], TEST_ASSERT_BUFFER_SIZE, \ + "expect/ actual conversion error %d", ret); \ + } \ + test_assert_buffer[TEST_ASSERT_BUFFER_SIZE - 1] = '\0'; \ + } while(false); \ + zassert_equal(memcmp(expected, actual, num_elements), 0, &test_assert_buffer[0]) + +#define TEST_ASSERT_EQUAL_HEX8_ARRAY_MESSAGE(expected, actual, num_elements, msg) \ + do { \ + int len = 0; \ + int ret; \ + ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, \ + "expect: >0x"); \ + len += ret; \ + for (int i = 0; (i < num_elements) && (ret >= 0); i++) { \ + ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, "%02x", \ + (unsigned int)(((const uint8_t*)(expected))[i])); \ + len += ret; \ + } \ + if (ret >= 0) { \ + ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, \ + "<\nactual: >0x"); \ + len += ret; \ + } \ + for (int i = 0; (i < num_elements) && (ret >= 0); i++) { \ + ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, "%02x", \ + (unsigned int)(((const uint8_t*)(actual))[i])); \ + len += ret; \ + } \ + if (ret >= 0) { \ + ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, \ + "<\n%s", msg); \ + } \ + if (ret < 0) { \ + (void)snprintf(&test_assert_buffer[0], TEST_ASSERT_BUFFER_SIZE, \ + "expect/ actual conversion error %d\n%s", ret, msg); \ + } \ + test_assert_buffer[TEST_ASSERT_BUFFER_SIZE - 1] = '\0'; \ + } while(false); \ + zassert_equal(memcmp(expected, actual, num_elements), 0, &test_assert_buffer[0]) + +#define TEST_ASSERT_EQUAL_INT(expected, actual) \ + zassert_equal((int)(expected), (int)(actual), "expect: >%d<\nactual: >%d<", \ + (int)(expected), (int)(actual)) + +#define TEST_ASSERT_EQUAL_INT8(expected, actual) \ + zassert_equal((int8_t)(expected), (int8_t)(actual), "expect: >%u<\nactual: >%u<", \ + (int)(int8_t)(expected), (int)(int8_t)(actual)) + +#define TEST_ASSERT_EQUAL_INT16(expected, actual) \ + zassert_equal((int16_t)(expected), (int16_t)(actual), "expect: >%d<\nactual: >0xd<", \ + (int)(int16_t)(expected), (int)(int16_t)(actual)) + +#define TEST_ASSERT_EQUAL_INT32(expected, actual) \ + zassert_equal((int32_t)(expected), (int32_t)(actual), "expect: >%d<\nactual: >0xd<", \ + (int)(int32_t)(expected), (int)(int32_t)(actual)) + +#define TEST_ASSERT_EQUAL_INT64(expected, actual) \ + zassert_equal((int64_t)(expected), (int64_t)(actual), \ + "expect: >%" PRIi64 "<\nactual: >%" PRIi64 "<", \ + (int64_t)(expected), (int64_t)(actual)) + +#define TEST_ASSERT_EQUAL_UINT(expected, actual) \ + zassert_equal((unsigned)(expected), (unsigned)(actual), "expect: >0x%x<\nactual: >0x%x<", \ + (unsigned)(expected), (unsigned)(actual)) + +#define TEST_ASSERT_EQUAL_UINT8(expected, actual) \ + zassert_equal((uint8_t)(expected), (uint8_t)(actual), "expect: >%u<\nactual: >%u<", \ + (unsigned)(uint8_t)(expected), (unsigned)(uint8_t)(actual)) + +#define TEST_ASSERT_EQUAL_UINT16(expected, actual) \ + zassert_equal((uint16_t)(expected), (uint16_t)(actual), "expect: >%u<\nactual: >%u<", \ + (unsigned)(uint16_t)(expected), (unsigned)(uint16_t)(actual)) + +#define TEST_ASSERT_EQUAL_UINT16_MESSAGE(expected, actual, msg) \ + zassert_equal((uint16_t)(expected), (uint16_t)(actual), "expect: >%u<\nactual: >%u<\n%s", \ + (unsigned)(uint16_t)(expected), (unsigned)(uint16_t)(actual), msg) + +#define TEST_ASSERT_EQUAL_UINT32(expected, actual) \ + zassert_equal((uint32_t)(expected), (uint32_t)(actual), "expect: >%u<\nactual: >%u<", \ + (unsigned)(uint32_t)(expected), (unsigned)(uint32_t)(actual)) + +#define TEST_ASSERT_EQUAL_UINT64(expected, actual) \ + zassert_equal((uint64_t)(expected), (uint64_t)(actual), \ + "expect: >%" PRIu64 "<\nactual: >%" PRIu64 "<", \ + (uint64_t)(expected), (uint64_t)(actual)) + +#define TEST_ASSERT_EQUAL_PTR(expected, actual) \ + zassert_equal_ptr(expected, actual, "expect: >%d<\nactual: >%d<", \ + (int)(expected), (int)(actual)) + +#define TEST_ASSERT_EQUAL_PTR_MESSAGE(expected, actual, msg) \ + zassert_equal_ptr(expected, actual, "expect: >%d<\nactual: >%d<\n%s", \ + (int)(expected), (int)(actual), msg) + +#define TEST_ASSERT_EQUAL_STRING(expected, actual) \ + zassert_equal(strcmp(expected, actual), 0, "expect: >%s<\nactual: >%s<", expected, actual) + +#define TEST_ASSERT_EQUAL_STRING_LEN(expected, actual, len) \ + zassert_equal(strncmp(expected, actual, len), 0, "expect: >%.*s<\nactual: >%.*s<", \ + (size_t)len, expected, (size_t)len, actual) + +#define TEST_ASSERT_EQUAL_STRING_LEN_MESSAGE(expected, actual, len, msg) \ + zassert_equal(strncmp(expected, actual, len), 0, "expect: >%.*s<\nactual: >%.*s<\n%s", \ + (size_t)len, expected, (size_t)len, actual, msg) + +#define TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, actual, msg) \ + zassert_equal(strcmp(expected, actual), 0, "expect: >%s<\nactual: >%s<\n%s", \ + expected, actual, msg) + +#define TEST_ASSERT_EQUAL_size_t(expected, actual) \ + zassert_equal((size_t)(expected),(size_t)(actual), "expect: >%d<\nactual: >%d<", \ + (int)(expected), (int)(actual)) + +#define TEST_ASSERT_GREATER_OR_EQUAL_INT(threshold, actual) \ + zassert_true((int)(threshold) <= (int)(actual), "expect: >= >%d<\nactual: >%d<", \ + (int)(threshold), (int)(actual)) + +#define TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(threshold, actual, msg) \ + zassert_true((int)(threshold) <= (int)(actual), "expect: >= >%d<\nactual: >%d<\n%s", \ + (int)(threshold), (int)(actual), msg) + +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT32(threshold, actual) \ + zassert_true((uint32_t)(threshold) <= (uint32_t)(actual), "expect: >= >%u<\nactual: >%u<", \ + (unsigned int)(uint32_t)(threshold), (unsigned int)(uint32_t)(actual)) + +#define TEST_ASSERT_GREATER_OR_EQUAL_size_t(threshold, actual) \ + zassert_true((size_t)(threshold) <= (size_t)(actual), "expect: >= >%d<\nactual: >%d<", \ + (int)(threshold), (int)(actual)) + +#define TEST_ASSERT_GREATER_THAN_UINT16(threshold, actual) \ + zassert_true((uint16_t)(threshold) < (uint16_t)(actual), "expect: > >%u<\nactual: >%u<", \ + (unsigned int)(uint16_t)(threshold), (unsigned int)(uint16_t)(actual)) + +#define TEST_ASSERT_LESS_THAN_INT(threshold, actual) \ + zassert_true((int)(threshold) > (int)(actual), "expect: < >%d<\nactual: >%d<", \ + (int)(threshold), (int)(actual)) + +#define TEST_ASSERT_LESS_THAN_INT_MESSAGE(threshold, actual, msg) \ + zassert_true((int)(threshold) > (int)(actual), "expect: < >%d<\nactual: >%d<\n%s", \ + (int)(threshold), (int)(actual), msg) + +#define TEST_ASSERT_LESS_THAN_size_t(threshold, actual) \ + zassert_true((size_t)(threshold) > (size_t)(actual), "expect: < >%d<\nactual: >%d<", \ + (int)(threshold), (int)(actual)) + +#define TEST_ASSERT_LESS_THAN_size_t_MESSAGE(threshold, actual, msg) \ + zassert_true((size_t)(threshold) > (size_t)(actual), "expect: < >%d<\nactual: >%d<\n%s", \ + (int)(threshold), (int)(actual), msg) + +#define TEST_ASSERT_LESS_THAN_UINT16(threshold, actual) \ + zassert_true((uint16_t)(threshold) > (uint16_t)(actual), "expect: < >%u<\nactual: >%u<", \ + (unsigned)(uint16_t)(threshold), (unsigned)(uint16_t)(actual)) + +#define TEST_ASSERT_LESS_OR_EQUAL_UINT8(threshold, actual) \ + zassert_true((uint8_t)(threshold) >= (uint8_t)(actual), "expect: <= >%u<\nactual: >%u<", \ + (unsigned)(uint8_t)(threshold), (unsigned)(uint8_t)(actual)) + +#define TEST_ASSERT_LESS_OR_EQUAL_size_t(threshold, actual) \ + zassert_true((size_t)(threshold) >= (size_t)(actual), "expect: <= >%d<\nactual: >%d<", \ + (int)(threshold), (int)(actual)) + +#define TEST_ASSERT_LESS_OR_EQUAL_size_t_MESSAGE(threshold, actual, msg) \ + zassert_true((size_t)(threshold) >= (size_t)(actual), "expect: <= >%d<\nactual: >%d<\n%s", \ + (int)(threshold), (int)(actual), msg) + +#define TEST_ASSERT_NOT_EQUAL(expected, actual) \ + zassert_not_equal(expected, actual, "expect: ! >%d<\nactual: >%d<", \ + (int)(expected), (int)(actual)) + +#define TEST_ASSERT_NOT_EQUAL_PTR(expected, actual) \ + zassert_not_equal((void *)expected, (void *)actual, "expect: ! >%d<\nactual: >", \ + (int)(expected), (int)(actual)) + +#define TEST_ASSERT_NOT_EQUAL_PTR_MESSAGE(expected, actual, msg) \ + zassert_not_equal((void *)expected, (void *)actual, "expect: ! >%d<\nactual: >%d<\n%s", \ + (int)(expected), (int)(actual), msg) #ifdef __cplusplus } /* extern "C" */ From e77a9155361e287fdda50c65cfa4f9e5856fe707 Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Sun, 19 Dec 2021 07:57:52 +0100 Subject: [PATCH 14/37] COM prepare - add ThingSet objects database Prepare for ThingSet Communication Context: - Make data_objects array a database structure. - Introduce a generic object reference for easy access to data objects. The reference holds the database and object onfo. - Allow the database and the data objects to be in ROM. Split out mutable part of data objects to keep it in RAM. - Move functions that are working on the database to extra files - ts_obj.(hc) to make them usable from all over the ThingSet implementation. - Use prefix ts_obj, TS_OBJ to mark all functions and structures related to ThingSet objects and the database. - Add macro for database definition THINGSET_DATABASE_DEFINE. Signed-off-by: Bobby Noelte --- src/thingset_obj.h | 490 ++++++++++++++++++++++++++++ src/ts_obj.c | 762 ++++++++++++++++++++++++++++++++++++++++++++ src/ts_obj.h | 776 +++++++++++++++++++++++++++++++++++++++++++++ src/ts_obj_log.c | 399 +++++++++++++++++++++++ 4 files changed, 2427 insertions(+) create mode 100644 src/thingset_obj.h create mode 100644 src/ts_obj.c create mode 100644 src/ts_obj.h create mode 100644 src/ts_obj_log.c diff --git a/src/thingset_obj.h b/src/thingset_obj.h new file mode 100644 index 0000000..8345bc5 --- /dev/null +++ b/src/thingset_obj.h @@ -0,0 +1,490 @@ +/* + * Copyright (c) 2017 Martin Jäger / Libre Solar + * Copyright (c) 2021..2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet data objects (public interface) + */ + +#ifndef THINGSET_OBJ_H_ +#define THINGSET_OBJ_H_ + +/** + * @brief ThingSet data objects. + * + * @note All structure definitions and functions that start with the prefix 'ts_' are not part of + * the public API and are just here for technical reasons. They should not be used in + * applications. + * + * @defgroup ts_obj_api_pub ThingSet data objects (public interface) + * @{ + */ + +#include "thingset_env.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Reserved data object IDs + */ +#define TS_ID_ROOT 0x0000U +#define TS_ID_TIME 0x0010U +#define TS_ID_NAME 0x0017U +#define TS_ID_METADATAURL 0x0018U +#define TS_ID_DEVICEID 0x001DU +#define TS_ID_INVALID 0xFFFFU + +/** + * @brief ThingSet data objects C types (used to cast void* pointers) + */ +enum TsType { + TS_T_BOOL, + TS_T_UINT64, + TS_T_INT64, + TS_T_UINT32, + TS_T_INT32, + TS_T_UINT16, + TS_T_INT16, + TS_T_FLOAT32, + TS_T_STRING, + TS_T_BYTES, + TS_T_ARRAY, + TS_T_DECFRAC, // CBOR decimal fraction + TS_T_GROUP, // internal object to describe data hierarchy + TS_T_EXEC, // functions + TS_T_SUBSET, + TS_T_INVALID +}; + +/** + * Data structure to specify a binary data buffer + */ +struct ts_bytes_buffer { + uint8_t *bytes; ///< Pointer to the buffer + uint16_t num_bytes; ///< Actual number of bytes in the buffer +}; + +/** + * Data structure to specify an array data object + */ +struct ts_array_info { + void *ptr; ///< Pointer to the array + uint16_t max_elements; ///< Maximum number of elements in the array + uint16_t num_elements; ///< Actual number of elements in the array + uint8_t type; ///< Type of the array elements +}; + +/** + * @brief ThingSet exec function pointer type. + */ +typedef void(*ts_obj_exec_t)(void); + +/* + * Functions to generate data_objects map and make compiler complain if wrong + * type is passed + */ + +#ifdef __cplusplus + +static inline void *ts_bool_to_void(bool *ptr) { return (void*) ptr; } +static inline void *ts_uint64_to_void(uint64_t *ptr) { return (void*) ptr; } +static inline void *ts_int64_to_void(int64_t *ptr) { return (void*) ptr; } +static inline void *ts_uint32_to_void(uint32_t *ptr) { return (void*) ptr; } +static inline void *ts_int32_to_void(int32_t *ptr) { return (void*) ptr; } +static inline void *ts_uint16_to_void(uint16_t *ptr) { return (void*) ptr; } +static inline void *ts_int16_to_void(int16_t *ptr) { return (void*) ptr; } +static inline void *ts_float_to_void(float *ptr) { return (void*) ptr; } +static inline void *ts_string_to_void(const char *ptr) { return (void*) ptr; } +static inline void *ts_bytes_to_void(struct ts_bytes_buffer *ptr) { return (void *) ptr; } +static inline void *ts_function_to_void(void (*fnptr)()) { return (void*) fnptr; } +static inline void *ts_array_to_void(struct ts_array_info *ptr) { return (void *) ptr; } + +#else + +#define ts_bool_to_void(ptr) ((void*)ptr) +#define ts_uint64_to_void(ptr) ((void*)ptr) +#define ts_int64_to_void(ptr) ((void*)ptr) +#define ts_uint32_to_void(ptr) ((void*)ptr) +#define ts_int32_to_void(ptr) ((void*)ptr) +#define ts_uint16_to_void(ptr) ((void*)ptr) +#define ts_int16_to_void(ptr) ((void*)ptr) +#define ts_float_to_void(ptr) ((void*)ptr) +#define ts_string_to_void(ptr) ((void*)ptr) +#define ts_bytes_to_void(ptr) ((void*)ptr) +#define ts_function_to_void(ptr) ((void*)ptr) +#define ts_array_to_void(ptr) ((void*)ptr) + +#endif + +/** + * @def TS_OBJ + * + * Initialise ThingSet database data object. + */ +#if !defined(__cplusplus) || (__cplusplus == 202002) +/* Designated initializer are a feature of C++20 and C in general */ +#define TS_OBJ(obj_id, obj_parent_id, obj_name, obj_data_ptr, obj_type, obj_detail, obj_access, \ + obj_subsets) \ + { .id = obj_id, \ + .type = obj_type, \ + .name = obj_name, \ + .parent = obj_parent_id, \ + .data = obj_data_ptr, \ + .meta_default.db_id = 0, \ + .meta_default.subsets = obj_subsets, \ + .meta_default.detail = obj_detail, \ + .meta_default.access = obj_access \ + } +#else +#define TS_OBJ(obj_id, obj_parent_id, obj_name, obj_data_ptr, obj_type, obj_detail, obj_access, \ + obj_subsets) \ + { obj_id, \ + obj_type, \ + obj_name, \ + obj_parent_id, \ + obj_data_ptr, \ + 0, \ + obj_subsets, \ + obj_detail, \ + obj_access \ + } +#endif + +#define TS_ITEM_BOOL(id, name, bool_ptr, parent_id, access, subsets) \ + TS_OBJ(id, parent_id, name, ts_bool_to_void(bool_ptr), TS_T_BOOL, 0, access, subsets) + +#define TS_ITEM_UINT64(id, name, uint64_ptr, parent_id, access, subsets) \ + TS_OBJ(id, parent_id, name, ts_uint64_to_void(uint64_ptr), TS_T_UINT64, 0, access, subsets) + +#define TS_ITEM_INT64(id, name, int64_ptr, parent_id, access, subsets) \ + TS_OBJ(id, parent_id, name, ts_int64_to_void(int64_ptr), TS_T_INT64, 0, access, subsets) + +#define TS_ITEM_UINT32(id, name, uint32_ptr, parent_id, access, subsets) \ + TS_OBJ(id, parent_id, name, ts_uint32_to_void(uint32_ptr), TS_T_UINT32, 0, access, subsets) + +#define TS_ITEM_INT32(id, name, int32_ptr, parent_id, access, subsets) \ + TS_OBJ(id, parent_id, name, ts_int32_to_void(int32_ptr), TS_T_INT32, 0, access, subsets) + +#define TS_ITEM_UINT16(id, name, uint16_ptr, parent_id, access, subsets) \ + TS_OBJ(id, parent_id, name, ts_uint16_to_void(uint16_ptr), TS_T_UINT16, 0, access, subsets) + +#define TS_ITEM_INT16(id, name, int16_ptr, parent_id, access, subsets) \ + TS_OBJ(id, parent_id, name, ts_int16_to_void(int16_ptr), TS_T_INT16, 0, access, subsets) + +#define TS_ITEM_FLOAT(id, name, float_ptr, digits, parent_id, access, subsets) \ + TS_OBJ(id, parent_id, name, ts_float_to_void(float_ptr), TS_T_FLOAT32, digits, access, subsets) + +#define TS_ITEM_DECFRAC(id, name, mantissa_ptr, exponent, parent_id, access, subsets) \ + TS_OBJ(id, parent_id, name, ts_int32_to_void(mantissa_ptr), TS_T_DECFRAC, exponent, access, subsets) + +#define TS_ITEM_STRING(id, name, char_ptr, buf_size, parent_id, access, subsets) \ + TS_OBJ(id, parent_id, name, ts_string_to_void(char_ptr), TS_T_STRING, buf_size, access, subsets) + +#define TS_ITEM_BYTES(id, name, bytes_buffer_ptr, buf_size, parent_id, access, subsets) \ + TS_OBJ(id, parent_id, name, ts_bytes_to_void(bytes_buffer_ptr), TS_T_BYTES, buf_size, access, subsets) + +#define TS_FUNCTION(id, name, void_function_ptr, parent_id, access) \ + TS_OBJ(id, parent_id, name, ts_function_to_void(void_function_ptr), TS_T_EXEC, 0, access, 0) + +#define TS_ITEM_ARRAY(id, name, array_info_ptr, digits, parent_id, access, subsets) \ + TS_OBJ(id, parent_id, name, ts_array_to_void(array_info_ptr), TS_T_ARRAY, digits, access, subsets) + +#define TS_SUBSET(id, name, subset, parent_id, access) \ + TS_OBJ(id, parent_id, name, NULL, TS_T_SUBSET, subset, access, 0) + +#define TS_GROUP(id, name, void_function_cb_ptr, parent_id) \ + TS_OBJ(id, parent_id, name, ts_function_to_void(void_function_cb_ptr), TS_T_GROUP, 0, TS_ANY_R, 0) + +/* + * Deprecated defines for spec v0.3 to maintain compatibility + */ + +#define TS_NODE_BOOL(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ + TS_ITEM_BOOL(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ + _Pragma ("GCC warning \"'TS_NODE_BOOL' macro is deprecated, use 'TS_ITEM_BOOL'\"") + +#define TS_NODE_UINT64(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ + TS_ITEM_UINT64(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ + _Pragma ("GCC warning \"'TS_NODE_UINT64' macro is deprecated, use 'TS_ITEM_UINT64'\"") + +#define TS_NODE_INT64(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ + TS_ITEM_INT64(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ + _Pragma ("GCC warning \"'TS_NODE_INT64' macro is deprecated, use 'TS_ITEM_INT64'\"") + +#define TS_NODE_UINT32(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ + TS_ITEM_UINT32(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ + _Pragma ("GCC warning \"'TS_NODE_UINT32' macro is deprecated, use 'TS_ITEM_UINT32'\"") + +#define TS_NODE_INT32(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ + TS_ITEM_INT32(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ + _Pragma ("GCC warning \"'TS_NODE_INT32' macro is deprecated, use 'TS_ITEM_INT32'\"") + +#define TS_NODE_UINT16(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ + TS_ITEM_UINT16(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ + _Pragma ("GCC warning \"'TS_NODE_UINT16' macro is deprecated, use 'TS_ITEM_UINT16'\"") + +#define TS_NODE_INT16(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ + TS_ITEM_INT16(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ + _Pragma ("GCC warning \"'TS_NODE_INT16' macro is deprecated, use 'TS_ITEM_INT16'\"") + +#define TS_NODE_FLOAT(_id, _name, _data_ptr, _digits, _parent, _acc, _pubsub) \ + TS_ITEM_FLOAT(_id, _name, _data_ptr, _digits, _parent, _acc, _pubsub) \ + _Pragma ("GCC warning \"'TS_NODE_FLOAT' macro is deprecated, use 'TS_ITEM_FLOAT'\"") + +#define TS_NODE_STRING(_id, _name, _data_ptr, _buf_size, _parent, _acc, _pubsub) \ + TS_ITEM_STRING(_id, _name, _data_ptr, _buf_size, _parent, _acc, _pubsub) \ + _Pragma ("GCC warning \"'TS_NODE_STRING' macro is deprecated, use 'TS_ITEM_STRING'\"") + +#define TS_NODE_BYTES(_id, _name, _data_ptr, _buf_size, _parent, _acc, _pubsub) \ + TS_ITEM_BYTES(_id, _name, _data_ptr, _buf_size, _parent, _acc, _pubsub) \ + _Pragma ("GCC warning \"'TS_NODE_BYTES' macro is deprecated, use 'TS_ITEM_BYTES'\"") + +#define TS_NODE_EXEC(_id, _name, _function_ptr, _parent, _acc) \ + TS_FUNCTION(_id, _name, _function_ptr, _parent, _acc) \ + _Pragma ("GCC warning \"'TS_NODE_EXEC' macro is deprecated, use 'TS_FUNCTION'\"") + +#define TS_NODE_ARRAY(_id, _name, _data_ptr, _digits, _parent, _acc, _pubsub) \ + TS_ITEM_ARRAY(_id, _name, _data_ptr, _digits, _parent, _acc, _pubsub) \ + _Pragma ("GCC warning \"'TS_NODE_ARRAY' macro is deprecated, use 'TS_ITEM_ARRAY'\"") + +#define TS_NODE_PUBSUB(_id, _name, _pubsub_channel, _parent, _acc, _pubsub) \ + TS_SUBSET(_id, _name, _pubsub_channel, _parent, _acc) \ + _Pragma ("GCC warning \"'TS_NODE_PUBSUB' macro is deprecated, use 'TS_SUBSET'\"") + +#define TS_NODE_PATH(_id, _name, _parent, _callback) \ + TS_GROUP(_id, _name, _callback, _parent) \ + _Pragma ("GCC warning \"'TS_NODE_PATH' macro is deprecated, use 'TS_GROUP'\"") + +/* + * Defines to make data object definitions more explicit + */ +#define TS_NO_CALLBACK NULL + +/* + * Access rights macros for data objects + */ +#define TS_ROLE_USR (1U << 0) // normal user +#define TS_ROLE_EXP (1U << 1) // expert user +#define TS_ROLE_MKR (1U << 2) // maker + +#define TS_USR_MASK (TS_ROLE_USR << 8 | TS_ROLE_USR) +#define TS_EXP_MASK (TS_ROLE_EXP << 8 | TS_ROLE_EXP) +#define TS_MKR_MASK (TS_ROLE_MKR << 8 | TS_ROLE_MKR) + +/** + * @brief Read access flags mask. + * + * Read flags are stored in the 8 least-significant bits. + */ +#define TS_READ_MASK (0x00FF & (TS_USR_MASK | TS_EXP_MASK | TS_MKR_MASK)) + +/** + * @brief Write access flags mask. + * + * Write flags are stored in the 8 most-significant bits. + */ +#define TS_WRITE_MASK (0xFF00 & (TS_USR_MASK | TS_EXP_MASK | TS_MKR_MASK)) + +#define TS_USR_R (TS_READ_MASK & TS_USR_MASK) +#define TS_EXP_R (TS_READ_MASK & TS_EXP_MASK) +#define TS_MKR_R (TS_READ_MASK & TS_MKR_MASK) +#define TS_ANY_R (TS_READ_MASK) + +#define TS_USR_W (TS_WRITE_MASK & TS_USR_MASK) +#define TS_EXP_W (TS_WRITE_MASK & TS_EXP_MASK) +#define TS_MKR_W (TS_WRITE_MASK & TS_MKR_MASK) +#define TS_ANY_W (TS_WRITE_MASK) + +#define TS_USR_RW (TS_USR_R | TS_USR_W) +#define TS_EXP_RW (TS_EXP_R | TS_EXP_W) +#define TS_MKR_RW (TS_MKR_R | TS_MKR_W) +#define TS_ANY_RW (TS_ANY_R | TS_ANY_W) + +/** @brief ThingSet data objects database ID type */ +typedef uint16_t ts_obj_db_id_t; + +/** @brief ThingSet data object ID type */ +typedef uint16_t ts_obj_id_t; + +/* support for legacy code with old nomenclature */ +typedef ts_obj_id_t ts_ctx_node_id_t __attribute__((deprecated)); + +/** + * @brief Struct for the mutable meta data of a ThingSet data object. + */ +struct ts_obj_meta { + /** @brief Data objects database ID of database the object belongs to. */ + ts_obj_db_id_t db_id; + + /** + * @brief Flags to assign data item to different data item subsets. + * + * May be used for e.g. for publication messages. + */ + uint16_t subsets; + + /** + * @brief Object type dependent details. + * + * - Exponent (10^exponent = factor to convert to SI unit) for decimal fraction type, + * - Decimal digits to use for printing of floats in JSON strings or + * - Length of string buffer for string type + */ + int16_t detail; + + /** @brief Flags to define read/write access */ + uint16_t access; +}; + +/** + * @brief ThingSet data object struct. + */ +struct ts_obj { + /** @brief ThingSet data object ID of data object. */ + ts_obj_id_t id; + + /** + * @brief Type of data object. + * + * One of TS_TYPE_INT32, _FLOAT, ... + */ + uint8_t type; + + /** @brief Name of data object */ + const char *name; + + /** @brief ThingSet data object ID of parent data object. */ + ts_obj_id_t parent; + + /** + * @brief Pointer to the variable containing the data. + * + * The variable type must match the type as specified. + */ + void *data; + + /** @brief Default value for mutable meta data of data object. */ + struct ts_obj_meta meta_default; +}; + +/** + * @brief ThingSet data objects database structure. + */ +struct ts_obj_db { + /** + * @brief ID of data objects database. + * + * Identifier for the database on this device. + */ + ts_obj_db_id_t id; + + /** @brief Unique context identifier of the context the database belongs to. */ + const uint64_t *uuid; + + /** @brief Array of data objects. */ + const struct ts_obj *objects; + + /** @brief Array of mutable meta data of data objects. */ + struct ts_obj_meta *meta; + + /** @brief Number of objects in the data_objects array */ + size_t num; + + /** + * @brief Additional data for remote context objects database. + * + * This is NULL for a local context objects database. + */ + struct ts_obj_rdb *rdb; +}; + +/* support for legacy code with old nomenclature */ +typedef struct ts_obj ts_data_node __attribute__((deprecated)); + +/** + * @def THINGSET_DATABASE_DEFINE + * + * @brief Define a ThingSet data objects database for local context data objects. + * + * Use like this: + * + * THINGSET_DATABASE_DEFINE(THINGSET_LOCID_DEFAULT, 0x123456789012345ULL, + * TS_GROUP(ID_INFO, "info", TS_NO_CALLBACK, ID_ROOT), + * TS_ITEM_STRING(0x19, "Manufacturer", manufacturer, 0, I D_INFO, TS_ANY_R, 0) + * ); + * + * @param locid Local context identifier. Paramter must expand to a positiv number. + * Usually 0. Limit defined by @ref TS_CONFIG_LOCAL_COUNT. + * @param ctx_uuid Unique identifier for the context the database belongs to. + * @param ... Initialisation for the data objects. + */ +#if !defined(__cplusplus) || (__cplusplus == 202002) +/* Designated initializer are a feature of C++20 and C in general */ +#define THINGSET_DATABASE_DEFINE(locid, ctx_uuid, ...) \ + const struct ts_obj TS_CAT(ts_obj_db_objects_, locid)[] = { __VA_ARGS__ }; \ + struct ts_obj_meta TS_CAT(ts_obj_db_meta_, locid) \ + [TS_ARRAY_SIZE(TS_CAT(ts_obj_db_objects_, locid))]; \ + const struct ts_obj_db TS_CAT(ts_obj_db_, locid) = { \ + .id = locid, \ + .uuid = &ctx_uuid, \ + .objects = &TS_CAT(ts_obj_db_objects_, locid)[0], \ + .meta = &TS_CAT(ts_obj_db_meta_, locid)[0], \ + .num = TS_ARRAY_SIZE(TS_CAT(ts_obj_db_objects_, locid)), \ + .rdb = NULL \ + } +#else +#define THINGSET_DATABASE_DEFINE(locid, ctx_uuid, ...) \ + const struct ts_obj TS_CAT(ts_obj_db_objects_, locid)[] = { __VA_ARGS__ }; \ + struct ts_obj_meta TS_CAT(ts_obj_db_meta_, locid) \ + [TS_ARRAY_SIZE(TS_CAT(ts_obj_db_objects_, locid))]; \ + const struct ts_obj_db TS_CAT(ts_obj_db_, locid) = { \ + locid, \ + &ctx_uuid, \ + &TS_CAT(ts_obj_db_objects_, locid)[0], \ + &TS_CAT(ts_obj_db_meta_, locid)[0], \ + TS_ARRAY_SIZE(TS_CAT(ts_obj_db_objects_, locid)), \ + NULL \ + } +#endif + +/** + * @brief ThingSet data objects database object identifier. + */ +typedef uint16_t ts_obj_db_oid_t; + +/** + * @brief ThingSet data objects database object reference. + */ +typedef struct ts_obj_db_oref { + /** @brief Data objects database ID. */ + ts_obj_id_t db_id; + /** @brief Data objects database object ID. */ + ts_obj_db_oid_t db_oid; +} thingset_oref_t; + +/** + * @brief Print all data objects as a structured JSON text to log buffer. + * + * @warning This is a recursive function and might cause stack overflows if run in constrained + * devices with large data object tree. Use with care and for testing only! + * + * @param[in] oref Database object reference to root object where to start with printing. + * @param[in] log Pointer to the log buffer. + * @param[in] len Length of the log buffer. + */ +void thingset_obj_log(thingset_oref_t oref, char *log, size_t len); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +/** + * @} + */ + +#endif /* THINGSET_OBJ_H_ */ diff --git a/src/ts_obj.c b/src/ts_obj.c new file mode 100644 index 0000000..0a3a577 --- /dev/null +++ b/src/ts_obj.c @@ -0,0 +1,762 @@ +/* + * Copyright (c) 2017 Martin Jäger / Libre Solar + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet data objects + */ + +#include "thingset_env.h" + +#include +#include +#include +#include + +#include "ts_mem.h" +#include "ts_obj.h" +#include "ts_log.h" + +/* Remote contexts' objects databases pool. */ +struct ts_obj_rdb ts_obj_rdbs[TS_CONFIG_REMOTE_COUNT]; + +/* Objects databases by database id. */ +const struct ts_obj_db *const ts_obj_dbs[TS_CONFIG_LOCAL_COUNT + TS_CONFIG_REMOTE_COUNT] = { +#if TS_CONFIG_LOCAL_COUNT > 0 + &ts_obj_db_0, +#endif +#if TS_CONFIG_LOCAL_COUNT > 1 + &ts_obj_db_1, +#endif +#if TS_CONFIG_LOCAL_COUNT > 2 + &ts_obj_db_2, +#endif +#if TS_CONFIG_LOCAL_COUNT > 3 + &ts_obj_db_3, +#endif +#if TS_CONFIG_LOCAL_COUNT > 4 + &ts_obj_db_4, +#endif +#if TS_CONFIG_LOCAL_COUNT > 5 +#error "Local context databases limited to 5" +#endif +#if TS_CONFIG_REMOTE_COUNT > 0 + &ts_obj_rdbs[TS_CONFIG_LOCAL_COUNT + 0].db, +#endif +#if TS_CONFIG_REMOTE_COUNT > 1 + &ts_obj_rdbs[TS_CONFIG_LOCAL_COUNT + 1].db, +#endif +#if TS_CONFIG_REMOTE_COUNT > 2 + &ts_obj_rdbs[TS_CONFIG_LOCAL_COUNT + 2].db, +#endif +#if TS_CONFIG_REMOTE_COUNT > 3 + &ts_obj_rdbs[TS_CONFIG_LOCAL_COUNT + 3].db, +#endif +#if TS_CONFIG_REMOTE_COUNT > 4 + &ts_obj_rdbs[TS_CONFIG_LOCAL_COUNT + 4].db, +#endif +#if TS_CONFIG_REMOTE_COUNT > 5 + &ts_obj_rdbs[TS_CONFIG_LOCAL_COUNT + 5].db, +#endif +#if TS_CONFIG_REMOTE_COUNT > 6 + &ts_obj_rdbs[TS_CONFIG_LOCAL_COUNT + 6].db, +#endif +#if TS_CONFIG_REMOTE_COUNT > 7 + &ts_obj_rdbs[TS_CONFIG_LOCAL_COUNT + 7].db, +#endif +#if TS_CONFIG_REMOTE_COUNT > 8 +#error "Remote context databases limited to 8" +#endif +}; + +/* Remote contexts' data objects pool */ +struct ts_obj ts_obj_remotes[TS_CONFIG_REMOTE_OBJECT_COUNT]; + +/* Management data for remote contexts' data objects pool. */ +struct ts_obj_remote_mngmt ts_obj_remotes_mngmt[TS_CONFIG_REMOTE_OBJECT_COUNT]; + +/* Memory pool for remote contexts' data objects data */ +TS_MEM_DEFINE(ts_obj_remotes_data, TS_CONFIG_REMOTE_OBJECT_COUNT * 20); + +static bool ts_obj_db_initialized = false; + +void ts_obj_db_init(void) +{ + if (ts_obj_db_initialized) { + return; + } + + /* Initialize data objects databases for local context */ + for (ts_obj_db_id_t did = 0; did < TS_CONFIG_LOCAL_COUNT; did++) { + /* database for local context objects */ + const struct ts_obj_db *db = ts_obj_db_by_id(did); + for (unsigned int i = 0; i < db->num; i++) { + db->meta[i] = db->objects[i].meta_default; + } + (void)ts_obj_db_check_id_duplicates(did); + } + + /* Initialize data objects databases pool for remote context */ + for (ts_obj_db_id_t did = TS_CONFIG_LOCAL_COUNT; did < TS_ARRAY_SIZE(ts_obj_dbs); did++) { + /* database for remote context objects */ + struct ts_obj_rdb *rdb = &ts_obj_rdbs[did - TS_CONFIG_LOCAL_COUNT]; + rdb->uuid = 0; + /* Link remote context object database to remote objects pool */ + rdb->remotes = &ts_obj_remotes[0]; + + /* Provide same interface as local context data objects database */ + rdb->db.objects = &ts_obj_remotes[0]; + rdb->db.num = 0; + rdb->db.meta = NULL; + rdb->db.uuid = &rdb->uuid; + rdb->db.id = TS_OBJ_DB_ID_INVALID; + } + + /* Initialize data objects pool for remote context */ + for (ts_obj_db_oid_t oid = 0; oid < TS_CONFIG_REMOTE_OBJECT_COUNT; oid++) { + ts_obj_remotes_mngmt[oid].ref_count = 0; + ts_obj_remotes[oid].id = TS_ID_INVALID; + ts_obj_remotes[oid].type = TS_T_INVALID; + ts_obj_remotes[oid].name = NULL; + ts_obj_remotes[oid].data = NULL; + ts_obj_remotes[oid].parent = TS_ID_INVALID; + ts_obj_remotes[oid].meta_default.db_id = TS_OBJ_DB_ID_INVALID; + ts_obj_remotes[oid].meta_default.access = 0; + ts_obj_remotes[oid].meta_default.detail = 0; + ts_obj_remotes[oid].meta_default.subsets = 0; + } + + ts_obj_db_initialized = true; +} + +int ts_obj_db_check_id_duplicates(ts_obj_db_id_t did) +{ + int ret = 0; + + TS_OBJ_DB_FOREACH_OREF(ts_obj_db_oref(did, 0), oref_i) { + TS_OBJ_DB_FOREACH_OREF(oref_i, oref_j) { + if (ts_obj_db_oref_is_same(oref_i, oref_j)) { + continue; + } + if (ts_obj_id(oref_i) == ts_obj_id(oref_j)) { + TS_LOGE("OBJ: %s duplicate data object ID 0x%X.\n", __func__, + (unsigned int)ts_obj_id(oref_i)); + ret = -EALREADY; + } + } + } + + return ret; +} + +thingset_oref_t ts_obj_db_oref_root(ts_obj_db_id_t did) +{ + thingset_oref_t oref; + + oref.db_id = did; + oref.db_oid = TS_OBJ_DB_OID_ROOT; + + return oref; +} + +thingset_oref_t ts_obj_db_oref_any(ts_obj_db_id_t did) +{ + thingset_oref_t oref; + + oref.db_id = did; + oref.db_oid = TS_OBJ_DB_OID_ANY; + + return oref; +} + +thingset_oref_t ts_obj_db_oref_by_object(const struct ts_obj *object) +{ + ts_obj_db_id_t did = object->meta_default.db_id; + const struct ts_obj_db *db = ts_obj_db_by_id(did); + + size_t idx = ((uint8_t *)object - (uint8_t *)&db->objects[0])/(sizeof(struct ts_obj)); + TS_ASSERT(idx < db->num, "OBJ: %s on invalid object reference '%u' (> %u)", __func__, + (unsigned int)idx, (unsigned int)db->num); + TS_ASSERT(&db->objects[idx] == object, "Invalid object to reference conversion"); + + thingset_oref_t oref; + oref.db_oid = (uint16_t)idx; + oref.db_id = did; + return oref; +} + +bool ts_obj_db_oref_is_valid(thingset_oref_t oref) +{ + if (oref.db_id >= TS_ARRAY_SIZE(ts_obj_dbs)) { + return false; + } + if ((oref.db_oid == TS_OBJ_DB_OID_ROOT) || (oref.db_oid == TS_OBJ_DB_OID_ANY)) { + return true; + } + if (oref.db_id < TS_CONFIG_LOCAL_COUNT) { + /* Local context database */ + const struct ts_obj_db *db = ts_obj_db_by_id(oref.db_id); + if (oref.db_oid >= db->num) { + return false; + } + } + else { + /* Remote context database */ + if (oref.db_oid >= TS_CONFIG_REMOTE_OBJECT_COUNT) { + return false; + } + if (ts_obj_meta(oref)->db_id != oref.db_id) { + return false; + } + } + return true; +} + +int ts_obj_db_oref_by_id(ts_obj_db_id_t did, ts_obj_id_t obj_id, thingset_oref_t *oref) +{ + if (obj_id == TS_ID_ROOT) { + /* Object id of root object */ + oref->db_id = did; + oref->db_oid = TS_OBJ_DB_OID_ROOT; + return 0; + } + + TS_OBJ_DB_FOREACH_OREF(ts_obj_db_oref(did, 0), object_oref) { + if (ts_obj_id(object_oref) == obj_id) { + *oref = object_oref; + return 0; + } + } + + TS_LOGD("OBJ: %s did not find object with id '%u' in database '%u'", + __func__, (unsigned int)obj_id, (unsigned int)did); + return -ENODATA; +} + +int ts_obj_by_name(thingset_oref_t parent, const char *name, size_t len, thingset_oref_t *oref) +{ + TS_ASSERT(ts_obj_db_oref_is_valid(parent), + "OBJ: %s for '%.*s' on invalid parent object reference (%u:%u)", + __func__, (int)len, name, (unsigned int)parent.db_id, (unsigned int)parent.db_oid); + + ts_obj_id_t parent_id; + if (ts_obj_db_oref_is_any(parent)) { + parent_id = TS_ID_INVALID; + } + else { + parent_id = ts_obj_id(parent); + } + + TS_OBJ_DB_FOREACH_OREF(ts_obj_db_oref(parent.db_id, 0), child_oref) { + if ((parent_id == TS_ID_INVALID) || (parent_id == ts_obj_parent_id(child_oref))) { + const char *child_name = ts_obj_name(child_oref); + if ((child_name != 0) && (strncmp(child_name, name, len) == 0) && + /* without length check foo and fooBar would be recognized as equal */ + (strnlen(child_name, len + 1) == len)) { + *oref = child_oref; + return 0; + } + } + } + + TS_LOGD("OBJ: %s did not find child '%.*s' of parent object (%u:%u) with id '%u'", + __func__, (int)len, name, (unsigned int)parent.db_id, (unsigned int)parent.db_oid, + (unsigned int)parent_id); + return -ENODATA; +} + +int ts_obj_by_path(thingset_oref_t parent, const char *path, size_t len, thingset_oref_t *oref) +{ + TS_ASSERT(ts_obj_db_oref_is_valid(parent), + "OBJ: %s for '%.*s' on invalid parent object reference (%u:%u)", + __func__, (int)len, path, (unsigned int)parent.db_id, (unsigned int)parent.db_oid); + + const char *start = path; + const char *end; + + if (path[0] == '/') { + /* Absolute path always starts at root parent */ + parent = ts_obj_db_oref_root(parent.db_id); + } + + /* maximum depth of 10 assumed */ + for (int i = 0; i < 10; i++) { + end = memchr(start, '/', ((uintptr_t)path + len) - (uintptr_t)start); + if ((end == NULL) || (end >= (path + len))) { + /* we are at the end of the path */ + return ts_obj_by_name(parent, start, ((uintptr_t)path + len) - (uintptr_t)start, oref); + } + else if (end == (path + len - 1)) { + /* path ends with slash */ + return ts_obj_by_name(parent, start, (uintptr_t)end - (uintptr_t)start, oref); + } + else { + /* go further down the path */ + int ret = ts_obj_by_name(parent, start, (uintptr_t)end - (uintptr_t)start, &parent); + if (ret != 0) { + break; + } + start = end + 1; /* Skip slash */ + } + } + + TS_LOGD("OBJ: %s did not find child '%.*s' of parent object (%u:%u)", + __func__, (int)len, path, (unsigned int)parent.db_id, (unsigned int)parent.db_oid); + return -ENODATA; +} + +const struct ts_obj *ts_obj(thingset_oref_t oref) +{ + TS_ASSERT(ts_obj_db_oref_is_object(oref), "OBJ: %s on invalid object reference (%u:%u)", + __func__, (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + + if (!ts_obj_db_oref_is_object(oref)) { + return NULL; + } + + const struct ts_obj_db *db = ts_obj_db_by_id(oref.db_id); + const struct ts_obj *obj; + if (oref.db_id < TS_CONFIG_LOCAL_COUNT) { + /* Local context database */ + obj = &db->objects[oref.db_oid]; + } + else { + /* Remote context database objects have mutable meta data in meta default */ + obj = &db->rdb->remotes[oref.db_oid]; + } + return obj; +} + +struct ts_obj_meta *ts_obj_meta(thingset_oref_t oref) +{ + TS_ASSERT(ts_obj_db_oref_is_object(oref), "OBJ: %s on invalid object reference (%u:%u)", + __func__, (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + + if (!ts_obj_db_oref_is_object(oref)) { + return NULL; + } + + const struct ts_obj_db *db = ts_obj_db_by_id(oref.db_id); + struct ts_obj_meta *meta; + if (oref.db_id < TS_CONFIG_LOCAL_COUNT) { + /* Local context database */ + meta = &db->meta[oref.db_oid]; + } + else { + /* Remote context database objects have mutable meta data in meta default */ + meta = &db->rdb->remotes[oref.db_oid].meta_default; + } + return meta; +} + +const struct ts_obj_meta *ts_obj_meta_default(thingset_oref_t oref) +{ + TS_ASSERT(ts_obj_db_oref_is_object(oref), "OBJ: %s on invalid object reference (%u:%u)", + __func__, (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + + if (!ts_obj_db_oref_is_object(oref)) { + return NULL; + } + + const struct ts_obj_db *db = ts_obj_db_by_id(oref.db_id); + const struct ts_obj_meta *meta; + if (oref.db_id < TS_CONFIG_LOCAL_COUNT) { + /* Local context database */ + meta = &db->objects[oref.db_oid].meta_default; + } + else { + /* Remote context database */ + meta = &db->rdb->remotes[oref.db_oid].meta_default; + } + return meta; +} + +int ts_obj_child_count(thingset_oref_t parent_oref, uint16_t *count) +{ + TS_ASSERT(ts_obj_db_oref_is_tree(parent_oref), + "OBJ: %s on invalid parent object reference (%u:%u)", __func__, + (unsigned int)parent_oref.db_id, (unsigned int)parent_oref.db_oid); + + if (!ts_obj_db_oref_is_tree(parent_oref)) { + return -EINVAL; + } + + if ((ts_obj_type(parent_oref) == TS_T_GROUP) || + (ts_obj_type(parent_oref) == TS_T_EXEC) || + (ts_obj_type(parent_oref) == TS_T_SUBSET)) { + int num_elements = 0; + thingset_oref_t child_oref = parent_oref; + for (child_oref.db_oid = 0; ts_obj_db_oref_is_valid(child_oref); child_oref.db_oid++) { + if (child_oref.db_oid == parent_oref.db_oid) { + /* That are we - never count */ + continue; + } + if (ts_obj_parent_id(child_oref) == ts_obj_id(parent_oref)) { + num_elements++; + } + } + *count = num_elements; + } + else { + *count = 0; + } + return 0; +} + +int ts_obj_child_first(thingset_oref_t parent_oref, thingset_oref_t *child_oref) +{ + TS_ASSERT(ts_obj_db_oref_is_tree(parent_oref), + "OBJ: %s on invalid parent object reference (%u:%u)", + __func__, (unsigned int)parent_oref.db_id, (unsigned int)parent_oref.db_oid); + + if (!ts_obj_db_oref_is_tree(parent_oref)) { + return -EINVAL; + } + + ts_obj_id_t parent_id = ts_obj_id(parent_oref); + if ((ts_obj_type(parent_oref) == TS_T_GROUP) || + (ts_obj_type(parent_oref) == TS_T_EXEC) || + (ts_obj_type(parent_oref) == TS_T_SUBSET)) { + thingset_oref_t oref = parent_oref; + for (oref.db_oid = 0; ts_obj_db_oref_is_valid(oref); oref.db_oid++) { + if (oref.db_oid == parent_oref.db_oid) { + /* That are we - never count */ + continue; + } + if (ts_obj_parent_id(oref) == parent_id) { + *child_oref = oref; + return 0; + } + } + } + else { + return -EINVAL; + } + return -ENODATA; +} + +int ts_obj_child_next(thingset_oref_t *child_oref) +{ + TS_ASSERT(ts_obj_db_oref_is_object(*child_oref), "OBJ: %s on invalid object reference (%u:%u)", + __func__, (unsigned int)child_oref->db_id, (unsigned int)child_oref->db_oid); + + if (!ts_obj_db_oref_is_object(*child_oref)) { + return -EINVAL; + } + + ts_obj_id_t parent_id = ts_obj_id(*child_oref); + thingset_oref_t oref = *child_oref; + for (; ts_obj_db_oref_is_valid(oref); oref.db_oid++) { + if (oref.db_oid == child_oref->db_oid) { + /* That are we - never count */ + continue; + } + if (ts_obj_parent_id(oref) == parent_id) { + *child_oref = oref; + return 0; + } + } + + return -ENODATA; +} + +ts_obj_id_t ts_obj_id(thingset_oref_t oref) +{ + TS_ASSERT(ts_obj_db_oref_is_tree(oref), "OBJ: %s on invalid object reference (%u:%u)", + __func__, (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + + if (oref.db_oid == TS_OBJ_DB_OID_ROOT) { + return TS_ID_ROOT; + } + + return ts_obj(oref)->id; +} + +const char *ts_obj_name(thingset_oref_t oref) +{ + TS_ASSERT(ts_obj_db_oref_is_tree(oref), "OBJ: %s on invalid object reference (%u:%u)", + __func__, (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + + if (oref.db_oid == TS_OBJ_DB_OID_ROOT) { + return "/"; + } + + return ts_obj(oref)->name; +} + +uint16_t ts_obj_subsets(thingset_oref_t oref) +{ + uint16_t object_subsets; + + if (!ts_obj_db_oref_is_object(oref)) { + TS_ASSERT(true, "OBJ: %s on invalid object reference (%u:%u)", __func__, + (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + object_subsets = 0; + } + else { + object_subsets = ts_obj_meta(oref)->subsets; + } + return object_subsets; +} + +int ts_obj_subsets_add(thingset_oref_t oref, uint16_t subsets) +{ + if (!ts_obj_db_oref_is_object(oref)) { + TS_ASSERT(true, "OBJ: %s on invalid object reference (%u:%u)", __func__, + (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + return -EINVAL; + } + ts_obj_meta(oref)->subsets |= subsets; + return 0; +} + +int ts_obj_subsets_remove(thingset_oref_t oref, uint16_t subsets) +{ + if (oref.db_oid == TS_OBJ_DB_OID_ROOT) { + return 0; + } + if (!ts_obj_db_oref_is_object(oref)) { + TS_ASSERT(true, "OBJ: %s on invalid object reference (%u:%u)", __func__, + (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + return -EINVAL; + } + ts_obj_meta(oref)->subsets &= ~subsets; + return 0; +} + +uint8_t ts_obj_type(thingset_oref_t oref) +{ + TS_ASSERT(ts_obj_db_oref_is_tree(oref), "OBJ: %s on invalid object reference (%u:%u)", + __func__, (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + + if (oref.db_oid == TS_OBJ_DB_OID_ROOT) { + /* Virtual root object is of type TS_T_GROUP */ + return TS_T_GROUP; + } + + return ts_obj(oref)->type; +} + +int16_t ts_obj_detail(thingset_oref_t oref) +{ + int16_t object_detail; + + if (oref.db_oid == TS_OBJ_DB_OID_ROOT) { + object_detail = 0; + } + else if (!ts_obj_db_oref_is_object(oref)) { + TS_ASSERT(true, "%s refers to invalid database object with database oid %u", __func__, + (unsigned int)oref.db_oid); + object_detail = 0; + } + else { + object_detail = ts_obj_meta(oref)->detail; + } + return object_detail; +} + +int ts_obj_db_alloc(thingset_uid_t *ctx_uid, ts_obj_id_t obj_id, thingset_oref_t *oref) +{ + /* Search remote objects database for context given by uid - create database if not available */ + ts_obj_db_id_t did; + ts_obj_db_id_t empty_did = TS_OBJ_DB_ID_INVALID; + ts_obj_db_id_t zero_did = TS_OBJ_DB_ID_INVALID; + for (did = TS_CONFIG_LOCAL_COUNT; did < TS_ARRAY_SIZE(ts_obj_dbs); did++) { + /* database for remote context objects */ + struct ts_obj_rdb *rdb = &ts_obj_rdbs[did - TS_CONFIG_LOCAL_COUNT]; + if (rdb->db.id == TS_OBJ_DB_ID_INVALID) { + if (empty_did == TS_OBJ_DB_ID_INVALID) { + empty_did = did; + } + continue; + } + if (rdb->uuid == *ctx_uid) { + /* Database already available - search for object with given ThingSet object ID */ + TS_OBJ_DB_FOREACH_OREF(ts_obj_db_oref(did, 0), oref_avail) { + if (ts_obj_id(oref_avail) == obj_id) { + /* Already allocated */ + *oref = oref_avail; + return 0; + } + } + break; + } + if (rdb->db.num == 0) { + if (zero_did == TS_OBJ_DB_ID_INVALID) { + zero_did = did; + } + } + } + if (did >= TS_ARRAY_SIZE(ts_obj_dbs)) { + /* No database with given ctx_uid. */ + if (empty_did == TS_OBJ_DB_ID_INVALID) { + if (zero_did == TS_OBJ_DB_ID_INVALID) { + return -ENOMEM; + } + /* Free available database with zero objects and use space again */ + did = zero_did; + } + else { + /* Use empty slot */ + did = empty_did; + } + struct ts_obj_rdb *rdb = &ts_obj_rdbs[did - TS_CONFIG_LOCAL_COUNT]; + rdb->uuid = *ctx_uid; + rdb->db.id = did; + } + for (ts_obj_db_oid_t oid = 0; oid < TS_CONFIG_REMOTE_OBJECT_COUNT; oid++) { + if (ts_obj_remotes_mngmt[oid].ref_count == 0) { + /* Init empty Slot */ + TS_ASSERT(ts_obj_remotes[oid].name == NULL, + "OBJ: %s data object with name assigned - database #%u object id %u", + __func__, (unsigned int)did, (unsigned int)oid); + TS_ASSERT(ts_obj_remotes[oid].data == NULL, + "OBJ: %s data object with data assigned - database #%u object id %u", + __func__, (unsigned int)did, (unsigned int)oid); + ts_obj_remotes[oid].meta_default.db_id = did; + ts_obj_remotes[oid].meta_default.access = 0; + ts_obj_remotes[oid].meta_default.detail = 0; + ts_obj_remotes[oid].meta_default.subsets = 0; + ts_obj_remotes[oid].id = obj_id; + ts_obj_remotes[oid].type = TS_T_INVALID; + ts_obj_remotes[oid].parent = TS_ID_INVALID; + + /* Increment number of objects of database */ + struct ts_obj_rdb *rdb = &ts_obj_rdbs[did - TS_CONFIG_LOCAL_COUNT]; + rdb->db.num++; + + /* Return object reference */ + oref->db_id = did; + oref->db_oid = oid; + + return 0; + } + } + return -ENOMEM; +} + +int ts_obj_unref(thingset_oref_t oref) +{ + if (!ts_obj_db_oref_is_object(oref)) { + return -EINVAL; + } + +#if TS_CONFIG_REMOTE_COUNT == 0 + return 0; +#else + if (oref.db_id < TS_CONFIG_LOCAL_COUNT) { + /* Ignore - this is a static local database */ + return 0; + } + + if (ts_obj_remotes_mngmt[oref.db_oid].ref_count == 0) { + /* Unref on already freed object !?! */ + return 0; + } + + ts_obj_remotes_mngmt[oref.db_oid].ref_count--; + if (ts_obj_remotes_mngmt[oref.db_oid].ref_count == 0) { + /* Free object */ + (void)ts_obj_data_free(oref); + struct ts_obj *obj = ts_obj_writable(oref); + obj->type = TS_T_INVALID; + obj->id = TS_ID_INVALID; + obj->meta_default.db_id = TS_OBJ_DB_ID_INVALID; + + /* Decrement number of objects of database */ + struct ts_obj_rdb *rdb = &ts_obj_rdbs[oref.db_id - TS_CONFIG_LOCAL_COUNT]; + rdb->db.num--; + } + + return 0; +#endif +} + +int ts_obj_data_alloc(thingset_oref_t oref, uint16_t size) +{ +#if TS_CONFIG_REMOTE_COUNT == 0 + return -EINVAL; +#else + if (!ts_obj_db_oref_is_object(oref)) { + return -EINVAL; + } + + if (oref.db_id < TS_CONFIG_LOCAL_COUNT) { + /* We can not change data size of static database */ + return -EINVAL; + } + + struct ts_obj *obj = ts_obj_writable(oref); + if (obj->data != NULL) { + ts_mem_free(&ts_obj_remotes_data, obj->data); + } + + return ts_mem_alloc(&ts_obj_remotes_data, size, THINGSET_TIMEOUT_IMMEDIATE, &obj->data); +#endif +} + +int ts_obj_data_free(thingset_oref_t oref) +{ +#if TS_CONFIG_REMOTE_COUNT == 0 + return -EINVAL; +#else + if (!ts_obj_db_oref_is_object(oref)) { + return -EINVAL; + } + + if (oref.db_id < TS_CONFIG_LOCAL_COUNT) { + /* Ignore - this is a static local database */ + return 0; + } + + struct ts_obj *obj = ts_obj_writable(oref); + if (obj->data != NULL) { + ts_mem_free(&ts_obj_remotes_data, obj->data); + obj->data = NULL; + } + + return 0; +#endif +} + +struct ts_obj *ts_obj_writable(thingset_oref_t oref) +{ + TS_ASSERT((oref.db_id < TS_ARRAY_SIZE(ts_obj_dbs)) && (oref.db_id >= TS_CONFIG_LOCAL_COUNT), + "OBJ: %s refers to data object with invalid database id %u", __func__, + (unsigned int)oref.db_id); + if (oref.db_oid == TS_OBJ_DB_OID_ROOT) { + return NULL; + } + + /* Remote context database */ + TS_ASSERT(oref.db_oid < TS_CONFIG_REMOTE_OBJECT_COUNT, + "OBJ: %s refers to data object with invalid database #%u object id %u", __func__, + (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + +#if TS_CONFIG_REMOTE_OBJECT_COUNT == 0 + return NULL; +#else + return &ts_obj_remotes[oref.db_oid]; +#endif +} + +void ts_obj_init_name(thingset_oref_t oref, const char *name) +{ + struct ts_obj *obj = ts_obj_writable(oref); + if (obj->name != NULL) { + ts_mem_free(&ts_obj_remotes_data, obj->name); + } + size_t name_len = strlen(name) + 1; + void *name_mem; + int ret = ts_mem_alloc(&ts_obj_remotes_data, name_len, THINGSET_TIMEOUT_IMMEDIATE, &name_mem); + if (ret != 0) { + obj->name = NULL; + } + else { + strncpy(name_mem, name, name_len); + obj->name = name_mem; + } +} diff --git a/src/ts_obj.h b/src/ts_obj.h new file mode 100644 index 0000000..debdfdd --- /dev/null +++ b/src/ts_obj.h @@ -0,0 +1,776 @@ +/* + * Copyright (c) 2017 Martin Jäger / Libre Solar + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet data objects (private interface) + */ + +#ifndef TS_OBJ_H_ +#define TS_OBJ_H_ + +/** + * @brief ThingSet data objects. + * + * @defgroup ts_obj_api_priv ThingSet data objects (private interface) + * @{ + */ + +#include "thingset.h" + +#include "ts_mem.h" +#include "ts_macro.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @def TS_OBJ_DB_ID_INVALID + * + * @brief Invalid data object database identifier. + */ +#define TS_OBJ_DB_ID_INVALID (UINT16_MAX) + +/** + * @def TS_OBJ_DB_OID_INVALID + * + * @brief Invalid data object database object identifier. + */ +#define TS_OBJ_DB_OID_INVALID (UINT16_MAX) + +/** + * @def TS_OBJ_DB_OID_ROOT + * + * @brief Data object database object identifier for (virtual) root object. + */ +#define TS_OBJ_DB_OID_ROOT (TS_OBJ_DB_OID_INVALID - 1) + +/** + * @def TS_OBJ_DB_OID_ANY + * + * @brief Data object database object identifier for any (all) object(s). + */ +#define TS_OBJ_DB_OID_ANY (TS_OBJ_DB_OID_INVALID - 2) + +/** + * @def TS_OBJ_DB_FOREACH_OREF + * + * @brief Loop on all database object references of a given data object database. + * + * Use like: + * + * TS_OBJ_DB_FOREACH_OREF(ts_obj_db_oref(did, 0), my_oref) { + * printf("%s", ts_obj_name(my_oref)) + * } + * + * @param[in] oref_start Starting object reference. + * @param[in] oref_iter Name to be used for iterator object reference. + */ +#if TS_CONFIG_REMOTE_COUNT == 0 +#define TS_OBJ_DB_FOREACH_OREF(oref_start, oref_iter) \ + for (thingset_oref_t oref_iter = oref_start; \ + ts_obj_db_oref_is_object(oref_iter); oref_iter.db_oid++) +#else +#define TS_OBJ_DB_FOREACH_OREF(oref_start, oref_iter) \ + for (thingset_oref_t oref_iter = oref_start; \ + ts_obj_db_oref_is_object(oref_iter); oref_iter.db_oid++) \ + if ((oref_iter.db_id < TS_CONFIG_LOCAL_COUNT) ? \ + true : (ts_obj_remotes[oref_iter.db_oid].meta_default.db_id == oref_iter.db_id)) +#endif + +/** + * @brief Remote context data object management data. + */ +struct ts_obj_remote_mngmt { + /** @brief Reference count to remote object */ + uint8_t ref_count; +}; + +/** + * @brief Remote context objects database. + */ +struct ts_obj_rdb { + /** @brief Local objects database interface for remote context objects database */ + struct ts_obj_db db; + + /** @brief Unique identifier for the ThingSet context of the remote objects database. */ + uint64_t uuid; + + /** @brief Array of remote context data objects. */ + struct ts_obj *remotes; +}; + +/* Local context objects databases */ +#if TS_CONFIG_LOCAL_COUNT > 0 +extern const struct ts_obj_db ts_obj_db_0; +#endif +#if TS_CONFIG_LOCAL_COUNT > 1 +extern const struct ts_obj_db ts_obj_db_1; +#endif +#if TS_CONFIG_LOCAL_COUNT > 2 +extern const struct ts_obj_db ts_obj_db_2; +#endif +#if TS_CONFIG_LOCAL_COUNT > 3 +extern const struct ts_obj_db ts_obj_db_3; +#endif +#if TS_CONFIG_LOCAL_COUNT > 4 +extern const struct ts_obj_db ts_obj_db_4; +#endif +#if TS_CONFIG_LOCAL_COUNT > 5 +#error "Local context databases limited to 5" +#endif + +/** + * @brief Remote contexts' data objects databases pool. + */ +extern struct ts_obj_rdb ts_obj_rdbs[TS_CONFIG_REMOTE_COUNT]; + +/** + *@brief Data objects databases by database id. + * + * Array of pointers to data objects databases. Array index == database id. + */ +extern const struct ts_obj_db *const ts_obj_dbs[TS_CONFIG_LOCAL_COUNT + TS_CONFIG_REMOTE_COUNT]; + +/** + * @brief Remote contexts' data objects pool. + * + * All remote context data objects databases share the same pool for remote objects. + */ +extern struct ts_obj ts_obj_remotes[TS_CONFIG_REMOTE_OBJECT_COUNT]; + +/** + * @brief Management data for remote contexts' data objects pool. + */ +extern struct ts_obj_remote_mngmt ts_obj_remotes_mngmt[TS_CONFIG_REMOTE_OBJECT_COUNT]; + +/** + * @var ts_obj_remotes_data + * + * @brief Memory pool for remote contexts' data objects data. + */ +TS_MEM_DECLARE(ts_obj_remotes_data); + +/** + * @brief Get data objects database by database ID. + * + * @param[in] did Data objects database ID. + * @return Pointer to data objects database. + */ +static inline const struct ts_obj_db *ts_obj_db_by_id(ts_obj_db_id_t did) +{ + TS_ASSERT(did < TS_ARRAY_SIZE(ts_obj_dbs), + "OBJ: %s invalid database id %u given with maximum database id %d", __func__, + (unsigned int)did, (unsigned int)TS_ARRAY_SIZE(ts_obj_dbs)); + return ts_obj_dbs[did]; +} + +/** + * @brief Get data objects database by object reference. + * + * @param[in] oref Object reference. + * @return Pointer to data objects database. + */ +static inline const struct ts_obj_db *ts_obj_db_by_oref(thingset_oref_t oref) +{ + return ts_obj_db_by_id(oref.db_id); +} + +/** + * @brief Initialise ThingSet objects databases. + */ +void ts_obj_db_init(void); + +/** + * @brief Check ThingSet objects database for consistent usage of object ids. + * + * Object ids shall be unique within the database. + * + * @param[in] did Data objects database ID. + * @return 0 on success, <0 on error. + */ +int ts_obj_db_check_id_duplicates(ts_obj_db_id_t did); + +/** + * @brief Create object reference to database object. + * + * @param[in] did Data objects database ID. + * @param[in] oid Object identifier of the object within the data objects database. + * @return object reference + */ +static inline thingset_oref_t ts_obj_db_oref(ts_obj_db_id_t did, ts_obj_db_oid_t oid) +{ + thingset_oref_t oref; + + oref.db_id = did; + oref.db_oid = oid; + + return oref; +} + +/** + * @brief Initialise object reference to link to object database. + * + * The reference is still invalid - but it is linked to the database. + * + * @param[in] did Data objects database ID. + * @param[out] oref Pointer to object reference. + */ +static inline void ts_obj_db_oref_init(ts_obj_db_id_t did, thingset_oref_t *oref) +{ + oref->db_id = did; + oref->db_oid = TS_OBJ_DB_OID_INVALID; +} + +/** + * @brief Get object reference to root object. + * + * @param[in] did Data objects database ID. + * @return Object reference to root object. + */ +thingset_oref_t ts_obj_db_oref_root(ts_obj_db_id_t did); + +/** + * @brief Get object reference to any (all) object(s). + * + * @param[in] did Data objects database ID. + * @return Object reference to any (all) object(s). + */ +thingset_oref_t ts_obj_db_oref_any(ts_obj_db_id_t did); + +/** + * @brief Get object reference of database object. + * + * @param[in] object Pointer to ThingSet object. + * @return object reference + */ +thingset_oref_t ts_obj_db_oref_by_object(const struct ts_obj *object); + +/** + * @brief Get object reference to object of given database with given object id. + * + * @param[in] did Data objects database ID. + * @param[in] obj_id ThingSet object ID. + * @param[out] oref Pointer to object reference. + */ +int ts_obj_db_oref_by_id(ts_obj_db_id_t did, ts_obj_id_t obj_id, thingset_oref_t *oref); + +/** + * @brief Is object reference a valid reference. + * + * Returns true also for reference to root object or any object. + * + * @param[in] oref Object reference. + * @returns True if object reference is valid, false otherwise. + */ +bool ts_obj_db_oref_is_valid(thingset_oref_t oref); + +/** + * @brief Is the object reference referencing an object. + * + * Returns false for reference to root object or for reference to any object. + * + * @param[in] oref Object reference. + * @returns True if object reference references a data object, false otherwise. + */ +static inline bool ts_obj_db_oref_is_object(thingset_oref_t oref) +{ + ts_obj_db_oid_t oid_max = (oref.db_id < TS_CONFIG_LOCAL_COUNT) ? + ts_obj_dbs[oref.db_id]->num : TS_CONFIG_REMOTE_OBJECT_COUNT; + return oref.db_oid < oid_max; +} + +/** + * @brief Is the object reference referencing a single tree object. + * + * Returns also true for reference to root object, but false for reference to any object. + * + * @param[in] oref Object reference. + * @returns True if object reference references a single tree object, false otherwise. + */ +static inline bool ts_obj_db_oref_is_tree(thingset_oref_t oref) +{ + ts_obj_db_oid_t oid_max = (oref.db_id < TS_CONFIG_LOCAL_COUNT) ? + ts_obj_dbs[oref.db_id]->num : TS_CONFIG_REMOTE_OBJECT_COUNT; + return (oref.db_oid == TS_OBJ_DB_OID_ROOT) || (oref.db_oid < oid_max); +} + +/** + * @brief Is the object reference referencing any (all) object(s). + * + * @param[in] oref Object reference. + * @returns True if the object reference is referencing any (all) object(s), false otherwise. + */ +static inline bool ts_obj_db_oref_is_any(thingset_oref_t oref) +{ + return (oref.db_oid == TS_OBJ_DB_OID_ANY) && ts_obj_db_oref_is_valid(oref); +} + +/** + * @brief Are the object references referencing the same object. + * + * @param[in] oref_a Object reference. + * @param[in] oref_b Object reference. + * @returns True if the same object is referenced, false otherwise. + */ +static inline bool ts_obj_db_oref_is_same(thingset_oref_t oref_a, thingset_oref_t oref_b) +{ + return (oref_a.db_id == oref_b.db_id) && (oref_a.db_oid == oref_b.db_oid); +} + +/** + * @brief Get object reference to object of given parent object with given name. + * + * @param[in] parent Object reference of parent object or any (all) object(s). + * @param[in] name Pointer to name. + * @param[in] len Length of name. + * @param[out] oref Pointer to object reference. + */ +int ts_obj_by_name(thingset_oref_t parent, const char *name, size_t len, thingset_oref_t *oref); + +/** + * @brief Get object reference to object of given parent object with given path. + * + * @param[in] parent Object reference of parent object or any (all) object(s). + * @param[in] path Pointer to path. + * @param[in] len Length of path. + * @param[out] oref Pointer to object reference. + */ +int ts_obj_by_path(thingset_oref_t parent, const char *path, size_t len, thingset_oref_t *oref); + +/** + * @brief Get object given by object reference. + * + * For reference to root object NULL is returned. + * + * @param[in] oref Object reference. + * @returns Pointer to object if available, NULL otherwise. + */ +const struct ts_obj *ts_obj(thingset_oref_t oref); + +/** + * @brief Get mutable object metadata for object given by object reference. + * + * For reference to root object NULL is returned. + * + * @param[in] oref Object reference. + * @returns Pointer to object mutable metadata if available, NULL otherwise. + */ +struct ts_obj_meta *ts_obj_meta(thingset_oref_t oref); + +/** + * @brief Get immutable object default metadata for object given by object reference. + * + * For reference to root object NULL is returned. + * + * @param[in] oref Object reference. + * @returns Pointer to object immutable default metadata if available, NULL otherwise. + */ +const struct ts_obj_meta *ts_obj_meta_default(thingset_oref_t oref); + +/** + * @brief Get default access authorisation of database object. + * + * @param[in] oref Database object reference to object. + * @return Default access authorisation of database object. + */ +static inline const uint16_t ts_obj_access_default(thingset_oref_t oref) +{ + return ts_obj_meta_default(oref)->access; +} + +/** + * @brief Get access authorisation of database object. + * + * @param[in] oref Database object reference to object. + * @return Access authorisation of database object. + */ +static inline uint16_t ts_obj_access(thingset_oref_t oref) +{ + if (oref.db_oid == TS_OBJ_DB_OID_ROOT) { + return TS_ANY_RW; + } + return ts_obj_meta(oref)->access; +} + +/** + * @brief Does authorisation provide read access to given database object. + * + * @param[in] oref Database object reference to object. + * @param[in] auth Authorisation. + * @return true on read access granted, false otherwise. + */ +static inline bool ts_obj_access_read(thingset_oref_t oref, uint16_t auth) +{ + return (ts_obj_access(oref) & TS_READ_MASK & auth) != 0; +} + +/** + * @brief Does authorisation provide write access to given database object. + * + * @param[in] oref Database object reference to object. + * @param[in] auth Authorisation. + * @return true on read access granted, false otherwise. + */ +static inline bool ts_obj_access_write(thingset_oref_t oref, uint16_t auth) +{ + return (ts_obj_access(oref) & TS_WRITE_MASK & auth) != 0; +} + +/** + * @brief Count the number of objects that are referencing the parent object as their parent. + * + * @param[in] parent_oref Database object reference to parent object. + * @param[out] count Pointer to count of child objects. + * @return 0 on success, <0 otherwise. + */ +int ts_obj_child_count(thingset_oref_t parent_oref, uint16_t *count); + +/** + * @brief Get reference to first object that is referencing the parent object as it's parent. + * + * @param[in] parent_oref Database object reference to parent object. + * @param[in,out] child_oref Pointer to object reference to current child on invocation, next child + * on return. + * @return 0 on success, <0 otherwise. + */ +int ts_obj_child_first(thingset_oref_t parent_oref, thingset_oref_t *child_oref); + +/** + * @brief Get reference to next object that is referencing the same parent object as it's parent. + * + * @param[in,out] child_oref Pointer to object reference to current child on invocation, next child + * on return. + * @return 0 on success, <0 otherwise. + */ +int ts_obj_child_next(thingset_oref_t *child_oref); + +/** + * @brief Get object identifier. + * + * @param[in] oref Database object reference to object. + * @return Object identifier or TS_ID_INVALID on invalid object reference. + */ +ts_obj_id_t ts_obj_id(thingset_oref_t oref); + +/** + * @brief Get object name. + * + * @param[in] oref Database object reference to object. + * @return Pointer to object name or NULL on invalid object reference. + */ +const char *ts_obj_name(thingset_oref_t oref); + +/** + * @brief Get object parent identifier. + * + * @param[in] oref Database object reference to object. + * @return Object parent identifier or TS_ID_INVALID on invalid object reference or no parent. + */ +static inline ts_obj_id_t ts_obj_parent_id(thingset_oref_t oref) +{ + TS_ASSERT(ts_obj_db_oref_is_object(oref), "OBJ: %s on invalid object reference (%u:%u)", + __func__, (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + + const struct ts_obj *object = ts_obj(oref); + if (object == NULL) { + return TS_ID_INVALID; + } + return object->parent; +} + +/** + * @brief Get the subsets the object is associated to. + * + * @param[in] oref Database object reference to object. + * @return Subsets flags or 0 on invalid object reference. + */ +uint16_t ts_obj_subsets(thingset_oref_t oref); + +/** + * @brief Add subset flags to the subsets the object is associated to. + * + * @param[in] oref Database object reference to object. + * @param[in] subsets Subset flags to be added. + * @returns 0 on success, <0 on invalid object reference. + */ +int ts_obj_subsets_add(thingset_oref_t oref, uint16_t subsets); + +/** + * @brief Remove subset flags from the subsets the object is associated to. + * + * @param[in] oref Database object reference to object. + * @param[in] subsets Subset flags to be removed. + * @returns 0 on success, <0 on invalid object reference. + */ +int ts_obj_subsets_remove(thingset_oref_t oref, uint16_t subsets); + +/** + * @brief Get object type. + * + * @param[in] oref Database object reference to object. + * @return Object type or TS_T_INVALID on invalid object reference. + */ +uint8_t ts_obj_type(thingset_oref_t oref); + +static inline void *ts_obj_data(thingset_oref_t oref) +{ + TS_ASSERT(ts_obj_db_oref_is_object(oref), "OBJ: %s on invalid object reference (%u:%u)", + __func__, (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + + return ts_obj(oref)->data; +} + +/** + * @brief Get object detail. + * + * @param[in] oref Database object reference to object. + * @return Object detail or 0 on invalid object reference. + */ +int16_t ts_obj_detail(thingset_oref_t oref); + +static inline struct ts_array_info *ts_obj_array_data(thingset_oref_t oref) +{ + return (struct ts_array_info *)ts_obj_data(oref); +} + +/** + * @brief Get booelean object data. + * + * @param[in] oref Database object reference to object. + * @return Pointer to boolean data. + */ +static inline bool *ts_obj_bool_data(thingset_oref_t oref) +{ + TS_ASSERT((ts_obj_type(oref) == TS_T_BOOL), + "OBJ: %s on invalid object reference (%u:%u)", + __func__, (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + + return (bool *)ts_obj_data(oref); +} + +/** + * @brief Get decfrac object mantissa data. + * + * @param[in] oref Database object reference to object. + * @return Pointer to mantissa data. + */ +static inline int32_t *ts_obj_decfrac_mantissa_data(thingset_oref_t oref) +{ + TS_ASSERT((ts_obj_type(oref) == TS_T_DECFRAC), + "OBJ: %s on invalid object reference (%u:%u)", + __func__, (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + + return (int32_t *)ts_obj_data(oref); +} + +/** + * @brief Get decfrac object exponent data. + * + * @param[in] oref Database object reference to object. + * @return Exponent data or 0 on invalid object reference. + */ +static inline int16_t ts_obj_decfrac_exponent_data(thingset_oref_t oref) +{ + TS_ASSERT((ts_obj_type(oref) == TS_T_DECFRAC), + "OBJ: %s on invalid object reference (%u:%u)", + __func__, (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + + return ts_obj_detail(oref); +} + +/** + * @brief Get group or function object execution data. + * + * @param[in] oref Database object reference to object. + * @return Pointer to execution function or NULL if there is no execution function. + */ +static inline ts_obj_exec_t ts_obj_exec_data(thingset_oref_t oref) +{ + TS_ASSERT((ts_obj_type(oref) == TS_T_GROUP) || (ts_obj_type(oref) == TS_T_EXEC), + "OBJ: %s on invalid object reference (%u:%u)", + __func__, (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + + if (oref.db_oid == TS_OBJ_DB_OID_ROOT) { + /* Virtual root object is of type TS_T_GROUP but does not have an exec function */ + return NULL; + } + + return (ts_obj_exec_t)ts_obj_data(oref); +} + +static inline float *ts_obj_f32_data(thingset_oref_t oref) +{ + return (float *)ts_obj_data(oref); +} + +static inline int16_t *ts_obj_i16_data(thingset_oref_t oref) +{ + return (int16_t *)ts_obj_data(oref); +} + +static inline int32_t *ts_obj_i32_data(thingset_oref_t oref) +{ + return (int32_t *)ts_obj_data(oref); +} + +static inline int64_t *ts_obj_i64_data(thingset_oref_t oref) +{ + return (int64_t *)ts_obj_data(oref); +} + +static inline uint8_t *ts_obj_mem_data(thingset_oref_t oref) +{ + return ((struct ts_bytes_buffer *)ts_obj_data(oref))->bytes; +} + +static inline uint16_t *ts_obj_mem_len(thingset_oref_t oref) +{ + return &((struct ts_bytes_buffer *)ts_obj_data(oref))->num_bytes; +} + +static inline char *ts_obj_string_data(thingset_oref_t oref) +{ + return (char *)ts_obj_data(oref); +} + +static inline uint16_t *ts_obj_u16_data(thingset_oref_t oref) +{ + return (uint16_t *)ts_obj_data(oref); +} + +static inline uint32_t *ts_obj_u32_data(thingset_oref_t oref) +{ + return (uint32_t *)ts_obj_data(oref); +} + +static inline uint64_t *ts_obj_u64_data(thingset_oref_t oref) +{ + return (uint64_t *)ts_obj_data(oref); +} + +/* + * Remote data objects handling + * ---------------------------- + */ + +/** + * @brief Allocate object space in a database for remote objects. + * + * The function does not allocate data space for the object. This has to be provided later on based + * on the object data type using @ref ts_obj_data_alloc(). + * + * @param[in] ctx_uid Unique id of the remote context the data object belongs to. + * @param[in] obj_id Id of the data object within the remote context's database. + * @param[out] oref Pointer to object reference. + * @return 0 on success, <0 on error. + */ +int ts_obj_db_alloc(thingset_uid_t *ctx_uid, ts_obj_id_t obj_id, thingset_oref_t *oref); + +/** + * @brief Free object and object data space in a database for remote objects. + * + * @note Database object reference must reference to database for remote context objects. + * + * @param[in] oref Object reference. + * @return 0 on success, <0 on error. + */ +int ts_obj_unref(thingset_oref_t oref); + +/** + * @brief Allocate memory space for object data. + * + * @note Database object reference must reference to database for remote context objects. + * + * @note The functions requires memory to be provided by ts_mem_alloc(). + * + * @param[in] oref Database object reference to object. + * @param[in] size Size of data to be allocated. + * @return 0 on success, <0 on error. + */ +int ts_obj_data_alloc(thingset_oref_t oref, uint16_t size); + +/** + * @brief Free memory space for object data. + * + * @note Database object reference must reference to database for remote context objects. + * + * @param[in] oref Database object reference to object. + * @return 0 on success, <0 on error. + */ +int ts_obj_data_free(thingset_oref_t oref); + +/** + * @brief Get writeable object given by object reference. + * + * For reference to root object NULL is returned. + * + * @param[in] oref Object reference. + * @returns Pointer to object if available, NULL otherwise. + */ +struct ts_obj *ts_obj_writable(thingset_oref_t oref); + +/** + * @brief Initialise object's detail info. + * + * @note Database object reference must reference to database for remote context objects. + * + * @param[in] oref Database object reference to object. + * @param[in] detail Object detail info. + */ +static inline void ts_obj_init_detail(thingset_oref_t oref, int16_t detail) +{ + ts_obj_meta(oref)->detail = detail; +} + +/** + * @brief Initialise object's id. + * + * @note Database object reference must reference to database for remote context objects. + * + * @param[in] oref Database object reference to object. + * @param[in] id Object id. + */ +static inline void ts_obj_init_id(thingset_oref_t oref, ts_obj_id_t id) +{ + ts_obj_writable(oref)->id = id; +} + +/** + * @brief Initialise object's name. + * + * @note Database object reference must reference to database for remote context objects. + * + * @note The functions requires memory to be provided by ts_mem_alloc(). + * + * @param[in] oref Database object reference to object. + * @param[in] name Object name. + */ +void ts_obj_init_name(thingset_oref_t oref, const char *name); + +/** + * @brief Initialise object's type. + * + * @note Database object reference must reference to database for remote context objects. + * + * @param[in] oref Database object reference to object. + * @param[in] type Object type. + */ +static inline void ts_obj_init_type(thingset_oref_t oref, uint8_t type) +{ + ts_obj_writable(oref)->type = type; +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +/** + * @} + */ + +#endif // TS_OBJ_H_ diff --git a/src/ts_obj_log.c b/src/ts_obj_log.c new file mode 100644 index 0000000..49099d5 --- /dev/null +++ b/src/ts_obj_log.c @@ -0,0 +1,399 @@ +/* + * Copyright (c) 2017 Martin Jäger / Libre Solar + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "thingset_env.h" + +#include +#include +#include +#include + +#include "ts_obj.h" +#include "ts_log.h" + +static const char *ts_obj_log_access(uint16_t access) +{ + /* "-,-,-,-,-,-,-" */ + static char access_str[6 * sizeof("TS_MKR_W") + 3]; + + if (access & ~TS_ANY_RW) { + /* Invalid access specification */ + snprintf(&access_str[0], sizeof(access_str), + "\"Invalid access specification 0x%04x - do not set 0x%04x", + (unsigned int)access, (unsigned int)(access & ~TS_ANY_RW)); + return &access_str[0]; + } + + switch (access) { + /* read/ write */ + case TS_ANY_RW: + return "\"TS_ANY_RW\""; + break; + case TS_MKR_RW: + return "\"TS_MKR_RW\""; + break; + case TS_EXP_RW: + return "\"TS_EXP_RW\""; + break; + case TS_USR_RW: + return "\"TS_USR_RW\""; + break; + /* write only */ + case TS_ANY_W: + return "\"TS_ANY_W\""; + break; + case TS_MKR_W: + return "\"TS_MKR_W\""; + break; + case TS_EXP_W: + return "\"TS_EXP_W\""; + break; + case TS_USR_W: + return "\"TS_USR_W\""; + break; + /* read only */ + case TS_ANY_R: + return "\"TS_ANY_R\""; + break; + case TS_MKR_R: + return "\"TS_MKR_R\""; + break; + case TS_EXP_R: + return "\"TS_EXP_R\""; + break; + case TS_USR_R: + return "\"TS_USR_R\""; + break; + } + snprintf(&access_str[0], sizeof(access_str), "\"%s,%s,%s,%s,%s,%s\"", + (access & TS_MKR_W) ? "TS_MKR_W" : "-", + (access & TS_EXP_W) ? "TS_EXP_W" : "-", + (access & TS_USR_W) ? "TS_USR_W" : "-", + (access & TS_MKR_R) ? "TS_MKR_R" : "-", + (access & TS_EXP_R) ? "TS_EXP_R" : "-", + (access & TS_USR_R) ? "TS_USR_R" : "-"); + return &access_str[0]; +} + +static const char *ts_obj_log_type(uint8_t type) +{ + switch (type) { + case TS_T_BOOL: + return "\"TS_T_BOOL\""; + case TS_T_UINT64: + return "\"TS_T_UINT64\""; + case TS_T_INT64: + return "\"TS_T_INT64\""; + case TS_T_UINT32: + return "\"TS_T_UINT32\""; + case TS_T_INT32: + return "\"TS_T_INT32\""; + case TS_T_UINT16: + return "\"TS_T_UINT16\""; + case TS_T_INT16: + return "\"TS_T_INT16\""; + case TS_T_FLOAT32: + return "\"TS_T_FLOAT32\""; + case TS_T_STRING: + return "\"TS_T_STRING\""; + case TS_T_BYTES: + return "\"TS_T_BYTES\""; + case TS_T_ARRAY: + return "\"TS_T_ARRAY\""; + case TS_T_DECFRAC: + return "\"TS_T_DECFRAC\""; + case TS_T_GROUP: + return "\"TS_T_GROUP\""; + case TS_T_EXEC: + return "\"TS_T_EXEC\""; + case TS_T_SUBSET: + return "\"TS_T_SUBSET\""; + } + return "\"\""; +} + +/** + * @brief Serialize a object value into a JSON string. + * + * @param[in] oref Database object reference to object which shall be serialized. + * @param[in,out] log Pointer to the buffer where the JSON value should be stored. + * @param[in] len Size of the buffer, i.e. maximum allowed length of the value. + * @returns Length of data written to buffer or 0 in case of error. + */ +static int ts_obj_log_value(thingset_oref_t oref, char *log, size_t len) +{ + if (len == 0) { + return 0; + } + + size_t log_pos = 0; + + switch (ts_obj_type(oref)) { +#if TS_CONFIG_64BIT_TYPES_SUPPORT + case TS_T_UINT64: + log_pos = snprintf(&log[log_pos], len - log_pos, "%" PRIu64, *ts_obj_u64_data(oref)); + break; + case TS_T_INT64: + log_pos = snprintf(&log[log_pos], len - log_pos, "%" PRIi64, *ts_obj_i64_data(oref)); + break; +#endif + case TS_T_UINT32: + log_pos = snprintf(&log[log_pos], len - log_pos, "%" PRIu32, *ts_obj_u32_data(oref)); + break; + case TS_T_INT32: + log_pos = snprintf(&log[log_pos], len - log_pos, "%" PRIi32, *ts_obj_i32_data(oref)); + break; + case TS_T_UINT16: + log_pos = snprintf(&log[log_pos], len - log_pos, "%" PRIu16, *ts_obj_u16_data(oref)); + break; + case TS_T_INT16: + log_pos = snprintf(&log[log_pos], len - log_pos, "%" PRIi16, *ts_obj_i16_data(oref)); + break; + case TS_T_FLOAT32: + { + float value = *ts_obj_f32_data(oref); + if (isnan(value) || isinf(value)) { + /* JSON spec does not support NaN and Inf, so we need to use null instead */ + return snprintf(log, len, "null,"); + } + else { + log_pos = snprintf(&log[log_pos], len - log_pos, "%f", value); + } + } + break; +#if TS_CONFIG_DECFRAC_TYPE_SUPPORT + case TS_T_DECFRAC: + log_pos = snprintf(&log[log_pos], len - log_pos, "%" PRIi32 "e%" PRIi16, + *ts_obj_decfrac_mantissa_data(oref), ts_obj_decfrac_exponent_data(oref)); + break; +#endif + case TS_T_BOOL: + log_pos = snprintf(&log[log_pos], len - log_pos, "%s", + (*ts_obj_bool_data(oref) == true ? "true" : "false")); + break; + case TS_T_EXEC: + { + log_pos = snprintf(&log[log_pos], len - log_pos, "["); + thingset_oref_t child_oref = oref; + for (child_oref.db_oid = 0; ts_obj_db_oref_is_valid(child_oref); child_oref.db_oid++) { + if (ts_obj_parent_id(child_oref) == ts_obj_id(oref)) { + log_pos += snprintf(&log[log_pos], len - log_pos, "\"%s\",", ts_obj_name(child_oref)); + } + } + if (log_pos > 1) { + log_pos--; // remove trailing comma + log_pos += snprintf(&log[log_pos], len - log_pos, "]"); + } + else { + log_pos = 0; + log_pos = snprintf(&log[log_pos], len - log_pos, "null"); + } + } + break; + case TS_T_STRING: + log_pos = snprintf(&log[log_pos], len - log_pos, "\"%s\"", ts_obj_string_data(oref)); + break; + case TS_T_SUBSET: + { + thingset_oref_t child_oref = oref; + log_pos = snprintf(&log[log_pos], len - log_pos, "["); + for (child_oref.db_oid = 0; ts_obj_db_oref_is_valid(child_oref); child_oref.db_oid++) { + if (ts_obj_subsets(child_oref) & (uint16_t)ts_obj_detail(oref)) { + log_pos += snprintf(&log[log_pos], len - log_pos, "\"%s\",", ts_obj_name(child_oref)); + } + } + if (log_pos > 1) { + log_pos--; // remove trailing comma + } + log_pos += snprintf(&log[log_pos], len - log_pos, "]"); + } + break; + case TS_T_ARRAY: + { + struct ts_array_info *array_info = ts_obj_array_data(oref); + if (!array_info) { + return 0; + } + log_pos += snprintf(&log[log_pos], len - log_pos, "["); + for (int i = 0; i < array_info->num_elements; i++) { + switch (array_info->type) { + case TS_T_UINT64: + log_pos += snprintf(&log[log_pos], len - log_pos, "%" PRIu64 ",", + ((uint64_t *)array_info->ptr)[i]); + break; + case TS_T_INT64: + log_pos += snprintf(&log[log_pos], len - log_pos, "%" PRIi64 ",", + ((int64_t *)array_info->ptr)[i]); + break; + case TS_T_UINT32: + log_pos += snprintf(&log[log_pos], len - log_pos, "%" PRIu32 ",", + ((uint32_t *)array_info->ptr)[i]); + break; + case TS_T_INT32: + log_pos += snprintf(&log[log_pos], len - log_pos, "%" PRIi32 ",", + ((int32_t *)array_info->ptr)[i]); + break; + case TS_T_UINT16: + log_pos += snprintf(&log[log_pos], len - log_pos, "%" PRIu16 ",", + ((uint16_t *)array_info->ptr)[i]); + break; + case TS_T_INT16: + log_pos += snprintf(&log[log_pos], len - log_pos, "%" PRIi16 ",", + ((int16_t *)array_info->ptr)[i]); + break; + case TS_T_FLOAT32: + log_pos += snprintf(&log[log_pos], len - log_pos, "%.*f,", ts_obj_detail(oref), + ((float *)array_info->ptr)[i]); + break; + default: + /* should not happen */ + TS_ASSERT(false, "OBJ: %s on unknown ThingSet object type (%u)", __func__, + (unsigned int)array_info->type); + break; + } + } + if (array_info->num_elements > 0) { + log_pos--; // remove trailing comma + } + log_pos += snprintf(&log[log_pos], len - log_pos, "]"); + } + break; + default: + TS_LOGD("OBJ: %s on unexpected type (%u)", __func__, + (unsigned int)ts_obj_type(oref)); + break; + } + + return log_pos; +} + +/** + * @brief Print all data objects as a structured JSON text to log buffer. + * + * @warning This is a recursive function and might cause stack overflows if run in constrained + * devices with large data object tree. Use with care and for testing only! + * + * @param[in] oref Database object reference to root object where to start with printing. + * @param[in] log Pointer to the log buffer. + * @param[in] len Length of the log buffer. + * @param[in] level Indentation level (=depth inside the data object tree) + * @returns Number of characters printed to buffer, 0 on error + */ +static size_t ts_obj_log_by_level(thingset_oref_t oref, char *log, size_t len, int level) +{ + TS_ASSERT(level < 10, "OBJ: %s on level %d exceeds maximum level (10)", __func__, level); + + if (len == 0) { + return 0; + } + + size_t log_pos = 0; + + bool first = true; + if (oref.db_oid == TS_OBJ_DB_OID_ROOT) { + log_pos += snprintf(&log[log_pos], len - log_pos, "{"); + } + + TS_OBJ_DB_FOREACH_OREF(ts_obj_db_oref(oref.db_id, 0), child_oref) { + if (log_pos >= len) { + break; + } + if (ts_obj_parent_id(child_oref) != ts_obj_id(oref)) { + continue; + } + /* Add end of line to line before */ + if (first) { + log_pos += snprintf(&log[log_pos], len - log_pos, "\n"); + first = false; + } + else { + log_pos += snprintf(&log[log_pos], len - log_pos, ",\n"); + } + if (log_pos >= len) { + break; + } + /* Add indent & characteristics */ + /* + log_pos += snprintf(&log[log_pos], len - log_pos, + "%*s// db_oid: %u, obj_id: %u, parent_id: %u, access: 0x%04x (default 0x%04x)\n", + 4 * (level + 1), "", (unsigned int)child_oref.db_oid, + (unsigned int)ts_obj_id(child_oref), + (unsigned int)ts_obj_parent_id(child_oref), + (unsigned int)ts_obj_access(child_oref), + (unsigned int)ts_obj_access_default(child_oref)); + */ + /* Add object at indent (level + 1) */ + log_pos += snprintf(&log[log_pos], len - log_pos, "%*s\"%s\": {", + 4 * (level + 1), "", ts_obj_name(child_oref)); + if (log_pos >= len) { + break; + } + /* Add database oid at indent (level + 2) */ + log_pos += snprintf(&log[log_pos], len - log_pos, "\n%*s\"db_oid\": %u,", 4 * (level + 2), + "", (unsigned int)child_oref.db_oid); + if (log_pos >= len) { + break; + } + /* Add object id at indent (level + 2) */ + log_pos += snprintf(&log[log_pos], len - log_pos, "\n%*s\"obj_id\": %u,", 4 * (level + 2), + "", (unsigned int)ts_obj_id(child_oref)); + if (log_pos >= len) { + break; + } + /* Add object access at indent (level + 2) */ + log_pos += snprintf(&log[log_pos], len - log_pos, "\n%*s\"access\": %s,", 4 * (level + 2), + "", ts_obj_log_access(ts_obj_access(child_oref))); + if (log_pos >= len) { + break; + } + /* Add object access default at indent (level + 2) */ + log_pos += snprintf(&log[log_pos], len - log_pos, "\n%*s\"access_default\": %s,", 4 * (level + 2), + "", ts_obj_log_access(ts_obj_access_default(child_oref))); + if (log_pos >= len) { + break; + } + /* Add object type at indent (level + 2) */ + log_pos += snprintf(&log[log_pos], len - log_pos, "\n%*s\"type\": %s,", 4 * (level + 2), + "", ts_obj_log_type(ts_obj_type(child_oref))); + if (log_pos >= len) { + break; + } + /* Add object data at indent (level + 2) */ + log_pos += snprintf(&log[log_pos], len - log_pos, "\n%*s\"data\": ", 4 * (level + 2), ""); + if (ts_obj_type(child_oref) == TS_T_BYTES) { + log_pos += snprintf(&log[log_pos], len - log_pos, ""); + } + else if (ts_obj_type(child_oref) == TS_T_GROUP) { + log_pos += snprintf(&log[log_pos], len - log_pos, "{"); + log_pos += ts_obj_log_by_level(child_oref, &log[log_pos], len - log_pos, level + 2); + log_pos += snprintf(&log[log_pos], len - log_pos, "\n%*s}", 4 * (level + 2), ""); + } + else { + log_pos += ts_obj_log_value(child_oref, &log[log_pos], len - log_pos); + } + if (log_pos >= len) { + break; + } + + log_pos += snprintf(&log[log_pos], len - log_pos, "\n%*s}", 4 * (level + 1), ""); + if (log_pos >= len) { + break; + } + } + if ((oref.db_oid == TS_OBJ_DB_OID_ROOT) && (len > log_pos + 4)) { + log_pos += snprintf(&log[log_pos], len - log_pos, "\n}\n"); + } + if (log_pos >= len) { + log_pos = len - 1; + } + log[log_pos] = '\0'; + return log_pos; +} + +void thingset_obj_log(thingset_oref_t oref, char *log, size_t len) +{ + (void)ts_obj_log_by_level(oref, log, len, 0); +} From 9029effd9314956692b87b650fac0389e2e65f2e Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Mon, 7 Feb 2022 10:22:16 +0100 Subject: [PATCH 15/37] COM prepare - add ThingSet COBS encoder and decoder Prepare for ThingSet Communication Framework: - Add encoder and decoder for the consistent overhead byte stuffing (COBS) algorithm. - Copy from nanocobs. nanocobs is from: https://github.com/charlesnicholson/nanocobs The documentation also at: https://github.com/charlesnicholson/nanocobs nanocobs is https://unlicense.org licensed. Signed-off-by: Bobby Noelte --- src/ts_cobs.c | 237 ++++++++++++++++++++++++++++++++++++++++++++++++ src/ts_cobs.h | 246 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 483 insertions(+) create mode 100644 src/ts_cobs.c create mode 100644 src/ts_cobs.h diff --git a/src/ts_cobs.c b/src/ts_cobs.c new file mode 100644 index 0000000..5ac2367 --- /dev/null +++ b/src/ts_cobs.c @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2021 Charles Nicholson, charles.nicholson@gmail.com + * Copyright (c) 2022 Bobby Noelte. + * SPDX-License-Identifier: Unlicense + * + * Copied from https: https://github.com/charlesnicholson/nanocobs. + */ + +/** + * @file + * @brief ThingSet COBS + */ + +#include "ts_cobs.h" + +#define COBS_ISV TS_COBS_INPLACE_SENTINEL_VALUE + +typedef unsigned char cobs_byte_t; + + + +int ts_cobs_decode_inplace(unsigned len, void *buf) +{ + if (!buf || (len < 2)) { + return -EINVAL; + } + + cobs_byte_t *const src = (cobs_byte_t *)buf; + unsigned ofs, cur = 0; + while ((ofs = src[cur]) != TS_COBS_FRAME_DELIMITER) { + src[cur] = 0; + cur += ofs; + if (cur > len) { + return -EBADMSG; + } + } + + if (cur != len - 1) { + return -EBADMSG; + } + src[0] = COBS_ISV; + src[len - 1] = COBS_ISV; + return 0; +} + +int ts_cobs_encode_inplace(unsigned int len, void *buf) +{ + if (!buf || (len < 2)) { + return -EINVAL; + } + + cobs_byte_t *const src = (cobs_byte_t *)buf; + if ((src[0] != COBS_ISV) || (src[len - 1] != COBS_ISV)) { + return -EBADMSG; + } + + unsigned patch = 0, cur = 1; + while (cur < len - 1) { + if (src[cur] == TS_COBS_FRAME_DELIMITER) { + unsigned const ofs = cur - patch; + if (ofs > 255) { + return -EBADMSG; + } + src[patch] = (cobs_byte_t)ofs; + patch = cur; + } + ++cur; + } + unsigned const ofs = cur - patch; + if (ofs > 255) { + return -EBADMSG; + } + src[patch] = (cobs_byte_t)ofs; + src[cur] = 0; + return 0; +} + +int ts_cobs_decode(unsigned int enc_len, void const *enc, unsigned int dec_max, + unsigned int *out_dec_len, void *out_dec) +{ + if (!enc || !out_dec || !out_dec_len) { + return -EINVAL; + } + if (enc_len < 2) { + return -EINVAL; + } + + cobs_byte_t const *const src = (cobs_byte_t const *)enc; + cobs_byte_t *const dst = (cobs_byte_t *)out_dec; + + if ((src[0] == TS_COBS_FRAME_DELIMITER) || (src[enc_len - 1] != TS_COBS_FRAME_DELIMITER)) { + return -EBADMSG; + } + + unsigned int src_idx = 0, dst_idx = 0; + + while (src_idx < (enc_len - 1)) { + unsigned int const code = src[src_idx++]; + if (!code) { + return -EBADMSG; + } + if ((src_idx + code) > enc_len) { + return -EBADMSG; + } + + if ((dst_idx + code - 1) > dec_max) { + return -ENOMEM; + } + for (unsigned i = 0; i < code - 1; ++i) { + dst[dst_idx++] = src[src_idx++]; + } + + if ((src_idx < (enc_len - 1)) && (code < 0xFF)) { + if (dst_idx >= dec_max) { + return -ENOMEM; + } + dst[dst_idx++] = 0; + } + } + + *out_dec_len = dst_idx; + return 0; +} + +int ts_cobs_encode(unsigned int dec_len, void const *dec, unsigned int enc_max, + unsigned int *out_enc_len, void *out_enc) +{ + if (!out_enc_len) { + return -EINVAL; + } + + struct ts_cobs_enc_ctx ctx; + int r; + r = ts_cobs_encode_inc_begin(out_enc, enc_max, &ctx); + if (r != 0) { + return r; + } + r = ts_cobs_encode_inc(&ctx, dec_len, dec); + if (r != 0) { + return r; + } + r = ts_cobs_encode_inc_end(&ctx, out_enc_len); + return r; +} + + +int ts_cobs_encode_inc_begin(struct ts_cobs_enc_ctx *ctx, unsigned int enc_max, void *out_enc) +{ + if (!out_enc || !ctx) { + return -EINVAL; + } + if (enc_max < 2) { + return -EINVAL; + } + + ctx->dst = out_enc; + ctx->dst_max = enc_max; + ctx->cur = 1; + ctx->code = 1; + ctx->code_idx = 0; + ctx->need_advance = 0; + return 0; +} + +int ts_cobs_encode_inc(struct ts_cobs_enc_ctx *ctx, unsigned int dec_len, void const *dec) +{ + if (!ctx || !dec) { + return -EINVAL; + } + unsigned dst_idx = ctx->cur; + unsigned const enc_max = ctx->dst_max; + if ((enc_max - dst_idx) < dec_len) { + return -ENOMEM; + } + + unsigned dst_code_idx = ctx->code_idx; + unsigned code = ctx->code; + int need_advance = ctx->need_advance; + + cobs_byte_t const *const src = (cobs_byte_t const *)dec; + cobs_byte_t *const dst = (cobs_byte_t *)ctx->dst; + unsigned src_idx = 0; + + if (need_advance) { + if (++dst_idx >= enc_max) { + return -ENOMEM; + } + need_advance = 0; + } + + while (dec_len--) { + cobs_byte_t const byte = src[src_idx]; + if (byte) { + dst[dst_idx] = byte; + if (++dst_idx >= enc_max) { + return -ENOMEM; + } + ++code; + } + + if ((byte == 0) || (code == 0xFF)) { + dst[dst_code_idx] = (cobs_byte_t)code; + dst_code_idx = dst_idx; + code = 1; + + if ((byte == 0) || dec_len) { + if (++dst_idx >= enc_max) { + return -ENOMEM; + } + } else { + need_advance = !dec_len; + } + } + ++src_idx; + } + + ctx->cur = dst_idx; + ctx->code = code; + ctx->code_idx = dst_code_idx; + ctx->need_advance = need_advance; + return 0; +} + + +int ts_cobs_encode_inc_end(struct ts_cobs_enc_ctx *ctx, unsigned int *out_enc_len) +{ + if (!ctx || !out_enc_len) { + return -EINVAL; + } + + cobs_byte_t *const dst = (cobs_byte_t *)ctx->dst; + unsigned cur = ctx->cur; + dst[ctx->code_idx] = (cobs_byte_t)ctx->code; + dst[cur++] = TS_COBS_FRAME_DELIMITER; + *out_enc_len = cur; + return 0; +} diff --git a/src/ts_cobs.h b/src/ts_cobs.h new file mode 100644 index 0000000..d5491b0 --- /dev/null +++ b/src/ts_cobs.h @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2021 Charles Nicholson, charles.nicholson@gmail.com + * Copyright (c) 2022 Bobby Noelte. + * SPDX-License-Identifier: Unlicense + */ + +/** + * @file + * @brief ThingSet COBS (private interface) + */ + +#ifndef TS_COBS_H_ +#define TS_COBS_H_ + +/** + * @brief ThingSet COBS encoder/ decoder. + * + * Copied from https://github.com/charlesnicholson/nanocobs. + * + * Implementation of the [Consistent Overhead Byte Stuffing] + * (https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing) ("COBS") + * algorithm, defined in the [paper](http://www.stuartcheshire.org/papers/COBSforToN.pdf) by + * Stuart Cheshire and Mary Baker. + * + * Users can encode and decode data in-place or into separate target buffers. Encoding can be + * incremental; users can encode multiple small buffers (e.g. header, then payloads) into one target. + * The `cobs` runtime requires no extra memory overhead. + * + * @defgroup ts_cobs_api_priv ThingSet COBS (private interface) + * @{ + */ + +#include "thingset_env.h" + +enum { + /** + * @brief COBS frame delimiter. + * + * All COBS frames end with this value. If you're scanning a data source + * for frame delimiters, the presence of this zero byte indicates the + * completion of a frame. + */ + TS_COBS_FRAME_DELIMITER = 0x00, + + /** @brief In-place encoding mandatory placeholder byte values. */ + TS_COBS_INPLACE_SENTINEL_VALUE = 0x5A, + + /** @brief In-place encodings that fit in a buffer of this size will always succeed. */ + TS_COBS_INPLACE_SAFE_BUFFER_SIZE = 256 +}; + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @def TS_COBS_ENCODE_MAX + * + * @brief Maximum possible size of buffer required to encode content. + * + * Returns the maximum possible size in bytes of the buffer required to encode + * a buffer of length @p decoded_len. Cannot fail. Defined as a macro to facilitate + * compile-time sizing of buffers. + * + * @note @p decoded_len is evaluated multiple times; don't call with mutating + * expressions! e.g. Don't do "TS_COBS_ENCODE_MAX(i++)". + * + * @param[in] decoded_len Length of decoded content + * @return The maximum possible size in bytes of the buffer required to encode + * a buffer of length @p decoded_len. + */ +#define TS_COBS_ENCODE_MAX(decoded_len) \ + (1 + (decoded_len) + (((decoded_len) + 253) / 254) + ((decoded_len) == 0)) + +/** + * @brief Decode in-place the contents of the provided buffer. + * + * Decode in-place the contents of the provided buffer @p buf of length @p len. + * + * Because decoding is in-place, the first and last bytes of @p buf will be set + * to the value TS_COBS_INPLACE_SENTINEL_VALUE if decoding succeeds. The decoded + * contents are stored in the inclusive span defined by buf[1] and buf[len-2]. + * + * @warning If the function returns -EBADMSG, the contents of @p buf are + * left indeterminate and must not be relied on to be fully encoded or decoded. + * + * @param[in] len Length of encoded content. + * @param[in,out] buf Pointer to buffer. + * @return 0 on success, <0 on error. + * @retval -EINVAL if a null pointer or invalid length are provided. + * @retval -EBADMSG if the encoded buffer contains any code bytes that exceed len or if the buffer + * starts with a 0 byte, or ends in a nonzero byte. + */ +int ts_cobs_decode_inplace(unsigned len, void *buf); + +/** + * @brief Encode in-place the contents of the provided buffer. + * + * Encode in-place the contents of the provided buffer @p buf of length @p len. + * + * Because encoding adds leading and trailing bytes, your buffer must reserve + * bytes 0 and len-1 for the encoding. If the first and last bytes of @p buf + * are not set to TS_COBS_INPLACE_SENTINEL_VALUE, the function will fail with + * -EBADMSG. + * + * If @p len is less than or equal to TS_COBS_INPLACE_SAFE_BUFFER_SIZE, the + * contents of @p buf will never cause encoding to fail. If @p len is larger + * than TS_COBS_INPLACE_SAFE_BUFFER_SIZE, encoding can possibly fail with + * -EBADMSG if there are more than 254 bytes between zeros. + * + * @warning If the function returns -EBADMSG, the contents of @p buf are + * left indeterminate and must not be relied on to be fully encoded or decoded. + * + * @param[in] len Length of encoded content including sentinel value at bytes 0 and len - 1. + * @param[in,out] buf Pointer to buffer. + * @return 0 on success, <0 on error. + * @retval -EINVAL if a null pointer or invalid length are provided. + * @retval -EBADMSG if the first and last bytes of buf are not set to TS_COBS_INPLACE_SENTINEL_VALUE + * or if len is larger than TS_COBS_INPLACE_SAFE_BUFFER_SIZE and there are more than + * 254 bytes between zeros. + */ +int ts_cobs_encode_inplace(unsigned int len, void *buf); + +/** + * @brief Decode encoded bytes. + * + * Decode @p enc_len encoded bytes from @p enc into @p out_dec, storing the decoded + * length in @p out_dec_len. + * + * @param[in] enc_len Length of encoded content. + * @param[in] enc Pointer to buffer of encoded content. + * @param[in] dec_max Maximum length for decoded content. + * @param[out] out_dec_len Pointer to store the length of the decoded content. + * @param[out] out_dec Pointer to buffer to store the decoded content. + * @return 0 on success, <0 on error. + * @retval -EINVAL if any of the input pointers are null, or if any of the lengths are invalid. + * @retval -EBADMSG if enc starts with a 0 byte, or does not end with a 0 byte. + * @retval -ENOMEM if the decoding exceeds dec_max bytes. + */ +int ts_cobs_decode(unsigned int enc_len, void const *enc, unsigned int dec_max, + unsigned int *out_dec_len, void *out_dec); + + +/** + * @brief Encode decoded bytes. + * + * Encode @p dec_len decoded bytes from @p dec into @p out_enc, storing the encoded + * length in @p out_enc_len. + * + * @param[in] dec_len Length of decoded content. + * @param[in] dec Pointer to buffer of decoded content. + * @param[in] enc_max Maximum length for encoded content. + * @param[out] out_enc_len Pointer to store the length of the encoded content. + * @param[out] out_enc Pointer to buffer to store the encoded content. + * @return 0 on success, <0 on error. + * @retval -EINVAL if any of the input pointers are null, or if any of the lengths are invalid. + * @retval -EBADMSG if enc starts with a 0 byte, or does not end with a 0 byte. + * @retval -ENOMEM if the encoding exceeds enc_max bytes. + */ +int ts_cobs_encode(unsigned int dec_len, void const *dec, unsigned int enc_max, + unsigned int *out_enc_len, void *out_enc); + + +/* Incremental encoding API */ + +/** @brief Intermediate encoding state for in cremental encoding. */ +struct ts_cobs_enc_ctx { + void *dst; + unsigned dst_max; + unsigned cur; + unsigned code_idx; + unsigned code; + int need_advance; +}; + + +/** + * @brief Begin an incremental encoding of data. + * + * Begin an incremental encoding of data into @p out_enc. The intermediate encoding state is stored + * in @p ctx, which can then be passed into future calls to ts_cobs_encode_inc(). + * + * @warning If the function returns an error, @p ctx shall not be used in future calls to + * ts_cobs_encode_inc(). + * + * @param[in] ctx Pointer to intermediate encoding state. + * @param[in] enc_max Maximum length for encoded content. + * @param[in] out_enc Pointer to buffer to store the encoded content. + * @return 0 on success, <0 on error. + * @retval -EINVAL if out_enc or out_ctx are null, or if enc_max is not large enough to + * hold the smallest possible encoding, + */ +int ts_cobs_encode_inc_begin(struct ts_cobs_enc_ctx *ctx, unsigned int enc_max, void *out_enc); + + +/** + * @brief Continue an encoding in progress with the new buffer. + * + * Continue an encoding in progress with the new @p dec buffer of length @p dec_len. Encodes + * @p dec_len decoded bytes from @p dec into the buffer that @p ctx was initialized with in + * ts_cobs_encode_inc_begin(). + * + * If the contents pointed to by @p dec can not be encoded in the remaining available buffer space, + * the function returns -ENOMEM. In this case, @p ctx remains unchanged and incremental encoding can + * be attempted again with different data, or finished with ts_cobs_encode_inc_end(). + * + * @param[in] ctx Pointer to intermediate encoding state. + * @param[in] dec_len Length of decoded content. + * @param[in] dec Pointer to buffer of decoded content. + * @return 0 on success, <0 on error. + * @retval -EINVAL if any of the input pointers are null, or dec_len is zero. + * @retval -ENOMEM if the contents pointed to by dec can not be encoded in the remaining + * available buffer space. + */ +int ts_cobs_encode_inc(struct ts_cobs_enc_ctx *ctx, unsigned int dec_len, void const *dec); + + +/** + * @brief Finish an incremental encoding. + * + * Finish an incremental encoding by writing the final code and delimiter. + * + * The final encoded length is written to @p out_enc_len, and the buffer + * passed to ts_cobs_encode_inc_begin() holds the full COBS-encoded frame. + * + * @warning No further calls to ts_cobs_encode_inc() or ts_cobs_encode_inc_end() can be safely made + * until @p ctx is re-initialized via a new call to ts_cobs_encode_inc_begin(). + * + * @param[in] ctx Pointer to intermediate encoding state. + * @param[out] out_enc_len Pointer to store the length of the encoded content. + * @return 0 on success, <0 on error. + * @retval -EINVAL if any of the input pointers are null. + */ +int ts_cobs_encode_inc_end(struct ts_cobs_enc_ctx *ctx, unsigned int *out_enc_len); + + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* TS_COBS_H_ */ From 039aa1534849b96f4c97c02b6c513b2e6b37d543 Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Sun, 19 Dec 2021 08:04:13 +0100 Subject: [PATCH 16/37] COM prepare - add ThingSet low memory footprint JSON parser Prepare for ThingSet Communication Context: - Add/ adapt JSMN JSON parser to even consume less memory. JSON parse information may become meta data that is associated to a communication message for all the message lifetime. Roughly half memory footprint, compared to current solution, for JSON tokens. Reduced storage need for tokens comes with restrictions: - The total length of the JSON data string is limited to a maximum of 2047 characters. - start: The start position of a token in the JSON data string is restricted to 0..2046 - end: The end position of a token must be withing 2047 bytes following the start position. - size: The number of tokens of a super token (eg. elements in an array) is restricted to 0..127. Signed-off-by: Bobby Noelte --- src/ts_jsmn.c | 569 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/ts_jsmn.h | 208 ++++++++++++++++++ 2 files changed, 777 insertions(+) create mode 100644 src/ts_jsmn.c create mode 100644 src/ts_jsmn.h diff --git a/src/ts_jsmn.c b/src/ts_jsmn.c new file mode 100644 index 0000000..fe33aa0 --- /dev/null +++ b/src/ts_jsmn.c @@ -0,0 +1,569 @@ +/* + * Copyright (c) 2010 Serge Zaitsev + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: MIT + * + * Copied from https://github.com/zserge/jsmn. + */ + +/** + * @file + * @brief ThingSet JSMN + */ + +#include "ts_jsmn.h" +#include "ts_macro.h" + +static inline uint16_t token_pos(struct ts_jsmn_token *token) +{ + return (uint16_t)(token->pos); +} + + +static inline uint16_t token_len(struct ts_jsmn_token *token) +{ + return (uint16_t)(token->len); +} + +static inline uint16_t token_end(struct ts_jsmn_token *token) +{ + return (uint16_t)(token->pos + token->len); +} + +static inline uint16_t token_type(struct ts_jsmn_token *token) +{ + return (uint16_t)(token->type); +} + +static inline uint16_t token_size(struct ts_jsmn_token *token) +{ + return (uint16_t)(token->size); +} + +static inline void token_set_pos(struct ts_jsmn_token *token, uint16_t pos) +{ + token->pos = pos; +} + +static inline void token_set_len(struct ts_jsmn_token *token, uint16_t len) +{ + token->len = len; +} + +static inline void token_set_end(struct ts_jsmn_token *token, uint16_t end) +{ + token->len = end - token->pos; +} + +static inline void token_set_type(struct ts_jsmn_token *token, uint16_t type) +{ + token->type = type; +} + +static inline void token_set_size(struct ts_jsmn_token *token, uint16_t size) +{ + token->size = size; +} + +static inline bool token_has_start(struct ts_jsmn_token *token) +{ + return token->pos != TS_JSMN_TOKEN_POS_MAX; +} + +static inline bool token_has_end(struct ts_jsmn_token *token) +{ + return token->len > 0; +} + +static inline bool token_is_type(struct ts_jsmn_token *token, uint16_t type) +{ + return token->type == type; +} + +static inline bool token_has_size(struct ts_jsmn_token *token) +{ + return token->size > 0; +} + +static inline void token_increment_size(struct ts_jsmn_token *token) +{ + token->size += 1; +} + +static bool token_is_valid(struct ts_jsmn_token *token) +{ + if (token == NULL) { + return false; + } + uint16_t val; + + val = token_type(token); + if ((val != TS_JSMN_OBJECT) && (val != TS_JSMN_ARRAY) && (val != TS_JSMN_STRING) + && (val != TS_JSMN_PRIMITIVE)) { + return false; + } + if (!token_has_start(token) || !token_has_end(token)) { + return false; + } + return true; +} + +/** + * @brief Create new token. + * + * Allocate a fresh unused token from the token pool and fill token type and boundaries. + */ +static int new_token(struct ts_jsmn_context *jsmn, uint16_t type, uint16_t start, uint16_t end, + struct ts_jsmn_token **token) +{ + /* Don't allow maximum for start position - regarded invalid */ + //TS_ASSERT(TS_JSMN_TOKEN_POS_MAX > start, "start (%u) >= TS_JSMN_TOKEN_POS_MAX (%u)", + // (unsigned int)start, (unsigned int)TS_JSMN_TOKEN_POS_MAX); + //TS_ASSERT(TS_JSMN_TOKEN_POS_MAX >= end, "end (%u) > TS_JSMN_TOKEN_POS_MAX (%u)", + // (unsigned int)end, (unsigned int)TS_JSMN_TOKEN_POS_MAX); + + if (jsmn->scratchpad.parser.toknext >= jsmn->num_tokens) { + return TS_JSMN_ERROR_NOMEM; + } + + /* Allocate a fresh unused token from the token pool. */ + struct ts_jsmn_token *tok = &jsmn->tokens[jsmn->scratchpad.parser.toknext++]; + + /* Fill token type and boundaries */ + token_set_pos(tok, start); + if (end > start) { + //TS_ASSERT(TS_JSMN_TOKEN_LEN_MAX >= (end - start), "len (%u) >= TS_JSMN_TOKEN_LEN_MAX (%u)", + // (unsigned int)(end - start), (unsigned int)TS_JSMN_TOKEN_LEN_MAX); + token_set_end(tok, end); + } else { + token_set_len(tok, 0); + } + token_set_type(tok, type); + token_set_size(tok, 0); +#ifdef JSMN_PARENT_LINKS + tok->parent = jsmn->scratch.parser.toksuper; +#endif + + /* return token */ + *token = tok; + return 0; +} + +/** + * Fills next available token with JSON primitive. + */ +static int parse_primitive(struct ts_jsmn_context *jsmn, const char *js, const size_t len) +{ + int ret; + struct ts_jsmn_token *token; + int start = jsmn->scratchpad.parser.pos; + + for (; jsmn->scratchpad.parser.pos < len && js[jsmn->scratchpad.parser.pos] != '\0'; jsmn->scratchpad.parser.pos++) { + switch (js[jsmn->scratchpad.parser.pos]) { +#ifndef JSMN_STRICT + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case ':': +#endif + case '\t': + case '\r': + case '\n': + case ' ': + case ',': + case ']': + case '}': + goto found; + break; + default: + /* to quiet a warning from gcc*/ + break; + } + if (js[jsmn->scratchpad.parser.pos] < 32 || js[jsmn->scratchpad.parser.pos] >= 127) { + jsmn->scratchpad.parser.pos = start; + return TS_JSMN_ERROR_INVAL; + } + } + +#ifdef JSMN_STRICT + /* In strict mode primitive must be followed by a comma/object/array */ + jsmn->scratch.parser.pos = start; + return TS_JSMN_ERROR_PART; +#endif + +found: + ret = new_token(jsmn, TS_JSMN_PRIMITIVE, start, jsmn->scratchpad.parser.pos, &token); + if (ret != 0) { + jsmn->scratchpad.parser.pos = start; + } else { + jsmn->scratchpad.parser.pos--; + } + return ret; +} + +/** + * Fills next token with JSON string. + */ +static int parse_string(struct ts_jsmn_context *jsmn, const char *js, const size_t len) +{ + int start = jsmn->scratchpad.parser.pos; + + jsmn->scratchpad.parser.pos++; + + /* Skip starting quote */ + for (; jsmn->scratchpad.parser.pos < len && js[jsmn->scratchpad.parser.pos] != '\0'; jsmn->scratchpad.parser.pos++) { + char c = js[jsmn->scratchpad.parser.pos]; + + /* Quote: end of string */ + if (c == '\"') { + struct ts_jsmn_token *token; + int ret = new_token(jsmn, TS_JSMN_STRING, start + 1, jsmn->scratchpad.parser.pos, &token); + if (ret != 0) { + jsmn->scratchpad.parser.pos = start; + } + return ret; + } + + /* Backslash: Quoted symbol expected */ + if (c == '\\' && jsmn->scratchpad.parser.pos + 1 < len) { + int i; + jsmn->scratchpad.parser.pos++; + switch (js[jsmn->scratchpad.parser.pos]) { + /* Allowed escaped symbols */ + case '\"': + case '/': + case '\\': + case 'b': + case 'f': + case 'r': + case 'n': + case 't': + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + jsmn->scratchpad.parser.pos++; + for (i = 0; i < 4 && jsmn->scratchpad.parser.pos < len && js[jsmn->scratchpad.parser.pos] != '\0'; i++) { + /* If it isn't a hex character we have an error */ + if (!((js[jsmn->scratchpad.parser.pos] >= 48 && js[jsmn->scratchpad.parser.pos] <= 57) || /* 0-9 */ + (js[jsmn->scratchpad.parser.pos] >= 65 && js[jsmn->scratchpad.parser.pos] <= 70) || /* A-F */ + (js[jsmn->scratchpad.parser.pos] >= 97 && js[jsmn->scratchpad.parser.pos] <= 102))) { /* a-f */ + jsmn->scratchpad.parser.pos = start; + return TS_JSMN_ERROR_INVAL; + } + jsmn->scratchpad.parser.pos++; + } + jsmn->scratchpad.parser.pos--; + break; + /* Unexpected symbol */ + default: + jsmn->scratchpad.parser.pos = start; + return TS_JSMN_ERROR_INVAL; + } + } + } + jsmn->scratchpad.parser.pos = start; + return TS_JSMN_ERROR_PART; +} + +/** + * Parse JSON string and fill tokens. + */ +int ts_jsmn_parse(struct ts_jsmn_context *jsmn, const char *js, uint16_t len) +{ + int ret = 0; + int count = 0; + + if (len >= TS_JSMN_JSON_LEN_MAX) { + ret = TS_JSMN_ERROR_NOMEM; + goto ts_jsmn_parse_end; + } + + /* prepare parser */ + jsmn->scratchpad.parser.pos = 0; + jsmn->scratchpad.parser.toknext = 0; + jsmn->scratchpad.parser.toksuper = -1; + + int i; + struct ts_jsmn_token *token; + + for (; jsmn->scratchpad.parser.pos < len && js[jsmn->scratchpad.parser.pos] != '\0'; jsmn->scratchpad.parser.pos++) { + char c; + uint16_t type; + + c = js[jsmn->scratchpad.parser.pos]; + switch (c) { + case '{': + case '[': + type = (c == '{' ? TS_JSMN_OBJECT : TS_JSMN_ARRAY); + count++; + /** @todo compare with original jsmn - validity of token->parent */ + ret = new_token(jsmn, type, jsmn->scratchpad.parser.pos, 0, &token); + if (ret != 0) { + goto ts_jsmn_parse_end; + } + if (jsmn->scratchpad.parser.toksuper != -1) { + struct ts_jsmn_token *tok = &jsmn->tokens[jsmn->scratchpad.parser.toksuper]; +#ifdef JSMN_STRICT + // In strict mode an object or array can't become a key + if (token_is_type(tok, TS_JSMN_OBJECT)) { + ret = TS_JSMN_ERROR_INVAL; + goto ts_jsmn_parse_end; + } +#endif + token_increment_size(tok); + } + jsmn->scratchpad.parser.toksuper = jsmn->scratchpad.parser.toknext - 1; + break; + case '}': + case ']': + type = (c == '}' ? TS_JSMN_OBJECT : TS_JSMN_ARRAY); +#ifdef JSMN_PARENT_LINKS + if (jsmn->scratch.parser.toknext < 1) { + ret = TS_JSMN_ERROR_INVAL; + goto ts_jsmn_parse_end; + } + token = &jsmn->tokens[jsmn->scratch.parser.toknext - 1]; + for (;;) { + if (token_has_start(token) && !token_has_end(token)) { + if (!token_is_type(token, type)) { + ret = TS_JSMN_ERROR_INVAL; + goto ts_jsmn_parse_end; + } + token_set_end(jsmn->scratch.parser.pos + 1); + jsmn->scratch.parser.toksuper = token->parent; + break; + } + if (token->parent == -1) { + if (!token_is_type(token, type) || (jsmn->scratch.parser.toksuper == -1)) { + ret = TS_JSMN_ERROR_INVAL; + goto ts_jsmn_parse_end; + } + break; + } + token = &tokens[token->parent]; + } +#else + for (i = jsmn->scratchpad.parser.toknext - 1; i >= 0; i--) { + token = &jsmn->tokens[i]; + if (token_has_start(token) && !token_has_end(token)) { + if (!token_is_type(token, type)) { + ret = TS_JSMN_ERROR_INVAL; + goto ts_jsmn_parse_end; + } + jsmn->scratchpad.parser.toksuper = -1; + token_set_end(token, jsmn->scratchpad.parser.pos + 1); + break; + } + } + /* Error if unmatched closing bracket */ + if (i == -1) { + ret = TS_JSMN_ERROR_INVAL; + goto ts_jsmn_parse_end; + } + for (; i >= 0; i--) { + token = &jsmn->tokens[i]; + if (token_has_start(token) && !token_has_end(token)) { + jsmn->scratchpad.parser.toksuper = i; + break; + } + } +#endif + break; + case '\"': + ret = parse_string(jsmn, js, len); + if (ret != 0) { + goto ts_jsmn_parse_end; + } + count++; + if (jsmn->scratchpad.parser.toksuper != -1) { + token_increment_size(&jsmn->tokens[jsmn->scratchpad.parser.toksuper]); + } + break; + case '\t': + case '\r': + case '\n': + case ' ': + break; + case ':': + jsmn->scratchpad.parser.toksuper = jsmn->scratchpad.parser.toknext - 1; + break; + case ',': + if (jsmn->scratchpad.parser.toksuper != -1 && + !token_is_type(&jsmn->tokens[jsmn->scratchpad.parser.toksuper], TS_JSMN_ARRAY) && + !token_is_type(&jsmn->tokens[jsmn->scratchpad.parser.toksuper], TS_JSMN_OBJECT)) { +#ifdef JSMN_PARENT_LINKS + jsmn->scratch.parser.toksuper = jsmn->tokens[jsmn->scratch.parser.toksuper].parent; +#else + for (i = jsmn->scratchpad.parser.toknext - 1; i >= 0; i--) { + if (token_is_type(&jsmn->tokens[i], TS_JSMN_ARRAY) || + token_is_type(&jsmn->tokens[i], TS_JSMN_OBJECT)) { + if (token_has_start(&jsmn->tokens[i]) && !token_has_end(&jsmn->tokens[i])) { + jsmn->scratchpad.parser.toksuper = i; + break; + } + } + } +#endif + } + break; +#ifdef JSMN_STRICT + /* In strict mode primitives are: numbers and booleans */ + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 't': + case 'f': + case 'n': + /* And they must not be keys of the object */ + if (jsmn->scratch.parser.toksuper != -1) { + const struct ts_jsmn_token *tok = &jsmn->tokens[jsmn->scratch.parser.toksuper]; + if (token_is_type(tok, TS_JSMN_OBJECT) || + (token_is_type(tok, TS_JSMN_STRING) && token_has_size(tok))) { + ret = TS_JSMN_ERROR_INVAL; + goto ts_jsmn_parse_end; + } + } +#else + /* In non-strict mode every unquoted value is a primitive */ + default: +#endif + ret = parse_primitive(jsmn, js, len); + if (ret != 0) { + goto ts_jsmn_parse_end; + } + count++; + if (jsmn->scratchpad.parser.toksuper != -1) { + token_increment_size(&jsmn->tokens[jsmn->scratchpad.parser.toksuper]); + } + break; + +#ifdef JSMN_STRICT + /* Unexpected char in strict mode */ + default: + ret = TS_JSMN_ERROR_INVAL; + goto ts_jsmn_parse_end; +#endif + } + } + + for (i = jsmn->scratchpad.parser.toknext - 1; i >= 0; i--) { + /* Unmatched opened object or array */ + if (token_has_start(&jsmn->tokens[i]) && !token_has_end(&jsmn->tokens[i])) { + ret = TS_JSMN_ERROR_PART; + goto ts_jsmn_parse_end; + } + } + +ts_jsmn_parse_end: + if ((ret == 0) && (count > 0)) { + jsmn->scratchpad.result.js = js; + jsmn->scratchpad.result.token_count = count; + } else { + jsmn->scratchpad.result.js = 0; + jsmn->scratchpad.result.token_count = 0; + } + return ret; +} + +/** + * @brief Initialise parser context. + * + * Create a new parser based over a given buffer with an array of tokens available. + */ +int ts_jsmn_init(struct ts_jsmn_context *jsmn, uint16_t num_tokens) +{ + /* Assert pointer alignment - align to 4 */ + TS_ASSERT(((uintptr_t)jsmn & 0x03) == 0, "JSM: %s context pointer %p not aligned", __func__, + jsmn); + + jsmn->scratchpad.result.js = NULL; + jsmn->scratchpad.result.token_count = 0; + + if (num_tokens == 0) { + return TS_JSMN_ERROR_NOMEM; + } + jsmn->num_tokens = num_tokens; + for (uint16_t i = 0; i < jsmn->num_tokens; i++) { + /* set start to invalid */ + token_set_pos(&jsmn->tokens[i], TS_JSMN_TOKEN_POS_MAX); + } + + return 0; +} + +uint16_t ts_jsmn_token_count(struct ts_jsmn_context *jsmn) +{ + return jsmn->scratchpad.result.token_count; +} + +const char *ts_jsmn_token_start(struct ts_jsmn_context *jsmn, uint16_t token_idx) +{ + return jsmn->scratchpad.result.js + token_pos(&jsmn->tokens[token_idx]); +} + +int ts_jsmn_token_by_index(struct ts_jsmn_context *jsmn, uint16_t token_idx, + uint16_t *type, uint16_t *size, const char **start, uint16_t *len) +{ + if (token_idx >= jsmn->num_tokens) { + return TS_JSMN_ERROR_NOMEM; + } + if (!token_is_valid(&jsmn->tokens[token_idx])) { + return TS_JSMN_ERROR_NOMEM; + } + *type = token_type(&jsmn->tokens[token_idx]); + *size = token_size(&jsmn->tokens[token_idx]); + *start = ts_jsmn_token_start(jsmn, token_idx); + *len = token_len(&jsmn->tokens[token_idx]); + + return 0; +} + +void ts_jsmn_dump(struct ts_jsmn_context *jsmn, char *dump, size_t len) +{ + TS_ASSERT(dump != NULL, "JSMN: %s on invalid dump buffer pointer (NULL)", __func__); + TS_ASSERT(len > 0, "JSMN: %s on invalid dump buffer len (0)", __func__); + + uint16_t tok_type; + uint16_t tok_size; + const char *tok_start; + uint16_t tok_len; + + size_t pos = 0; + for(uint16_t i = 0; (i < jsmn->num_tokens) && (pos < len); i++) { + if (ts_jsmn_token_by_index(jsmn, i, &tok_type, &tok_size, &tok_start, &tok_len) != 0) { + break; + } + const char *type; + if (tok_type == TS_JSMN_UNDEFINED) { + type = "UNDEF "; + } + else if (tok_type == TS_JSMN_OBJECT) { + type = "OBJECT "; + } + else if (tok_type == TS_JSMN_ARRAY) { + type = "ARRAY "; + } + else if (tok_type == TS_JSMN_STRING) { + type = "STRING "; + } + else if (tok_type == TS_JSMN_PRIMITIVE) { + type = "PRIMITIV"; + } + else { + type = "unknown "; + } + pos += snprintf(&dump[pos], len - pos, "#%.*u: %s %u '%.*s'\n", + 2, (unsigned int)i, type, (unsigned int)tok_size, + (int)tok_len, tok_start); + } + if (pos >= len) { + pos = len - 1; + } + dump[pos] = '\0'; +} diff --git a/src/ts_jsmn.h b/src/ts_jsmn.h new file mode 100644 index 0000000..12ecdba --- /dev/null +++ b/src/ts_jsmn.h @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2010 Serge Zaitsev + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: MIT + */ + +/** + * @file + * @brief ThingSet JSMN (private interface) + */ + +#ifndef TS_JSMN_H_ +#define TS_JSMN_H_ + +/** + * @brief ThingSet JSMN JSON parser. + * + * Copied from https://github.com/zserge/jsmn. + * + * Reduced storage need for tokens by the following restrictions: + * - The total length of the JSON data string is limited to a maximum of 2047 characters. + * - start: The start position of a token in the JSON data string is restricted to 0..2046 + * - end: The end position of a token must be withing 2047 bytes following the start position. + * - size: The number of tokens of a super token (eg. elements in an array) is restricted to 0..127. + * + * @defgroup ts_jsmn_api_priv ThingSet JSMN (private interface) + * @{ + */ + +#include "thingset_env.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Maximum position of token in JSON data string */ +#define TS_JSMN_TOKEN_POS_MAX 0x07FFU + +#define TS_JSMN_TOKEN_POS_MAX_BITS (11) + +/** @brief Maximum length of token in JSON data string */ +#define TS_JSMN_TOKEN_LEN_MAX 0x07FFU + +#define TS_JSMN_TOKEN_LEN_MAX_BITS (11) + +/** @brief Maximum muber of tokens of a super token */ +#define TS_JSMN_TOKEN_SIZE_MAX 0x007FU + +#define TS_JSMN_TOKEN_SIZE_MAX_BITS (7) + +/** @brief Maximum muber of tokens of a super token */ +#define TS_JSMN_TOKEN_TYPE_MAX 0x0007U + +#define TS_JSMN_TOKEN_TYPE_MAX_BITS (3) + +/** @brief Maximum total length of JSON data string */ +#define TS_JSMN_JSON_LEN_MAX TS_JSMN_TOKEN_POS_MAX + + +/** @brief JSON undefined type identifier. */ +#define TS_JSMN_UNDEFINED (0) + +/** @brief JSON object type identifier. */ +#define TS_JSMN_OBJECT (1) + +/** @brief JSON array type identifier. */ +#define TS_JSMN_ARRAY (2) + +/** @brief JSON string type identifier. */ +#define TS_JSMN_STRING (3) + +/** + * @brief JSON primitive type identifier. + * + * Number, boolean (true/false) or null. + */ +#define TS_JSMN_PRIMITIVE (4) + +enum ts_jsmn_error { + /* Not enough tokens were provided */ + TS_JSMN_ERROR_NOMEM = -ENOMEM, + /* Invalid character inside JSON string */ + TS_JSMN_ERROR_INVAL = -EINVAL, + /* The string is not a full JSON packet, more bytes expected */ + TS_JSMN_ERROR_PART = -ENAVAIL +}; + +/** + * @brief JSON token descriptor. + */ +struct ts_jsmn_token { + /** @brief Type of token */ + uint32_t type : TS_JSMN_TOKEN_TYPE_MAX_BITS; + /** @brief Start position in JSON data string */ + uint32_t pos : TS_JSMN_TOKEN_POS_MAX_BITS; + /** @brief Size of token (aka. number of sub-tokens) */ + uint32_t size : TS_JSMN_TOKEN_SIZE_MAX_BITS; + /** @brief Length of token in JSON data string */ + uint32_t len : TS_JSMN_TOKEN_LEN_MAX_BITS; +#ifdef JSMN_PARENT_LINKS + int16_t parent; +#endif +}; + +/** + * @brief JSON parser context. + * + * Contains an array of token blocks available. Also stores + * the string being parsed now and current position in that string. + */ +struct ts_jsmn_context { + /** @brief Scratchpad memory. */ + union { + struct { + /** @brief Offset in the JSON data string. */ + uint16_t pos; + /** @brief Next token to allocate. */ + uint16_t toknext; + /** @brief Superior token node, e.g. parent object or array. */ + int16_t toksuper; + } parser; + struct { + /** @brief Pointer to JSON data string that was parsed. */ + const char *js; + /** @brief Number of tokens that were parsed. */ + uint16_t token_count; + } result; + } scratchpad; + /** @brief Total number of tokens that can be used by the parser. */ + uint16_t num_tokens; + /** @brief Array of tokens. */ + struct ts_jsmn_token tokens[0]; +}; + +#define TS_JSMN_CONTEXT_NUM_TOKEN_FROM_SIZE(size) \ + ((size - sizeof(struct ts_jsmn_context)) / sizeof(struct ts_jsmn_token)) + +#define TS_JSMN_CONTEXT_NUM_TOKEN(buffer_name) \ + TS_JSMN_CONTEXT_NUM_TOKEN_FROM_SIZE(sizeof(buffer_name)) + +#define TS_JSMN_CONTEXT(buffer_name) (struct ts_jsmn_context *)&buffer_name[0] + +/** + * @brief Create JSON parser over an array of tokens + */ +int ts_jsmn_init(struct ts_jsmn_context *jsmn, uint16_t num_tokens); + +/** + * @brief Run JSON parser. + * + * Parse a JSON data string into an array of tokens, each describing a single JSON object. + * + * @param[in] jsmn Parser context. + * @param[in] js Pointer to JSON data string. + * @param[in] len Length of JSON data string. + * @returns 0 on success, <0 otherwise + */ +int ts_jsmn_parse(struct ts_jsmn_context *jsmn, const char *js, uint16_t len); + +/** + * @brief Number of parsed tokens. + * + * @param[in] jsmn Parser context. + * @returns Number of parsed tokens. + */ +uint16_t ts_jsmn_token_count(struct ts_jsmn_context *jsmn); + +/** + * @brief Start of token text. + * + * @param[in] jsmn Parser context. + * @param[in] token_idx Token index. + * @returns Pointer to token JSON text.. + */ +const char *ts_jsmn_token_start(struct ts_jsmn_context *jsmn, uint16_t token_idx); + +/** + * @brief Get info of token with given index in token array. + * + * @param[in] jsmn Parser context. + * @param[in] token_idx Token index. + * @param[out] type Pointer to write type info to. + * @param[out] size Pointer to write size info to. + * @param[out] start Pointer to write token text start position to. + * @param[out] len Pointer to write token text length info to. + * @returns 0 on success, <0 otherwise. + */ +int ts_jsmn_token_by_index(struct ts_jsmn_context *jsmn, uint16_t token_idx, + uint16_t *type, uint16_t *size, const char **start, uint16_t *len); + +/** + * @brief Dump parser token array in human readable form. + * + * @param[in] jsmn Parser context. + * @param[in] dump Ponter to dump buffer. + * @param[in] len Length of dump buffer. + */ +void ts_jsmn_dump(struct ts_jsmn_context *jsmn, char *dump, size_t len); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* TS_JSMN_H_ */ From b7cdb6b8284c5189e24d55988a991f20da31396d Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Mon, 7 Feb 2022 11:11:42 +0100 Subject: [PATCH 17/37] COM prepare - add ThingSet remote two-way queue interface and implementation Prepare for ThingSet Communication Context: - Add ThingSet interface for remote two way queues. - Add Thingset remote two way queue implementation. RBBQ is a Single Producer Single Consumer, lockless, no_std, thread safe, two-way queue. The remote two-way queue connects the local end of the queue with a remote end. It abstracts away the communication between the queue ends. The two-way queue is made of two lock free ring buffers with contiguous reservations. The ring buffers are implementations of the [lock-free ring-buffer with contiguous reservations] (https://blog.systems.ethz.ch/blog/2019/the-design-and-implementation-of-a-lock-free-ring-buffer-with-contiguous-reservations.html) described by Andrea Lattuada and James Munns. RBBQ is designed (primarily) to be a First-In, First-Out queue for use with DMA on embedded systems. While Circular/ Ring Buffers allow you to send data between two threads (or from an interrupt to main code), you must push the data one piece at a time. With RBBQ, you instead are granted a block of contiguous memory, which can be filled (or emptied) by a DMA engine. RBBQ works on communication ports. Data may not only be exchanged between threads sharing the ring buffer memory but also between devices that are connected by some kind of communication. This mainly targets communication where you can exchange fixed size (one ring buffer size) data packets to synchronize the ring buffers of the remote two-way queues (e.g. by SPI). Here you gain the benefit of DMA transfers between the devices and DMA transfers to fill or empty the ring buffers. Signed-off-by: Bobby Noelte --- src/thingset_rbbq.h | 205 +++++++++++++++++++ src/ts_rbbq.c | 465 ++++++++++++++++++++++++++++++++++++++++++++ src/ts_rbbq.h | 413 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 1083 insertions(+) create mode 100644 src/thingset_rbbq.h create mode 100644 src/ts_rbbq.c create mode 100644 src/ts_rbbq.h diff --git a/src/thingset_rbbq.h b/src/thingset_rbbq.h new file mode 100644 index 0000000..6f05bd6 --- /dev/null +++ b/src/thingset_rbbq.h @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2020..2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet remote two-way queue (public interface) + */ + +#ifndef THINGSET_RBBQ_H_ +#define THINGSET_RBBQ_H_ + +/** + * @brief ThingSet remote two-way queue. + * + * The remote two-way queue connects the local end of the queue with a remote end. It abstracts + * away the communication between the queue ends. + * + * The remote two-way queue is made up of two lock free ring buffers providing contiguous + * reservations of data blocks. One ring buffer is for transmit, the other one for receive. + * + * @defgroup ts_rbbq_api_pub ThingSet remote two-way queue (public interface) + * @{ + */ + +#include "thingset_env.h" +#include "thingset_time.h" + +/* forward declaration */ +struct thingset_rbbq; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Allocate a data block in the transmit ring buffer. + * + * The data block has to be transmitted to hand it over to the remote side. + * + * @note A data block that is allocated but not transmitted blocks allocation. + * + * @param[in] queue Pointer to the remote two-way queue + * @param[in] channel Message channel + * @param[in] size The size of the data block + * @param[out] data Pointer to memory allocated to the data block + * @param[in] timeout_ms maximum time to wait in milliseconds + * @return 0 on success, <0 otherwise + */ +int thingset_rbbq_alloc(struct thingset_rbbq *queue, uint16_t channel, uint16_t size, uint8_t **data, + thingset_time_ms_t timeout_ms); + + +/** + * @brief Transmit allocated data block. + * + * @note The data block shall not be accessed after it is transmitted. + * + * @param[in] queue Pointer to the remote two-way queue + * @param[in] data Pointer to the data block + * @return 0 on success, <0 otherwise + */ +int thingset_rbbq_transmit(struct thingset_rbbq *queue, uint8_t *data); + + +/** + * @brief Receive data block. + * + * @param[in] queue Pointer to the remote two-way queue + * @param[out] channel Message channel + * @param[out] size The size of the data block + * @param[out] data Pointer to the data block + * @param[in] timeout_ms maximum time to wait + * @return 0 on success, <0 otherwise + */ +int thingset_rbbq_receive(struct thingset_rbbq *queue, uint16_t *channel, uint16_t *size, + uint8_t **data, thingset_time_ms_t timeout_ms); + + +/** + * @brief Free received data block from the receive ring buffer. + * + * @note The data block shall not be accessed after it is freed. + * + * @param[in] queue Pointer to the remote two-way queue + * @param[in] data Pointer to the data block + * @return 0 on success, <0 otherwise + */ +int thingset_rbbq_free(struct thingset_rbbq *queue, uint8_t *data); + + +/** + * @brief Initialise a queue. + * + * Buffer must be initailized before it can be used. + * + * @param[in] queue Pointer to the remote two-way queue + * @return 0 on success, <0 otherwise + */ +int thingset_rbbq_init(struct thingset_rbbq *queue); + + +/** + * @brief Start data exchange on queue. + * + * Buffer must be initailized before data exchange can be started. + * + * @param[in] queue Pointer to the remote two-way queue + * @return 0 on success, <0 otherwise + */ +int thingset_rbbq_start(struct thingset_rbbq *queue); + + +/** + * @brief Stop data exchange on queue. + * + * @param[in] queue Pointer to the remote two-way queue + * @return 0 on success, <0 otherwise + */ +int thingset_rbbq_stop(struct thingset_rbbq *queue); + + +/** + * @brief Wait for next receive transfer. + * + * @param[in] queue Pointer to the queue + * @param timeout_ms maximum time to wait + */ +int thingset_rbbq_wait_receive(struct thingset_rbbq *queue, thingset_time_ms_t timeout_ms); + + +/** + * @brief Wait for next transmit transfer. + * + * @param[in] queue Pointer to the queue + * @param timeout_ms maximum time to wait + */ +int thingset_rbbq_wait_transmit(struct thingset_rbbq *queue, thingset_time_ms_t timeout_ms); + + +/** + * @brief Monitor rbbq communcation for health. + * + * @param[in] queue Pointer to the remote two-way queue + * @return 0 on health, <0 otherwise + */ +int thingset_rbbq_monitor(struct thingset_rbbq *queue); + + +/** + * @brief Name of the queue. + * + * @param[in] queue Pointer to the queue + * @return Pointer to name, 0 if no name found. + */ +const char *thingset_rbbq_name(struct thingset_rbbq *queue); + + +/** + * @brief Retrieve the queue by name. + * + * The call locks the queue to the current thread. + * + * @param[in] name Buffer name to search for. + * @return pointer to queue structure; NULL if not found or cannot be used. + */ +struct thingset_rbbq *rbbq_get_binding(const char *name); + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +/** + * @} + */ + +/** + * @page ts_topic_rbbq Remote two-way queue + * + * RBBQ is a Single Producer Single Consumer, lockless, no_std, thread safe, two-way queue. + * + * The remote two-way queue connects the local end of the queue with a remote end. It abstracts + * away the communication between the queue ends. + * + * The two-way queue is made of two lock free ring buffers with contiguous reservations. + * The ring buffers are implementations of the [lock-free ring-buffer with contiguous reservations] + * (https://blog.systems.ethz.ch/blog/2019/the-design-and-implementation-of-a-lock-free-ring-buffer-with-contiguous-reservations.html) + * described by Andrea Lattuada and James Munns. + * + * RBBQ is designed (primarily) to be a First-In, First-Out queue for use with DMA on embedded + * systems. While Circular/ Ring Buffers allow you to send data between two threads (or from an + * interrupt to main code), you must push the data one piece at a time. With RBBQ, you instead are + * granted a block of contiguous memory, which can be filled (or emptied) by a DMA engine. + * + * RBBQ works on communication ports. Data may not only be exchanged between threads sharing the + * ring buffer memory but also between devices that are connected by some kind of communication. + * This mainly targets communication where you can exchange fixed size (one ring buffer size) data + * packets to synchronize the ring buffers of the remote two-way queues (e.g. by SPI). + * Here you gain the benefit of DMA transfers between the devices and DMA transfers to fill or empty + * the ring buffers. + */ + +#endif /* THINGSET_RBBQ_H_ */ diff --git a/src/ts_rbbq.c b/src/ts_rbbq.c new file mode 100644 index 0000000..6b86d0f --- /dev/null +++ b/src/ts_rbbq.c @@ -0,0 +1,465 @@ +/** + * Copyright (c) 2020..2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet two-way queue with contiguous reservation + */ + +#include + +#include "ts_rbbq.h" + +#define RBBQ_MESSAGE_ALLOC_NOMEM (-1) +#define RBBQ_MESSAGE_ALLOC_AT_END 0 +#define RBBQ_MESSAGE_ALLOC_AT_START 1 +#define RBBQ_MESSAGE_ALLOC_AT_MIDDLE 2 +#define RBBQ_MESSAGE_FREE_NOMEM (-1) +#define RBBQ_MESSAGE_FREE_TO_WRITE 0 +#define RBBQ_MESSAGE_FREE_TO_WATERMARK 1 +#define RBBQ_MESSAGE_CORRUPTED (-2) + + +/** + * @brief Head of linked list of registered queues. + */ +static struct thingset_rbbq *rbbq_queues = 0; + +static inline int rbbq_alloc_avail(const struct thingset_rbbq *queue, uint16_t size) +{ + uint16_t write_idx = queue->alloc_write_idx; + uint16_t read_idx = atomic_load(&queue->tx_shadow_read_idx); + uint16_t end_idx = queue->port->tx_data_size; + + if (write_idx >= read_idx) { + if (size <= (end_idx - write_idx)) { + return RBBQ_MESSAGE_ALLOC_AT_END; + } else if (size < read_idx) { + return RBBQ_MESSAGE_ALLOC_AT_START; + } + } else { /* write_idx < read_idx */ + if (size < (read_idx - write_idx)) { + return RBBQ_MESSAGE_ALLOC_AT_MIDDLE; + } + } + return RBBQ_MESSAGE_ALLOC_NOMEM; +} + +int thingset_rbbq_alloc(struct thingset_rbbq *queue, uint16_t channel, uint16_t size, uint8_t **data, + thingset_time_ms_t timeout_ms) +{ + int ret; + + if (ts_rbbq_state(queue) != TS_RBBQ_QUEUE_STATE_RUNNING) { + return -EAGAIN; + } + + struct timespec timeout = thingset_time_timeout_spec(timeout_ms); + ret = pthread_mutex_timedlock(&queue->alloc_mutex, &timeout); + if (ret != 0) { + /* cannot lock alloc..transmit operation */ + return ret; + } + + uint8_t *raw_data; + int alloc_mode = rbbq_alloc_avail(queue, size + sizeof(struct ts_rbbq_data_header)); + switch (alloc_mode) { + case RBBQ_MESSAGE_ALLOC_AT_END: + raw_data = queue->port->tx_data + queue->alloc_write_idx; + rbbq_data_channel_set(raw_data, channel); + rbbq_data_payload_size_set(raw_data, size); + *data = rbbq_data_payload(raw_data); + + queue->alloc_data = raw_data; + queue->alloc_watermark_idx = queue->port->tx_data_size; + queue->alloc_write_idx += rbbq_data_size(raw_data); + break; + case RBBQ_MESSAGE_ALLOC_AT_START: + raw_data = queue->port->tx_data; + rbbq_data_channel_set(raw_data, channel); + rbbq_data_payload_size_set(raw_data, size); + *data = rbbq_data_payload(raw_data); + + queue->alloc_data = raw_data; + queue->alloc_watermark_idx = queue->alloc_write_idx; + queue->alloc_write_idx = rbbq_data_size(raw_data); + break; + case RBBQ_MESSAGE_ALLOC_AT_MIDDLE: + raw_data = queue->port->tx_data + queue->alloc_write_idx; + rbbq_data_channel_set(raw_data, channel); + rbbq_data_payload_size_set(raw_data, size); + *data = rbbq_data_payload(raw_data); + + queue->alloc_data = raw_data; + queue->alloc_write_idx += rbbq_data_size(raw_data); + break; + case RBBQ_MESSAGE_ALLOC_NOMEM: + ret = -ENOMEM; + (void)pthread_mutex_unlock(&queue->alloc_mutex); + break; + default: + /* should never happend */ + ret = -EFAULT; + (void)pthread_mutex_unlock(&queue->alloc_mutex); + break; + } + return ret; +} + + +int thingset_rbbq_transmit(struct thingset_rbbq *queue, uint8_t *data) +{ + if (ts_rbbq_state(queue) != TS_RBBQ_QUEUE_STATE_RUNNING) { + return -EAGAIN; + } + + if ((data == NULL) || (data != rbbq_data_payload(queue->alloc_data))) { + return -EINVAL; + } + + /* Inform port that a new transmit data is available. */ + int ret = queue->port->port_api->transmit(queue); + + queue->alloc_data = NULL; + + (void)pthread_mutex_unlock(&queue->alloc_mutex); + + return ret; +} + + +/** + * @brief Is there a messsage received that can be freed. + * + * Two possible memory configurations + * - write leads and read follows (write ≥ read), + * the valid data (written, but not yet processed by the reader) + * is in the section of the buffer after read and before write; + * - read leads and write follows (read > write), + * the valid data is after read, till the watermark, and from the + * start of the buffer till write. + */ +static inline int rbbq_data_free_avail_unprotected(const struct thingset_rbbq *queue) +{ + int ret; + uint16_t avail_size; + uint16_t write_idx; + uint16_t watermark_idx; + uint16_t read_idx; + + /* Read from rx control */ + uint16_t other_read_idx; + rbbq_control(queue->port->rx_control, &write_idx, &watermark_idx, &other_read_idx); + + /* We work on this read_idx (not other) */ + read_idx = queue->free_read_idx; + + if (write_idx >= read_idx) { + avail_size = write_idx - read_idx; + ret = RBBQ_MESSAGE_FREE_TO_WRITE; + } else { /* write_idx < read_idx */ + avail_size = watermark_idx - read_idx; + ret = RBBQ_MESSAGE_FREE_TO_WATERMARK; + } + if (avail_size == 0) { + ret = RBBQ_MESSAGE_FREE_NOMEM; + } else { + uint8_t *raw_data = &queue->port->rx_data[read_idx]; + if (rbbq_data_size(raw_data) > avail_size) { + /* corrupted data */ + ret = RBBQ_MESSAGE_CORRUPTED; + } + } + return ret; +} + + +int thingset_rbbq_receive(struct thingset_rbbq *queue, uint16_t *channel, uint16_t *size, + uint8_t **data, thingset_time_ms_t timeout_ms) +{ + if (ts_rbbq_state(queue) != TS_RBBQ_QUEUE_STATE_RUNNING) { + LOG_WRN("%s receive request on queue not running (state: %d)", + thingset_rbbq_name(queue), (int)ts_rbbq_state(queue)); + return -EAGAIN; + } + + int ret; + do { + ret = rbbq_receive_lock(queue, timeout_ms); + if (ret != 0) { + /* cannot lock receive..free operation */ + return ret; + } + int free_mode = rbbq_data_free_avail_unprotected(queue); + if (free_mode == RBBQ_MESSAGE_FREE_NOMEM) { + /* no data available */ + rbbq_receive_unlock(queue); + ret = thingset_rbbq_wait_receive(queue, timeout_ms); + if (ret == 0) { + continue; + } + ret = -ENOMEM; + } else if (free_mode == RBBQ_MESSAGE_CORRUPTED) { + rbbq_receive_unlock(queue); + ret = -EFAULT; + } else { + ret = 0; + } + } while (false); + if (ret == 0) { + uint8_t *raw_data = &queue->port->rx_data[queue->free_read_idx]; + *channel = rbbq_data_channel(raw_data); + *size = rbbq_data_payload_size(raw_data); + *data = rbbq_data_payload(raw_data); + + queue->free_data = raw_data; + queue->free_read_idx += rbbq_data_size(raw_data); + } + + return ret; +} + + +int thingset_rbbq_free(struct thingset_rbbq *queue, uint8_t *data) +{ + if (ts_rbbq_state(queue) == TS_RBBQ_QUEUE_STATE_NONE) { + return -ENODEV; + } + + if (queue->free_data == NULL) { + /* double free */ + return -ENOMEM; + } + if ((data == NULL) || (data != rbbq_data_payload(queue->free_data))) { + return -EINVAL; + } + + /* Inform port that a received data was freed. */ + int ret = queue->port->port_api->receive(queue); + queue->free_data = NULL; + + rbbq_receive_unlock(queue); + + return ret; +} + + +int thingset_rbbq_init(struct thingset_rbbq *queue) +{ + if (!ts_rbbq_state_cas(queue, TS_RBBQ_QUEUE_STATE_NONE, TS_RBBQ_QUEUE_STATE_INIT)) { + return -EEXIST; + } + + int ret; + + ret = pthread_cond_init(&queue->port_receive_cond, NULL); + if (ret != 0) { + goto thingset_rbbq_init_error; + } + ret = pthread_cond_init(&queue->port_transmit_cond, NULL); + if (ret != 0) { + goto thingset_rbbq_init_error; + } + ret = pthread_mutex_init(&queue->alloc_mutex, NULL); + if (ret != 0) { + goto thingset_rbbq_init_error; + } + ret = pthread_mutex_init(&queue->receive_mutex, NULL); + if (ret != 0) { + goto thingset_rbbq_init_error; + } + + queue->alloc_data = NULL; + queue->alloc_write_idx = 0; + queue->alloc_watermark_idx = 0xFFFF; + + queue->free_data = NULL; + queue->free_read_idx = 0; + + /* call the port to finalise initialisation */ + ret = queue->port->port_api->init(queue); + if (ret != 0) { + goto thingset_rbbq_init_error; + } + + /* sync alloc and free markers to size of buffer set by port */ + rbbq_control(queue->port->tx_control, + &queue->alloc_write_idx, + &queue->alloc_watermark_idx, + &queue->free_read_idx); + + ts_rbbq_state_set(queue, TS_RBBQ_QUEUE_STATE_READY); + return 0; + +thingset_rbbq_init_error: + ts_rbbq_state_set(queue, TS_RBBQ_QUEUE_STATE_NONE); + return ret; +} + + +int thingset_rbbq_start(struct thingset_rbbq *queue) +{ + atomic_val_t last_state; + + if (!ts_rbbq_state_cas(queue, TS_RBBQ_QUEUE_STATE_READY, TS_RBBQ_QUEUE_STATE_START)) { + if (!ts_rbbq_state_cas(queue, TS_RBBQ_QUEUE_STATE_SUSPENDED, TS_RBBQ_QUEUE_STATE_START)) { + return -EBUSY; + } else { + last_state = TS_RBBQ_QUEUE_STATE_SUSPENDED; + } + } else { + last_state = TS_RBBQ_QUEUE_STATE_READY; + } + + /* Request port to start data exchange. */ + int ret = queue->port->port_api->start(queue); + if (ret != 0) { + ts_rbbq_state_set(queue, last_state); + } else { + ts_rbbq_state_set(queue, TS_RBBQ_QUEUE_STATE_RUNNING); + } + + return ret; +} + + +int thingset_rbbq_stop(struct thingset_rbbq *queue) +{ + if (!ts_rbbq_state_cas(queue, TS_RBBQ_QUEUE_STATE_RUNNING, TS_RBBQ_QUEUE_STATE_STOP)) { + return -EBUSY; + } + + /* Request port to stop data exchange. */ + int ret = queue->port->port_api->stop(queue); + if (ret != 0) { + ts_rbbq_state_set(queue, TS_RBBQ_QUEUE_STATE_RUNNING); + } else { + ts_rbbq_state_set(queue, TS_RBBQ_QUEUE_STATE_SUSPENDED); + } + + return ret; +} + + +/* +(void) pthread_mutex_lock(&t.mn); + t.waiters++; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += 5; + rc = 0; + while (! mypredicate(&t) && rc == 0) + rc = pthread_cond_timedwait(&t.cond, &t.mn, &ts); + t.waiters--; + if (rc == 0) setmystate(&t); +(void) pthread_mutex_unlock(&t.mn); + */ +int thingset_rbbq_wait_receive(struct thingset_rbbq *queue, thingset_time_ms_t timeout_ms) +{ + int ret; + struct timespec timeout = thingset_time_timeout_spec(timeout_ms); + + (void)pthread_mutex_lock(&queue->port_receive_mutex); + + ret = pthread_cond_timedwait(&queue->port_receive_cond, &queue->port_receive_mutex, &timeout); + + (void)pthread_mutex_unlock(&queue->port_receive_mutex); + + return ret; +} + + +int thingset_rbbq_wait_transmit(struct thingset_rbbq *queue, thingset_time_ms_t timeout_ms) +{ + int ret; + struct timespec timeout = thingset_time_timeout_spec(timeout_ms); + + (void)pthread_mutex_lock(&queue->port_transmit_mutex); + + ret = pthread_cond_timedwait(&queue->port_transmit_cond, &queue->port_transmit_mutex, &timeout); + + (void)pthread_mutex_unlock(&queue->port_transmit_mutex); + + return ret; +} + + +int thingset_rbbq_monitor(struct thingset_rbbq *queue) +{ + return queue->port->port_api->monitor(queue); +} + + +const char *thingset_rbbq_name(struct thingset_rbbq *queue) +{ + return queue->name; +} + + +struct thingset_rbbq *rbbq_get_binding(const char *name) +{ + struct thingset_rbbq *queue = rbbq_queues; + while (queue != NULL) { + if (strcmp(queue->name, name) == 0) { + return queue; + } + queue = queue->next; + } + return NULL; +} + +/* + * ----------------------------------------------- + * INTERNAL interface + * ----------------------------------------------- + */ + +void ts_rbbq_event_raise_receive(struct thingset_rbbq *queue) +{ + /* + * We got a receive buffer update. + * Store read_idx_other from receive buffer to shadow store to make it + * available even if the receive buffer may be locked due to corrupted + * data or receive operation later on. + */ + atomic_store(&queue->tx_shadow_read_idx, rbbq_control_other_read_idx(queue->port->rx_control)); + + pthread_cond_signal(&queue->port_receive_cond); +} + + +void rbbq_event_raise_transmit(struct thingset_rbbq *queue) +{ + pthread_cond_signal(&queue->port_transmit_cond); +} + + +int rbbq_register_binding(struct thingset_rbbq *new_queue) +{ + if ((new_queue->next != NULL) + || (new_queue->name == NULL) + || (new_queue->port == NULL)) { + return -EINVAL; + } + + if (rbbq_queues == NULL) { + /* first queue */ + rbbq_queues = new_queue; + } else { + struct thingset_rbbq *queue = rbbq_queues; + while (queue != NULL) { + if (queue == new_queue) { + /* Already registered */ + return -EALREADY; + } + if (queue->next == NULL) { + queue->next = new_queue; + new_queue->next = NULL; + break; + } + queue = queue->next; + } + } + return 0; +} diff --git a/src/ts_rbbq.h b/src/ts_rbbq.h new file mode 100644 index 0000000..611f0c4 --- /dev/null +++ b/src/ts_rbbq.h @@ -0,0 +1,413 @@ +/* + * Copyright (c) 2020..2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet remote two-way queue (private interface) + */ + +#ifndef TS_RBBQ_H_ +#define TS_RBBQ_H_ + +/** + * @brief ThingSet remote two-way queue. + * + * The two-way queue is based on two lock free ring buffers with contiguous reservations. + * + * Buffer architecture. + * + * Transmit buffer: + * - buffer data + * - raw data blocks + * - header (big endian) + * - payload == data block + * - buffer control (big endian) + * + * Receive buffer: + * - buffer data + * - raw data blocks + * - header (big endian) + * - payload == data block + * - buffer control (big endian) + * + * @defgroup ts_rbbq_api_priv ThingSet remote two-way queue (private interface) + * @{ + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "thingset_rbbq.h" + +#include "ts_endian.h" + +/* Queue states */ +#define TS_RBBQ_QUEUE_STATE_NONE 0 +#define TS_RBBQ_QUEUE_STATE_INIT 1 +#define TS_RBBQ_QUEUE_STATE_READY 2 +#define TS_RBBQ_QUEUE_STATE_START 3 +#define TS_RBBQ_QUEUE_STATE_RUNNING 4 +#define TS_RBBQ_QUEUE_STATE_STOP 5 +#define TS_RBBQ_QUEUE_STATE_SUSPENDED 6 + +/** + * @brief Buffer control + * + * Buffer control is part of the transmit ring buffer and the receive ring buffer. + * It is transmitted to the other side of the communication together with the data. + * + * @note Shall be initialised by the device on init. + */ +struct ts_rbbq_buffer_control { + uint16_t this_write_idx; /**< write index on this ring buffer */ + uint16_t this_watermark_idx; /**< watermark index on this ring buffer */ + uint16_t other_read_idx; /**< read index on other ring buffer */ +}; + +/* + * Port architectur: + * + * Queues work on ports. + */ + +/** + * @brief ThingSet remote two-way queue port API + */ +struct ts_rbbq_port_api { + /** + * @brief queue requests port to initialise. + */ + int (*init)(struct thingset_rbbq *queue); + /** + * @brief queue requests port to start data exchange. + */ + int (*start)(struct thingset_rbbq *queue); + /** + * @brief queue requests port to stop data exchange. + */ + int (*stop)(struct thingset_rbbq *queue); + /** + * @brief queue informs port that a new transmit data block is available. + * + * The port shall take: + * - queue->alloc_watermark_idx + * - queue->alloc_write_idx + * and update at a convenient time (if transmit buffer is not locked by transfer op): + * - queue->port->tx_control->this_watermark_idx + * - queue->port->tx_control->this_write_idx + * + * The call shall be non-blocking. + */ + int (*transmit)(struct thingset_rbbq *queue); + /** + * @brief queue informs port that a received data block was freed. + * + * The port shall take: + * - queue->free_read_idx + * and update at a convenient time (if transmit buffer is not locked by transfer op): + * - queue->port->tx_control->other_read_idx + * + * The call shall be non-blocking. + */ + int (*receive)(struct thingset_rbbq *queue); + /** + * @brief queue requests port to monitor data exchange. + */ + int (*monitor)(struct thingset_rbbq *queue); +}; + +/** + * @brief Port management structure. + */ +struct ts_rbbq_port { + const struct ts_rbbq_port_api *port_api; + struct ts_rbbq_buffer_control *tx_control; + struct ts_rbbq_buffer_control *rx_control; + uint16_t tx_data_size; + uint8_t *tx_data; + uint16_t rx_data_size; + uint8_t *rx_data; +}; + +/** + * @brief Remote two-way queue management structure. + * + * @note Shall be initialised by the queue on init. + */ +struct thingset_rbbq { + struct thingset_rbbq *next; /**< Manage all queues */ + const char *name; /**< Name of the queue */ + + /* Port signal on receive */ + pthread_cond_t port_receive_cond; /**< Condition var for port to signal new receive */ + pthread_mutex_t port_receive_mutex; /**< Mutex to lock access to receive condition var */ + /* Port signal on transmit */ + pthread_cond_t port_transmit_cond; /**< Condition var for port to signal new transmit */ + pthread_mutex_t port_transmit_mutex; /**< Mutex to lock access to transmit condition var */ + + const struct ts_rbbq_port *port; + + pthread_mutex_t alloc_mutex; /**< Mutex to lock allocation operation up to transmit */ + uint8_t *alloc_data; /**< Data block currently allocated */ + uint16_t alloc_write_idx; /**< write index of allocated data on tx buffer */ + uint16_t alloc_watermark_idx; /**< watermark index of allocated data on tx buffer */ + atomic_uint_fast16_t tx_shadow_read_idx; /**< Shadow read idx on tx buffer */ + + pthread_mutex_t receive_mutex; /**< Mutex to lock receive operation from receive to free */ + uint8_t *free_data; /**< Data block currently allocated */ + uint16_t free_read_idx; /**< read index of freed data on rx buffer */ + + atomic_uint_fast8_t state; /**< Queue state. */ +}; + + +/* + * Data block architectur: + * + * Header: + * - channel: 2 bytes in big endian byte order + * - payload size: 2 bytes in big endian byte order + * Payload: + * - data: N bytes + */ + +/** + * @brief Data block header + */ +struct ts_rbbq_data_header { + uint16_t channel; + uint16_t size; +}; + +/** + * @brief Get the state of the queue. + * + * Atomic get of the queue state. + */ +static inline uint8_t ts_rbbq_state(struct thingset_rbbq *queue) +{ + return atomic_load(&queue->state); +} + + +/** + * @brief Set the state of the queue. + * + * Atomic set of the queue state. + */ +static inline void ts_rbbq_state_set(struct thingset_rbbq *queue, uint8_t state) +{ + atomic_store(&queue->state, state); +} + +/** + * @brief Compare and set the state of the queue. + * + * This routine performs an atomic compare-and-set on the queue state. + * + * If the current value of the queue state equals old_state, the queue state is set to new_state. + * If the current value of the queue state does not equal old_state, the queue state + * is left unchanged. + * + * @param queue + * @param old_state Original state to compare against. + * @param new_state New state to store. + * @return True if queue state is set to new_state, false otherwise. + */ +static inline bool ts_rbbq_state_cas(struct thingset_rbbq *queue, uint8_t old_state, uint8_t new_state) +{ + return atomic_compare_exchange_strong(&queue->state, &old_state, new_state); +} + +/** + * @brief Get the channel of the raw data block. + */ +static inline uint16_t rbbq_data_channel(uint8_t *raw_data) +{ + struct ts_rbbq_data_header *header = (struct ts_rbbq_data_header *)raw_data; + + return ts_endian_get_be16((const uint8_t *)&header->channel); +} + + +/** + * @brief Set the channel of the raw data block. + */ +static inline void rbbq_data_channel_set(uint8_t *raw_data, uint16_t channel) +{ + struct ts_rbbq_data_header *header = (struct ts_rbbq_data_header *)raw_data; + + sys_put_be16(channel, (uint8_t *)&header->channel); +} + + +/** + * @brief Get the payload size of the raw data block. + */ +static inline uint16_t rbbq_data_payload_size(uint8_t *raw_data) +{ + struct ts_rbbq_data_header *header = (struct ts_rbbq_data_header *)raw_data; + + return ts_endian_get_be16((const uint8_t *)&header->size); +} + + +/** + * @brief Set the payload size of the raw data block. + */ +static inline void rbbq_data_payload_size_set(uint8_t *raw_data, uint16_t size) +{ + struct ts_rbbq_data_header *header = (struct ts_rbbq_data_header *)raw_data; + + sys_put_be16(size, (uint8_t *)&header->size); +} + + +/** + * @brief Get the payload of the raw data block. + */ +static inline uint8_t *rbbq_data_payload(uint8_t *raw_data) +{ + return raw_data + sizeof(struct ts_rbbq_data_header); +} + + +/** + * @brief Get the size of the raw data block. + */ +static inline uint16_t rbbq_data_size(uint8_t *raw_data) +{ + struct ts_rbbq_data_header *header = (struct ts_rbbq_data_header *)raw_data; + + return ts_endian_get_be16((const uint8_t *)&header->size) + sizeof(struct ts_rbbq_data_header); +} + + +/** + * @brief Calculate the size of the raw data block. + * + * @param payload_size The size of the payload == user data block. + */ +static inline uint16_t rbbq_data_size_calc(uint16_t payload_size) +{ + return payload_size + sizeof(struct ts_rbbq_data_header); +} + +/** + * @brief Get rbbq control. + * + * @param[in] control Pointer to RBBQ control struct + * @param[out] this_write_idx Write index of this buffer + * @param[out] this_watermark_idx Watermark index of this buffer + * @param[out] other_read_idx Read index of the other buffer + */ +static inline void rbbq_control(const struct ts_rbbq_buffer_control *control, + uint16_t *this_write_idx, + uint16_t *this_watermark_idx, + uint16_t *other_read_idx) +{ + *this_write_idx = ts_endian_get_be16((const uint8_t *)&control->this_write_idx); + *this_watermark_idx = ts_endian_get_be16((const uint8_t *)&control->this_watermark_idx); + *other_read_idx = ts_endian_get_be16((const uint8_t *)&control->other_read_idx); +} + +/** + * @brief Get other read index of rbbq control. + * + * @param[in] control Pointer to RBBQ control struct + * @return Read index of the other buffer + */ +static inline uint16_t rbbq_control_other_read_idx(const struct ts_rbbq_buffer_control *control) +{ + return ts_endian_get_be16((const uint8_t *)&control->other_read_idx); +} + + +/** + * @brief Set rbbq control. + * + * @param control Pointer to RBBQ control struct + * @param this_write_idx Write index of this buffer + * @param this_watermark_idx Watermark index of this buffer + * @param other_read_idx Read index of the other buffer + */ +static inline void rbbq_control_set(struct ts_rbbq_buffer_control *control, + uint16_t this_write_idx, + uint16_t this_watermark_idx, + uint16_t other_read_idx) +{ + sys_put_be16(this_write_idx, (uint8_t *)&control->this_write_idx); + sys_put_be16(this_watermark_idx, (uint8_t *)&control->this_watermark_idx); + sys_put_be16(other_read_idx, (uint8_t *)&control->other_read_idx); +} + +/** + * @brief Lock receive buffer. + * + * Used internally but also to be called by a rbbq port + * if the receive buffer can temporarily not be accessed + * (e.g. due to a non lock free update). + */ +static inline int rbbq_receive_lock(struct thingset_rbbq *queue, thingset_time_ms_t timeout_ms) +{ + struct timespec timeout = thingset_time_timeout_spec(timeout_ms); + + return pthread_mutex_timedlock(&queue->receive_mutex, &timeout); +} + + +/** + * @brief Unlock receive buffer. + */ +static inline void rbbq_receive_unlock(struct thingset_rbbq *queue) +{ + (void)pthread_mutex_unlock(&queue->receive_mutex); +} + + +/** + * @brief Callback on receive. + * + * To be called by rbbq port after new data was received. + * + * @param[in] queue Pointer to the remote two-way queue + */ +void ts_rbbq_event_raise_receive(struct thingset_rbbq *queue); + + +/** + * @brief Callback on transmit. + * + * To be called by rbbq port after data was transmitted. + * + * @param[in] queue Pointer to the queue + */ +void rbbq_event_raise_transmit(struct thingset_rbbq *queue); + + +/** + * @brief Register the queue to make it available to rbbq_get_binding(). + * + * The queue struct must at least be partly filled: + * - .next = NULL + * - .name = "A queue name" + * - .port = pointer to port struct + * + * @param[in] queue Pointer to the queue + * @return 0 on success, <0 otherwise + */ +int rbbq_register_binding(struct thingset_rbbq *queue); + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +/** + * @} + */ + +#endif /* TS_RBBQ_H_ */ From 9da8ae02284f56afaad78ae90f11a4735daa09ef Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Sun, 19 Dec 2021 08:07:13 +0100 Subject: [PATCH 18/37] COM prepare - add ThingSet message interface and implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prepare for ThingSet Communication Context: - Add ThingSet interface for messages. - Add Thingset message implementation based on communication buffers (ts_buf.h) The ThingSet message implementation encapsulates all message specific functionality: - message allocation from the communication buffer pool (ts_buf.h) - message encoding/ decoding for text and binary messages including functions to add and pull value primitives (int, string, array, map, ...). CBOR encoding and decoding uses the TinyCBOR lib. JSON decoding uses ThingSet´s own JSMN decoder with reduced memory consumption. JSON encoding uses a simple JSON encoder that is part of the message implementation. - message status - message import and export - message log - message ThingSet protocol support to add and pull ThingSet request/ response/ statement messages in text and binary format The message communication buffers are organized in a special way to support above functionality without additional memory requirements: - scratchroom - Room always occupied by the scratchpad - headroom - data already processed - data - the payload - tailroom - space to fill additional payload - scratchroom - additional room occupied by the scratchpad for specific tasks The scratchpad varies depending on the actual use for e.g. decoding, encoding ... Signed-off-by: Bobby Noelte --- src/thingset_msg.h | 175 ++++ src/ts_msg.c | 167 ++++ src/ts_msg.h | 1740 ++++++++++++++++++++++++++++++++ src/ts_msg_coder.c | 463 +++++++++ src/ts_msg_export.c | 165 ++++ src/ts_msg_log.c | 231 +++++ src/ts_msg_proto.c | 2310 +++++++++++++++++++++++++++++++++++++++++++ src/ts_msg_value.c | 1318 ++++++++++++++++++++++++ 8 files changed, 6569 insertions(+) create mode 100644 src/thingset_msg.h create mode 100644 src/ts_msg.c create mode 100644 src/ts_msg.h create mode 100644 src/ts_msg_coder.c create mode 100644 src/ts_msg_export.c create mode 100644 src/ts_msg_log.c create mode 100644 src/ts_msg_proto.c create mode 100644 src/ts_msg_value.c diff --git a/src/thingset_msg.h b/src/thingset_msg.h new file mode 100644 index 0000000..59bb686 --- /dev/null +++ b/src/thingset_msg.h @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2017 Martin Jäger / Libre Solar + * Copyright (c) 2021..2022 Bobby Noelte + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet message (public interface) + */ + +#ifndef THINGSET_MSG_H_ +#define THINGSET_MSG_H_ + +/** + * @brief ThingSet message (buffers). + * + * @note All structure definitions and functions that start with the prefix 'ts_' are not part of + * the public API and are just here for technical reasons. They should not be used in + * applications. + * + * @defgroup ts_msg_api_pub ThingSet message (public interface) + * @{ + */ + +/* + * Protocol function codes (same as CoAP) + */ +#define TS_GET 0x01 +#define TS_POST 0x02 +#define TS_DELETE 0x04 +#define TS_FETCH 0x05 +#define TS_PATCH 0x07 // it's actually iPATCH + +#define TS_PUBMSG 0x1F + +/* + * Status codes (same as CoAP) + */ + +// success +#define TS_STATUS_CREATED 0x81 +#define TS_STATUS_DELETED 0x82 +#define TS_STATUS_VALID 0x83 +#define TS_STATUS_CHANGED 0x84 +#define TS_STATUS_CONTENT 0x85 + +// client errors +#define TS_STATUS_BAD_REQUEST 0xA0 +#define TS_STATUS_UNAUTHORIZED 0xA1 // need to authenticate +#define TS_STATUS_FORBIDDEN 0xA3 // trying to write read-only value +#define TS_STATUS_NOT_FOUND 0xA4 +#define TS_STATUS_METHOD_NOT_ALLOWED 0xA5 +#define TS_STATUS_REQUEST_INCOMPLETE 0xA8 +#define TS_STATUS_CONFLICT 0xA9 +#define TS_STATUS_REQUEST_TOO_LARGE 0xAD +#define TS_STATUS_UNSUPPORTED_FORMAT 0xAF + +// server errors +#define TS_STATUS_INTERNAL_SERVER_ERR 0xC0 +#define TS_STATUS_NOT_IMPLEMENTED 0xC1 + +// ThingSet specific errors +#define TS_STATUS_RESPONSE_TOO_LARGE 0xE1 + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief ThingSet message (buffer). + * + * A ThingSet message buffer is build on top of generic buffers. + * + * A thingset message buffer can not be created directly. It always has to be allocated. + */ +struct thingset_msg {}; + + +/** + * @brief Allocate a ThingSet message buffer from the buffer pool. + * + * The message buffer is allocated with reference count set to 1. + * + * Allocation accounts for scratchpads minimum size. + * + * @param[in] size The size of the message buffer + * @param[in] timeout_ms maximum time to wait in milliseconds. + * @param[out] buffer Pointer to message buffer + * @return 0 on success, <0 otherwise + */ +int thingset_msg_alloc(uint16_t msg_size, thingset_time_ms_t timeout_ms, struct thingset_msg **msg); + +/** + * @brief Allocate a ThingSet message buffer for binary messages from the buffer pool. + * + * The message buffer is allocated with reference count set to 1. + * + * Allocation accounts for CBOR scratchpads minimum size. + * + * @param[in] msg_size Maximum size of the binary message, + * @param[in] timeout_ms maximum time to wait in milliseconds. + * @param[out] buffer Pointer to message buffer + * @return 0 on success, <0 otherwise + */ +int thingset_msg_alloc_cbor(uint16_t msg_size, thingset_time_ms_t timeout_ms, struct thingset_msg **msg); + +/** + * @brief Allocate a ThingSet message buffer for text messages from the buffer pool. + * + * The message buffer is allocated with reference count set to 1. + * + * Allocation accounts for JSON scratchpads minimum size. + * + * @param[in] msg_size Maximum size of the text message, + * @param[in] timeout_ms maximum time to wait in milliseconds. + * @param[out] buffer Pointer to message buffer + * @return 0 on success, <0 otherwise + */ +int thingset_msg_alloc_json(uint16_t msg_size, thingset_time_ms_t timeout_ms, struct thingset_msg **msg); + +/** + * @brief Clone message. + * + * Duplicate given message communication buffer including any data currently stored. + * + * @param[in] msg Pointer to the source message communication buffer. + * @param[in] timeout_ms maximum time to wait in milliseconds. + * @param[out] clone Pointer to the cloned buffer. + * @return The original tail of the destinatio buffer, before incremented by new data. + */ +int thingset_msg_clone(struct thingset_msg *msg, thingset_time_ms_t timeout_ms, struct thingset_msg **clone); + + +/** + * @brief Reset message buffer. + * + * Reset buffer data so the message buffer can be reused for other purposes. + * + * @param[in] msg Pointer to the message. + */ +void thingset_msg_reset(struct thingset_msg *msg); + +/** + * @brief Mark message used. + * + * Increment the reference count of a message. + * + * @param[in] msg Pointer to the message. + * @return 0 on success, <0 otherwise + */ +int thingset_msg_ref(struct thingset_msg *msg); + +/** + * @brief Mark message unused. + * + * Decrement the reference count of a message. The message is put back into the + * message buffer pool if the reference count reaches zero. + * + * @note The message shall not be accessed after it is marked unused. + * + * @param[in] msg Pointer to the message. + * @return 0 on success, <0 otherwise + */ +int thingset_msg_unref(struct thingset_msg *msg); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +/** + * @} + */ + +#endif /* THINGSET_MSG_H_ */ diff --git a/src/ts_msg.c b/src/ts_msg.c new file mode 100644 index 0000000..ec46cda --- /dev/null +++ b/src/ts_msg.c @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * ThingSet message core support + * ----------------------------- + */ + +#include "thingset_env.h" + +#include +#include +#include +#include + +#include "ts_buf.h" +#include "ts_msg.h" +#include "ts_obj.h" + + +#define MAX_SIZE(a, b) (((a) > (b)) ? (a) : (b)) + +/** + * @brief Maximum buffer size needed for message scratchpads. + */ +const uint16_t ts_msg_scratchpad_size_max = sizeof(struct ts_msg_buf_scratchroom) + + MAX_SIZE(sizeof(struct ts_msg_proc_scratchroom), + MAX_SIZE(sizeof(struct ts_msg_json_enc_scratchroom) + 1, + MAX_SIZE(sizeof(struct ts_msg_json_dec_scratchroom) + 16 * sizeof(struct ts_jsmn_token), + MAX_SIZE(sizeof(struct ts_msg_cbor_enc_scratchroom), + sizeof(struct ts_msg_cbor_dec_scratchroom))))); + +/** + * @brief Maximum buffer size needed for raw message scratchpads. + */ +const uint16_t ts_msg_scratchpad_size_max_raw = sizeof(struct ts_msg_buf_scratchroom) + + sizeof(struct ts_msg_proc_scratchroom); + +/** + * @brief Maximum buffer size needed for binary message scratchpads. + */ +const uint16_t ts_msg_scratchpad_size_max_cbor = sizeof(struct ts_msg_buf_scratchroom) + + MAX_SIZE(sizeof(struct ts_msg_cbor_enc_scratchroom), + sizeof(struct ts_msg_cbor_dec_scratchroom)); + +/** + * @brief Maximum buffer size needed for text message scratchpads. + */ +const uint16_t ts_msg_scratchpad_size_max_json = sizeof(struct ts_msg_buf_scratchroom) + + MAX_SIZE(sizeof(struct ts_msg_json_enc_scratchroom) + 1, + sizeof(struct ts_msg_json_dec_scratchroom) + 16 * sizeof(struct ts_jsmn_token)); + +int ts_msg_alloc_raw(uint16_t msg_size, thingset_time_ms_t timeout_ms, struct thingset_msg **msg) +{ + /* Allocate buffer and account for message size + scratchpad size + alignment */ + int ret = ts_buf_alloc(msg_size + ts_msg_scratchpad_size_max_raw + 3, timeout_ms, + (struct ts_buf **)msg); + if (ret != 0) { + TS_LOGE("MSG: %s faulting - can not alloc message (requested: %u, total %u)", + __func__, (unsigned int)msg_size, + (unsigned int)(msg_size + sizeof(struct ts_msg_buf_scratchroom) + 3)); + return ret; + } + TS_LOGD("MSG: %s alloc message 0x%" PRIXPTR " (requested: %u, total %u)", __func__, + (uintptr_t)*msg, (unsigned int)msg_size, + (unsigned int)(msg_size + sizeof(struct ts_msg_buf_scratchroom) + 3)); + thingset_msg_reset(*msg); + return 0; +} + +int thingset_msg_alloc(uint16_t msg_size, thingset_time_ms_t timeout_ms, struct thingset_msg **msg) +{ + /* Allocate buffer and account for message size + scratchpad size + alignment */ + int ret = ts_buf_alloc(msg_size + ts_msg_scratchpad_size_max + 3, timeout_ms, + (struct ts_buf **)msg); + if (ret != 0) { + TS_LOGE("MSG: %s faulting - can not alloc message (requested: %u, total %u)", + __func__, (unsigned int)msg_size, + (unsigned int)(msg_size + ts_msg_scratchpad_size_max + 3)); + return ret; + } + TS_LOGD("MSG: %s alloc message 0x%" PRIXPTR " (requested: %u, total %u)", __func__, + (uintptr_t)*msg, (unsigned int)msg_size, + (unsigned int)(msg_size + ts_msg_scratchpad_size_max + 3)); + thingset_msg_reset(*msg); + return 0; +} + +int thingset_msg_alloc_cbor(uint16_t msg_size, thingset_time_ms_t timeout_ms, struct thingset_msg **msg) +{ + /* Allocate buffer and account for message size + scratchpad size + alignment */ + int ret = ts_buf_alloc(msg_size + ts_msg_scratchpad_size_max_cbor + 3, timeout_ms, + (struct ts_buf **)msg); + if (ret != 0) { + TS_LOGE("MSG: %s faulting - can not alloc message (requested: %u, total %u)", + __func__, (unsigned int)msg_size, + (unsigned int)(msg_size + ts_msg_scratchpad_size_max_cbor + 3)); + return ret; + } + TS_LOGD("MSG: %s alloc message 0x%" PRIXPTR " (requested: %u, total %u)", __func__, + (uintptr_t)*msg, (unsigned int)msg_size, + (unsigned int)(msg_size + ts_msg_scratchpad_size_max_cbor + 3)); + thingset_msg_reset(*msg); + return 0; +} + +int thingset_msg_alloc_json(uint16_t msg_size, thingset_time_ms_t timeout_ms, struct thingset_msg **msg) +{ + /* Allocate buffer and account for message size + scratchpad size + alignment */ + int ret = ts_buf_alloc(msg_size + ts_msg_scratchpad_size_max_json + 3, timeout_ms, + (struct ts_buf **)msg); + if (ret != 0) { + TS_LOGE("MSG: %s faulting - can not alloc message (requested: %u, total %u)", + __func__, (unsigned int)msg_size, + (unsigned int)(msg_size + ts_msg_scratchpad_size_max_json + 3)); + return ret; + } + TS_LOGD("MSG: %s alloc message 0x%" PRIXPTR " (requested: %u, total %u)", __func__, + (uintptr_t)*msg, (unsigned int)msg_size, + (unsigned int)(msg_size + ts_msg_scratchpad_size_max_json + 3)); + thingset_msg_reset(*msg); + return 0; +} + +int thingset_msg_clone(struct thingset_msg *msg, thingset_time_ms_t timeout_ms, struct thingset_msg **clone) +{ + TS_LOGD("MSG: %s clone message 0x%" PRIXPTR " to 0x%" PRIXPTR, __func__, + (uintptr_t)msg, (uintptr_t)*clone); + return ts_buf_clone((struct ts_buf *)msg, timeout_ms, (struct ts_buf **)clone); +} + +void thingset_msg_reset(struct thingset_msg *msg) +{ + struct ts_buf *buf = (struct ts_buf *)msg; + ts_buf_reset(buf); + + /* Initialize message buffer scratchpad */ + struct ts_msg_buf_scratchroom *scratchpad = (struct ts_msg_buf_scratchroom *)ts_buf_data(buf); + scratchpad->status.valid = TS_MSG_VALID_UNSET; + scratchpad->status.type = 0; + scratchpad->status.proto = 0; + scratchpad->status.encoding = TS_MSG_ENCODING_NONE; + scratchpad->status.code = 0; + scratchpad->scratchtype.thingset = TS_MSG_THINGSET; + scratchpad->scratchtype.id = TS_MSG_SCRATCHPAD_STD; + scratchpad->scratchroom.size = 0; + scratchpad->auth = 0; + + ts_buf_reserve(buf, sizeof(struct ts_msg_buf_scratchroom)); + + TS_MSG_ASSERT(msg); + TS_LOGD("MSG: %s reset message 0x%" PRIXPTR, __func__, (uintptr_t)msg); +} + +int thingset_msg_ref(struct thingset_msg *msg) +{ + TS_LOGD("MSG: %s ref message 0x%" PRIXPTR, __func__, (uintptr_t)msg); + return ts_buf_ref((struct ts_buf *)msg); +} + +int thingset_msg_unref(struct thingset_msg *msg) +{ + TS_LOGD("MSG: %s unref message 0x%" PRIXPTR, __func__, (uintptr_t)msg); + return ts_buf_unref((struct ts_buf *)msg); +} diff --git a/src/ts_msg.h b/src/ts_msg.h new file mode 100644 index 0000000..c35f2cc --- /dev/null +++ b/src/ts_msg.h @@ -0,0 +1,1740 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet message (private interface) + */ + +#ifndef TS_MSG_H_ +#define TS_MSG_H_ + +/** + * @brief Abstraction over ThingSet messages. + * + * Messages are organized in a special way in message buffers: + * - scratchroom - Room always occupied by the scratchpad + * - headroom - data already processed + * - data - the payload + * - tailroom - space to fill additional payload + * - scratchroom - additional room occupied by the scratchpad for specific tasks + * + * @defgroup ts_msg_api_priv ThingSet message (private interface) + * @{ + */ + +#include "thingset_env.h" +#include "thingset_msg.h" + +#include "ts_macro.h" +#include "ts_log.h" +#include "ts_cobs.h" +#include "ts_jsmn.h" +#include "ts_buf.h" +#include "ts_obj.h" +#include "ts_port.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Message status + * + * @defgroup ts_msg_api_status_priv ThingSet message status (private interface) + * + * @{ + */ + +#define TS_MSG_STATUS_TYPE(code) (code >> 4) +#define TS_MSG_STATUS_ID(code) (code & 0x0F) + +/** + * @brief Message validity. + */ +enum ts_msg_valid { + TS_MSG_VALID_UNSET = 0, + TS_MSG_VALID_OK = 1, + TS_MSG_VALID_ERROR = 2, +}; + +/** + * @brief Message protocol. + */ +enum ts_msg_proto { + TS_MSG_PROTO_BIN = 1, + TS_MSG_PROTO_TXT = 2 +}; + + +/** + * @brief Message type. + */ +enum ts_msg_type { + TS_MSG_TYPE_REQUEST = 0, + TS_MSG_TYPE_RESPONSE = 1, + TS_MSG_TYPE_STATEMENT = 2 +}; + +/** + * @brief Message encoding. + */ +enum ts_msg_encoding { + TS_MSG_ENCODING_NONE = 0, + TS_MSG_ENCODING_COBS = 1, + TS_MSG_ENCODING_CAN = 2 +}; + + +/** + * @brief Message codes (status codes same as CoAP). + */ +enum ts_msg_code { +// request message code - TS_MSG_VALID_OK + TS_MSG_CODE_BIN_GET = 0x01, + TS_MSG_CODE_BIN_POST = 0x02, + TS_MSG_CODE_BIN_DELETE = 0x04, + TS_MSG_CODE_BIN_FETCH = 0x05, + TS_MSG_CODE_BIN_PATCH = 0x07, + TS_MSG_CODE_BIN_STATEMENT = 0x1F, + TS_MSG_CODE_TXT_GET = '?', + TS_MSG_CODE_TXT_PATCH = '=', + TS_MSG_CODE_TXT_CREATE = '+', + TS_MSG_CODE_TXT_DELETE = '-', + TS_MSG_CODE_TXT_EXEC = '!', + TS_MSG_CODE_TXT_STATEMENT = '#', + +// response message code - general - TS_MSG_VALID_OK + TS_MSG_CODE_TXT_RESPONSE = ':', + +// response message code - success - TS_MSG_VALID_OK + TS_MSG_CODE_CREATED = 0x81, + TS_MSG_CODE_DELETED = 0x82, + TS_MSG_CODE_VALID = 0x83, + TS_MSG_CODE_CHANGED = 0x84, + TS_MSG_CODE_CONTENT = 0x85, + TS_MSG_CODE_EXPORT = 0x86, + +// request status code/ response message code - client errors - TS_MSG_VALID_ERROR/ TS_MSG_VALID_OK + TS_MSG_CODE_BAD_REQUEST = 0xA0, + TS_MSG_CODE_UNAUTHORIZED = 0xA1, // need to authenticate + TS_MSG_CODE_FORBIDDEN = 0xA3, // trying to write read-only value + TS_MSG_CODE_NOT_FOUND = 0xA4, + TS_MSG_CODE_METHOD_NOT_ALLOWED = 0xA5, + TS_MSG_CODE_REQUEST_INCOMPLETE = 0xA8, + TS_MSG_CODE_CONFLICT = 0xA9, + TS_MSG_CODE_REQUEST_TOO_LARGE = 0xAD, + TS_MSG_CODE_UNSUPPORTED_FORMAT = 0xAF, +// request status code/ response message code - server errors - TS_MSG_VALID_ERROR/ TS_MSG_VALID_OK + TS_MSG_CODE_INTERNAL_SERVER_ERR = 0xC0, + TS_MSG_CODE_NOT_IMPLEMENTED = 0xC1, +// request status code/ response message code - specific errors - TS_MSG_VALID_ERROR/ TS_MSG_VALID_OK + TS_MSG_CODE_RESPONSE_TOO_LARGE = 0xE1, + +// request status code - TS_MSG_VALID_OK + TS_MSG_CODE_REQUEST_CREATE = 0x01U, + TS_MSG_CODE_REQUEST_DELETE = 0x02U, + TS_MSG_CODE_REQUEST_EXEC = 0x03U, + TS_MSG_CODE_REQUEST_FETCH_IDS = 0x04U, + TS_MSG_CODE_REQUEST_FETCH_NAMES = 0x05U, + TS_MSG_CODE_REQUEST_FETCH_SINGLE = 0x06U, + TS_MSG_CODE_REQUEST_FETCH_VALUES = 0x07U, + TS_MSG_CODE_REQUEST_GET_IDS = 0x08U, + TS_MSG_CODE_REQUEST_GET_IDS_VALUES = 0x09U, + TS_MSG_CODE_REQUEST_GET_NAMES = 0x0AU, + TS_MSG_CODE_REQUEST_GET_NAMES_VALUES = 0x0BU, + TS_MSG_CODE_REQUEST_GET_VALUES = 0x0CU, + TS_MSG_CODE_REQUEST_PATCH = 0x0DU +}; + +/** + * @brief ThingSet message status code type. + */ +typedef uint8_t ts_msg_status_code_t; + +/** + * @brief Internal message status. + * + * A message status describes the message content: + * - validity + * - protocol + * - type + * - encoding + * + * and for each type of message the specific characteristic of the message given by the message + * code. + */ +struct ts_msg_stat { + /** + * @brief Message validity. + */ + uint8_t valid : 2; + + /** + * @brief Message protocol. + */ + uint8_t proto : 2; + + /** + * @brief Message type. + */ + uint8_t type : 2; + + /** + * @brief Message encoding. + */ + uint8_t encoding : 2; + + /** + * @brief Message status code. + */ + ts_msg_status_code_t code; +}; + +/** + * @} ts_msg_api_status_priv + */ + +/** + * @brief ThingSet message buffer support + * + * @defgroup ts_msg_api_buf_priv ThingSet message buffer (private interface) + * @{ + */ + +#define TS_MSG_THINGSET 0x7U + +/** @brief Standard scratchpad that is always available to a message buffer */ +#define TS_MSG_SCRATCHPAD_STD 0x0U + +/** + * @brief Data structure for standard scratchroom at start of message buffer. + */ +struct ts_msg_buf_scratchroom { + /** @brief Message scratchpad type */ + struct { + uint8_t thingset : 4; + uint8_t id : 4; + } scratchtype; + + /** @brief Message status. */ + struct ts_msg_stat status; + + /** @brief Authorisation rights at the time the message was received/ created */ + uint16_t auth; + + /** + * @brief Message extra scratchroom definition. + * + * @note Must be last element in standard scratchrom as buf[2] may be used as + * extension to message. + */ + union { + /** @brief Size of extra scratchroom allocated at buffer tailroom. */ + uint16_t size; + /** @brief Minimal extra scratchroom buffer in case no tailroom is allocated. */ + uint8_t buf[2]; + } scratchroom; +}; + +/** + * @def TS_MSG_ASSERT + * + * @brief Assert message in message buffer is a ThingSet message. + * + * @note Macro asserts on wrong message type if TS_CONFIG_ASSERT is enabled. + * + * @param[in] msg Pointer to the message. + */ +#if !TS_CONFIG_ASSERT +#define TS_MSG_ASSERT(msg) +#else +#define TS_MSG_ASSERT(msg) \ + do { \ + struct ts_msg_buf_scratchroom *ts_assert_scratchpad = \ + (struct ts_msg_buf_scratchroom *)(ts_buf_data((struct ts_buf *)msg) \ + - ts_buf_headroom((struct ts_buf *)msg)); \ + TS_ASSERT((ts_assert_scratchpad != NULL), \ + "Invalid ThingSet msg scratchpad (NULL)."); \ + TS_ASSERT((ts_assert_scratchpad->scratchtype.thingset == TS_MSG_THINGSET), \ + "Invalid ThingSet msg type (0x%x).", \ + (unsigned int)(ts_assert_scratchpad->scratchtype.thingset)); \ + } while(0) +#endif + +/** + * @def TS_MSG_ASSERT_SCRATCHTYPE + * + * @brief Assert scratchpad of message in message buffer is of given scratchpad type. + * + * @note Macro asserts on wrong message type or scratchpad type if TS_CONFIG_ASSERT is enabled. + * + * @param[in] msg Pointer to the message. + * @param[in] type Scratchpad type. + */ +#if !TS_CONFIG_ASSERT +#define TS_MSG_ASSERT_SCRATCHTYPE(msg, type) +#else +#define TS_MSG_ASSERT_SCRATCHTYPE(msg, type) \ + do { \ + struct ts_msg_buf_scratchroom *ts_assert_scratchpad = \ + (struct ts_msg_buf_scratchroom *)(ts_buf_data((struct ts_buf *)msg) \ + - ts_buf_headroom((struct ts_buf *)msg)); \ + TS_ASSERT((ts_assert_scratchpad != NULL), \ + "Invalid ThingSet msg scratchpad (NULL)."); \ + TS_ASSERT((ts_assert_scratchpad->scratchtype.thingset == TS_MSG_THINGSET), \ + "Invalid ThingSet msg type (0x%x).", \ + (unsigned int)(ts_assert_scratchpad->scratchtype.thingset)); \ + TS_ASSERT((ts_assert_scratchpad->scratchtype.id == type), \ + "Unexpected ThingSet msg scratchpad type (0x%x) - expected 0x%x.", \ + (unsigned int)(ts_assert_scratchpad->scratchtype.id), (unsigned int)type); \ + } while(0) +#endif + +/** + * @def TS_MSG_BUF_SCRATCHPAD_PTR_INIT + * + * @brief Define and initialize pointer to standard scratchpad of start of message. + * + * @note Macro asserts on wrong message type if TS_CONFIG_ASSERT is enabled. + * + * @param[in] name Object name of pointer to standard scratchpad. + * @param[in] msg Pointer to the message. + */ +#define TS_MSG_BUF_SCRATCHPAD_PTR_INIT(name, msg) \ + TS_MSG_ASSERT(msg); \ + struct ts_msg_buf_scratchroom *name = \ + (struct ts_msg_buf_scratchroom *)(ts_buf_data((struct ts_buf *)msg) \ + - ts_buf_headroom((struct ts_buf*)msg)) + +/** + * @brief Allocate a ThingSet message buffer from the buffer pool. + * + * The message buffer is allocated with reference count set to 1. + * + * Allocation accounts for process scratchpad size only. + * + * @param[in] size The size of the message buffer + * @param[in] timeout_ms maximum time to wait in milliseconds. + * @param[out] buffer Pointer to message buffer + * @return 0 on success, <0 otherwise + */ +int ts_msg_alloc_raw(uint16_t msg_size, thingset_time_ms_t timeout_ms, struct thingset_msg **msg); + +/** + * @brief Current data pointer of the message. + * + * Get a pointer to the current start of the message in the message buffer. + * + * @param[in] msg Pointer to the message. + * @return Current start of the message in the message buffer. + */ +static inline uint8_t *ts_msg_data(struct thingset_msg *msg) +{ + TS_MSG_ASSERT(msg); + + return ts_buf_data((struct ts_buf *)msg); +} + +/** + * @brief Current tail pointer of the message. + * + * Get a pointer to the current end of the message in the message buffer. + * + * @param[in] msg Pointer to the message. + * @return Current tail pointer of the message. + */ +static inline uint8_t *ts_msg_tail(struct thingset_msg *msg) +{ + TS_MSG_ASSERT(msg); + + return ts_buf_tail((struct ts_buf *)msg); +} + +/** + * @brief Current length of message in message buffer. + * + * @param[in] msg Pointer to the message. + * @return Current length of message. + */ +static inline uint16_t ts_msg_len(struct thingset_msg *msg) +{ + TS_MSG_ASSERT(msg); + + return ts_buf_len((struct ts_buf *)msg); +} + +/** + * @brief Size of message buffer headroom. + * + * Free space at the beginning of the message buffer. + * + * @param[in] msg Pointer to the message. + * @return Number of bytes available at the beginning of the message buffer. + */ +static inline uint16_t ts_msg_headroom(struct thingset_msg *msg) +{ + TS_MSG_ASSERT(msg); + + return ts_buf_headroom((struct ts_buf *)msg) - sizeof(struct ts_msg_buf_scratchroom); +} + +/** + * @brief Size of message buffer tailroom. + * + * Free space at the end of the message buffer. + * + * @param[in] msg Pointer to the message. + * @return Number of bytes available at the end of the message buffer. + */ +static inline uint16_t ts_msg_tailroom(struct thingset_msg *msg) +{ + TS_MSG_BUF_SCRATCHPAD_PTR_INIT(scratchpad, msg); + return ts_buf_tailroom((struct ts_buf *)msg) - scratchpad->scratchroom.size; +} + +/** + * @brief Size of message buffer scratchroom. + * + * Scratchpad space. + * + * @param[in] msg Pointer to the message. + * @return Number of bytes available. + */ +static inline uint16_t ts_msg_scratchroom(struct thingset_msg *msg) +{ + TS_MSG_BUF_SCRATCHPAD_PTR_INIT(scratchpad, msg); + return scratchpad->scratchroom.size; +} + +/** + * @brief Get authentification rights from message data. + * + * @param[in] msg Pointer to the message. + * @return Authentification rights. + */ +static inline uint16_t ts_msg_auth(struct thingset_msg *msg) +{ + TS_MSG_BUF_SCRATCHPAD_PTR_INIT(scratchpad, msg); + return scratchpad->auth; +} + +/** + * @brief Set authentification rights in message data. + * + * @param[in] msg Pointer to the message. + * @param[in] auth Authentification rights. + */ +static inline void ts_msg_auth_set(struct thingset_msg *msg, uint16_t auth) +{ + TS_MSG_BUF_SCRATCHPAD_PTR_INIT(scratchpad, msg); + scratchpad->auth = auth; + TS_LOGD("MSG: %s set message 0x%" PRIXPTR " authorisation to 0x%04x", __func__, + (uintptr_t)msg, (unsigned int)auth); +} + +/** + * @ingroup ts_msg_api_status_priv + * @brief Set message status by message status code. + * + * @param[in] msg Pointer to the message. + * @param[in] valid Message validty. + * @param[in] proto Message protocol. + * @param[in] type Message type. + * @param[in] code Message status code. + */ +static inline void ts_msg_status_set(struct thingset_msg *msg, enum ts_msg_valid valid, + enum ts_msg_proto proto, enum ts_msg_type type, + enum ts_msg_code code) +{ + TS_MSG_BUF_SCRATCHPAD_PTR_INIT(scratchpad, msg); + scratchpad->status.valid = valid; + scratchpad->status.proto = proto; + scratchpad->status.type = type; + scratchpad->status.code = code; + + TS_ASSERT((scratchpad->status.type == TS_MSG_TYPE_REQUEST) || + (scratchpad->status.type == TS_MSG_TYPE_RESPONSE) || + (scratchpad->status.type == TS_MSG_TYPE_STATEMENT), + "MSG: %s sets message status to invalid type 0x%x", __func__, + (unsigned int)scratchpad->status.type); + + TS_LOGD("MSG: %s set message 0x%" PRIXPTR " status valid to 0x%02x", __func__, + (uintptr_t)msg, (unsigned int)valid); + TS_LOGD("MSG: %s set message 0x%" PRIXPTR " status proto to 0x%02x", __func__, + (uintptr_t)msg, (unsigned int)proto); + TS_LOGD("MSG: %s set message 0x%" PRIXPTR " status type to 0x%02x", __func__, + (uintptr_t)msg, (unsigned int)type); + TS_LOGD("MSG: %s set message 0x%" PRIXPTR " status code to 0x%02x", __func__, + (uintptr_t)msg, (unsigned int)code); +} + +/** + * @brief Get message protocol. + * + * @param[in] msg Pointer to the message. + * @return Message validity. + */ +static inline enum ts_msg_valid ts_msg_status_valid(struct thingset_msg *msg) +{ + TS_MSG_BUF_SCRATCHPAD_PTR_INIT(scratchpad, msg); + return (enum ts_msg_valid)scratchpad->status.valid; +} + +/** + * @brief Get message protocol. + * + * @param[in] msg Pointer to the message. + * @return Message protocol. + */ +static inline enum ts_msg_proto ts_msg_status_proto(struct thingset_msg *msg) +{ + TS_MSG_BUF_SCRATCHPAD_PTR_INIT(scratchpad, msg); + return (enum ts_msg_proto)scratchpad->status.proto; +} + +/** + * @brief Get message type. + * + * @param[in] msg Pointer to the message. + * @return Message type. + */ +static inline enum ts_msg_type ts_msg_status_type(struct thingset_msg *msg) +{ + TS_MSG_BUF_SCRATCHPAD_PTR_INIT(scratchpad, msg); + return (enum ts_msg_type)scratchpad->status.type; +} + + +/** + * @brief Get message encoding. + * + * @param[in] msg Pointer to the message. + * @return Message encoding. + */ +static inline enum ts_msg_encoding ts_msg_status_encoding(struct thingset_msg *msg) +{ + TS_MSG_BUF_SCRATCHPAD_PTR_INIT(scratchpad, msg); + return (enum ts_msg_encoding)scratchpad->status.encoding; +} + +/** + * @brief Get message status as message status code. + * + * @param[in] msg Pointer to the message. + * @return Message status code. + */ +static inline ts_msg_status_code_t ts_msg_status_code(struct thingset_msg *msg) +{ + TS_MSG_BUF_SCRATCHPAD_PTR_INIT(scratchpad, msg); + return scratchpad->status.code; +} + +/** + * @brief Set message status code. + * + * @param[in] msg Pointer to the message. + * @param[in] code Message status code. + */ +static inline void ts_msg_status_code_set(struct thingset_msg *msg, enum ts_msg_code code) +{ + TS_MSG_BUF_SCRATCHPAD_PTR_INIT(scratchpad, msg); + scratchpad->status.code = code; +} + +/** + * @brief Set message status to error. + * + * Marks the message to be invalid due to error. + * + * @param[in] msg Pointer to the message. + * @param[in] error Message status error code. + */ +static inline void ts_msg_status_error_set(struct thingset_msg *msg, enum ts_msg_code error) +{ + TS_MSG_BUF_SCRATCHPAD_PTR_INIT(scratchpad, msg); + scratchpad->status.valid = TS_MSG_VALID_ERROR; + scratchpad->status.code = error; +} + +/** + * @brief Get message status. + * + * @param[in] msg Pointer to the message. + * @return Message status. + */ +static inline struct ts_msg_stat ts_msg_status(struct thingset_msg *msg) +{ + TS_MSG_BUF_SCRATCHPAD_PTR_INIT(scratchpad, msg); + return scratchpad->status; +} + +/** @} ts_msg_api_buf_priv */ + +/* + * ThingSet message parsing state support + * -------------------------------------- + */ + +/** + * @brief Structure for parsing state of a message buffer. + * + * This is used for temporarily storing the parsing state of a message buffer + * while giving control of the parsing to a routine which we don't control. + */ +struct ts_msg_state { + /** @brief Data pointer to the beginning of the remaining message */ + uint8_t *data; + /** @brief Data pointer after the end of the message */ + uint8_t *tail; +}; + +/** + * @brief Save the parsing state of a message buffer. + * + * Save the parsing state of a buffer so it can be restored later by ts_msg_state_restore(). + * + * @note Does not save scratchpad state(s). + * + * @param[in] msg Pointer to the message. + * @param[out] state Pointer to storage for the state. + */ +static inline void ts_msg_state_save(struct thingset_msg *msg, struct ts_msg_state *state) +{ + state->data = ts_msg_data(msg); + state->tail = ts_msg_tail(msg); +} + +/** + * @brief Restore the parsing state of a message buffer. + * + * Restore the parsing state of a buffer from a state previously stored by ts_msg_state_save(). + * + * @note Does not restore scratchpad state(s). + * + * @param[in] msg Pointer to the message. + * @param[out] state Stored state. + */ +static inline void ts_msg_state_restore(struct thingset_msg *msg, struct ts_msg_state *state) +{ + ts_buf_push((struct ts_buf *)msg, ts_msg_data(msg) - state->data); + ts_buf_add((struct ts_buf *)msg, state->tail - ts_msg_tail(msg)); + + TS_ASSERT(ts_msg_data(msg) == state->data, + "MSG: %s restores message 0x%" PRIXPTR " to invalid data 0x%" PRIXPTR + " instead of 0x%" PRIXPTR, __func__, (uintptr_t)msg, (uintptr_t)ts_msg_data(msg), + (uintptr_t)state->data); + TS_ASSERT(ts_msg_tail(msg) == state->tail, + "MSG: %s restores message 0x%" PRIXPTR " to invalid tail 0x%" PRIXPTR + " instead of 0x%" PRIXPTR, __func__, (uintptr_t)msg, (uintptr_t)ts_msg_tail(msg), + (uintptr_t)state->tail); +} + +/* + * ThingSet message (en/de-)coder support + * -------------------------------------- + */ + +/** + * @brief Message processing support. + * + * @defgroup ts_msg_api_proc_priv ThingSet message processing support (private interface) + * + * @{ + */ + +/** @brief Scratchpad for ThingSet context message processing */ +#define TS_MSG_SCRATCHPAD_PROC 0x1U + +/** + * @brief Data structure for message processing scratchroom at end of message. + */ +struct ts_msg_proc_scratchroom { + /** @brief Source port */ + thingset_portid_t port_src; + + /** @brief Destination port */ + thingset_portid_t port_dest; + + /** @brief Unique context identifier of node the message was sent from or shall be send to */ + thingset_uid_t ctx_uid; + + /** @brief Subset to use if message is for import/ export of ThingSet data objects. */ + uint16_t import_export_subset; + + /** @brief Size hint for response message if this message is a request */ + uint16_t response_size_hint; +}; + +/** + * @def TS_MSG_PROC_SCRATCHPAD_PTR_INIT + * + * @brief Define and initialize pointer to message processing scratchpad of message. + * + * @note Macro asserts on wrong message or scratchpad type if TS_CONFIG_ASSERT is enabled. + * + * @param[in] name Object name of pointer to message processing scratchpad. + * @param[in] msg Pointer to the message. + */ +#define TS_MSG_PROC_SCRATCHPAD_PTR_INIT(name, msg) \ + TS_MSG_ASSERT_SCRATCHTYPE(msg, TS_MSG_SCRATCHPAD_PROC); \ + /* ts_msg_tailroom() accounts for scratchroom */ \ + struct ts_msg_proc_scratchroom *name = \ + (struct ts_msg_proc_scratchroom *)(ts_msg_tail(msg) + ts_msg_tailroom(msg)) + +/** + * @brief Setup message buffer for ThingSet context message processing. + * + * @param[in] buffer Pointer to message buffer + * @return 0 on success, <0 otherwise + */ +int ts_msg_proc_setup(struct thingset_msg *msg); + +/** + * @brief Get unique context identifier from message processing data. + * + * @param[in] msg Pointer to the message. + * @returns Pointer to node identifier. + */ +static inline const thingset_uid_t *ts_msg_proc_get_ctx_uid(struct thingset_msg *msg) +{ + TS_MSG_PROC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + return &scratchpad->ctx_uid; +} + +/** + * @brief Set unique context identifier in message processing data. + * + * @param[in] msg Pointer to the message. + * @param[in] ctx_uid Pointer to unique context identifier. + */ +static inline void ts_msg_proc_set_ctx_uid(struct thingset_msg *msg, const thingset_uid_t *ctx_uid) +{ + TS_MSG_PROC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + scratchpad->ctx_uid = *ctx_uid; +} + +/** + * @brief Get source port from message processing data. + * + * @param[in] msg Pointer to the message. + * @returns Source port identifier. + */ +static inline thingset_portid_t ts_msg_proc_get_port_src(struct thingset_msg *msg) +{ + TS_MSG_PROC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + return scratchpad->port_src; +} + +/** + * @brief Set source port in message processing data. + * + * @param[in] msg Pointer to the message. + * @param[in] port_id Source port identifier. + */ +static inline void ts_msg_proc_set_port_src(struct thingset_msg *msg, thingset_portid_t port_id) +{ + TS_MSG_PROC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + scratchpad->port_src = port_id; +} + +/** + * @brief Get destination port from message processing data. + * + * @param[in] msg Pointer to the message. + * @returns Destination port identifier. + */ +static inline thingset_portid_t ts_msg_proc_get_port_dest(struct thingset_msg *msg) +{ + TS_MSG_PROC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + return scratchpad->port_dest; +} + +/** + * @brief Set Destination port in message processing data. + * + * @param[in] msg Pointer to the message. + * @param[in] port_id Destination port identifier. + */ +static inline void ts_msg_proc_set_port_dest(struct thingset_msg *msg, thingset_portid_t port_id) +{ + TS_MSG_PROC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + scratchpad->port_dest = port_id; +} + +/** + * @brief Get reponse size hint from message processing data. + * + * A request message may contain a hint of the suitable size for a response message. + * + * @param[in] msg Pointer to the message. + * @returns Response size. 0 indicates no hint. + */ +static inline uint16_t ts_msg_proc_get_resp_size(struct thingset_msg *msg) +{ + TS_MSG_PROC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + return scratchpad->response_size_hint; +} + +/** + * @brief Set response size hint in message processing data. + * + * A request message may contain a hint of the suitable size for a response message. + * + * @param[in] msg Pointer to the message. + * @param[in] resp_size Response size. 0 indicates no hint. + */ +static inline void ts_msg_proc_set_resp_size(struct thingset_msg *msg, uint16_t resp_size) +{ + TS_MSG_PROC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + scratchpad->response_size_hint = resp_size; +} + +/** + * @} + */ + +/** + * @brief Message JSON encoder support. + * + * @defgroup ts_msg_api_json_enc_priv ThingSet message JSON encoder (private interface) + * @{ + */ + +/** @brief Scratchpad for JSON encoding */ +#define TS_MSG_SCRATCHPAD_JSON_ENC 0x2U + +/** + * @brief Data structure for JSON encoder. + */ +struct ts_msg_json_encoder { + /** @brief Number of remaining elements for a map or array. */ + uint16_t remaining; + struct { + /** @brief Encoder is encoding a map. */ + uint16_t map : 1; + /** @brief Encoder is encoding an unbounded map. */ + uint16_t map_unbounded : 1; + /** @brief Encoder is encoding an array. */ + uint16_t array : 1; + /** @brief Encoder is encoding an unbounded array. */ + uint16_t array_unbounded : 1; + /** @brief Next value is a key. */ + uint16_t key : 1; + } flags; +}; + +/** + * @brief Data structure for JSON encoder scratchroom at end of message. + */ +struct ts_msg_json_enc_scratchroom { + /** @brief String buffer for conversion. */ + char s[32]; + uint8_t current; + struct ts_msg_json_encoder instance[3]; +}; + +/** + * @def TS_MSG_JSON_ENC_SCRATCHPAD_PTR_INIT + * + * @brief Define and initialize pointer to JSON encoder scratchpad of message. + * + * @note Macro asserts on wrong message or scratchpad type if TS_CONFIG_ASSERT is enabled. + * + * @param[in] name Object name of pointer to JSON encoder scratchpad. + * @param[in] msg Pointer to the message. + */ +#define TS_MSG_JSON_ENC_SCRATCHPAD_PTR_INIT(name, msg) \ + TS_MSG_ASSERT_SCRATCHTYPE(msg, TS_MSG_SCRATCHPAD_JSON_ENC); \ + /* ts_msg_tailroom() accounts for scratchroom */ \ + struct ts_msg_json_enc_scratchroom *name = \ + (struct ts_msg_json_enc_scratchroom *)(ts_msg_tail(msg) + ts_msg_tailroom(msg)) + +/** + * @brief Setup message buffer for JSON encoding. + * + * @param[in] buffer Pointer to message buffer + * @return 0 on success, <0 otherwise + */ +int ts_msg_json_enc_setup(struct thingset_msg *msg); + +/** + * @brief Get current Encoder of JSON encoder scratchpad. + * + * @param[in] msg Pointer to the message. + * @return current encoder + */ +static inline struct ts_msg_json_encoder *ts_msg_scratchpad_json_enc_current(struct thingset_msg *msg) +{ + TS_MSG_JSON_ENC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + return &(scratchpad->instance[scratchpad->current]); +} + +/** + * @brief Update message data position based on JSON encoder result. + * + * @param[in] msg Pointer to the message. + * @param[in] func function that calls + * @param[in] s JSON string to add to message. + * @param[in] len Length of JSON string. + * @returns 0 on success, <0 on encoder error. + */ +int ts_msg_scratchpad_json_enc_update(struct thingset_msg *msg, const char *func, const char *s, uint16_t len); + +static inline bool ts_msg_scratchpad_json_enc_is_top(struct thingset_msg *msg) +{ + TS_MSG_JSON_ENC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + return scratchpad->current == 0; +} + +/** + * @brief Add an array to the JSON message. + * + * @param[in] msg Pointer to the message. + * @param[in] num_elements Number of elements in the array. 0 indicates an unbounded array. + * @returns 0 on success, <0 otherwise. + */ +int ts_msg_add_array_json(struct thingset_msg* msg, uint16_t num_elements); + +/** + * @brief Add array end to the JSON message. + * + * @param[in] msg Pointer to the message. + * @returns 0 on success, <0 otherwise. + */ +int ts_msg_add_array_end_json(struct thingset_msg* msg); + +int ts_msg_add_bool_json(struct thingset_msg* msg, bool b); + +int ts_msg_add_decfrac_json(struct thingset_msg* msg, int32_t mantissa, int16_t exponent); + +int ts_msg_add_f32_json(struct thingset_msg* msg, float val, int precision); + +int ts_msg_add_i16_json(struct thingset_msg* msg, int16_t val); + +int ts_msg_add_i32_json(struct thingset_msg* msg, int32_t val); + +int ts_msg_add_i64_json(struct thingset_msg* msg, int64_t val); + +/** + * @brief Add a map to the JSON message. + * + * @param[in] msg Pointer to the message. + * @param[in] num_elements Number of elements in the map. 0 indicates an unbounded map. + * @returns 0 on success, <0 otherwise. + */ +int ts_msg_add_map_json(struct thingset_msg* msg, uint16_t num_elements); + +/** + * @brief Add map end to the JSON message. + * + * @param[in] msg Pointer to the message. + * @returns 0 on success, <0 otherwise. + */ +int ts_msg_add_map_end_json(struct thingset_msg* msg); + +int ts_msg_add_string_json(struct thingset_msg* msg, const char *s); + +int ts_msg_add_u16_json(struct thingset_msg* msg, uint16_t val); + +int ts_msg_add_u32_json(struct thingset_msg* msg, uint32_t val); + +int ts_msg_add_u64_json(struct thingset_msg* msg, uint64_t val); + +/** + * @} + */ + +/** + * @brief Message JSON decoder support. + * + * @defgroup ts_msg_api_json_dec_priv ThingSet message JSON decoder (private interface) + * @{ + */ + +/** @brief Scratchpad for JSMN JSON parser */ +#define TS_MSG_SCRATCHPAD_JSON_DEC 0x3U + +/** + * @brief Data structure for JSON decoder (JSMN) scratchroom at end of message. + */ +struct ts_msg_json_dec_scratchroom { + /** @brief Index of current token for pull operation */ + uint16_t token_idx; + /** @brief JSMN (JSON decoder) context */ + struct ts_jsmn_context jsmn; +}; + +/** + * @def TS_MSG_JSON_DEC_SCRATCHPAD_PTR_INIT + * + * @brief Define and initialize pointer to JSMN (JSON decoder) scratchpad of message. + * + * @note Macro asserts on wrong message or scratchpad type if TS_CONFIG_ASSERT is enabled. + * + * @param[in] name Object name of pointer to JSMN (JSON decoder) scratchpad. + * @param[in] msg Pointer to the message. + */ +#define TS_MSG_JSON_DEC_SCRATCHPAD_PTR_INIT(name, msg) \ + TS_MSG_ASSERT_SCRATCHTYPE(msg, TS_MSG_SCRATCHPAD_JSON_DEC); \ + /* ts_msg_tailroom() accounts for scratchroom */ \ + struct ts_msg_json_dec_scratchroom *name = (struct ts_msg_json_dec_scratchroom *)(ts_msg_tail(msg) \ + + ts_msg_tailroom(msg)) + +/** + * @brief Setup message buffer for JSON decoding/ parsing. + * + * Current message data position is taken as start of JSON data string. + * JSON data string is up to end of buffer. + * + * @param[in] buffer Pointer to message buffer + * @return 0 on success, <0 otherwise + */ +int ts_msg_json_dec_setup(struct thingset_msg *msg); + +/** + * @brief Update message data position based on JSON decoder result. + * + * @param[in] msg Pointer to the message. + * @param[in] func function that calls + * @param[in] start Start of JSON string in message. + * @param[in] len Length of JSON string. + * @returns 0 on success, <0 on decoder error. + */ +int ts_msg_json_dec_update(struct thingset_msg *msg, const char *func, const char *start, + uint16_t len); + +int ts_msg_pull_type_json(struct thingset_msg* msg, uint16_t *jsmn_type); + +int ts_msg_pull_array_json(struct thingset_msg* msg, uint16_t *num_elements); + +int ts_msg_pull_bool_json(struct thingset_msg* msg, bool *b); + +int ts_msg_pull_array_end_json(struct thingset_msg* msg); + +int ts_msg_pull_decfrac_json(struct thingset_msg* msg, int32_t *mantissa, int16_t *exponent); + +int ts_msg_pull_f32_json(struct thingset_msg* msg, float *val); + +int ts_msg_pull_i16_json(struct thingset_msg* msg, int16_t *val); + +int ts_msg_pull_i32_json(struct thingset_msg* msg, int32_t *val); + +int ts_msg_pull_i64_json(struct thingset_msg* msg, int64_t *val); + +int ts_msg_pull_map_json(struct thingset_msg* msg, uint16_t *num_elements); + +int ts_msg_pull_map_end_json(struct thingset_msg* msg); + +int ts_msg_pull_mem_json(struct thingset_msg* msg, const uint8_t **mem, uint16_t *mem_len); + +/** + * @brief Pull a JSON text string from the JSON message. + * + * @param[in] msg Pointer to the message. + * @param[out] s Pointer to text string. + * @param[out] s_len Pointer to length of text string. + * @returns 0 on success, <0 otherwise. + * @return -EINVAL JSON data element is not a text string + * @return -ENOMEM No JSON data element. + */ +int ts_msg_pull_string_json(struct thingset_msg* msg, const char **s, uint16_t *s_len); + +int ts_msg_pull_u16_json(struct thingset_msg* msg, uint16_t *val); + +int ts_msg_pull_u32_json(struct thingset_msg* msg, uint32_t *val); + +int ts_msg_pull_u64_json(struct thingset_msg* msg, uint64_t *val); + +/** + * @} + */ + +/** + * @brief Message CBOR encoder support. + * + * @defgroup ts_msg_api_cbor_enc_priv ThingSet message CBOR encoder (private interface) + * @{ + */ + +/** @brief Scratchpad for TinyCBOR encoder */ +#define TS_MSG_SCRATCHPAD_CBOR_ENC 0x4U + +/** + * @brief Data structure for TinyCBOR encoder scratchroom at end of message. + */ +struct ts_msg_cbor_enc_scratchroom { + uint8_t current; + struct CborEncoder instance[3]; +}; + +/** + * @def TS_MSG_CBOR_ENC_SCRATCHPAD_PTR_INIT + * + * @brief Define and initialize pointer to TinyCBOR encoder scratchpad of message. + * + * @note Macro asserts on wrong message or scratchpad type if TS_CONFIG_ASSERT is enabled. + * + * @param[in] name Object name of pointer to TinyCBOR encoder scratchpad. + * @param[in] msg Pointer to the message. + */ +#define TS_MSG_CBOR_ENC_SCRATCHPAD_PTR_INIT(name, msg) \ + TS_MSG_ASSERT_SCRATCHTYPE(msg, TS_MSG_SCRATCHPAD_CBOR_ENC); \ + /* ts_msg_tailroom() accounts for scratchroom */ \ + struct ts_msg_cbor_enc_scratchroom *name = \ + (struct ts_msg_cbor_enc_scratchroom *)(ts_msg_tail(msg) + ts_msg_tailroom(msg)) + +/** + * @brief Setup message buffer for CBOR encoding. + * + * Current message data position is taken as start of CBOR data. + * Complete current tailroom is reserved to encoded CBOR data. + * + * @note If the default assumptions about the CBOR data start and size are invalid the encoder has + * to be initialized again. + * + * @param[in] buffer Pointer to message buffer + * @return 0 on success, <0 otherwise + */ +int ts_msg_cbor_enc_setup(struct thingset_msg *msg); + +/** + * @brief Get current CborEncoder of TinyCBOR encoder scratchpad. + * + * @param[in] msg Pointer to the message. + * @return current encoder + */ +static inline struct CborEncoder *ts_msg_scratchpad_tinycbor_enc_current(struct thingset_msg *msg) +{ + TS_MSG_CBOR_ENC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + return &(scratchpad->instance[scratchpad->current]); +} + +/** + * @brief Update message data position based on TnyCBOR encoder result. + * + * @param[in] msg Pointer to the message. + * @param[in] func function that calls + * @param[in] error Enocder result. + * @returns 0 on success, <0 on encoder error. + */ +int ts_msg_scratchpad_tinycbor_enc_update(struct thingset_msg *msg, const char *func, CborError error); + +static inline bool ts_msg_scratchpad_tinycbor_enc_is_top(struct thingset_msg *msg) +{ + TS_MSG_CBOR_ENC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + return scratchpad->current == 0; +} + +int ts_msg_add_array_cbor(struct thingset_msg* msg, uint16_t num_elements); + +int ts_msg_add_array_end_cbor(struct thingset_msg* msg); + +int ts_msg_add_bool_cbor(struct thingset_msg* msg, bool b); + +int ts_msg_add_decfrac_cbor(struct thingset_msg* msg, int32_t mantissa, int16_t exponent); + +int ts_msg_add_f32_cbor(struct thingset_msg* msg, float val, int precision); + +int ts_msg_add_i64_cbor(struct thingset_msg* msg, int64_t val); + +static inline int ts_msg_add_i16_cbor(struct thingset_msg* msg, int16_t val) +{ + return ts_msg_add_i64_cbor(msg, (int64_t)val); +} + +static inline int ts_msg_add_i32_cbor(struct thingset_msg* msg, int32_t val) +{ + return ts_msg_add_i64_cbor(msg, (int64_t)val); +} + +int ts_msg_add_map_cbor(struct thingset_msg* msg, uint16_t num_elements); + +int ts_msg_add_map_end_cbor(struct thingset_msg* msg); + +int ts_msg_add_mem_cbor(struct thingset_msg* msg, const uint8_t *mem, uint16_t len); + +int ts_msg_add_string_cbor(struct thingset_msg* msg, const char *s); + +int ts_msg_add_undefined_cbor(struct thingset_msg* msg); + +int ts_msg_add_u64_cbor(struct thingset_msg* msg, uint64_t val); + +static inline int ts_msg_add_u16_cbor(struct thingset_msg* msg, uint16_t val) +{ + return ts_msg_add_u64_cbor(msg, (uint64_t)val); +} + +static inline int ts_msg_add_u32_cbor(struct thingset_msg* msg, uint32_t val) +{ + return ts_msg_add_u64_cbor(msg, (uint64_t)val); +} + +/** + * @} + */ + +/** + * @brief Message CBOR decoder support. + * + * @defgroup ts_msg_api_cbor_dec_priv ThingSet message CBOR decoder (private interface) + * @{ + */ + +/** @brief Scratchpad for TinyCBOR decoder/parser */ +#define TS_MSG_SCRATCHPAD_CBOR_DEC 0x5U + +/** + * @brief Data structure for TinyCBOR decoder scratchroom at end of message. + */ +struct ts_msg_cbor_dec_scratchroom { + uint8_t current; + struct CborParser parser; + struct CborValue value[3]; +}; + +/** + * @def TS_MSG_CBOR_DEC_SCRATCHPAD_PTR_INIT + * + * @brief Define and initialize pointer to TinyCBOR decoder scratchpad of message. + * + * @note Macro asserts on wrong message or scratchpad type if TS_CONFIG_ASSERT is enabled. + * + * @param[in] name Object name of pointer to TinyCBOR decoder scratchpad. + * @param[in] msg Pointer to the message. + */ +#define TS_MSG_CBOR_DEC_SCRATCHPAD_PTR_INIT(name, msg) \ + TS_MSG_ASSERT_SCRATCHTYPE(msg, TS_MSG_SCRATCHPAD_CBOR_DEC); \ + /* ts_msg_tailroom() accounts for scratchroom */ \ + struct ts_msg_cbor_dec_scratchroom *name = \ + (struct ts_msg_cbor_dec_scratchroom *)(ts_msg_tail(msg) + ts_msg_tailroom(msg)) + +/** + * @brief Setup message buffer for CBOR decoding. + * + * Current message data position is taken as start of CBOR data. + * CBOR data size is up to end of buffer. + * + * @note If the default assumptions about the CBOR data start and size are invalid the parser has + * to be initialized again: + * TS_MSG_CBOR_DEC_SCRATCHPAD_PTR_INIT(decoder, msg); + * cbor_parser_init(new_start, new_size, new_flags, &decoder->parser, &decoder->value); + * + * @param[in] buffer Pointer to message buffer + * @return 0 on success, <0 otherwise + */ +int ts_msg_cbor_dec_setup(struct thingset_msg *msg); + +/** + * @brief Get current CborValue iterator of TinyCBOR decoder scratchpad. + * + * @param[in] msg Pointer to the message. + * @return current iterator + */ +static inline struct CborValue *ts_msg_scratchpad_tinycbor_dec_current(struct thingset_msg *msg) +{ + TS_MSG_CBOR_DEC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + return &(scratchpad->value[scratchpad->current]); +} + +/** + * @brief Update message data position based on decoder result. + * + * @param[in] msg Pointer to the message. + * @param[in] advance_value advance decoder to next CBOR value + * @param[in] func function that calls + * @param[in] error Deocder result. + * @returns 0 on success, <0 on decoder error. + */ +int ts_msg_scratchpad_tinycbor_dec_update(struct thingset_msg *msg, bool advance_value, + const char *func, CborError error); + +static inline bool ts_msg_scratchpad_tinycbor_dec_is_top(struct thingset_msg *msg) +{ + TS_MSG_CBOR_DEC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + return scratchpad->current == 0; +} + + +int ts_msg_pull_type_cbor(struct thingset_msg* msg, CborType *cbor_type); + +int ts_msg_pull_array_cbor(struct thingset_msg* msg, uint16_t *num_elements); + +int ts_msg_pull_array_end_cbor(struct thingset_msg* msg); + +int ts_msg_pull_bool_cbor(struct thingset_msg* msg, bool *b); + +int ts_msg_pull_decfrac_cbor(struct thingset_msg* msg, int32_t *mantissa, int16_t *exponent); + +int ts_msg_pull_f32_cbor(struct thingset_msg* msg, float *val); + +int ts_msg_pull_i16_cbor(struct thingset_msg* msg, int16_t *val); + +int ts_msg_pull_i32_cbor(struct thingset_msg* msg, int32_t *val); + +int ts_msg_pull_i64_cbor(struct thingset_msg* msg, int64_t *val); + +int ts_msg_pull_map_cbor(struct thingset_msg* msg, uint16_t *num_elements); + +int ts_msg_pull_map_end_cbor(struct thingset_msg* msg); + +int ts_msg_pull_mem_cbor(struct thingset_msg* msg, const uint8_t **mem, uint16_t *len); + +/** + * @brief Pull a CBOR text string from the message. + * + * Pull definitive length text string. Return error if the string is not of definitive length + * or the data element is not a text string. + * + * @param[in] msg Pointer to the message. + * @param[out] s Pointer to text string. + * @param[out] len Pointer to length of text string. + * @returns 0 on success, <0 otherwise. + * @return -EINVAL CBOR data element is not a definitive length test string + * @return -ENOMEM No CBOR data element. + */ +int ts_msg_pull_string_cbor(struct thingset_msg* msg, const char **s, uint16_t *len); + +int ts_msg_pull_u16_cbor(struct thingset_msg* msg, uint16_t *val); + +int ts_msg_pull_u32_cbor(struct thingset_msg* msg, uint32_t *val); + +int ts_msg_pull_u64_cbor(struct thingset_msg* msg, uint64_t *val); + +/** + * @} + */ + +/** + * @brief Message COBS encoder support. + * + * @defgroup ts_msg_api_cobs_enc_priv ThingSet message COBS encoder (private interface) + * @{ + */ + +/** @brief Scratchpad for COBS encoder */ +#define TS_MSG_SCRATCHPAD_COBS_ENC 0x6U + +/** + * @brief Setup message buffer for COBS encoding. + * + * Current message data position is taken as start of (decoded) data to be COBS encoded. + * + * @param[in] buffer Pointer to message buffer + * @return 0 on success, <0 otherwise + */ +int ts_msg_cobs_enc_setup(struct thingset_msg *msg); + +/** + * @} + */ + +/** + * @brief Message COBS decoder support. + * + * @defgroup ts_msg_api_cobs_dec_priv ThingSet message COBS decoder (private interface) + * @{ + */ + +/** @brief Scratchpad for COBS decoder */ +#define TS_MSG_SCRATCHPAD_COBS_DEC 0x7U + +/** + * @brief Setup message buffer for COBS decoding. + * + * Current message data position is taken as start of COBS encoded data to be decoded. + * + * @param[in] buffer Pointer to message buffer + * @return 0 on success, <0 otherwise + */ +int ts_msg_cobs_dec_setup(struct thingset_msg *msg); + +/** + * @} + */ + +/* + * ThingSet message primitive value support + * ---------------------------------------- + */ + +static inline uint8_t *ts_msg_add(struct thingset_msg* msg, uint16_t len) +{ + return ts_buf_add((struct ts_buf *)msg, len); +} + +int ts_msg_add_mem(struct thingset_msg* msg, const uint8_t *mem, uint16_t len); + +int ts_msg_add_u8(struct thingset_msg* msg, uint8_t val); + +static inline uint8_t *ts_msg_pull(struct thingset_msg* msg, uint16_t len) +{ + TS_ASSERT(len <= ts_msg_len(msg), "Pull %u but only %u in message.", + (unsigned int)len, (unsigned int)ts_msg_len(msg)); + return ts_buf_pull((struct ts_buf *)msg, len); +} + +int ts_msg_pull_u8(struct thingset_msg* msg, uint8_t *val); + +int ts_msg_pull_path(struct thingset_msg* msg, const char **path, uint16_t *len); + +static inline uint8_t *ts_msg_push(struct thingset_msg* msg, uint16_t len) +{ + TS_ASSERT(len <= ts_msg_headroom(msg), + "Push %u but only %u available in headroom of message 0x%" PRIXPTR ".", + (unsigned int)len, (unsigned int)ts_msg_headroom(msg), (uintptr_t)msg); + return ts_buf_push((struct ts_buf *)msg, len); +} + +static inline uint8_t *ts_msg_remove(struct thingset_msg* msg, uint16_t len) +{ + TS_ASSERT(len <= ts_msg_len(msg), "Remove %u but only %u in message 0x%" PRIXPTR ".", + (unsigned int)len, (unsigned int)ts_msg_len(msg), (uintptr_t)msg); + return ts_buf_remove((struct ts_buf *)msg, len); +} + +/* + * Logging support + * --------------- + */ + +/** + * @brief Print the message to log buffer. + * + * @param[in] msg Pointer to the message. + * @param[in] log Pointer to the log buffer. + * @param[in] len Length of the log buffer. + * @returns Number of characters printed to buffer. + */ +int ts_msg_log(struct thingset_msg* msg, char *log, size_t len); + +/* + * ThingSet export and import support + * ---------------------------------- + */ + +/** + * @brief Add export data in CBOR format for given subset(s). + * + * This function creates a specific export message that may later on be used to import the data. + * + * @note The function may be used to store data in the EEPROM or other non-volatile memory. + * + * @param[in] msg Pointer to the message. + * @param[in] did Data objects database ID. + * @param[in] subsets Flags to select which subset(s) of data items should be exported. + * @returns 0 in case of siccess, <0 on error. + */ +int ts_msg_add_export_cbor(struct thingset_msg* msg, ts_obj_db_id_t did, uint16_t subsets); + +/** + * @brief Add export data in JSON format for given subset(s). + * + * This function creates a specific export message that may later on be used to import the data. + * + * @note The function may be used to store data in the EEPROM or other non-volatile memory. + * + * @param[in] msg Pointer to the message. + * @param[in] did Data objects database ID. + * @param[in] subsets Flags to select which subset(s) of data items should be exported. + * @returns 0 in case of success, <0 on error. + */ +int ts_msg_add_export_json(struct thingset_msg* msg, ts_obj_db_id_t did, uint16_t subsets); + +/** + * @brief Remove export data from message and fill into data objects. + * + * @note The function may be used to initialize data objects from previously exported data. + * + * @param[in] msg Pointer to the message. + * @param[in] did Data objects database ID. + * @returns 0 in case of success, <0 on error. + */ +int ts_msg_pull_export(struct thingset_msg* msg, ts_obj_db_id_t did); + + +/** + * @brief Load message from buffer. + * + * @param[in] msg Pointer to the message. + * @param[in] buf Pointer to the buffer where the message is stored. + * @param[in] buf_len Length of message in buffer. + * @returns 0 in case of success, <0 on error. + */ +int ts_msg_load(struct thingset_msg* msg, const uint8_t *buf, uint16_t buf_len); + +/** + * @brief Save message to buffer. + * + * @param[in] msg Pointer to the message. + * @param[in] buf Pointer to the buffer where the message shall be stored. + * @param[in,out] buf_len Length of the buffer. Maximum allowed length of the data on invocation. + * Message data length on return. + * @returns 0 in case of success, <0 on error. + */ +int ts_msg_save(struct thingset_msg* msg, uint8_t *buf, uint16_t *buf_len); + +/* + * ThingSet Protocol support + * ------------------------- + */ + +/** + * @brief ThingSet message protocol support + * + * @defgroup ts_msg_api_proto_priv ThingSet message protocol support (private interface) + * @{ + */ + +/** + * @brief Add ThingSet protocol status code at the end of the message. + * + * A status-code = 2( hex ). + * + * @param[in] msg Pointer to the message. + * @param[in] code Status code + * @returns 0 on success, <0 otherwise. + */ +int ts_msg_add_status(struct thingset_msg* msg, ts_msg_status_code_t code); + +/** + * @brief Add ThingSet object at the end of the message in CBOR format. + * + * Increments the data length of the message to account for more data added at the end. + * + * @param[in] msg Pointer to the message. + * @param[in] oref Database object reference to object which shall be serialized. + * @returns 0 on success, <0 otherwise. + */ +int ts_msg_add_object_cbor(struct thingset_msg *msg, thingset_oref_t oref); + +/** + * @brief Add ThingSet object at the end of the message in JSON format. + * + * Increments the data length of the message to account for more data added at the end. + * + * @param[in] msg Pointer to the message. + * @param[in] oref Database object reference to object which shall be serialized. + * @returns 0 on success, <0 otherwise. + */ +int ts_msg_add_object_json(struct thingset_msg *msg, thingset_oref_t oref); + +/** + * @brief Add ThingSet protocol GET request in CBOR format. + * + * The object to get is either specified by it's parent object identifier + * or by the path. + * + * If path is NULL the object identifier will be used. + * + * @param[in] msg Pointer to the message. + * @param[in] object_id Identifier of object to get. + * @param[in] path Pointer to path of object to get. + * @returns 0 on success, <0 otherwise. + */ +int ts_msg_add_request_get_cbor(struct thingset_msg* msg, ts_obj_id_t object_id, const char *path); + +/** + * @brief Add ThingSet protocol GET request in JSON format. + * + * @param[in] msg Pointer to the message. + * @param[in] path Pointer to path of object to get. + * @returns 0 on success, <0 otherwise. + */ +int ts_msg_add_request_get_json(struct thingset_msg *msg, const char *path); + +/** + * @brief Add ThingSet protocol response to GET request in CBOR format. + * + * @param[in] msg Pointer to the message. + * @param[in] oref Database object reference to parent object. + * @param[in] request Pointer to the request message. + * @returns 0 on success, <0 otherwise. + */ +int ts_msg_add_response_get_cbor(struct thingset_msg* msg, thingset_oref_t oref, + struct thingset_msg* request); + +/** + * @brief Add ThingSet protocol response to GET request in JSON format. + * + * @param[in] msg Pointer to the message. + * @param[in] oref Database object reference to parent object. + * @param[in] request Pointer to the request message. + * @returns 0 on success, <0 otherwise. + */ +int ts_msg_add_response_get_json(struct thingset_msg* msg, thingset_oref_t oref, + struct thingset_msg* request); + +/** + * @brief Add ThingSet protocol FETCH request in CBOR format. + * + * The parent object of the objects to fetch is either specified by it's parent object identifier + * or by the path. + * + * If path is NULL the object identifier will be used. + * + * The objects are either specified by the object identifiers or by their names. + * + * If names is NULL the object identifiers will be used. + * + * @param[in] msg Pointer to the message. + * @param[in] object_id Identifier of the parent object to fetch from. + * @param[in] path Pointer to path of parent object to fetch from. + * @param[in] object_count Number of objects to fetch. + * @param[in] object_ids Array of object identifiers of the objects to fetch. + * @param[in] object_names Array of names of the objects to fetch. + * @returns 0 on success, <0 otherwise. + */ +int ts_msg_add_request_fetch_cbor(struct thingset_msg* msg, ts_obj_id_t object_id, const char *path, + uint16_t object_count, ts_obj_id_t *object_ids, + const char **object_names); + +/** + * @brief Add ThingSet protocol FETCH request in JSON format. + * + * @param[in] msg Pointer to the message. + * @param[in] path Pointer to path of parent object to fetch from. + * @param[in] object_count Number of objects to fetch. + * @param[in] object_names Array of names of the objects to fetch. + * @returns 0 on success, <0 otherwise. + */ +int ts_msg_add_request_fetch_json(struct thingset_msg* msg, const char *path, + uint16_t object_count, const char **object_names); + +/** + * @brief Add ThingSet protocol response to FETCH request in CBOR format. + * + * @param[in] msg Pointer to the message. + * @param[in] oref Database object reference to parent object. + * @param[in] request Pointer to the request message. + * @returns 0 on success, <0 otherwise. + */ +int ts_msg_add_response_fetch_cbor(struct thingset_msg* msg, thingset_oref_t oref, + struct thingset_msg* request); + +/** + * @brief Add ThingSet protocol response to FETCH request in JSON format. + * + * @param[in] msg Pointer to the message. + * @param[in] oref Database object reference to parent object. + * @param[in] request Pointer to the request message. + * @returns 0 on success, <0 otherwise. + */ +int ts_msg_add_response_fetch_json(struct thingset_msg* msg, thingset_oref_t oref, + struct thingset_msg* request); + +/** + * @brief Add ThingSet protocol status response in CBOR format. + * + * Status code is taken from message status of @p msg. + * + * Switches message scratchpad to CBOR encoding. + * + * @param[in] msg Pointer to the message. + * @returns 0 on success, <0 otherwise. + */ +int ts_msg_add_response_status_cbor(struct thingset_msg* msg); + +/** + * @brief Add ThingSet protocol status response in JSON format. + * + * Status code is taken from message status of @p msg. + * + * Switches message scratchpad to JSON encoding. + * + * @param[in] msg Pointer to the message. + * @param[in] code Status code + * @returns 0 on success, <0 otherwise. + */ +int ts_msg_add_response_status_json(struct thingset_msg* msg); + +/** + * @brief Add ThingSet protocol statement in CBOR format. + * + * @param[in] msg Pointer to the message. + * @param[in] oref Database object reference to object. + * @returns 0 on success, <0 otherwise. + */ +int ts_msg_add_statement_cbor(struct thingset_msg* msg, thingset_oref_t oref); + +/** + * @brief Add ThingSet protocol statement in JSON format. + * + * @param[in] msg Pointer to the message. + * @param[in] oref Database object reference to object. + * @returns 0 on success, <0 otherwise. + */ +int ts_msg_add_statement_json(struct thingset_msg* msg, thingset_oref_t oref); + +/** + * @brief Remove ThingSet object from the message in CBOR format. + * + * @param[in] msg Pointer to the message. + * @param[in] oref Database object reference to object which shall be deserialized. + * @param[in] patch If true patch object, otherwise only check. + * @returns 0 on success, <0 otherwise. + */ +int ts_msg_pull_object_cbor(struct thingset_msg *msg, thingset_oref_t oref, bool patch); + +/** + * @brief Remove ThingSet object from the message in JSON format. + * + * @param[in] msg Pointer to the message. + * @param[in] oref Database object reference to object which shall be deserialized. + * @param[in] patch If true patch object, otherwise only check. + * @returns 0 on success, <0 otherwise. + */ +int ts_msg_pull_object_json(struct thingset_msg *msg, thingset_oref_t oref, bool patch); + +/** + * @brief Remove ThingSet protocol request in CBOR format from message. + * + * Parse the CBOR request, set request message status and provide object of request. + * + * @param[in] msg Pointer to the message. + * @param[in,out] oref Reference to object. ThingSet object database must be set in reference. + * @returns 0 on success, <0 otherwise. + */ +int ts_msg_pull_request_cbor(struct thingset_msg* msg, thingset_oref_t *oref); + +/** + * @brief Remove ThingSet protocol request in JSON format from message. + * + * Parse the JSON request and provide the request status and object of request. + * + * @param[in] msg Pointer to the message. + * @param[in,out] oref Reference to object. ThingSet object database must be set in reference. + * @returns 0 on success, <0 otherwise. + */ +int ts_msg_pull_request_json(struct thingset_msg* msg, thingset_oref_t *oref); + +/** + * @brief Remove ThingSet protocol status code from message. + * + * A status-code = 2( hex ). + * A status description ends by a trailing '.' + * + * @param[in] msg Pointer to the message. + * @param[out] code Pointer to status code. + * @param[out] description Pointer to description. + * @param[out] len Length of the description including trailing '.'. + * @returns 0 on success, <0 otherwise. + */ +int ts_msg_pull_status(struct thingset_msg* msg, ts_msg_status_code_t *code, + const char **description, uint16_t *len); + +/** + * @} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +/** + * @} + */ + +#endif /* TS_MSG_H_ */ diff --git a/src/ts_msg_coder.c b/src/ts_msg_coder.c new file mode 100644 index 0000000..1d2b20c --- /dev/null +++ b/src/ts_msg_coder.c @@ -0,0 +1,463 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * ThingSet message (en/de-)coder support + * -------------------------------------- + */ + +#include +#include +#include +#include + +#include "ts_msg.h" + +/* Helper */ + +int ts_msg_scratchpad_json_enc_update(struct thingset_msg *msg, const char *func, const char *s, + uint16_t len) +{ + int ret = 0; + struct ts_msg_json_encoder *encoder = ts_msg_scratchpad_json_enc_current(msg); + + if (s == NULL) { + /* JSON string already added to message */ + (void)ts_msg_add(msg, len); + } + else if (len > ts_msg_tailroom(msg)) { + TS_LOGE("JSON encoder: %s advance not possible (%u) - not enough memory (%u)", func, + (unsigned int)len, (unsigned int)ts_msg_tailroom(msg)); + return -ENOMEM; + } + else { + (void)ts_msg_add_mem(msg, (const uint8_t *)s, len); + } + + if (encoder->flags.map_unbounded) { + if (encoder->flags.key) { + /* We added a key */ + encoder->flags.key = 0; + len += 1; + ret = ts_msg_add_u8(msg, ':'); + } + else { + /* We added a value */ + encoder->flags.key = 1; + len += 1; + ret = ts_msg_add_u8(msg, ','); + } + } + else if (encoder->flags.map) { + if (encoder->remaining == 0) { + TS_LOGE("JSON encoder: %s advance to next key/value after end of map", func); + ret = -ENOMEM; + } + else if (encoder->flags.key) { + /* We added a key */ + encoder->flags.key = 0; + len += 1; + ret = ts_msg_add_u8(msg, ':'); + } + else { + /* We added a value */ + encoder->flags.key = 1; + encoder->remaining--; + if (encoder->remaining) { + len += 1; + ret = ts_msg_add_u8(msg, ','); + } + } + } + else if (encoder->flags.array_unbounded) { + len += 1; + ret = ts_msg_add_u8(msg, ','); + } + else if (encoder->flags.array) { + if (encoder->remaining == 0) { + TS_LOGE("JSON encoder: %s advance to next value after end of array", func); + ret = -ENOMEM; + } + else { + encoder->remaining--; + if (encoder->remaining) { + len += 1; + ret = ts_msg_add_u8(msg, ','); + } + } + } + else if (encoder->remaining != 0) { + TS_LOGE("JSON encoder: %s remaining elements but no map or array", func); + ret = -ENOMEM; + } + + TS_LOGD("JSON encoder: %s advance by %u", func, len); + return ret; +} + +int ts_msg_json_dec_update(struct thingset_msg *msg, const char *func, const char *start, + uint16_t len) +{ + TS_MSG_JSON_DEC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + scratchpad->token_idx++; + if (scratchpad->token_idx < ts_jsmn_token_count(&scratchpad->jsmn)) { + /* There is a next token - advance message only to next token JSON text start */ + TS_ASSERT((ts_jsmn_token_start(&scratchpad->jsmn, scratchpad->token_idx) + >= (const char *)ts_msg_data(msg)), + "JSON decoder: %s expects token start increment", func); + len = ts_jsmn_token_start(&scratchpad->jsmn, scratchpad->token_idx) + - (const char *)ts_msg_data(msg); + } + else { + /* Account for prefix white space that JSMN already removed */ + len += start - (const char *)ts_msg_data(msg); + } + ts_msg_pull(msg, len); + TS_LOGD("JSON decoder: %s advance by %u", func, len); + return 0; +} + +int ts_msg_scratchpad_tinycbor_enc_update(struct thingset_msg *msg, const char *func, CborError error) +{ + if (error != CborNoError) { + const char *error_msg; + int ret; + switch ((int)error) { + case CborUnknownError: + error_msg = "unknown"; + ret = -EFAULT; + break; + case CborErrorTooManyItems: + error_msg = "too may items"; + ret = -E2BIG; + break; + case CborErrorTooFewItems: + error_msg = "too few items"; + ret = -EINVAL; + break; + case CborErrorOutOfMemory: + error_msg = "out of memory"; + ret = -ENOMEM; + break; + case CborErrorInternalError: + error_msg = "internal"; + ret = -ENOTSUP; + break; + default: + error_msg = "unexpected"; + ret = -EFAULT; + break; + } + TS_LOGE("CBOR encoder: %s fails on %s (%s)", func, error_msg, cbor_error_string(error)); + return ret; + } + struct CborEncoder *encoder = ts_msg_scratchpad_tinycbor_enc_current(msg); + uint16_t advance = cbor_encoder_get_buffer_size(encoder, ts_msg_tail(msg)); + (void)ts_msg_add(msg, advance); + TS_LOGD("CBOR encoder: %s advance by %u", func, advance); + return 0; +} + +int ts_msg_scratchpad_tinycbor_dec_update(struct thingset_msg *msg, bool advance_value, + const char *func, CborError error) +{ + struct CborValue *it = ts_msg_scratchpad_tinycbor_dec_current(msg); + + if (error != CborNoError) { + TS_LOGE("CBOR decoder: %s faulting (%s)", func, cbor_error_string(error)); + return -EINVAL; + } + if (advance_value) { + if (ts_msg_len(msg) == 0) { + TS_LOGD("CBOR decoder: %s advance to next value after end of message", func); + return -ENOMEM; + } + error = cbor_value_advance(it); + if (error != CborNoError) { + TS_LOGE("CBOR decoder: %s faulting on advance to next value (%s)", func, + cbor_error_string(error)); + return -ENOMEM; + } + } + uint16_t advance = cbor_value_get_next_byte(it) - ts_msg_data(msg); + (void)ts_msg_pull(msg, advance); + if (ts_msg_len(msg) == 0) { + TS_LOGD("CBOR decoder: %s advanced by %u to message end", func, advance); + } + else if (ts_msg_scratchpad_tinycbor_dec_is_top(msg)) { + /* reinit parser to next value - tinycbor handles top level different from container */ + TS_LOGD("CBOR decoder: %s advance top level by %u", func, advance); + TS_MSG_CBOR_DEC_SCRATCHPAD_PTR_INIT(decoder, msg); + error = cbor_parser_init(ts_msg_data(msg), ts_msg_len(msg), 0, + &decoder->parser, &decoder->value[0]); + if (error != CborNoError) { + TS_LOGE("CBOR decoder: %s faulting on top level advance to next value (%s)", func, + cbor_error_string(error)); + return -ENOMEM; + } + } + else { + TS_LOGD("CBOR decoder: %s advance sub level by %u", func, advance); + } + return 0; +} + +int ts_msg_json_enc_setup(struct thingset_msg *msg) +{ + if (ts_buf_tailroom((struct ts_buf *)msg) < sizeof(struct ts_msg_json_enc_scratchroom)) { + TS_LOGE("JSON encoder: %s faulting - msg tailroom less than minimum (%u)", __func__, + (unsigned int)ts_buf_tailroom((struct ts_buf *)msg)); + return -ENOMEM; + } + + TS_MSG_BUF_SCRATCHPAD_PTR_INIT(scratchpad_std, msg); + scratchpad_std->scratchtype.id = TS_MSG_SCRATCHPAD_JSON_ENC; + scratchpad_std->scratchroom.size = sizeof(struct ts_msg_json_enc_scratchroom); + + TS_MSG_JSON_ENC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + scratchpad->current = 0; + + scratchpad->instance[0].flags.array = 0; + scratchpad->instance[0].flags.array_unbounded = 0; + scratchpad->instance[0].flags.map = 0; + scratchpad->instance[0].flags.map_unbounded = 0; + scratchpad->instance[0].flags.key = 0; + scratchpad->instance[0].remaining = 0; + + return 0; +} + +int ts_msg_json_dec_setup(struct thingset_msg *msg) +{ + if (ts_buf_tailroom((struct ts_buf *)msg) < sizeof(struct ts_msg_json_dec_scratchroom)) { + /* Minimum size not available - assure no negative size calculation later on */ + TS_LOGE("JSON decoder: %s faulting on message 0x%" PRIXPTR + " - msg tailroom less than minimum (%u)", __func__, (uintptr_t)msg, + (unsigned int)ts_buf_tailroom((struct ts_buf *)msg)); + return -ENOMEM; + } + + int ret; + TS_MSG_BUF_SCRATCHPAD_PTR_INIT(scratchpad_std, msg); + scratchpad_std->scratchtype.id = TS_MSG_SCRATCHPAD_JSON_DEC; + /* + * Assure alignment of 4 bytes of jsmn context + * and at least one remaining tailroom byte for `\0`. + * + * Account for offset of JSMN context within JSMN scratchroom. + */ + size_t offset = offsetof(struct ts_msg_json_dec_scratchroom, jsmn); + uint16_t align = (uintptr_t)(ts_buf_tail((struct ts_buf *)msg) + offset) & 0x03U; + if (align > 0) { + align = 4 - align; + } + scratchpad_std->scratchroom.size = ts_buf_tailroom((struct ts_buf *)msg) - align; + + if (scratchpad_std->scratchroom.size + < (sizeof(struct ts_msg_json_dec_scratchroom) + 16 * sizeof(struct ts_jsmn_token))) { + /* We need a minimum of 16 token space. */ + TS_LOGE("JSON decoder: %s faulting on message 0x%" PRIXPTR + " - msg tailroom does not provide for 16 tokens (%u)", __func__, (uintptr_t)msg, + (unsigned int)(((size_t)scratchpad_std->scratchroom.size - + sizeof(struct ts_msg_json_dec_scratchroom)) / sizeof(struct ts_jsmn_token))); + ret = -ENOMEM; + } + else { + TS_MSG_JSON_DEC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + uint16_t num_tokens = (scratchpad_std->scratchroom.size - sizeof(struct ts_msg_json_dec_scratchroom)) + / sizeof(struct ts_jsmn_token); + ret = ts_jsmn_init(&scratchpad->jsmn, num_tokens); + if (ret == 0) { + /* Init pull index */ + scratchpad->token_idx = 0; + /* Assure JSON data string ends with a '\0' for std string functions */ + if (*ts_msg_tail(msg) != 0) { + TS_ASSERT(ts_msg_tailroom(msg) > 0, "wrong alignment of JSMN scratchpad"); + *ts_msg_tail(msg) = 0; + } + /* Parse whole JSON string */ + ret = ts_jsmn_parse(&scratchpad->jsmn, ts_msg_data(msg), ts_msg_len(msg)); + if ((ret == 0) && (ts_jsmn_token_count(&scratchpad->jsmn) == 0)) { + TS_LOGD("JSON decoder: %s faulting - no JSON data in message", __func__); + ret = -ENODATA; + } + } + } + if (ret != 0) { + scratchpad_std->scratchroom.size = 0; + scratchpad_std->scratchtype.id = TS_MSG_SCRATCHPAD_STD; + } + + return ret; +} + +int ts_msg_cbor_enc_setup(struct thingset_msg *msg) +{ + if (ts_buf_tailroom((struct ts_buf *)msg) < sizeof(struct ts_msg_cbor_enc_scratchroom)) { + TS_LOGE("CBOR encoder: %s faulting - msg tailroom less than minimum (%u)", __func__, + (unsigned int)ts_buf_tailroom((struct ts_buf *)msg)); + return -ENOMEM; + } + + TS_MSG_BUF_SCRATCHPAD_PTR_INIT(scratchpad_std, msg); + scratchpad_std->scratchtype.id = TS_MSG_SCRATCHPAD_CBOR_ENC; + scratchpad_std->scratchroom.size = sizeof(struct ts_msg_cbor_enc_scratchroom); + + TS_MSG_CBOR_ENC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + scratchpad->current = 0; + cbor_encoder_init(&scratchpad->instance[0], ts_msg_tail(msg), ts_msg_tailroom(msg), 0); + + return 0; +} + +int ts_msg_cbor_dec_setup(struct thingset_msg *msg) +{ + struct ts_buf *buf = (struct ts_buf *)msg; + + if (ts_buf_tailroom(buf) < sizeof(struct ts_msg_cbor_dec_scratchroom)) { + TS_LOGE("CBOR decoder: %s faulting - msg tailroom less than minimum (%u)", __func__, + (unsigned int)ts_buf_tailroom(buf)); + return -ENOMEM; + } + + TS_MSG_BUF_SCRATCHPAD_PTR_INIT(scratchpad_std, msg); + + scratchpad_std->scratchroom.size = sizeof(struct ts_msg_cbor_dec_scratchroom); + scratchpad_std->scratchtype.id = TS_MSG_SCRATCHPAD_CBOR_DEC; + + TS_MSG_CBOR_DEC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + + CborError error = cbor_parser_init(ts_msg_data(msg), ts_msg_len(msg), 0, + &scratchpad->parser, &scratchpad->value[0]); + if (error == CborNoError) { + error = cbor_value_validate_basic(&scratchpad->value[0]); + } + if (error != CborNoError) { + scratchpad_std->scratchroom.size = 0; + scratchpad_std->scratchtype.id = TS_MSG_SCRATCHPAD_STD; + return -EINVAL; + } + scratchpad->current = 0; + return 0; +} + +int ts_msg_cobs_enc_setup(struct thingset_msg *msg) +{ + if (ts_msg_status_encoding(msg) != TS_MSG_ENCODING_NONE) { + TS_LOGE("COBS encoder: %s faulting - expecting uncoded message (got %u)", __func__, + (unsigned int)ts_msg_status_encoding(msg)); + return -EBADMSG; + } + + TS_MSG_BUF_SCRATCHPAD_PTR_INIT(scratchpad_std, msg); + /* Set buf[2] (aka. size) to 0 bytes - buf[1] may be used by encoder */ + scratchpad_std->scratchroom.size = 0; + scratchpad_std->scratchtype.id = TS_MSG_SCRATCHPAD_COBS_ENC; + + uint8_t *buf = ts_msg_data(msg); + + /* Add framing sentinel for encoding */ + TS_ASSERT(buf > &scratchpad_std->scratchroom.buf[1], + "COBS encoder: %s faulting - msg data start at 0x%" PRIxPTR + " overlaps scratchpad extra scratchroom buf[1] at 0x%" PRIxPTR, __func__, + (uintptr_t)buf, (uintptr_t)&scratchpad_std->scratchroom.buf[1]); + /* + * Need to work on raw buffer as we may extend the message into std scratchroom at start of + * message buffer using extra scratchroom buf[1]. + */ + buf = ts_buf_push((struct ts_buf *)msg, 1); + buf[0] = TS_COBS_INPLACE_SENTINEL_VALUE; + int ret = ts_msg_add_u8(msg, TS_COBS_INPLACE_SENTINEL_VALUE); + if (ret != 0) { + /* Revert starting sentinel */ + (void)ts_buf_pull((struct ts_buf *)msg, 1); + return ret; + } + + ret = ts_cobs_encode_inplace(ts_msg_len(msg), buf); + if (ret != 0) { + /* Revert starting & ending sentinel */ + (void)ts_buf_pull((struct ts_buf *)msg, 1); + (void)ts_msg_remove(msg, 1); + return ret; + } + + scratchpad_std->status.encoding = TS_MSG_ENCODING_COBS; + return 0; +} + +int ts_msg_cobs_dec_setup(struct thingset_msg *msg) +{ + int ret = 0; + + if (ts_msg_status_encoding(msg) != TS_MSG_ENCODING_COBS) { + TS_LOGE("COBS decoder: %s faulting - expecting COBS encoded message (got %u)", __func__, + (unsigned int)ts_msg_status_encoding(msg)); + return -EBADMSG; + } + + TS_MSG_BUF_SCRATCHPAD_PTR_INIT(scratchpad_std, msg); + + uint8_t *buf = ts_msg_data(msg); + if (scratchpad_std->scratchtype.id == TS_MSG_SCRATCHPAD_COBS_ENC) { + /* Extra scratchroom buf[1] may be used for first byte of encoded message */ + if (buf < &scratchpad_std->scratchroom.buf[1]) { + TS_LOGE("COBS decoder: %s faulting - msg data start at 0x%" PRIxPTR + " exceeds scratchpad extra scratchroom buf[1] at 0x%" PRIxPTR, __func__, + (uintptr_t)buf, (uintptr_t)&scratchpad_std->scratchroom.buf[1]); + return -EBADMSG; + } + } + + scratchpad_std->scratchtype.id = TS_MSG_SCRATCHPAD_COBS_DEC; + + uint16_t len = ts_msg_len(msg); + + if (buf[len - 1] != 0) { + ret = ts_msg_add_u8(msg, 0); + if (ret != 0) { + return ret; + } + len += 1; + } + + ret = ts_cobs_decode_inplace(len, buf); + if (ret == 0) { + /* Skip decoding sentinels at beginning and end of buffer */ + (void)ts_msg_pull(msg, 1); + (void)ts_msg_remove(msg, 1); + scratchpad_std->status.encoding = TS_MSG_ENCODING_NONE; + scratchpad_std->scratchroom.size = 0; + scratchpad_std->scratchtype.id = TS_MSG_SCRATCHPAD_STD; + } + + return ret; +} + +int ts_msg_proc_setup(struct thingset_msg *msg) +{ + struct ts_buf *buf = (struct ts_buf *)msg; + + if (ts_buf_tailroom(buf) < sizeof(struct ts_msg_proc_scratchroom)) { + TS_LOGE("MSG processing: %s faulting - msg tailroom less than minimum (%u)", __func__, + (unsigned int)ts_buf_tailroom(buf)); + return -ENOMEM; + } + + TS_MSG_BUF_SCRATCHPAD_PTR_INIT(scratchpad_std, msg); + + scratchpad_std->scratchroom.size = sizeof(struct ts_msg_proc_scratchroom); + scratchpad_std->scratchtype.id = TS_MSG_SCRATCHPAD_PROC; + + TS_MSG_PROC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + + scratchpad->port_src = THINGSET_PORT_ID_INVALID; + scratchpad->port_dest = THINGSET_PORT_ID_INVALID; + scratchpad->ctx_uid = 0; + scratchpad->import_export_subset = 0; + scratchpad->response_size_hint = 0; + + return 0; +} diff --git a/src/ts_msg_export.c b/src/ts_msg_export.c new file mode 100644 index 0000000..f075785 --- /dev/null +++ b/src/ts_msg_export.c @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * ThingSet message export and import support + * ------------------------------------------ + */ + +#include "ts_msg.h" +#include "ts_obj.h" +#include "ts_ctx.h" + +int ts_msg_add_export_cbor(struct thingset_msg* msg, ts_obj_db_id_t did, uint16_t subsets) +{ + /* mark export message */ + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_REQUEST, + TS_MSG_CODE_EXPORT); + int ret = ts_msg_add_response_status_cbor(msg); + if (ret != 0) { + return ret; + } + + // find out number of elements to be serialized + int num_elements = 0; + thingset_oref_t oref = { .db_id = did }; + for (oref.db_oid = 0; ts_obj_db_oref_is_valid(oref); oref.db_oid++) { + if (ts_obj_subsets(oref) & subsets) { + num_elements++; + } + } + + ret = ts_msg_add_map_cbor(msg, num_elements); + if (ret != 0) { + return ret; + } + + for (oref.db_oid = 0; ts_obj_db_oref_is_valid(oref) && (ret == 0); oref.db_oid++) { + if (ts_obj_subsets(oref) & subsets) { + ret = ts_msg_add_u16_cbor(msg, ts_obj_id(oref)); + if (ret != 0) { + break; + } + ret = ts_msg_add_object_cbor(msg, oref); + } + } + + if (ret == 0) { + ret = ts_msg_add_map_end_cbor(msg); + } + + return ret; +} + +int ts_msg_add_export_json(struct thingset_msg* msg, ts_obj_db_id_t did, uint16_t subsets) +{ + /* mark export message */ + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_REQUEST, + TS_MSG_CODE_EXPORT); + int ret = ts_msg_add_response_status_json(msg); + if (ret != 0) { + return ret; + } + + ret = ts_msg_add_u8(msg, ' '); + if (ret != 0) { + return ret; + } + + // find out number of elements to be serialized + int num_elements = 0; + thingset_oref_t oref = { .db_id = did }; + for (oref.db_oid = 0; ts_obj_db_oref_is_valid(oref); oref.db_oid++) { + if (ts_obj_subsets(oref) & subsets) { + num_elements++; + } + } + + ret = ts_msg_add_map_json(msg, num_elements); + if (ret != 0) { + return ret; + } + + for (oref.db_oid = 0; ts_obj_db_oref_is_valid(oref) && (ret == 0); oref.db_oid++) { + if (ts_obj_subsets(oref) & subsets) { + ret = ts_msg_add_string_json(msg, ts_obj_name(oref)); + if (ret != 0) { + break; + } + ret = ts_msg_add_object_json(msg, oref); + } + } + + if (ret == 0) { + ret = ts_msg_add_map_end_json(msg); + } + + return ret; +} + +int ts_msg_pull_export(struct thingset_msg* msg, ts_obj_db_id_t did) +{ + int ret; + + uint8_t method_id = ts_msg_data(msg)[0]; + if (method_id == ':') { + /* JSON */ + ts_msg_pull(msg, 1); + ts_msg_status_code_t code; + const char *description; + uint16_t len; + ret = ts_msg_pull_status(msg, &code, &description, &len); + if ((ret == 0) && (code != TS_MSG_CODE_EXPORT)) { + ret = -EINVAL; + } + if (ret == 0) { + ret = ts_msg_json_dec_setup(msg); + } + } + else if (method_id != TS_MSG_CODE_EXPORT) { + ret = -EINVAL; + } + else { + /* CBOR */ + ts_msg_pull(msg, 1); + ret = ts_msg_cbor_dec_setup(msg); + } + if (ret != 0) { + return ret; + } + + struct thingset_msg *response = NULL; + thingset_oref_t oref = { .db_id = did }; + if (method_id == TS_MSG_CODE_EXPORT) { + /* CBOR */ + ret = ts_ctx_process_patch_cbor(msg, oref, &response); + } + else { + /* JSON */ + ret = ts_ctx_process_patch_json(msg, oref, &response); + } + + if (response != NULL) { + thingset_msg_unref(response); + } + return ret; +} + +int ts_msg_load(struct thingset_msg *msg, const uint8_t *buf, uint16_t buf_len) +{ + thingset_msg_reset(msg); + + return ts_msg_add_mem(msg, buf, buf_len); +} + +int ts_msg_save(struct thingset_msg *msg, uint8_t *buf, uint16_t *buf_len) +{ + if (*buf_len < ts_msg_len(msg)) { + return -ENOMEM; + } + *buf_len = ts_msg_len(msg); + (void)memcpy(buf, ts_msg_data(msg), *buf_len); + return 0; +} diff --git a/src/ts_msg_log.c b/src/ts_msg_log.c new file mode 100644 index 0000000..569ce40 --- /dev/null +++ b/src/ts_msg_log.c @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Logging support + * --------------- + */ + +#include "thingset_env.h" + +#include +#include +#include +#include +#include + +#include "ts_msg.h" +#include "ts_obj.h" + + +struct ts_msg_log_cbor_token { + char *log; + size_t len; +}; + +/** + * @brief CborStreamFunction for output to log buffer. + */ +static CborError ts_msg_log_cbor(void *token, const char *fmt, ...) +{ + struct ts_msg_log_cbor_token *tok = (struct ts_msg_log_cbor_token *)token; + + va_list args; + va_start(args, fmt); + size_t len = vsnprintf(tok->log, tok->len, fmt, args); + va_end(args); + + tok->log = &tok->log[len]; + tok->len -= len; + + return CborNoError; +} + +int ts_msg_log(struct thingset_msg* msg, char *log, size_t len) +{ + int ret = 0; + size_t log_pos = 0; + + /* Report actual buffer positions */ + log_pos += snprintf(&log[log_pos], len - log_pos, "%.4u,%.4u,%.4u,", + (unsigned int)ts_msg_headroom(msg), (unsigned int)ts_msg_len(msg), + (unsigned int)ts_msg_tailroom(msg)); + + /* Report authorisation rights */ + log_pos += snprintf(&log[log_pos], len - log_pos, "h'%.4x',", (unsigned int)ts_msg_auth(msg)); + + /* remember actual message buffer position */ + struct ts_msg_state msg_state; + ts_msg_state_save(msg, &msg_state); + + /* rewind to start of message */ + (void)ts_msg_push(msg, ts_msg_headroom(msg)); + + /* Get command */ + uint8_t command_val; + ret = ts_msg_pull_u8(msg, &command_val); + if (ret != 0) { + log_pos += snprintf(&log[log_pos], len - log_pos, "error(\"initial command missing.\")"); + goto end; + } + const char *command = "simple"; + bool proto_bin = false; + bool proto_response = false; + switch (command_val) { + case TS_MSG_CODE_BIN_GET: + command = "bin-get"; + proto_bin = true; + break; + case TS_MSG_CODE_BIN_POST: + command = "bin-get"; + proto_bin = true; + break; + case TS_MSG_CODE_BIN_DELETE: + command = "bin-delete"; + proto_bin = true; + break; + case TS_MSG_CODE_BIN_FETCH: + command = "bin-fetch"; + proto_bin = true; + break; + case TS_MSG_CODE_BIN_PATCH: + command = "bin-ipatch"; + proto_bin = true; + break; + case TS_MSG_CODE_BIN_STATEMENT: + command = "bin-statement"; + proto_bin = true; + break; + case TS_MSG_CODE_TXT_GET: + command = "txt-get"; + break; + case TS_MSG_CODE_TXT_PATCH: + command = "txt-patch"; + break; + case TS_MSG_CODE_TXT_CREATE: + command = "txt-create"; + break; + case TS_MSG_CODE_TXT_DELETE: + command = "txt-delete"; + break; + case TS_MSG_CODE_TXT_EXEC: + command = "txt-exec"; + break; + case TS_MSG_CODE_TXT_STATEMENT: + command = "txt-statement"; + break; + case TS_MSG_CODE_TXT_RESPONSE: + command = "txt-reponse"; + proto_response = true; + break; + default: + if (command_val >= 0x80U) { + command = "bin-reponse"; + proto_bin = true; + proto_response = true; + } + else { + log_pos += snprintf(&log[log_pos], len - log_pos, "ERROR: invalid command (0x%.2x).", + (unsigned int)command_val); + goto end; + } + break; + } + if (proto_response && !proto_bin) { + /* txt response */ + log_pos += snprintf(&log[log_pos], len - log_pos, "%s(\"%c\"),", command, command_val); + ts_msg_status_code_t code; + const char *desc; + uint16_t desc_len; + ret = ts_msg_pull_status(msg, &code, &desc, &desc_len); + if (ret != 0) { + log_pos += snprintf(&log[log_pos], len - log_pos, "ERROR: invalid/ missing status."); + goto end; + } + log_pos += snprintf(&log[log_pos], len - log_pos, "h'%.2x',\"%.*s\"", code, + desc_len, desc); + } + else if (proto_bin) { + /* bin request and bin response */ + log_pos += snprintf(&log[log_pos], len - log_pos, "%s(h'%.2x')", command, + (unsigned int)command_val); + } + else { + /* txt request */ + const char *path; + uint16_t path_len; + ret = ts_msg_pull_path(msg, &path, &path_len); + if (ret != 0) { + log_pos += snprintf(&log[log_pos], len - log_pos, "ERROR: path missing."); + goto end; + } + if ((command_val == '?') && (ts_msg_len(msg) > 0)) { + /* Some data is following */ + command = "txt-fetch"; + } + log_pos += snprintf(&log[log_pos], len - log_pos, "%s(\"%c\"),\"%.*s\"", + command, command_val, + path_len, path != NULL ? path : ""); + } + + if (proto_bin) { + /* Cbor message type */ + CborError error; + struct ts_msg_log_cbor_token token; + CborValue it; + CborParser parser; + CborType type; + const uint8_t *next_byte; + + error = cbor_parser_init(ts_msg_data(msg), ts_msg_len(msg), 0, &parser, &it); + if (!cbor_value_at_end(&it)) { + log_pos += snprintf(&log[log_pos], len - log_pos, ","); + } + while (!cbor_value_at_end(&it) && (log_pos < len) && (error == CborNoError)) { + token.log = &log[log_pos]; + token.len = len - log_pos; + type = cbor_value_get_type(&it); + error = cbor_value_to_pretty_stream(ts_msg_log_cbor, (void *)&token, &it, + CborPrettyDefaultFlags); + if (error == CborErrorUnknownType) { + TS_LOGE("CBOR: unknown type: %u (major type: %u, 5bit value: %u)", + (unsigned int)type, + (unsigned int)(it.source.ptr[0] >> 5), + (unsigned int)(it.source.ptr[0] & 0x01FU)); + } + next_byte = cbor_value_get_next_byte(&it); + if (next_byte < ts_msg_tail(msg)) { + error = cbor_parser_init(next_byte, next_byte - ts_msg_tail(msg), 0, &parser, &it); + } + log_pos = token.log - log; + if (log_pos >= len) { + goto end; + } + log[log_pos] = ','; + log_pos++; + } + } + else { + /* JSON message type */ + if (ts_msg_len(msg) > 1) { + log_pos += snprintf(&log[log_pos], len - log_pos, ",%.*s", + ts_msg_len(msg) - 1, ts_msg_data(msg) + 1); + } + } +end: + /* terminate log buffer */ + if (log_pos >= len) { + log_pos = len - 1; + } + else if (log[log_pos - 1] == ',') { + log_pos -= 1; + } + log[log_pos] = '\0'; + + /* wind buffer to saved position */ + ts_msg_state_restore(msg, &msg_state); + + return log_pos; +} diff --git a/src/ts_msg_proto.c b/src/ts_msg_proto.c new file mode 100644 index 0000000..cc1892d --- /dev/null +++ b/src/ts_msg_proto.c @@ -0,0 +1,2310 @@ +/* + * Copyright (c) 2017 Martin Jäger / Libre Solar + * Copyright (c) 2021..2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * ThingSet Protocol + * ----------------- + */ + +#include "thingset_env.h" + +#include +#include +#include +#include + +#include "ts_msg.h" + +int ts_msg_add_status(struct thingset_msg *msg, ts_msg_status_code_t code) +{ + char hex[2]; + + hex[0] = code >> 4; + hex[1] = code & 0x0F; + for (int i = 0; i < 2; i++) { + if (hex[i] <= 9) { + hex[i] += '0'; + } + else { + hex[i] += 'A' - 10; + } + } + + return ts_msg_add_mem(msg, (const uint8_t *)&hex[0], 2); +} + +static int ts_msg_add_object_array_cbor(struct thingset_msg *msg, + const struct ts_array_info *array_info, int16_t detail) +{ + if (!array_info) { + return 0; + } + + // Add the length field to the beginning of the CBOR buffer and update the CBOR buffer index + int ret = ts_msg_add_array_cbor(msg, array_info->num_elements); + if (ret != 0) { + return ret; + } + + for (int i = 0; (i < array_info->num_elements) && (ret == 0); i++) { + switch (array_info->type) { + case TS_T_UINT64: + ret = ts_msg_add_u64_cbor(msg, ((uint64_t *)array_info->ptr)[i]); + break; + case TS_T_INT64: + ret = ts_msg_add_i64_cbor(msg, ((int64_t *)array_info->ptr)[i]); + break; + case TS_T_UINT32: + ret = ts_msg_add_u32_cbor(msg, ((uint32_t *)array_info->ptr)[i]); + break; + case TS_T_INT32: + ret = ts_msg_add_i32_cbor(msg, ((int32_t *)array_info->ptr)[i]); + break; + case TS_T_UINT16: + ret = ts_msg_add_u16_cbor(msg, ((uint16_t *)array_info->ptr)[i]); + break; + case TS_T_INT16: + ret = ts_msg_add_i16_cbor(msg, ((int16_t *)array_info->ptr)[i]); + break; + case TS_T_FLOAT32: + ret = ts_msg_add_f32_cbor(msg, ((float *)array_info->ptr)[i], detail); + break; + case TS_T_BOOL: + ret = ts_msg_add_bool_cbor(msg, ((bool *)array_info->ptr)[i]); + break; + default: + /* should not happen */ + TS_LOGD("CBOR encoder: %s - unexpected type (%u)", __func__, + (unsigned int)array_info->type); + break; + } + } + int ret_close = ts_msg_add_array_end_cbor(msg); + if (ret_close != 0) { + return ret_close; + } + return ret; +} + +int ts_msg_add_object_cbor(struct thingset_msg *msg, thingset_oref_t oref) +{ + TS_ASSERT(ts_obj_db_oref_is_object(oref), "MSG: %s on invalid object reference (%u:%u)", + __func__, (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + + int ret = 0; +#if 0 + int objects_found; +#endif + + switch (ts_obj_type(oref)) { + case TS_T_UINT64: + ret = ts_msg_add_u64_cbor(msg, *ts_obj_u64_data(oref)); + break; + case TS_T_INT64: + ret = ts_msg_add_i64_cbor(msg, *ts_obj_i64_data(oref)); + break; + case TS_T_UINT32: + ret = ts_msg_add_u32_cbor(msg, *ts_obj_u32_data(oref)); + break; + case TS_T_INT32: + ret = ts_msg_add_i32_cbor(msg, *ts_obj_i32_data(oref)); + break; + case TS_T_UINT16: + ret = ts_msg_add_u16_cbor(msg, *ts_obj_u16_data(oref)); + break; + case TS_T_INT16: + ret = ts_msg_add_i16_cbor(msg, *ts_obj_i16_data(oref)); + break; + case TS_T_FLOAT32: + ret = ts_msg_add_f32_cbor(msg, *ts_obj_f32_data(oref), ts_obj_detail(oref)); + break; + case TS_T_DECFRAC: + ret = ts_msg_add_decfrac_cbor(msg, *ts_obj_decfrac_mantissa_data(oref), + ts_obj_decfrac_exponent_data(oref)); + break; + case TS_T_BOOL: + ret = ts_msg_add_bool_cbor(msg, *ts_obj_bool_data(oref)); + break; +#if 0 + case TS_T_EXEC: + ret = ts_msg_add_string_json(msg, "["); + objects_found = 0; + for (unsigned int i = 0; (i < db->num) && (ret == 0); i++) { + if (db->objects[i].parent == object->id) { + objects_found++; + ret = ts_msg_add_string_json(msg, db->objects[i].name); + if (ret != 0) { + break; + } + ret = ts_msg_add_string_json(msg, ","); + } + } + if (ret != 0) { + break; + } + /* Remove trailing [ ... ',' or unnecessary '[' */ + ts_buf_remove(msg, 1); + if (objects_found) { + ret = ts_msg_add_u8(msg, ']'); + } + else { + ret = ts_msg_add_mem(msg, (const uint8_t *)&"null", 4); + } + break; +#endif + case TS_T_STRING: + ret = ts_msg_add_string_cbor(msg, ts_obj_string_data(oref)); + break; + case TS_T_BYTES: + ret = ts_msg_add_mem_cbor(msg, ts_obj_mem_data(oref), *ts_obj_mem_len(oref)); + break; +#if 0 + case TS_T_SUBSET: + ret = ts_msg_add_u8(msg, '['); + objects_found = 0; + for (unsigned int i = 0; (i < db->num) && (ret == 0); i++) { + if (db->flags[i].subsets & (uint16_t)object->detail) { + objects_found++; + ret = ts_msg_add_string_json(msg, db->objects[i].name); + if (ret != 0) { + break; + } + ret = ts_msg_add_u8(msg, ','); + } + } + if (ret != 0) { + break; + } + if (objects_found) { + /* Remove trailing [ ... ',' */ + ts_buf_remove(msg, 1); + } + ret = ts_msg_add_u8(msg, ']'); + break; +#endif + case TS_T_ARRAY: + ret = ts_msg_add_object_array_cbor(msg, ts_obj_array_data(oref), ts_obj_detail(oref)); + break; + default: + TS_LOGD("CBOR encoder: %s - unexpected type (%u)", __func__, + (unsigned int)ts_obj_type(oref)); + break; + } + return ret; +} + +int ts_msg_add_object_json(struct thingset_msg *msg, thingset_oref_t oref) +{ + TS_ASSERT(ts_obj_db_oref_is_object(oref), "MSG: %s on invalid object reference (%u:%u)", + __func__, (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + + int ret = 0; + + switch (ts_obj_type(oref)) { +#if TS_CONFIG_64BIT_TYPES_SUPPORT + case TS_T_UINT64: + ret = ts_msg_add_u64_json(msg, *ts_obj_u64_data(oref)); + break; + case TS_T_INT64: + ret = ts_msg_add_i64_json(msg, *ts_obj_i64_data(oref)); + break; +#endif + case TS_T_UINT32: + ret = ts_msg_add_u32_json(msg, *ts_obj_u32_data(oref)); + break; + case TS_T_INT32: + ret = ts_msg_add_i32_json(msg, *ts_obj_i32_data(oref)); + break; + case TS_T_UINT16: + ret = ts_msg_add_u16_json(msg, *ts_obj_u16_data(oref)); + break; + case TS_T_INT16: + ret = ts_msg_add_i16_json(msg, *ts_obj_i16_data(oref)); + break; + case TS_T_FLOAT32: + ret = ts_msg_add_f32_json(msg, *ts_obj_f32_data(oref), ts_obj_detail(oref)); + break; +#if TS_CONFIG_DECFRAC_TYPE_SUPPORT + case TS_T_DECFRAC: + ret = ts_msg_add_decfrac_json(msg, *ts_obj_decfrac_mantissa_data(oref), + ts_obj_decfrac_exponent_data(oref)); + break; +#endif + case TS_T_BOOL: + ret = ts_msg_add_bool_json(msg, *ts_obj_bool_data(oref)); + break; + case TS_T_EXEC: + { + ts_obj_id_t parent_id = ts_obj_id(oref); + ret = ts_msg_add_array_json(msg, 0); + if (ret != 0) { + break; + } + TS_OBJ_DB_FOREACH_OREF(ts_obj_db_oref(oref.db_id, 0), child_oref) { + if (ts_obj_parent_id(child_oref) == parent_id) { + ret = ts_msg_add_string_json(msg, ts_obj_name(child_oref)); + if (ret != 0) { + break; + } + } + } + int ret_end = ts_msg_add_array_end_json(msg); + if (ret == 0) { + ret = ret_end; + } + if (ret != 0) { + break; + } + if (ts_msg_tail(msg)[-2] == '[') { + /* This is an empty array - remove and add 'null' */ + ts_msg_remove(msg, 2); + ret = ts_msg_add_mem(msg, (const uint8_t *)&"null", 4); + } + } + break; + case TS_T_STRING: + ret = ts_msg_add_string_json(msg, ts_obj_string_data(oref)); + break; + case TS_T_GROUP: + { + ts_obj_id_t parent_id = ts_obj_id(oref); + ret = ts_msg_add_array_json(msg, 0); + if (ret != 0) { + break; + } + TS_OBJ_DB_FOREACH_OREF(ts_obj_db_oref(oref.db_id, 0), child_oref) { + if (ts_obj_parent_id(child_oref) == parent_id) { + ret = ts_msg_add_string_json(msg, ts_obj_name(child_oref)); + if (ret != 0) { + break; + } + } + } + int ret_end = ts_msg_add_array_end_json(msg); + if (ret == 0) { + ret = ret_end; + } + } + break; + case TS_T_SUBSET: + { + uint16_t subsets = (uint16_t)ts_obj_detail(oref); + ret = ts_msg_add_array_json(msg, 0); + if (ret != 0) { + break; + } + TS_OBJ_DB_FOREACH_OREF(ts_obj_db_oref(oref.db_id, 0), child_oref) { + if (ts_obj_subsets(child_oref) & subsets) { + ret = ts_msg_add_string_json(msg, ts_obj_name(child_oref)); + if (ret != 0) { + break; + } + } + } + int ret_end = ts_msg_add_array_end_json(msg); + if (ret == 0) { + ret = ret_end; + } + } + break; + case TS_T_ARRAY: + { + struct ts_array_info *array_info = ts_obj_array_data(oref); + if (!array_info) { + break; + } + ret = ts_msg_add_array_json(msg, array_info->num_elements); + for (int i = 0; (i < array_info->num_elements) && (ret == 0); i++) { + switch (array_info->type) { + case TS_T_UINT64: + ret = ts_msg_add_u64_json(msg, ((uint64_t *)array_info->ptr)[i]); + break; + case TS_T_INT64: + ret = ts_msg_add_i64_json(msg, ((int64_t *)array_info->ptr)[i]); + break; + case TS_T_UINT32: + ret = ts_msg_add_u32_json(msg, ((uint32_t *)array_info->ptr)[i]); + break; + case TS_T_INT32: + ret = ts_msg_add_i32_json(msg, ((int32_t *)array_info->ptr)[i]); + break; + case TS_T_UINT16: + ret = ts_msg_add_u16_json(msg, ((uint16_t *)array_info->ptr)[i]); + break; + case TS_T_INT16: + ret = ts_msg_add_i16_json(msg, ((int16_t *)array_info->ptr)[i]); + break; + case TS_T_FLOAT32: + ret = ts_msg_add_f32_json(msg, ((float *)array_info->ptr)[i], + ts_obj_detail(oref)); + break; + default: + /* should not happen */ + TS_ASSERT(false, "Unexpected ThingSet array element type (%u)", + (unsigned int)array_info->type); + break; + } + } + int ret_end = ts_msg_add_array_end_json(msg); + if (ret == 0) { + ret = ret_end; + } + } + break; + default: + TS_LOGD("JSON encoder: %s got unexpected object type %u - object: %s", __func__, + (unsigned int)ts_obj_type(oref), ts_obj_name(oref)); + break; + } + return ret; +} + +int ts_msg_add_request_get_cbor(struct thingset_msg *msg, ts_obj_id_t object_id, const char *path) +{ + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_REQUEST, 0); + + int ret = ts_msg_add_u8(msg, TS_GET); + if (ret != 0) { + return ret; + } + + ret = ts_msg_cbor_enc_setup(msg); + if (ret != 0) { + return ret; + } + + if (path != NULL) { + ret = ts_msg_add_string_cbor(msg, path); + } + else { + ret = ts_msg_add_u16_cbor(msg, object_id); + } + return ret; +} + +int ts_msg_add_request_get_json(struct thingset_msg *msg, const char *path) +{ + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_REQUEST, 0); + + int ret = ts_msg_add_u8(msg, '?'); + if (ret != 0) { + return ret; + } + + return ts_msg_add_mem(msg, path, strlen(path)); +} + +int ts_msg_add_response_get_cbor(struct thingset_msg *msg, thingset_oref_t oref, + struct thingset_msg* request) +{ + TS_ASSERT(ts_obj_db_oref_is_valid(oref), + "MSG CBOR: %s on invalid object reference (%u:%u)", + __func__, (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + + int ret; + + /* initialize response with success message */ + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_CONTENT); + ret = ts_msg_add_response_status_cbor(msg); + if (ret != 0) { + return ret; + } + + ts_obj_id_t parent_id; + if (ts_obj_db_oref_is_object(oref)) { + if (ts_obj_type(oref) != TS_T_GROUP && ts_obj_type(oref) != TS_T_EXEC) { + return ts_msg_add_object_cbor(msg, oref); + } + parent_id = ts_obj_id(oref); + } + else { + parent_id = TS_ID_ROOT; + } + + /* find out number of elements to be serialized */ + int num_elements = 0; + TS_OBJ_DB_FOREACH_OREF(ts_obj_db_oref(oref.db_id, 0), child_oref) { + if (ts_obj_access_read(child_oref, TS_READ_MASK) && + (ts_obj_parent_id(child_oref) == parent_id)) { + num_elements++; + } + } + + ts_msg_status_code_t request_code = ts_msg_status_code(request); + bool include_ids = (request_code == TS_MSG_CODE_REQUEST_GET_IDS) || + (request_code == TS_MSG_CODE_REQUEST_GET_IDS_VALUES); + bool include_names = (request_code == TS_MSG_CODE_REQUEST_GET_NAMES) || + (request_code == TS_MSG_CODE_REQUEST_GET_NAMES_VALUES); + bool include_values = (request_code == TS_MSG_CODE_REQUEST_GET_VALUES) || + (request_code == TS_MSG_CODE_REQUEST_GET_IDS_VALUES) || + (request_code == TS_MSG_CODE_REQUEST_GET_NAMES_VALUES); + + if (ts_obj_db_oref_is_object(oref) && ts_obj_type(oref) == TS_T_EXEC && include_values) { + // bad request, as we can't read exec object's values + thingset_msg_reset(msg); + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_BAD_REQUEST); + return ts_msg_add_response_status_cbor(msg); + } + + if ((include_ids || include_names) && include_values) { + ret = ts_msg_add_map_cbor(msg, num_elements); + } + else { + ret = ts_msg_add_array_cbor(msg, num_elements); + } + if (ret != 0) { + goto ts_msg_add_response_get_cbor_error; + } + + TS_OBJ_DB_FOREACH_OREF(ts_obj_db_oref(oref.db_id, 0), child_oref) { + if (ts_obj_access_read(child_oref, TS_READ_MASK) && + (ts_obj_parent_id(child_oref) == parent_id)) { + if (include_ids) { + ret = ts_msg_add_u16_cbor(msg, ts_obj_id(child_oref)); + if (ret != 0) { + goto ts_msg_add_response_get_cbor_error; + } + } + if (include_names) { + ret = ts_msg_add_string_cbor(msg, ts_obj_name(child_oref)); + if (ret != 0) { + goto ts_msg_add_response_get_cbor_error; + } + } + if (include_values) { + if (ts_obj_type(child_oref) == TS_T_GROUP) { + /* bad request, as we can't read internal path object's values */ + thingset_msg_reset(msg); + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_BAD_REQUEST); + return ts_msg_add_response_status_cbor(msg); + } + ret = ts_msg_add_object_cbor(msg, child_oref); + if (ret != 0) { + goto ts_msg_add_response_get_cbor_error; + } + } + } + } + + if ((include_ids || include_names) && include_values) { + ret = ts_msg_add_map_end_cbor(msg); + } + else { + ret = ts_msg_add_array_end_cbor(msg); + } + + if (ret != 0) { +ts_msg_add_response_get_cbor_error: +#if TS_CONFIG_LOG + char log_buf[200]; + ret = ts_msg_log(msg, &log_buf[0], sizeof(log_buf)); + TS_LOGD("MSG CBOR: %s on get request 0x%" PRIXPTR " creates too large response 0x%" PRIXPTR + " message: >%d<, >%.*s<", + __func__, (uintptr_t)request, (uintptr_t)msg, ts_msg_len(msg), ret, &log_buf[0]); +#endif + thingset_msg_reset(msg); + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_RESPONSE_TOO_LARGE); + return ts_msg_add_response_status_cbor(msg); + } + + return ret; +} + +int ts_msg_add_response_get_json(struct thingset_msg *msg, thingset_oref_t oref, + struct thingset_msg* request) +{ + TS_ASSERT(ts_obj_db_oref_is_valid(oref), + "MSG CBOR: %s on invalid object reference (%u:%u)", + __func__, (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + + int ret; + + /* initialize response with success message */ + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_CONTENT); + ret = ts_msg_add_response_status_json(msg); + if (ret != 0) { + return ret; + } + + ts_obj_id_t parent_id; + if (ts_obj_db_oref_is_object(oref)) { + if (ts_obj_type(oref) != TS_T_GROUP && ts_obj_type(oref) != TS_T_EXEC) { + // get value of data object + ret = ts_msg_add_u8(msg, ' '); + if (ret != 0) { + return ret; + } + return ts_msg_add_object_json(msg, oref); + } + parent_id = ts_obj_id(oref); + } + else { + parent_id = TS_ID_ROOT; + } + + ts_msg_status_code_t request_code = ts_msg_status_code(request); + bool include_names = (request_code == TS_MSG_CODE_REQUEST_GET_NAMES) || + (request_code == TS_MSG_CODE_REQUEST_GET_NAMES_VALUES); + bool include_values = (request_code == TS_MSG_CODE_REQUEST_GET_VALUES) || + (request_code == TS_MSG_CODE_REQUEST_GET_NAMES_VALUES); + TS_ASSERT(include_names || include_values, "At least one of names or values must be included"); + + if (ts_obj_db_oref_is_object(oref) && (ts_obj_type(oref) == TS_T_EXEC) && include_values) { + // bad request, as we can't read exec object's values + thingset_msg_reset(msg); + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_BAD_REQUEST); + return ts_msg_add_response_status_json(msg); + } + + ret = ts_msg_add_u8(msg, ' '); + if (ret != 0) { + return ret; + } + + if (include_names && include_values) { + ret = ts_msg_add_map_json(msg, 0); + } + else { + ret = ts_msg_add_array_json(msg, 0); + } + if (ret != 0) { + return ret; + } + + TS_OBJ_DB_FOREACH_OREF(ts_obj_db_oref(oref.db_id, 0), child_oref) { + if (ts_obj_access_read(child_oref, TS_READ_MASK) && + (ts_obj_parent_id(child_oref) == parent_id)) { + if (include_names) { + ret = ts_msg_add_string_json(msg, ts_obj_name(child_oref)); + if (ret != 0) { + break; + } + } + if (include_values) { + if (ts_obj_type(child_oref) == TS_T_GROUP) { + /* bad request, as we can't read internal path object's values */ + TS_LOGD("MSG JSON: %s on get request 0x%" PRIXPTR + " tries to get value of group type object '%s'", __func__, + (uintptr_t)request, ts_obj_name(child_oref)); + thingset_msg_reset(msg); + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_BAD_REQUEST); + return ts_msg_add_response_status_json(msg); + } + ret = ts_msg_add_object_json(msg, child_oref); + } + } + } + + if (ret == 0) { + if (include_names && include_values) { + ret = ts_msg_add_map_end_json(msg); + } + else { + ret = ts_msg_add_array_end_json(msg); + } + } + if (ret != 0) { +#if TS_CONFIG_LOG + char log_buf[200]; + ret = ts_msg_log(msg, &log_buf[0], sizeof(log_buf)); + TS_LOGD("MSG JSON: %s on get request 0x%" PRIXPTR " creates too large response 0x%" PRIXPTR + " message: >%d<, >%.*s<", + __func__, (uintptr_t)request, (uintptr_t)msg, ts_msg_len(msg), ret, &log_buf[0]); +#endif + thingset_msg_reset(msg); + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_RESPONSE_TOO_LARGE); + return ts_msg_add_response_status_json(msg); + } + + return 0; +} + +int ts_msg_add_request_fetch_cbor(struct thingset_msg *msg, ts_obj_id_t object_id, const char *path, + uint16_t object_count, ts_obj_id_t *object_ids, + const char **object_names) +{ + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_REQUEST, 0); + + int ret = ts_msg_add_u8(msg, TS_FETCH); + if (ret != 0) { + return ret; + } + + ret = ts_msg_cbor_enc_setup(msg); + if (ret != 0) { + return ret; + } + + if (path != NULL) { + ret = ts_msg_add_string_cbor(msg, path); + } + else { + ret = ts_msg_add_u16_cbor(msg, object_id); + } + if (ret != 0) { + return ret; + } + + if (object_count == 0) { + ret = ts_msg_add_undefined_cbor(msg); + } + else { + if (object_count > 1) { + ret = ts_msg_add_array_cbor(msg, object_count); + } + if (ret != 0) { + return ret; + } + if (object_names == NULL) { + for (uint16_t i = 0; i < object_count; i++) { + ret = ts_msg_add_u16_cbor(msg, object_ids[i]); + if (ret != 0) { + return ret; + } + } + } + else { + for (uint16_t i = 0; i < object_count; i++) { + ret = ts_msg_add_string_cbor(msg, object_names[i]); + if (ret != 0) { + return ret; + } + } + } + if (object_count > 1) { + ret = ts_msg_add_array_end_cbor(msg); + } + } + + return ret; +} + +int ts_msg_add_request_fetch_json(struct thingset_msg* msg, const char *path, + uint16_t object_count, const char **object_names) +{ + return -ENOTSUP; +} + +int ts_msg_add_response_fetch_cbor(struct thingset_msg *msg, thingset_oref_t oref, + struct thingset_msg *request) +{ + if (ts_msg_status_code(request) == TS_MSG_CODE_REQUEST_FETCH_NAMES) { + /* Add all names behind endpoint */ + ts_msg_status_code_set(request, TS_MSG_CODE_REQUEST_GET_NAMES); + return ts_msg_add_response_get_cbor(msg, oref, request); + } + if (ts_msg_status_code(request) == TS_MSG_CODE_REQUEST_FETCH_IDS) { + /* Add all ids behind endpoint */ + ts_msg_status_code_set(request, TS_MSG_CODE_REQUEST_GET_IDS); + return ts_msg_add_response_get_cbor(msg, oref, request); + } + if ((ts_msg_status_code(request) != TS_MSG_CODE_REQUEST_FETCH_VALUES) && + (ts_msg_status_code(request) != TS_MSG_CODE_REQUEST_FETCH_SINGLE)) { + /* Unexpected fetch request status - this should never happen */ + TS_ASSERT(true, "Unexpected fetch request status code (%02x)", + (unsigned int)ts_msg_status_code(request)); + goto ts_msg_add_response_fetch_cbor_bad_request; + } + + /* Add data defined by names/ids in request array */ + TS_ASSERT(ts_obj_db_oref_is_valid(oref), + "MSG CBOR: %s on invalid object reference (%u:%u)", + __func__, (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + + /* initialize response with success message */ + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_CONTENT); + int ret = ts_msg_add_response_status_cbor(msg); + if (ret != 0) { + return ret; + } + + uint16_t len; + if (ts_msg_status_code(request) == TS_MSG_CODE_REQUEST_FETCH_SINGLE) { + len = 1; + } + else { + ret = ts_msg_pull_array_cbor(request, &len); + if ((ret != 0) || (len == 0)) { + goto ts_msg_add_response_fetch_cbor_bad_request; + } + } + + if (len > 1) { + ret = ts_msg_add_array_cbor(msg, len); + if (ret != 0) { + return ret; + } + } + + uint16_t request_auth = ts_msg_auth(request); + for (uint16_t i = 0; (i < len) && (ret == 0); i++) { + thingset_oref_t child_oref; + CborType t; + ret = ts_msg_pull_type_cbor(request, &t); + if (ret != 0) { + goto ts_msg_add_response_fetch_cbor_bad_request; + } + if (t == CborIntegerType) { + ts_obj_id_t obj_id; + ret = ts_msg_pull_u16_cbor(request, &obj_id); + if (ret != 0) { + goto ts_msg_add_response_fetch_cbor_bad_request; + } + ret = ts_obj_db_oref_by_id(oref.db_id, obj_id, &child_oref); + if (ret != 0) { + goto ts_msg_add_response_fetch_cbor_not_found; + } + } + else if (t == CborTextStringType) { + const char *path; + uint16_t path_len; + ret = ts_msg_pull_string_cbor(request, &path, &path_len); + if (ret != 0) { + goto ts_msg_add_response_fetch_cbor_bad_request; + } + ret = ts_obj_by_name(oref, path, path_len, &child_oref); + if (ret != 0) { + goto ts_msg_add_response_fetch_cbor_not_found; + } + } + else { + goto ts_msg_add_response_fetch_cbor_bad_request; + } + + if (!ts_obj_access_read(child_oref, request_auth)) { + thingset_msg_reset(msg); + if (ts_obj_access_read(child_oref, TS_READ_MASK)) { + TS_LOGD("MSG: %s on fetch request 0x%" PRIXPTR + " not authorized: >0x%04x< - object: %s", + __func__, (uintptr_t)request, (unsigned int)request_auth, + ts_obj_name(child_oref)); + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_UNAUTHORIZED); + ret = ts_msg_add_response_status_cbor(msg); + } + else { + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_FORBIDDEN); + ret = ts_msg_add_response_status_cbor(msg); + } + return ret; + } + ret = ts_msg_add_object_cbor(msg, child_oref); + } + if (ret == 0) { + if (ts_msg_status_code(request) != TS_MSG_CODE_REQUEST_FETCH_SINGLE) { + ret = ts_msg_pull_array_end_cbor(request); + if (ret != 0) { + goto ts_msg_add_response_fetch_cbor_bad_request; + } + } + + if (len > 1) { + ret = ts_msg_add_array_end_cbor(msg); + } + } + + if (ret != 0) { +#if TS_CONFIG_LOG + char log_buf[200]; + ret = ts_msg_log(msg, &log_buf[0], sizeof(log_buf)); + TS_LOGD("MSG CBOR: %s on fetch request 0x%" PRIXPTR " creates too large response 0x%" PRIXPTR + " message: >%d<, >%.*s<", + __func__, (uintptr_t)request, (uintptr_t)msg, ts_msg_len(msg), ret, &log_buf[0]); +#endif + thingset_msg_reset(msg); + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_RESPONSE_TOO_LARGE); + return ts_msg_add_response_status_cbor(msg); + } + return 0; + +ts_msg_add_response_fetch_cbor_bad_request: + thingset_msg_reset(msg); + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_BAD_REQUEST); + return ts_msg_add_response_status_cbor(msg); + +ts_msg_add_response_fetch_cbor_not_found: + thingset_msg_reset(msg); + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_NOT_FOUND); + return ts_msg_add_response_status_cbor(msg); +} + +int ts_msg_add_response_fetch_json(struct thingset_msg *msg, thingset_oref_t oref, + struct thingset_msg *request) +{ + TS_ASSERT(ts_obj_db_oref_is_valid(oref), + "MSG JSON: %s on invalid object reference (%u:%u)", + __func__, (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + + /* initialize response with success message */ + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_CONTENT); + int ret = ts_msg_add_response_status_json(msg); + if (ret != 0) { + return ret; + } + + ret = ts_msg_json_dec_setup(request); + if (ret != 0) { + thingset_msg_reset(msg); + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_INTERNAL_SERVER_ERR); + return ts_msg_add_response_status_json(msg); + } + TS_MSG_JSON_DEC_SCRATCHPAD_PTR_INIT(request_scratchpad, request); + struct ts_jsmn_context *jsmn = &request_scratchpad->jsmn; + + ts_msg_add_u8(msg, ' '); + + uint16_t type, size, len; + const char *start; + uint8_t token_idx = 0; // current token + bool bracket = false; + thingset_oref_t child_oref; + uint16_t request_auth = ts_msg_auth(request); + while (ts_jsmn_token_by_index(jsmn, token_idx, &type, &size, &start, &len) == 0) { + if ((token_idx == 0) && (type == TS_JSMN_ARRAY)) { + bracket = true; + ts_msg_add_u8(msg, '['); + token_idx++; + continue; + } + + if (type != TS_JSMN_STRING) { + thingset_msg_reset(msg); + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_BAD_REQUEST); + return ts_msg_add_response_status_json(msg); + } + + ret = ts_obj_by_name(oref, start, len, &child_oref); + if (ret != 0) { + thingset_msg_reset(msg); + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_NOT_FOUND); + return ts_msg_add_response_status_json(msg); + } + if (ts_obj_type(child_oref) == TS_T_GROUP) { + /* bad request, as we can't read internal path object's values */ + thingset_msg_reset(msg); + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_BAD_REQUEST); + return ts_msg_add_response_status_json(msg); + } + if (!ts_obj_access_read(child_oref, request_auth)) { + thingset_msg_reset(msg); + if (ts_obj_access_read(child_oref, TS_READ_MASK)) { + TS_LOGD("%s: fetch not authorized: >0x%04x< - object: %s >0x%04x<", __func__, + (unsigned int)request_auth, ts_obj_name(child_oref), + (unsigned int)ts_obj_access(child_oref)); + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_UNAUTHORIZED); + ret = ts_msg_add_response_status_json(msg); + } + else { + TS_LOGD("%s: fetch forbidden: >0x%04x< - object: %s >0x%04x<", __func__, + (unsigned int)request_auth, ts_obj_name(child_oref), + (unsigned int)ts_obj_access(child_oref)); + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_FORBIDDEN); + ret = ts_msg_add_response_status_json(msg); + } + return ret; + } + + ret = ts_msg_add_object_json(msg, child_oref); + if (ret == 0) { + ret = ts_msg_add_u8(msg, ','); + } + + if (ret != 0) { +#if TS_CONFIG_LOG + char log_buf[200]; + ret = ts_msg_log(msg, &log_buf[0], sizeof(log_buf)); + TS_LOGD("MSG JSON: %s on fetch request 0x%" PRIXPTR " creates too large response 0x%" + PRIXPTR " message: >%d<, >%.*s<", + __func__, (uintptr_t)request, (uintptr_t)msg, ts_msg_len(msg), ret, &log_buf[0]); +#endif + thingset_msg_reset(msg); + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_RESPONSE_TOO_LARGE); + return ts_msg_add_response_status_json(msg); + } + + token_idx++; + } + + /* Remove trailing comma */ + ts_msg_remove(msg, 1); + if (bracket) { + /* buffer will be long enough as we dropped last 1 character */ + (void)ts_msg_add_u8(msg, (uint8_t)']'); + } + + return 0; +} + +int ts_msg_add_response_status_cbor(struct thingset_msg *msg) +{ + TS_ASSERT(ts_msg_status_proto(msg) == TS_MSG_PROTO_BIN, + "MSG: %s - invalid protocol %u of message 0x%" PRIXPTR, __func__, + (unsigned int)ts_msg_status_proto(msg), (uintptr_t)msg); + + ts_msg_status_code_t code = ts_msg_status_code(msg); + TS_ASSERT(code >= TS_STATUS_CREATED, + "MSG: %s - invalid code 0x%02x to add to message 0x%" PRIXPTR, __func__, + (unsigned int)code, (uintptr_t)msg); + + int ret = ts_msg_add_u8(msg, code); + if (ret != 0) { + return ret; + } + + if ((code == TS_MSG_CODE_CONTENT) || (code == TS_MSG_CODE_EXPORT)) { + /* Extra CBOR data will follow - prepare for CBOR encoding */ + return ts_msg_cbor_enc_setup(msg); + } + + return 0; +} + +int ts_msg_add_response_status_json(struct thingset_msg *msg) +{ + TS_ASSERT(ts_msg_status_proto(msg) == TS_MSG_PROTO_TXT, + "MSG: %s - invalid protocol %u of message 0x%" PRIXPTR, __func__, + (unsigned int)ts_msg_status_proto(msg), (uintptr_t)msg); + ts_msg_status_code_t code = ts_msg_status_code(msg); + TS_ASSERT(code >= TS_STATUS_CREATED, + "MSG: %s - invalid code 0x%02x to add to message 0x%" PRIXPTR, __func__, + (unsigned int)code, (uintptr_t)msg); + + if (ts_msg_tailroom(msg) < 4) { + /* Minimum size of status message */ + return -ENOMEM; + } + + (void)ts_msg_add_u8(msg, ':'); /* 1 byte */ + (void)ts_msg_add_status(msg, code); /* 2 bytes */ + + if (TS_CONFIG_VERBOSE_STATUS_MESSAGES) { + const char *status_msg; + switch (code) { + // success + case TS_MSG_CODE_CREATED: + status_msg = " Created"; + break; + case TS_MSG_CODE_DELETED: + status_msg = " Deleted"; + break; + case TS_MSG_CODE_VALID: + status_msg = " Valid"; + break; + case TS_MSG_CODE_CHANGED: + status_msg = " Changed"; + break; + case TS_MSG_CODE_CONTENT: + status_msg = " Content"; + break; + case TS_MSG_CODE_EXPORT: + status_msg = " Export"; + break; + // client errors + case TS_MSG_CODE_BAD_REQUEST: + status_msg = " Bad Request"; + break; + case TS_MSG_CODE_UNAUTHORIZED: + status_msg = " Unauthorized"; + break; + case TS_MSG_CODE_FORBIDDEN: + status_msg = " Forbidden"; + break; + case TS_MSG_CODE_NOT_FOUND: + status_msg = " Not Found"; + break; + case TS_MSG_CODE_METHOD_NOT_ALLOWED: + status_msg = " Method Not Allowed"; + break; + case TS_MSG_CODE_REQUEST_INCOMPLETE: + status_msg = " Request Entity Incomplete"; + break; + case TS_MSG_CODE_CONFLICT: + status_msg = " Conflict"; + break; + case TS_MSG_CODE_REQUEST_TOO_LARGE: + status_msg = " Request Entity Too Large"; + break; + case TS_MSG_CODE_UNSUPPORTED_FORMAT: + status_msg = " Unsupported Content-Format"; + break; + // server errors + case TS_MSG_CODE_INTERNAL_SERVER_ERR: + status_msg = " Internal Server Error"; + break; + case TS_MSG_CODE_NOT_IMPLEMENTED: + status_msg = " Not Implemented"; + break; + // ThingSet specific errors + case TS_MSG_CODE_RESPONSE_TOO_LARGE: + status_msg = " Response too large"; + break; + default: + status_msg = " Error"; + break; + }; + uint16_t len = strlen(status_msg); + if (len < ts_msg_tailroom(msg)) { + /* Size of string and 1 extra character space for '.' available */ + (void)ts_msg_add_mem(msg, (const uint8_t *)status_msg, len); + } + } + + (void)ts_msg_add_u8(msg, '.'); /* 1 byte */ + + if ((code == TS_MSG_CODE_CONTENT) || (code == TS_MSG_CODE_EXPORT)) { + /* Extra JSON data will follow - prepare for json encoding */ + return ts_msg_json_enc_setup(msg); + } + + return 0; +} + +int ts_msg_add_statement_cbor(struct thingset_msg *msg, thingset_oref_t oref) +{ + TS_ASSERT(ts_obj_db_oref_is_object(oref), "MSG CBOR: %s on invalid object reference (%u:%u)", + __func__, (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + + if (!ts_obj_db_oref_is_object(oref)) { + TS_LOGE("MSG CBOR: %s on invalid object reference (%u:%u)", + __func__, (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + return -EINVAL; + } + if (ts_obj_parent_id(oref) != TS_ID_ROOT) { + // currently only supporting top level objects + TS_LOGD("MSG CBOR: %s - object '%s' is not top level - not supported", __func__, + ts_obj_name(oref)); + return 0; + } + + int ret = 0; + if ((ts_obj_type(oref) == TS_T_SUBSET) || (ts_obj_type(oref) == TS_T_GROUP)) { + ret = ts_msg_add_u8(msg, TS_MSG_CODE_BIN_STATEMENT); + if (ret != 0) { + return ret; + } + /* Switch to cbor encoding for the rest of the message */ + ret = ts_msg_cbor_enc_setup(msg); + if (ret != 0) { + return ret; + } + /* serialize object id */ + ret = ts_msg_add_u16_cbor(msg, ts_obj_id(oref)); + if (ret != 0) { + return ret; + } + + /* find out number of elements to be serialized */ + int num_elements = 0; + thingset_oref_t child_oref = oref; + for (child_oref.db_oid = 0; ts_obj_db_oref_is_object(child_oref); child_oref.db_oid++) { + if (((ts_obj_type(oref) == TS_T_SUBSET) && (ts_obj_subsets(child_oref) + & ts_obj_detail(oref))) || + ((ts_obj_type(oref) == TS_T_GROUP) && (ts_obj_parent_id(child_oref) + == ts_obj_id(oref)))) { + num_elements++; + } + } + + /* Add array of element values */ + ret = ts_msg_add_array_cbor(msg, num_elements); + if (ret != 0) { + return ret; + } + for (child_oref.db_oid = 0; ts_obj_db_oref_is_object(child_oref); child_oref.db_oid++) { + if (((ts_obj_type(oref) == TS_T_SUBSET) && (ts_obj_subsets(child_oref) + & ts_obj_detail(oref))) || + ((ts_obj_type(oref) == TS_T_GROUP) && (ts_obj_parent_id(child_oref) + == ts_obj_id(oref)))) { + ret = ts_msg_add_object_cbor(msg, child_oref); + if (ret != 0) { + return ret; + } + } + } + ret = ts_msg_add_array_end_cbor(msg); + } + return ret; +} + +int ts_msg_add_statement_json(struct thingset_msg *msg, thingset_oref_t oref) +{ + TS_ASSERT(ts_obj_db_oref_is_object(oref), "MSG JSON: %s on invalid object reference (%u:%u)", + __func__, (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + + if (!ts_obj_db_oref_is_object(oref)) { + TS_LOGE("MSG JSON: %s on invalid object reference (%u:%u)", + __func__, (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + return -EINVAL; + } + if (ts_obj_parent_id(oref) != TS_ID_ROOT) { + // currently only supporting top level objects + TS_LOGD("MSG JSON: %s - object '%s' is not top level - not supported", __func__, + ts_obj_name(oref)); + return 0; + } + + int ret = ts_msg_json_enc_setup(msg); + if (ret != 0) { + return ret; + } + + if ((ts_obj_type(oref) == TS_T_SUBSET) || (ts_obj_type(oref) == TS_T_GROUP)) { + ret = ts_msg_add_u8(msg, TS_MSG_CODE_TXT_STATEMENT); + if (ret != 0) { + return ret; + } + const char *name = ts_obj_name(oref); + ret = ts_msg_add_mem(msg, name, strlen(name)); + if (ret != 0) { + return ret; + } + ret = ts_msg_add_u8(msg, ' '); + if (ret != 0) { + return ret; + } + + /* find out number of elements to be serialized */ + int num_elements = 0; + thingset_oref_t child_oref = oref; + for (child_oref.db_oid = 0; ts_obj_db_oref_is_object(child_oref); child_oref.db_oid++) { + if (((ts_obj_type(oref) == TS_T_SUBSET) && (ts_obj_subsets(child_oref) + & ts_obj_detail(oref))) || + ((ts_obj_type(oref) == TS_T_GROUP) && (ts_obj_parent_id(child_oref) + == ts_obj_id(oref)))) { + num_elements++; + } + } + + /* Add map of elements */ + ret = ts_msg_add_map_json(msg, num_elements); + if (ret != 0) { + return ret; + } + for (child_oref.db_oid = 0; ts_obj_db_oref_is_object(child_oref); child_oref.db_oid++) { + if (((ts_obj_type(oref) == TS_T_SUBSET) && (ts_obj_subsets(child_oref) + & ts_obj_detail(oref))) || + ((ts_obj_type(oref) == TS_T_GROUP) && (ts_obj_parent_id(child_oref) + == ts_obj_id(oref)))) { + ret = ts_msg_add_string_json(msg, ts_obj_name(child_oref)); + if (ret != 0) { + return ret; + } + ret = ts_msg_add_object_json(msg, child_oref); + if (ret != 0) { + return ret; + } + } + } + ret = ts_msg_add_map_end_json(msg); + } + return ret; +} + +int ts_msg_pull_status(struct thingset_msg *msg, ts_msg_status_code_t *code, + const char **description, uint16_t *len) +{ + if (ts_msg_len(msg) < 2) { + return -ENOMEM; + } + + const uint8_t *s = ts_msg_data(msg); + if (s >= ts_msg_tail(msg) - 3) { + return -ENOMEM; + } + int status_code = 0; + for(int i = 0; i < 2; i++) { + status_code = status_code * 16; + /* Find the decimal representation of hex[i] */ + if(s[i] >= '0' && s[i] <= '9') + { + status_code += s[i] - (uint8_t)'0'; + } + else if(s[i] >= 'a' && s[i] <= 'f') + { + status_code += s[i] - (uint8_t)'a' + 10; + } + else if(s[i] >= 'A' && s[i] <= 'F') + { + status_code += s[i] - (uint8_t)'A' + 10; + } + else { + return -EINVAL; + } + } + if (s[2] == '.') { + /* No description */ + *description = &s[2]; + *len = 1; + *code = status_code; + (void)ts_msg_pull(msg, 3); + return 0; + } + s = &s[2]; + /* skip space */ + while (isspace((char)*s) && (s < ts_msg_tail(msg))) { + s++; + } + if (s >= ts_msg_tail(msg)) { + return -ENOMEM; + } + /* search '.' */ + const char *desc = (const char *)s; + while (s < ts_msg_tail(msg)) { + if ((char)*s == '.') { + /* Description found */ + *description = desc; + *len = s + 1 - (const uint8_t *)desc; + *code = status_code; + (void)ts_msg_pull(msg, s + 1 - ts_msg_data(msg)); + return 0; + } + s++; + } + return -EINVAL; +} + +int ts_msg_pull_object_cbor(struct thingset_msg *msg, thingset_oref_t oref, bool patch) +{ + int ret = 0; + + switch (ts_obj_type(oref)) { +#if TS_CONFIG_64BIT_TYPES_SUPPORT + case TS_T_UINT64: + { + uint64_t val_u64; + ret = ts_msg_pull_u64_cbor(msg, &val_u64); + if (ret == 0) { + if (patch) { + *ts_obj_u64_data(oref) = val_u64; + } + break; + } + + float val_f32; + ret = ts_msg_pull_f32_cbor(msg, &val_f32); + if (ret == 0) { + if (patch) { + *ts_obj_u64_data(oref) = (uint64_t)val_f32; + } + break; + } + } + break; + case TS_T_INT64: + { + int64_t val_i64; + ret = ts_msg_pull_i64_cbor(msg, &val_i64); + if (ret == 0) { + if (patch) { + *ts_obj_i64_data(oref) = val_i64; + } + break; + } + + float val_f32; + ret = ts_msg_pull_f32_cbor(msg, &val_f32); + if (ret == 0) { + if (patch) { + *ts_obj_i64_data(oref) = (int64_t)val_f32; + } + break; + } + } + break; +#endif + case TS_T_UINT32: + { + uint32_t val_u32; + ret = ts_msg_pull_u32_cbor(msg, &val_u32); + if (ret == 0) { + if (patch) { + *ts_obj_u32_data(oref) = val_u32; + } + break; + } + + float val_f32; + ret = ts_msg_pull_f32_cbor(msg, &val_f32); + if (ret == 0) { + if (patch) { + *ts_obj_u32_data(oref) = (uint32_t)val_f32; + } + break; + } + } + break; + case TS_T_INT32: + { + int32_t val_i32; + ret = ts_msg_pull_i32_cbor(msg, &val_i32); + if (ret == 0) { + if (patch) { + *ts_obj_i32_data(oref) = val_i32; + } + break; + } + + float val_f32; + ret = ts_msg_pull_f32_cbor(msg, &val_f32); + if (ret == 0) { + if (patch) { + *ts_obj_i32_data(oref) = (int32_t)val_f32; + } + break; + } + } + break; + case TS_T_UINT16: + { + uint16_t val_u16; + ret = ts_msg_pull_u16_cbor(msg, &val_u16); + if (ret == 0) { + if (patch) { + *ts_obj_u16_data(oref) = val_u16; + } + break; + } + + float val_f32; + ret = ts_msg_pull_f32_cbor(msg, &val_f32); + if (ret == 0) { + if (patch) { + *ts_obj_u16_data(oref) = (uint16_t)val_f32; + } + break; + } + } + break; + case TS_T_INT16: + { + int16_t val_i16; + ret = ts_msg_pull_i16_cbor(msg, &val_i16); + if (ret == 0) { + if (patch) { + *ts_obj_i16_data(oref) = val_i16; + } + break; + } + + float val_f32; + ret = ts_msg_pull_f32_cbor(msg, &val_f32); + if (ret == 0) { + if (patch) { + *ts_obj_i16_data(oref) = (int16_t)val_f32; + } + break; + } + } + break; + case TS_T_FLOAT32: + if (patch) { + ret = ts_msg_pull_f32_cbor(msg, ts_obj_f32_data(oref)); + } + else { + float val_f32; + ret = ts_msg_pull_f32_cbor(msg, &val_f32); + } + break; +#if TS_CONFIG_DECFRAC_TYPE_SUPPORT + case TS_T_DECFRAC: + { + int32_t val_mantissa; + int16_t val_exponent; + ret = ts_msg_pull_decfrac_cbor(msg, &val_mantissa, &val_exponent); + if (ret == 0) { + if (patch) { + int16_t exponent = ts_obj_decfrac_exponent_data(oref); + for (int16_t i = val_exponent; i < exponent; i++) { + val_mantissa /= 10; + } + for (int16_t i = val_exponent; i > exponent; i--) { + val_mantissa *= 10; + } + *ts_obj_decfrac_mantissa_data(oref) = val_mantissa; + } + break; + } + + float val_f32; + ret = ts_msg_pull_f32_cbor(msg, &val_f32); + if (ret == 0) { + if (patch) { + int16_t exponent = ts_obj_decfrac_exponent_data(oref); + + for (int16_t i = 0; i < exponent; i++) { + val_f32 /= 10.0F; + } + for (int i = 0; i > exponent; i--) { + val_f32 *= 10.0F; + } + *ts_obj_decfrac_mantissa_data(oref) = (int32_t)val_f32; + } + break; + } + + ret = ts_msg_pull_i32_cbor(msg, &val_mantissa); + if (ret == 0) { + if (patch) { + int16_t exponent = ts_obj_decfrac_exponent_data(oref); + + for (int16_t i = 0; i < exponent; i++) { + val_mantissa /= 10; + } + for (int i = 0; i > exponent; i--) { + val_mantissa *= 10; + } + *ts_obj_decfrac_mantissa_data(oref) = val_mantissa; + } + break; + } + } + break; +#endif + case TS_T_BOOL: + if (patch) { + ret = ts_msg_pull_bool_cbor(msg, ts_obj_bool_data(oref)); + } + else { + bool val_b; + ret = ts_msg_pull_bool_cbor(msg, &val_b); + } + break; + case TS_T_STRING: + { + const char *s; + uint16_t len; + ret = ts_msg_pull_string_cbor(msg, &s, &len); + if (ret != 0) { + break; + } + if ((len + 1) >= ts_obj_detail(oref)) { + /* String does not fit into object */ + ret = -ENOMEM; + break; + } + if (patch) { + (void)strncpy(ts_obj_string_data(oref), s, (size_t)len); + ts_obj_string_data(oref)[len] = '\0'; + } + } + break; +#if TS_CONFIG_BYTE_STRING_TYPE_SUPPORT + case TS_T_BYTES: + { + const uint8_t *mem_data; + uint16_t mem_len; + ret = ts_msg_pull_mem_cbor(msg, &mem_data, &mem_len); + if (ret != 0) { + break; + } + if (mem_len > ts_obj_detail(oref)) { + ret = -ENOMEM; + break; + } + if (patch) { + (void)memcpy(ts_obj_mem_data(oref), mem_data, mem_len); + *ts_obj_mem_len(oref) = mem_len; + } + } + break; +#endif + case TS_T_ARRAY: + { + struct ts_array_info *array_info = ts_obj_array_data(oref); + uint16_t num_elements; + ret = ts_msg_pull_array_cbor(msg, &num_elements); + if (ret != 0) { + break; + } + if (num_elements > array_info->max_elements) { + ret = -ENOMEM; + break; + } + for (int i = 0; (i < num_elements) && (ret == 0); i++) { + switch (array_info->type) { +#if TS_CONFIG_64BIT_TYPES_SUPPORT + case TS_T_UINT64: + if (patch) { + ret = ts_msg_pull_u64_cbor(msg, &(((uint64_t *)array_info->ptr)[i])); + } + else { + uint64_t val_u64; + ret = ts_msg_pull_u64_cbor(msg, &val_u64); + } + break; + case TS_T_INT64: + if (patch) { + ret = ts_msg_pull_i64_cbor(msg, &(((int64_t *)array_info->ptr)[i])); + } + else { + int64_t val_i64; + ret = ts_msg_pull_i64_cbor(msg, &val_i64); + } + break; +#endif + case TS_T_UINT32: + if (patch) { + ret = ts_msg_pull_u32_cbor(msg, &(((uint32_t *)array_info->ptr)[i])); + } + else { + uint32_t val_u32; + ret = ts_msg_pull_u32_cbor(msg, &val_u32); + } + break; + case TS_T_INT32: + if (patch) { + ret = ts_msg_pull_i32_cbor(msg, &(((int32_t *)array_info->ptr)[i])); + } + else { + int32_t val_i32; + ret = ts_msg_pull_i32_cbor(msg, &val_i32); + } + break; + case TS_T_UINT16: + if (patch) { + ret = ts_msg_pull_u16_cbor(msg, &(((uint16_t *)array_info->ptr)[i])); + } + else { + uint16_t val_u16; + ret = ts_msg_pull_u16_cbor(msg, &val_u16); + } + break; + case TS_T_INT16: + if (patch) { + ret = ts_msg_pull_i16_cbor(msg, &(((int16_t *)array_info->ptr)[i])); + } + else { + int16_t val_i16; + ret = ts_msg_pull_i16_cbor(msg, &val_i16); + } + break; + case TS_T_FLOAT32: + if (patch) { + ret = ts_msg_pull_f32_cbor(msg, &(((float *)array_info->ptr)[i])); + } + else { + float val_f32; + ret = ts_msg_pull_f32_cbor(msg, &val_f32); + } + break; + default: + TS_LOGD("ThingSet JSON: %s - unexpected type (%u)", __func__, + (unsigned int)ts_obj_type(oref)); + if (!patch) { + ret = -EINVAL; + } + break; + } + } + if (ret != 0) { + break; + } + return ts_msg_pull_array_end_cbor(msg); + } + break; + default: + TS_LOGD("ThingSet JSON: %s - unexpected type (%u)", __func__, + (unsigned int)ts_obj_type(oref)); + if (!patch) { + ret = -EINVAL; + } + break; + } + return ret; +} + +int ts_msg_pull_object_json(struct thingset_msg *msg, thingset_oref_t oref, bool patch) +{ + int ret = 0; + + switch (ts_obj_type(oref)) { +#if TS_CONFIG_64BIT_TYPES_SUPPORT + case TS_T_UINT64: + { + uint64_t val_u64; + ret = ts_msg_pull_u64_json(msg, &val_u64); + if (ret == 0) { + if (patch) { + *ts_obj_u64_data(oref) = val_u64; + } + break; + } + + float val_f32; + ret = ts_msg_pull_f32_json(msg, &val_f32); + if (ret == 0) { + if (patch) { + *ts_obj_u64_data(oref) = (uint64_t)val_f32; + } + break; + } + } + break; + case TS_T_INT64: + { + int64_t val_i64; + ret = ts_msg_pull_i64_json(msg, &val_i64); + if (ret == 0) { + if (patch) { + *ts_obj_i64_data(oref) = val_i64; + } + break; + } + + float val_f32; + ret = ts_msg_pull_f32_json(msg, &val_f32); + if (ret == 0) { + if (patch) { + *ts_obj_i64_data(oref) = (int64_t)val_f32; + } + break; + } + } + break; +#endif + case TS_T_UINT32: + { + uint32_t val_u32; + ret = ts_msg_pull_u32_json(msg, &val_u32); + if (ret == 0) { + if (patch) { + *ts_obj_u32_data(oref) = val_u32; + } + break; + } + + float val_f32; + ret = ts_msg_pull_f32_json(msg, &val_f32); + if (ret == 0) { + if (patch) { + *ts_obj_u32_data(oref) = (uint32_t)val_f32; + } + break; + } + } + break; + case TS_T_INT32: + { + int32_t val_i32; + ret = ts_msg_pull_i32_json(msg, &val_i32); + if (ret == 0) { + if (patch) { + *ts_obj_i32_data(oref) = val_i32; + } + break; + } + + float val_f32; + ret = ts_msg_pull_f32_json(msg, &val_f32); + if (ret == 0) { + if (patch) { + *ts_obj_i32_data(oref) = (int32_t)val_f32; + } + break; + } + } + break; + case TS_T_UINT16: + { + uint16_t val_u16; + ret = ts_msg_pull_u16_json(msg, &val_u16); + if (ret == 0) { + if (patch) { + *ts_obj_u16_data(oref) = val_u16; + } + break; + } + + float val_f32; + ret = ts_msg_pull_f32_json(msg, &val_f32); + if (ret == 0) { + if (patch) { + *ts_obj_u16_data(oref) = (uint16_t)val_f32; + } + break; + } + } + break; + case TS_T_INT16: + { + int16_t val_i16; + ret = ts_msg_pull_i16_json(msg, &val_i16); + if (ret == 0) { + if (patch) { + *ts_obj_i16_data(oref) = val_i16; + } + break; + } + + float val_f32; + ret = ts_msg_pull_f32_json(msg, &val_f32); + if (ret == 0) { + if (patch) { + *ts_obj_i16_data(oref) = (int16_t)val_f32; + } + break; + } + } + break; + case TS_T_FLOAT32: + if (patch) { + ret = ts_msg_pull_f32_json(msg, ts_obj_f32_data(oref)); + } + else { + float val_f32; + ret = ts_msg_pull_f32_json(msg, &val_f32); + } + break; +#if TS_CONFIG_DECFRAC_TYPE_SUPPORT + case TS_T_DECFRAC: + { + int32_t val_mantissa; + int16_t val_exponent; + ret = ts_msg_pull_decfrac_json(msg, &val_mantissa, &val_exponent); + if (ret == 0) { + if (patch) { + int16_t exponent = ts_obj_decfrac_exponent_data(oref); + for (int16_t i = val_exponent; i < exponent; i++) { + val_mantissa /= 10; + } + for (int16_t i = val_exponent; i > exponent; i--) { + val_mantissa *= 10; + } + *ts_obj_decfrac_mantissa_data(oref) = val_mantissa; + } + break; + } + + float val_f32; + ret = ts_msg_pull_f32_json(msg, &val_f32); + if (ret == 0) { + if (patch) { + int16_t exponent = ts_obj_decfrac_exponent_data(oref); + + for (int16_t i = 0; i < exponent; i++) { + val_f32 /= 10.0F; + } + for (int i = 0; i > exponent; i--) { + val_f32 *= 10.0F; + } + *ts_obj_decfrac_mantissa_data(oref) = (int32_t)val_f32; + } + break; + } + + ret = ts_msg_pull_i32_json(msg, &val_mantissa); + if (ret == 0) { + if (patch) { + int16_t exponent = ts_obj_decfrac_exponent_data(oref); + + for (int16_t i = 0; i < exponent; i++) { + val_mantissa /= 10; + } + for (int i = 0; i > exponent; i--) { + val_mantissa *= 10; + } + *ts_obj_decfrac_mantissa_data(oref) = val_mantissa; + } + break; + } + } + break; +#endif + case TS_T_BOOL: + if (patch) { + ret = ts_msg_pull_bool_json(msg, ts_obj_bool_data(oref)); + } + else { + bool val_b; + ret = ts_msg_pull_bool_json(msg, &val_b); + } + break; + case TS_T_STRING: + { + const char *s; + uint16_t len; + ret = ts_msg_pull_string_json(msg, &s, &len); + if (ret != 0) { + break; + } + if ((len + 1) >= ts_obj_detail(oref)) { + /* String does not fit into object */ + ret = -ENOMEM; + break; + } + if (patch) { + (void)strncpy(ts_obj_string_data(oref), s, (size_t)len); + ts_obj_string_data(oref)[len] = '\0'; + } + } + break; +#if TS_CONFIG_BYTE_STRING_TYPE_SUPPORT + case TS_T_BYTES: + return -ENOTSUP; +#endif + case TS_T_ARRAY: + { + struct ts_array_info *array_info = ts_obj_array_data(oref); + uint16_t num_elements; + ret = ts_msg_pull_array_json(msg, &num_elements); + if (ret != 0) { + break; + } + if (num_elements > array_info->max_elements) { + ret = -ENOMEM; + break; + } + for (int i = 0; (i < num_elements) && (ret == 0); i++) { + switch (array_info->type) { +#if TS_CONFIG_64BIT_TYPES_SUPPORT + case TS_T_UINT64: + if (patch) { + ret = ts_msg_pull_u64_json(msg, &(((uint64_t *)array_info->ptr)[i])); + } + else { + uint64_t val_u64; + ret = ts_msg_pull_u64_json(msg, &val_u64); + } + break; + case TS_T_INT64: + if (patch) { + ret = ts_msg_pull_i64_json(msg, &(((int64_t *)array_info->ptr)[i])); + } + else { + int64_t val_i64; + ret = ts_msg_pull_i64_json(msg, &val_i64); + } + break; +#endif + case TS_T_UINT32: + if (patch) { + ret = ts_msg_pull_u32_json(msg, &(((uint32_t *)array_info->ptr)[i])); + } + else { + uint32_t val_u32; + ret = ts_msg_pull_u32_json(msg, &val_u32); + } + break; + case TS_T_INT32: + if (patch) { + ret = ts_msg_pull_i32_json(msg, &(((int32_t *)array_info->ptr)[i])); + } + else { + int32_t val_i32; + ret = ts_msg_pull_i32_json(msg, &val_i32); + } + break; + case TS_T_UINT16: + if (patch) { + ret = ts_msg_pull_u16_json(msg, &(((uint16_t *)array_info->ptr)[i])); + } + else { + uint16_t val_u16; + ret = ts_msg_pull_u16_json(msg, &val_u16); + } + break; + case TS_T_INT16: + if (patch) { + ret = ts_msg_pull_i16_json(msg, &(((int16_t *)array_info->ptr)[i])); + } + else { + int16_t val_i16; + ret = ts_msg_pull_i16_json(msg, &val_i16); + } + break; + case TS_T_FLOAT32: + if (patch) { + ret = ts_msg_pull_f32_json(msg, &(((float *)array_info->ptr)[i])); + } + else { + float val_f32; + ret = ts_msg_pull_f32_json(msg, &val_f32); + } + break; + default: + TS_LOGD("ThingSet JSON: %s - unexpected type (%u)", __func__, + (unsigned int)ts_obj_type(oref)); + if (!patch) { + ret = -EINVAL; + } + break; + } + } + if (ret != 0) { + break; + } + return ts_msg_pull_array_end_json(msg); + } + break; + default: + TS_LOGD("ThingSet JSON: %s - unexpected type (%u)", __func__, + (unsigned int)ts_obj_type(oref)); + if (!patch) { + ret = -EINVAL; + } + break; + } + return ret; +} + +int ts_msg_pull_request_cbor(struct thingset_msg *msg, thingset_oref_t *oref) +{ + TS_ASSERT(oref->db_id != TS_OBJ_DB_ID_INVALID, "object reference has to declare database"); + TS_ASSERT(ts_msg_status_type(msg) == TS_MSG_TYPE_REQUEST, "Unexpected message type (%d)", + (int)ts_msg_status_type(msg)); + TS_ASSERT(ts_msg_status_proto(msg) == TS_MSG_PROTO_BIN, "Unexpected message proto (%d)", + (int)ts_msg_status_proto(msg)); + + uint8_t req_method_id; + int ret = ts_msg_pull_u8(msg, &req_method_id); + if (ret != 0) { + ts_msg_status_error_set(msg, TS_MSG_CODE_BAD_REQUEST); + goto ts_msg_pull_request_cbor_end; + } + + ret = ts_msg_cbor_dec_setup(msg); + if (ret != 0) { + /* Something went wrong in msg init */ + ts_msg_status_error_set(msg, TS_MSG_CODE_INTERNAL_SERVER_ERR); + goto ts_msg_pull_request_cbor_end; + } + + CborType path_type; + ret = ts_msg_pull_type_cbor(msg, &path_type); + if (ret != 0) { + ts_msg_status_error_set(msg, TS_MSG_CODE_BAD_REQUEST); + goto ts_msg_pull_request_cbor_end; + } + + /* Get data object (first parameter of the request) */ + if (path_type == CborIntegerType) { + ts_obj_id_t obj_id; + ret = ts_msg_pull_u16_cbor(msg, &obj_id); + if (ret != 0) { + ts_msg_status_error_set(msg, TS_MSG_CODE_BAD_REQUEST); + goto ts_msg_pull_request_cbor_end; + } + ret = ts_obj_db_oref_by_id(oref->db_id, obj_id, oref); + if (ret != 0) { + /* We did not get the object reference - assure it is invalid */ + ts_obj_db_oref_init(oref->db_id, oref); + } + } + else if (path_type == CborTextStringType) { + const char *path; + uint16_t path_len; + ret = ts_msg_pull_string_cbor(msg, &path, &path_len); + if (ret != 0) { + ts_msg_status_error_set(msg, TS_MSG_CODE_BAD_REQUEST); + goto ts_msg_pull_request_cbor_end; + } + if (path[0] == '/') { + if ((req_method_id == TS_GET) && (path_len == 1)) { + *oref = ts_obj_db_oref_root(oref->db_id); + ts_msg_status_code_set(msg, TS_MSG_CODE_REQUEST_GET_NAMES); + goto ts_msg_pull_request_cbor_end; + } + ts_msg_status_error_set(msg, TS_MSG_CODE_BAD_REQUEST); + goto ts_msg_pull_request_cbor_end; + } + if ((path[path_len - 1] == '/')) { + path_len -= 1; + } + ret = ts_obj_by_path(ts_obj_db_oref_any(oref->db_id), path, path_len, oref); + if (ret != 0) { + /* We did not get the object reference - assure it is invalid */ + ts_obj_db_oref_init(oref->db_id, oref); + if (path_len > 0) { + ts_msg_status_error_set(msg, TS_MSG_CODE_NOT_FOUND); + goto ts_msg_pull_request_cbor_end; + } + } + } else { + /* First data object is of unexpected type */ + ts_msg_status_error_set(msg, TS_MSG_CODE_BAD_REQUEST); + goto ts_msg_pull_request_cbor_end; + } + + if (req_method_id == TS_GET) { + if (path_type == CborTextStringType) { + ts_msg_status_code_set(msg, TS_MSG_CODE_REQUEST_GET_NAMES_VALUES); + } + else { + ts_msg_status_code_set(msg, TS_MSG_CODE_REQUEST_GET_IDS_VALUES); + } + } + else if (ts_msg_len(msg) == 0) { + /* no payload data */ + ts_msg_status_error_set(msg, TS_MSG_CODE_BAD_REQUEST); + } + else if (req_method_id == TS_POST) { + if (ts_obj_type(*oref) == TS_T_EXEC) { + // object is generally executable, but are we authorized? + if (ts_obj_access_write(*oref, TS_WRITE_MASK)) { + if (!ts_obj_access_write(*oref, ts_msg_auth(msg))) { + ts_msg_status_error_set(msg, TS_MSG_CODE_UNAUTHORIZED); + } + else { + ts_msg_status_code_set(msg, TS_MSG_CODE_REQUEST_EXEC); + } + } else { + ts_msg_status_error_set(msg, TS_MSG_CODE_FORBIDDEN); + } + } + else { + ts_msg_status_code_set(msg, TS_MSG_CODE_REQUEST_CREATE); + } + } + else if (req_method_id == TS_DELETE) { + if ((ts_obj_type(*oref) != TS_T_ARRAY) && (ts_obj_type(*oref) != TS_T_SUBSET)) { + ts_msg_status_error_set(msg, TS_MSG_CODE_METHOD_NOT_ALLOWED); + } + else if (ts_obj_access_write(*oref, TS_WRITE_MASK)) { + if (!ts_obj_access_write(*oref, ts_msg_auth(msg))) { + ts_msg_status_error_set(msg, TS_MSG_CODE_UNAUTHORIZED); + } + else { + ts_msg_status_code_set(msg, TS_MSG_CODE_REQUEST_DELETE); + } + } else { + ts_msg_status_error_set(msg, TS_MSG_CODE_FORBIDDEN); + } + } + else if (req_method_id == TS_FETCH) { + CborType t; + ret = ts_msg_pull_type_cbor(msg, &t); + if (ret != 0) { + ts_msg_status_error_set(msg, TS_MSG_CODE_BAD_REQUEST); + } + else if (t == CborUndefinedType) { + /* Request all ids/ names behind endpoint */ + if (path_type == CborTextStringType) { + ts_msg_status_code_set(msg, TS_MSG_CODE_REQUEST_FETCH_NAMES); + } + else { + ts_msg_status_code_set(msg, TS_MSG_CODE_REQUEST_FETCH_IDS); + } + } + else if (t == CborArrayType) { + /* Request data defined by names/ids in array */ + ts_msg_status_code_set(msg, TS_MSG_CODE_REQUEST_FETCH_VALUES); + } + else if (((t == CborTextStringType) && (path_type == CborTextStringType)) || + ((t == CborIntegerType) && (path_type == CborIntegerType))) { + /* Request single data defined by name/id */ + ts_msg_status_code_set(msg, TS_MSG_CODE_REQUEST_FETCH_SINGLE); + } + else { + /* Unexpected fetch request */ + TS_LOGD("%s: unexpected fetch value of type %u", __func__, (unsigned int)t); + ts_msg_status_error_set(msg, TS_MSG_CODE_BAD_REQUEST); + } + } + else if (req_method_id == TS_PATCH) { + if (ts_obj_db_oref_is_object(*oref)) { + ts_msg_status_code_set(msg, TS_MSG_CODE_REQUEST_PATCH); + } + else { + ts_msg_status_error_set(msg, TS_MSG_CODE_NOT_FOUND); + } + } + else { + /* Unknown/ invalid command */ + ts_msg_status_error_set(msg, TS_MSG_CODE_BAD_REQUEST); + } + +ts_msg_pull_request_cbor_end: + if (ts_msg_status_valid(msg) != TS_MSG_VALID_OK) { + /* Assure object reference is invalid */ + ts_obj_db_oref_init(oref->db_id, oref); + } + + return 0; +} + +int ts_msg_pull_request_json(struct thingset_msg *msg, thingset_oref_t *oref) +{ + TS_ASSERT(oref->db_id != TS_OBJ_DB_ID_INVALID, "object reference has to declare database"); + TS_ASSERT(ts_msg_status_type(msg) == TS_MSG_TYPE_REQUEST, "Unexpected message type (%d)", + (int)ts_msg_status_type(msg)); + TS_ASSERT(ts_msg_status_proto(msg) == TS_MSG_PROTO_TXT, "Unexpected message type (%d)", + (int)ts_msg_status_proto(msg)); + + uint8_t req_method_id; + int ret = ts_msg_pull_u8(msg, &req_method_id); + if (ret != 0) { + ts_msg_status_error_set(msg, TS_MSG_CODE_BAD_REQUEST); + goto ts_msg_pull_request_json_end; + } + + const char *path; + uint16_t path_len; + ret = ts_msg_pull_path(msg, &path, &path_len); + if (ret != 0) { + ts_msg_status_error_set(msg, TS_MSG_CODE_BAD_REQUEST); + goto ts_msg_pull_request_json_end; + } + + /* Get data object (first parameter of the request) */ + if (path_len == 0) { + /* Take empty path as root path */ + *oref = ts_obj_db_oref_root(oref->db_id); + } + else if (path[0] == '/') { + if ((req_method_id == '?') && (path_len == 1)) { + *oref = ts_obj_db_oref_root(oref->db_id); + ts_msg_status_code_set(msg, TS_MSG_CODE_REQUEST_GET_NAMES); + goto ts_msg_pull_request_json_end; + } + ts_msg_status_error_set(msg, TS_MSG_CODE_BAD_REQUEST); + goto ts_msg_pull_request_json_end; + } + else { + uint16_t object_path_len = path_len; + if ((path[path_len - 1] == '/')) { + object_path_len -= 1; + } + ret = ts_obj_by_path(ts_obj_db_oref_any(oref->db_id), path, object_path_len, oref); + if (ret != 0) { + /* We did not get the object reference - assure it is invalid */ + ts_obj_db_oref_init(oref->db_id, oref); + if (object_path_len > 0) { + ts_msg_status_error_set(msg, TS_MSG_CODE_NOT_FOUND); + goto ts_msg_pull_request_json_end; + } + } + } + + if (req_method_id == '?') { + /* GET, FETCH */ + if (ts_msg_len(msg) == 0) { + // no payload data + if ((path_len > 0) && (path[path_len - 1] == '/')) { + if (ts_obj_db_oref_is_tree(*oref) && + (ts_obj_type(*oref) == TS_T_GROUP || ts_obj_type(*oref) == TS_T_EXEC)) { + ts_msg_status_code_set(msg, TS_MSG_CODE_REQUEST_GET_NAMES); + } + else { + // device discovery is only allowed for internal objects + ts_msg_status_error_set(msg, TS_MSG_CODE_BAD_REQUEST); + } + } + else if (ts_obj_db_oref_is_object(*oref) && (ts_obj_type(*oref) == TS_T_EXEC)) { + // bad request, as we can't read exec object's values + ts_msg_status_error_set(msg, TS_MSG_CODE_BAD_REQUEST); + } + else { + ts_msg_status_code_set(msg, TS_MSG_CODE_REQUEST_GET_NAMES_VALUES); + } + } + else { + ts_msg_status_code_set(msg, TS_MSG_CODE_REQUEST_FETCH_VALUES); + } + } + else if (req_method_id == '!') { + /* EXEC */ + if (ts_obj_db_oref_is_object(*oref) && (ts_obj_type(*oref) == TS_T_EXEC)) { + // object is generally executable, but are we authorized? + if (ts_obj_access_write(*oref, TS_WRITE_MASK)) { + if (!ts_obj_access_write(*oref, ts_msg_auth(msg))) { + ts_msg_status_error_set(msg, TS_MSG_CODE_UNAUTHORIZED); + } + else { + ts_msg_status_code_set(msg, TS_MSG_CODE_REQUEST_EXEC); + } + } else { + ts_msg_status_error_set(msg, TS_MSG_CODE_FORBIDDEN); + } + } + else { + ts_msg_status_error_set(msg, TS_MSG_CODE_BAD_REQUEST); + } + } + else if (ts_msg_len(msg) == 0) { + // no payload data + ts_msg_status_error_set(msg, TS_MSG_CODE_BAD_REQUEST); + } + else if (req_method_id == '=') { + /* PATCH */ + ts_msg_status_code_set(msg, TS_MSG_CODE_REQUEST_PATCH); + } + else if (req_method_id == '+') { + /* CREATE */ + ts_msg_status_code_set(msg, TS_MSG_CODE_REQUEST_CREATE); + } + else if (req_method_id == '-') { + /* DELETE */ + if ((ts_obj_type(*oref) != TS_T_ARRAY) && (ts_obj_type(*oref) != TS_T_SUBSET)) { + ts_msg_status_error_set(msg, TS_MSG_CODE_METHOD_NOT_ALLOWED); + } + else if (ts_obj_access_write(*oref, TS_WRITE_MASK)) { + if (!ts_obj_access_write(*oref, ts_msg_auth(msg))) { + ts_msg_status_error_set(msg, TS_MSG_CODE_UNAUTHORIZED); + } + else { + ts_msg_status_code_set(msg, TS_MSG_CODE_REQUEST_DELETE); + } + } else { + ts_msg_status_error_set(msg, TS_MSG_CODE_FORBIDDEN); + } + } + else { + /* Unknown/ invalid command */ + ts_msg_status_error_set(msg, TS_MSG_CODE_BAD_REQUEST); + } + +ts_msg_pull_request_json_end: + if (ts_msg_status_valid(msg) != TS_MSG_VALID_OK) { + /* Assure object reference is invalid */ + ts_obj_db_oref_init(oref->db_id, oref); + } + else if (ts_msg_len(msg) > 0) { + /* There is some extra content in the message */ + ret = ts_msg_json_dec_setup(msg); + if (ret != 0) { + ts_obj_db_oref_init(oref->db_id, oref); + if (ret == -ENOMEM) { + /* Could not parse - not enough memory */ + ts_msg_status_error_set(msg, TS_MSG_CODE_REQUEST_TOO_LARGE); + } + else if ((ret == -EINVAL) || (ret == -ENAVAIL)) { + /* Invalid character inside JSON string or the string is not a full JSON packet. */ + ts_msg_status_error_set(msg, TS_MSG_CODE_BAD_REQUEST); + } + else if (ret == -ENODATA) { + /* No data in JSON string */ + ts_msg_status_error_set(msg, TS_MSG_CODE_BAD_REQUEST); + } + else { + /* Should not happen */ + ts_msg_status_error_set(msg, TS_MSG_CODE_INTERNAL_SERVER_ERR); + } + } + } + + return 0; +} diff --git a/src/ts_msg_value.c b/src/ts_msg_value.c new file mode 100644 index 0000000..07194cd --- /dev/null +++ b/src/ts_msg_value.c @@ -0,0 +1,1318 @@ +/* + * Copyright (c) 2017 Martin Jäger / Libre Solar + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Value primitives + * ---------------- + */ + +#include +#include +#include +#include +#include + +#include "ts_msg.h" + +/* Interface */ + +int ts_msg_add_mem(struct thingset_msg* msg, const uint8_t *mem, uint16_t len) +{ + if (len > ts_msg_tailroom(msg)) { + TS_LOGD("PRIMITIVE: %s faulting - can not add memory: %u, avail: %u", + __func__, (unsigned int)len, (unsigned int)ts_msg_tailroom(msg)); + return -ENOMEM; + } + (void)ts_buf_add_mem((struct ts_buf *)msg, mem, len); + TS_LOGD("PRIMITIVE: %s advance by %u", __func__, len); + return 0; +} + +int ts_msg_add_u8(struct thingset_msg* msg, uint8_t val) +{ + if (1 > ts_msg_tailroom(msg)) { + TS_LOGD("PRIMITIVE: %s faulting - can not add byte: %u, avail: %u", + __func__, (unsigned int)1, (unsigned int)ts_msg_tailroom(msg)); + return -ENOMEM; + } + (void)ts_buf_add_u8((struct ts_buf *)msg, val); + TS_LOGD("PRIMITIVE: %s advance by %u", __func__, 1); + return 0; +} + +int ts_msg_pull_u8(struct thingset_msg* msg, uint8_t *val) +{ + if (ts_msg_len(msg) == 0) { + return -ENOMEM; + } + *val = *ts_msg_data(msg); + (void)ts_msg_pull(msg, 1); + return 0; +} + +int ts_msg_pull_path(struct thingset_msg* msg, const char **path, uint16_t *len) +{ + uint16_t path_len = 0; + uint16_t path_max_len = ts_msg_len(msg); + const char *path_s = (const char *)ts_msg_data(msg); + + /* Skip space */ + uint16_t spaces = 0; + while (spaces < path_max_len) { + if (!isspace(path_s[spaces])) { + break; + } + spaces++; + } + if (spaces >= path_max_len) { + /* Spaces at end of message/ end of message -> take it as empty path */ + *path = NULL; + goto ts_msg_pull_path_end; + } + + /* Get path */ + path_len = strspn(&path_s[spaces], + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._-/"); + if (path_len > (path_max_len - spaces)) { + path_len = path_max_len - spaces; + } + if (path_len == 0) { + if (spaces > 0) { + /* There is space - take it as empty path */ + *path = NULL; + goto ts_msg_pull_path_end; + } + return -EINVAL; + } + *path = path_s; + +ts_msg_pull_path_end: + *len = path_len; + (void)ts_msg_pull(msg, spaces + path_len); + return 0; +} + +/* JSON values */ + +int ts_msg_add_array_json(struct thingset_msg* msg, uint16_t num_elements) +{ + TS_MSG_JSON_ENC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + if (scratchpad->current >= 2) { + TS_LOGD("JSON encoder: %s without more encoders", __func__); + return -ENOMEM; + } + + scratchpad->current++; + + /* Initialise new encoder */ + struct ts_msg_json_encoder *array_encoder = ts_msg_scratchpad_json_enc_current(msg); + array_encoder->flags.array = 0; + array_encoder->flags.array_unbounded = 0; + array_encoder->flags.map = 0; + array_encoder->flags.map_unbounded = 0; + array_encoder->remaining = 0; + + int ret = ts_msg_scratchpad_json_enc_update(msg, __func__, "[", 1); + if (ret != 0) { + scratchpad->current--; + return ret; + } + + if (num_elements == 0) { + array_encoder->flags.array_unbounded = 1; + } + else { + array_encoder->flags.array = 1; + } + /* Next element to come is a value - always for arrays */ + array_encoder->flags.key = 0; + array_encoder->remaining = num_elements; + + return 0; +} + +int ts_msg_add_array_end_json(struct thingset_msg* msg) +{ + if (ts_msg_scratchpad_json_enc_is_top(msg)) { + /* Can not close on top level encoder */ + TS_LOGD("JSON encoder: %s close while on top level encoder", __func__); + return -ENOMEM; + } + + struct ts_msg_json_encoder *array_encoder = ts_msg_scratchpad_json_enc_current(msg); + TS_ASSERT(!(array_encoder->flags.map && array_encoder->flags.map_unbounded), + "JSON encoder: %s unbounded and bounded array defined at same time",__func__); + if (!array_encoder->flags.array && !array_encoder->flags.array_unbounded) { + TS_LOGD("JSON encoder: %s close on non array encoder", __func__); + return -EINVAL; + } + + int ret = 0; + TS_MSG_JSON_ENC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + + /* Check value insertions */ + if (array_encoder->remaining > 0) { + TS_LOGD("JSON encoder: %s close %s%s array on missing elements (%u)", __func__, + array_encoder->flags.map ? "bounded" : "", + array_encoder->flags.map_unbounded ? "unbounded" : "", + (unsigned int)array_encoder->remaining); + ret = -EINVAL; + goto ts_msg_add_array_end_json_end; + } + + if (scratchpad->instance[scratchpad->current].flags.array_unbounded) { + /* Remove trailing comma */ + if (ts_msg_tail(msg)[-1] == ',') { + ts_msg_remove(msg, 1); + } + } + +ts_msg_add_array_end_json_end: + + scratchpad->current--; + + int ret_update = ts_msg_scratchpad_json_enc_update(msg, __func__, "]", 1); + if (ret == 0) { + ret = ret_update; + } + + return ret; +} + +int ts_msg_add_bool_json(struct thingset_msg* msg, bool b) +{ + const char *s; + uint16_t len; + if (b) { + s = "true"; + len = sizeof("true") - 1; + } else { + s = "false"; + len = sizeof("false") - 1; + } + return ts_msg_scratchpad_json_enc_update(msg, __func__, s, len); +} + +int ts_msg_add_decfrac_json(struct thingset_msg* msg, int32_t mantissa, int16_t exponent) +{ + TS_MSG_JSON_ENC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + uint16_t len = snprintf(&scratchpad->s[0], sizeof(scratchpad->s) - 1, "%" PRIi32 "e%" PRIi16, + mantissa, exponent); + return ts_msg_scratchpad_json_enc_update(msg, __func__, + (const uint8_t *)&scratchpad->s[0], len); +} + +int ts_msg_add_f32_json(struct thingset_msg* msg, float val, int precision) +{ + TS_MSG_JSON_ENC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + uint16_t len; + if (isnan(val) || isinf(val)) { + /* JSON spec does not support NaN and Inf, so we need to use null instead */ + len = snprintf(&scratchpad->s[0], sizeof(scratchpad->s) - 1, "null"); + } + else { + len = snprintf(&scratchpad->s[0], sizeof(scratchpad->s) - 1, "%.*f", precision, val); + } + return ts_msg_scratchpad_json_enc_update(msg, __func__, + (const uint8_t *)&scratchpad->s[0], len); +} + +int ts_msg_add_i16_json(struct thingset_msg* msg, int16_t val) +{ + TS_MSG_JSON_ENC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + uint16_t len = snprintf(&scratchpad->s[0], sizeof(scratchpad->s) - 1, "%" PRIi16, val); + return ts_msg_scratchpad_json_enc_update(msg, __func__, + (const uint8_t *)&scratchpad->s[0], len); +} + +int ts_msg_add_i32_json(struct thingset_msg* msg, int32_t val) +{ + TS_MSG_JSON_ENC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + uint16_t len = snprintf(&scratchpad->s[0], sizeof(scratchpad->s) - 1, "%" PRIi32, val); + return ts_msg_scratchpad_json_enc_update(msg, __func__, + (const uint8_t *)&scratchpad->s[0], len); +} + +int ts_msg_add_i64_json(struct thingset_msg* msg, int64_t val) +{ + TS_MSG_JSON_ENC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + uint16_t len = snprintf(&scratchpad->s[0], sizeof(scratchpad->s) - 1, "%" PRIi64, val); + return ts_msg_scratchpad_json_enc_update(msg, __func__, + (const uint8_t *)&scratchpad->s[0], len); +} + +int ts_msg_add_map_json(struct thingset_msg* msg, uint16_t num_elements) +{ + TS_MSG_JSON_ENC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + if (scratchpad->current >= 2) { + TS_LOGD("JSON encoder: %s without more encoders", __func__); + return -ENOMEM; + } + + scratchpad->current++; + + /* Initialise new encoder */ + struct ts_msg_json_encoder *map_encoder = ts_msg_scratchpad_json_enc_current(msg); + map_encoder->flags.array = 0; + map_encoder->flags.array_unbounded = 0; + map_encoder->flags.map = 0; + map_encoder->flags.map_unbounded = 0; + map_encoder->remaining = 0; + + int ret = ts_msg_scratchpad_json_enc_update(msg, __func__, "{", 1); + if (ret != 0) { + scratchpad->current--; + return ret; + } + + if (num_elements == 0) { + map_encoder->flags.map_unbounded = 1; + } + else { + map_encoder->flags.map = 1; + } + /* Next element to come is a key */ + map_encoder->flags.key = 1; + map_encoder->remaining = num_elements; + + return 0; +} + +int ts_msg_add_map_end_json(struct thingset_msg* msg) +{ + if (ts_msg_scratchpad_json_enc_is_top(msg)) { + /* Can not close on top level encoder */ + TS_LOGD("JSON encoder: %s close while on top level encoder", __func__); + return -ENOMEM; + } + + struct ts_msg_json_encoder *map_encoder = ts_msg_scratchpad_json_enc_current(msg); + TS_ASSERT(!(map_encoder->flags.map && map_encoder->flags.map_unbounded), + "JSON encoder: %s unbounded and bounded map defined at same time",__func__); + if (!map_encoder->flags.map && !map_encoder->flags.map_unbounded) { + TS_LOGD("JSON encoder: %s close on non map encoder", __func__); + return -EINVAL; + } + + int ret = 0; + TS_MSG_JSON_ENC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + + /* Check map key:value insertions */ + if (map_encoder->remaining > 0) { + TS_LOGD("JSON encoder: %s close %s%s map on missing elements (%u)", __func__, + map_encoder->flags.map ? "bounded" : "", + map_encoder->flags.map_unbounded ? "unbounded" : "", + (unsigned int)map_encoder->remaining); + ret = -EINVAL; + goto ts_msg_add_map_end_json_end; + } + if (!map_encoder->flags.key) { + /* Value to come - last element added was a key. */ + TS_LOGD("JSON encoder: %s close %s%s map on key element", __func__, + map_encoder->flags.map ? "bounded" : "", + map_encoder->flags.map_unbounded ? "unbounded" : ""); + ret = -EINVAL; + goto ts_msg_add_map_end_json_end; + } + + if (scratchpad->instance[scratchpad->current].flags.map_unbounded) { + /* Remove trailing comma */ + if (ts_msg_tail(msg)[-1] == ',') { + ts_msg_remove(msg, 1); + } + } + +ts_msg_add_map_end_json_end: + + scratchpad->current--; + + int ret_update = ts_msg_scratchpad_json_enc_update(msg, __func__, "}", 1); + if (ret == 0) { + ret = ret_update; + } + + return ret; +} + +int ts_msg_add_string_json(struct thingset_msg* msg, const char *s) +{ + /* additional 2x" (+2), trailing 0 for snprintf (+1) */ + uint16_t len = strnlen(s, ts_msg_tailroom(msg)) + 3; + if (len > ts_msg_tailroom(msg)) { + return -ENOMEM; + } + (void)snprintf((char *)ts_msg_tail(msg), len, "\"%s\"", s); + return ts_msg_scratchpad_json_enc_update(msg, __func__, NULL, len - 1); +} + +int ts_msg_add_u16_json(struct thingset_msg* msg, uint16_t val) +{ + TS_MSG_JSON_ENC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + uint16_t len = snprintf(&scratchpad->s[0], sizeof(scratchpad->s) - 1, "%" PRIu16, val); + return ts_msg_scratchpad_json_enc_update(msg, __func__, + (const uint8_t *)&scratchpad->s[0], len); +} + +int ts_msg_add_u32_json(struct thingset_msg* msg, uint32_t val) +{ + TS_MSG_JSON_ENC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + uint16_t len = snprintf(&scratchpad->s[0], sizeof(scratchpad->s) - 1, "%" PRIu32, val); + return ts_msg_scratchpad_json_enc_update(msg, __func__, + (const uint8_t *)&scratchpad->s[0], len); +} + + +int ts_msg_add_u64_json(struct thingset_msg* msg, uint64_t val) +{ + TS_MSG_JSON_ENC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + uint16_t len = snprintf(&scratchpad->s[0], sizeof(scratchpad->s) - 1, "%" PRIu64, val); + return ts_msg_scratchpad_json_enc_update(msg, __func__, + (const uint8_t *)&scratchpad->s[0], len); +} + +int ts_msg_pull_type_json(struct thingset_msg* msg, uint16_t *jsmn_type) +{ + TS_MSG_JSON_DEC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + uint16_t type, size, len; + const char *start; + int ret = ts_jsmn_token_by_index(&scratchpad->jsmn, scratchpad->token_idx, + &type, &size, &start, &len); + if (ret != 0) { + TS_LOGD("JSON decoder: %s pull while at end", __func__); + return -ENOMEM; + } + *jsmn_type = type; + return 0; +} + +int ts_msg_pull_array_json(struct thingset_msg* msg, uint16_t *num_elements) +{ + TS_MSG_JSON_DEC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + uint16_t type, size, len; + const char *start; + int ret = ts_jsmn_token_by_index(&scratchpad->jsmn, scratchpad->token_idx, + &type, &size, &start, &len); + if (ret != 0) { + TS_LOGD("JSON decoder: %s pull while at end", __func__); + return -ENOMEM; + } + if (type != TS_JSMN_ARRAY) { + TS_LOGD("JSON decoder: %s with wrong type (%u)", __func__, (unsigned int)type); + return -EINVAL; + } + *num_elements = size; + return ts_msg_json_dec_update(msg, __func__, start, len); +} + +int ts_msg_pull_array_end_json(struct thingset_msg* msg) +{ + return 0; +} + +int ts_msg_pull_bool_json(struct thingset_msg* msg, bool *b) +{ + TS_MSG_JSON_DEC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + uint16_t type, size, len; + const char *start; + int ret = ts_jsmn_token_by_index(&scratchpad->jsmn, scratchpad->token_idx, + &type, &size, &start, &len); + if (ret != 0) { + TS_LOGD("JSON decoder: %s pull while at end", __func__); + return -ENOMEM; + } + if (type != TS_JSMN_PRIMITIVE) { + TS_LOGD("JSON decoder: %s with wrong type (%u)", __func__, (unsigned int)type); + return -EINVAL; + } + + if (*start == 't' || *start == '1') { + *b = true; + } + else if (*start == 'f' || *start == '0') { + *b = false; + } + else { + TS_LOGD("JSON decoder: %s '%.*s' is not a bool", __func__, len, start); + return -EINVAL; + } + return ts_msg_json_dec_update(msg, __func__, start, len); +} + +int ts_msg_pull_decfrac_json(struct thingset_msg* msg, int32_t *mantissa, int16_t *exponent) +{ + TS_MSG_JSON_DEC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + uint16_t type, size, len; + const char *start; + int ret = ts_jsmn_token_by_index(&scratchpad->jsmn, scratchpad->token_idx, + &type, &size, &start, &len); + if (ret != 0) { + TS_LOGD("JSON decoder: %s pull while at end", __func__); + return -ENOMEM; + } + if (type != TS_JSMN_PRIMITIVE) { + TS_LOGD("JSON decoder: %s with wrong type (%u)", __func__, (unsigned int)type); + return -EINVAL; + } + + int32_t val_mantissa; + int16_t val_exponent; + char *endptr = NULL; + val_mantissa = strtol(start, &endptr, 10); + if (endptr > (start + len)) { + TS_LOGD("JSON decoder: %s '%.*s' is not a decfrac", __func__, len, start); + return -EINVAL; + } + if (endptr == (start + len)) { + val_exponent = 0; + } + else if ((*endptr != 'e') && (*endptr != 'E')) { + TS_LOGD("JSON decoder: %s '%.*s' is not a decfrac", __func__, len, start); + return -EINVAL; + } + else { + endptr++; + val_exponent = (int16_t)strtol(endptr, &endptr, 10); + if (endptr != (start + len)) { + TS_LOGD("JSON decoder: %s '%.*s' is not a decfrac", __func__, len, start); + return -EINVAL; + } + } + + *mantissa = val_mantissa; + *exponent = val_exponent; + return ts_msg_json_dec_update(msg, __func__, start, len); +} + +int ts_msg_pull_f32_json(struct thingset_msg* msg, float *val) +{ + TS_MSG_JSON_DEC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + uint16_t type, size, len; + const char *start; + int ret = ts_jsmn_token_by_index(&scratchpad->jsmn, scratchpad->token_idx, + &type, &size, &start, &len); + if (ret != 0) { + TS_LOGD("JSON decoder: %s pull while at end", __func__); + return -ENOMEM; + } + if (type != TS_JSMN_PRIMITIVE) { + TS_LOGD("JSON decoder: %s with wrong type (%u)", __func__, (unsigned int)type); + return -EINVAL; + } + + float val_float; + if (strcmp(start, "null") == 0) { + val_float = NAN; + } + else { + char *endptr = NULL; + val_float = strtod(start, &endptr); + if (endptr != (start + len)) { + TS_LOGD("JSON decoder: %s '%.*s' is not a f32", __func__, len, start); + return -EINVAL; + } + } + *val = val_float; + return ts_msg_json_dec_update(msg, __func__, start, len); +} + +int ts_msg_pull_i16_json(struct thingset_msg* msg, int16_t *val) +{ + TS_MSG_JSON_DEC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + uint16_t type, size, len; + const char *start; + int ret = ts_jsmn_token_by_index(&scratchpad->jsmn, scratchpad->token_idx, + &type, &size, &start, &len); + if (ret != 0) { + TS_LOGD("JSON decoder: %s pull while at end", __func__); + return -ENOMEM; + } + if (type != TS_JSMN_PRIMITIVE) { + TS_LOGD("JSON decoder: %s with wrong type (%u)", __func__, (unsigned int)type); + return -EINVAL; + } + + char *endptr = NULL; + int16_t val_i16 = (int16_t)strtol(start, &endptr, 10); + if (endptr != (start + len)) { + TS_LOGD("JSON decoder: %s '%.*s' is not an i16", __func__, len, start); + return -EINVAL; + } + *val = val_i16; + return ts_msg_json_dec_update(msg, __func__, start, len); +} + +int ts_msg_pull_i32_json(struct thingset_msg* msg, int32_t *val) +{ + TS_MSG_JSON_DEC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + uint16_t type, size, len; + const char *start; + int ret = ts_jsmn_token_by_index(&scratchpad->jsmn, scratchpad->token_idx, + &type, &size, &start, &len); + if (ret != 0) { + TS_LOGD("JSON decoder: %s pull while at end", __func__); + return -ENOMEM; + } + if (type != TS_JSMN_PRIMITIVE) { + TS_LOGD("JSON decoder: %s with wrong type (%u)", __func__, (unsigned int)type); + return -EINVAL; + } + + char *endptr = NULL; + int32_t val_i32 = strtol(start, &endptr, 10); + if (endptr != (start + len)) { + TS_LOGD("JSON decoder: %s '%.*s' is not an i32", __func__, len, start); + return -EINVAL; + } + *val = val_i32; + return ts_msg_json_dec_update(msg, __func__, start, len); +} + +int ts_msg_pull_i64_json(struct thingset_msg* msg, int64_t *val) +{ + TS_MSG_JSON_DEC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + uint16_t type, size, len; + const char *start; + int ret = ts_jsmn_token_by_index(&scratchpad->jsmn, scratchpad->token_idx, + &type, &size, &start, &len); + if (ret != 0) { + TS_LOGD("JSON decoder: %s pull while at end", __func__); + return -ENOMEM; + } + if (type != TS_JSMN_PRIMITIVE) { + TS_LOGD("JSON decoder: %s with wrong type (%u)", __func__, (unsigned int)type); + return -EINVAL; + } + + char *endptr = NULL; + int64_t val_i64 = strtoll(start, &endptr, 10); + if (endptr != (start + len)) { + TS_LOGD("JSON decoder: %s '%.*s' is not an i64", __func__, len, start); + return -EINVAL; + } + *val = val_i64; + return ts_msg_json_dec_update(msg, __func__, start, len); +} + +int ts_msg_pull_map_json(struct thingset_msg* msg, uint16_t *num_elements) +{ + TS_MSG_JSON_DEC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + uint16_t type, size, len; + const char *start; + int ret = ts_jsmn_token_by_index(&scratchpad->jsmn, scratchpad->token_idx, + &type, &size, &start, &len); + if (ret != 0) { + TS_LOGD("JSON decoder: %s pull while at end", __func__); + return -ENOMEM; + } + if (type != TS_JSMN_OBJECT) { + TS_LOGD("JSON decoder: %s with wrong type (%u)", __func__, (unsigned int)type); + return -EINVAL; + } + *num_elements = size; + return ts_msg_json_dec_update(msg, __func__, start, len); +} + +int ts_msg_pull_map_end_json(struct thingset_msg* msg) +{ + return 0; +} + +int ts_msg_pull_mem_json(struct thingset_msg* msg, const uint8_t **mem, uint16_t *mem_len) +{ + TS_MSG_JSON_DEC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + uint16_t type, size, len; + const char *start; + int ret = ts_jsmn_token_by_index(&scratchpad->jsmn, scratchpad->token_idx, + &type, &size, &start, &len); + if (ret != 0) { + TS_LOGD("JSON decoder: %s pull while at end", __func__); + return -ENOMEM; + } + if (type != TS_JSMN_STRING) { + TS_LOGD("JSON decoder: %s with wrong type (%u)", __func__, (unsigned int)type); + return -EINVAL; + } + + /** @todo Do in place conversion from base64 to bytes */ + TS_LOGD("JSON decoder: %s not supported", __func__); + return -ENOTSUP; + + return ts_msg_json_dec_update(msg, __func__, start, len); +} + +int ts_msg_pull_string_json(struct thingset_msg* msg, const char **s, uint16_t *s_len) +{ + TS_MSG_JSON_DEC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + uint16_t type, size, len; + const char *start; + int ret = ts_jsmn_token_by_index(&scratchpad->jsmn, scratchpad->token_idx, + &type, &size, &start, &len); + if (ret != 0) { + TS_LOGD("JSON decoder: %s pull while at end", __func__); + return -ENOMEM; + } + if (type != TS_JSMN_STRING) { + TS_LOGD("JSON decoder: %s with wrong type (%u)", __func__, (unsigned int)type); + return -EINVAL; + } + *s = start; + *s_len = len; + return ts_msg_json_dec_update(msg, __func__, start, len); +} + +int ts_msg_pull_u16_json(struct thingset_msg* msg, uint16_t *val) +{ + TS_MSG_JSON_DEC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + uint16_t type, size, len; + const char *start; + int ret = ts_jsmn_token_by_index(&scratchpad->jsmn, scratchpad->token_idx, + &type, &size, &start, &len); + if (ret != 0) { + TS_LOGD("JSON decoder: %s pull while at end", __func__); + return -ENOMEM; + } + if (type != TS_JSMN_PRIMITIVE) { + TS_LOGD("JSON decoder: %s with wrong type (%u)", __func__, (unsigned int)type); + return -EINVAL; + } + + char *endptr = NULL; + uint16_t val_u16 = (uint16_t)strtoul(start, &endptr, 10); + if (endptr != (start + len)) { + TS_LOGD("JSON decoder: %s '%.*s' is not an u16", __func__, len, start); + return -EINVAL; + } + *val = val_u16; + return ts_msg_json_dec_update(msg, __func__, start, len); +} + +int ts_msg_pull_u32_json(struct thingset_msg* msg, uint32_t *val) +{ + TS_MSG_JSON_DEC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + uint16_t type, size, len; + const char *start; + int ret = ts_jsmn_token_by_index(&scratchpad->jsmn, scratchpad->token_idx, + &type, &size, &start, &len); + if (ret != 0) { + TS_LOGD("JSON decoder: %s pull while at end", __func__); + return -ENOMEM; + } + if (type != TS_JSMN_PRIMITIVE) { + TS_LOGD("JSON decoder: %s with wrong type (%u)", __func__, (unsigned int)type); + return -EINVAL; + } + + char *endptr = NULL; + uint32_t val_u32 = strtoul(start, &endptr, 10); + if (endptr != (start + len)) { + TS_LOGD("JSON decoder: %s '%.*s' is not an u32", __func__, len, start); + return -EINVAL; + } + *val = val_u32; + return ts_msg_json_dec_update(msg, __func__, start, len); +} + +int ts_msg_pull_u64_json(struct thingset_msg* msg, uint64_t *val) +{ + TS_MSG_JSON_DEC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + uint16_t type, size, len; + const char *start; + int ret = ts_jsmn_token_by_index(&scratchpad->jsmn, scratchpad->token_idx, + &type, &size, &start, &len); + if (ret != 0) { + TS_LOGD("JSON decoder: %s pull while at end", __func__); + return -ENOMEM; + } + if (type != TS_JSMN_PRIMITIVE) { + TS_LOGD("JSON decoder: %s with wrong type (%u)", __func__, (unsigned int)type); + return -EINVAL; + } + + char *endptr = NULL; + uint64_t val_u64 = strtoull(start, &endptr, 10); + if (endptr != (start + len)) { + TS_LOGD("JSON decoder: %s '%.*s' is not an u64", __func__, len, start); + return -EINVAL; + } + *val = val_u64; + return ts_msg_json_dec_update(msg, __func__, start, len); +} + +/* CBOR values */ + +int ts_msg_add_array_cbor(struct thingset_msg* msg, uint16_t num_elements) +{ + TS_MSG_CBOR_ENC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + if (scratchpad->current >= 2) { + TS_LOGD("CBOR encoder: %s without more encoders", __func__); + return -ENOMEM; + } + + struct CborEncoder *encoder = ts_msg_scratchpad_tinycbor_enc_current(msg); + scratchpad->current++; + struct CborEncoder *array_encoder = ts_msg_scratchpad_tinycbor_enc_current(msg); + + CborError error = cbor_encoder_create_array(encoder, array_encoder, num_elements); + if (error != CborNoError) { + scratchpad->current--; + } + return ts_msg_scratchpad_tinycbor_enc_update(msg, __func__, error); +} + +int ts_msg_add_array_end_cbor(struct thingset_msg* msg) +{ + if (ts_msg_scratchpad_tinycbor_enc_is_top(msg)) { + /* Can not close on top level encoder */ + TS_LOGD("CBOR encoder: %s close while on top level encoder", __func__); + return -ENOMEM; + } + + TS_MSG_CBOR_ENC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + struct CborEncoder *array_encoder = ts_msg_scratchpad_tinycbor_enc_current(msg); + scratchpad->current--; + struct CborEncoder *encoder = ts_msg_scratchpad_tinycbor_enc_current(msg); + + CborError error = cbor_encoder_close_container(encoder, array_encoder); + + return ts_msg_scratchpad_tinycbor_enc_update(msg, __func__, error); +} + +int ts_msg_add_bool_cbor(struct thingset_msg* msg, bool b) +{ + struct CborEncoder *encoder = ts_msg_scratchpad_tinycbor_enc_current(msg); + CborError error = cbor_encode_boolean(encoder, b); + return ts_msg_scratchpad_tinycbor_enc_update(msg, __func__, error); +} + +int ts_msg_add_decfrac_cbor(struct thingset_msg* msg, int32_t mantissa, int16_t exponent) +{ + /* Tag */ + struct CborEncoder *encoder = ts_msg_scratchpad_tinycbor_enc_current(msg); + CborError error = cbor_encode_tag(encoder, CborDecimalTag); + int ret = ts_msg_scratchpad_tinycbor_enc_update(msg, __func__, error); + if (ret != 0) { + return ret; + } + + /* Value */ + ret = ts_msg_add_array_cbor(msg, 2); + if (ret != 0) { + return ret; + } + ret = ts_msg_add_i16_cbor(msg, exponent); + if (ret != 0) { + return ret; + } + ret = ts_msg_add_i32_cbor(msg, mantissa); + if (ret != 0) { + return ret; + } + ret = ts_msg_add_array_end_cbor(msg); + return ret; +} + +int ts_msg_add_f32_cbor(struct thingset_msg* msg, float val, int precision) +{ + + if (precision == 0) { + // round to 0 digits: use int +#if TS_64BIT_TYPES_SUPPORT + int64_t val_i64 = llroundf(val); + return ts_msg_add_i64_cbor(msg, val_i64); +#else + return ts_msg_add_i32_cbor(msg, lroundf(val)); +#endif + } + struct CborEncoder *encoder = ts_msg_scratchpad_tinycbor_enc_current(msg); + CborError error = cbor_encode_float(encoder, val); + return ts_msg_scratchpad_tinycbor_enc_update(msg, __func__, error); +} + +int ts_msg_add_i64_cbor(struct thingset_msg* msg, int64_t val) +{ + struct CborEncoder *encoder = ts_msg_scratchpad_tinycbor_enc_current(msg); + CborError error = cbor_encode_int(encoder, val); + return ts_msg_scratchpad_tinycbor_enc_update(msg, __func__, error); +} + +int ts_msg_add_map_cbor(struct thingset_msg* msg, uint16_t num_elements) +{ + TS_MSG_CBOR_ENC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + if (scratchpad->current >= 2) { + TS_LOGD("CBOR encoder: %s without more encoders", __func__); + return -ENOMEM; + } + + struct CborEncoder *encoder = ts_msg_scratchpad_tinycbor_enc_current(msg); + scratchpad->current++; + struct CborEncoder *map_encoder = ts_msg_scratchpad_tinycbor_enc_current(msg); + + CborError error = cbor_encoder_create_map(encoder, map_encoder, num_elements); + if (error != CborNoError) { + scratchpad->current--; + } + return ts_msg_scratchpad_tinycbor_enc_update(msg, __func__, error); +} + +int ts_msg_add_map_end_cbor(struct thingset_msg* msg) +{ + if (ts_msg_scratchpad_tinycbor_enc_is_top(msg)) { + /* Can not close on top level encoder */ + TS_LOGD("CBOR encoder: %s close while on top level encoder", __func__); + return -ENOMEM; + } + + TS_MSG_CBOR_ENC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + struct CborEncoder *map_encoder = ts_msg_scratchpad_tinycbor_enc_current(msg); + scratchpad->current--; + struct CborEncoder *encoder = ts_msg_scratchpad_tinycbor_enc_current(msg); + + CborError error = cbor_encoder_close_container(encoder, map_encoder); + + return ts_msg_scratchpad_tinycbor_enc_update(msg, __func__, error); +} + +int ts_msg_add_mem_cbor(struct thingset_msg* msg, const uint8_t *mem, uint16_t len) +{ + struct CborEncoder *encoder = ts_msg_scratchpad_tinycbor_enc_current(msg); + CborError error = cbor_encode_byte_string(encoder, mem, (size_t)len); + return ts_msg_scratchpad_tinycbor_enc_update(msg, __func__, error); +} + +int ts_msg_add_string_cbor(struct thingset_msg* msg, const char *s) +{ + struct CborEncoder *encoder = ts_msg_scratchpad_tinycbor_enc_current(msg); + CborError error = cbor_encode_text_stringz(encoder, s); + return ts_msg_scratchpad_tinycbor_enc_update(msg, __func__, error); +} + +int ts_msg_add_undefined_cbor(struct thingset_msg* msg) +{ + struct CborEncoder *encoder = ts_msg_scratchpad_tinycbor_enc_current(msg); + CborError error = cbor_encode_undefined(encoder); + return ts_msg_scratchpad_tinycbor_enc_update(msg, __func__, error); +} + +int ts_msg_add_u64_cbor(struct thingset_msg* msg, uint64_t val) +{ + struct CborEncoder *encoder = ts_msg_scratchpad_tinycbor_enc_current(msg); + CborError error = cbor_encode_uint(encoder, val); + return ts_msg_scratchpad_tinycbor_enc_update(msg, __func__, error); +} + +int ts_msg_pull_type_cbor(struct thingset_msg* msg, CborType *cbor_type) +{ + CborType type = cbor_value_get_type(ts_msg_scratchpad_tinycbor_dec_current(msg)); + if (type == CborInvalidType) { + TS_LOGD("CBOR decoder: %s %s", __func__, cbor_error_string(type)); + return -EINVAL; + } + TS_LOGD("CBOR decoder: %s %u", __func__, (unsigned int)type); + *cbor_type = type; + return 0; +} + +int ts_msg_pull_array_cbor(struct thingset_msg* msg, uint16_t *num_elements) +{ + TS_MSG_CBOR_DEC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + if (scratchpad->current >= 2) { + TS_LOGD("CBOR decoder: pull array but no more value iterator available"); + return -ENOMEM; + } + + struct CborValue *it = ts_msg_scratchpad_tinycbor_dec_current(msg); + if (cbor_value_at_end(it)) { + TS_LOGD("CBOR decoder: pull array while on end"); + return -ENOMEM; + } + if (!cbor_value_is_array(it)) { + TS_LOGD("CBOR decoder: pull array with wrong type (%u)", + (unsigned int)cbor_value_get_type(it)); + return -EINVAL; + } + + scratchpad->current++; + struct CborValue *array_it = ts_msg_scratchpad_tinycbor_dec_current(msg); + + CborError error = cbor_value_enter_container(it, array_it); + if (error != CborNoError) { + scratchpad->current--; + return -EINVAL; + } + + size_t length; + error = cbor_value_get_array_length(it, &length); + if ((error != CborNoError) || (length > UINT16_MAX)) { + scratchpad->current--; + return -EINVAL; + } + *num_elements = (uint16_t)length; + + return ts_msg_scratchpad_tinycbor_dec_update(msg, false, __func__, CborNoError); +} + +int ts_msg_pull_array_end_cbor(struct thingset_msg* msg) +{ + if (ts_msg_scratchpad_tinycbor_dec_is_top(msg)) { + /* Can not close array on top level value iterator */ + TS_LOGD("CBOR decoder: %s while on top level value iterator", __func__); + return -ENOMEM; + } + + struct CborValue *array_it = ts_msg_scratchpad_tinycbor_dec_current(msg); + if (!cbor_value_at_end(array_it)) { + /* we did not pull all of the values */ + TS_LOGD("CBOR decoder: %s while value iterator still holds value(s)", __func__); + return -EAGAIN; + } + + TS_MSG_CBOR_DEC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + scratchpad->current--; + struct CborValue *it = ts_msg_scratchpad_tinycbor_dec_current(msg); + + CborError error = cbor_value_leave_container(it, array_it); + + return ts_msg_scratchpad_tinycbor_dec_update(msg, false, __func__, error); +} + +int ts_msg_pull_bool_cbor(struct thingset_msg* msg, bool *b) +{ + struct CborValue *it = ts_msg_scratchpad_tinycbor_dec_current(msg); + if ((ts_msg_len(msg) == 0) || cbor_value_at_end(it)) { + TS_LOGD("CBOR decoder: %s pull while at end", __func__); + return -ENOMEM; + } + if (!cbor_value_is_boolean(it)) { + TS_LOGD("CBOR decoder: %s with wrong type (%u)", __func__, + (unsigned int)cbor_value_get_type(it)); + return -EINVAL; + } + + (void)cbor_value_get_boolean(it, b); /* can't fail */ + + return ts_msg_scratchpad_tinycbor_dec_update(msg, true, __func__, CborNoError); +} + +int ts_msg_pull_decfrac_cbor(struct thingset_msg* msg, int32_t *mantissa, int16_t *exponent) +{ + /* Tag */ + struct CborValue *it = ts_msg_scratchpad_tinycbor_dec_current(msg); + if (cbor_value_is_tag(it)) { + CborTag tag; + (void)cbor_value_get_tag(it, &tag); + if (tag != CborDecimalTag) { + TS_LOGD("CBOR decoder: %s wrong tag (%u)", __func__, (unsigned int)tag); + return -EINVAL; + } + (void)ts_msg_scratchpad_tinycbor_dec_update(msg, true, __func__, CborNoError); + } + else { + TS_LOGD("CBOR decoder: %s not tagged", __func__); + if (!cbor_value_is_container(it)) { + TS_LOGD("CBOR decoder: %s value is not a container", __func__); + return -EINVAL; + } + } + + /* Value */ + uint16_t num_elements; + int ret = ts_msg_pull_array_cbor(msg, &num_elements); + if (ret != 0) { + return ret; + } + if (num_elements != 2) { + TS_LOGD("CBOR decoder: %s wrong number of elements (%u)", __func__, + (unsigned int)num_elements); + return -EINVAL; + } + int32_t val_mantissa; + int16_t val_exponent; + ret = ts_msg_pull_i16_cbor(msg, &val_exponent); + if (ret != 0) { + return ret; + } + ret = ts_msg_pull_i32_cbor(msg, &val_mantissa); + if (ret != 0) { + return ret; + } + ret = ts_msg_pull_array_end_cbor(msg); + if (ret != 0) { + return ret; + } + + *mantissa = val_mantissa; + *exponent = val_exponent; + return 0; +} + +int ts_msg_pull_f32_cbor(struct thingset_msg* msg, float *val) +{ + struct CborValue *it = ts_msg_scratchpad_tinycbor_dec_current(msg); + if (cbor_value_at_end(it)) { + TS_LOGD("CBOR decoder: %s pull while at end", __func__); + return -ENOMEM; + } + CborError error; + if (cbor_value_is_float(it)) { + error = cbor_value_get_float(it, val); + } + else if (cbor_value_is_integer(it)) { + uint64_t val_uint64; + error = cbor_value_get_uint64(it, &val_uint64); + *val = (float)val_uint64; + } + else { + TS_LOGD("CBOR decoder: pull f32 with wrong type (%u)", + (unsigned int)cbor_value_get_type(it)); + return -EINVAL; + } + return ts_msg_scratchpad_tinycbor_dec_update(msg, true, __func__, error); +} + +int ts_msg_pull_i16_cbor(struct thingset_msg* msg, int16_t *val) +{ + struct CborValue *it = ts_msg_scratchpad_tinycbor_dec_current(msg); + if ((ts_msg_len(msg) == 0) || cbor_value_at_end(it)) { + TS_LOGD("CBOR decoder: %s while at end", __func__); + return -ENOMEM; + } + if (!cbor_value_is_integer(it)) { + TS_LOGD("CBOR decoder: %s with wrong type (%u)", __func__, + (unsigned int)cbor_value_get_type(it)); + return -EINVAL; + } + + int64_t val_i64; + (void)cbor_value_get_int64(it, &val_i64); /* can't fail */ + if ((val_i64 < INT16_MIN) || (val_i64 > INT16_MAX)) { + TS_LOGD("CBOR decoder: %s value exceeds range (%" PRIi64 ")", __func__, val_i64); + return EINVAL; + } + *val = (int16_t)val_i64; + return ts_msg_scratchpad_tinycbor_dec_update(msg, true, __func__, CborNoError); +} + +int ts_msg_pull_i32_cbor(struct thingset_msg* msg, int32_t *val) +{ + struct CborValue *it = ts_msg_scratchpad_tinycbor_dec_current(msg); + if ((ts_msg_len(msg) == 0) || cbor_value_at_end(it)) { + TS_LOGD("CBOR decoder: %s while at end", __func__); + return -ENOMEM; + } + if (!cbor_value_is_integer(it)) { + TS_LOGD("CBOR decoder: %s with wrong type (%u)", __func__, + (unsigned int)cbor_value_get_type(it)); + return -EINVAL; + } + + int64_t val_i64; + (void)cbor_value_get_int64(it, &val_i64); /* can't fail */ + if ((val_i64 < INT32_MIN) || (val_i64 > INT32_MAX)) { + TS_LOGD("CBOR decoder: %s value exceeds range (%" PRIu64 ")", __func__, val_i64); + return EINVAL; + } + *val = (int32_t)val_i64; + return ts_msg_scratchpad_tinycbor_dec_update(msg, true, __func__, CborNoError); +} + +int ts_msg_pull_i64_cbor(struct thingset_msg* msg, int64_t *val) +{ + struct CborValue *it = ts_msg_scratchpad_tinycbor_dec_current(msg); + if ((ts_msg_len(msg) == 0) || cbor_value_at_end(it)) { + TS_LOGD("CBOR decoder: %s while at end", __func__); + return -ENOMEM; + } + if (!cbor_value_is_integer(it)) { + TS_LOGD("CBOR decoder: %s with wrong type (%u)", __func__, + (unsigned int)cbor_value_get_type(it)); + return -EINVAL; + } + + (void)cbor_value_get_int64(it, val); /* can't fail */ + + return ts_msg_scratchpad_tinycbor_dec_update(msg, true, __func__, CborNoError); +} + +int ts_msg_pull_map_cbor(struct thingset_msg* msg, uint16_t *num_elements) +{ + TS_MSG_CBOR_DEC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + if (scratchpad->current >= 2) { + TS_LOGD("CBOR decoder: pull map but no more value iterator available"); + return -ENOMEM; + } + + struct CborValue *it = ts_msg_scratchpad_tinycbor_dec_current(msg); + if (cbor_value_at_end(it)) { + TS_LOGD("CBOR decoder: pull map while on end"); + return -ENOMEM; + } + if (!cbor_value_is_map(it)) { + TS_LOGD("CBOR decoder: pull map with wrong type (%u)", + (unsigned int)cbor_value_get_type(it)); + return -EINVAL; + } + + scratchpad->current++; + struct CborValue *map_it = ts_msg_scratchpad_tinycbor_dec_current(msg); + + CborError error = cbor_value_enter_container(it, map_it); + if (error != CborNoError) { + scratchpad->current--; + return -EINVAL; + } + + size_t length; + error = cbor_value_get_map_length(it, &length); + if ((error != CborNoError) || (length > UINT16_MAX)) { + scratchpad->current--; + return -EINVAL; + } + *num_elements = (uint16_t)length; + + return ts_msg_scratchpad_tinycbor_dec_update(msg, false, __func__, CborNoError); +} + +int ts_msg_pull_map_end_cbor(struct thingset_msg* msg) +{ + if (ts_msg_scratchpad_tinycbor_dec_is_top(msg)) { + /* Can not close map on top level value iterator */ + TS_LOGD("CBOR decoder: %s while on top level value iterator", __func__); + return -ENOMEM; + } + + struct CborValue *map_it = ts_msg_scratchpad_tinycbor_dec_current(msg); + if (!cbor_value_at_end(map_it)) { + /* we did not pull all of the values */ + TS_LOGD("CBOR decoder: %s while value iterator still holds value(s)", __func__); + return -EAGAIN; + } + + TS_MSG_CBOR_DEC_SCRATCHPAD_PTR_INIT(scratchpad, msg); + scratchpad->current--; + struct CborValue *it = ts_msg_scratchpad_tinycbor_dec_current(msg); + + CborError error = cbor_value_leave_container(it, map_it); + + return ts_msg_scratchpad_tinycbor_dec_update(msg, false, __func__, error); +} + +int ts_msg_pull_mem_cbor(struct thingset_msg* msg, const uint8_t **mem, uint16_t *len) +{ + struct CborValue *it = ts_msg_scratchpad_tinycbor_dec_current(msg); + if ((ts_msg_len(msg) == 0) || cbor_value_at_end(it)) { + TS_LOGD("CBOR decoder: %s while at end", __func__); + return -ENOMEM; + } + if (!cbor_value_is_byte_string(it)) { + TS_LOGD("CBOR decoder: %s with wrong type (%u)", __func__, + (unsigned int)cbor_value_get_type(it)); + return -EINVAL; + } + if (!cbor_value_is_length_known(it)) { + TS_LOGD("CBOR decoder: %s with unknown length", __func__); + return -EINVAL; + } + + size_t val_len; + (void)cbor_value_get_string_length(it, &val_len); /* error already checked */ + + CborError error = cbor_value_advance(it); + *mem = cbor_value_get_next_byte(it) - val_len; + *len = (uint16_t)val_len; + + return ts_msg_scratchpad_tinycbor_dec_update(msg, false, __func__, error); +} + +int ts_msg_pull_string_cbor(struct thingset_msg* msg, const char **s, uint16_t *len) +{ + struct CborValue *it = ts_msg_scratchpad_tinycbor_dec_current(msg); + if ((ts_msg_len(msg) == 0) || cbor_value_at_end(it)) { + TS_LOGD("CBOR decoder: %s while at end", __func__); + return -ENOMEM; + } + if (!cbor_value_is_text_string(it)) { + TS_LOGD("CBOR decoder: %s with wrong type (%u)", __func__, + (unsigned int)cbor_value_get_type(it)); + return -EINVAL; + } + if (!cbor_value_is_length_known(it)) { + TS_LOGD("CBOR decoder: %s with unknown length", __func__); + return -EINVAL; + } + + size_t val_len; + (void)cbor_value_get_string_length(it, &val_len); /* error already checked */ + + CborError error = cbor_value_advance(it); + *s = cbor_value_get_next_byte(it) - val_len; + *len = (uint16_t)val_len; + + return ts_msg_scratchpad_tinycbor_dec_update(msg, false, __func__, error); +} + +int ts_msg_pull_u16_cbor(struct thingset_msg* msg, uint16_t *val) +{ + struct CborValue *it = ts_msg_scratchpad_tinycbor_dec_current(msg); + if ((ts_msg_len(msg) == 0) || cbor_value_at_end(it)) { + TS_LOGD("CBOR decoder: %s while at end", __func__); + return -ENOMEM; + } + if (!cbor_value_is_unsigned_integer(it)) { + TS_LOGD("CBOR decoder: %s with wrong type (%u)", __func__, + (unsigned int)cbor_value_get_type(it)); + return -EINVAL; + } + + uint64_t val_u64; + (void)cbor_value_get_uint64(it, &val_u64); /* can't fail */ + if (val_u64 > UINT16_MAX) { + TS_LOGD("CBOR decoder: %s value exceeds range (%" PRIu64 ")", __func__, val_u64); + return EINVAL; + } + *val = (uint16_t)val_u64; + return ts_msg_scratchpad_tinycbor_dec_update(msg, true, __func__, CborNoError); +} + +int ts_msg_pull_u32_cbor(struct thingset_msg* msg, uint32_t *val) +{ + struct CborValue *it = ts_msg_scratchpad_tinycbor_dec_current(msg); + if ((ts_msg_len(msg) == 0) || cbor_value_at_end(it)) { + TS_LOGD("CBOR decoder: %s while at end", __func__); + return -ENOMEM; + } + if (!cbor_value_is_unsigned_integer(it)) { + TS_LOGD("CBOR decoder: %s with wrong type (%u)", __func__, + (unsigned int)cbor_value_get_type(it)); + return -EINVAL; + } + + uint64_t val_u64; + (void)cbor_value_get_uint64(it, &val_u64); /* can't fail */ + if (val_u64 > UINT32_MAX) { + TS_LOGD("CBOR decoder: %s value exceeds range (%" PRIu64 ")", __func__, val_u64); + return EINVAL; + } + *val = (uint32_t)val_u64; + return ts_msg_scratchpad_tinycbor_dec_update(msg, true, __func__, CborNoError); +} + +int ts_msg_pull_u64_cbor(struct thingset_msg* msg, uint64_t *val) +{ + struct CborValue *it = ts_msg_scratchpad_tinycbor_dec_current(msg); + if ((ts_msg_len(msg) == 0) || cbor_value_at_end(it)) { + TS_LOGD("CBOR decoder: %s while at end", __func__); + return -ENOMEM; + } + if (!cbor_value_is_unsigned_integer(it)) { + TS_LOGD("CBOR decoder: %s with wrong type (%u)", __func__, + (unsigned int)cbor_value_get_type(it)); + return -EINVAL; + } + + (void)cbor_value_get_uint64(it, val); /* can't fail */ + + return ts_msg_scratchpad_tinycbor_dec_update(msg, true, __func__, CborNoError); +} From bb198eab2a63383efbac7d232f9d1d4bebeb25de Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Sun, 19 Dec 2021 08:30:55 +0100 Subject: [PATCH 19/37] COM prepare - add ThingSet port interface for communication ports Prepare for ThingSet Communication Context: - Add ThingSet interface for communication ports. - Add macro to define a port THINGSET_PORT A port is an interface utilized by ThingSet for ThingSet communication. It abstracts the physical/logical interface. Signed-off-by: Bobby Noelte --- src/thingset_port.h | 263 ++++++++++++++++++++++++++++++++++++++++++++ src/ts_port.c | 54 +++++++++ src/ts_port.h | 142 ++++++++++++++++++++++++ 3 files changed, 459 insertions(+) create mode 100644 src/thingset_port.h create mode 100644 src/ts_port.c create mode 100644 src/ts_port.h diff --git a/src/thingset_port.h b/src/thingset_port.h new file mode 100644 index 0000000..a0919c3 --- /dev/null +++ b/src/thingset_port.h @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet communication port (public interface) + */ + +#ifndef THINGSET_PORT_H_ +#define THINGSET_PORT_H_ + +/** + * @brief ThingSet communication port. + * + * An interface utilized by a ThingSet local context for ThingSet communication. + * + * @note All structure definitions and functions that start with the prefix 'ts_' are not part of + * the public API and are just here for technical reasons. They should not be used in + * applications. + * + * @defgroup ts_port_api_pub ThingSet communication port (public interface) + * @{ + */ + +#include "thingset_env.h" + +#include "ts_macro.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Forward declaration */ +typedef uint8_t thingset_locid_t; +struct thingset_port; +struct thingset_msg; + +/** + * @brief ThingSet communication port identifier type. + * + * Port identifiers are specifc to a ThingSet device. + */ +typedef uint8_t thingset_portid_t; + +/** + * @def THINGSET_PORT_ID_INVALID + * + * @brief Invalid port identifier + */ +#define THINGSET_PORT_ID_INVALID UINT8_MAX + +/** + * @brief Signature of ThingSet communication port initialisation function. + * + * @param[in] port Port. + * @return 0 on success, <0 otherwise. + */ +typedef int (*thingset_port_init_fn_t)(const struct thingset_port *port); + +/** + * @brief Signature of ThingSet communication port run function. + * + * @param[in] port Port. + * @return 0 on success, <0 otherwise. + */ +typedef int (*thingset_port_run_fn_t)(const struct thingset_port *port); + + +/** + * @brief Signature of ThingSet communication port transmit function. + * + * @param[in] port Port to send at. + * @param[in] msg Pointer to message buffer to be send. + * @param[in] timeout_ms maximum time to wait in milliseconds. + * @return 0 on success, <0 otherwise. + */ +typedef int (*thingset_port_transmit_fn_t)(const struct thingset_port *port, struct thingset_msg *msg, + thingset_time_ms_t timeout_ms); +/** + * @} + * @addtogroup ts_port_api_priv + * @{ + */ + +/** + * @brief ThingSet communication port API function struct. + */ +struct ts_port_api { + /** + * @brief Initialize ThingSet communication port. + * + * @param[in] port Port. + */ + int (*init)(const struct thingset_port *port); + + /** + * @brief Run ThingSet communication port. + * + * @param[in] port Port. + */ + int (*run)(const struct thingset_port *port); + + /** + * @brief Transmit buffer on port. + * + * @param[in] port Port to send at. + * @param[in] msg Pointer to message buffer to be send. + * @param[in] timeout_ms maximum time to wait in milliseconds. + */ + int (*transmit)(const struct thingset_port *port, struct thingset_msg *msg, + thingset_time_ms_t timeout_ms); +}; + +/** + * @} + * @addtogroup ts_port_api_pub + * @{ + */ + +/** + * @brief A ThingSet communication port. + * + * Runtime port structure (in ROM) per port instance. + */ +struct thingset_port { + /** @brief Identifier of the ThingSet local context the port is attached to. */ + thingset_locid_t loc_id; + /** @brief Name of the port instance */ + const char *name; + /** @brief Address of port instance config information */ + const void *config; + /** @brief Address of the API structure exposed by the port instance */ + const struct ts_port_api *api; + /** @brief Address of the port instance private data */ + void *data; +}; + +/** + * @def THINGSET_PORT_DATA_STRUCT + * + * @brief Define a ThingSet port data structure type. + * + * The port data structure type is part of the port's public interface and has to be provided to + * allow the definition of a port instance by @ref THINGSET_PORT_DEFINE. + * + * Use it like this define the port data structure type in an appropriate header file: + * + * THINGSET_PORT_DATA_STRUCT(loopback_simple) { + * thingset_portid_t other_port_id; + * }; + * + * @param port_type The type name of the port. + */ +#define THINGSET_PORT_DATA_STRUCT(port_type) struct TS_CAT(port_type, _data) + +/** + * @def THINGSET_PORT_CONFIG_STRUCT + * + * @brief Define a ThingSet port data structure type. + * + * The port config structure type is part a the port's public interface and has to be provided to + * allow the definition of a port instance by @ref THINGSET_PORT_DEFINE. + * + * Use it like this define the port config structure type in an appropriate header file: + * + * THINGSET_PORT_CONFIG_STRUCT(loopback_simple) { + * const char *other_port; + * }; + * + * @param port_type The type name of the port. + */ +#define THINGSET_PORT_CONFIG_STRUCT(port_type) struct TS_CAT(port_type, _config) + +#define THINGSET_PORT_CONFIG(port_type, port) \ + ((THINGSET_PORT_CONFIG_STRUCT(port_type) *)(port->config)) + +#define THINGSET_PORT_DATA(port_type, port) \ + ((THINGSET_PORT_DATA_STRUCT(port_type) *)(port->data)) + +/** + * @def THINGSET_PORT_TYPE + * + * @brief Define a ThingSet communication port type. + * + * @note To be used for port implementation only. Users (creators) of a port instances do not need + * to define the port type. It is implictly made known by @ref THINGSET_PORT_DEFINE. + * + * @param port_type The type name of the port. + * @param init_fn Initialisation function. + * @param run_fn Run function. + * @param transmit_fn Transmit function. + */ +#define THINGSET_PORT_TYPE(port_type, init_fn, run_fn, transmit_fn) \ + const struct ts_port_api TS_CAT(port_type, _api) = { \ + .init = &init_fn, \ + .run = &run_fn, \ + .transmit = transmit_fn } + +/** + * @def THINGSET_PORT_DEFINE + * + * @brief Define a ThingSet port instance. + * + * This macro defines a ThingSet port instance. + * + * Use it like this to define a port instance: + * + * THINGSET_PORT_DEFINE(MY_PORT_ID, my_port_type, my_port_name, MY_PORT_LOCID, + * .my_port_config_attr = ...); + * + * @param[in] port_id Port identifier. Paramter must expand to a positiv number. + * Limit defined by @ref TS_CONFIG_PORT_COUNT. + * @param[in] port_type The type of the port. + * @param[in] port_name The name this instance of the port exposes to the system. + * @param[in] port_loc_id Identifier of ThingSet local context the port is attached to. + * @param ... Initialisation values for the port's immutable data + * (see @ref THINGSET_PORT_CONFIG_STRUCT). + */ +#define THINGSET_PORT_DEFINE(port_id, port_type, port_name, port_loc_id, ...) \ + TS_STATIC_ASSERT(port_id < TS_CONFIG_PORT_COUNT, "port id >= " \ + TS_STRINGIFY(TS_CONFIG_PORT_COUNT)); \ + extern const struct ts_port_api TS_CAT(port_type, _api); \ + THINGSET_PORT_DATA_STRUCT(port_type) TS_CAT(ts_port_data_, port_id); \ + const THINGSET_PORT_CONFIG_STRUCT(port_type) TS_CAT(ts_port_config_, port_id) = { \ + __VA_ARGS__ }; \ + const struct thingset_port TS_CAT(ts_port_, port_id) = { \ + .loc_id = port_loc_id, \ + .name = port_name, \ + .config = &TS_CAT(ts_port_config_, port_id), \ + .api = &TS_CAT(port_type, _api), \ + .data = &TS_CAT(ts_port_data_, port_id) } + +/** + * @brief Get name of port instance. + * + * @param[in] port_id Port identifier. + * @return name of post instance. + */ +const char *thingset_port_name(thingset_portid_t port_id); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +/** + * @} + */ + +/** + * @page ts_topic_port Ports + * + * Ports allow to communicate with other ThingSet instances via physical or logical interfaces. + * Physical interfaces may be RS232, RS485, SPI, CAN, WiFi, Ethernet, ... . Logical interfaces may + * be everything that translates between the ThingSet protocol and other worlds. Ports for logical + * interfaces are called virtual ports. + * + * A device holds exactly one ports table with descriptors for all it's ports. A port is associated + * to just one local context. + */ + +#endif /* THINGSET_PORT_H_ */ diff --git a/src/ts_port.c b/src/ts_port.c new file mode 100644 index 0000000..e37dfbb --- /dev/null +++ b/src/ts_port.c @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "thingset_env.h" + +#include "ts_port.h" + +#include + +/* ThingSet device port table. */ +const struct thingset_port *const ts_ports[TS_CONFIG_PORT_COUNT] = { +#if TS_CONFIG_PORT_COUNT > 0 + &ts_port_0, +#endif +#if TS_CONFIG_PORT_COUNT > 1 + &ts_port_1, +#endif +#if TS_CONFIG_PORT_COUNT > 2 + &ts_port_2, +#endif +#if TS_CONFIG_PORT_COUNT > 3 + &ts_port_3, +#endif +#if TS_CONFIG_PORT_COUNT > 4 + &ts_port_4, +#endif +#if TS_CONFIG_PORT_COUNT > 5 +#error "Device ports limited to 5" +#endif +}; + +int ts_ports_init(thingset_locid_t locid) +{ + for (thingset_portid_t port_id = 0; port_id < TS_CONFIG_PORT_COUNT; port_id++) { + const struct thingset_port *port = ts_ports[port_id]; + if (port->loc_id != locid) { + continue; + } + if (port->api->init != NULL) { + int ret = ts_port_init(port); + if (ret != 0) { + return ret; + } + } + } + return 0; +} + +const char *thingset_port_name(thingset_portid_t port_id) +{ + return ts_port_by_id(port_id)->name; +} diff --git a/src/ts_port.h b/src/ts_port.h new file mode 100644 index 0000000..6f5c9dd --- /dev/null +++ b/src/ts_port.h @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet communication port (private interface) + */ + +#ifndef TS_PORT_H_ +#define TS_PORT_H_ + +/** + * @brief ThingSet communication port. + * + * @defgroup ts_port_api_priv ThingSet communication port (private interface) + * @{ + */ + +#include + +#include "thingset_port.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @def TS_PORT_ID_PHANTOM + * + * @brief Phantom port identifier. + * + * A phantom port represents a port that we assume to exist but needs to be fully evaluated. + * This may be the case if we want to access a ThingSet device but do not know which port to use + * for the ThingSet messages. + */ +#define TS_PORT_ID_PHANTOM (THINGSET_PORT_ID_INVALID - 1) + +/* Device ThingSet ports */ +#if TS_CONFIG_PORT_COUNT > 0 +extern const struct thingset_port ts_port_0; +#endif +#if TS_CONFIG_PORT_COUNT > 1 +extern const struct thingset_port ts_port_1; +#endif +#if TS_CONFIG_PORT_COUNT > 2 +extern const struct thingset_port ts_port_2; +#endif +#if TS_CONFIG_PORT_COUNT > 3 +extern const struct thingset_port ts_port_3; +#endif +#if TS_CONFIG_PORT_COUNT > 4 +extern const struct thingset_port ts_port_4; +#endif +#if TS_CONFIG_PORT_COUNT > 5 +#error "Device ports limited to 5" +#endif + +/** + * @brief ThingSet device port table. + * + * Array of pointers to ports. Array index == port id. + */ +extern const struct thingset_port *const ts_ports[TS_CONFIG_PORT_COUNT]; + +/** + * @brief Initialize the ports of the ThingSet device port table. + * + * @return 0 on success, <0 otherwise. + */ +int ts_ports_init(thingset_locid_t locid); + +/** + * @brief Get ThingSet port by port ID. + * + * @param[in] port_id Port ID. + * @return Pointer to port structure. + */ +static inline const struct thingset_port *ts_port_by_id(thingset_portid_t port_id) +{ + TS_ASSERT(port_id < TS_ARRAY_SIZE(ts_ports), + "PORT: %s invalid port id %u given with maximum port id %d", __func__, + (unsigned int)port_id, (unsigned int)TS_ARRAY_SIZE(ts_ports)); + +#if TS_CONFIG_PORT_COUNT == 0 + return NULL; +#else + return ts_ports[port_id]; +#endif +} + +/** + * @brief Initialize ThingSet port. + * + * @param[in] port Port. + */ +static inline int ts_port_init(const struct thingset_port *port) +{ +#if TS_CONFIG_PORT_COUNT == 0 + return 0; +#else + return port->api->init(port); +#endif +}; + +/** + * @brief Run ThingSet communication port. + * + * @param[in] port Port. + */ +static inline int ts_port_run(const struct thingset_port *port) +{ + return port->api->run(port); +}; + +/* + * Helpers + * ------- + */ + +/** + * @brief Check for same port. + * + * @param port_a Pointer to port. + * @param port_b Pointer to port. + * @returns true on same, false otherwise. + */ +static inline bool ts_port_same(const struct thingset_port *port_a, const struct thingset_port *port_b) +{ + return (port_a == port_b); +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +/** + * @} + */ + +#endif /* TS_PORT_H_ */ From 6fc41caf819fe11e871d613f3243506c6cd5cadb Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Sun, 19 Dec 2021 08:36:28 +0100 Subject: [PATCH 20/37] COM prepare - add ThingSet communication context Prepare for ThingSet Communication Context: - Add generic context that can be implemented by different specific contexts. - Add available context as specific core context. The core context feature set targets simple devices that does not need full communication support and basically only provides ThingSet object data. - Add specific communication context. The communication context feature set targets complex devices and applications that needs to use or control other remote devices. The context implementation allows to have several contexts on the same device. Each context may either be a core context or a communication context. Signed-off-by: Bobby Noelte --- src/thingset_ctx.h | 840 ++++++++++++++++++++++++++ src/ts_ctx.c | 226 +++++++ src/ts_ctx.h | 536 +++++++++++++++++ src/ts_ctx_cmd.c | 34 ++ src/ts_ctx_core.c | 71 +++ src/ts_ctx_export.c | 72 +++ src/ts_ctx_msg.c | 60 ++ src/ts_ctx_node.c | 190 ++++++ src/ts_ctx_obj.c | 57 ++ src/ts_ctx_process.c | 1354 ++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 3440 insertions(+) create mode 100644 src/thingset_ctx.h create mode 100644 src/ts_ctx.c create mode 100644 src/ts_ctx.h create mode 100644 src/ts_ctx_cmd.c create mode 100644 src/ts_ctx_core.c create mode 100644 src/ts_ctx_export.c create mode 100644 src/ts_ctx_msg.c create mode 100644 src/ts_ctx_node.c create mode 100644 src/ts_ctx_obj.c create mode 100644 src/ts_ctx_process.c diff --git a/src/thingset_ctx.h b/src/thingset_ctx.h new file mode 100644 index 0000000..f8b2819 --- /dev/null +++ b/src/thingset_ctx.h @@ -0,0 +1,840 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet local context (public interface) + */ + +#ifndef THINGSET_CTX_H_ +#define THINGSET_CTX_H_ + +/** + * @brief Abstraction over ThingSet local context. + * + * The abstraction of a ThingSet local context provides a 'generic' local context that is + * implemented by a specific variant of local context - core or communication. + * There is the generic local context identifier that provides access to a local context without + * actually knowing the exact variant of a context that is currently used. The local context + * identifier is an unsigned integer value of type @ref thingset_locid_t. + * + * Access to the different variants of context is hiden behind the set of `generic` functions with + * prefix `thingset_` that use the generic local context identifier and handle all variants of + * contexts. + * + * Local contexts are defined with the help of special macros - @ref THINGSET_CORE_DEFINE and + * @ref THINGSET_COM_DEFINE. + * + * - @ref THINGSET_CORE_DEFINE expects that just one core context exists per device. The local + * context identifier is given by @ref TS_CONFIG_CORE_LOCID, which by default is 0. + * - @ref THINGSET_COM_DEFINE takes the local context identifier as a macro parameter that is + * expected to expand to an unsigned number. + * + * @defgroup ts_ctx_api_pub ThingSet local context (public interface) + * @{ + */ + +#include "thingset_env.h" +#include "thingset_time.h" +#include "thingset_obj.h" +#include "thingset_msg.h" +#include "thingset_port.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @} + * @addtogroup ts_ctx_api_priv + * @{ + */ + +/** + * @brief ThingSet invalid context type identifier. + */ +#define TS_CTX_TYPE_INVALID 0 + +/** + * @brief ThingSet core context type identifier. + */ +#define TS_CTX_TYPE_CORE 1 + +/** + * @brief ThingSet communication context type identifier. + */ +#define TS_CTX_TYPE_COM 2 + +/** + * @} + * @addtogroup ts_ctx_api_pub + * @{ + */ + +/** + * @def THINGSET_LOCID_DEFAULT + * + * @brief Default ThingSet local context identifier. + */ +#define THINGSET_LOCID_DEFAULT 0 + +/** + * @brief ThingSet local context identifier. + * + * A local context identifier identifies a ThingSet context that is local to a device. + * + * The default local context identifier is 0. + */ +typedef uint8_t thingset_locid_t; + +/** + * @brief ThingSet unique context identifier. + * + * A unique context identifier identifies a specific ThingSet context. It shall be unique within all + * contexts known to a device - local and remote ones. + * + * The unique context identifier may be equivalent to a device ID as exposed through the application + * layer protocol. It may be translated to a communication port specific addressing (e.g. CAN ID). + */ +typedef uint64_t thingset_uid_t; + +/** + * @} + * @addtogroup ts_ctx_api_priv + * @{ + */ + +/** + * @brief Context node table element. + * + * Node information of other node known to this ThingSet context. + */ +struct ts_ctx_node { + /** @brief Unique context identifier of this node. */ + thingset_uid_t ctx_uid; + + /** + * @brief Reference to port (by port table entry) this node was seen last. + */ + thingset_portid_t port_id; + + /** + * @brief Time the node was last seen on the port. + */ + thingset_time_ms_t last_seen_time; + + /** + * @brief Reference to port (by port table entry) that responses from this node shall + * be routed to. + */ + thingset_portid_t response_port_id; +}; + +/** + * @brief Context node table. + */ +struct ts_ctx_node_table { + /** + * @brief Context node table elements. + */ + struct ts_ctx_node nodes[TS_CONFIG_NODETABLE_SIZE]; +}; + +/** + * @brief Private data of a ThingSet context object. + */ +struct ts_ctx_data { + /** + * @brief Mutex for message processing. + */ + pthread_mutex_t process_mutex; + + /** + * @brief Communication protocol to use for application initiated communication. + */ + bool app_protocol_use_bin; + + /** + * @brief Current authorisation status (authorisation as "normal" user as default) + */ + uint16_t _auth_flags; +}; + +/** + * @brief Private data of a ThingSet core context object. + */ +struct ts_ctx_core_data { + /** + * @brief Data common to all context variants. + * + * Must be first to make context data types interchangeable + */ + struct ts_ctx_data common; + + /** + * @brief Pointer to response message buffer. + * + * Data is provided in thingset_process_buf(). + */ + uint8_t *resp_buf; + + /** + * @brief Size of response message buffer. + * + * Data is provided in thingset_process_buf(). + */ + uint16_t resp_buf_size; +}; + +/** + * @brief Private data of a ThingSet communication context object. + */ +struct ts_ctx_com_data { + /** + * @brief Data common to all context variants. + * + * Must be first to make context data types interchangeable + */ + struct ts_ctx_data common; + + /** + * @brief Message queue for message processing by context. + */ + struct ts_impl_bufq process_bufq; + + /** + * @brief Table of nodes known by this context. + */ + struct ts_ctx_node_table node_table; +}; + +/** + * @brief ThingSet generic local context. + * + * Runtime generic local context structure (in ROM) per context object. + */ +struct ts_ctx { + /** + * @brief Type of this local context. + * + * One of @p TS_CTX_TYPE_CORE or @p TS_CTX_TYPE_COM + */ + uint16_t ctx_type; + + /** @brief Data objects database ID of this local context. */ + ts_obj_db_id_t db_id; + + /** + * @brief Pointer to generic local context data. + * + * The pointer may be casted to the specific context data structure. That is why the specific + * context data always have the common context data as it's first element. + */ + struct ts_ctx_data *data; + + /** @brief Pointer to specific variant context. */ + const void *variant; +}; + +/** + * @brief Core variant ThingSet local context. + * + * Runtime core variant local context structure (in ROM) per context object. + */ +struct ts_ctx_core { + /* This is a placeholder for future core context extensions */ +}; + +/** + * @brief Communication variant ThingSet local context. + * + * Runtime communication variant local context structure (in ROM) per context object. + */ +struct ts_ctx_com { + /* This is a placeholder for future communication context extensions */ +}; + +/* + * ThingSet generic context support + * -------------------------------- + */ + +/** @cond IGNORE */ + +#if TS_CONFIG_LOCAL_COUNT > 0 +extern const struct ts_ctx ts_ctx_0; +#endif +#if TS_CONFIG_LOCAL_COUNT > 1 +extern const struct ts_ctx ts_ctx_1; +#endif +#if TS_CONFIG_LOCAL_COUNT > 2 +extern const struct ts_ctx ts_ctx_2; +#endif +#if TS_CONFIG_LOCAL_COUNT > 3 +extern const struct ts_ctx ts_ctx_3; +#endif +#if TS_CONFIG_LOCAL_COUNT > 4 +extern const struct ts_ctx ts_ctx_4; +#endif +#if TS_CONFIG_LOCAL_COUNT > 5 +#error "Local contexts limited to 5" +#endif + +/* forward declarations for public interface */ +extern thingset_oref_t ts_obj_db_oref_root(ts_obj_db_id_t did); +extern thingset_oref_t ts_obj_db_oref_any(ts_obj_db_id_t did); +extern thingset_oref_t ts_obj_db_oref_by_object(const struct ts_obj *object); +extern int ts_obj_db_oref_by_id(ts_obj_db_id_t did, ts_obj_id_t obj_id, thingset_oref_t *oref); +extern int ts_obj_by_path(thingset_oref_t parent, const char *path, size_t len, + thingset_oref_t *oref); +extern ts_obj_db_id_t ts_ctx_obj_db(thingset_locid_t locid); + +/** @endcond */ + +/** + * @} + * @addtogroup ts_ctx_api_pub + * @{ + */ + +/** + * @brief Initialize a ThingSet local context. + * + * @param[in] locid ThingSet local context identifier. + * @returns 0 on success, <0 otherwise. + */ +int thingset_init(thingset_locid_t locid); + +/** + * @brief Run a ThingSet local context. + * + * @param[in] locid ThingSet local context identifier. + * @returns 0 on success, <0 otherwise. + */ +int thingset_run(thingset_locid_t locid); + +/** + * @brief Use text mode protocol for application initiated communication. + * + * @param[in] locid ThingSet local context identifier. + */ +void thingset_protocol_set_txt(thingset_locid_t locid); + +/** + * @brief Use binary mode protocol for application initiated communication. + * + * @param[in] locid ThingSet local context identifier. + */ +void thingset_protocol_set_bin(thingset_locid_t locid); + +/** + * @brief Get current authorisation level. + * + * The authorisation flags must match with access flags to allow read/write access to a data object. + * + * @param[in] locid ThingSet local context identifier. + * @return Flags that define the authorisation level (1 = access allowed) + */ +uint16_t thingset_authorisation(thingset_locid_t locid); + +/** + * @brief Set current authorisation level. + * + * The authorisation flags must match with access flags to allow read/write access to a data object. + * + * @param[in] locid ThingSet local context identifier. + * @param[in] auth Flags to define authorisation level (1 = access allowed) + */ +void thingset_authorisation_set(thingset_locid_t locid, uint16_t auth); + +/* + * ThingSet context (receive) message processing + * --------------------------------------------- + */ + +/** + * @brief Process a ThingSet message and transfer resulting message. + * + * If the message processing results in an output message, this output message is transfered to + * the appropriate sink. The sink depends on the context. + * + * This function expects the process type scratchpad of the message to be set. + * + * The function consumes the message buffer (decreases the reference count). + * + * @param[in] locid ThingSet local context identifier. + * @param[in] msg Pointer to message buffer. + * @returns 0 on success, <0 otherwise. + */ +int thingset_process(thingset_locid_t locid, struct thingset_msg *msg); + +/** + * @brief Process a ThingSet request buffer and create response in response buffer. + * + * This function also detects if JSON or CBOR format is used + * + * @param[in] locid ThingSet local context identifier. + * @param[in] request Pointer to the ThingSet request buffer + * @param[in] request_len Length of the data in the request buffer + * @param[in,out] response Pointer to the buffer where the ThingSet response should be stored + * @param[in] response_size Size of the response buffer, i.e. maximum allowed length of the response + * @returns Actual length of the response written to the buffer or <= 0 in case of error or if no + * response message has been generated (e.g. because a statement was processed) + */ +int thingset_process_buf(thingset_locid_t locid, const uint8_t *request, size_t request_len, + uint8_t *response, size_t response_size); + +/* + * ThingSet context database import/ export support + * ------------------------------------------------ + */ + +/** + * @brief Export data for given subset(s) of data objects. + * + * This function can be used e.g. to store data in the EEPROM or other non-volatile memory. + * + * @param[in] locid ThingSet local context identifier. + * @param[in] subsets Flags to select which subset(s) of data items should be exported. + * @param[in] buf Pointer to the buffer where the data should be stored + * @param[in] buf_size Size of the buffer, i.e. maximum allowed length of the data + * @returns Actual length of the data written to the buffer or <= 0 in case of error + */ +int thingset_export_buf(thingset_locid_t locid, uint16_t subsets, uint8_t *buf, uint16_t buf_size); + +/** + * @brief Import data from buffer into data objects. + * + * This function can be used to initialize data objects from previously exported data (using + * thingset_export_buf() function) and stored in the EEPROM or other non-volatile memory. + * + * @param[in] locid ThingSet local context identifier. + * @param[in] buf Buffer containing ID/value map that should be written to the data objects + * @param[in] buf_len Length of the data in the buffer + * @param[in] auth_flags Authentication flags to be used in this function (to override _auth_flags) + * @param[in] subsets Flags to select which subset(s) of data items should be imported + * @returns 0 on success, <0 otherwise. + */ +int thingset_import_buf(thingset_locid_t locid, uint8_t *buf, uint16_t buf_len, + uint16_t auth_flags, uint16_t subsets); + +/* + * ThingSet context message support + * -------------------------------- + */ + +/** + * @brief Generate statement message based on object reference to group or subset. + * + * @param[in] locid ThingSet local context identifier. + * @param[in] oref Object reference to group or subset object specifying the items to be published + * @param[out] buf Pointer to the buffer where the publication message should be stored + * @param[in] buf_size Size of the message buffer, i.e. maximum allowed length of the message + * @returns Actual length of the message written to the buffer or 0 in case of error + */ +int thingset_add_statement_buf(thingset_locid_t locid, thingset_oref_t oref, uint8_t *buf, + uint16_t buf_size); + +/** + * @brief ThingSet communication context. + * + * Defines and functions that are specific to the communication context only. + * + * @note All structure definitions and functions that start with the prefix 'ts_' are not part of + * the public API and are just here for technical reasons. They should not be used in + * applications. + * + * @defgroup ts_ctx_com_api_pub ThingSet communication context public interface + * @{ + */ + +/** + * @def THINGSET_COM_DEFINE + * + * @brief Define a communication variant ThingSet local context object. + * + * @param locid Local context identifier. Paramter must expand to a positiv number. + * Usually 0. Limit defined by @ref TS_CONFIG_LOCAL_COUNT. + */ +#if TS_CONFIG_COM +#define THINGSET_COM_DEFINE(locid) \ + struct ts_ctx_com_data TS_CAT(ts_ctx_com_data_, locid); \ + const struct ts_ctx_com TS_CAT(ts_ctx_com_, locid) = { \ + }; \ + const struct ts_ctx TS_CAT(ts_ctx_, locid) = { \ + .ctx_type = TS_CTX_TYPE_COM, \ + .db_id = locid, \ + .data = &TS_CAT(ts_ctx_com_data_, locid).common, \ + .variant = &TS_CAT(ts_ctx_com_, locid) \ + } +#else +#define THINGSET_COM_DEFINE(locid) \ + const struct ts_ctx TS_CAT(ts_ctx_, locid) = { \ + .ctx_type = TS_CTX_TYPE_INVALID \ + } +#endif + +/** + * @brief Get communication port identifier by port name. + * + * @param[in] locid ThingSet local context identifier. + * @param[in] port_name Name of port. + * @param[out] port_id Identifier of port. + * @returns 0 on success, <0 otherwise. + */ +int thingset_port(thingset_locid_t locid, const char *port_name, + thingset_portid_t *port_id); + +/** + * @} ts_ctx_com_api_pub + */ + +/** + * @brief ThingSet core context. + * + * Defines and functions that are specific to the core context only. + * + * @note All structure definitions and functions that start with the prefix 'ts_' are not part of + * the public API and are just here for technical reasons. They should not be used in + * applications. + * + * @defgroup ts_ctx_core_api_pub ThingSet core context public interface + * @{ + */ + +/** + * @def THINGSET_CORE_DEFINE + * + * @brief Define a core variant ThingSet local context object. + */ +#if TS_CONFIG_CORE +#define THINGSET_CORE_DEFINE() \ + struct ts_ctx_core_data TS_CAT(ts_ctx_core_data_, TS_CONFIG_CORE_LOCID); \ + const struct ts_ctx_core TS_CAT(ts_ctx_core_, TS_CONFIG_CORE_LOCID) = { \ + }; \ + const struct ts_ctx TS_CAT(ts_ctx_, TS_CONFIG_CORE_LOCID) = { \ + .ctx_type = TS_CTX_TYPE_CORE, \ + .db_id = TS_CONFIG_CORE_LOCID, \ + .data = &TS_CAT(ts_ctx_core_data_, TS_CONFIG_CORE_LOCID).common, \ + .variant = &TS_CAT(ts_ctx_core_, TS_CONFIG_CORE_LOCID) \ + } +#else +#define THINGSET_CORE_DEFINE() \ + const struct ts_ctx TS_CAT(ts_ctx_, TS_CONFIG_CORE_LOCID) = { \ + .ctx_type = TS_CTX_TYPE_INVALID \ + } +#endif + +/** + * @def THINGSET_CORE_DATABASE_DEFINE + * + * @brief Define a ThingSet data objects database for core variant local context data objects. + * + * Use like this: + * + * THINGSET_CORE_DATABASE_DEFINE(0x123456789012345ULL, + * TS_GROUP(ID_INFO, "info", TS_NO_CALLBACK, ID_ROOT), + * TS_ITEM_STRING(0x19, "Manufacturer", manufacturer, 0, I D_INFO, TS_ANY_R, 0) + * ); + * + * + * @note @ref THINGSET_CORE_DATABASE_DEFINE works on the single core variant ThingSet local context. + * + * @param ctx_uuid Unique identifier for the context the database belongs to. + * @param ... Initialisation for the data objects. + */ +#define THINGSET_CORE_DATABASE_DEFINE(ctx_uuid, ...) \ + THINGSET_DATABASE_DEFINE(TS_CONFIG_CORE_LOCID, ctx_uuid, __VA_ARGS__) + +/** + * @brief Initialize the ThingSet core context. + * + * @note @ref thingset_core_init works on the single core variant ThingSet local context. + */ +static inline int thingset_core_init(void) +{ + return thingset_init(TS_CONFIG_CORE_LOCID); +} + +/** + * @brief Process a ThingSet request buffer and create response in response buffer. + * + * This function also detects if JSON or CBOR format is used + * + * @note @ref thingset_core_process works on the single core variant ThingSet local context. + * + * @param[in] request Pointer to the ThingSet request buffer + * @param[in] request_len Length of the data in the request buffer + * @param[in,out] response Pointer to the buffer where the ThingSet response should be stored + * @param[in] response_size Size of the response buffer, i.e. maximum allowed length of the response + * @returns Actual length of the response written to the buffer or <= 0 in case of error or if no + * response message has been generated (e.g. because a statement was processed) + */ +static inline int thingset_core_process(const uint8_t *request, size_t request_len, + uint8_t *response, size_t response_size) +{ + return thingset_process_buf(TS_CONFIG_CORE_LOCID, request, request_len, response, + response_size); +} + +/** + * @brief Log all data objects as a structured JSON text. + * + * @warning This function uses a huge buffer for dumping the text before handing it to logging. + * It might cause stack overflows if run in constrained devices with a large data object + * tree. Use with care and for testing only! + * + * @note @ref thingset_dump_json works on the single core variant ThingSet local context. + * + * @param obj_id Root object ID where to start with printing + * @param level Indentation level (=depth inside the data object tree) + */ +void thingset_dump_json(ts_obj_id_t obj_id, int level); + +/** + * @brief Retrieve data in JSON format for given subset(s). + * + * This function does not return a complete ThingSet message, but only the payload data as a + * name/value map. It can be used e.g. to store data in the EEPROM or other non-volatile memory. + * + * @note @ref thingset_txt_export works on the single core variant ThingSet local context. + * + * @param buf Pointer to the buffer where the data should be stored + * @param buf_size Size of the buffer, i.e. maximum allowed length of the data + * @param subsets Flags to select which subset(s) of data items should be exported + * @returns Actual length of the data written to the buffer or 0 in case of error + */ +static inline int thingset_txt_export(char *buf, size_t buf_size, uint16_t subsets) +{ + thingset_protocol_set_txt(TS_CONFIG_CORE_LOCID); + return thingset_export_buf(TS_CONFIG_CORE_LOCID, subsets, (uint8_t *)buf, buf_size); +} + +/** + * @brief Generate statement message in JSON format based on pointer to group or subset. + * + * This is the fastest method to generate a statement as it does not require to search through the + * entire data objects array. + * + * @note @ref thingset_txt_statement works on the single core variant ThingSet local context. + * + * @param buf Pointer to the buffer where the publication message should be stored + * @param buf_size Size of the message buffer, i.e. maximum allowed length of the message + * @param object Group or subset object specifying the items to be published + * @returns Actual length of the message written to the buffer or 0 in case of error + */ +static inline int thingset_txt_statement(char *buf, size_t buf_size, const struct ts_obj *object) +{ + thingset_protocol_set_txt(TS_CONFIG_CORE_LOCID); + return thingset_add_statement_buf(TS_CONFIG_CORE_LOCID, ts_obj_db_oref_by_object(object), + (uint8_t *)buf, buf_size); +} + +/** + * @brief Generate statement message in JSON format based on path. + * + * @note @ref thingset_txt_statement_by_path works on the single core variant ThingSet local context. + * + * @param buf Pointer to the buffer where the publication message should be stored + * @param buf_size Size of the message buffer, i.e. maximum allowed length of the message + * @param path Path to group or subset object specifying the items to be published + * @returns Actual length of the message written to the buffer or 0 in case of error + */ +static inline int thingset_txt_statement_by_path(char *buf, size_t buf_size, const char *path) +{ + thingset_oref_t oref; + int ret = ts_obj_by_path(ts_obj_db_oref_any(ts_ctx_obj_db(TS_CONFIG_CORE_LOCID)), + path, strlen(path), &oref); + if (ret == 0) { + thingset_protocol_set_txt(TS_CONFIG_CORE_LOCID); + return thingset_add_statement_buf(TS_CONFIG_CORE_LOCID, oref, (uint8_t *)buf, buf_size); + } + return 0; +} + +/** + * @brief Generate statement message in JSON format based on data object ID. + * + * @note @ref thingset_txt_statement_by_id works on the single core variant ThingSet local context. + * + * @param buf Pointer to the buffer where the publication message should be stored + * @param buf_size Size of the message buffer, i.e. maximum allowed length of the message + * @param path ID of group or subset object specifying the items to be published + * @returns Actual length of the message written to the buffer or 0 in case of error + */ +static inline int thingset_txt_statement_by_id(char *buf, size_t buf_size, ts_obj_id_t id) +{ + thingset_oref_t oref; + int ret = ts_obj_db_oref_by_id(TS_CONFIG_CORE_LOCID, id, &oref); + if (ret == 0) { + thingset_protocol_set_txt(TS_CONFIG_CORE_LOCID); + return thingset_add_statement_buf(TS_CONFIG_CORE_LOCID, oref, (uint8_t *)buf, buf_size); + } + return 0; +} + +/** + * @brief Retrieve data in CBOR format for given subset(s). + * + * This function does not return a complete ThingSet message, but only the payload data as an + * ID/value map. It can be used e.g. to store data in the EEPROM or other non-volatile memory. + * + * @note @ref thingset_bin_export works on the single core variant ThingSet local context. + * + * @param ts Pointer to ThingSet context. + * @param buf Pointer to the buffer where the data should be stored + * @param buf_size Size of the buffer, i.e. maximum allowed length of the data + * @param subsets Flags to select which subset(s) of data items should be exported + * @returns Actual length of the data written to the buffer or 0 in case of error + */ +static inline int thingset_bin_export(uint8_t *buf, size_t buf_size, uint16_t subsets) +{ + thingset_protocol_set_bin(TS_CONFIG_CORE_LOCID); + return thingset_export_buf(TS_CONFIG_CORE_LOCID, subsets, buf, buf_size); +} + +/** + * @brief Generate statement message in CBOR format based on pointer to group or subset. + * + * This is the fastest method to generate a statement as it does not require to search through the + * entire date nodes array. + * + * @note @ref thingset_bin_statement works on the single core variant ThingSet local context. + * + * @param buf Pointer to the buffer where the publication message should be stored + * @param buf_size Size of the message buffer, i.e. maximum allowed length of the message + * @param object Group or subset object specifying the items to be published + * + * @returns Actual length of the message written to the buffer or 0 in case of error + */ +static inline int thingset_bin_statement(uint8_t *buf, size_t buf_size, const struct ts_obj *object) +{ + thingset_protocol_set_bin(TS_CONFIG_CORE_LOCID); + return thingset_add_statement_buf(TS_CONFIG_CORE_LOCID, ts_obj_db_oref_by_object(object), buf, + buf_size); +} + +/** + * @brief Generate statement message in CBOR format based on path. + * + * @note @ref thingset_bin_statement_by_path works on the single core variant ThingSet local context. + * + * @param buf Pointer to the buffer where the publication message should be stored + * @param buf_size Size of the message buffer, i.e. maximum allowed length of the message + * @param path Path to group or subset object specifying the items to be published + * + * @returns Actual length of the message written to the buffer or 0 in case of error + */ +static inline int thingset_bin_statement_by_path(uint8_t *buf, size_t buf_size, const char *path) +{ + thingset_oref_t oref; + int ret = ts_obj_by_path(ts_obj_db_oref_any(ts_ctx_obj_db(TS_CONFIG_CORE_LOCID)), path, + strlen(path), &oref); + if (ret == 0) { + thingset_protocol_set_bin(TS_CONFIG_CORE_LOCID); + return thingset_add_statement_buf(TS_CONFIG_CORE_LOCID, oref, buf, buf_size); + } + return 0; +} + +/** + * @brief Generate statement message in CBOR format based on data object ID. + * + * @note @ref thingset_bin_statement_by_id works on the single core variant ThingSet local context. + * + * @param buf Pointer to the buffer where the publication message should be stored + * @param buf_size Size of the message buffer, i.e. maximum allowed length of the message + * @param path ID of group or subset object specifying the items to be published + * @returns Actual length of the message written to the buffer or 0 in case of error + */ +static inline int thingset_bin_statement_by_id(uint8_t *buf, size_t buf_size, ts_obj_id_t id) +{ + thingset_oref_t oref; + int ret = ts_obj_db_oref_by_id(TS_CONFIG_CORE_LOCID, id, &oref); + if (ret == 0) { + thingset_protocol_set_bin(TS_CONFIG_CORE_LOCID); + return thingset_add_statement_buf(TS_CONFIG_CORE_LOCID, oref, buf, buf_size); + } + return 0; +} + +/** + * @brief Import data in CBOR format into data objects. + * + * This function can be used to initialize data objects from previously exported data (using + * thingset_bin_export function) and stored in the EEPROM or other non-volatile memory. + * + * @note @ref thingset_bin_import works on the single core variant ThingSet local context. + * + * @param buf Buffer containing ID/value map that should be written to the data objects + * @param buf_len Length of the data in the buffer + * @param auth_flags Authentication flags to be used in this function (to override _auth_flags) + * @param subsets Flags to select which subset(s) of data items should be imported + * @returns TS_STATUS_CHANGED on success. + */ +static inline int thingset_bin_import(uint8_t *buf, uint16_t buf_len, uint16_t auth_flags, + uint16_t subsets) +{ + int ret = thingset_import_buf(TS_CONFIG_CORE_LOCID, buf, buf_len, auth_flags, subsets); + return ret == 0 ? TS_STATUS_CHANGED : TS_STATUS_BAD_REQUEST; +} + +/** + * @brief Get data object by ID. + * + * @note @ref thingset_object_by_id works on the single core variant ThingSet local context. + * + * @param id Data object ID + * @returns Pointer to data object or NULL if object is not found + */ +const struct ts_obj *thingset_object_by_id(ts_obj_id_t id); + +/** + * @brief Get data object by name. + * + * As the names are not necessarily unique in the entire data tree, the parent is needed + * + * @note @ref thingset_object_by_name works on the single core variant ThingSet local context. + * + * @param name Data object name + * @param len Length of the object name + * @param parent Data object ID of the parent or -1 for global search + * @returns Pointer to data object or NULL if object is not found + */ +const struct ts_obj *thingset_object_by_name(const char *name, size_t len, int32_t parent); + +/** + * @brief Get data object by path. + * + * Get the endpoint object of a provided path. + * + * @note @ref thingset_object_by_path works on the single core variant ThingSet local context. + * + * @param path Path with multiple object names separated by forward slash. + * @param len Length of the entire path + * + * @returns Pointer to data object or NULL if object is not found + */ +const struct ts_obj *thingset_object_by_path(const char *path, size_t len); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +/** @page ts_topic_ctx Contexts + +TBD + +*/ + +#endif /* THINGSET_CTX_H_ */ diff --git a/src/ts_ctx.c b/src/ts_ctx.c new file mode 100644 index 0000000..169ca45 --- /dev/null +++ b/src/ts_ctx.c @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "thingset_env.h" +#include "thingset_time.h" + +#include "ts_bufq.h" +#include "ts_obj.h" +#include "ts_port.h" +#include "ts_ctx.h" +#include "ts_msg.h" +#include "ts_macro.h" + +#include + +/** + *@brief Local contexts by context id. + * + * Array of pointers to contexts. Array index == context id. + */ +const struct ts_ctx *const ts_ctxs[TS_CONFIG_LOCAL_COUNT] = { +#if TS_CONFIG_LOCAL_COUNT > 0 + &ts_ctx_0, +#endif +#if TS_CONFIG_LOCAL_COUNT > 1 + &ts_ctx_1, +#endif +#if TS_CONFIG_LOCAL_COUNT > 2 + &ts_ctx_2, +#endif +#if TS_CONFIG_LOCAL_COUNT > 3 + &ts_ctx_3, +#endif +#if TS_CONFIG_LOCAL_COUNT > 4 + &ts_ctx_4, +#endif +#if TS_CONFIG_LOCAL_COUNT > 5 +#error "Local contexts limited to 5" +#endif +}; + +int thingset_init(thingset_locid_t locid) +{ + int ret; + + /* Assure initialisation of object database(s). */ + ts_obj_db_init(); + + thingset_protocol_set_txt(locid); + thingset_authorisation_set(locid, TS_USR_MASK); + + pthread_mutexattr_t process_mutex_attr; + ret = pthread_mutexattr_init(&process_mutex_attr); + TS_ASSERT(ret == 0, "%s fails on pthread_mutexattr_init with %d", __func__, ret); + ret = pthread_mutexattr_settype(&process_mutex_attr, PTHREAD_MUTEX_RECURSIVE); + TS_ASSERT(ret == 0, "%s fails on pthread_mutexattr_settype PTHREAD_MUTEX_RECURSIVE with %d", + __func__, ret); + + if (TS_CTX_IS_CORE(locid)) { + struct ts_ctx_core_data *data = ts_ctx_core_data(locid); + + ret = pthread_mutex_init(&data->common.process_mutex, &process_mutex_attr); + + data->resp_buf = NULL; + data->resp_buf_size = 0; + } + else if (TS_CTX_IS_COM(locid)) { + struct ts_ctx_com_data *data = ts_ctx_com_data(locid); + + ret = pthread_mutex_init(&data->common.process_mutex, &process_mutex_attr); + if (ret != 0) { + return ret; + } + + ts_ctx_node_table_init(locid); + + ret = ts_ports_init(locid); + } + else { + TS_ASSERT(false, "%s used with unknown context", __func__); + ret = -ENOTSUP; + } + + return ret; +} + +int thingset_run(thingset_locid_t locid) +{ + int ret; + + if (TS_CTX_IS_CORE(locid)) { + TS_ASSERT(false, "%s can not be used with core context", __func__); + ret = -ENOTSUP; + } + else if (TS_CTX_IS_COM(locid)) { + ret = 0; + for (thingset_portid_t port_id = 0; port_id < TS_ARRAY_SIZE(ts_ports); port_id++) { + const struct thingset_port *port = ts_ports[port_id]; + if ((port->loc_id == locid) && (port->api->run != NULL)) { + ret = ts_port_run(port); + if (ret != 0) { + break; + } + } + } + } + else { + TS_ASSERT(false, "%s used with unknown context", __func__); + ret = -ENOTSUP; + } + + return ret; +} + +void thingset_protocol_set_txt(thingset_locid_t locid) +{ + TS_ASSERT(locid < TS_CONFIG_LOCAL_COUNT, "CTX: %s on invalid local context identifier %u", + __func__, (unsigned int)locid); + ts_ctxs[locid]->data->app_protocol_use_bin = false; +} + +void thingset_protocol_set_bin(thingset_locid_t locid) +{ + TS_ASSERT(locid < TS_CONFIG_LOCAL_COUNT, "CTX: %s on invalid local context identifier %u", + __func__, (unsigned int)locid); + ts_ctxs[locid]->data->app_protocol_use_bin = true; +} + +uint16_t thingset_authorisation(thingset_locid_t locid) +{ + TS_ASSERT(locid < TS_CONFIG_LOCAL_COUNT, "CTX: %s on invalid local context identifier %u", + __func__, (unsigned int)locid); + return ts_ctxs[locid]->data->_auth_flags; +} + +void thingset_authorisation_set(thingset_locid_t locid, uint16_t auth) +{ + TS_ASSERT(locid < TS_CONFIG_LOCAL_COUNT, "CTX: %s on invalid local context identifier %u", + __func__, (unsigned int)locid); + TS_LOGD("CTX: %s set context %d authorisation to 0x%04x", __func__, + (unsigned int)locid, (unsigned int)auth); + ts_ctxs[locid]->data->_auth_flags = auth; +} + +ts_obj_db_id_t ts_ctx_obj_db(thingset_locid_t locid) +{ + TS_ASSERT(locid < TS_CONFIG_LOCAL_COUNT, "CTX: %s on invalid local context identifier %u", + __func__, (unsigned int)locid); + return ts_ctxs[locid]->db_id; +} + +const thingset_uid_t *ts_ctx_uid(thingset_locid_t locid) +{ + ts_obj_db_id_t did = ts_ctx_obj_db(locid); + + if (did != TS_OBJ_DB_ID_INVALID) { + return ts_obj_db_by_id(did)->uuid; + } + + return NULL; +} + +int thingset_port(thingset_locid_t locid, const char *port_name, thingset_portid_t *port_id) +{ + int ret; + + if (TS_CTX_IS_CORE(locid)) { + TS_ASSERT(false, "%s can not be used with core context", __func__); + ret = -ENOTSUP; + } + else if (TS_CTX_IS_COM(locid)) { + for (thingset_portid_t id = 0; id < TS_ARRAY_SIZE(ts_ports); id++) { + const struct thingset_port *port = ts_ports[id]; + if ((port->loc_id == locid) && (strcmp(port->name, port_name) == 0)) { + *port_id = id; + return 0; + } + } + ret = -ENAVAIL; + } + else { + TS_ASSERT(false, "%s used with unknown context", __func__); + ret = -ENOTSUP; + } + + return ret; +} + +int ts_ctx_transmit(thingset_locid_t locid, thingset_portid_t port_id, struct thingset_msg *msg, + thingset_time_ms_t timeout_ms) +{ + int ret; + + if (TS_CTX_IS_CORE(locid)) { + const struct ts_ctx_core_data *data = ts_ctx_core_data(locid); + if (ts_msg_len(msg) > data->resp_buf_size ) { + ret = -ENOMEM; + } + else { + memcpy(data->resp_buf, ts_msg_data(msg), ts_msg_len(msg)); + ret = 0; + } + } + else if (TS_CTX_IS_COM(locid)) { + const struct thingset_port *port = ts_port_by_id(port_id); + ret = port->api->transmit(port, msg, timeout_ms); + } + else { + TS_ASSERT(false, "%s used with unknown context", __func__); + ret = -ENOTSUP; + } + + return ret; +} + +/* helper functions */ + +bool ts_ctx_uid_equal(const thingset_uid_t *uuid_a, const thingset_uid_t *uuid_b) +{ + if (uuid_a == uuid_b) { + return true; + } + return *uuid_a == *uuid_b; +} diff --git a/src/ts_ctx.h b/src/ts_ctx.h new file mode 100644 index 0000000..5d24302 --- /dev/null +++ b/src/ts_ctx.h @@ -0,0 +1,536 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet local context (private interface) + */ + +/** + * @brief Abstraction over ThingSet local context. + * + * @defgroup ts_ctx_api_priv ThingSet local context (private interface) + * @{ + */ + +#ifndef TS_COM_H_ +#define TS_COM_H_ + +#include "thingset_ctx.h" +#include "ts_port.h" +#include "ts_obj.h" +#include "ts_msg.h" +#include "ts_macro.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ThingSet context handling + * ------------------------- + */ + +/** + * @def TS_CTX_IS_CORE + * + * @brief Is given local context of core variant type. + * + * @param locid Local context identifier. + */ +#if TS_CONFIG_CORE && TS_CONFIG_COM +#define TS_CTX_IS_CORE(locid) (ts_ctxs[locid]->ctx_type == TS_CTX_TYPE_CORE) +#elif TS_CONFIG_CORE +#define TS_CTX_IS_CORE(locid) (true) +#elif TS_CONFIG_COM +#define TS_CTX_IS_CORE(locid) (false) +#endif + +/** + * @def TS_CTX_IS_COM + * + * @brief Is given local context of communication variant type. + * + * @param locid Local context identifier. + */ +#if TS_CONFIG_CORE && TS_CONFIG_COM +#define TS_CTX_IS_COM(locid) (ts_ctxs[locid]->ctx_type == TS_CTX_TYPE_COM) +#elif TS_CONFIG_CORE +#define TS_CTX_IS_COM(locid) (false) +#elif TS_CONFIG_COM +#define TS_CTX_IS_COM(locid) (true) +#endif + +/** + *@brief Local contexts by context id. + * + * Array of pointers to contexts. Array index == context id. + */ +extern const struct ts_ctx *const ts_ctxs[TS_CONFIG_LOCAL_COUNT]; + +/** + * @brief Get core variant local context given by local context identifier. + * + * @param[in] locid ThingSet local context identifier. + * @return Pointer to core variant local context. + */ +static inline const struct ts_ctx_core *ts_ctx_core_context(thingset_locid_t locid) +{ + TS_ASSERT(locid < TS_CONFIG_LOCAL_COUNT, "CTX: %s on invalid local context identifier %u", + __func__, (unsigned int)locid); +#if TS_CONFIG_CORE && TS_CONFIG_COM + TS_ASSERT(TS_CTX_IS_CORE(locid), "CTX: %s on wrong context type", __func__); + return (const struct ts_ctx_core *)(ts_ctxs[locid]->variant); +#elif TS_CONFIG_CORE + return (const struct ts_ctx_core *)(ts_ctxs[locid]->variant); +#elif TS_CONFIG_COM + TS_ASSERT(false, "CTX: %s on wrong context type", __func__); + return NULL; +#endif +} + +/** + * @brief Get core variant local context data given by local context identifier. + * + * @param[in] locid ThingSet local context identifier. + * @return Pointer to core variant local context data. + */ +static inline struct ts_ctx_core_data *ts_ctx_core_data(thingset_locid_t locid) +{ + TS_ASSERT(locid < TS_CONFIG_LOCAL_COUNT, "CTX: %s on invalid local context identifier %u", + __func__, (unsigned int)locid); +#if TS_CONFIG_CORE && TS_CONFIG_COM + TS_ASSERT(TS_CTX_IS_CORE(locid), "CTX: %s on wrong context type", __func__); + return (struct ts_ctx_core_data *)(ts_ctxs[locid]->data); +#elif TS_CONFIG_CORE + return (struct ts_ctx_core_data *)(ts_ctxs[locid]->data); +#elif TS_CONFIG_COM + TS_ASSERT(false, "CTX: %s on wrong context type", __func__); + return NULL; +#endif +} + +/** + * @brief Get communication variant local context given by local context identifier. + * + * @param[in] locid ThingSet local context identifier. + * @return Pointer to communication variant local context. + */ +static inline const struct ts_ctx_com *ts_ctx_com_context(thingset_locid_t locid) +{ + TS_ASSERT(locid < TS_CONFIG_LOCAL_COUNT, "CTX: %s on invalid local context identifier %u", + __func__, (unsigned int)locid); +#if TS_CONFIG_CORE && TS_CONFIG_COM + TS_ASSERT(TS_CTX_IS_COM(locid), "CTX: %s on wrong context type", __func__); + return (const struct ts_ctx_com *)(ts_ctxs[locid]->variant); +#elif TS_CONFIG_CORE + TS_ASSERT(false, "CTX: %s on wrong context type", __func__); + return NULL; +#elif TS_CONFIG_COM + return (const struct ts_ctx_com *)(ts_ctxs[locid]->variant); +#endif +} + +/** + * @brief Get communication variant local context data given by local context identifier. + * + * @param[in] locid ThingSet local context identifier. + * @return Pointer to communication variant local context data. + */ +static inline struct ts_ctx_com_data *ts_ctx_com_data(thingset_locid_t locid) +{ + TS_ASSERT(locid < TS_CONFIG_LOCAL_COUNT, "CTX: %s on invalid local context identifier %u", + __func__, (unsigned int)locid); +#if TS_CONFIG_CORE && TS_CONFIG_COM + TS_ASSERT(TS_CTX_IS_COM(locid), "CTX: %s on wrong context type", __func__); + return (struct ts_ctx_com_data *)(ts_ctxs[locid]->data); +#elif TS_CONFIG_CORE + TS_ASSERT(false, "CTX: %s on wrong context type", __func__); + return NULL; +#elif TS_CONFIG_COM + return (struct ts_ctx_com_data *)(ts_ctxs[locid]->data); +#endif +} + +/** + * @brief Get the data objects database of this local context. + * + * @param[in] locid ThingSet local context identifier. + * @return Data objects database ID. + */ +ts_obj_db_id_t ts_ctx_obj_db(thingset_locid_t locid); + +/** + * @brief Get the unique identifier of this context. + * + * @param[in] locid ThingSet local context identifier. + * @return Pointer to unique identifier + */ +const thingset_uid_t *ts_ctx_uid(thingset_locid_t locid); + +/** + * @brief Is binary mode protocol selected for application initiated communication. + * + * @param[in] locid ThingSet local context identifier. + * @return True in case the binary protocol is selected, false otherwise. + */ +static inline bool ts_ctx_protocol_is_bin(thingset_locid_t locid) +{ + TS_ASSERT(locid < TS_CONFIG_LOCAL_COUNT, "CTX: %s on invalid local context identifier %u", + __func__, (unsigned int)locid); + return ts_ctxs[locid]->data->app_protocol_use_bin; +} + +/* + * ThingSet context (receive) message processing + * --------------------------------------------- + */ + +/** + * @brief Lock local context for message processing. + * + * The local context is locked to the calling thread. Recursive relock is possible. + * + * @param[in] locid ThingSet local context identifier. + * @returns 0 on success, <0 otherwise. + * @retval -EOWNERDEAD if locked by another thread. + */ +int ts_ctx_process_lock(thingset_locid_t locid); + +/** + * @brief Unock local context from message processing. + * + * Release local context from calling thread. + * + * @param[in] locid ThingSet local context identifier. + * @returns 0 on success, <0 otherwise. + */ +int ts_ctx_process_unlock(thingset_locid_t locid); + +/** + * @brief Process a ThingSet message and create out message. + * + * This function expects the process type scratchpad of the message to be set. + * + * The function consumes the input message buffer (decreases the reference count). + * + * The functions locks the local context to the calling thread during message processing. + * + * @param[in] locid ThingSet local context identifier. + * @param[in] msg_in Pointer to input message buffer. + * @param[out] msg_out Pointer to output message buffer. Null if there is none. + * @returns 0 on success, <0 otherwise. + */ +int ts_ctx_process_msg(thingset_locid_t locid, struct thingset_msg *msg_in, + struct thingset_msg **msg_out); + +/** + * @brief Process a ThingSet request. + * + * This function also detects if JSON or CBOR format is used. + * + * The function consumes the request message buffer (decreases the reference count). + * + * @param[in] locid ThingSet local context identifier. + * @param[in] request Pointer to request message buffer. + * @param[out] response Pointer to response message buffer. Null if there is none. + * @returns 0 on success, <0 otherwise. + */ +int ts_ctx_process_request(thingset_locid_t locid, struct thingset_msg *request, + struct thingset_msg **response); + + +/** + * @brief Process a ThingSet request error. + * + * Allocate and create a response message for a request error. + * + * @param[in] response_msg_proto Message protocol to use for response message. + * @param[in] response_msg_code Code to report in response message. + * @param[out] response Pointer to response message buffer. Null if there is none. + * @returns 0 on success, <0 otherwise. + */ +int ts_ctx_process_request_error(enum ts_msg_proto response_msg_proto, + enum ts_msg_code response_msg_code, + struct thingset_msg **response); + +/** + * @brief Process a ThingSet CREATE request in CBOR format. + * + * The function consumes the request message buffer (decreases the reference count). + * + * @param[in] request Pointer to request message buffer. + * @param[in] oref Database object reference to object the request is for. + * @param[out] response Pointer to response message buffer. Null if there is none. + * @returns 0 on success, <0 otherwise. Even in case of success no response message may have been + * generated (e.g. because a statement was processed). + */ +int ts_ctx_process_create_cbor(struct thingset_msg *request, thingset_oref_t oref, + struct thingset_msg **response); + +/** + * @brief Process a ThingSet CREATE request in JSON format. + * + * The function consumes the request message buffer (decreases the reference count). + * + * @param[in] request Pointer to request message buffer. + * @param[in] oref Database object reference to object the request is for. + * @param[out] response Pointer to response message buffer. Null if there is none. + * @returns 0 on success, <0 otherwise. Even in case of success no response message may have been + * generated (e.g. because a statement was processed). + */ +int ts_ctx_process_create_json(struct thingset_msg *request, thingset_oref_t oref, + struct thingset_msg **response); + +/** + * @brief Process a ThingSet DELETE request in CBOR format. + * + * The function consumes the request message buffer (decreases the reference count). + * + * @param[in] request Pointer to request message buffer. + * @param[in] oref Database object reference to object the request is for. + * @param[out] response Pointer to response message buffer. Null if there is none. + * @returns 0 on success, <0 otherwise. Even in case of success no response message may have been + * generated (e.g. because a statement was processed). + */ +int ts_ctx_process_delete_cbor(struct thingset_msg *request, thingset_oref_t oref, + struct thingset_msg **response); + +/** + * @brief Process a ThingSet DELETE request in JSON format. + * + * The function consumes the request message buffer (decreases the reference count). + * + * @param[in] request Pointer to request message buffer. + * @param[in] oref Database object reference to object the request is for. + * @param[out] response Pointer to response message buffer. Null if there is none. + * @returns 0 on success, <0 otherwise. Even in case of success no response message may have been + * generated (e.g. because a statement was processed). + */ +int ts_ctx_process_delete_json(struct thingset_msg *request, thingset_oref_t oref, + struct thingset_msg **response); + +/** + * @brief Process a ThingSet EXEC request in CBOR format. + * + * The function consumes the request message buffer (decreases the reference count). + * + * @param[in] request Pointer to request message buffer. + * @param[in] oref Database object reference to object the request is for. + * @param[out] response Pointer to response message buffer. Null if there is none. + * @returns 0 on success, <0 otherwise. Even in case of success no response message may have been + * generated (e.g. because a statement was processed). + */ +int ts_ctx_process_exec_cbor(struct thingset_msg *request, thingset_oref_t oref, + struct thingset_msg **response); + +/** + * @brief Process a ThingSet EXEC request in JSON format. + * + * The function consumes the request message buffer (decreases the reference count). + * + * @param[in] request Pointer to request message buffer. + * @param[in] oref Database object reference to object the request is for. + * @param[out] response Pointer to response message buffer. Null if there is none. + * @returns 0 on success, <0 otherwise. Even in case of success no response message may have been + * generated (e.g. because a statement was processed). + */ +int ts_ctx_process_exec_json(struct thingset_msg *request, thingset_oref_t oref, + struct thingset_msg **response); + +/** + * @brief Process a ThingSet PATCH request in CBOR format. + * + * The function consumes the request message buffer (decreases the reference count). + * + * @param[in] request Pointer to request message buffer. + * @param[in] oref Database object reference to object the request is for. + * @param[out] response Pointer to response message buffer. Null if there is none. + * @returns 0 on success, <0 otherwise. Even in case of success no response message may have been + * generated (e.g. because a statement was processed). + */ +int ts_ctx_process_patch_cbor(struct thingset_msg *request, thingset_oref_t oref, + struct thingset_msg **response); + +/** + * @brief Process a ThingSet PATCH request in JSON format. + * + * The function consumes the request message buffer (decreases the reference count). + * + * @param[in] request Pointer to request message buffer. + * @param[in] oref Database object reference to object the request is for. + * @param[out] response Pointer to response message buffer. Null if there is none. + * @returns 0 on success, <0 otherwise. Even in case of success no response message may have been + * generated (e.g. because a statement was processed). + */ +int ts_ctx_process_patch_json(struct thingset_msg *request, thingset_oref_t oref, + struct thingset_msg **response); + +/** + * @brief Process the part of a ThingSet request in CBOR format to set object data. + * + * The function consumes the request message buffer (decreases the reference count). + * + * @param[in] request Pointer to request message buffer. + * @param[in] oref Database object reference to object the request is for. + * @param[out] response Pointer to response message buffer. Null if there is none. + * @returns 0 on success, <0 otherwise. Even in case of success no response message may have been + * generated (e.g. because a statement was processed). + */ +int ts_ctx_process_set_cbor(struct thingset_msg *request, thingset_oref_t oref, + struct thingset_msg **response); + +/** + * @brief Process the part of a ThingSet request in JSON format to set object data. + * + * The function consumes the request message buffer (decreases the reference count). + * + * @param[in] request Pointer to request message buffer. + * @param[in] oref Database object reference to object the request is for. + * @param[out] response Pointer to response message buffer. Null if there is none. + * @returns 0 on success, <0 otherwise. Even in case of success no response message may have been + * generated (e.g. because a statement was processed). + */ +int ts_ctx_process_set_json(struct thingset_msg *request, thingset_oref_t oref, + struct thingset_msg **response); + +/* + * ThingSet context port handling + * ------------------------------ + */ + +/** + * @brief Transmit message on port. + * + * @param[in] locid ThingSet local context identifier. + * @param[in] port_id Port identifier of port to send at. + * @param[in] msg Pointer to message buffer to be send. + * @param[in] timeout_ms maximum time to wait in milliseconds. + * @return 0 on success, <0 otherwise. + */ +int ts_ctx_transmit(thingset_locid_t locid, thingset_portid_t port_id, + struct thingset_msg *msg, thingset_time_ms_t timeout_ms); + + +/* + * ThingSet context node handling + * ------------------------------ + */ + +/** + * @brief Init node table. + * + * @param[in] locid ThingSet local context identifier. + */ +void ts_ctx_node_table_init(thingset_locid_t locid); + +/** + * @brief Init node table entry to phantom node. + * + * Also no port info shall be attached to the node table entry. + * + * @param[in] locid ThingSet local context identifier. + * @param[in] node_idx Index of node table element. + * @param[in] ctx_uid Ponter to unique context identifier of neighbour/ originator node. + */ +void ts_ctx_node_init_phantom(thingset_locid_t locid, uint16_t node_idx, + const thingset_uid_t *ctx_uid); + +/** + * @brief Get a node table entry. + * + * If the node table entry already exists return the existing one. + * + * @param[in] locid ThingSet local context identifier. + * @param[in] ctx_uid Ponter to unique context identifier of neighbour/ originator node. + * @param[out] node_idx Index of node table element. + * @returns 0 on success, <0 otherwise. + */ +int ts_ctx_node_get(thingset_locid_t locid, const thingset_uid_t* ctx_uid, uint16_t* node_idx); + +/** + * @brief Search best candidate node for eviction from node table. + * + * Best candidate is a node that was not seen for a long time. + * + * @param[in] locid ThingSet local context identifier. + * @return node_idx of best candidate node for eviction. + */ +uint16_t ts_ctx_node_evict(thingset_locid_t locid); + +/** + * @brief Free a node table entry. + * + * Mark node information unused. + * + * @param[in] locid ThingSet local context identifier. + * @param[in] node_idx Index of node table element. + */ +void ts_ctx_node_free(thingset_locid_t locid, uint16_t node_idx); + +/** + * @brief Lookup the node table element of a node. + * + * @param[in] locid ThingSet local context identifier. + * @param[in] ctx_uid Ponter to unique context identifier of neighbour/ originator node. + * @param[out] node_idx Index of node table element. + * @returns 0 on success, <0 otherwise. + */ +int ts_ctx_node_lookup(thingset_locid_t locid, const thingset_uid_t* ctx_uid, uint16_t* node_idx); + +/* + * ThingSet context application remote object support + * -------------------------------------------------- + */ + +int ts_ctx_obj_connect(thingset_locid_t locid, thingset_uid_t *ctx_uid, + ts_obj_id_t obj_id, const char *obj_path, thingset_time_ms_t timeout_ms, + thingset_oref_t *oref); + +int ts_ctx_obj_disconnect(thingset_locid_t locid, thingset_oref_t oref); + +/* + * ThingSet context application command support + * -------------------------------------------- + */ + +int ts_ctx_cmd_publish(thingset_locid_t locid, thingset_portid_t *app_port_id, + thingset_oref_t oref, thingset_time_ms_t period_ms); + +int ts_ctx_cmd_subscribe(thingset_locid_t locid, thingset_portid_t *app_port_id, + thingset_oref_t oref); + +int ts_ctx_cmd_change(thingset_locid_t locid, thingset_portid_t *app_port_id, + thingset_oref_t oref, thingset_time_ms_t timeout_ms); + +int ts_ctx_cmd_fetch(thingset_locid_t locid, thingset_portid_t *app_port_id, + thingset_oref_t oref, uint16_t subsets, thingset_time_ms_t timeout_ms); + +int ts_ctx_cmd_exec(thingset_locid_t locid, thingset_portid_t *app_port_id, + thingset_oref_t oref, thingset_time_ms_t timeout_ms); + +/* + * Helpers + * ------- + */ + +/** + * @brief Check unique context identifiers to be equal. + * + * @param uuid_a Pointer to uuid of a context. + * @param uuid_b Pointer to uuid of a context. + * @returns true on equal, false otherwise. + */ +bool ts_ctx_uid_equal(const thingset_uid_t *uuid_a, const thingset_uid_t *uuid_b); + +#ifdef __cplusplus +} +#endif + +/** + * @} ts_ctx_api_priv + */ + +#endif /* TS_COM_H_ */ diff --git a/src/ts_ctx_cmd.c b/src/ts_ctx_cmd.c new file mode 100644 index 0000000..9de83e7 --- /dev/null +++ b/src/ts_ctx_cmd.c @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * ThingSet context application command support + * -------------------------------------------- + */ + +#include "thingset_env.h" +#include "thingset_time.h" + +#include "ts_obj.h" +#include "ts_ctx.h" +#include "ts_msg.h" +#include "ts_macro.h" + +#include + +int ts_ctx_cmd_publish(thingset_locid_t locid, thingset_portid_t *app_port_id, + thingset_oref_t oref, thingset_time_ms_t period_ms); + +int ts_ctx_cmd_subscribe(thingset_locid_t locid, thingset_portid_t *app_port_id, + thingset_oref_t oref); + +int ts_ctx_cmd_change(thingset_locid_t locid, thingset_portid_t *app_port_id, + thingset_oref_t oref, thingset_time_ms_t timeout_ms); + +int ts_ctx_cmd_fetch(thingset_locid_t locid, thingset_portid_t *app_port_id, + thingset_oref_t oref, uint16_t subsets, thingset_time_ms_t timeout_ms); + +int ts_ctx_cmd_exec(thingset_locid_t locid, thingset_portid_t *app_port_id, + thingset_oref_t oref, thingset_time_ms_t timeout_ms); diff --git a/src/ts_ctx_core.c b/src/ts_ctx_core.c new file mode 100644 index 0000000..7839175 --- /dev/null +++ b/src/ts_ctx_core.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2017 Martin Jäger / Libre Solar + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "ts_msg.h" +#include "ts_ctx.h" +#include "ts_log.h" + + +void thingset_dump_json(ts_obj_id_t obj_id, int level) +{ + TS_ASSERT(level == 0, "Deprecated function - only level == 0 supported"); + + if (!TS_CONFIG_LOG || (TS_CONFIG_LOG_LEVEL < 3)) { + return; + } + + thingset_oref_t oref; + int ret = ts_obj_db_oref_by_id(ts_ctx_obj_db(TS_CONFIG_CORE_LOCID), obj_id, &oref); + if (ret != 0) { + return; + } + + char dump_buffer[10000]; + thingset_obj_log(oref, &dump_buffer[0], sizeof(dump_buffer)); + + TS_LOGI("%s", &dump_buffer[0]); +} + +const struct ts_obj *thingset_object_by_id(ts_obj_id_t id) +{ + thingset_oref_t oref; + int ret = ts_obj_db_oref_by_id(TS_CONFIG_CORE_LOCID, id, &oref); + if (ret != 0) { + return NULL; + } + return ts_obj(oref); +} + +const struct ts_obj *thingset_object_by_name(const char *name, size_t len, int32_t parent) +{ + int ret; + thingset_oref_t oref = ts_obj_db_oref_any(ts_ctx_obj_db(TS_CONFIG_CORE_LOCID)); + if (parent >= 0) { + ret = ts_obj_db_oref_by_id(oref.db_id, parent, &oref); + if (ret != 0) { + return NULL; + } + } + + ret = ts_obj_by_name(oref, name, len, &oref); + if (ret != 0) { + return NULL; + } + return ts_obj(oref); +} + +const struct ts_obj *thingset_object_by_path(const char *path, size_t len) +{ + thingset_oref_t oref; + int ret = ts_obj_by_path(ts_obj_db_oref_any(ts_ctx_obj_db(TS_CONFIG_CORE_LOCID)), path, len, + &oref); + if (ret != 0) { + return NULL; + } + return ts_obj(oref); +} diff --git a/src/ts_ctx_export.c b/src/ts_ctx_export.c new file mode 100644 index 0000000..b4ef8a0 --- /dev/null +++ b/src/ts_ctx_export.c @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "ts_msg.h" +#include "ts_ctx.h" + +int thingset_export_buf(thingset_locid_t locid, uint16_t subsets, uint8_t *buf, uint16_t buf_size) +{ + struct thingset_msg *msg; + int ret; + + if (ts_ctx_protocol_is_bin(locid)) { + ret = thingset_msg_alloc_cbor(buf_size, THINGSET_TIMEOUT_IMMEDIATE, &msg); + if (ret != 0) { + return ret; + } + ret = ts_msg_add_export_cbor(msg, ts_ctx_obj_db(locid), subsets); + } + else { + ret = thingset_msg_alloc_json(buf_size, THINGSET_TIMEOUT_IMMEDIATE, &msg); + if (ret != 0) { + return ret; + } + ret = ts_msg_add_export_json(msg, ts_ctx_obj_db(locid), subsets); + } + if (ret != 0) { + goto ts_ctx_export_buf_error; + } + +#if TS_CONFIG_LOG + char log_buf[200]; + ret = ts_msg_log(msg, &log_buf[0], sizeof(log_buf)); + TS_LOGD("CTX: %s created message: >%d<, >%.*s<", __func__, ts_msg_len(msg), ret, &log_buf[0]); +#endif + + if (ts_msg_len(msg) > buf_size) { + ret = -ENOMEM; + goto ts_ctx_export_buf_error; + } + ret = ts_msg_len(msg); + (void)memcpy(buf, ts_msg_data(msg), ret); + +ts_ctx_export_buf_error: + thingset_msg_unref(msg); + return ret; +} + +int thingset_import_buf(thingset_locid_t locid, uint8_t *buf, uint16_t buf_len, + uint16_t auth_flags, uint16_t subsets) +{ + struct thingset_msg *msg; + int ret = thingset_msg_alloc(buf_len, THINGSET_TIMEOUT_IMMEDIATE, &msg); + if (ret != 0) { + return ret; + } + ret = ts_msg_add_mem(msg, buf, buf_len); + if (ret != 0) { + thingset_msg_unref(msg); + return ret; + } + ts_msg_auth_set(msg, auth_flags); + +#if TS_CONFIG_LOG + char log_buf[200]; + ret = ts_msg_log(msg, &log_buf[0], sizeof(log_buf)); + TS_LOGD("CTX: %s created message: >%d<, >%.*s<", __func__, ts_msg_len(msg), ret, &log_buf[0]); +#endif + + return ts_msg_pull_export(msg, ts_ctx_obj_db(locid)); +} diff --git a/src/ts_ctx_msg.c b/src/ts_ctx_msg.c new file mode 100644 index 0000000..12d1f6c --- /dev/null +++ b/src/ts_ctx_msg.c @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * ThingSet context message support + * -------------------------------- + */ + +#include "ts_msg.h" +#include "ts_ctx.h" + +int thingset_add_statement_buf(thingset_locid_t locid, thingset_oref_t oref, uint8_t *buf, + uint16_t buf_size) +{ + TS_ASSERT(ts_obj_db_oref_is_object(oref), "MSG: %s on invalid object reference (%u:%u)", + __func__, (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + + struct thingset_msg *msg; + int ret; + + if (ts_ctx_protocol_is_bin(locid)) { + ret = thingset_msg_alloc_cbor(buf_size, THINGSET_TIMEOUT_IMMEDIATE, &msg); + if (ret != 0) { + return ret; + } + ret = ts_msg_add_statement_cbor(msg, oref); + } + else { + ret = thingset_msg_alloc_json(buf_size, THINGSET_TIMEOUT_IMMEDIATE, &msg); + if (ret != 0) { + return ret; + } + ret = ts_msg_add_statement_json(msg, oref); + } + if (ret != 0) { + goto ts_ctx_msg_add_statement_buf_error; + } + +#if TS_CONFIG_LOG + char log_buf[200]; + ret = ts_msg_log(msg, &log_buf[0], sizeof(log_buf)); + TS_LOGD("ThingSet: %s created message: >%d<, >%.*s<", __func__, ts_msg_len(msg), ret, &log_buf[0]); +#endif + + if (ts_msg_len(msg) > buf_size) { + TS_LOGE("ThingSet: %s message does not fit into buffer: >%u< vs. >%u<", __func__, + (unsigned int)ts_msg_len(msg), (unsigned int)buf_size); + ret = -ENOMEM; + goto ts_ctx_msg_add_statement_buf_error; + } + + ret = ts_msg_len(msg); + (void)memcpy(buf, ts_msg_data(msg), ret); + +ts_ctx_msg_add_statement_buf_error: + thingset_msg_unref(msg); + return ret; +} diff --git a/src/ts_ctx_node.c b/src/ts_ctx_node.c new file mode 100644 index 0000000..c74adfc --- /dev/null +++ b/src/ts_ctx_node.c @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet context node handling + */ + +#include "thingset_env.h" + +#include "ts_port.h" +#include "ts_ctx.h" +#include "ts_macro.h" +#include "ts_log.h" + +void ts_ctx_node_table_init(thingset_locid_t locid) +{ + if (TS_CTX_IS_CORE(locid)) { + TS_ASSERT(false, "CTX: %s can not be used with core context", __func__); + } + else if (TS_CTX_IS_COM(locid)) { + struct ts_ctx_com_data *data = ts_ctx_com_data(locid); + + for (uint16_t i = 0; i < TS_ARRAY_SIZE(data->node_table.nodes); i++) { + struct ts_ctx_node *node = &data->node_table.nodes[i]; + + /* Mark node table entry unused */ + node->port_id = THINGSET_PORT_ID_INVALID; + } + } + else { + TS_ASSERT(false, "CTX: %s used with unknown context", __func__); + } +} + +void ts_ctx_node_init_phantom(thingset_locid_t locid, uint16_t node_idx, + const thingset_uid_t *ctx_uid) +{ + if (TS_CTX_IS_CORE(locid)) { + TS_ASSERT(false, "CTX: %s can not be used with core context", __func__); + } + else if (TS_CTX_IS_COM(locid)) { + struct ts_ctx_com_data *data = ts_ctx_com_data(locid); + struct ts_ctx_node *node = &data->node_table.nodes[node_idx]; + + node->ctx_uid = *ctx_uid; + node->last_seen_time = thingset_time_ms(); + node->port_id = TS_PORT_ID_PHANTOM; + } + else { + TS_ASSERT(false, "CTX: %s used with unknown context", __func__); + } +} + +int ts_ctx_node_get(thingset_locid_t locid, const thingset_uid_t* ctx_uid, uint16_t* node_idx) +{ + int ret; + + if (TS_CTX_IS_CORE(locid)) { + TS_ASSERT(false, "CTX: %s can not be used with core context", __func__); + ret = -ENOTSUP; + } + else if (TS_CTX_IS_COM(locid)) { + struct ts_ctx_com_data *data = ts_ctx_com_data(locid); + uint16_t match_idx = UINT16_MAX; + uint16_t empty_idx = UINT16_MAX; /* just in case we need it */ + + for (uint16_t i = 0; i < TS_ARRAY_SIZE(data->node_table.nodes); i++) { + struct ts_ctx_node *node = &data->node_table.nodes[i]; + if (node->port_id == THINGSET_PORT_ID_INVALID) { + /* empty */ + if (empty_idx == UINT16_MAX) { + empty_idx = i; + } + } + else if (ts_ctx_uid_equal(ctx_uid, &node->ctx_uid)) { + /* match */ + match_idx = i; + break; + } + } + if (match_idx == UINT16_MAX) { + /* Node is missing in node table */ + if (empty_idx == UINT16_MAX) { + /* No space left in node table - search node to be evicted */ + match_idx = ts_ctx_node_evict(locid); + ts_ctx_node_free(locid, match_idx); + } + else { + match_idx = empty_idx; + } + /* Initialise node table entry */ + ts_ctx_node_init_phantom(locid, match_idx, ctx_uid); + } + *node_idx = match_idx; + ret = 0; + } + else { + TS_ASSERT(false, "CTX: %s used with unknown context", __func__); + ret = -ENOTSUP; + } + + return ret; +} + +uint16_t ts_ctx_node_evict(thingset_locid_t locid) +{ + uint16_t oldest_idx = UINT16_MAX; + + if (TS_CTX_IS_CORE(locid)) { + TS_ASSERT(false, "CTX: %s can not be used with core context", __func__); + } + else if (TS_CTX_IS_COM(locid)) { + struct ts_ctx_com_data *data = ts_ctx_com_data(locid); + thingset_time_ms_t last_seen_time = thingset_time_ms(); + + for (uint16_t i = 0; i < TS_ARRAY_SIZE(data->node_table.nodes); i++) { + struct ts_ctx_node *node = &data->node_table.nodes[i]; + /* Search oldest entry */ + if (node->last_seen_time <= last_seen_time) { + last_seen_time = node->last_seen_time; + oldest_idx = i; + } + } + + TS_ASSERT(oldest_idx != UINT16_MAX, "At least one entry must match."); + } + else { + TS_ASSERT(false, "CTX: %s used with unknown context", __func__); + } + + return oldest_idx; +} + +void ts_ctx_node_free(thingset_locid_t locid, uint16_t node_idx) +{ + if (TS_CTX_IS_CORE(locid)) { + TS_ASSERT(false, "CTX: %s can not be used with core context", __func__); + } + else if (TS_CTX_IS_COM(locid)) { + struct ts_ctx_com_data *data = ts_ctx_com_data(locid); + struct ts_ctx_node *node = &data->node_table.nodes[node_idx]; + + if (node->port_id == THINGSET_PORT_ID_INVALID) { + /* Already unused */ + TS_LOGD("CTX: %s multiple free for node with index %u", __func__, (unsigned)node_idx); + return; + } + /* Mark node table entry unused */ + node->port_id = THINGSET_PORT_ID_INVALID; + } + else { + TS_ASSERT(false, "CTX: %s used with unknown context", __func__); + } +} + +int ts_ctx_node_lookup(thingset_locid_t locid, const thingset_uid_t* ctx_uid, uint16_t* node_idx) +{ + int ret; + + if (TS_CTX_IS_CORE(locid)) { + TS_ASSERT(false, "CTX: %s can not be used with core context", __func__); + ret = -ENOTSUP; + } + else if (TS_CTX_IS_COM(locid)) { + struct ts_ctx_com_data *data = ts_ctx_com_data(locid); + for (uint16_t i = 0; i < TS_ARRAY_SIZE(data->node_table.nodes); i++) { + struct ts_ctx_node *node = &data->node_table.nodes[i]; + if (node->port_id == THINGSET_PORT_ID_INVALID) { + /* empty */ + continue; + } + else if (ts_ctx_uid_equal(ctx_uid, &node->ctx_uid)) { + /* match */ + *node_idx = i; + return 0; + } + } + ret = -ENODEV; + } + else { + TS_ASSERT(false, "CTX: %s used with unknown context", __func__); + ret = -ENOTSUP; + } + + return ret; + +} diff --git a/src/ts_ctx_obj.c b/src/ts_ctx_obj.c new file mode 100644 index 0000000..2a4ce2d --- /dev/null +++ b/src/ts_ctx_obj.c @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * ThingSet context (application) object support + * --------------------------------------------- + */ + +#include "thingset_env.h" +#include "thingset_time.h" + +#include "ts_obj.h" +#include "ts_ctx.h" +#include "ts_msg.h" +#include "ts_macro.h" + +#include + +int ts_ctx_obj_connect(thingset_locid_t locid, thingset_uid_t *ctx_uid, + ts_obj_id_t obj_id, const char *obj_path, thingset_time_ms_t timeout_ms, + thingset_oref_t *oref) +{ + if (TS_CTX_IS_CORE(locid)) { + /* Core context does not support information about other remote contexts */ + return -EINVAL; + } + if (TS_CTX_IS_COM(locid)) { + if ((ctx_uid == NULL) || ts_ctx_uid_equal(ts_ctx_uid(locid), ctx_uid)) { + /* local context */ + ts_obj_db_id_t did = ts_ctx_obj_db(locid); + if (did == TS_OBJ_DB_ID_INVALID) { + return -ENOMEM; + } + if (obj_path != NULL) { + return ts_obj_by_path(ts_obj_db_oref_any(did), obj_path, strlen(obj_path), oref); + } + else { + return ts_obj_db_oref_by_id(did, obj_id, oref); + } + } + } + + return 0; +} + +int ts_ctx_obj_disconnect(thingset_locid_t locid, thingset_oref_t oref) +{ + if (oref.db_id == ts_ctx_obj_db(locid)) { + /* local context */ + return 0; + } + /* remote context */ + + return 0; +} diff --git a/src/ts_ctx_process.c b/src/ts_ctx_process.c new file mode 100644 index 0000000..4e8a7a0 --- /dev/null +++ b/src/ts_ctx_process.c @@ -0,0 +1,1354 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * ThingSet context (receive) message processing + * --------------------------------------------- + */ + +#include "thingset_env.h" +#include "thingset_time.h" + +#include "ts_obj.h" +#include "ts_ctx.h" +#include "ts_msg.h" +#include "ts_macro.h" + +#include + + +int ts_ctx_process_lock(thingset_locid_t locid) +{ + int ret; + + if (TS_CTX_IS_CORE(locid)) { + struct ts_ctx_core_data *data = ts_ctx_core_data(locid); + ret = pthread_mutex_lock(&data->common.process_mutex); + } + else if (TS_CTX_IS_COM(locid)) { + struct ts_ctx_com_data *data = ts_ctx_com_data(locid); + ret = pthread_mutex_lock(&data->common.process_mutex); + } + else { + TS_ASSERT(false, "CTX: %s used with unknown context", __func__); + ret = -ENOTSUP; + } + + return ret; +} + +int ts_ctx_process_unlock(thingset_locid_t locid) +{ + int ret; + + if (TS_CTX_IS_CORE(locid)) { + struct ts_ctx_core_data *data = ts_ctx_core_data(locid); + ret = pthread_mutex_unlock(&data->common.process_mutex); + } + else if (TS_CTX_IS_COM(locid)) { + struct ts_ctx_com_data *data = ts_ctx_com_data(locid); + ret = pthread_mutex_unlock(&data->common.process_mutex); + } + else { + TS_ASSERT(false, "CTX: %s used with unknown context", __func__); + ret = -ENOTSUP; + } + + return ret; +} + +int thingset_process(thingset_locid_t locid, struct thingset_msg *msg) +{ + struct thingset_msg *msg_out = NULL; + + int ret = ts_ctx_process_msg(locid, msg, &msg_out); + + if ((ret != 0) || (msg_out == NULL)) { + return ret; + } + + thingset_portid_t port_id_dest = ts_msg_proc_get_port_dest(msg_out); + ret = ts_ctx_transmit(locid, port_id_dest, msg_out, THINGSET_TIMEOUT_FOREVER); + + return ret; +} + +int ts_ctx_process_msg(thingset_locid_t locid, struct thingset_msg *msg_in, + struct thingset_msg **msg_out) +{ + TS_MSG_ASSERT_SCRATCHTYPE(msg_in, TS_MSG_SCRATCHPAD_PROC); + + int ret = ts_ctx_process_lock(locid); + if (ret != 0) { + return ret; + } + + /* Update message status */ + if (ts_msg_status(msg_in).valid == TS_MSG_VALID_UNSET) { + uint8_t command_id = *ts_msg_data(msg_in); + if (strchr("?=+-!#:", command_id) != NULL) { + /* Text mode message */ + if (command_id == '#') { + ts_msg_status_set(msg_in, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_STATEMENT, + 0); + } + else if (command_id == ':') { + ts_msg_status_set(msg_in, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_RESPONSE, + 0); + } + else { + ts_msg_status_set(msg_in, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_REQUEST, + 0); + } + } + else if ((strchr("\x01\x02\x04\x05\x07\x1F", command_id) != NULL) || + (command_id >= 0x80U)) { + /* Binary mode message */ + if (command_id == TS_MSG_CODE_BIN_STATEMENT) { + ts_msg_status_set(msg_in, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_STATEMENT, + 0); + } + else if (command_id >= 0x80U) { + ts_msg_status_set(msg_in, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + command_id); + } + else { + ts_msg_status_set(msg_in, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_REQUEST, + 0); + } + } + else { + TS_ASSERT(false, "%s can not process message with command 0x%.2u '%c'", __func__, + (unsigned int)command_id, (char)command_id); + ret = -ENOTSUP; + goto ts_ctx_process_msg_unlock; + } + } + + /* Remember current state of ThingSet objects authorisation flags in message */ + ts_msg_auth_set(msg_in, thingset_authorisation(locid)); + + /* Save processing info - msg_in may be consumed by processing */ + thingset_portid_t port_id_src = ts_msg_proc_get_port_src(msg_in); + thingset_portid_t port_id_dest = ts_msg_proc_get_port_dest(msg_in); + + if (TS_CTX_IS_COM(locid) && ts_msg_proc_get_port_dest(msg_in) != THINGSET_PORT_ID_INVALID) { + /* We have a destination port - just route the message to the port */ + *msg_out = msg_in; + + struct ts_ctx_com_data *data = ts_ctx_com_data(locid); + if (ts_msg_status_type(msg_in) == TS_MSG_TYPE_REQUEST) { + /* + * An application connected to source port is doing a request to another port. + * Link the two ports for the given unique context identifier until we got a response. + */ + uint16_t node_idx; + ret = ts_ctx_node_get(locid, ts_msg_proc_get_ctx_uid(msg_in), &node_idx); + if (ret != 0) { + goto ts_ctx_process_msg_unlock; + } + /* Check whether we have an ongoing request to this node */ + /** @todo check ongoing request */ + if (false) { + ret = -EBUSY; + goto ts_ctx_process_msg_unlock; + } + /* Link the ports */ + data->node_table.nodes[node_idx].ctx_uid = *ts_msg_proc_get_ctx_uid(msg_in); + data->node_table.nodes[node_idx].port_id = port_id_dest; + data->node_table.nodes[node_idx].response_port_id = port_id_src; + data->node_table.nodes[node_idx].last_seen_time = thingset_time_ms(); + } + } + /* + * Core context always works on this context. + * Communication context with no destination port also works on this context. + */ + else if (ts_msg_status_type(msg_in) == TS_MSG_TYPE_REQUEST) { + /* Text or binary mode request to this context */ + ret = ts_ctx_process_request(locid, msg_in, msg_out); + if (ret != 0) { + goto ts_ctx_process_msg_unlock; + } + if (*msg_out != NULL) { + ret = ts_msg_proc_setup(*msg_out); + if (ret != 0) { + goto ts_ctx_process_msg_unlock; + } + if (TS_CTX_IS_COM(locid)) { + ts_msg_proc_set_ctx_uid(*msg_out, ts_ctx_uid(locid)); + ts_msg_proc_set_port_src(*msg_out, THINGSET_PORT_ID_INVALID); + ts_msg_proc_set_port_dest(*msg_out, port_id_src); + } + else if (TS_CTX_IS_CORE(locid)) { + ts_msg_proc_set_port_src(*msg_out, THINGSET_PORT_ID_INVALID); + ts_msg_proc_set_port_dest(*msg_out, THINGSET_PORT_ID_INVALID); + } + } + } + else if (TS_CTX_IS_COM(locid)) { + /* Success or error response message - look who is waiting for this response message */ + struct ts_ctx_com_data *data = ts_ctx_com_data(locid); + + uint16_t node_idx; + ret = ts_ctx_node_lookup(locid, ts_msg_proc_get_ctx_uid(msg_in), &node_idx); + if (ret != 0) { + /* Nobody is waiting - just consume */ + thingset_msg_unref(msg_in); + *msg_out = NULL; + ret = 0; + goto ts_ctx_process_msg_unlock; + } + /* Update last seen */ + data->node_table.nodes[node_idx].last_seen_time = thingset_time_ms(); + + /* Transfer message to application port */ + *msg_out = msg_in; + ts_msg_proc_set_port_dest(*msg_out, data->node_table.nodes[node_idx].response_port_id); + } + else { + TS_ASSERT(false, "%s can not process success or error response message with other than" + " communication context", __func__); + ret = -ENOTSUP; + } + +ts_ctx_process_msg_unlock: + int ret_unlock = ts_ctx_process_unlock(locid); + if (ret == 0) { + ret = ret_unlock; + } + + return ret; +} + +int thingset_process_buf(thingset_locid_t locid, const uint8_t *request, size_t request_len, + uint8_t *response, size_t response_size) +{ + struct thingset_msg *msg = NULL; + int ret = 0; + + if (request[0] < 0x20) { + ret = thingset_msg_alloc_cbor((uint16_t)request_len + 1, THINGSET_TIMEOUT_IMMEDIATE, &msg); + } + else { + ret = thingset_msg_alloc_json((uint16_t)request_len + 1, THINGSET_TIMEOUT_IMMEDIATE, &msg); + } + if (ret != 0) { + return ret; + } + ret = ts_msg_proc_setup(msg); + if (ret != 0) { + goto ts_ctx_process_buf_error; + } + ret = ts_msg_add_mem(msg, request, (uint16_t)request_len); + if (ret != 0) { + goto ts_ctx_process_buf_error; + } + if (ts_msg_tailroom(msg) == 0) { + goto ts_ctx_process_buf_error; + } + /* Add hint of response message size to request */ + ts_msg_proc_set_resp_size(msg, response_size); + /* Assure string end for JSON type messages */ + *ts_msg_tail(msg) = '\0'; + +#if TS_CONFIG_LOG + char log_buf[200]; + ret = ts_msg_log(msg, &log_buf[0], sizeof(log_buf)); + TS_LOGD("%s processes message: >%d<, >%.*s<", __func__, ts_msg_len(msg), ret, &log_buf[0]); +#endif + + struct thingset_msg *msg_out = NULL; + ret = ts_ctx_process_msg(locid, msg, &msg_out); + if (ret != 0) { + if (msg_out != NULL) { + thingset_msg_unref(msg_out); + } + goto ts_ctx_process_buf_error; + } + + unsigned int response_len = 0; + if (msg_out != NULL) { +#if TS_CONFIG_LOG + ret = ts_msg_log(msg_out, &log_buf[0], sizeof(log_buf)); + TS_LOGD("%s created reponse: >%d<, >%.*s<", __func__, ts_msg_len(msg_out), ret, + &log_buf[0]); +#endif + if (ts_msg_len(msg_out) > response_size) { + ret = -ENOMEM; + } + else { + response_len = ts_msg_len(msg_out); + memcpy(response, ts_msg_data(msg_out), response_len); + ret = response_len; + } + thingset_msg_unref(msg_out); + } + if (response_size > response_len) { + /* Assure string end for JSON type reponse messages */ + response[response_len] = '\0'; + } + return ret; + +ts_ctx_process_buf_error: + response[0] = '\0'; + thingset_msg_unref(msg); + return ret; +} + +int ts_ctx_process_request(thingset_locid_t locid, struct thingset_msg *request, + struct thingset_msg **response) +{ + TS_ASSERT(ts_msg_status_type(request) == TS_MSG_TYPE_REQUEST, + "ThingSet: %s unexpected message type (%d)", __func__, + (int)ts_msg_status_type(request)); + + int ret; + thingset_oref_t oref = ts_obj_db_oref_any(ts_ctx_obj_db(locid)); + uint16_t response_size_hint = ts_msg_proc_get_resp_size(request); + + if (ts_msg_status_proto(request) == TS_MSG_PROTO_BIN) { + ret = ts_msg_pull_request_cbor(request, &oref); + } + else if (ts_msg_status_proto(request) == TS_MSG_PROTO_TXT) { + ret = ts_msg_pull_request_json(request, &oref); + } + else { + /* Not a ThingSet command --> ignore and set response to None */ + *response = NULL; + return 0; + } + if (ret != 0) { + return ret; + } + + if (ts_msg_status_valid(request) != TS_MSG_VALID_OK) { + /* Request is faulty */ + return ts_ctx_process_request_error(ts_msg_status_proto(request), + ts_msg_status_code(request), response); + } + + switch (ts_msg_status_code(request)) { + case TS_MSG_CODE_REQUEST_CREATE: + if (ts_msg_status_proto(request) == TS_MSG_PROTO_BIN) { + ret = ts_ctx_process_create_cbor(request, oref, response); + } else { + ret = ts_ctx_process_create_json(request, oref, response); + } + break; + case TS_MSG_CODE_REQUEST_DELETE: + if (ts_msg_status_proto(request) == TS_MSG_PROTO_BIN) { + ret = ts_ctx_process_delete_cbor(request, oref, response); + } else { + ret = ts_ctx_process_delete_json(request, oref, response); + } + break; + case TS_MSG_CODE_REQUEST_EXEC: + TS_ASSERT(ts_obj_db_oref_is_object(oref), + "OBJ: %s EXEC on invalid object reference (%u:%u)", + __func__, (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + if (ts_msg_status_proto(request) == TS_MSG_PROTO_BIN) { + ret = ts_ctx_process_exec_cbor(request, oref, response); + } else { + ret = ts_ctx_process_exec_json(request, oref, response); + } + if ((ret == 0) && (ts_msg_status_code(*response) == TS_MSG_CODE_VALID)) { + /* Call it if processing succeeded */ + ts_obj_exec_t exec = ts_obj_exec_data(oref); + TS_ASSERT(exec != NULL, "CTX: %s on no exec function (NULL)", __func__); + (*exec)(); + } + break; + case TS_MSG_CODE_REQUEST_FETCH_IDS: + case TS_MSG_CODE_REQUEST_FETCH_NAMES: + case TS_MSG_CODE_REQUEST_FETCH_SINGLE: + case TS_MSG_CODE_REQUEST_FETCH_VALUES: + if (response_size_hint == 0) { + response_size_hint = 256; + } + if (ts_msg_status_proto(request) == TS_MSG_PROTO_BIN) { + ret = thingset_msg_alloc_cbor(response_size_hint, 0, response); + if (ret != 0) { + return ts_ctx_process_request_error(ts_msg_status_proto(request), + TS_MSG_CODE_INTERNAL_SERVER_ERR, response); + } + ret = ts_msg_add_response_fetch_cbor(*response, oref, request); + } else { + ret = thingset_msg_alloc_json(response_size_hint, 0, response); + if (ret != 0) { + return ts_ctx_process_request_error(ts_msg_status_proto(request), + TS_MSG_CODE_INTERNAL_SERVER_ERR, response); + } + ret = ts_msg_add_response_fetch_json(*response, oref, request); + } + thingset_msg_unref(request); + break; + case TS_MSG_CODE_REQUEST_GET_IDS: + case TS_MSG_CODE_REQUEST_GET_IDS_VALUES: + case TS_MSG_CODE_REQUEST_GET_NAMES: + case TS_MSG_CODE_REQUEST_GET_NAMES_VALUES: + case TS_MSG_CODE_REQUEST_GET_VALUES: + if (response_size_hint == 0) { + response_size_hint = 256; + } + if (ts_msg_status_proto(request) == TS_MSG_PROTO_BIN) { + ret = thingset_msg_alloc_cbor(response_size_hint, 0, response); + if (ret != 0) { + return ts_ctx_process_request_error(ts_msg_status_proto(request), + TS_MSG_CODE_INTERNAL_SERVER_ERR, response); + } + ret = ts_msg_add_response_get_cbor(*response, oref, request); + } else { + ret = thingset_msg_alloc_json(response_size_hint, 0, response); + if (ret != 0) { + return ts_ctx_process_request_error(ts_msg_status_proto(request), + TS_MSG_CODE_INTERNAL_SERVER_ERR, response); + } + ret = ts_msg_add_response_get_json(*response, oref, request); + } + thingset_msg_unref(request); + break; + case TS_MSG_CODE_REQUEST_PATCH: + TS_ASSERT(ts_obj_db_oref_is_tree(oref), + "OBJ: %s PATCH on invalid object reference (%u:%u)", + __func__, (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + if (ts_msg_status_proto(request) == TS_MSG_PROTO_BIN) { + ret = ts_ctx_process_patch_cbor(request, oref, response); + } else { + ret = ts_ctx_process_patch_json(request, oref, response); + } + + if ((ret == 0) && (ts_msg_status_code(*response) == TS_MSG_CODE_CHANGED)) { + ts_obj_exec_t exec = ts_obj_exec_data(oref); + if (exec != NULL) { + /* Call it if processing succeeded and change function available */ + (*exec)(); + } + } + break; + default: + /* Should never happen */ + TS_ASSERT(false, "Unexpected message status code (0x%02x)", + (unsigned int)ts_msg_status_code(request)); + ret = 0; + break; + } + return ret; +} + +int ts_ctx_process_request_error(enum ts_msg_proto response_msg_proto, + enum ts_msg_code response_msg_code, + struct thingset_msg **response) +{ + int ret; + + if (response_msg_proto == TS_MSG_PROTO_TXT) { + if (TS_CONFIG_VERBOSE_STATUS_MESSAGES) { + ret = ts_msg_alloc_raw(31, 0, response); + if (ret != 0) { + ret = ts_msg_alloc_raw(4, 0, response); + } + } + else { + ret = ts_msg_alloc_raw(4, 0, response); + } + } + else { + ret = ts_msg_alloc_raw(1, 0, response); + } + if (ret != 0) { + return ret; + } + + ts_msg_status_set(*response, TS_MSG_VALID_OK, response_msg_proto, TS_MSG_TYPE_RESPONSE, + response_msg_code); + if (response_msg_proto == TS_MSG_PROTO_BIN) { + ret = ts_msg_add_response_status_cbor(*response); + } + else { + ret = ts_msg_add_response_status_json(*response); + } + + return ret; +} + +int ts_ctx_process_create_cbor(struct thingset_msg *request, thingset_oref_t oref, + struct thingset_msg **response) +{ + TS_MSG_ASSERT_SCRATCHTYPE(request, TS_MSG_SCRATCHPAD_CBOR_DEC); + + int ret = thingset_msg_alloc_json(16, 0, response); + if (ret != 0) { + return ts_ctx_process_request_error(ts_msg_status_proto(request), + TS_MSG_CODE_INTERNAL_SERVER_ERR, response); + } + + if (ts_obj_type(oref) == TS_T_ARRAY) { + // Remark: See commit history with implementation for pub/sub ID arrays as inspiration + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_NOT_IMPLEMENTED); + return ts_msg_add_response_status_cbor(*response); + } + else if (ts_obj_type(oref) == TS_T_SUBSET) { + thingset_oref_t new_oref; + ts_obj_id_t id; + const char *name; + uint16_t name_len; + ret = ts_msg_pull_u16_cbor(request, &id); + if (ret == 0) { + ret = ts_obj_db_oref_by_id(oref.db_id, id, &new_oref); + if (ret != 0) { + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, + TS_MSG_TYPE_RESPONSE, TS_MSG_CODE_NOT_FOUND); + return ts_msg_add_response_status_cbor(*response); + } + } + else { + ret = ts_msg_pull_string_cbor(request, &name, &name_len); + if (ret == 0) { + ret = ts_obj_by_name(oref, name, name_len, &new_oref); + if (ret != 0) { + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, + TS_MSG_TYPE_RESPONSE, TS_MSG_CODE_NOT_FOUND); + return ts_msg_add_response_status_cbor(*response); + } + } + } + if (ret != 0) { + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_BAD_REQUEST); + return ts_msg_add_response_status_cbor(*response); + } + + ret = ts_obj_subsets_add(new_oref, (uint16_t)ts_obj_detail(oref)); + if (ret != 0) { + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_INTERNAL_SERVER_ERR); + return ts_msg_add_response_status_cbor(*response); + } + + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_CREATED); + return ts_msg_add_response_status_cbor(*response); + } + + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_METHOD_NOT_ALLOWED); + return ts_msg_add_response_status_cbor(*response); +} + +int ts_ctx_process_create_json(struct thingset_msg *request, thingset_oref_t oref, + struct thingset_msg **response) +{ + TS_MSG_ASSERT_SCRATCHTYPE(request, TS_MSG_SCRATCHPAD_JSON_DEC); + + int ret = thingset_msg_alloc_json(16, 0, response); + if (ret != 0) { + return ts_ctx_process_request_error(ts_msg_status_proto(request), + TS_MSG_CODE_INTERNAL_SERVER_ERR, response); + } + + if (ts_obj_type(oref) == TS_T_ARRAY) { + // Remark: See commit history with implementation for pub/sub ID arrays as inspiration + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_NOT_IMPLEMENTED); + return ts_msg_add_response_status_json(*response); + } + else if (ts_obj_type(oref) == TS_T_SUBSET) { + thingset_oref_t new_oref; + const char *name; + uint16_t name_len; + ret = ts_msg_pull_string_json(request, &name, &name_len); + if (ret == 0) { + ret = ts_obj_by_name(ts_obj_db_oref_any(oref.db_id), name, name_len, &new_oref); + if (ret != 0) { + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, + TS_MSG_TYPE_RESPONSE, TS_MSG_CODE_NOT_FOUND); + return ts_msg_add_response_status_json(*response); + } + } + if (ret != 0) { + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_BAD_REQUEST); + return ts_msg_add_response_status_json(*response); + } + + ret = ts_obj_subsets_add(new_oref, (uint16_t)ts_obj_detail(oref)); + if (ret != 0) { + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_INTERNAL_SERVER_ERR); + return ts_msg_add_response_status_cbor(*response); + } + + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_CREATED); + return ts_msg_add_response_status_json(*response); + } + + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_METHOD_NOT_ALLOWED); + return ts_msg_add_response_status_json(*response); +} + +int ts_ctx_process_delete_cbor(struct thingset_msg *request, thingset_oref_t oref, + struct thingset_msg **response) +{ + TS_MSG_ASSERT_SCRATCHTYPE(request, TS_MSG_SCRATCHPAD_CBOR_DEC); + + int ret = thingset_msg_alloc_json(16, 0, response); + if (ret != 0) { + return ts_ctx_process_request_error(ts_msg_status_proto(request), + TS_MSG_CODE_INTERNAL_SERVER_ERR, response); + } + + if (ts_obj_type(oref) == TS_T_ARRAY) { + // Remark: See commit history with implementation for pub/sub ID arrays as inspiration + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_NOT_IMPLEMENTED); + return ts_msg_add_response_status_cbor(*response); + } + else if (ts_obj_type(oref) == TS_T_SUBSET) { + thingset_oref_t del_oref; + ts_obj_id_t id; + const char *name; + uint16_t name_len; + ret = ts_msg_pull_u16_cbor(request, &id); + if (ret == 0) { + ret = ts_obj_db_oref_by_id(oref.db_id, id, &del_oref); + if (ret != 0) { + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, + TS_MSG_TYPE_RESPONSE, TS_MSG_CODE_NOT_FOUND); + return ts_msg_add_response_status_cbor(*response); + } + } + else { + ret = ts_msg_pull_string_cbor(request, &name, &name_len); + if (ret == 0) { + ret = ts_obj_by_name(oref, name, name_len, &del_oref); + if (ret != 0) { + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, + TS_MSG_TYPE_RESPONSE, TS_MSG_CODE_NOT_FOUND); + return ts_msg_add_response_status_cbor(*response); + } + } + } + if (ret != 0) { + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_BAD_REQUEST); + return ts_msg_add_response_status_cbor(*response); + } + + ret = ts_obj_subsets_remove(del_oref, (uint16_t)ts_obj_detail(oref)); + if (ret != 0) { + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_INTERNAL_SERVER_ERR); + return ts_msg_add_response_status_cbor(*response); + } + + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_DELETED); + return ts_msg_add_response_status_cbor(*response); + } + + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_METHOD_NOT_ALLOWED); + return ts_msg_add_response_status_cbor(*response); +} + +int ts_ctx_process_delete_json(struct thingset_msg *request, thingset_oref_t oref, + struct thingset_msg **response) +{ + TS_MSG_ASSERT_SCRATCHTYPE(request, TS_MSG_SCRATCHPAD_JSON_DEC); + + int ret = thingset_msg_alloc_json(16, 0, response); + if (ret != 0) { + return ts_ctx_process_request_error(ts_msg_status_proto(request), + TS_MSG_CODE_INTERNAL_SERVER_ERR, response); + } + + if (ts_obj_type(oref) == TS_T_ARRAY) { + // Remark: See commit history with implementation for pub/sub ID arrays as inspiration + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_NOT_IMPLEMENTED); + return ts_msg_add_response_status_json(*response); + } + else if (ts_obj_type(oref) == TS_T_SUBSET) { + thingset_oref_t del_oref; + const char *name; + uint16_t name_len; + ret = ts_msg_pull_string_json(request, &name, &name_len); + if (ret == 0) { + ret = ts_obj_by_name(ts_obj_db_oref_any(oref.db_id), name, name_len, &del_oref); + if (ret != 0) { + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, + TS_MSG_TYPE_RESPONSE, TS_MSG_CODE_NOT_FOUND); + return ts_msg_add_response_status_json(*response); + } + } + if (ret != 0) { + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_BAD_REQUEST); + return ts_msg_add_response_status_json(*response); + } + + ret = ts_obj_subsets_remove(del_oref, (uint16_t)ts_obj_detail(oref)); + if (ret != 0) { + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_INTERNAL_SERVER_ERR); + return ts_msg_add_response_status_cbor(*response); + } + + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_DELETED); + return ts_msg_add_response_status_json(*response); + } + + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_METHOD_NOT_ALLOWED); + return ts_msg_add_response_status_json(*response); +} + +int ts_ctx_process_exec_cbor(struct thingset_msg *request, thingset_oref_t oref, + struct thingset_msg **response) +{ + int ret = ts_ctx_process_set_cbor(request, oref, response); + if (ret != 0) { + return ret; + } + + /* EXEC reports TS_MSG_CODE_VALID on success - set reports TS_MSG_CODE_CHANGED */ + if ((ts_msg_status_valid(*response) == TS_MSG_VALID_OK) && + (ts_msg_status_code(*response) == TS_MSG_CODE_CHANGED)) { + thingset_msg_reset(*response); + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_VALID); + return ts_msg_add_response_status_cbor(*response); + } + return 0; +} + +int ts_ctx_process_exec_json(struct thingset_msg *request, thingset_oref_t oref, + struct thingset_msg **response) +{ + int ret = ts_ctx_process_set_json(request, oref, response); + if (ret != 0) { + return ret; + } + + /* EXEC reports TS_MSG_CODE_VALID on success - set reports TS_MSG_CODE_CHANGED */ + if ((ts_msg_status_valid(*response) == TS_MSG_VALID_OK) && + (ts_msg_status_code(*response) == TS_MSG_CODE_CHANGED)) { + thingset_msg_reset(*response); + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_VALID); + return ts_msg_add_response_status_json(*response); + } + return 0; +} + +int ts_ctx_process_get_cbor(struct thingset_msg *request, thingset_oref_t oref, + struct thingset_msg **response) +{ + return 0; +} + +int ts_ctx_process_get_json(struct thingset_msg *request, thingset_oref_t oref, + struct thingset_msg **response) +{ + return 0; +} + +int ts_ctx_process_patch_cbor(struct thingset_msg *request, thingset_oref_t oref, + struct thingset_msg **response) +{ + return ts_ctx_process_set_cbor(request, oref, response); +} + +int ts_ctx_process_patch_json(struct thingset_msg *request, thingset_oref_t oref, + struct thingset_msg **response) +{ + return ts_ctx_process_set_json(request, oref, response); +} + +int ts_ctx_process_set_cbor(struct thingset_msg *request, thingset_oref_t oref, + struct thingset_msg **response) +{ + TS_ASSERT(ts_obj_db_oref_is_tree(oref), + "CTX: %s on invalid object reference (%u:%u)", __func__, + (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + + int ret = thingset_msg_alloc_cbor(16, 0, response); + if (ret != 0) { + return ts_ctx_process_request_error(ts_msg_status_proto(request), + TS_MSG_CODE_INTERNAL_SERVER_ERR, response); + } + + /* + * Check and validate request format + * --------------------------------- + */ + uint16_t child_count; + ret = ts_obj_child_count(oref, &child_count); + TS_ASSERT(ret == 0, "CTX: %s calling ts_obj_child_count() fails (%d) - should never fail", + __func__, ret); + + if (child_count == 0) { + if (ts_msg_len(request) == 0) { + if (ts_obj_type(oref) == TS_T_EXEC) { + /* void function */ + goto ts_process_set_cbor_end; + } + /* For all other types we expect a value */ + TS_LOGD("CTX: %s on object %s with %d children but no value provided", __func__, + ts_obj_name(oref), (unsigned int)child_count); + goto ts_process_set_cbor_bad_request; + } + } + + struct ts_msg_state request_state; + ts_msg_state_save(request, &request_state); + + TS_MSG_ASSERT_SCRATCHTYPE(request, TS_MSG_SCRATCHPAD_CBOR_DEC); + + bool set_by_map = false; + bool set_by_array = false; + uint16_t num_elements; + if (child_count == 0) { + if (ts_obj_type(oref) == TS_T_EXEC) { + /* Special case - None parameters may be indicated by empty array */ + ret = ts_msg_pull_array_cbor(request, &num_elements); + if ((ret != 0) || (num_elements != 0)) { + TS_LOGD("CTX: %s on object %s with %d children but value provided", __func__, + ts_obj_name(oref), (unsigned int)child_count); + goto ts_process_set_cbor_bad_request; + } + /* void function */ + goto ts_process_set_cbor_end; + } + else { + TS_ASSERT(!(ts_obj_type(oref) == TS_T_GROUP) && + !(ts_obj_type(oref) == TS_T_SUBSET), + "CTX: %s on %s%s %s with no children (0)", __func__, + ts_obj_type(oref) == TS_T_GROUP ? "group" : "", + ts_obj_type(oref) == TS_T_SUBSET ? "subset" : "", + ts_obj_name(oref)); + /* No children - set value of object itself */ + num_elements = 1; + } + } + else if (child_count == 1) { + /* One child - value may be given by array with one element or by map or by value itself */ + ret = ts_msg_pull_array_cbor(request, &num_elements); + if (ret == 0) { + if (num_elements != 1) { + TS_LOGD("CTX: %s on object %s with %d children but array " + "with %u elements provided", __func__, ts_obj_name(oref), + (unsigned int)child_count, (unsigned int)num_elements); + goto ts_process_set_cbor_bad_request; + } + set_by_array = true; + } + else { + ret = ts_msg_pull_map_cbor(request, &num_elements); + if (ret == 0) { + if (num_elements != 1) { + TS_LOGD("CTX: %s on object %s with %d children but map " + "with %u elements provided", __func__, ts_obj_name(oref), + (unsigned int)child_count, (unsigned int)num_elements); + goto ts_process_set_cbor_bad_request; + } + set_by_map = true; + } + else { + ret = 0; + num_elements = 1; + } + } + } + else { + /* child_count > 1 - value may be given by map only */ + ret = ts_msg_pull_map_cbor(request, &num_elements); + if (ret != 0) { + TS_LOGD("CTX: %s on object %s with %d children but no map provided", __func__, + ts_obj_name(oref), (unsigned int)child_count); + goto ts_process_set_cbor_bad_request; + } + set_by_map = true; + } + + /* + * Loop through all elements to check if request is valid + * ------------------------------------------------------ + */ + TS_ASSERT(ret == 0, "ThingSet: %s should handle all errors before here (%d)", __func__, ret); + thingset_oref_t child_oref; + for (uint16_t count = 0;(count < num_elements) && (ret == 0); count++) { + /* Get child oref */ + if (set_by_map) { + /* Check whether object is in database */ + const char *name; + uint16_t name_len; + ret = ts_msg_pull_string_cbor(request, &name, &name_len); + if (ret == 0) { + /* We got the object name */ + ret = ts_obj_by_name(oref, name, name_len, &child_oref); + if (ret != 0) { + goto ts_process_set_cbor_not_found; + } + } + else { + ts_obj_id_t obj_id; + ret = ts_msg_pull_u16_cbor(request, &obj_id); + if (ret != 0) { + TS_LOGD("ThingSet: %s set by unsupported key format - object: %s", __func__, + ts_obj_name(child_oref)); + goto ts_process_set_cbor_unsupported_format; + } + ret = ts_obj_db_oref_by_id(oref.db_id, obj_id, &child_oref); + if (ret != 0) { + goto ts_process_set_cbor_not_found; + } + } + } + else if (set_by_array) { + if (count == 0) { + if (child_count == 0) { + child_oref = oref; + } + else { + ret = ts_obj_child_first(oref, &child_oref); + TS_ASSERT(ret == 0, + "ThingSet: %s calling ts_obj_child_next() fails (%d) - should never fail", + __func__, ret); + } + } + else { + ret = ts_obj_child_next(&child_oref); + TS_ASSERT(ret == 0, + "ThingSet: %s calling ts_obj_child_next() fails (%d) - should never fail", + __func__, ret); + } + } + else if (count == 0) { + /* no map, no array -> simple object */ + if (child_count == 0) { + child_oref = oref; + } + else { + ret = ts_obj_child_first(oref, &child_oref); + TS_ASSERT(ret == 0, + "ThingSet: %s calling ts_obj_child_first() fails (%d) - should never fail", + __func__, ret); + } + } + else { + /* count > 0 */ + TS_ASSERT(false, "ThingSet: %s count > 0 - should never happen for object set", + __func__); + goto ts_process_set_cbor_bad_request; + } + + /* Check access */ + if (!ts_obj_access_write(child_oref, ts_msg_auth(request))) { + if (ts_obj_access_write(child_oref, TS_WRITE_MASK)) { + TS_LOGD("%s: set not authorized: >0x%04x< - object: %s >0x%04x<", __func__, + (unsigned int)ts_msg_auth(request), ts_obj_name(child_oref), + (unsigned int)ts_obj_access(child_oref)); + thingset_msg_unref(request); + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, + TS_MSG_TYPE_RESPONSE, TS_MSG_CODE_UNAUTHORIZED); + return ts_msg_add_response_status_cbor(*response); + } + else { + TS_LOGD("%s: set forbidden: >0x%04x< - object: %s >0x%04x<", __func__, + (unsigned int)ts_msg_auth(request), ts_obj_name(child_oref), + (unsigned int)ts_obj_access(child_oref)); + thingset_msg_unref(request); + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, + TS_MSG_TYPE_RESPONSE, TS_MSG_CODE_FORBIDDEN); + return ts_msg_add_response_status_cbor(*response); + } + } + + /* Check object type and available buffer length vs. value provided */ + ret = ts_msg_pull_object_cbor(request, child_oref, false); + if (ret != 0) { + TS_LOGD("ThingSet: %s set by unsupported value format - object: %s", __func__, + ts_obj_name(child_oref)); + goto ts_process_set_cbor_unsupported_format; + } + } + if (set_by_map) { + ret = ts_msg_pull_map_end_cbor(request); + } + else if (set_by_array) { + ret = ts_msg_pull_array_end_cbor(request); + } + if (ret != 0) { + goto ts_process_set_cbor_bad_request; + } + + /* + * Actually write data + * ------------------- + */ + ts_msg_state_restore(request, &request_state); + ret = ts_msg_cbor_dec_setup(request); + TS_ASSERT(ret == 0, "Scratchpad init fail after message state restore"); + + if (set_by_map) { + (void)ts_msg_pull_map_cbor(request, &num_elements); + } + else if (set_by_array) { + (void)ts_msg_pull_array_cbor(request, &num_elements); + } + for (uint16_t count = 0; count < num_elements; count++) { + /* Get child oref */ + if (set_by_map) { + /* Check whether object is in database */ + const char *name; + uint16_t name_len; + ret = ts_msg_pull_string_cbor(request, &name, &name_len); + if (ret == 0) { + /* We got the object name */ + (void)ts_obj_by_name(oref, name, name_len, &child_oref); + } + else { + ts_obj_id_t obj_id; + (void)ts_msg_pull_u16_cbor(request, &obj_id); + (void)ts_obj_db_oref_by_id(oref.db_id, obj_id, &child_oref); + } + } + else if (set_by_array) { + if (count == 0) { + if (child_count == 0) { + child_oref = oref; + } + else { + (void)ts_obj_child_first(oref, &child_oref); + } + } + else { + (void)ts_obj_child_next(&child_oref); + } + } + else { + if (child_count == 0) { + child_oref = oref; + } + else { + (void)ts_obj_child_first(oref, &child_oref); + } + } + + (void)ts_msg_pull_object_cbor(request, child_oref, true); + } + +ts_process_set_cbor_end: + thingset_msg_unref(request); + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_CHANGED); + return ts_msg_add_response_status_cbor(*response); + +ts_process_set_cbor_bad_request: + thingset_msg_unref(request); + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_BAD_REQUEST); + return ts_msg_add_response_status_cbor(*response); + +ts_process_set_cbor_not_found: + thingset_msg_unref(request); + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_NOT_FOUND); + return ts_msg_add_response_status_cbor(*response); + +ts_process_set_cbor_unsupported_format: + thingset_msg_unref(request); + ts_msg_status_set(*response, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_UNSUPPORTED_FORMAT); + return ts_msg_add_response_status_cbor(*response); +} + +int ts_ctx_process_set_json(struct thingset_msg *request, thingset_oref_t oref, + struct thingset_msg **response) +{ + TS_ASSERT(ts_obj_db_oref_is_tree(oref), + "CTX: %s on invalid object reference (%u:%u)", __func__, + (unsigned int)oref.db_id, (unsigned int)oref.db_oid); + + /* + * Check and validate request format + * --------------------------------- + */ + uint16_t child_count; + int ret = ts_obj_child_count(oref, &child_count); + TS_ASSERT(ret == 0, "CTX: %s calling ts_obj_child_count() fails (%d) - should never fail", + __func__, ret); + + if (child_count == 0) { + if (ts_msg_len(request) == 0) { + if (ts_obj_type(oref) == TS_T_EXEC) { + /* void function */ + goto ts_process_set_json_end; + } + /* For all other types we expect a value */ + TS_LOGD("CTX: %s on object %s with %d children but no value provided", __func__, + ts_obj_name(oref), (unsigned int)child_count); + goto ts_process_set_json_bad_request; + } + } + + struct ts_msg_state request_state; + ts_msg_state_save(request, &request_state); + + TS_MSG_ASSERT_SCRATCHTYPE(request, TS_MSG_SCRATCHPAD_JSON_DEC); + + bool set_by_map = false; + bool set_by_array = false; + uint16_t num_elements; + if (child_count == 0) { + if (ts_obj_type(oref) == TS_T_EXEC) { + /* Special case - None parameters may be indicated by empty array */ + ret = ts_msg_pull_array_cbor(request, &num_elements); + if ((ret != 0) || (num_elements != 0)) { + TS_LOGD("CTX: %s on object %s with %d children but value provided", __func__, + ts_obj_name(oref), (unsigned int)child_count); + goto ts_process_set_json_bad_request; + } + /* void function */ + goto ts_process_set_json_end; + } + else { + TS_ASSERT(!(ts_obj_type(oref) == TS_T_GROUP) && !(ts_obj_type(oref) == TS_T_SUBSET), + "ThingSet: %s on %s%s %s with no children (0)", __func__, + ts_obj_type(oref) == TS_T_GROUP ? "group" : "", + ts_obj_type(oref) == TS_T_SUBSET ? "subset" : "", + ts_obj_name(oref)); + /* No children - set value of object itself */ + num_elements = 1; + } + } + else if (child_count == 1) { + /* One child - value may be given by array with one element or by map or by value itself */ + ret = ts_msg_pull_array_json(request, &num_elements); + if (ret == 0) { + if (num_elements != 1) { + TS_LOGD("CTX: %s on object %s with %d children but array " + "with %u elements provided", __func__, ts_obj_name(oref), + (unsigned int)child_count, (unsigned int)num_elements); + goto ts_process_set_json_bad_request; + } + set_by_array = true; + } + else { + ret = ts_msg_pull_map_json(request, &num_elements); + if (ret == 0) { + if (num_elements != 1) { + TS_LOGD("CTX: %s on object %s with %d children but map " + "with %u elements provided", __func__, ts_obj_name(oref), + (unsigned int)child_count, (unsigned int)num_elements); + goto ts_process_set_json_bad_request; + } + set_by_map = true; + } + else { + ret = 0; + num_elements = 1; + } + } + } + else { + /* child_count > 1 - value may be given by map only */ + ret = ts_msg_pull_map_json(request, &num_elements); + if (ret != 0) { + TS_LOGD("CTX: %s on object %s with %d children but no map provided", __func__, + ts_obj_name(oref), (unsigned int)child_count); + goto ts_process_set_json_bad_request; + } + set_by_map = true; + } + + /* + * Loop through all elements to check if request is valid + * ------------------------------------------------------ + */ + TS_ASSERT(ret == 0, "CTX: %s should handle all errors before here (%d)", __func__, ret); + thingset_oref_t child_oref; + for (uint16_t count = 0;(count < num_elements) && (ret == 0); count++) { + /* Get child oref */ + if (set_by_map) { + /* Check whether object is in database */ + const char *name; + uint16_t name_len; + ret = ts_msg_pull_string_json(request, &name, &name_len); + if (ret == 0) { + /* We got the object name */ + ret = ts_obj_by_name(oref, name, name_len, &child_oref); + if (ret != 0) { + goto ts_process_set_json_not_found; + } + } + else { + ts_obj_id_t obj_id; + ret = ts_msg_pull_u16_json(request, &obj_id); + if (ret != 0) { + TS_LOGD("ThingSet: %s set by unsupported key format - object: %s", __func__, + ts_obj_name(child_oref)); + goto ts_process_set_json_unsupported_format; + } + ret = ts_obj_db_oref_by_id(oref.db_id, obj_id, &child_oref); + if (ret != 0) { + goto ts_process_set_json_not_found; + } + } + } + else if (set_by_array) { + if (count == 0) { + if (child_count == 0) { + child_oref = oref; + } + else { + ret = ts_obj_child_first(oref, &child_oref); + TS_ASSERT(ret == 0, + "ThingSet: %s calling ts_obj_child_next() fails (%d) - should never fail", + __func__, ret); + } + } + else { + ret = ts_obj_child_next(&child_oref); + TS_ASSERT(ret == 0, + "ThingSet: %s calling ts_obj_child_next() fails (%d) - should never fail", + __func__, ret); + } + } + else if (count == 0) { + /* no map, no array -> simple object */ + if (child_count == 0) { + child_oref = oref; + } + else { + ret = ts_obj_child_first(oref, &child_oref); + TS_ASSERT(ret == 0, + "ThingSet: %s calling ts_obj_child_first() fails (%d) - should never fail", + __func__, ret); + } + } + else { + /* count > 0 */ + TS_ASSERT(false, "ThingSet: %s count > 0 - should never happen for object set", + __func__); + goto ts_process_set_json_bad_request; + } + + /* Check access */ + if (!ts_obj_access_write(child_oref, ts_msg_auth(request))) { + if (ts_obj_access_write(child_oref, TS_WRITE_MASK)) { + TS_LOGD("%s: set not authorized: >0x%04x< - object: %s >0x%04x<", __func__, + (unsigned int)ts_msg_auth(request), ts_obj_name(child_oref), + (unsigned int)ts_obj_access(child_oref)); + thingset_msg_unref(request); + return ts_ctx_process_request_error(TS_MSG_PROTO_TXT, TS_MSG_CODE_UNAUTHORIZED, + response); + } + else { + TS_LOGD("%s: set forbidden: >0x%04x< - object: %s >0x%04x<", __func__, + (unsigned int)ts_msg_auth(request), ts_obj_name(child_oref), + (unsigned int)ts_obj_access(child_oref)); + thingset_msg_unref(request); + return ts_ctx_process_request_error(TS_MSG_PROTO_TXT, TS_MSG_CODE_FORBIDDEN, + response); + } + } + + /* Check object type and available buffer length vs. value provided */ + ret = ts_msg_pull_object_json(request, child_oref, false); + if (ret != 0) { + TS_LOGD("ThingSet: %s set by unsupported value format - object: %s", __func__, + ts_obj_name(child_oref)); + goto ts_process_set_json_unsupported_format; + } + } + if (set_by_map) { + ret = ts_msg_pull_map_end_json(request); + } + else if (set_by_array) { + ret = ts_msg_pull_array_end_json(request); + } + if (ret != 0) { + goto ts_process_set_json_bad_request; + } + + /* + * Actually write data + * ------------------- + */ + ts_msg_state_restore(request, &request_state); + ret = ts_msg_json_dec_setup(request); + TS_ASSERT(ret == 0, "Scratchpad init fail after message state restore"); + + if (set_by_map) { + (void)ts_msg_pull_map_json(request, &num_elements); + } + else if (set_by_array) { + (void)ts_msg_pull_array_json(request, &num_elements); + } + for (uint16_t count = 0; count < num_elements; count++) { + /* Get child oref */ + if (set_by_map) { + /* Check whether object is in database */ + const char *name; + uint16_t name_len; + ret = ts_msg_pull_string_json(request, &name, &name_len); + if (ret == 0) { + /* We got the object name */ + (void)ts_obj_by_name(oref, name, name_len, &child_oref); + } + else { + ts_obj_id_t obj_id; + (void)ts_msg_pull_u16_json(request, &obj_id); + (void)ts_obj_db_oref_by_id(oref.db_id, obj_id, &child_oref); + } + } + else if (set_by_array) { + if (count == 0) { + if (child_count == 0) { + child_oref = oref; + } + else { + (void)ts_obj_child_first(oref, &child_oref); + } + } + else { + (void)ts_obj_child_next(&child_oref); + } + } + else { + if (child_count == 0) { + child_oref = oref; + } + else { + (void)ts_obj_child_first(oref, &child_oref); + } + } + + (void)ts_msg_pull_object_json(request, child_oref, true); + } + +ts_process_set_json_end: + thingset_msg_unref(request); + return ts_ctx_process_request_error(TS_MSG_PROTO_TXT, TS_MSG_CODE_CHANGED, response); + +ts_process_set_json_bad_request: + thingset_msg_unref(request); + return ts_ctx_process_request_error(TS_MSG_PROTO_TXT, TS_MSG_CODE_BAD_REQUEST, response); + +ts_process_set_json_not_found: + thingset_msg_unref(request); + return ts_ctx_process_request_error(TS_MSG_PROTO_TXT, TS_MSG_CODE_NOT_FOUND, response); + +ts_process_set_json_unsupported_format: + thingset_msg_unref(request); + return ts_ctx_process_request_error(TS_MSG_PROTO_TXT, TS_MSG_CODE_UNSUPPORTED_FORMAT, response); +} From c1853c1c2184ad4822894bda399e95fb8fb306a2 Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Sun, 19 Dec 2021 08:51:01 +0100 Subject: [PATCH 21/37] COM prepare - add ThingSet application communication interface Prepare for ThingSet Communication Context: - Add application interface to communication context - Add application port implementation that connects the application to the communication context. Signed-off-by: Bobby Noelte --- src/thingset_app.h | 247 +++++++++++++++++++++++++++++++++++++++++++++ src/ts_app.c | 73 ++++++++++++++ src/ts_app.h | 63 ++++++++++++ 3 files changed, 383 insertions(+) create mode 100644 src/thingset_app.h create mode 100644 src/ts_app.c create mode 100644 src/ts_app.h diff --git a/src/thingset_app.h b/src/thingset_app.h new file mode 100644 index 0000000..c2cd9db --- /dev/null +++ b/src/thingset_app.h @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet application (public interface) + */ + +#ifndef THINGSET_APP_H_ +#define THINGSET_APP_H_ + +/** + * @brief ThingSet application. + * + * @note All structure definitions and functions that start with the prefix 'ts_' are not part of + * the public API and are just here for technical reasons. They should not be used in + * applications. + * + * @defgroup ts_app_api_pub ThingSet application (public interface) + * @{ + */ + +#include "thingset_env.h" + +#include "thingset_time.h" +#include "thingset_msg.h" +#include "thingset_obj.h" +#include "thingset_port.h" +#include "thingset_ctx.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @brief ThingSet application ID. + * + * The application ID is the same as the ID of the port that connects the application to a local + * context. + */ +typedef thingset_portid_t thingset_appid_t; + +/** + * @brief ThingSet application. + */ +struct thingset_app { + /** @brief Pointer to application instance immutable data */ + const void *config; + + /** @brief Pointer to application instance mutable data */ + void *data; + + /** + * @brief Initialise application. + * + * @param[in] app Pointer to application. + * @return 0 on success, <0 otherwise. + */ + int (*init)(const struct thingset_app *app); + + /** + * @brief Run application. + * + * @param[in] app Pointer to application. + * @return 0 on success, <0 otherwise. + */ + int (*run)(const struct thingset_app *app); +}; + +/** + * @brief Signature of a ThingSet application initialisation function. + * + * @param[in] app Pointer to application. + * @return 0 on success, <0 otherwise. + */ +typedef int (*thingset_app_init_fn_t)(const struct thingset_app *app); + +/** + * @brief Signature of a ThingSet application run function. + * + * @param[in] app_port Port. + * @return 0 on success, <0 otherwise. + */ +typedef int (*thingset_app_run_fn_t)(const struct thingset_app *app); + +/** + * @} + * @addtogroup ts_app_api_priv + * @{ + */ + +/** + * @brief Mutable data structure of application port. + */ +struct ts_app_port_data { + /** + * @brief Port receive buffer queue. + * + * The queue the messages that are send to the application. + */ + struct ts_impl_bufq rx_bufq; +}; + +/** + * @} + * @addtogroup ts_app_api_pub + * @{ + */ + +/** + * @def THINGSET_APP_DEFINE + * + * @brief Define a ThingSet application instance. + * + * A ThingSet application is identified by it's application ID: + * + * - @p app_id : The application identifier is the identifier of the ThingSet port that connects + * the application to a local context. Therefor application port id and application id + * are just two different ways to refer to the same identifier. + * + * - @p app_name : The name of the application must be unique. + * + * A ThingSet application has to provide two functions that connect it to the ThingSet context. + * + * - @p app_init : The initialisation function called to initialise the application. The signature + * of the init function has to be of @ref thingset_app_init_fn_t. + * - @p app_run : The run function called to run the application. The signature + * of the run function has to be of @ref thingset_app_run_fn_t. + * + * A ThingSet application may have it's private data: + * + * - @p app_config : immutable data of the application + * - @p app_data : mutable data of the application + * + * This macro also initialises a ThingSet application port. That is the ThingSet communication port + * that this application uses to communicate. The port - defined by @p app_id - links the + * application to the local context given by @p app_loc_id. + * + * @param[in] app_id Application (port) identifier. Paramter must expand to a positiv number. + * Limit defined by @ref TS_CONFIG_PORT_COUNT. + * @param[in] app_name Name of the application. + * @param[in] app_loc_id Identifier of ThingSet local context the application is attached to. + * @param[in] app_init Init function to be called by the ThingSet context to ininitialize this + * application. + * @param[in] app_run Run function to be called by the ThingSet context to run this application. + * @param[in] app_config Pointer to configuration data of this application. + * @param[in] app_data Pointer to the mutable data of this application. + */ +#define THINGSET_APP_DEFINE(app_id, app_name, app_loc_id, app_init, app_run, app_config, app_data) \ + extern const struct ts_port_api ts_app_port_api; \ + struct ts_app_port_data TS_CAT(ts_port_data_, app_id); \ + const struct thingset_app TS_CAT(ts_port_config_, app_id) = { \ + .config = &TS_EXPAND(app_config), \ + .data = &TS_EXPAND(app_data), \ + .init = &app_init, \ + .run = &app_run, \ + }; \ + const struct thingset_port TS_CAT(ts_port_, app_id) = { \ + .loc_id = app_loc_id, \ + .name = app_name, \ + .config = &TS_CAT(ts_port_config_, app_id), \ + .api = &ts_app_port_api, \ + .data = &TS_CAT(ts_port_data_, app_id) } + +/** + * @brief Get name of application instance. + * + * @param[in] app_id Application (port) identifier. + * @return name of application instance. + */ +const char *thingset_app_name(thingset_appid_t app_id); + +/** + * @brief Get ThingSet context the application instance is attached to. + * + * @param[in] app_id Application (port) identifier. + * @return ThingSet local context identifier. + */ +thingset_locid_t thingset_app_ctx(thingset_appid_t app_id); + +/** + * @brief Allocate remote object entry within remote objects database. + * + * The remote object is allocated in the remote objects database of the context the application + * is linked to. + * + * @param[in] app_id Application (port) identifier. + * @param[in] ctx_uid Universal id of the context the remote object lives in. + * @param[in] obj_id Id of the remote object. + * @param[in] obj_path Path of the remote object. + * @param[in] timeout_ms Maximum time to wait in milliseconds. + * @param[out] oref Pointer to object reference for the remote object. + * @return 0 on success, <0 otherwise. + */ +int thingset_obj_alloc(thingset_appid_t app_id, thingset_uid_t *ctx_uid, + ts_obj_id_t obj_id, const char *obj_path, thingset_time_ms_t timeout_ms, + thingset_oref_t *oref); + +int thingset_obj_free(thingset_appid_t app_id, thingset_oref_t oref); + +int thingset_publish(thingset_appid_t app_id, thingset_oref_t oref, thingset_time_ms_t period_ms); + +int thingset_connect(thingset_appid_t app_id, thingset_oref_t oref, thingset_time_ms_t timeout_ms); + +int thingset_subscribe(thingset_appid_t app_id, thingset_oref_t oref); + +int thingset_change(thingset_appid_t app_id, thingset_oref_t oref, thingset_time_ms_t timeout_ms); + +int thingset_fetch(thingset_appid_t app_id, thingset_oref_t oref, uint16_t subsets, + thingset_time_ms_t timeout_ms); + +int thingset_exec(thingset_appid_t app_id, thingset_oref_t oref, thingset_time_ms_t timeout_ms); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +/** + * @} + */ + +/** @page ts_topic_app Applications + +A ThingSet application is defined by two functions and up to two data sets an an application name. +The application functions are called by the ThingSet context. One function is for application +initialisation, the other one is to run the application. + +A ThingSet application is identified by it's name: + + name : The name of the application must be unique. + +A ThingSet application has to provide two functions that connect it to the ThingSet context. + + init : int my_app_init(const struct ts_port *app_port) + run : int my_app_run(const struct ts_port *app_port) + +A ThingSet application may have it's private data: + + config : immutable data of the application + data : mutable data of the application + +*/ + +#endif /* THINGSET_APP_H_ */ diff --git a/src/ts_app.c b/src/ts_app.c new file mode 100644 index 0000000..25d3e90 --- /dev/null +++ b/src/ts_app.c @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet application + */ + +#include "ts_bufq.h" +#include "ts_port.h" +#include "ts_ctx.h" +#include "ts_app.h" + + +int ts_app_port_init(const struct thingset_port *port) +{ + const struct thingset_app *app = (const struct thingset_app *)port->config; + struct ts_app_port_data *data = (struct ts_app_port_data *)port->data; + + int ret = ts_bufq_init(&data->rx_bufq); + if (ret != 0) { + return ret; + } + + ret = app->init(app); + return ret; +} + +int ts_app_port_run(const struct thingset_port *port) +{ + const struct thingset_app *app = (const struct thingset_app *)port->config; + + return app->run(app); +} + +int ts_app_port_transmit(const struct thingset_port *port, struct thingset_msg *msg, + thingset_time_ms_t timeout_ms) +{ + struct ts_app_port_data *data = (struct ts_app_port_data *)port->data; + + return ts_bufq_put(&data->rx_bufq, (struct ts_buf *)msg); +} + +THINGSET_PORT_TYPE(ts_app_port, ts_app_port_init, ts_app_port_run, ts_app_port_transmit); + +int ts_app_request(thingset_appid_t app_id, struct thingset_msg *request, + thingset_time_ms_t timeout_ms, struct thingset_msg **response) +{ + ts_msg_proc_set_port_src(request, app_id); + + /* Let context process the request generated by the application */ + int ret = thingset_process(thingset_app_ctx(app_id), request); + if (ret != 0) { + return ret; + } + + /* Get response of request */ + THINGSET_PORT_DATA_STRUCT(ts_app_port) *port_data = THINGSET_PORT_DATA(ts_app_port, + ts_port_by_id(app_id)); + return ts_bufq_get(&port_data->rx_bufq, timeout_ms, (struct ts_buf **)response); +} + +const char *thingset_app_name(thingset_appid_t app_id) +{ + return ts_port_by_id(app_id)->name; +}; + +thingset_locid_t thingset_app_ctx(thingset_appid_t app_id) +{ + return ts_port_by_id(app_id)->loc_id; +} diff --git a/src/ts_app.h b/src/ts_app.h new file mode 100644 index 0000000..38ee561 --- /dev/null +++ b/src/ts_app.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet application (private interface) + */ + +#ifndef TS_APP_H_ +#define TS_APP_H_ + +/** + * @brief ThingSet application. + * + * @defgroup ts_app_api_priv ThingSet application (private interface) + * @{ + */ + +#include "thingset_app.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Application port API. + */ +extern const struct ts_port_api ts_app_port_api; + +int ts_app_port_init(const struct thingset_port *port); +int ts_app_port_run(const struct thingset_port *port); +int ts_app_port_transmit(const struct thingset_port *port, struct thingset_msg *msg, + thingset_time_ms_t timeout_ms); + +/** + * @brief Issue request message at application port and get response. + * + * The function consumes the request message buffer (decreases the reference count). + * + * @note This is a low level functions - if possible, use instead one of thingset_publish(), + * thingset_connect(), thingset_subscribe(), thingset_change(), thingset_fetch(), + * thingset_exec(). + * + * @param[in] tsc Pointer to ThingSet data and communication context. + * @param[in] request Pointer to request message buffer to be send. + * @param[in] timeout_ms maximum time to wait for response in milliseconds. + * @param[out] response Pointer to return response message buffer to. + * @returns 0 on success, <0 otherwise. + */ +int ts_app_request(thingset_appid_t app_id, struct thingset_msg *request, + thingset_time_ms_t timeout_ms, struct thingset_msg **response); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +/** + * @} + */ + +#endif /* TS_APP_H_ */ From b860f1290405d6df321c5d5bc45dd7940b5de452 Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Sun, 19 Dec 2021 08:55:55 +0100 Subject: [PATCH 22/37] COM prepare - add ThingSet C++ core context interface Prepare for ThingSet Communication Context: - Add a C++ interface to the core context functionality. Signed-off-by: Bobby Noelte --- src/thingset_cpp.h | 192 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 src/thingset_cpp.h diff --git a/src/thingset_cpp.h b/src/thingset_cpp.h new file mode 100644 index 0000000..6b016d0 --- /dev/null +++ b/src/thingset_cpp.h @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2017 Martin Jäger / Libre Solar + * Copyright (c) 2021..2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet local context (public C++ interface) + */ + +#ifndef THINGSET_CPP_H_ +#define THINGSET_CPP_H_ + +#include "thingset_env.h" +#include "thingset_msg.h" +#include "thingset_obj.h" +#include "thingset_ctx.h" + +#ifdef __cplusplus + +extern "C" { + +/* Provide C++ naming for C constructs. */ +typedef ts_obj_id_t ThingSetObjectId; +typedef struct ts_bytes_buffer ThingSetBytesBuffer; +typedef struct ts_array_info ThingSetArrayInfo; +typedef const struct ts_obj ThingSetObject; + +#if TS_CONFIG_CPP_LEGACY +/* compatibility to legacy CPP interface */ +typedef ThingSetBytesBuffer TsBytesBuffer __attribute__((deprecated)); +typedef ThingSetArrayInfo ArrayInfo __attribute__((deprecated)); +typedef ThingSetObject DataNode __attribute__((deprecated)); +#endif + +} /* extern 'C' */ + +/** + * @brief Main ThingSet class. + * + * Class ThingSet is a C++ shim for the C implementation of ThingSet. + * See the respective C functions for a detailed description. + * + * Class ThingSet is a singleton and works on the single core variant ThingSet local context. The + * context has to be defined by @ref THINGSET_CORE_DEFINE(). The associated data objects database + * has to be defined by @ref THINGSET_CORE_DATABASE_DEFINE(). + */ +class ThingSet +{ +public: + + inline ThingSet(void) + { + (void)thingset_core_init(); + }; + + inline int process(uint8_t *request, size_t req_len, uint8_t *response, size_t resp_size) + { + return thingset_core_process(request, req_len, response, resp_size); + }; + + inline void dump_json(ts_obj_id_t obj_id = TS_ID_ROOT, int level = 0) + { + thingset_dump_json(obj_id, level); + }; + + inline void set_authorisation(uint16_t flags) + { + thingset_authorisation_set(TS_CONFIG_CORE_LOCID, flags); + }; + + inline int txt_export(char *buf, size_t size, const uint16_t subsets) + { + return thingset_txt_export(buf, size, subsets); + }; + + inline int txt_statement(char *buf, size_t size, ThingSetObject *object) + { + return thingset_txt_statement(buf, size, object); + }; + + inline int txt_statement(char *buf, size_t size, const char *path) + { + return thingset_txt_statement_by_path(buf, size, path); + }; + + inline int txt_statement(char *buf, size_t size, ThingSetObjectId id) + { + return thingset_txt_statement_by_id(buf, size, id); + }; + + inline int bin_export(uint8_t *buf, size_t size, const uint16_t subsets) + { + return thingset_bin_export(buf, size, subsets); + }; + + inline int bin_import(uint8_t *buf, size_t size, uint16_t auth_flags, const uint16_t subsets) + { + return thingset_bin_import(buf, size, auth_flags, subsets); + }; + + inline int bin_statement(uint8_t *buf, size_t size, ThingSetObject *object) + { + return thingset_bin_statement(buf, size, object); + }; + + inline int bin_statement(uint8_t *buf, size_t size, const char *path) + { + return thingset_bin_statement_by_path(buf, size, path); + }; + + inline int bin_statement(uint8_t *buf, size_t size, ThingSetObjectId id) + { + return thingset_bin_statement_by_id(buf, size, id); + }; + + inline ThingSetObject *get_object(ThingSetObjectId id) + { + return thingset_object_by_id(id); + }; + + inline ThingSetObject *get_object(const char *name, size_t len, int32_t parent = -1) + { + return thingset_object_by_name(name, len, parent); + }; + + inline ThingSetObject *get_endpoint(const char *path, size_t len) + { + return thingset_object_by_path(path, len); + }; + + /* + * Deprecated functions from ThingSet v0.3 interface + */ + + /** + * Generate statement (previously known as publication message) in JSON format. + * + * @param buf Pointer to the buffer where the publication message should be stored + * @param buf_size Size of the message buffer, i.e. maximum allowed length of the message + * @param subset Flag to select which subset of data items should be published + * + * @returns Actual length of the message written to the buffer or 0 in case of error + */ + inline int txt_pub(char *buf, size_t size, const uint16_t subset) + __attribute__((deprecated)) + { + buf[0] = '#'; + buf[1] = ' '; + int ret = thingset_txt_export(&buf[2], size - 2, subset); + return (ret > 0) ? 2 + ret : 0; + }; + + /** + * Generate statement (previously known as publication message) in CBOR format. + * + * @param buf Pointer to the buffer where the publication message should be stored + * @param buf_size Size of the message buffer, i.e. maximum allowed length of the message + * @param subset Flag to select which subset of data items should be published + * + * @returns Actual length of the message written to the buffer or 0 in case of error + */ + inline int bin_pub(uint8_t *buf, size_t size, const uint16_t subset) + __attribute__((deprecated)) + { + buf[0] = TS_PUBMSG; + int ret = thingset_bin_export(&buf[1], size - 1, subset); + return (ret > 0) ? 1 + ret : 0; + }; + + /** + * Update data objects based on values provided by from other pub msg. + * + * @param buf Buffer containing pub message and data that should be written to the data objects + * @param len Length of the data in the buffer + * @param auth_flags Authentication flags to be used in this function (to override _auth_flags) + * @param subset Subscribe channel (as bitfield) + * + * @returns ThingSet status code + */ + inline int bin_sub(uint8_t *cbor_data, size_t len, uint16_t auth_flags, uint16_t subsets) + __attribute__((deprecated)) + { + return thingset_bin_import(cbor_data + 1, len - 1, auth_flags, subsets); + }; + +}; + +#endif /* __cplusplus */ + +#endif /* THINGSET_CPP_H_ */ From 63b230eb87f4b3ca88f4dc8b61a7abf348b65968 Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Sun, 19 Dec 2021 10:09:06 +0100 Subject: [PATCH 23/37] COM prepare - add simple loopback port Prepare for ThingSet Communication Context: - add simple loopback port that utilizes the new communication context port interface. A very basic loopback implementation that lacks a lot of features, e.g. thread safety. Use with most care. Signed-off-by: Bobby Noelte --- src/ports/loopback_simple/CMakeLists.txt | 9 ++++ src/ports/loopback_simple/loopback_simple.c | 48 +++++++++++++++++++++ src/ports/loopback_simple/loopback_simple.h | 25 +++++++++++ 3 files changed, 82 insertions(+) create mode 100644 src/ports/loopback_simple/CMakeLists.txt create mode 100644 src/ports/loopback_simple/loopback_simple.c create mode 100644 src/ports/loopback_simple/loopback_simple.h diff --git a/src/ports/loopback_simple/CMakeLists.txt b/src/ports/loopback_simple/CMakeLists.txt new file mode 100644 index 0000000..a69a5ba --- /dev/null +++ b/src/ports/loopback_simple/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2021 Martin Jäger / Libre Solar +# Copyright (c) 2021 Bobby Noelte. +# +# SPDX-License-Identifier: Apache-2.0 + +if(DEFINED CONFIG_THINGSET_PORT_LOOPBACK_SIMPLE) +target_sources(ts PRIVATE loopback_simple.c) +message(STATUS "ThingSet: Added Simple Loopback Port") +endif() diff --git a/src/ports/loopback_simple/loopback_simple.c b/src/ports/loopback_simple/loopback_simple.c new file mode 100644 index 0000000..4ef1c8f --- /dev/null +++ b/src/ports/loopback_simple/loopback_simple.c @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "../../ts_ctx.h" +#include "../../ts_msg.h" + +#include "loopback_simple.h" + +int loopback_simple_init(const struct thingset_port *port) +{ + return 0; +} + +int loopback_simple_run(const struct thingset_port *port) +{ + return 0; +} + +int loopback_simple_transmit(const struct thingset_port *port, struct thingset_msg *msg, + thingset_time_ms_t timeout_ms) +{ + /* Behave as the other end of the loopback - we fake the receive. */ + + /* Pretend to act as a "normal" port that allocates the buffers by itself to better resemble + * this behaviour for e.g. testing. */ + struct thingset_msg *other_msg; + int ret = thingset_msg_clone(msg, 0, &other_msg); + thingset_msg_unref(msg); + if (ret != 0) { + return ret; + } + + ret = ts_msg_proc_setup(other_msg); + if (ret != 0) { + return ret; + } + TS_MSG_PROC_SCRATCHPAD_PTR_INIT(scratchpad, other_msg); + scratchpad->port_src = THINGSET_PORT_CONFIG(loopback_simple, port)->other_port_id; + scratchpad->port_dest = THINGSET_PORT_ID_INVALID; + + ret = thingset_process(ts_port_by_id(scratchpad->port_src)->loc_id, other_msg); + return ret; +} + +THINGSET_PORT_TYPE(loopback_simple, loopback_simple_init, loopback_simple_run, + loopback_simple_transmit); diff --git a/src/ports/loopback_simple/loopback_simple.h b/src/ports/loopback_simple/loopback_simple.h new file mode 100644 index 0000000..26dd6a4 --- /dev/null +++ b/src/ports/loopback_simple/loopback_simple.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + * + * Simple ThingSet loopback communication ports. + * + * A very basic loopback implementation that lacks a lot of features, e.g. thread safety. + * Use with most care. + * + * Application programming interface. + */ + +#ifndef LOOPBACK_SIMPLE_H_ +#define LOOPBACK_SIMPLE_H_ + +#include "../../thingset_ctx.h" + +THINGSET_PORT_CONFIG_STRUCT(loopback_simple) { + thingset_portid_t other_port_id; +}; + +THINGSET_PORT_DATA_STRUCT(loopback_simple) { +}; + +#endif /* LOOPBACK_SIMPLE_H_ */ From 194eb107d1553b5ceee4fc86e61c8a81ad867b66 Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Wed, 26 Jan 2022 07:54:39 +0100 Subject: [PATCH 24/37] COM prepare - add shell app Prepare for ThingSet Communication Context: - add shell application that uses the new communication context app interface. - add build instructions for Native implementation - add build instructions for Zephyr implementation Signed-off-by: Bobby Noelte --- apps/shell/ts_shell.c | 375 +++++++++++++++++++++++++++++++ apps/shell/ts_shell.h | 271 ++++++++++++++++++++++ apps/shell/ts_shell_abuf.c | 59 +++++ apps/shell/ts_shell_abuf.h | 53 +++++ apps/shell/ts_shell_g.c | 144 ++++++++++++ apps/shell/ts_shell_g.h | 44 ++++ native/apps/shell/CMakeLists.txt | 7 + zephyr/apps/shell/CMakeLists.txt | 8 + zephyr/apps/shell/Kconfig | 34 +++ 9 files changed, 995 insertions(+) create mode 100644 apps/shell/ts_shell.c create mode 100644 apps/shell/ts_shell.h create mode 100644 apps/shell/ts_shell_abuf.c create mode 100644 apps/shell/ts_shell_abuf.h create mode 100644 apps/shell/ts_shell_g.c create mode 100644 apps/shell/ts_shell_g.h create mode 100644 native/apps/shell/CMakeLists.txt create mode 100644 zephyr/apps/shell/CMakeLists.txt create mode 100644 zephyr/apps/shell/Kconfig diff --git a/apps/shell/ts_shell.c b/apps/shell/ts_shell.c new file mode 100644 index 0000000..06fd650 --- /dev/null +++ b/apps/shell/ts_shell.c @@ -0,0 +1,375 @@ +/* + * Copyright (c) 2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet shell app + */ + +#include + +#include "../../src/thingset.h" + +#include "../../src/ts_mem.h" +#include "../../src/ts_obj.h" +#include "../../src/ts_port.h" +#include "../../src/ts_msg.h" +#include "../../src/ts_ctx.h" +#include "../../src/ts_app.h" + +#include "ts_shell.h" + +/* Shell application */ + +const struct ts_shell_config ts_shell_config; + +struct ts_shell_data ts_shell_data; + +#if TS_CONFIG_SHELL_MEM_SIZE > 0 +TS_MEM_DEFINE(ts_shell_mem, TS_CONFIG_SHELL_MEM_SIZE); +#endif + +THINGSET_APP_DEFINE(TS_CONFIG_SHELL_PORTID, TS_CONFIG_SHELL_NAME, TS_CONFIG_SHELL_LOCID, + ts_shell_init, ts_shell_run, ts_shell_config, ts_shell_data); + +int ts_shell_init(const struct thingset_app *app) +{ + /* Ignore app - shell is a singleton that may be called with app == NULL */ + app = &TS_CAT(ts_port_config_, TS_CONFIG_SHELL_PORTID); + + /* By default we route to the local context the shell is attached to */ + ts_shell_data.port_id_dst = THINGSET_PORT_ID_INVALID; + ts_shell_data.response_size_hint = 500; + + return ts_impl_shell_init(app); +} + +/* Shell commands */ + +const char *ts_shell_help = "ts [node | obj | port | txt]"; +const char *ts_shell_help_node = "ts node [list | ...]"; +const char *ts_shell_help_obj = "ts obj [dump | ...]"; +const char *ts_shell_help_port = "ts port [list | ...]"; +const char *ts_shell_help_txt = "ts txt "; + +int ts_shell_cmd_txt(const struct ts_shell *shell, size_t argc, char **argv) +{ + if (argc <= 1) { + goto ts_shell_cmd_txt_help; + } + + int ret; + struct thingset_msg *request; + struct thingset_msg *response; + uint16_t msg_size = strlen(argv[1]); + + for (int i = 2; i < argc; i++) { + msg_size += 1 + strlen(argv[i]); + } + + ret = thingset_msg_alloc_json(msg_size, 10, &request); + + for (int i = 1; (i < argc) && (ret == 0); i++) { + if (i == 1) { + ret = ts_msg_json_enc_setup(request); + } + else { + ret = ts_msg_add_u8(request, ' '); + } + if (ret != 0) { + break; + } + ret = ts_msg_add_mem(request, (const uint8_t *)argv[i], strlen(argv[i])); + } + if (ret != 0) { + return ret; + } + + ret = ts_msg_proc_setup(request); + if (ret != 0) { + return ret; + } + ts_msg_proc_set_port_dest(request, ts_shell_data.port_id_dst); + ts_msg_proc_set_resp_size(request, ts_shell_data.response_size_hint); + + ret = ts_app_request(TS_CONFIG_SHELL_PORTID, request, 10, &response); + if (ret != 0) { + return ret; + } + + ts_shell_print(shell, "%.*s", (int)ts_msg_len(response), (const char *)ts_msg_data(response)); + ret = thingset_msg_unref(response); + + return ret; + +ts_shell_cmd_txt_help: + ts_shell_print(shell, "%s", ts_shell_help_txt); + return TS_SHELL_CMD_HELP_PRINTED; +} + +int ts_shell_cmd_node(const struct ts_shell *shell, size_t argc, char **argv) +{ + int ret = 0; + + if (TS_CTX_IS_CORE(TS_CONFIG_SHELL_LOCID)) { + ts_shell_print(shell, "not supported by core context"); + ret = -ENOTSUP; + } + else if (argc <= 1) { + goto ts_shell_cmd_node_help; + } + else if (strcmp("list", argv[1]) == 0) { + struct ts_ctx_com_data *data = ts_ctx_com_data(TS_CONFIG_SHELL_LOCID); + + ts_shell_print(shell, "Nodes table (context 0x%" PRIx64 "):", + *ts_ctx_uid(TS_CONFIG_SHELL_LOCID)); + for (uint16_t i = 0; i < TS_ARRAY_SIZE(data->node_table.nodes); i++) { + struct ts_ctx_node *node = &data->node_table.nodes[i]; + if (node->port_id == THINGSET_PORT_ID_INVALID) { + /* empty */ + ts_shell_print(shell, "%u: ", (unsigned int)i); + } + else { + const char *port_name = thingset_port_name(node->port_id); + const char *response_port_name; + if (node->response_port_id == THINGSET_PORT_ID_INVALID) { + response_port_name = ""; + } + else { + response_port_name = thingset_port_name(node->response_port_id); + } + ts_shell_print(shell, "%u: 0x%" PRIx64 + " at port \"%s\" responding to port \"%s\" seen last at %u", + (unsigned int)i, node->ctx_uid, port_name, response_port_name, + node->last_seen_time); + } + } + } + else { + goto ts_shell_cmd_node_help; + } + + return ret; + +ts_shell_cmd_node_help: + ts_shell_print(shell, "%s", ts_shell_help_node); + return TS_SHELL_CMD_HELP_PRINTED; +} + +int ts_shell_cmd_port(const struct ts_shell *shell, size_t argc, char **argv) +{ + int ret = 0; + + if (TS_CTX_IS_CORE(TS_CONFIG_SHELL_LOCID)) { + ts_shell_print(shell, "not supported by core context"); + ret = -ENOTSUP; + } + else if (argc <= 1) { + goto ts_shell_cmd_port_help; + } + else if (strcmp("list", argv[1]) == 0) { + ts_shell_print(shell, "Ports table (device):"); + for (thingset_portid_t port_id = 0; port_id < TS_ARRAY_SIZE(ts_ports); port_id++) { + const char *name = thingset_port_name(port_id); + if (name != NULL) { + thingset_uid_t ctx_uid = *ts_ctx_uid(ts_port_by_id(port_id)->loc_id); + ts_shell_print(shell, "%u: \"%s\" at context 0x%" PRIx64, (unsigned int)port_id, name, + ctx_uid); + } + } + } + else { + goto ts_shell_cmd_port_help; + } + + return ret; + +ts_shell_cmd_port_help: + ts_shell_print(shell, "%s", ts_shell_help_port); + return TS_SHELL_CMD_HELP_PRINTED; +} + +int ts_shell_cmd_obj(const struct ts_shell *shell, size_t argc, char **argv) +{ + int ret = 0; + + if (argc <= 1) { + goto ts_shell_cmd_obj_help; + } + else if (strcmp("dump", argv[1]) == 0) { + char dump_buffer[20000]; + thingset_obj_log(ts_obj_db_oref_root(ts_ctx_obj_db(TS_CONFIG_SHELL_LOCID)), + &dump_buffer[0], sizeof(dump_buffer)); + ts_shell_print(shell, "%s", dump_buffer); + } + else { + goto ts_shell_cmd_obj_help; + } + + return ret; + +ts_shell_cmd_obj_help: + ts_shell_print(shell, "%s", ts_shell_help_obj); + return TS_SHELL_CMD_HELP_PRINTED; +} + +int ts_shell_cmd_ts(const struct ts_shell *shell, size_t argc, char **argv) +{ + int ret; + + if (argc < 1) { + goto ts_shell_cmd_ts_help; + } + else if (strcmp("node", argv[0]) == 0) { + ret = ts_shell_cmd_node(shell, argc, &argv[0]); + } + else if (strcmp("obj", argv[0]) == 0) { + ret = ts_shell_cmd_obj(shell, argc, &argv[0]); + } + else if (strcmp("port", argv[0]) == 0) { + ret = ts_shell_cmd_port(shell, argc, &argv[0]); + } + else if (strcmp("txt", argv[0]) == 0) { + ret = ts_shell_cmd_txt(shell, argc, &argv[0]); + } + else { + goto ts_shell_cmd_ts_help; + } + + return ret; + +ts_shell_cmd_ts_help: + ts_shell_print(shell, "%s", ts_shell_help); + return TS_SHELL_CMD_HELP_PRINTED; +} + +int ts_shell_cmd(const struct ts_shell *shell, size_t argc, char **argv) +{ + int ret; + + if (argc < 1) { + goto ts_shell_cmd_help; + } + else if (strcmp("ts", argv[0]) == 0) { + ret = ts_shell_cmd_ts(shell, argc - 1, &argv[1]); + } + else { + goto ts_shell_cmd_help; + } + + return ret; + +ts_shell_cmd_help: + ts_shell_print(shell, "%s", ts_shell_help); + return TS_SHELL_CMD_HELP_PRINTED; +} + +int ts_shell_split_cmd(char *cmd, int *argc, char ***argv) +{ + /* Find start of first command argument */ + char *start = cmd; + while (isspace(*start) != 0) { + *start = 0; + start++; + } + + /* Count occurance of delim in string */ + int count = 0; + char *end = start; + while (*end != 0) { + /* Skip over non white space */ + while ((*end != 0) && (isspace(*end) == 0)) { + /* Skip over escaped char */ + if (*end == '\\') { + end += 2; + } + /* Skip over (") marked string */ + else if (*end == '"') { + end++; + while ((*end != 0) && (*end != '"')) { + /* Skip escaped \" */ + if (*end == '\\') { + end += 2; + } + else { + end++; + } + } + } + /* Skip over (') marked string */ + else if (*end == '\'') { + end++; + while ((*end != 0) && (*end != '\'')) { + /* Skip escaped \' */ + if (*end == '\\') { + end += 2; + } + else { + end++; + } + } + } + /* Skip over normal char */ + else { + end++; + } + } + + /* + * Null terminate the string at delimiter + * and skip past our new null and any following delimiters. + */ + while ((*end != 0) && (isspace(*end) != 0)) { + *end = 0; + end++; + } + count++; + } + + /* Allocate dynamic array */ + char **arguments; + int ret = ts_shell_alloc(count * sizeof(char *), 100, (uint8_t **)&arguments); + if (ret != 0) { + return ret; + } + + /* Fill command argument vector */ + for (int i = 0; i < count; i++) { + /* Copy start of string */ + TS_LOGD("SHELL: %s created command argument %d = \"%s\"", __func__, i, start); + arguments[i] = start; + /* Forward to next delimiter */ + while ((start < end) && (*start != 0)) { + start++; + } + /* skip delimiter */ + while ((start < end) && (*start == 0)) { + start++; + } + } + + *argv = arguments; + *argc = count; + + return 0; +} + +int ts_shell_alloc(size_t block_size, thingset_time_ms_t timeout_ms, uint8_t **mem_block) +{ +#if TS_CONFIG_SHELL_MEM_SIZE == 0 + return -ENOTSUP; +#else + return ts_mem_alloc(&ts_shell_mem, block_size, timeout_ms, (void **)mem_block); +#endif +} + +int ts_shell_free(const uint8_t *mem_block) +{ +#if TS_CONFIG_SHELL_MEM_SIZE == 0 + return -ENOTSUP; +#else + return ts_mem_free(&ts_shell_mem, mem_block); +#endif +} diff --git a/apps/shell/ts_shell.h b/apps/shell/ts_shell.h new file mode 100644 index 0000000..a985acf --- /dev/null +++ b/apps/shell/ts_shell.h @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet shell app (private interface) + */ + +#ifndef TS_SHELL_H_ +#define TS_SHELL_H_ + +/** + * @brief ThingSet shell app. + * + * @defgroup ts_shell_api_priv ThingSet message (private interface) + * @{ + */ + +#include "../../src/thingset_env.h" +#include "../../src/thingset_time.h" +#include "../../src/thingset_port.h" +#include "../../src/thingset_app.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @} + * @addtogroup ts_impl_api + * @{ + */ + +#if TS_DOXYGEN + +/** + * @brief Implementation for @ref TS_SHELL_CMD_HELP_PRINTED. + */ +#define TS_IMPL_SHELL_CMD_HELP_PRINTED + +/** + * @brief Implementation for @ref ts_shell. + */ +#define ts_impl_shell + +/** + * @brief Implementation for @ref ts_shell_print. + */ +#define ts_impl_shell_print(shell, format, ...) + +/** + * @brief Implementation for @ref ts_shell_error. + */ +#define ts_impl_shell_error(shell, format, ...) + +#endif /* TS_DOXYGEN */ + +/** + * @brief Initialise specific shell implementation. + * + * Called by @ref ts_shell_init after basic shell initialisation. + */ +int ts_impl_shell_init(const struct thingset_app *app); + +/** + * @brief Implementation for @ref ts_shell_run. + */ +int ts_impl_shell_run(const struct thingset_app *app); + +/** + * @brief Implementation for @ref ts_shell_get_line. + */ +int ts_impl_shell_get_line(const struct ts_impl_shell *shell, const char *prompt, char **line); + +/** + * @brief Implementation for @ref ts_shell_execute_cmd. + */ +int ts_impl_shell_execute_cmd(const char* cmd); + +/** + * @brief Implementation for @ref ts_shell_execute_output. + */ +int ts_impl_shell_execute_output(size_t *sizep, const char **output); + +/** + * @brief Implementation for @ref ts_shell_join. + */ +int ts_impl_shell_join(void); + +/** + * @} + * @addtogroup ts_shell_api_priv + * @{ + */ + +struct ts_shell_config { +}; + +struct ts_shell_data { + /** @brief Destination port for messages created by the shell. */ + thingset_portid_t port_id_dst; + /** @brief Size hint for response message to the shell. */ + uint16_t response_size_hint; +}; + +extern const struct ts_shell_config ts_shell_config; +extern struct ts_shell_data ts_shell_data;; + +/** + * @def ts_shell + * + * Shell structure. + */ +#define ts_shell ts_impl_shell + +/** + * @def TS_SHELL_CMD_HELP_PRINTED + * + * Return value of command to indicate help text was printed. + */ +#define TS_SHELL_CMD_HELP_PRINTED TS_IMPL_SHELL_CMD_HELP_PRINTED + +/** + * @brief Print normal message to the shell. + * + * @param[in] shell Pointer to the shell instance. + * @param[in] format Format string. + * @param[in] ... List of parameters to print. + */ +#define ts_shell_print(shell, format, ...) ts_impl_shell_print(shell, format, ##__VA_ARGS__) + +/** + * @brief Print error message to the shell. + * + * @param[in] shell Pointer to the shell instance. + * @param[in] format Format string. + * @param[in] ... List of parameters to print. + */ +#define ts_shell_error(shell, format, ...) ts_impl_shell_error(shell, format, ##__VA_ARGS__) + +int ts_shell_init(const struct thingset_app *app); + +static inline int ts_shell_run(const struct thingset_app *app) +{ + return ts_impl_shell_run(app); +} + +int ts_shell_cmd_txt(const struct ts_shell *shell, size_t argc, char **argv); + +int ts_shell_cmd_node(const struct ts_shell *shell, size_t argc, char **argv); + +int ts_shell_cmd_port(const struct ts_shell *shell, size_t argc, char **argv); + +int ts_shell_cmd_obj(const struct ts_shell *shell, size_t argc, char **argv); + +int ts_shell_cmd_ts(const struct ts_shell *shell, size_t argc, char **argv); + +int ts_shell_cmd(const struct ts_shell *shell, size_t argc, char **argv); + +/** + * @brief Get next line of shell input. + * + * Line must be freed using ts_shell_free(). + * + * @param[in] prompt Prompt to display. + * @param[out] line New input line allocated with ts_shell_alloc(). + * @return 0 on success, <0 otherwise. + */ +static inline int ts_shell_get_line(const struct ts_shell *shell, const char *prompt, char **line) +{ + return ts_impl_shell_get_line(shell, prompt, line); +} + +/** + * @brief Execute command. + * + * Pass command line to shell to execute. + * + * @note This function should only be used for debugging/diagnostic. + * + * @warning This function shall not be called from shell command context! + * + * @param[in] cmd Command to be executed. + * @return Result of the execution. + */ +static inline int ts_shell_execute_cmd(const char* cmd) +{ + return ts_impl_shell_execute_cmd(cmd); +} + +/** + * @brief Returns the buffered output of ts_shell_execute_cmd() and resets the pointer + * + * The returned data is always followed by a nul character at position *sizep + * + * @note This function should only be used for debugging/diagnostic. + * + * @warning This function shall not be called from shell command context! + * + * @param[out] sizep Returns size of data in shell buffer + * @param[out] output Returns pointer to buffer containing shell execute output + */ +static inline int ts_shell_execute_output(size_t *sizep, const char **output) +{ + return ts_impl_shell_execute_output(sizep, output); +} + +/** + * @brief Wait for (all) shell(s) to end working. + */ +static inline int ts_shell_join(void) +{ + return ts_impl_shell_join(); +} + +/** + * @brief Helper. + * + * Modifies @p cmd. @p argv array is allocated by the function and shall be freed by ts_shell_free() + * after usage. + */ +int ts_shell_split_cmd(char *cmd, int *argc, char ***argv); + +/** + * @brief Allocate memory block from shell memory pool. + * + * Allocates and returns a memory block from the memory region owned by the shell(s). The total size + * size of the memory region is given by @ref TS_CONFIG_SHELL_MEM_SIZE. + * + * If no memory is available immediately, the call will block for the specified timeout waiting for + * memory to be freed. If the allocation cannot be performed by the expiration of the timeout, an + * error will be returned. + * + * @param[in] block_size Size of memory block (in bytes). + * @param[in] timeout_ms Maximum time to wait in milliseconds. + * @param[out] mem_block Pointer to memory block. + * @return 0 on success, <0 otherwise. + * @retval -ENOMEM Returned without waiting on no memory available. + * @retval -EAGAIN Waiting period timed out. + * @retval -EINVAL Invalid data supplied. + */ +int ts_shell_alloc(size_t block_size, thingset_time_ms_t timeout_ms, uint8_t **mem_block); + +/** + * @brief Free memory block allocated from the shell memory pool. + * + * Release a previously allocated memory block back to the shell memory pool. + * + * @param[in] mem_pool Address of the memory pool. + * @param[in] mem_block Pointer to memory block. + * @return 0 on success, <0 otherwise + */ +int ts_shell_free(const uint8_t *mem_block); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +/** @page ts_topic_app_shell ThingSet shell application + +*/ + +/** + * @} + */ + + +#endif /* TS_SHELL_H_ */ diff --git a/apps/shell/ts_shell_abuf.c b/apps/shell/ts_shell_abuf.c new file mode 100644 index 0000000..5b47b60 --- /dev/null +++ b/apps/shell/ts_shell_abuf.c @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet shell append buffer. + */ + +#include "../../src/thingset_env.h" + +#include "ts_shell.h" +#include "ts_shell_abuf.h" + +#include + +void ts_shell_abuf_init(struct ts_shell_abuf *ab) { + ab->b = NULL; + ab->len = 0; + ab->size = 0; +} + +int ts_shell_abuf_reserve(struct ts_shell_abuf *ab, uint16_t len) { + if (len > (ab->size - ab->len)) { + char *new; + /* Allocate buffer in chunks of 128 bytes */ + uint16_t new_size = ab->size + ((len / 128) + 1) * 128; + if (new_size > (TS_CONFIG_SHELL_MEM_SIZE / 2)) { + new_size = TS_CONFIG_SHELL_MEM_SIZE / 2; + } + int ret = ts_shell_alloc(new_size, 10, (uint8_t **)&new); + if (ret != 0) { + return ret; + } + memcpy(new, ab->b, ab->len); + ts_shell_free(ab->b); + ab->b = new; + ab->size = new_size; + } + return 0; +} + +void ts_shell_abuf_append(struct ts_shell_abuf *ab, const char *s, uint16_t len) { + int ret = ts_shell_abuf_reserve(ab, len); + if (ret != 0) { + return; + } + + memcpy(&ab->b[ab->len], s, len); + ab->len += len; +} + +void ts_shell_abuf_free(struct ts_shell_abuf *ab) { + if (ab->b != NULL) { + ts_shell_free(ab->b); + } + ts_shell_abuf_init(ab); +} diff --git a/apps/shell/ts_shell_abuf.h b/apps/shell/ts_shell_abuf.h new file mode 100644 index 0000000..cd57e28 --- /dev/null +++ b/apps/shell/ts_shell_abuf.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet shell append buffer. + */ + +#ifndef TS_SHELL_ABUF_H_ +#define TS_SHELL_ABUF_H_ + +/** + * @addtogroup ts_shell_api_priv + * @{ + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Append buffer. + * + * We define a very simple "append buffer" structure, that is a shell memory pool + * allocated string where we can append to. + * + * Taken from linenoise. + */ +struct ts_shell_abuf { + char *b; + uint16_t len; + uint16_t size; +}; + +void ts_shell_abuf_init(struct ts_shell_abuf *ab); + +int ts_shell_abuf_reserve(struct ts_shell_abuf *ab, uint16_t len); + +void ts_shell_abuf_append(struct ts_shell_abuf *ab, const char *s, uint16_t len); + +void ts_shell_abuf_free(struct ts_shell_abuf *ab); + +#ifdef __cplusplus +} +#endif + +/** @} */ + +#endif /* TS_SHELL_ABUF_H_ */ diff --git a/apps/shell/ts_shell_g.c b/apps/shell/ts_shell_g.c new file mode 100644 index 0000000..6e04cc4 --- /dev/null +++ b/apps/shell/ts_shell_g.c @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet shell generic implementation + */ + +#include "../../src/thingset_env.h" + +#include "ts_shell.h" +#include "ts_shell_g.h" +#include "ts_shell_abuf.h" + +#include + +/* Shell application */ + +atomic_flag ts_shell_running_g = ATOMIC_FLAG_INIT; + +pthread_t ts_shell_thread_g; + +struct ts_shell_abuf ts_shell_output_g = {.b = NULL, .len = 0, .size = 0}; + +void *ts_shell_fun_g(void *ptr) +{ + char *line; + while(ts_shell_get_line(NULL, "> ", &line) == 0) { + (void)ts_shell_execute_cmd(line); + ts_shell_free(line); + } + + atomic_flag_clear(&ts_shell_running_g); + pthread_exit(NULL); +} + +int ts_shell_run_g(void) +{ + if (atomic_flag_test_and_set(&ts_shell_running_g)) { + /* Shell already running */ + TS_ASSERT(false, "SHELL: %s requests run on already running shell", __func__); + return -EALREADY; + } + + int ret = pthread_create(&ts_shell_thread_g, NULL, ts_shell_fun_g, (void*)NULL); + + return ret; +} + + +int ts_shell_printf_g(const char *fmt, ...) +{ + va_list arg_ptr; + + /* Get required length */ + va_start(arg_ptr, fmt); + int len = vsnprintf(NULL, 0, fmt, arg_ptr); + va_end(arg_ptr); + if (len < 0) { + return len; + } + + /* Reserve memory in output buffer */ + if (len > 0) { + int ret = ts_shell_abuf_reserve(&ts_shell_output_g, len + 1); + if (ret != 0) { + return ret; + } + } + + /* Really write to output buffer */ + va_start(arg_ptr, fmt); + (void)vsnprintf(&ts_shell_output_g.b[ts_shell_output_g.len], len, fmt, arg_ptr); + va_end(arg_ptr); + ts_shell_output_g.len += len; + ts_shell_output_g.b[ts_shell_output_g.len] = '\0'; + + return 0; +} + +int ts_shell_execute_cmd_g(const char* cmd) +{ + if ((cmd == NULL) || (cmd[0] == '\0')) { + return 0; + } + + unsigned int len = strlen(cmd); + char *command; + int ret = ts_shell_alloc(len, 10, (uint8_t **)&command); + if (ret != 0) { + return ret; + } + memcpy(command, cmd, len); + + int argc; + char **argv; + ret = ts_shell_split_cmd(command, &argc, &argv); + if (ret != 0) { + ts_shell_free(command); + return ret; + } + if ((argc > 0) && (strcmp(argv[0], "clear") == 0)) { + ts_shell_print(NULL, "\x1b[H\x1b[2J"); + ts_shell_output_g.len = 0; + return 0; + } + + /* Free output buffer of last command */ + ts_shell_abuf_free(&ts_shell_output_g); + + /* Now really execute the command */ + ret = ts_shell_cmd(NULL, argc, argv); + ts_shell_free((const uint8_t *)command); + ts_shell_free((const uint8_t *)argv); + + /* Print new shell output to stdout */ + if (ts_shell_output_g.len > 0) { + fputs(&ts_shell_output_g.b[0], stdout); + } + + return ret; +} + +int ts_shell_execute_output_g(size_t *sizep, const char **output) +{ + if ((ts_shell_output_g.b == NULL) || + (ts_shell_output_g.size == 0) || + (ts_shell_output_g.len == 0)) { + *sizep = 0; + *output = NULL; + return -ENAVAIL; + } + + *sizep = ts_shell_output_g.len; + *output = ts_shell_output_g.b; + return 0; +} + +int ts_shell_join_g(void) +{ + return pthread_join(ts_shell_thread_g, NULL); +} diff --git a/apps/shell/ts_shell_g.h b/apps/shell/ts_shell_g.h new file mode 100644 index 0000000..7be0294 --- /dev/null +++ b/apps/shell/ts_shell_g.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet shell generic implementation + */ + +#ifndef TS_SHELL_G_H_ +#define TS_SHELL_G_H_ + +/** + * @addtogroup ts_shell_api_priv + * @{ + */ + +#include + +void *ts_shell_fun_g(void *ptr); + +int ts_shell_run_g(void); + +int ts_shell_printf_g(const char *fmt, ...); + +int ts_shell_execute_cmd_g(const char* cmd); + +int ts_shell_execute_output_g(size_t *sizep, const char **output); + +int ts_shell_join_g(void); + +#ifdef __cplusplus +extern "C" { +#endif + + +#ifdef __cplusplus +} +#endif + +/** @} */ + +#endif /* TS_SHELL_G_H_ */ diff --git a/native/apps/shell/CMakeLists.txt b/native/apps/shell/CMakeLists.txt new file mode 100644 index 0000000..ee90d67 --- /dev/null +++ b/native/apps/shell/CMakeLists.txt @@ -0,0 +1,7 @@ +# Copyright (c) 2022 Bobby Noelte. +# SPDX-License-Identifier: Apache-2.0 + +target_sources(ts PRIVATE ${THINGSET_BASE}/apps/shell/ts_shell.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/thingset/ts_impl_shell.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/thingset/ts_impl_shell_linenoise.c) +message(STATUS "ThingSet: Added Shell Application") diff --git a/zephyr/apps/shell/CMakeLists.txt b/zephyr/apps/shell/CMakeLists.txt new file mode 100644 index 0000000..68d3053 --- /dev/null +++ b/zephyr/apps/shell/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright (c) 2022 Bobby Noelte. +# SPDX-License-Identifier: Apache-2.0 + +if(DEFINED CONFIG_THINGSET_SHELL) +target_sources(ts PRIVATE ${THINGSET_BASE}/apps/shell/ts_shell.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/zephyr/thingset/ts_impl_shell.c) +message(STATUS "ThingSet: Added Shell Application") +endif() diff --git a/zephyr/apps/shell/Kconfig b/zephyr/apps/shell/Kconfig new file mode 100644 index 0000000..fe4d7fc --- /dev/null +++ b/zephyr/apps/shell/Kconfig @@ -0,0 +1,34 @@ +# Copyright (c) 2022 Bobby Noelte +# SPDX-License-Identifier: Apache-2.0 + +config THINGSET_SHELL + bool "ThingSet Shell Application" + depends on SHELL + depends on THINGSET_COM + help + This option enables the ThingSet shell application for Zephyr. + +if THINGSET_SHELL + +config THINGSET_SHELL_NAME + string "Shell Application name" + help + The name the shell application shall use at the local context. + +config THINGSET_SHELL_LOCID + int "Shell Application local context ID" + help + The local context the shell applicationm shall be attached to. + +config THINGSET_SHELL_PORTID + int "Shell Application port ID" + help + The ID of the port the shell shall communicate to the local context. + +config THINGSET_SHELL_MEM_SIZE + int "Dynamic memory the ThingSet shell may use" + default 32 + help + The total size of memory the ThingSet shell may allocate. Used e.g for argument array. + +endif # THINGSET_SHELL_ZEPHYR From 5da7ad649d15160cd8f9c79b99520436c7d97e4e Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Mon, 17 Jan 2022 08:19:08 +0100 Subject: [PATCH 25/37] COM prepare - add ThingSet Test support Prepare for ThingSet Communication Context: - Add test rigging and complex test assertions. Signed-off-by: Bobby Noelte --- test/test_support.c | 687 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 687 insertions(+) create mode 100644 test/test_support.c diff --git a/test/test_support.c b/test/test_support.c new file mode 100644 index 0000000..d986d76 --- /dev/null +++ b/test/test_support.c @@ -0,0 +1,687 @@ +/* + * Copyright (c) 2020 Martin Jäger / Libre Solar + * Copyright (c) 2021..2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "test.h" + +/* + * Test environment + * ---------------- + */ + +char test_assert_buffer[TEST_ASSERT_BUFFER_SIZE]; +const char *test_file_current = ""; +const char *test_func_current = ""; + +char test_log_buf[TEST_LOG_BUFFER_SIZE]; +uint8_t test_req_buf[TEST_REQ_BUFFER_LEN]; +uint8_t test_resp_buf[TEST_RESP_BUFFER_LEN]; +struct ts_array_info pub_serial_array; + +/* + * Test functions and flags used for core variant ThingSet context + * --------------------------------------------------------------- + */ + +bool test_core_conf_callback_called; +bool test_core_dummy_called; + +void test_core_dummy_function(void) +{ + test_core_dummy_called = 1; +} + +void test_core_conf_callback(void) +{ + test_core_conf_callback_called = 1; +} + +void test_core_reset_function(void) +{ + TS_LOGD("Core context reset function called!\n"); +} + +void test_core_auth_function(void) +{ + const char pass_exp[] = "expert123"; + const char pass_mkr[] = "maker456"; + + if (strlen(pass_exp) == strlen(auth_password) && + strncmp(auth_password, pass_exp, strlen(pass_exp)) == 0) + { + thingset_authorisation_set(TEST_CORE_LOCID, TS_EXP_RW | TS_USR_RW); + } + else if (strlen(pass_mkr) == strlen(auth_password) && + strncmp(auth_password, pass_mkr, strlen(pass_mkr)) == 0) + { + thingset_authorisation_set(TEST_CORE_LOCID, TS_MKR_RW | TS_EXP_RW | TS_USR_RW); + } + else { + thingset_authorisation_set(TEST_CORE_LOCID, TS_USR_RW); + } + + TS_LOGD("Core context auth function called, password: %s\n", auth_password); +} + +/* + * Test functions and flags used for communication variant ThingSet context + * ------------------------------------------------------------------------ + */ + +bool test_instance_conf_callback_called; +bool test_instance_dummy_called; + +void test_instance_dummy_function(void) +{ + test_instance_dummy_called = 1; +} + +void test_instance_conf_callback(void) +{ + test_instance_conf_callback_called = 1; +} + +void test_instance_reset_function() +{ + TS_LOGD("Instance context reset function called!\n"); +} + +void test_instance_auth_function() +{ + const char pass_exp[] = "expert123"; + const char pass_mkr[] = "maker456"; + + if (strlen(pass_exp) == strlen(auth_password) && + strncmp(auth_password, pass_exp, strlen(pass_exp)) == 0) + { + thingset_authorisation_set(TEST_INSTANCE_LOCID, TS_EXP_MASK | TS_USR_MASK); + } + else if (strlen(pass_mkr) == strlen(auth_password) && + strncmp(auth_password, pass_mkr, strlen(pass_mkr)) == 0) + { + thingset_authorisation_set(TEST_INSTANCE_LOCID, TS_MKR_MASK | TS_USR_MASK); + } + else { + thingset_authorisation_set(TEST_INSTANCE_LOCID, TS_USR_MASK); + } + + TS_LOGD("Instance context auth function called, password: %s\n", auth_password); +} + +void test_neighbour_reset_function() +{ + TS_LOGD("Neighbour context reset function called!\n"); +} + +/* + * Helpers and complex assert functions + * ------------------------------------ + */ + +int _hex2bin(uint8_t *bin, size_t bin_size, const char *hex) +{ + int len = strlen(hex); + unsigned int pos = 0; + for (int i = 0; i < len; i += 3) { + if (pos < bin_size) { + bin[pos++] = (char)strtoul(&hex[i], NULL, 16); + } + else { + return 0; + } + } + return pos; +} + +// determines the size of a cbor data item starting at given pointer +#define CBOR_TYPE_MASK 0xE0 /* top 3 bits */ +#define CBOR_INFO_MASK 0x1F /* low 5 bits */ + +/* Jump Table for Initial Byte (cf. table 5) */ +#define CBOR_UINT 0x00 /* type 0 */ +#define CBOR_NEGINT 0x20 /* type 1 */ +#define CBOR_BYTES 0x40 /* type 2 */ +#define CBOR_TEXT 0x60 /* type 3 */ +#define CBOR_ARRAY 0x80 /* type 4 */ +#define CBOR_MAP 0xA0 /* type 5 */ +#define CBOR_TAG 0xC0 /* type 6 */ +#define CBOR_7 0xE0 /* type 7 (float and other types) */ + +#define CBOR_NUM_MAX 23 /* maximum number that can be directl encoded */ + +/* Major types (cf. section 2.1) */ +/* Major type 0: Unsigned integers */ +#define CBOR_UINT8_FOLLOWS 24 /* 0x18 */ +#define CBOR_UINT16_FOLLOWS 25 /* 0x19 */ +#define CBOR_UINT32_FOLLOWS 26 /* 0x1a */ +#define CBOR_UINT64_FOLLOWS 27 /* 0x1b */ + +/* Indefinite Lengths for Some Major types (cf. section 2.2) */ +#define CBOR_VAR_FOLLOWS 31 /* 0x1f */ + +/* Major type 6: Semantic tagging */ +#define CBOR_DATETIME_STRING_FOLLOWS 0 +#define CBOR_DATETIME_EPOCH_FOLLOWS 1 +#define CBOR_DECFRAC_ARRAY_FOLLOWS 4 + +/* Major type 7: Float and other types */ +#define CBOR_FALSE (CBOR_7 | 20) +#define CBOR_TRUE (CBOR_7 | 21) +#define CBOR_NULL (CBOR_7 | 22) +#define CBOR_UNDEFINED (CBOR_7 | 23) +#define CBOR_SIMPLE (CBOR_7 | 24) +#define CBOR_FLOAT16 (CBOR_7 | 25) +#define CBOR_FLOAT32 (CBOR_7 | 26) +#define CBOR_FLOAT64 (CBOR_7 | 27) +#define CBOR_BREAK (CBOR_7 | 31) + +static int _cbor_size(const uint8_t *data) +{ + uint8_t type = data[0] & CBOR_TYPE_MASK; + uint8_t info = data[0] & CBOR_INFO_MASK; + + if (type == CBOR_UINT || type == CBOR_NEGINT) { + if (info <= CBOR_NUM_MAX) + return 1; + switch (info) { + case CBOR_UINT8_FOLLOWS: + return 2; + case CBOR_UINT16_FOLLOWS: + return 3; + case CBOR_UINT32_FOLLOWS: + return 5; + case CBOR_UINT64_FOLLOWS: + return 9; + } + } + else if (type == CBOR_BYTES || type == CBOR_TEXT) { + if (info <= CBOR_NUM_MAX) { + return info + 1; + } + else { + if (info == CBOR_UINT8_FOLLOWS) + return 1 + data[1]; + else if (info == CBOR_UINT16_FOLLOWS) + return 1 + (data[1] << 8 | data[2]); + else + return 0; // longer string / byte array not supported + } + } +#if TS_CONFIG_DECFRAC_TYPE_SUPPORT + else if (type == CBOR_TAG && info == CBOR_DECFRAC_ARRAY_FOLLOWS) { + int pos = 2; + pos += _cbor_size(&data[pos]); // exponent + pos += _cbor_size(&data[pos]); // mantissa + return pos; + } +#endif + else if (type == CBOR_7) { + switch (data[0]) { + case CBOR_FALSE: + case CBOR_TRUE: + return 1; + break; + case CBOR_FLOAT32: + return 5; + break; + case CBOR_FLOAT64: + return 9; + break; + } + } + + return 0; // float16, arrays, maps, tagged types, etc. curently not supported +} + +static int _bin2hex(char *hex, size_t hex_size, const uint8_t *bin, size_t bin_size) +{ + size_t bin_idx, hex_idx; + + for (bin_idx = hex_idx = 0; bin_idx < bin_size; bin_idx++) { + uint8_t b = bin[bin_idx] >> 4; + if (hex_idx >= hex_size) { + return -1; + } + hex[hex_idx++] = (char) (87 + b + (((b - 10) >> 31) & -39)); + b = bin[bin_idx] & 0xf; + if (hex_idx >= hex_size) { + return -1; + } + hex[hex_idx++] = (char) (87 + b + (((b - 10) >> 31) & -39)); + if (hex_idx < hex_size) { + hex[hex_idx++] = ' '; + } + } + if (hex_idx >= hex_size) { + return -1; + } + hex[hex_idx] = '\0'; + + return hex_idx; +} + +void assert_bin_resp(const uint8_t *resp_buf, int resp_len, const char *exp_hex, + unsigned int assert_line, const char *assert_msg) +{ + char resp_hex[100]; + uint8_t exp_b[100]; + + int exp_len = _hex2bin(exp_b, sizeof(exp_b), exp_hex); + (void)_bin2hex(resp_hex, sizeof(resp_hex), resp_buf, resp_len); + + UNITY_TEST_ASSERT_EQUAL_INT(exp_len, resp_len, assert_line, assert_msg); + TEST_ASSERT_EQUAL_HEX8_ARRAY_MESSAGE(exp_b, resp_buf, exp_len, assert_msg); +} + +void assert_bin_req(const uint8_t *req_b, int req_len, const char *exp_hex, + unsigned int assert_line, const char *assert_msg) +{ + int resp_len = thingset_process_buf(TEST_CORE_LOCID, req_b, req_len, test_resp_buf, + TEST_RESP_BUFFER_LEN); + + assert_bin_resp(test_resp_buf, resp_len, exp_hex, assert_line, assert_msg); +} + +void assert_bin_req_exp_bin(const uint8_t *req_b, int req_len, const uint8_t *exp_b, int exp_len, + unsigned int assert_line, const char *assert_msg) +{ + char exp_hex[exp_len * 3 + 1]; + (void)_bin2hex(exp_hex, sizeof(exp_hex), exp_b, exp_len); + + assert_bin_req(req_b, req_len, exp_hex, assert_line, assert_msg); +} + +void assert_bin_req_hex(const char *req_hex, const char *exp_hex, + unsigned int assert_line, const char *assert_msg) +{ + int req_len = _hex2bin(test_req_buf, TEST_REQ_BUFFER_LEN, req_hex); + + UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT(TEST_REQ_BUFFER_LEN, req_len, assert_line, + assert_msg); + + assert_bin_req(test_req_buf, req_len, exp_hex, assert_line, assert_msg); +} + +void assert_txt_resp(int exp_len, const char *exp_s, const char *msg) +{ + int resp_buf_len = strnlen((char *)test_resp_buf, sizeof(test_resp_buf)); + + TEST_ASSERT_EQUAL_STRING_MESSAGE(exp_s, (char *)test_resp_buf, msg); + TEST_ASSERT_EQUAL_MESSAGE(exp_len, resp_buf_len, msg); +} + +void assert_txt_req(const char *req_s, const char *exp_s, const char *msg) +{ + int req_len = strlen(req_s); + TEST_ASSERT_LESS_OR_EQUAL_size_t_MESSAGE(TEST_REQ_BUFFER_LEN - 1, req_len, msg); + + strncpy((char *)test_req_buf, req_s, req_len); + int resp_len = thingset_process_buf(TEST_CORE_LOCID, test_req_buf, req_len, test_resp_buf, + TEST_RESP_BUFFER_LEN); + TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(0, resp_len, msg); + + assert_txt_resp(resp_len, exp_s, msg); +} + +static void _txt_patch(char const *name, char const *value, const char *msg) +{ + int req_len = snprintf((char *)test_req_buf, TEST_REQ_BUFFER_LEN, "=conf {\"%s\":%s}", name, value); + int resp_len = thingset_process_buf(TEST_CORE_LOCID, test_req_buf, req_len, test_resp_buf, + TEST_RESP_BUFFER_LEN); + TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(0, resp_len, msg); + + int resp_buf_len = strnlen((char *)test_resp_buf, sizeof(test_resp_buf)); + + TEST_ASSERT_EQUAL_MESSAGE(resp_len, resp_buf_len, msg); + TEST_ASSERT_EQUAL_STRING_MESSAGE(":84 Changed.", (char *)test_resp_buf, msg); +} + +static int _txt_fetch(char const *name, char *value_read, const char *msg) +{ + int req_len = snprintf((char *)test_req_buf, TEST_REQ_BUFFER_LEN, "?conf \"%s\"", name); + int resp_len = thingset_process_buf(TEST_CORE_LOCID, test_req_buf, req_len, test_resp_buf, + TEST_RESP_BUFFER_LEN); + TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(0, resp_len, msg); + + int resp_buf_len = strnlen((char *)test_resp_buf, sizeof(test_resp_buf)); + + TEST_ASSERT_EQUAL_MESSAGE(resp_len, resp_buf_len, msg); + + int pos_dot = strchr((char *)test_resp_buf, '.') - (char *)test_resp_buf + 1; + char buf[100]; + strncpy(buf, (char *)test_resp_buf, pos_dot); + buf[pos_dot] = '\0'; + + TEST_ASSERT_EQUAL_STRING_MESSAGE(":85 Content.", buf, msg); + + return snprintf(value_read, strnlen((char *)test_resp_buf, sizeof(test_resp_buf)) - pos_dot, "%s", test_resp_buf + pos_dot + 1); +} + +// returns length of read value +static int _bin_fetch(uint16_t id, char *value_read, const char *msg) +{ + uint8_t req[] = { + TS_FETCH, + 0x18, ID_CONF, + 0x19, (uint8_t)(id >> 8), (uint8_t)id + }; + int ret = thingset_process_buf(TEST_CORE_LOCID, req, sizeof(req), test_resp_buf, + TEST_RESP_BUFFER_LEN); + TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(0, ret, msg); + + TEST_ASSERT_EQUAL_HEX8_MESSAGE(TS_STATUS_CONTENT, test_resp_buf[0], msg); + + int value_len = _cbor_size((uint8_t*)test_resp_buf + 1); + TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(0, value_len, msg); + + memcpy(value_read, test_resp_buf + 1, value_len); + return value_len; +} + +// returns length of read value +static void _bin_patch(uint16_t id, char *value, const char *msg) +{ + uint8_t req[100] = { + TS_PATCH, + 0x18, ID_CONF, + 0xA1, + 0x19, (uint8_t)(id >> 8), (uint8_t)id + }; + unsigned int len = _cbor_size((uint8_t*)value); + TEST_ASSERT_LESS_THAN_size_t_MESSAGE(sizeof(req) - 7, len, msg); + + memcpy(req + 7, value, len); + int ret = thingset_process_buf(TEST_CORE_LOCID, req, len + 7, test_resp_buf, + TEST_RESP_BUFFER_LEN); + TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(0, ret, msg); + + TEST_ASSERT_EQUAL_HEX8_MESSAGE(TS_STATUS_CHANGED, test_resp_buf[0], msg); +} + +void assert_json2cbor(char const *name, char const *json_value, uint16_t id, + const char *const cbor_value_hex, const char *msg) +{ + char buf[100]; // temporary data storage (JSON or CBOR) + uint8_t cbor_value[100]; + int len = _hex2bin(cbor_value, sizeof(cbor_value), (char *)cbor_value_hex); + + TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(0, len, msg); + TEST_ASSERT_LESS_THAN_INT_MESSAGE(100, len, msg); + + _txt_patch(name, json_value, msg); + len = _bin_fetch(id, buf, msg); + + TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(0, len, msg); + TEST_ASSERT_LESS_THAN_INT_MESSAGE(100, len, msg); + + //TEST_ASSERT_EQUAL_HEX8_ARRAY_MESSAGE(&cbor_value[0], &buf[0], len, msg); +} + +void assert_cbor2json(char const *name, char const *json_value, uint16_t id, char const *cbor_value_hex, const char *msg) +{ + char buf[100]; // temporary data storage (JSON or CBOR) + char cbor_value[100]; + _hex2bin((uint8_t *)cbor_value, sizeof(cbor_value), (char *)cbor_value_hex); + + _bin_patch(id, cbor_value, msg); + _txt_fetch(name, buf, msg); + + TEST_ASSERT_EQUAL_STRING_MESSAGE(json_value, buf, msg); +} + +void assert_msg_add_response_get_cbor(struct thingset_msg *response, struct thingset_msg *request, + const char *object_path, int expected_ret, + const char *expected_str, + unsigned int assert_line, const char *assert_msg) +{ + int ret; + thingset_oref_t object_oref = ts_obj_db_oref_any(ts_ctx_obj_db(TEST_INSTANCE_LOCID)); + char tc_desc[300]; + + /* Prepare assert message */ + snprintf(&tc_desc[0], sizeof(tc_desc) - 1, "%s\nAt %s#%d", assert_msg, __FILE__, __LINE__); + + TS_LOGD("%s", &tc_desc[sizeof("Assertion triggered by")]); + + thingset_msg_reset(response); + UNITY_TEST_ASSERT_EQUAL_UINT16(0, ts_msg_len(response), assert_line, &tc_desc[0]); + + if (object_path != NULL) { + ret = ts_obj_by_path(ts_obj_db_oref_any(ts_ctx_obj_db(TEST_INSTANCE_LOCID)), object_path, + (size_t)strlen(object_path), &object_oref); + UNITY_TEST_ASSERT_EQUAL_INT(0, ret, assert_line, &tc_desc[0]); + } + + ret = ts_msg_add_response_get_cbor(response, object_oref, request); + ts_msg_log(response, &test_log_buf[0], sizeof(test_log_buf)); + + /* Prepare assert message (again) */ + const char *object_name = ""; + ts_obj_id_t object_id = 0; + ts_obj_id_t object_parent_id = 0; + uint8_t object_type = 0; + if (ts_obj_db_oref_is_object(object_oref)) { + object_id = ts_obj_id(object_oref); + object_name = ts_obj_name(object_oref); + object_parent_id = ts_obj_parent_id(object_oref); + object_type = ts_obj_type(object_oref); + } + snprintf(&tc_desc[0], sizeof(tc_desc) - 1, + "%s - '%s', id: %u, type: %u, parent: %u -> %s\nAt %s#%d", assert_msg, object_name, + (unsigned int)object_id, (unsigned int)object_type, (unsigned int)object_parent_id, + &test_log_buf[0], __FILE__, __LINE__); + + UNITY_TEST_ASSERT_EQUAL_INT(expected_ret, ret, assert_line, &tc_desc[0]); + if (expected_str != NULL) { + UNITY_TEST_ASSERT_EQUAL_STRING(expected_str, &test_log_buf[ASSERT_MSG_LOG_SKIP], + assert_line, &tc_desc[0]); + } + else { + UNITY_TEST_ASSERT_EQUAL_UINT16(0, ts_msg_len(response), assert_line, &tc_desc[0]); + } +} + +void assert_msg_fetch_cbor(struct thingset_msg *request, struct thingset_msg *response, + const char *object_path, + bool test_object_id, + bool test_object_ids, uint16_t object_count, const char **object_names, + /* returns of ts_msg_add_request_fetch_cbor */ + int expected_ret_add_request, const char *expected_str_add_request, + /* returns of ts_msg_pull_request_cbor */ + int expected_ret_pull_request, uint8_t expected_status_id_pull_request, + /* returns of ts_msg_add_response_fetch_cbor */ + int expected_ret_add_response, const char *expected_str_add_response, + unsigned int assert_line, const char *assert_msg) +{ + int ret; + thingset_oref_t object_oref = ts_obj_db_oref_any(ts_ctx_obj_db(TEST_INSTANCE_LOCID)); + ts_obj_id_t object_id; + char tc_desc[300]; + ts_obj_id_t names_object_ids[object_count]; + struct ts_msg_stat expected_status_pull_request; + + TS_LOGD("%s", assert_msg + sizeof("Assertion triggered by")); + + expected_status_pull_request.type = TS_MSG_TYPE_REQUEST; + expected_status_pull_request.code = expected_status_id_pull_request; + + /* Prepare assert message */ + snprintf(&tc_desc[0], sizeof(tc_desc) - 1, "%s\nAt %s#%d", assert_msg, __FILE__, __LINE__); + + thingset_msg_reset(request); + UNITY_TEST_ASSERT_EQUAL_INT(0, ts_msg_len(request), assert_line, &tc_desc[0]); + thingset_msg_reset(response); + UNITY_TEST_ASSERT_EQUAL_INT(0, ts_msg_len(response), assert_line, &tc_desc[0]); + + /* + * Create fetch request + * -------------------- + */ + + /* Prepare assert message (again) */ + snprintf(&tc_desc[0], sizeof(tc_desc) - 1, + "%s - %s\nAt %s#%d", assert_msg, (object_path == NULL) ? "" : object_path, + __FILE__, __LINE__); + + if (object_path != NULL) { + ret = ts_obj_by_path(ts_obj_db_oref_any(ts_ctx_obj_db(TEST_INSTANCE_LOCID)), object_path, + (size_t)strlen(object_path), &object_oref); + UNITY_TEST_ASSERT_EQUAL_INT(0, ret, assert_line, &tc_desc[0]); + } + if (test_object_id) { + UNITY_TEST_ASSERT(ts_obj_db_oref_is_valid(object_oref), assert_line, &tc_desc[0]); + object_id = ts_obj_id(object_oref); + object_path = 0; + } + else { + object_id = 0; + } + ts_obj_id_t *object_ids = NULL; + if (test_object_ids) { + for (uint16_t j = 0; j < object_count; j++) { + const char *name = object_names[j]; + size_t name_len = strlen(name); + thingset_oref_t fetch_oref; + ret = ts_obj_by_name(object_oref, name, name_len, &fetch_oref); + UNITY_TEST_ASSERT_EQUAL_INT(0, ret, assert_line, &tc_desc[0]); + names_object_ids[j] = ts_obj_id(fetch_oref); + } + object_ids = &names_object_ids[0]; + object_names = NULL; + } + ret = ts_msg_add_request_fetch_cbor(request, object_id, object_path, + object_count, object_ids, object_names); + ts_msg_log(request, &test_log_buf[0], sizeof(test_log_buf)); + + /* Prepare assert message (again) */ + const char *object_name = ""; + ts_obj_id_t object_parent_id = 0; + uint8_t object_type = 0; + if (ts_obj_db_oref_is_object(object_oref)) { + object_id = ts_obj_id(object_oref); + object_name = ts_obj_name(object_oref); + object_parent_id = ts_obj_parent_id(object_oref); + object_type = ts_obj_type(object_oref); + } + snprintf(&tc_desc[0], sizeof(tc_desc) - 1, + "%s - '%s', id: %u, type: %u, parent: %u -> %s\nAt %s#%d", assert_msg, object_name, + (unsigned int)object_id, (unsigned int)object_type, (unsigned int)object_parent_id, + &test_log_buf[0], __FILE__, __LINE__); + + UNITY_TEST_ASSERT_EQUAL_INT(expected_ret_add_request, ret, assert_line, &tc_desc[0]); + if (expected_str_add_request != NULL) { + UNITY_TEST_ASSERT_EQUAL_STRING(expected_str_add_request, + &test_log_buf[ASSERT_MSG_LOG_SKIP], assert_line, &tc_desc[0]); + } + else { + UNITY_TEST_ASSERT_EQUAL_UINT16(0, ts_msg_len(request), assert_line, &tc_desc[0]); + } + + /* + * Pull fetch request + * ------------------ + */ + + thingset_oref_t request_oref = { .db_id = TEST_INSTANCE_LOCID }; + ts_msg_auth_set(request, thingset_authorisation(TEST_INSTANCE_LOCID)); + ret = ts_msg_pull_request_cbor(request, &request_oref); + + /* Prepare assert message (again) */ + object_name = ""; + if (ts_obj_db_oref_is_object(request_oref)) { + object_id = ts_obj_id(request_oref); + object_name = ts_obj_name(request_oref); + object_parent_id = ts_obj_parent_id(request_oref); + object_type = ts_obj_type(request_oref); + } + snprintf(&tc_desc[0], sizeof(tc_desc) - 1, + "%s - '%s', id: %u, type: %u, parent: %u, status: 0x%02x\nAt %s#%d", + assert_msg, object_name, (unsigned int)object_id, (unsigned int)object_type, + (unsigned int)object_parent_id, (unsigned int)ts_msg_status_code(request), + __FILE__, __LINE__); + + UNITY_TEST_ASSERT_EQUAL_INT(expected_ret_pull_request, ret, assert_line, &tc_desc[0]); + UNITY_TEST_ASSERT_EQUAL_UINT8(expected_status_pull_request.type, ts_msg_status_type(request), + assert_line, &tc_desc[0]); + UNITY_TEST_ASSERT_EQUAL_UINT8(expected_status_pull_request.code, ts_msg_status_code(request), + assert_line, &tc_desc[0]); + if (test_object_id) { + UNITY_TEST_ASSERT_EQUAL_UINT16(object_id, ts_obj_id(object_oref), assert_line, &tc_desc[0]); + } + UNITY_TEST_ASSERT_EQUAL_UINT16(thingset_authorisation(TEST_INSTANCE_LOCID), + ts_msg_auth(request), assert_line, &tc_desc[0]); + + /* + * Create fetch response + * --------------------- + */ + + ret = ts_msg_add_response_fetch_cbor(response, request_oref, request); + ts_msg_log(response, &test_log_buf[0], sizeof(test_log_buf)); + + /* Prepare assert message (again) */ + object_name = ""; + if (ts_obj_db_oref_is_object(request_oref)) { + object_id = ts_obj_id(request_oref); + object_name = ts_obj_name(request_oref); + object_parent_id = ts_obj_parent_id(request_oref); + object_type = ts_obj_type(request_oref); + } + snprintf(&tc_desc[0], sizeof(tc_desc) - 1, + "%s - '%s', id: %u, type: %u, parent: %u, status: %u -> %s\nAt %s%d", + assert_msg, object_name, (unsigned int)object_id, (unsigned int)object_type, + (unsigned int)object_parent_id, (unsigned int)ts_msg_status_code(request), + &test_log_buf[0], __FILE__, __LINE__); + + UNITY_TEST_ASSERT_EQUAL_INT(expected_ret_add_response, ret, assert_line, &tc_desc[0]); + if (expected_str_add_response != NULL) { + UNITY_TEST_ASSERT_EQUAL_STRING(expected_str_add_response, + &test_log_buf[ASSERT_MSG_LOG_SKIP], + assert_line, &tc_desc[0]); + } + else { + UNITY_TEST_ASSERT_EQUAL_UINT16(0, ts_msg_len(response), assert_line, &tc_desc[0]); + } +} + +void assert_msg_add_response_get_json(struct thingset_msg *response, struct thingset_msg *request, + ts_obj_id_t obj_id, int expected_ret, + const char *expected_str, + unsigned int assert_line, const char *assert_msg) +{ + int ret; + thingset_oref_t object_oref; + char tc_desc[300]; + + /* Prepare assert message */ + snprintf(&tc_desc[0], sizeof(tc_desc) - 1, "%s\nAt %s#%d", assert_msg, __FILE__, __LINE__); + + TS_LOGD("%s", &tc_desc[sizeof("Assertion triggered by")]); + + ts_obj_db_oref_init(TEST_INSTANCE_LOCID, &object_oref); + + thingset_msg_reset(response); + UNITY_TEST_ASSERT_EQUAL_UINT16(0, ts_msg_len(response), assert_line, &tc_desc[0]); + + ret = ts_obj_db_oref_by_id(TEST_INSTANCE_LOCID, obj_id, &object_oref ); + UNITY_TEST_ASSERT_EQUAL_INT(0, ret, assert_line, &tc_desc[0]); + + ret = ts_msg_add_response_get_json(response, object_oref, request); + + /* Prepare assert message (again) */ + ts_msg_log(response, &test_log_buf[0], sizeof(test_log_buf)); + snprintf(&tc_desc[0], sizeof(tc_desc) - 1, + "%s -> ret: %d, oref: %u, name: '%s', >%s<\nAt %s%d", assert_msg, + ret, (unsigned int)object_oref.db_oid, ts_obj_name(object_oref), + &test_log_buf[ASSERT_MSG_LOG_SKIP], __FILE__, __LINE__); + + UNITY_TEST_ASSERT_EQUAL_INT(expected_ret, ret, assert_line, &tc_desc[0]); + UNITY_TEST_ASSERT_EQUAL_STRING_LEN(expected_str, ts_msg_data(response), ts_msg_len(response), + assert_line, &tc_desc[0]); + UNITY_TEST_ASSERT_EQUAL_UINT16(strlen(expected_str), ts_msg_len(response), assert_line, + &tc_desc[0]); +} From 71b1fa075db8bd43bc3790da7426d5e2c9e0f5e7 Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Sun, 19 Dec 2021 12:09:23 +0100 Subject: [PATCH 26/37] COM prepare - add tests for new communication related functions Prepare for ThingSet Communication Context: - add tests for OSAL and enviroment - assert support - communication buffers - time support - low memory footprint JSMN JSON parser - add tests for communication and supporting contexts - generic context - core context - communication context - message handling - object data handling Signed-off-by: Bobby Noelte --- test/test_assert.c | 61 ++ test/test_buf.c | 92 ++ test/test_core.c | 618 ++++++++++++ test/test_ctx.c | 254 +++++ test/test_jsmn.c | 99 ++ test/test_msg.c | 2249 ++++++++++++++++++++++++++++++++++++++++++++ test/test_obj.c | 143 +++ test/test_time.c | 32 + 8 files changed, 3548 insertions(+) create mode 100644 test/test_assert.c create mode 100644 test/test_buf.c create mode 100644 test/test_core.c create mode 100644 test/test_ctx.c create mode 100644 test/test_jsmn.c create mode 100644 test/test_msg.c create mode 100644 test/test_obj.c create mode 100644 test/test_time.c diff --git a/test/test_assert.c b/test/test_assert.c new file mode 100644 index 0000000..1ecfc5b --- /dev/null +++ b/test/test_assert.c @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020 Martin Jäger / Libre Solar + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "test.h" + +/** + * @brief Test Asserts + * + * This test verifies various assert macros used in the unit tests. + * + */ +void test_assert(void) +{ + TEST_ASSERT(1); + TEST_ASSERT_TRUE(1); + TEST_ASSERT_TRUE_MESSAGE(1, "test_assert"); + TEST_ASSERT_FALSE(0); + TEST_ASSERT_NULL(NULL); + TEST_ASSERT_NOT_NULL("foo"); + TEST_ASSERT_EQUAL(1, 1); + TEST_ASSERT_EQUAL_MESSAGE(1, 1, "test_assert"); + TEST_ASSERT_EQUAL_FLOAT(123.4567890123456789, 123.4567890123456789); + TEST_ASSERT_EQUAL_HEX(0x1234, 0x1234); + TEST_ASSERT_EQUAL_HEX8(0x34, 0x34); + TEST_ASSERT_EQUAL_HEX8_MESSAGE(0x34, 0x34, "test_assert"); + TEST_ASSERT_EQUAL_HEX8_ARRAY("1234", "1234", 4); + TEST_ASSERT_EQUAL_HEX8_ARRAY_MESSAGE("1234", "1234", 4, "test_assert"); + TEST_ASSERT_EQUAL_INT(2, 2); + TEST_ASSERT_EQUAL_INT32(-32, -32); + TEST_ASSERT_EQUAL_UINT(3U, 3U); + TEST_ASSERT_EQUAL_UINT8(0x103U, 0x203U); + TEST_ASSERT_EQUAL_UINT16(0x303U, 0x303U); + TEST_ASSERT_EQUAL_UINT32(0x12345678U, 0x12345678U); + TEST_ASSERT_EQUAL_PTR(NULL, NULL); + TEST_ASSERT_EQUAL_STRING("ttt", "ttt"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("ttt", "ttt", "test_assert"); + TEST_ASSERT_GREATER_OR_EQUAL_size_t(100, 100); + TEST_ASSERT_GREATER_OR_EQUAL_size_t(100, 101); + TEST_ASSERT_LESS_THAN_size_t(100, 99); + TEST_ASSERT_LESS_THAN_size_t_MESSAGE(100, 99, "test_assert"); + TEST_ASSERT_LESS_OR_EQUAL_UINT8(UINT8_MAX, UINT8_MAX); + TEST_ASSERT_LESS_OR_EQUAL_UINT8(UINT8_MAX, UINT8_MAX - 1); + TEST_ASSERT_LESS_OR_EQUAL_UINT8(0, 0); + TEST_ASSERT_LESS_OR_EQUAL_size_t(100, 100); + TEST_ASSERT_LESS_OR_EQUAL_size_t(100, 99); + TEST_ASSERT_LESS_OR_EQUAL_size_t_MESSAGE(100, 99, "test_assert"); + TEST_ASSERT_NOT_EQUAL(1, 2); +} + +void tests_assert(void) +{ + UNITY_BEGIN(); + + // Test asserts + RUN_TEST(test_assert); + + UNITY_END(); +} diff --git a/test/test_buf.c b/test/test_buf.c new file mode 100644 index 0000000..a373ae6 --- /dev/null +++ b/test/test_buf.c @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2021 Bobby Noelte + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "test.h" + +/** + * @brief Test communication buffer + * + * This test verifies message buffer usage: + * - ts_buf_alloc() + * - ts_buf_ref() + * - ts_buf_unref() + */ +void test_buf(void) +{ + int ret; + struct ts_buf *buffers[TS_CONFIG_BUF_COUNT + 1]; + + /* Check buffer pool size for testing */ + TEST_ASSERT_GREATER_OR_EQUAL_size_t(16, TS_CONFIG_BUF_COUNT); + TEST_ASSERT_GREATER_OR_EQUAL_size_t(1024, TS_CONFIG_BUF_DATA_SIZE); + + /* Allocate all buffers */ + for (int i = 0; i < TS_CONFIG_BUF_COUNT; i++) { + ret = ts_buf_alloc(10, 10, &buffers[i]); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_NOT_NULL(buffers[i]); + } + + /* Try one more */ + ret = ts_buf_alloc(10, 10, &buffers[TS_CONFIG_BUF_COUNT]); + TEST_ASSERT_NOT_EQUAL(0, ret); + + /* Double unref on single ref first buffer - Expect second unref to fail */ + ret = ts_buf_unref(buffers[0]); + TEST_ASSERT_EQUAL(0, ret); + ret = ts_buf_unref(buffers[0]); + TEST_ASSERT_NOT_EQUAL(0, ret); + + /* We should now get another buffer */ + ret = ts_buf_alloc(10, 10, &buffers[0]); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_NOT_NULL(buffers[0]); + + /* Double unref on double ref first buffer - Expect 2nd unref to pass */ + ret = ts_buf_ref(buffers[0]); + TEST_ASSERT_EQUAL(0, ret); + ret = ts_buf_unref(buffers[0]); + TEST_ASSERT_EQUAL(0, ret); + ret = ts_buf_unref(buffers[0]); + TEST_ASSERT_EQUAL(0, ret); + + /* Expect third unref to fail */ + ret = ts_buf_unref(buffers[0]); + TEST_ASSERT_NOT_EQUAL(0, ret); + + /* We should now get another buffer again */ + ret = ts_buf_alloc(10, 10, &buffers[0]); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_NOT_NULL(buffers[0]); + + /* Unref all buffers */ + for (int i = 0; i < TS_CONFIG_BUF_COUNT; i++) { + ret = ts_buf_unref(buffers[i]); + TEST_ASSERT_EQUAL(0, ret); + } + + /* Allocate all buffers again */ + for (int i = 0; i < TS_CONFIG_BUF_COUNT; i++) { + ret = ts_buf_alloc(10, 10, &buffers[i]); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_NOT_NULL(buffers[i]); + } + + /* Unref all buffers */ + for (int i = 0; i < TS_CONFIG_BUF_COUNT; i++) { + ret = ts_buf_unref(buffers[i]); + TEST_ASSERT_EQUAL(0, ret); + } +} + +void tests_buf(void) +{ + UNITY_BEGIN(); + + // Operating system abstraction + RUN_TEST(test_buf); + + UNITY_END(); +} diff --git a/test/test_core.c b/test/test_core.c new file mode 100644 index 0000000..a1f77d0 --- /dev/null +++ b/test/test_core.c @@ -0,0 +1,618 @@ +/* + * Copyright (c) 2020 Martin Jäger / Libre Solar + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "test.h" + +char test_log_buf[300]; +uint8_t test_req_buf[TEST_REQ_BUFFER_LEN]; +uint8_t test_resp_buf[TEST_RESP_BUFFER_LEN]; + +bool conf_callback_called; +bool dummy_called_flag; +struct ts_array_info pub_serial_array; + +void dummy(void) +{ + dummy_called_flag = 1; +} + +void conf_callback(void) +{ + conf_callback_called = 1; +} + +void reset_function() +{ + LOG_DBG("Reset function called!\n"); +} + +void auth_function() +{ + const char pass_exp[] = "expert123"; + const char pass_mkr[] = "maker456"; + + if (strlen(pass_exp) == strlen(auth_password) && + strncmp(auth_password, pass_exp, strlen(pass_exp)) == 0) + { + thingset_authorisation_set(&test_ts_core, TS_EXP_RW | TS_USR_RW); + } + else if (strlen(pass_mkr) == strlen(auth_password) && + strncmp(auth_password, pass_mkr, strlen(pass_mkr)) == 0) + { + thingset_authorisation_set(&test_ts_core, TS_MKR_RW | TS_EXP_RW | TS_USR_RW); + } + else { + thingset_authorisation_set(&test_ts_core, TS_USR_RW); + } + + LOG_DBG("Auth function called, password: %s\n", auth_password); +} + +int _hex2bin(uint8_t *bin, size_t bin_size, const char *hex) +{ + int len = strlen(hex); + unsigned int pos = 0; + for (int i = 0; i < len; i += 3) { + if (pos < bin_size) { + bin[pos++] = (char)strtoul(&hex[i], NULL, 16); + } + else { + return 0; + } + } + return pos; +} + +// determines the size of a cbor data item starting at given pointer +#define CBOR_TYPE_MASK 0xE0 /* top 3 bits */ +#define CBOR_INFO_MASK 0x1F /* low 5 bits */ + +/* Jump Table for Initial Byte (cf. table 5) */ +#define CBOR_UINT 0x00 /* type 0 */ +#define CBOR_NEGINT 0x20 /* type 1 */ +#define CBOR_BYTES 0x40 /* type 2 */ +#define CBOR_TEXT 0x60 /* type 3 */ +#define CBOR_ARRAY 0x80 /* type 4 */ +#define CBOR_MAP 0xA0 /* type 5 */ +#define CBOR_TAG 0xC0 /* type 6 */ +#define CBOR_7 0xE0 /* type 7 (float and other types) */ + +#define CBOR_NUM_MAX 23 /* maximum number that can be directl encoded */ + +/* Major types (cf. section 2.1) */ +/* Major type 0: Unsigned integers */ +#define CBOR_UINT8_FOLLOWS 24 /* 0x18 */ +#define CBOR_UINT16_FOLLOWS 25 /* 0x19 */ +#define CBOR_UINT32_FOLLOWS 26 /* 0x1a */ +#define CBOR_UINT64_FOLLOWS 27 /* 0x1b */ + +/* Indefinite Lengths for Some Major types (cf. section 2.2) */ +#define CBOR_VAR_FOLLOWS 31 /* 0x1f */ + +/* Major type 6: Semantic tagging */ +#define CBOR_DATETIME_STRING_FOLLOWS 0 +#define CBOR_DATETIME_EPOCH_FOLLOWS 1 +#define CBOR_DECFRAC_ARRAY_FOLLOWS 4 + +/* Major type 7: Float and other types */ +#define CBOR_FALSE (CBOR_7 | 20) +#define CBOR_TRUE (CBOR_7 | 21) +#define CBOR_NULL (CBOR_7 | 22) +#define CBOR_UNDEFINED (CBOR_7 | 23) +#define CBOR_SIMPLE (CBOR_7 | 24) +#define CBOR_FLOAT16 (CBOR_7 | 25) +#define CBOR_FLOAT32 (CBOR_7 | 26) +#define CBOR_FLOAT64 (CBOR_7 | 27) +#define CBOR_BREAK (CBOR_7 | 31) + +static int _cbor_size(const uint8_t *data) +{ + uint8_t type = data[0] & CBOR_TYPE_MASK; + uint8_t info = data[0] & CBOR_INFO_MASK; + + if (type == CBOR_UINT || type == CBOR_NEGINT) { + if (info <= CBOR_NUM_MAX) + return 1; + switch (info) { + case CBOR_UINT8_FOLLOWS: + return 2; + case CBOR_UINT16_FOLLOWS: + return 3; + case CBOR_UINT32_FOLLOWS: + return 5; + case CBOR_UINT64_FOLLOWS: + return 9; + } + } + else if (type == CBOR_BYTES || type == CBOR_TEXT) { + if (info <= CBOR_NUM_MAX) { + return info + 1; + } + else { + if (info == CBOR_UINT8_FOLLOWS) + return 1 + data[1]; + else if (info == CBOR_UINT16_FOLLOWS) + return 1 + (data[1] << 8 | data[2]); + else + return 0; // longer string / byte array not supported + } + } +#if TS_CONFIG_DECFRAC_TYPE_SUPPORT + else if (type == CBOR_TAG && info == CBOR_DECFRAC_ARRAY_FOLLOWS) { + int pos = 2; + pos += _cbor_size(&data[pos]); // exponent + pos += _cbor_size(&data[pos]); // mantissa + return pos; + } +#endif + else if (type == CBOR_7) { + switch (data[0]) { + case CBOR_FALSE: + case CBOR_TRUE: + return 1; + break; + case CBOR_FLOAT32: + return 5; + break; + case CBOR_FLOAT64: + return 9; + break; + } + } + + return 0; // float16, arrays, maps, tagged types, etc. curently not supported +} + +static int _bin2hex(char *hex, size_t hex_size, const uint8_t *bin, size_t bin_size) +{ + size_t bin_idx, hex_idx; + + for (bin_idx = hex_idx = 0; bin_idx < bin_size; bin_idx++) { + uint8_t b = bin[bin_idx] >> 4; + if (hex_idx >= hex_size) { + return -1; + } + hex[hex_idx++] = (char) (87 + b + (((b - 10) >> 31) & -39)); + b = bin[bin_idx] & 0xf; + if (hex_idx >= hex_size) { + return -1; + } + hex[hex_idx++] = (char) (87 + b + (((b - 10) >> 31) & -39)); + if (hex_idx < hex_size) { + hex[hex_idx++] = ' '; + } + } + if (hex_idx >= hex_size) { + return -1; + } + hex[hex_idx] = '\0'; + + return hex_idx; +} + +void assert_bin_resp(const uint8_t *resp_buf, int resp_len, const char *exp_hex, const char *msg) +{ + char resp_hex[100]; + uint8_t exp_b[100]; + + int exp_len = _hex2bin(exp_b, sizeof(exp_b), exp_hex); + (void)_bin2hex(resp_hex, sizeof(resp_hex), resp_buf, resp_len); + + TEST_ASSERT_EQUAL_MESSAGE(exp_len, resp_len, msg); + TEST_ASSERT_EQUAL_HEX8_ARRAY_MESSAGE(exp_b, resp_buf, exp_len, msg); +} + +void assert_bin_req(const uint8_t *req_b, int req_len, const char *exp_hex, const char* msg) +{ + int resp_len = thingset_process_buf(&test_ts_core, req_b, req_len, test_resp_buf, TEST_RESP_BUFFER_LEN); + + assert_bin_resp(test_resp_buf, resp_len, exp_hex, msg); +} + +void assert_bin_req_exp_bin(const uint8_t *req_b, int req_len, const uint8_t *exp_b, int exp_len, const char* msg) +{ + char exp_hex[exp_len * 3 + 1]; + (void)_bin2hex(exp_hex, sizeof(exp_hex), exp_b, exp_len); + + assert_bin_req(req_b, req_len, exp_hex, msg); +} + +void assert_bin_req_hex(const char *req_hex, const char *exp_hex, const char *msg) +{ + int req_len = _hex2bin(test_req_buf, TEST_REQ_BUFFER_LEN, req_hex); + + TEST_ASSERT_LESS_OR_EQUAL_size_t_MESSAGE(TEST_REQ_BUFFER_LEN, req_len, msg); + + assert_bin_req(test_req_buf, req_len, exp_hex, msg); +} + +void assert_txt_resp(int exp_len, const char *exp_s, const char *msg) +{ + int resp_buf_len = strlen((char *)test_resp_buf); + + TEST_ASSERT_EQUAL_STRING_MESSAGE(exp_s, (char *)test_resp_buf, msg); + TEST_ASSERT_EQUAL_MESSAGE(exp_len, resp_buf_len, msg); +} + +void assert_txt_req(const char *req_s, const char *exp_s, const char *msg) +{ + int req_len = strlen(req_s); + TEST_ASSERT_LESS_OR_EQUAL_size_t_MESSAGE(TEST_REQ_BUFFER_LEN - 1, req_len, msg); + + strncpy((char *)test_req_buf, req_s, req_len); + int resp_len = thingset_process_buf(&test_ts_core, test_req_buf, req_len, test_resp_buf, + TEST_RESP_BUFFER_LEN); + TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(0, resp_len, msg); + + assert_txt_resp(resp_len, exp_s, msg); +} + +static void _txt_patch(char const *name, char const *value, const char *msg) +{ + int req_len = snprintf((char *)test_req_buf, TEST_REQ_BUFFER_LEN, "=conf {\"%s\":%s}", name, value); + int resp_len = thingset_process_buf(&test_ts_core, test_req_buf, req_len, test_resp_buf, + TEST_RESP_BUFFER_LEN); + TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(0, resp_len, msg); + + int resp_buf_len = strlen((char *)test_resp_buf); + + TEST_ASSERT_EQUAL_MESSAGE(resp_len, resp_buf_len, msg); + TEST_ASSERT_EQUAL_STRING_MESSAGE(":84 Changed.", (char *)test_resp_buf, msg); +} + +static int _txt_fetch(char const *name, char *value_read, const char *msg) +{ + int req_len = snprintf((char *)test_req_buf, TEST_REQ_BUFFER_LEN, "?conf \"%s\"", name); + int resp_len = thingset_process_buf(&test_ts_core, test_req_buf, req_len, test_resp_buf, + TEST_RESP_BUFFER_LEN); + TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(0, resp_len, msg); + + int resp_buf_len = strlen((char *)test_resp_buf); + + TEST_ASSERT_EQUAL_MESSAGE(resp_len, resp_buf_len, msg); + + int pos_dot = strchr((char *)test_resp_buf, '.') - (char *)test_resp_buf + 1; + char buf[100]; + strncpy(buf, (char *)test_resp_buf, pos_dot); + buf[pos_dot] = '\0'; + + TEST_ASSERT_EQUAL_STRING_MESSAGE(":85 Content.", buf, msg); + + return snprintf(value_read, strlen((char *)test_resp_buf) - pos_dot, "%s", test_resp_buf + pos_dot + 1); +} + +// returns length of read value +static int _bin_fetch(uint16_t id, char *value_read, const char *msg) +{ + uint8_t req[] = { + TS_FETCH, + 0x18, ID_CONF, + 0x19, (uint8_t)(id >> 8), (uint8_t)id + }; + int ret = thingset_process_buf(&test_ts_core, req, sizeof(req), test_resp_buf, + TEST_RESP_BUFFER_LEN); + TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(0, ret, msg); + + TEST_ASSERT_EQUAL_HEX8_MESSAGE(TS_STATUS_CONTENT, test_resp_buf[0], msg); + + int value_len = _cbor_size((uint8_t*)test_resp_buf + 1); + TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(0, value_len, msg); + + memcpy(value_read, test_resp_buf + 1, value_len); + return value_len; +} + +// returns length of read value +static void _bin_patch(uint16_t id, char *value, const char *msg) +{ + uint8_t req[100] = { + TS_PATCH, + 0x18, ID_CONF, + 0xA1, + 0x19, (uint8_t)(id >> 8), (uint8_t)id + }; + unsigned int len = _cbor_size((uint8_t*)value); + TEST_ASSERT_LESS_THAN_size_t_MESSAGE(sizeof(req) - 7, len, msg); + + memcpy(req + 7, value, len); + int ret = thingset_process_buf(&test_ts_core, req, len + 7, test_resp_buf, + TEST_RESP_BUFFER_LEN); + TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(0, ret, msg); + + TEST_ASSERT_EQUAL_HEX8_MESSAGE(TS_STATUS_CHANGED, test_resp_buf[0], msg); +} + +void assert_json2cbor(char const *name, char const *json_value, uint16_t id, + const char *const cbor_value_hex, const char *msg) +{ + char buf[100]; // temporary data storage (JSON or CBOR) + uint8_t cbor_value[100]; + int len = _hex2bin(cbor_value, sizeof(cbor_value), (char *)cbor_value_hex); + + TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(0, len, msg); + TEST_ASSERT_LESS_THAN_INT_MESSAGE(100, len, msg); + + _txt_patch(name, json_value, msg); + len = _bin_fetch(id, buf, msg); + + TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(0, len, msg); + TEST_ASSERT_LESS_THAN_INT_MESSAGE(100, len, msg); + + //TEST_ASSERT_EQUAL_HEX8_ARRAY_MESSAGE(&cbor_value[0], &buf[0], len, msg); +} + +void assert_cbor2json(char const *name, char const *json_value, uint16_t id, char const *cbor_value_hex, const char *msg) +{ + char buf[100]; // temporary data storage (JSON or CBOR) + char cbor_value[100]; + _hex2bin((uint8_t *)cbor_value, sizeof(cbor_value), (char *)cbor_value_hex); + + _bin_patch(id, cbor_value, msg); + _txt_fetch(name, buf, msg); + + TEST_ASSERT_EQUAL_STRING_MESSAGE(json_value, buf, msg); +} + +void assert_msg_add_response_get_cbor(struct thingset_msg *response, struct thingset_msg *request, + const char *object_path, int expected_ret, + const char *expected_str, const char *assert_msg) +{ + int ret; + thingset_oref_t object_oref; + char tc_desc[300]; + + /* Prepare assert message */ + int len = snprintf(&tc_desc[0], sizeof(tc_desc) - 1, "%s: ", assert_msg); + + LOG_DBG("%s", &tc_desc[sizeof("Assertion triggered by")]); + + ts_obj_db_oref_init(TEST_DB_ID_INSTANCE, &object_oref); + + thingset_msg_reset(response); + TEST_ASSERT_EQUAL_MESSAGE(0, ts_msg_len(response), &tc_desc[0]); + + if (object_path == NULL) { + ts_obj_db_oref_init(TEST_DB_ID_INSTANCE, &object_oref); + } + else { + ret = ts_obj_db_oref_by_path(TEST_DB_ID_INSTANCE, object_path, + (size_t)strlen(object_path), + &object_oref); + TEST_ASSERT_EQUAL_MESSAGE(0, ret, &tc_desc[0]); + } + + ret = ts_msg_add_response_get_cbor(response, object_oref, request); + ts_msg_log(response, &test_log_buf[0], sizeof(test_log_buf)); + + /* Complete assert message */ + const char *object_name = ""; + ts_obj_id_t object_id = 0; + ts_obj_id_t object_parent_id = 0; + uint8_t object_type = 0; + if (ts_obj_db_oref_is_valid(object_oref)) { + object_id = ts_obj_id(object_oref); + object_name = ts_obj_name(object_oref); + object_parent_id = ts_obj_parent_id(object_oref); + object_type = ts_obj_type(object_oref); + } + snprintf(&tc_desc[len], sizeof(tc_desc) - len - 1, + " - '%s', id: %u, type: %u, parent: %u -> %s", object_name, (unsigned int)object_id, + (unsigned int)object_type, (unsigned int) object_parent_id, &test_log_buf[0]); + + TEST_ASSERT_EQUAL_MESSAGE(expected_ret, ret, &tc_desc[0]); + if (expected_str != NULL) { + TEST_ASSERT_EQUAL_STRING_MESSAGE(expected_str, &test_log_buf[0], &tc_desc[0]); + } + else { + TEST_ASSERT_EQUAL_MESSAGE(0, ts_msg_len(response), &tc_desc[0]); + } +} + +void assert_msg_fetch_cbor(struct thingset_msg *request, struct thingset_msg *response, + const char *object_path, + bool test_object_id, + bool test_object_ids, uint16_t object_count, const char **object_names, + /* returns of ts_msg_add_request_fetch_cbor */ + int expected_ret_add_request, const char *expected_str_add_request, + /* returns of ts_msg_pull_request_cbor */ + int expected_ret_pull_request, uint8_t expected_status_id_pull_request, + /* returns of ts_msg_add_response_fetch_cbor */ + int expected_ret_add_response, const char *expected_str_add_response, + const char *assert_msg) +{ + int ret; + thingset_oref_t object_oref; + ts_obj_id_t object_id; + char tc_desc[300]; + ts_obj_id_t names_object_ids[object_count]; + struct ts_msg_stat expected_status_pull_request; + + /* Prepare assert message */ + int len = snprintf(&tc_desc[0], sizeof(tc_desc) - 1, "%s: ", assert_msg); + + LOG_DBG("%s", &tc_desc[sizeof("Assertion triggered by")]); + + expected_status_pull_request.type = TS_MSG_TYPE_REQUEST; + expected_status_pull_request.code = expected_status_id_pull_request; + + thingset_msg_reset(request); + TEST_ASSERT_EQUAL_MESSAGE(0, ts_msg_len(request), &tc_desc[0]); + thingset_msg_reset(response); + TEST_ASSERT_EQUAL_MESSAGE(0, ts_msg_len(response), &tc_desc[0]); + + /* + * Create fetch request + * -------------------- + */ + if (object_path == NULL) { + ts_obj_db_oref_init(TEST_DB_ID_INSTANCE, &object_oref); + } + else { + ret = ts_obj_db_oref_by_path(TEST_DB_ID_INSTANCE, object_path, + (size_t)strlen(object_path), + &object_oref); + TEST_ASSERT_EQUAL_MESSAGE(0, ret, &tc_desc[0]); + } + if (test_object_id) { + TEST_ASSERT_TRUE_MESSAGE(ts_obj_db_oref_is_valid(object_oref), &tc_desc[0]); + object_id = ts_obj_id(object_oref); + object_path = 0; + } + else { + object_id = 0; + } + ts_obj_id_t *object_ids = NULL; + if (test_object_ids) { + for (uint16_t j = 0; j < object_count; j++) { + const char *name = object_names[j]; + size_t name_len = strlen(name); + int32_t parent_id = -1; + if (ts_obj_db_oref_is_valid(object_oref)) { + parent_id = (int32_t)ts_obj_id(object_oref); + } + thingset_oref_t fetch_oref; + ret = ts_obj_db_oref_by_name(TEST_DB_ID_INSTANCE, name, name_len, parent_id, + &fetch_oref); + TEST_ASSERT_EQUAL_MESSAGE(0, ret, &tc_desc[0]); + names_object_ids[j] = ts_obj_id(fetch_oref); + } + object_ids = &names_object_ids[0]; + object_names = NULL; + } + ret = ts_msg_add_request_fetch_cbor(request, object_id, object_path, + object_count, object_ids, object_names); + ts_msg_log(request, &test_log_buf[0], sizeof(test_log_buf)); + + /* Complete assert message */ + const char *object_name = ""; + ts_obj_id_t object_parent_id = 0; + uint8_t object_type = 0; + if (ts_obj_db_oref_is_valid(object_oref)) { + object_id = ts_obj_id(object_oref); + object_name = ts_obj_name(object_oref); + object_parent_id = ts_obj_parent_id(object_oref); + object_type = ts_obj_type(object_oref); + } + snprintf(&tc_desc[len], sizeof(tc_desc) - len - 1, + " - '%s', id: %u, type: %u, parent: %u -> %s", object_name, (unsigned int)object_id, + (unsigned int)object_type, (unsigned int)object_parent_id, &test_log_buf[0]); + + TEST_ASSERT_EQUAL_MESSAGE(expected_ret_add_request, ret, &tc_desc[0]); + if (expected_str_add_request != NULL) { + TEST_ASSERT_EQUAL_STRING_MESSAGE(expected_str_add_request, + &test_log_buf[0], &tc_desc[0]); + } + else { + TEST_ASSERT_EQUAL_MESSAGE(0, ts_msg_len(request), &tc_desc[0]); + } + + /* + * Pull fetch request + * ------------------ + */ + + /* Prepare assert message (again) */ + len = snprintf(&tc_desc[0], sizeof(tc_desc) - 1, "%s: ", assert_msg); + + thingset_oref_t request_oref = { .db_id = TEST_DB_ID_INSTANCE }; + ts_msg_auth_set(request, thingset_authorisation(&test_ts_instance)); + ret = ts_msg_pull_request_cbor(request, &request_oref); + + /* Complete assert message */ + object_name = ""; + if (ts_obj_db_oref_is_valid(request_oref)) { + object_id = ts_obj_id(request_oref); + object_name = ts_obj_name(request_oref); + object_parent_id = ts_obj_parent_id(request_oref); + object_type = ts_obj_type(request_oref); + } + snprintf(&tc_desc[len], sizeof(tc_desc) - len - 1, + " - '%s', id: %u, type: %u, parent: %u, status: 0x%02x", + object_name, (unsigned int)object_id, (unsigned int)object_type, + (unsigned int)object_parent_id, (unsigned int)ts_msg_status_code(request)); + + TEST_ASSERT_EQUAL_MESSAGE(expected_ret_pull_request, ret, &tc_desc[0]); + TEST_ASSERT_EQUAL_MESSAGE(expected_status_pull_request.type, ts_msg_status_type(request), + &tc_desc[0]); + TEST_ASSERT_EQUAL_MESSAGE(expected_status_pull_request.code, ts_msg_status_code(request), + &tc_desc[0]); + if (test_object_id) { + TEST_ASSERT_EQUAL_MESSAGE(object_id, ts_obj_id(object_oref), &tc_desc[0]); + } + TEST_ASSERT_EQUAL_UINT16_MESSAGE(thingset_authorisation(&test_ts_instance), + ts_msg_auth(request), &tc_desc[0]); + + /* + * Create fetch response + * --------------------- + */ + + /* Prepare assert message (again) */ + len = snprintf(&tc_desc[0], sizeof(tc_desc) - 1, "%s: ", assert_msg); + + ret = ts_msg_add_response_fetch_cbor(response, request_oref, request); + ts_msg_log(response, &test_log_buf[0], sizeof(test_log_buf)); + + /* Complete assert message */ + object_name = ""; + if (ts_obj_db_oref_is_valid(request_oref)) { + object_id = ts_obj_id(request_oref); + object_name = ts_obj_name(request_oref); + object_parent_id = ts_obj_parent_id(request_oref); + object_type = ts_obj_type(request_oref); + } + snprintf(&tc_desc[len], sizeof(tc_desc) - len - 1, + " - '%s', id: %u, type: %u, parent: %u, status: %u -> %s", + object_name, (unsigned int)object_id, (unsigned int)object_type, + (unsigned int)object_parent_id, (unsigned int)ts_msg_status_code(request), + &test_log_buf[0]); + + TEST_ASSERT_EQUAL_MESSAGE(expected_ret_add_response, ret, &tc_desc[0]); + if (expected_str_add_response != NULL) { + TEST_ASSERT_EQUAL_STRING_MESSAGE(expected_str_add_response, &test_log_buf[0], &tc_desc[0]); + } + else { + TEST_ASSERT_EQUAL_MESSAGE(0, ts_msg_len(response), &tc_desc[0]); + } +} + +void assert_msg_add_response_get_json(struct thingset_msg *response, struct thingset_msg *request, + ts_obj_id_t obj_id, int expected_ret, + const char *expected_str, const char *assert_msg) +{ + int ret; + thingset_oref_t object_oref; + char tc_desc[300]; + + /* Prepare assert message */ + int len = snprintf(&tc_desc[0], sizeof(tc_desc) - 1, "%s: ", assert_msg); + + LOG_DBG("%s", &tc_desc[sizeof("Assertion triggered by")]); + + ts_obj_db_oref_init(TEST_DB_ID_INSTANCE, &object_oref); + + thingset_msg_reset(response); + TEST_ASSERT_EQUAL_MESSAGE(0, ts_msg_len(response), &tc_desc[0]); + + ret = ts_obj_db_oref_by_id(TEST_DB_ID_INSTANCE, obj_id, &object_oref ); + TEST_ASSERT_EQUAL_MESSAGE(0, ret, &tc_desc[0]); + + ret = ts_msg_add_response_get_json(response, object_oref, request); + + /* Complete assert message */ + ts_msg_log(response, &test_log_buf[0], sizeof(test_log_buf)); + snprintf(&tc_desc[len], sizeof(tc_desc) - len - 1, + " -> ret: %d, oref: %u, name: '%s', >%s<", + ret, (unsigned int)object_oref.db_oid, ts_obj_name(object_oref), + &test_log_buf[0]); + + TEST_ASSERT_EQUAL_MESSAGE(expected_ret, ret, &tc_desc[0]); + TEST_ASSERT_EQUAL_STRING_LEN_MESSAGE(expected_str, ts_msg_data(response), + ts_msg_len(response), &tc_desc[0]); + TEST_ASSERT_EQUAL_MESSAGE(strlen(expected_str), ts_msg_len(response), &tc_desc[0]); +} diff --git a/test/test_ctx.c b/test/test_ctx.c new file mode 100644 index 0000000..c906b70 --- /dev/null +++ b/test/test_ctx.c @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2021 Bobby Noelte + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "test.h" + +/** + * @brief Test communication context + * + * This test verifies communication context usage: + * - THINGSET_COM_DEFINE() + * - thingset_init() + * - thingset_run() + */ +void test_ctx_com(void) +{ + int ret; + + /* Assure communication context is enabled for testing */ + TEST_ASSERT_TRUE(TS_CONFIG_COM); + + /* Assure "instance" test context static init */ + const struct ts_ctx *test_ts_instance = &TS_CAT(ts_ctx_, TEST_INSTANCE_LOCID); + struct ts_ctx_data *test_ts_instance_data = &TS_CAT(ts_ctx_com_data_, TEST_INSTANCE_LOCID).common; + const void *test_ts_instance_variant = &TS_CAT(ts_ctx_com_, TEST_INSTANCE_LOCID); + + TEST_ASSERT_EQUAL(1, TEST_INSTANCE_LOCID); + TEST_ASSERT_EQUAL_UINT8(TS_CTX_TYPE_COM, test_ts_instance->ctx_type); + TEST_ASSERT_EQUAL_UINT16(TEST_INSTANCE_LOCID, test_ts_instance->db_id); + TEST_ASSERT_EQUAL_PTR(test_ts_instance_data, test_ts_instance->data); + TEST_ASSERT_EQUAL_PTR(test_ts_instance_variant, test_ts_instance->variant); + + /* Assure specific context is correctly detected when used as generic context */ + TEST_ASSERT_TRUE(TS_CTX_IS_COM(TEST_INSTANCE_LOCID)); + + /* Assure specific context is correctly accessed by generic context functions */ + thingset_authorisation_set(TEST_INSTANCE_LOCID, 0xFFFF); + TEST_ASSERT_EQUAL_UINT16(0xFFFF, test_ts_instance_data->_auth_flags); + TEST_ASSERT_EQUAL_UINT16(0xFFFF, thingset_authorisation(TEST_INSTANCE_LOCID)); + + ret = thingset_init(TEST_INSTANCE_LOCID); + TEST_ASSERT_EQUAL(0, ret); + + ret = thingset_run(TEST_INSTANCE_LOCID); + TEST_ASSERT_EQUAL(0, ret); +} + +/** + * @brief Test communication context port table + * + * This test verifies port table usage: + * - static initialisation by + * - THINGSET_APP_DEFINE + * - THINGSET_PORT_DEFINE + */ +void test_ctx_com_port(void) +{ + /* Assure communication context is enabled for testing */ + TEST_ASSERT_TRUE(TS_CONFIG_COM); + + /* Assure device port table static init */ + TEST_ASSERT_EQUAL(TEST_PORT_COUNT, TS_CONFIG_PORT_COUNT); + + /* port 0 */ + TEST_ASSERT_EQUAL(0, TEST_APP_INSTANCE_PORTID); + TEST_ASSERT_EQUAL_PTR(&TS_CAT(ts_port_, TEST_APP_INSTANCE_PORTID), + ts_ports[TEST_APP_INSTANCE_PORTID]); + TEST_ASSERT_EQUAL_UINT8(TEST_INSTANCE_LOCID, TS_CAT(ts_port_, TEST_APP_INSTANCE_PORTID).loc_id); + TEST_ASSERT_EQUAL_STRING(TEST_APP_INSTANCE_NAME, + TS_CAT(ts_port_, TEST_APP_INSTANCE_PORTID).name); + TEST_ASSERT_EQUAL_PTR(&TS_CAT(ts_port_config_, TEST_APP_INSTANCE_PORTID), + TS_CAT(ts_port_, TEST_APP_INSTANCE_PORTID).config); + TEST_ASSERT_EQUAL_PTR(&TS_CAT(ts_port_data_, TEST_APP_INSTANCE_PORTID), + TS_CAT(ts_port_, TEST_APP_INSTANCE_PORTID).data); + TEST_ASSERT_EQUAL_PTR(&ts_app_port_api, TS_CAT(ts_port_, TEST_APP_INSTANCE_PORTID).api); + + /* port 1 */ + TEST_ASSERT_EQUAL(1, TEST_APP_NEIGHBOUR_PORTID); + TEST_ASSERT_EQUAL_PTR(&TS_CAT(ts_port_, TEST_APP_NEIGHBOUR_PORTID), + ts_ports[TEST_APP_NEIGHBOUR_PORTID]); + TEST_ASSERT_EQUAL_UINT8(TEST_NEIGHBOUR_LOCID, + TS_CAT(ts_port_, TEST_APP_NEIGHBOUR_PORTID).loc_id); + TEST_ASSERT_EQUAL_STRING(TEST_APP_NEIGHBOUR_NAME, + TS_CAT(ts_port_, TEST_APP_NEIGHBOUR_PORTID).name); + TEST_ASSERT_EQUAL_PTR(&TS_CAT(ts_port_config_, TEST_APP_NEIGHBOUR_PORTID), + TS_CAT(ts_port_, TEST_APP_NEIGHBOUR_PORTID).config); + TEST_ASSERT_EQUAL_PTR(&TS_CAT(ts_port_data_, TEST_APP_NEIGHBOUR_PORTID), + TS_CAT(ts_port_, TEST_APP_NEIGHBOUR_PORTID).data); + TEST_ASSERT_EQUAL_PTR(&ts_app_port_api, TS_CAT(ts_port_, TEST_APP_NEIGHBOUR_PORTID).api); + + /* port 2 */ + TEST_ASSERT_EQUAL(2, TEST_SHELL_PORTID); + TEST_ASSERT_EQUAL_PTR(&TS_CAT(ts_port_, TEST_SHELL_PORTID), ts_ports[TEST_SHELL_PORTID]); + TEST_ASSERT_EQUAL_UINT8(TEST_SHELL_LOCID, TS_CAT(ts_port_, TEST_SHELL_PORTID).loc_id); + TEST_ASSERT_EQUAL_STRING(TEST_SHELL_NAME, TS_CAT(ts_port_, TEST_SHELL_PORTID).name); + TEST_ASSERT_EQUAL_PTR(&ts_app_port_api, TS_CAT(ts_port_, TEST_SHELL_PORTID).api); + + + /* port 3 */ + TEST_ASSERT_EQUAL(3, TEST_PORT_LOOPBACK_A_PORTID); + TEST_ASSERT_EQUAL_PTR(&TS_CAT(ts_port_, TEST_PORT_LOOPBACK_A_PORTID), + ts_ports[TEST_PORT_LOOPBACK_A_PORTID]); + TEST_ASSERT_EQUAL_UINT8(TEST_PORT_LOOPBACK_A_LOCID, + TS_CAT(ts_port_, TEST_PORT_LOOPBACK_A_PORTID).loc_id); + TEST_ASSERT_EQUAL_STRING(TEST_PORT_LOOPBACK_A_NAME, + TS_CAT(ts_port_, TEST_PORT_LOOPBACK_A_PORTID).name); + TEST_ASSERT_EQUAL_PTR(&TS_CAT(ts_port_config_, TEST_PORT_LOOPBACK_A_PORTID), + TS_CAT(ts_port_, TEST_PORT_LOOPBACK_A_PORTID).config); + TEST_ASSERT_EQUAL_PTR(&TS_CAT(ts_port_data_, TEST_PORT_LOOPBACK_A_PORTID), + TS_CAT(ts_port_, TEST_PORT_LOOPBACK_A_PORTID).data); + TEST_ASSERT_EQUAL_PTR(&TS_CAT(loopback_simple, _api), + TS_CAT(ts_port_, TEST_PORT_LOOPBACK_A_PORTID).api); + + /* port 4 */ + TEST_ASSERT_EQUAL(4, TEST_PORT_LOOPBACK_B_PORTID); + TEST_ASSERT_EQUAL_PTR(&TS_CAT(ts_port_, TEST_PORT_LOOPBACK_B_PORTID), + ts_ports[TEST_PORT_LOOPBACK_B_PORTID]); + TEST_ASSERT_EQUAL_UINT8(TEST_PORT_LOOPBACK_B_LOCID, + TS_CAT(ts_port_, TEST_PORT_LOOPBACK_B_PORTID).loc_id); + TEST_ASSERT_EQUAL_STRING(TEST_PORT_LOOPBACK_B_NAME, + TS_CAT(ts_port_, TEST_PORT_LOOPBACK_B_PORTID).name); + TEST_ASSERT_EQUAL_PTR(&TS_CAT(ts_port_config_, TEST_PORT_LOOPBACK_B_PORTID), + TS_CAT(ts_port_, TEST_PORT_LOOPBACK_B_PORTID).config); + TEST_ASSERT_EQUAL_PTR(&TS_CAT(ts_port_data_, TEST_PORT_LOOPBACK_B_PORTID), + TS_CAT(ts_port_, TEST_PORT_LOOPBACK_B_PORTID).data); + TEST_ASSERT_EQUAL_PTR(&TS_CAT(loopback_simple, _api), + TS_CAT(ts_port_, TEST_PORT_LOOPBACK_B_PORTID).api); +} + +/** + * @brief Test communication context node table + * + * This test verifies node table usage: + * - ts_ctx_node_init_phantom() - called by ts_ctx_node_get() + * - ts_ctx_node_get() + * - ts_ctx_node_lookup() + */ +void test_ctx_com_node(void) +{ + int ret; + uint16_t node_idx; + + /* Prepare test context */ + + ret = thingset_init(TEST_INSTANCE_LOCID); + TEST_ASSERT_EQUAL(0, ret); + + ret = thingset_run(TEST_INSTANCE_LOCID); + TEST_ASSERT_EQUAL(0, ret); + + /* Run tests */ + + ret = ts_ctx_node_lookup(TEST_INSTANCE_LOCID, &test_uid_neighbour, &node_idx); + TEST_ASSERT_NOT_EQUAL(0, ret); + + node_idx = UINT16_MAX; + ret = ts_ctx_node_get(TEST_INSTANCE_LOCID, &test_uid_neighbour, &node_idx); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT16(0, node_idx); + + node_idx = UINT16_MAX; + ret = ts_ctx_node_lookup(TEST_INSTANCE_LOCID, &test_uid_neighbour, &node_idx); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT16(0, node_idx); + + node_idx = UINT16_MAX; + ret = ts_ctx_node_lookup(TEST_INSTANCE_LOCID, & + test_uid_instance, &node_idx); + TEST_ASSERT_NOT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT16(UINT16_MAX, node_idx); + + /* Get already existing device table entry */ + ret = ts_ctx_node_get(TEST_INSTANCE_LOCID, &test_uid_neighbour, &node_idx); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT16(0, node_idx); + + node_idx = UINT16_MAX; + ret = ts_ctx_node_lookup(TEST_INSTANCE_LOCID, &test_uid_neighbour, &node_idx); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT16(0, node_idx); + + struct ts_ctx_com_data *test_ts_instance_data = &TS_CAT(ts_ctx_com_data_, TEST_INSTANCE_LOCID); + + /* Fake node 0 to be the oldest one */ + test_ts_instance_data->node_table.nodes[0].last_seen_time = 0; + for (uint16_t i = 1; i < TS_ARRAY_SIZE(test_ts_instance_data->node_table.nodes); i++) { + test_ts_instance_data->node_table.nodes[i].last_seen_time = 1; + } + node_idx = ts_ctx_node_evict(TEST_INSTANCE_LOCID); + TEST_ASSERT_EQUAL_UINT16(0, node_idx); + + /* Free all nodes - silently ignore if node is already freed */ + for (uint16_t i = 1; i < TS_ARRAY_SIZE(test_ts_instance_data->node_table.nodes); i++) { + ts_ctx_node_free(TEST_INSTANCE_LOCID, i); + TEST_ASSERT_EQUAL_UINT16(THINGSET_PORT_ID_INVALID, + test_ts_instance_data->node_table.nodes[i].port_id); + } + + /* Aquire all nodes */ + thingset_uid_t node_ids[TS_ARRAY_SIZE(test_ts_instance_data->node_table.nodes)]; + for (uint16_t i = 1; i < TS_ARRAY_SIZE(test_ts_instance_data->node_table.nodes); i++) { + node_ids[i] = i; + ret = ts_ctx_node_get(TEST_INSTANCE_LOCID, &node_ids[i], &node_idx); + TEST_ASSERT_EQUAL(0, ret); + } + /* Additional aquire should not fail */ + ret = ts_ctx_node_get(TEST_INSTANCE_LOCID, &test_uid_neighbour, &node_idx); + TEST_ASSERT_EQUAL(0, ret); +} + +/** + * @brief Test core context + */ +void test_ctx_core(void) +{ + /* Assure core context is enabled for testing */ + TEST_ASSERT_TRUE(TS_CONFIG_CORE); + + /* Assure "core" test context static init */ + const struct ts_ctx *test_ts_core = &TS_CAT(ts_ctx_, TEST_CORE_LOCID); + struct ts_ctx_data *test_ts_core_data = &TS_CAT(ts_ctx_core_data_, TEST_CORE_LOCID).common; + const void *test_ts_core_variant = &TS_CAT(ts_ctx_core_, TEST_CORE_LOCID); + + TEST_ASSERT_EQUAL(0, TEST_CORE_LOCID); + TEST_ASSERT_EQUAL_UINT8(TS_CTX_TYPE_CORE, test_ts_core->ctx_type); + TEST_ASSERT_EQUAL_UINT16(TEST_CORE_LOCID, test_ts_core->db_id); + TEST_ASSERT_EQUAL_PTR(test_ts_core_data, test_ts_core->data); + TEST_ASSERT_EQUAL_PTR(test_ts_core_variant, test_ts_core->variant); + + /* Assure specific context is correctly detected when used as generic context */ + TEST_ASSERT_TRUE(TS_CTX_IS_CORE(TEST_CORE_LOCID)); + + /* Assure specific context is correctly accessed by generic context functions */ + thingset_authorisation_set(TEST_CORE_LOCID, 0xFFFF); + TEST_ASSERT_EQUAL_UINT16(0xFFFF, test_ts_core->data->_auth_flags); + TEST_ASSERT_EQUAL_UINT16(0xFFFF, thingset_authorisation(TEST_CORE_LOCID)); + + int ret = thingset_init(TEST_CORE_LOCID); + TEST_ASSERT_EQUAL(0, ret); +} + +void tests_ctx(void) +{ + UNITY_BEGIN(); + + // Test environment + RUN_TEST(test_ctx_core); + RUN_TEST(test_ctx_com); + RUN_TEST(test_ctx_com_port); + RUN_TEST(test_ctx_com_node); + + UNITY_END(); +} diff --git a/test/test_jsmn.c b/test/test_jsmn.c new file mode 100644 index 0000000..bba209c --- /dev/null +++ b/test/test_jsmn.c @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2021 Bobby Noelte + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "../src/ts_jsmn.h" + +#include "test.h" + +#define TEST_JSMN_NUM_TOKEN 100 + +/** + * @brief Test JSMN JSON parser + * + * This test verifies the JSMN JSON parser usage: + * - ts_jsmn_init() + * - ts_jsmn_parse() + * - ts_jsmn_token_by_index() + * - ts_jsmn_dump() + * + */ +void test_jsmn(void) +{ + int ret; + char test_dump[1000]; + uint8_t test_jsmn_context_buffer[sizeof(struct ts_jsmn_context) + + (TEST_JSMN_NUM_TOKEN * sizeof(struct ts_jsmn_token))]; + const char *test_js = "[1,2,3,{\"a\":\"b\"},[4.56,\"cd\",[7.8,-9e10]]],{\"f\":12}"; + const char *test_js_expect = +"#00: ARRAY 5 '[1,2,3,{\"a\":\"b\"},[4.56,\"cd\",[7.8,-9e10]]]'\n" +"#01: PRIMITIV 0 '1'\n" +"#02: PRIMITIV 0 '2'\n" +"#03: PRIMITIV 0 '3'\n" +"#04: OBJECT 1 '{\"a\":\"b\"}'\n" +"#05: STRING 1 'a'\n" +"#06: STRING 0 'b'\n" +"#07: ARRAY 3 '[4.56,\"cd\",[7.8,-9e10]]'\n" +"#08: PRIMITIV 0 '4.56'\n" +"#09: STRING 0 'cd'\n" +"#10: ARRAY 2 '[7.8,-9e10]'\n" +"#11: PRIMITIV 0 '7.8'\n" +"#12: PRIMITIV 0 '-9e10'\n" +"#13: OBJECT 1 '{\"f\":12}'\n" +"#14: STRING 1 'f'\n" +"#15: PRIMITIV 0 '12'\n"; + + struct ts_jsmn_context *test_jsmn_ctx = TS_JSMN_CONTEXT(test_jsmn_context_buffer); + uint16_t test_jsmn_num_tokens = TS_JSMN_CONTEXT_NUM_TOKEN(test_jsmn_context_buffer); + uint16_t test_js_len = strlen(test_js); + + TEST_ASSERT_EQUAL_size_t(4, sizeof(struct ts_jsmn_token)); + TEST_ASSERT_EQUAL_UINT16(TEST_JSMN_NUM_TOKEN, test_jsmn_num_tokens); + TEST_ASSERT_EQUAL_UINT16(50, test_js_len); + + ret = ts_jsmn_init(test_jsmn_ctx, test_jsmn_num_tokens); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_jsmn_token_count(test_jsmn_ctx); + TEST_ASSERT_EQUAL(0, ret); /* number of tokens */ + + ret = ts_jsmn_parse(test_jsmn_ctx, test_js, test_js_len); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_jsmn_token_count(test_jsmn_ctx); + TEST_ASSERT_EQUAL(16, ret); /* number of tokens */ + + for(uint16_t i = 0; i < test_jsmn_ctx->num_tokens; i++) { + uint16_t tok_type; + uint16_t tok_size; + const char *tok_start; + uint16_t tok_len; + int ret = ts_jsmn_token_by_index(test_jsmn_ctx, i, &tok_type, &tok_size, &tok_start, + &tok_len); + if (ret != 0) { + TEST_ASSERT_GREATER_OR_EQUAL_UINT16(16, i); + } + else { + TEST_ASSERT_LESS_THAN_UINT16(16, i); + TEST_ASSERT_NOT_NULL(tok_start); + TEST_ASSERT_LESS_THAN_UINT64((uintptr_t)test_js + test_js_len, (uintptr_t)tok_start); + TEST_ASSERT_GREATER_OR_EQUAL_UINT64((uintptr_t)test_js, (uintptr_t)tok_start); + TEST_ASSERT_NOT_EQUAL_UINT16(0, tok_len); + TEST_ASSERT_LESS_THAN_UINT16(test_js_len, tok_len); + } + } + + ts_jsmn_dump(test_jsmn_ctx, &test_dump[0], sizeof(test_dump)); + TEST_ASSERT_EQUAL_STRING(test_js_expect, &test_dump[0]); +} + +void tests_jsmn(void) +{ + UNITY_BEGIN(); + + // Operating system abstraction + RUN_TEST(test_jsmn); + + UNITY_END(); +} diff --git a/test/test_msg.c b/test/test_msg.c new file mode 100644 index 0000000..68ec01a --- /dev/null +++ b/test/test_msg.c @@ -0,0 +1,2249 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "test.h" + +#include + +void test_msg_alloc(void) +{ + int ret; + struct thingset_msg *msg = NULL; + + /* Check buffer pool size for testing */ + TEST_ASSERT_GREATER_OR_EQUAL_size_t(16, TS_CONFIG_BUF_COUNT); + TEST_ASSERT_GREATER_OR_EQUAL_size_t(1024, TS_CONFIG_BUF_DATA_SIZE); + + ret = thingset_msg_alloc(128, 10, &msg); + TEST_ASSERT_EQUAL(0, ret); + + thingset_msg_unref(msg); +} + +void test_msg_add_primitives(void) +{ + int ret; + struct thingset_msg *msg = NULL; + float val_f32; + bool val_bool; + uint16_t val_u16; + uint32_t val_u32; + uint64_t val_u64; + const char *val_str; + int16_t val_i16; + int32_t val_i32; + int64_t val_i64; + CborType cbor_type; + + ret = thingset_msg_alloc(32, 10, &msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + /* + * Values - raw + * ------------ + */ + + ret = ts_msg_add_u8(msg, (uint8_t)'1'); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(1, ts_msg_len(msg)); + + ret = ts_msg_add_u8(msg, (uint8_t)'2'); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(2, ts_msg_len(msg)); + + ret = ts_msg_add_mem(msg, (const uint8_t *)&"3456", 4); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_STRING_LEN("123456", ts_msg_data(msg), ts_msg_len(msg)); + TEST_ASSERT_EQUAL(6, ts_msg_len(msg)); + + /* + * Bool - JSON + * ----------- + */ + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_bool_json(msg, true); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_STRING_LEN("true", ts_msg_data(msg), ts_msg_len(msg)); + TEST_ASSERT_EQUAL(4, ts_msg_len(msg)); + + ret = ts_msg_json_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_bool_json(msg, &val_bool); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(true, val_bool); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_bool_json(msg, false); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_STRING_LEN("false", ts_msg_data(msg), ts_msg_len(msg)); + TEST_ASSERT_EQUAL(5, ts_msg_len(msg)); + + ret = ts_msg_json_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_bool_json(msg, &val_bool); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(false, val_bool); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_u8(msg, (uint8_t)'0'); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(1, ts_msg_len(msg)); + + ret = ts_msg_json_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_bool_json(msg, &val_bool); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(false, val_bool); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_mem(msg, (const uint8_t *)"12345", 5); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(5, ts_msg_len(msg)); + + ret = ts_msg_json_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_bool_json(msg, &val_bool); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(true, val_bool); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + /* + * DecFrac - JSON + * -------------- + */ + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_decfrac_json(msg, INT32_MAX, INT16_MAX); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_json_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_decfrac_json(msg, &val_i32, &val_i16); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_INT32(INT32_MAX, val_i32); + TEST_ASSERT_EQUAL_INT16(INT16_MAX, val_i16); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_decfrac_json(msg, INT32_MIN, INT16_MIN); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_json_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_decfrac_json(msg, &val_i32, &val_i16); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_INT32(INT32_MIN, val_i32); + TEST_ASSERT_EQUAL_INT16(INT16_MIN, val_i16); + + /* + * Float - JSON + * ------------ + */ + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_decfrac_json(msg, INT32_MAX, INT16_MAX); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_STRING_LEN("2147483647e32767", ts_msg_data(msg), ts_msg_len(msg)); + TEST_ASSERT_EQUAL(16, ts_msg_len(msg)); + + ret = ts_msg_json_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_decfrac_json(msg, &val_i32, &val_i16); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_INT32(INT32_MAX, val_i32); + TEST_ASSERT_EQUAL_INT16(INT16_MAX, val_i16); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_decfrac_json(msg, INT32_MIN, INT16_MIN); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_STRING_LEN("-2147483648e-32768", ts_msg_data(msg), ts_msg_len(msg)); + TEST_ASSERT_EQUAL(18, ts_msg_len(msg)); + + ret = ts_msg_json_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_decfrac_json(msg, &val_i32, &val_i16); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_INT32(INT32_MIN, val_i32); + TEST_ASSERT_EQUAL_INT16(INT16_MIN, val_i16); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_f32_json(msg, 123.45678, 5); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_STRING_LEN("123.45678", ts_msg_data(msg), ts_msg_len(msg)); + TEST_ASSERT_EQUAL(9, ts_msg_len(msg)); + + ret = ts_msg_json_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_f32_json(msg, &val_f32); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_FLOAT(123.45678, val_f32); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_f32_json(msg, 1.0/0.0, 5); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_STRING_LEN("null", ts_msg_data(msg), ts_msg_len(msg)); + TEST_ASSERT_EQUAL(4, ts_msg_len(msg)); + + ret = ts_msg_json_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_f32_json(msg, &val_f32); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_FLOAT(NAN, val_f32); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_f32_json(msg, 0.0/0.0, 5); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_STRING_LEN("null", ts_msg_data(msg), ts_msg_len(msg)); + TEST_ASSERT_EQUAL(4, ts_msg_len(msg)); + + ret = ts_msg_json_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_f32_json(msg, &val_f32); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_FLOAT(NAN, val_f32); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + /* + * Signed Integer - JSON + * --------------------- + */ + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_i16_json(msg, INT16_MAX); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_STRING_LEN("32767", ts_msg_data(msg), ts_msg_len(msg)); + TEST_ASSERT_EQUAL(5, ts_msg_len(msg)); + + ret = ts_msg_json_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_i16_json(msg, &val_i16); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_INT16(INT16_MAX, val_i16); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_i16_json(msg, INT16_MIN); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_STRING_LEN("-32768", ts_msg_data(msg), ts_msg_len(msg)); + TEST_ASSERT_EQUAL(6, ts_msg_len(msg)); + + ret = ts_msg_json_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_i16_json(msg, &val_i16); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_INT16(INT16_MIN, val_i16); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_i32_json(msg, INT32_MAX); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_STRING_LEN("2147483647", ts_msg_data(msg), ts_msg_len(msg)); + TEST_ASSERT_EQUAL(10, ts_msg_len(msg)); + + ret = ts_msg_json_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_i32_json(msg, &val_i32); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_INT32(INT32_MAX, val_i32); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_i32_json(msg, INT32_MIN); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_STRING_LEN("-2147483648", ts_msg_data(msg), ts_msg_len(msg)); + TEST_ASSERT_EQUAL(11, ts_msg_len(msg)); + + ret = ts_msg_json_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_i32_json(msg, &val_i32); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_INT32(INT32_MIN, val_i32); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_i64_json(msg, INT64_MAX); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_STRING_LEN("9223372036854775807", ts_msg_data(msg), ts_msg_len(msg)); + TEST_ASSERT_EQUAL(19, ts_msg_len(msg)); + + ret = ts_msg_json_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_i64_json(msg, &val_i64); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_INT64(INT64_MAX, val_i64); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_i64_json(msg, INT64_MIN); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_STRING_LEN("-9223372036854775808", ts_msg_data(msg), ts_msg_len(msg)); + TEST_ASSERT_EQUAL(20, ts_msg_len(msg)); + + ret = ts_msg_json_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_i64_json(msg, &val_i64); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_INT64(INT64_MIN, val_i64); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + /* + * String - JSON + * ------------- + */ + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + const char *exp_str = "Test JSON String"; + ret = ts_msg_add_string_json(msg, exp_str); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_json_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_string_json(msg, &val_str, &val_u16); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT16(strlen(exp_str), val_u16); + TEST_ASSERT_EQUAL_STRING_LEN(exp_str, val_str, val_u16); + + /* + * Unsigned Integer - JSON + * ----------------------- + */ + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_u16_json(msg, UINT16_MAX); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_STRING_LEN("65535", ts_msg_data(msg), ts_msg_len(msg)); + TEST_ASSERT_EQUAL(5, ts_msg_len(msg)); + + ret = ts_msg_json_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_u16_json(msg, &val_u16); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT16(UINT16_MAX, val_u16); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_u16_json(msg, 0); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_STRING_LEN("0", ts_msg_data(msg), ts_msg_len(msg)); + TEST_ASSERT_EQUAL(1, ts_msg_len(msg)); + + ret = ts_msg_json_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_u16_json(msg, &val_u16); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT16(0, val_u16); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_u32_json(msg, UINT32_MAX); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_STRING_LEN("4294967295", ts_msg_data(msg), ts_msg_len(msg)); + TEST_ASSERT_EQUAL(10, ts_msg_len(msg)); + + ret = ts_msg_json_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_u32_json(msg, &val_u32); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT32(UINT32_MAX, val_u32); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_u32_json(msg, 0); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_STRING_LEN("0", ts_msg_data(msg), ts_msg_len(msg)); + TEST_ASSERT_EQUAL(1, ts_msg_len(msg)); + + ret = ts_msg_json_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_u32_json(msg, &val_u32); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT32(0, val_u32); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_u64_json(msg, UINT64_MAX); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_STRING_LEN("18446744073709551615", ts_msg_data(msg), ts_msg_len(msg)); + TEST_ASSERT_EQUAL(20, ts_msg_len(msg)); + + ret = ts_msg_json_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_u64_json(msg, &val_u64); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT64(UINT64_MAX, val_u64); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_u64_json(msg, 0); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_STRING_LEN("0", ts_msg_data(msg), ts_msg_len(msg)); + TEST_ASSERT_EQUAL(1, ts_msg_len(msg)); + + ret = ts_msg_json_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_u64_json(msg, &val_u64); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT64(0, val_u64); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + /* + * Array - CBOR + * ----------------------- + */ + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_cbor_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_array_cbor(msg, 2); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT16(1, ts_msg_len(msg)); + + val_bool = false; + ret = ts_msg_add_bool_cbor(msg, val_bool); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT16(2, ts_msg_len(msg)); + + val_bool = true; + ret = ts_msg_add_bool_cbor(msg, val_bool); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT16(3, ts_msg_len(msg)); + + ret = ts_msg_add_array_end_cbor(msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT16(3, ts_msg_len(msg)); + + val_bool = false; + ret = ts_msg_add_bool_cbor(msg, val_bool); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT16(4, ts_msg_len(msg)); + + ret = ts_msg_cbor_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_bool_cbor(msg, &val_bool); + TEST_ASSERT_EQUAL(-EINVAL, ret); + + ret = ts_msg_pull_type_cbor(msg, &cbor_type); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(CborArrayType, cbor_type); + + ret = ts_msg_pull_array_cbor(msg, &val_u16); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(2, val_u16); + + ret = ts_msg_pull_array_end_cbor(msg); + TEST_ASSERT_EQUAL(-EAGAIN, ret); + + ret = ts_msg_pull_bool_cbor(msg, &val_bool); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(false, val_bool); + + ret = ts_msg_pull_bool_cbor(msg, &val_bool); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(true, val_bool); + + ret = ts_msg_pull_array_end_cbor(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_bool_cbor(msg, &val_bool); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(false, val_bool); + TEST_ASSERT_EQUAL_UINT16(0, ts_msg_len(msg)); + + ret = ts_msg_pull_bool_cbor(msg, &val_bool); + TEST_ASSERT_EQUAL(-ENOMEM, ret); + + /* + * DecFrac - CBOR + * -------------- + */ + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_cbor_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_decfrac_cbor(msg, INT32_MAX, INT16_MAX); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_cbor_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_decfrac_cbor(msg, &val_i32, &val_i16); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_INT32(INT32_MAX, val_i32); + TEST_ASSERT_EQUAL_INT16(INT16_MAX, val_i16); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_cbor_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_decfrac_cbor(msg, INT32_MIN, INT16_MIN); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_cbor_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_decfrac_cbor(msg, &val_i32, &val_i16); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_INT32(INT32_MIN, val_i32); + TEST_ASSERT_EQUAL_INT16(INT16_MIN, val_i16); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_cbor_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_decfrac_cbor(msg, 123, 456); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_cbor_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_decfrac_cbor(msg, &val_i32, &val_i16); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_INT32(123, val_i32); + TEST_ASSERT_EQUAL_INT16(456, val_i16); + + /* + * Float - CBOR + * ----------------------- + */ + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_cbor_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + val_u16 = ts_msg_len(msg); + ret = ts_msg_add_f32_cbor(msg, 123.45678, 5); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_GREATER_THAN_UINT16(val_u16, ts_msg_len(msg)); + TEST_ASSERT_EQUAL_UINT16(5, ts_msg_len(msg)); + + val_u16 = ts_msg_len(msg); + ret = ts_msg_add_f32_cbor(msg, 123.45678, 0); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_GREATER_THAN_UINT16(val_u16, ts_msg_len(msg)); + TEST_ASSERT_EQUAL_UINT16(7, ts_msg_len(msg)); + + ret = ts_msg_cbor_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + val_u16 = ts_msg_len(msg); + ret = ts_msg_pull_f32_cbor(msg, &val_f32); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_FLOAT(123.45678, val_f32); + TEST_ASSERT_LESS_THAN_UINT16(val_u16, ts_msg_len(msg)); + + val_u16 = ts_msg_len(msg); + ret = ts_msg_pull_f32_cbor(msg, &val_f32); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_FLOAT(123.0, val_f32); + TEST_ASSERT_LESS_THAN_UINT16(val_u16, ts_msg_len(msg)); + + /* + * Signed Integer - CBOR + * --------------------- + */ + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_cbor_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_i16_cbor(msg, INT16_MAX); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(3, ts_msg_len(msg)); + + ret = ts_msg_cbor_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_i16_cbor(msg, &val_i16); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_INT16(INT16_MAX, val_i16); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_cbor_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_i16_cbor(msg, INT16_MIN); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(3, ts_msg_len(msg)); + + ret = ts_msg_cbor_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_i16_cbor(msg, &val_i16); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_INT16(INT16_MIN, val_i16); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_cbor_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_i32_cbor(msg, INT32_MAX); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(5, ts_msg_len(msg)); + + ret = ts_msg_cbor_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_i32_cbor(msg, &val_i32); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_INT32(INT32_MAX, val_i32); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_cbor_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_i32_cbor(msg, INT32_MIN); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(5, ts_msg_len(msg)); + + ret = ts_msg_cbor_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_i32_cbor(msg, &val_i32); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_INT32(INT32_MIN, val_i32); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_cbor_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_i64_cbor(msg, INT64_MAX); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(9, ts_msg_len(msg)); + + ret = ts_msg_cbor_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_i64_cbor(msg, &val_i64); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_INT64(INT64_MAX, val_i64); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_cbor_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_i64_cbor(msg, INT64_MIN); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(9, ts_msg_len(msg)); + + ret = ts_msg_cbor_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_i64_cbor(msg, &val_i64); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_INT64(INT64_MIN, val_i64); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + /* + * String - CBOR + * ------------- + */ + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_cbor_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_string_cbor(msg, "12345"); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT16(6, ts_msg_len(msg)); + + ret = ts_msg_add_string_cbor(msg, "67890"); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT16(12, ts_msg_len(msg)); + + ret = ts_msg_cbor_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_string_cbor(msg, &val_str ,&val_u16); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT16(5, val_u16); + TEST_ASSERT_EQUAL_STRING_LEN("12345", val_str, 5); + + ret = ts_msg_pull_string_cbor(msg, &val_str ,&val_u16); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT16(5, val_u16); + TEST_ASSERT_EQUAL_STRING_LEN("67890", val_str, 5); + + /* + * Byte string - CBOR + * ------------------ + */ + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_cbor_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_mem_cbor(msg, (uint8_t *)"12345", 5); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT16(6, ts_msg_len(msg)); + + ret = ts_msg_cbor_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + const uint8_t *val_u8_ptr; + ret = ts_msg_pull_mem_cbor(msg, &val_u8_ptr ,&val_u16); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT16(5, val_u16); + TEST_ASSERT_EQUAL_HEX8_ARRAY((uint8_t *)"12345", val_u8_ptr, 5); + + /* + * Unsigned Integer - CBOR + * ----------------------- + */ + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_cbor_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_u16_cbor(msg, UINT16_MAX); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(3, ts_msg_len(msg)); + + ret = ts_msg_cbor_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_u16_cbor(msg, &val_u16); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT16(UINT16_MAX, val_u16); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_cbor_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_u16_cbor(msg, 0); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(1, ts_msg_len(msg)); + + ret = ts_msg_cbor_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_u16_cbor(msg, &val_u16); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT16(0, val_u16); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_cbor_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_u32_cbor(msg, UINT32_MAX); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(5, ts_msg_len(msg)); + + ret = ts_msg_cbor_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_u32_cbor(msg, &val_u32); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT32(UINT32_MAX, val_u32); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_cbor_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_u32_cbor(msg, 0); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(1, ts_msg_len(msg)); + + ret = ts_msg_cbor_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_u32_cbor(msg, &val_u32); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT32(0, val_u32); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_cbor_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_u64_cbor(msg, UINT64_MAX); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(9, ts_msg_len(msg)); + + ret = ts_msg_cbor_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_u64_cbor(msg, &val_u64); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT64(UINT64_MAX, val_u64); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = ts_msg_cbor_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_add_u64_cbor(msg, 0); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(1, ts_msg_len(msg)); + + ret = ts_msg_cbor_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_u64_cbor(msg, &val_u64); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT64(0, val_u64); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + thingset_msg_unref(msg); +} + +void test_msg_add_response_cbor(void) +{ + int ret; + struct thingset_msg *msg = NULL; + struct thingset_msg *big_msg = NULL; + struct thingset_msg *request = NULL; + + ret = thingset_msg_alloc_cbor(64, 10, &msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + ret = thingset_msg_alloc_cbor(160, 10, &big_msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(0, ts_msg_len(big_msg)); + ret = thingset_msg_alloc_cbor(64, 10, &request); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + /* + * ts_msg_add_response_status_cbor + * ------------------------------- + */ + + thingset_msg_reset(msg); + + ret = ts_msg_cbor_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_RESPONSE, + TS_MSG_CODE_CHANGED); + ret = ts_msg_add_response_status_cbor(msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT8(TS_MSG_CODE_CHANGED, *ts_msg_data(msg)); + + /* + * ts_msg_add_response_get_cbor + * ---------------------------- + */ + + thingset_msg_reset(request); + ts_msg_status_set(request, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_REQUEST, 0); + + ts_msg_status_code_set(request, TS_MSG_CODE_REQUEST_GET_VALUES); + TEST_ASSERT_MSG_ADD_RESPONSE_GET_CBOR("GET - measurement values", + msg, request, "meas", 0, + "h'0000',bin-reponse(h'85'),[14.100000381469727f, 5.130000114440918f, 22]"); + + ts_msg_status_code_set(request, TS_MSG_CODE_REQUEST_GET_IDS); + TEST_ASSERT_MSG_ADD_RESPONSE_GET_CBOR("GET - measurement values ids", + msg, request, "meas", 0, "h'0000',bin-reponse(h'85'),[113, 114, 115]"); + + ts_msg_status_code_set(request, TS_MSG_CODE_REQUEST_GET_NAMES_VALUES); + TEST_ASSERT_MSG_ADD_RESPONSE_GET_CBOR("GET - measurement names/values", + msg, request, "meas", 0, + "h'0000',bin-reponse(h'85'),{\"Bat_V\": 14.100000381469727f, " + "\"Bat_A\": 5.130000114440918f, \"Ambient_degC\": 22}"); + + ts_msg_status_code_set(request, TS_MSG_CODE_REQUEST_GET_VALUES); + TEST_ASSERT_MSG_ADD_RESPONSE_GET_CBOR("GET - missing object - return error", + msg, request, 0, 0, + "h'0000',bin-reponse(h'a0')"); + + ts_msg_status_code_set(request, TS_MSG_CODE_REQUEST_GET_NAMES_VALUES); + TEST_ASSERT_MSG_ADD_RESPONSE_GET_CBOR("GET - object with parent", + msg, request, ".pub/report", 0, + "h'0000',bin-reponse(h'85'),{\"Enable\": false, \"Interval_ms\": 1000}"); + + /* + * ts_msg_add_response_fetch_cbor + * ------------------------------ + * + * Full round: + * ts_msg_add_request_fetch_cbor + * -> ts_msg_pull_request_cbor + * -> ts_msg_add_response_fetch_cbor + */ + + const char * tc_response_fetch_cbor_object_names[] = { + "ui64", "i64", "ui32", "i32", "ui16", "i16", "f32", "bool", "strbuf" + }; + const char * tc_response_fetch_cbor_float_array_names[] = { + "arrayfloat", + }; + const char * tc_response_fetch_cbor_rounded_float_names[] = { + "f32_rounded", + }; + + /* For testing we need full access authorisation to objects */ + thingset_authorisation_set(TEST_INSTANCE_LOCID, TS_ANY_RW); + TEST_ASSERT_EQUAL_UINT16(TS_ANY_RW, thingset_authorisation(TEST_INSTANCE_LOCID)); + + TEST_ASSERT_MSG_FETCH_CBOR("FETCH meas - ids by id", msg, big_msg, + "meas", + true, false, 0, NULL, + 0, "h'0000',bin-fetch(h'05'),2,undefined", + 0, TS_MSG_CODE_REQUEST_FETCH_IDS, + 0, "h'0000',bin-reponse(h'85'),[113, 114, 115]"); + + TEST_ASSERT_MSG_FETCH_CBOR("FETCH meas - names by id", msg, big_msg, + "meas", + false, false, 0, NULL, + 0, "h'0000',bin-fetch(h'05'),\"meas\",undefined", + 0, TS_MSG_CODE_REQUEST_FETCH_NAMES, + 0, "h'0000',bin-reponse(h'85'),[\"Bat_V\", \"Bat_A\", \"Ambient_degC\"]"); + + TEST_ASSERT_MSG_FETCH_CBOR("FETCH conf - single rounded float by id", msg, big_msg, + "conf", + true, true, TS_ARRAY_SIZE(tc_response_fetch_cbor_rounded_float_names), + &tc_response_fetch_cbor_rounded_float_names[0], + 0, "h'0000',bin-fetch(h'05'),6,24586", + 0, TS_MSG_CODE_REQUEST_FETCH_SINGLE, + 0, "h'0000',bin-reponse(h'85'),8"); + + TEST_ASSERT_MSG_FETCH_CBOR("FETCH conf - single float array by id", msg, big_msg, + "conf", + true, true, TS_ARRAY_SIZE(tc_response_fetch_cbor_float_array_names), + &tc_response_fetch_cbor_float_array_names[0], + 0, "h'0000',bin-fetch(h'05'),6,28676", + 0, TS_MSG_CODE_REQUEST_FETCH_SINGLE, + 0, "h'0000',bin-reponse(h'85'),[2.2699999809265137f, 3.440000057220459f]"); + + TEST_ASSERT_MSG_FETCH_CBOR("FETCH conf - multiple objects by id", msg, big_msg, + "conf", + true, true, TS_ARRAY_SIZE(tc_response_fetch_cbor_object_names), + &tc_response_fetch_cbor_object_names[0], + 0, "h'0000'," + "bin-fetch(h'05'),6,[24577, 24578, 24579, 24580, 24581, 24582, 24583, 24584, 24585]", + 0, TS_MSG_CODE_REQUEST_FETCH_VALUES, + 0, "h'0000'," + "bin-reponse(h'85'),[1, 2, 3, 4, 5, 6, 8.3999996185302734f, true, \"test\"]"); + + TEST_ASSERT_MSG_FETCH_CBOR("FETCH conf - multiple objects by name", msg, big_msg, + "conf", + false, false, TS_ARRAY_SIZE(tc_response_fetch_cbor_object_names), + &tc_response_fetch_cbor_object_names[0], + 0, "h'0000'," + "bin-fetch(h'05'),\"conf\",[\"ui64\", \"i64\", \"ui32\", \"i32\", \"ui16\", " + "\"i16\", \"f32\", \"bool\", \"strbuf\"]", + 0, TS_MSG_CODE_REQUEST_FETCH_VALUES, + 0, "h'0000'," + "bin-reponse(h'85'),[1, 2, 3, 4, 5, 6, 8.3999996185302734f, true, \"test\"]"); + + thingset_msg_unref(msg); + thingset_msg_unref(big_msg); + thingset_msg_unref(request); +} + +void test_msg_add_response_json(void) +{ + int ret; + struct thingset_msg *msg = NULL; + struct thingset_msg *request = NULL; + thingset_oref_t object_oref; + const char *req_str; + const char *resp_str; + + object_oref = ts_obj_db_oref_any(ts_ctx_obj_db(TEST_INSTANCE_LOCID)); + + ret = thingset_msg_alloc_json(64, 10, &msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + ret = thingset_msg_alloc_json(64, 10, &request); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(0, ts_msg_len(request)); + + + /* + * ts_msg_add_response_status_json + * ------------------------------- + */ + + thingset_msg_reset(msg); + + ret = ts_msg_json_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + + const struct { + ts_msg_status_code_t code; + const char *expected; + } tc_response_status_json[] = { +#if TS_CONFIG_VERBOSE_STATUS_MESSAGES + {.code = TS_STATUS_CREATED, .expected = ":81 Created."}, + {.code = TS_STATUS_DELETED, .expected = ":82 Deleted."}, + {.code = TS_STATUS_VALID, .expected = ":83 Valid."}, + {.code = TS_STATUS_CHANGED, .expected = ":84 Changed."}, + {.code = TS_STATUS_CONTENT, .expected = ":85 Content."}, + {.code = TS_STATUS_BAD_REQUEST, .expected = ":A0 Bad Request."}, + {.code = TS_STATUS_UNAUTHORIZED, .expected = ":A1 Unauthorized."}, + {.code = TS_STATUS_FORBIDDEN, .expected = ":A3 Forbidden."}, + {.code = TS_STATUS_NOT_FOUND, .expected = ":A4 Not Found."}, + {.code = TS_STATUS_METHOD_NOT_ALLOWED, .expected = ":A5 Method Not Allowed."}, + {.code = TS_STATUS_REQUEST_INCOMPLETE, .expected = ":A8 Request Entity Incomplete."}, + {.code = TS_STATUS_CONFLICT, .expected = ":A9 Conflict."}, + {.code = TS_STATUS_REQUEST_TOO_LARGE, .expected = ":AD Request Entity Too Large."}, + {.code = TS_STATUS_UNSUPPORTED_FORMAT, .expected = ":AF Unsupported Content-Format."}, + {.code = TS_STATUS_INTERNAL_SERVER_ERR, .expected = ":C0 Internal Server Error."}, + {.code = TS_STATUS_NOT_IMPLEMENTED, .expected = ":C1 Not Implemented."}, + {.code = TS_STATUS_RESPONSE_TOO_LARGE, .expected = ":E1 Response too large."}, + {.code = 0x0FF, .expected = ":FF Error."}, +#else + {.code = TS_STATUS_CREATED, .expected = ":81."}, + {.code = TS_STATUS_DELETED, .expected = ":82."}, + {.code = TS_STATUS_VALID, .expected = ":83."}, + {.code = TS_STATUS_CHANGED, .expected = ":84."}, + {.code = TS_STATUS_CONTENT, .expected = ":85."}, + {.code = TS_STATUS_BAD_REQUEST, .expected = ":A0."}, + {.code = TS_STATUS_UNAUTHORIZED, .expected = ":A1."}, + {.code = TS_STATUS_FORBIDDEN, .expected = ":A3."}, + {.code = TS_STATUS_NOT_FOUND, .expected = ":A4."}, + {.code = TS_STATUS_METHOD_NOT_ALLOWED, .expected = ":A5."}, + {.code = TS_STATUS_REQUEST_INCOMPLETE, .expected = ":A8."}, + {.code = TS_STATUS_CONFLICT, .expected = ":A9."}, + {.code = TS_STATUS_REQUEST_TOO_LARGE, .expected = ":AD."}, + {.code = TS_STATUS_UNSUPPORTED_FORMAT, .expected = ":AF."}, + {.code = TS_STATUS_INTERNAL_SERVER_ERR, .expected = ":C0."}, + {.code = TS_STATUS_NOT_IMPLEMENTED, .expected = ":C1."}, + {.code = TS_STATUS_RESPONSE_TOO_LARGE, .expected = ":E1."}, + {.code = 0x0FF, .expected = ":FF."}, +#endif + }; + + for (int i = 0; i < TS_ARRAY_SIZE(tc_response_status_json); i++) { + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL_MESSAGE(0, ts_msg_len(msg), tc_response_status_json[i].expected); + + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_RESPONSE, + tc_response_status_json[i].code); + ret = ts_msg_add_response_status_json(msg); + TEST_ASSERT_EQUAL_MESSAGE(0, ret, tc_response_status_json[i].expected); + TEST_ASSERT_EQUAL_STRING_LEN_MESSAGE(tc_response_status_json[i].expected, ts_msg_data(msg), + ts_msg_len(msg), tc_response_status_json[i].expected); + TEST_ASSERT_EQUAL_MESSAGE(strlen(tc_response_status_json[i].expected), ts_msg_len(msg), + tc_response_status_json[i].expected); + } + + /* + * ts_msg_add_response_get_json + * ---------------------------- + */ + + thingset_msg_reset(msg); + thingset_msg_reset(request); + + ts_msg_status_set(request, TS_MSG_VALID_OK, TS_MSG_PROTO_BIN, TS_MSG_TYPE_REQUEST, 0); + +#if TS_CONFIG_VERBOSE_STATUS_MESSAGES + ts_msg_status_code_set(request, TS_MSG_CODE_REQUEST_GET_VALUES); + TEST_ASSERT_MSG_ADD_RESPONSE_GET_JSON("GET values - DeviceID", + msg, request, 0x1b, 0, + ":85 Content. \"" TEST_DEVICE_ID_INSTANCE "\""); + + ts_msg_status_code_set(request, TS_MSG_CODE_REQUEST_GET_VALUES); + TEST_ASSERT_MSG_ADD_RESPONSE_GET_JSON("GET values - meas", + msg, request, ID_MEAS, 0, + ":85 Content. [14.10,5.13,22]"); + + ts_msg_status_code_set(request, TS_MSG_CODE_REQUEST_GET_NAMES_VALUES); + TEST_ASSERT_MSG_ADD_RESPONSE_GET_JSON("GET names & values - meas", + msg, request, ID_MEAS, 0, + ":85 Content. {\"Bat_V\":14.10,\"Bat_A\":5.13,\"Ambient_degC\":22}"); + + ts_msg_status_code_set(request, TS_MSG_CODE_REQUEST_GET_VALUES); + TEST_ASSERT_MSG_ADD_RESPONSE_GET_JSON("GET values - report", + msg, request, ID_REPORT, 0, + ":85 Content. [\"Timestamp_s\",\"Bat_V\",\"Bat_A\",\"Ambient_degC\"]"); + + ts_msg_status_code_set(request, TS_MSG_CODE_REQUEST_GET_VALUES); + TEST_ASSERT_MSG_ADD_RESPONSE_GET_JSON("GET values - .pub -> bad request", + msg, request, ID_PUB, 0, + ":A0 Bad Request."); + + ts_msg_status_code_set(request, TS_MSG_CODE_REQUEST_GET_VALUES); + TEST_ASSERT_MSG_ADD_RESPONSE_GET_JSON("GET values - x-reset -> bad request", + msg, request, 0xE1, 0, + ":A0 Bad Request."); + + ts_msg_status_code_set(request, TS_MSG_CODE_REQUEST_GET_VALUES); + TEST_ASSERT_MSG_ADD_RESPONSE_GET_JSON("GET values - arrayi32", + msg, request, 0x7003, 0, + ":85 Content. [4,2,8,4]"); + + ts_msg_status_code_set(request, TS_MSG_CODE_REQUEST_GET_VALUES); + TEST_ASSERT_MSG_ADD_RESPONSE_GET_JSON("GET values - arrayfloat", + msg, request, 0x7004, 0, + ":85 Content. [2.27,3.44]"); +#else +#error "Not Supported" +#endif + + /* Full round - ts_msg_pull_request_json -> ts_msg_add_response_get_json */ + + thingset_msg_reset(msg); + thingset_msg_reset(request); + + req_str = "?meas"; + resp_str = ":85 Content. {\"Bat_V\":14.10,\"Bat_A\":5.13,\"Ambient_degC\":22}"; + + ts_msg_status_set(request, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_REQUEST, 0); + ret = ts_msg_add_mem(request, (const uint8_t *)req_str, strlen(req_str)); + TEST_ASSERT_EQUAL(0, ret); + ts_msg_auth_set(request, thingset_authorisation(TEST_INSTANCE_LOCID)); + + ret = ts_msg_pull_request_json(request, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_OK, ts_msg_status_valid(request)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(request)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(request)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_REQUEST_GET_NAMES_VALUES, ts_msg_status_code(request)); + TEST_ASSERT_EQUAL_UINT16(TS_EXPAND(TS_ANY_RW), ts_msg_auth(request)); + + ret = ts_msg_add_response_get_json(msg, object_oref, request); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT16(strlen(resp_str), ts_msg_len(msg)); + TEST_ASSERT_EQUAL_STRING_LEN(resp_str, ts_msg_data(msg), strlen(resp_str)); + + /* + * ts_msg_add_response_fetch_json + * ------------------------------ + */ + + /* Full round - ts_msg_pull_request_json -> ts_msg_add_response_fetch_json */ + + thingset_msg_reset(msg); + thingset_msg_reset(request); + + f32 = 52.80; + b = false; + i32 = 50; + req_str = "?conf [\"f32\",\"bool\",\"i32\"]"; + resp_str = ":85 Content. [52.80,false,50]"; + + ts_msg_status_set(request, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_REQUEST, 0); + ret = ts_msg_add_mem(request, (const uint8_t *)req_str, strlen(req_str)); + TEST_ASSERT_EQUAL(0, ret); + ts_msg_auth_set(request, thingset_authorisation(TEST_INSTANCE_LOCID)); + + ret = ts_msg_pull_request_json(request, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_OK, ts_msg_status_valid(request)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(request)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(request)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_REQUEST_FETCH_VALUES, ts_msg_status_code(request)); + TEST_ASSERT_EQUAL_UINT16(TS_ANY_RW, ts_msg_auth(request)); + + const char *log_str = "h'0707',txt-fetch(\"?\"),\"conf\"," + "[\"f32\",\"bool\",\"i32\"]"; + ret = ts_msg_log(request, &test_log_buf[0], sizeof(test_log_buf)); + TEST_ASSERT_EQUAL_STRING_LEN(log_str, &test_log_buf[ASSERT_MSG_LOG_SKIP], ret); + + ret = ts_msg_add_response_fetch_json(msg, object_oref, request); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL_UINT16(strlen(resp_str), ts_msg_len(msg)); + TEST_ASSERT_EQUAL_STRING_LEN(resp_str, ts_msg_data(msg), strlen(resp_str)); + + log_str = "h'0000',txt-reponse(\":\"),h'85',\"Content.\"," + "[52.80,false,50]"; + ret = ts_msg_log(msg, &test_log_buf[0], sizeof(test_log_buf)); + TEST_ASSERT_EQUAL_STRING_LEN(log_str, &test_log_buf[ASSERT_MSG_LOG_SKIP], ret); +} + +void test_msg_add_statement_cbor(void) +{ + int ret; + struct thingset_msg *msg = NULL; + thingset_oref_t object_oref; + char tc_desc[300]; + + object_oref = ts_obj_db_oref_any(ts_ctx_obj_db(TEST_INSTANCE_LOCID)); + + ret = thingset_msg_alloc_cbor(64, 10, &msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + const struct { + const char *tc; + const char *object_path; + int expected_ret; + const char *expected_str; + } tc_add_statement_cbor[] = { + { .tc = "STATEMENT subset - report", + .object_path = "report", + .expected_ret = 0, + .expected_str = "h'0000',bin-statement(h'1f'),10," + "[12345678, 14.100000381469727f, 5.130000114440918f, 22]" }, + { .tc = "STATEMENT group - info", + .object_path = "info", + .expected_ret = 0, + .expected_str = "h'0000',bin-statement(h'1f'),1," + "[\"Libre Solar\", 12345678, \"ABCD1234\"]" }, + { .tc = "STATEMENT missing object - error", + .object_path = 0, + .expected_ret = -EINVAL, + .expected_str = 0 }, + { .tc = "STATEMENT object with parent - not supported, silently ignore", + .object_path = ".pub/report", + .expected_ret = 0, + .expected_str = 0 }, + }; + + for (int i = 0; i < TS_ARRAY_SIZE(tc_add_statement_cbor); i++) { + /* Prepare assert message */ + int len = snprintf(&tc_desc[0], sizeof(tc_desc) - 1, + "TC#%.2d %s", i, tc_add_statement_cbor[i].tc); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL_MESSAGE(0, ts_msg_len(msg), &tc_desc[0]); + ts_msg_log(msg, &test_log_buf[0], sizeof(test_log_buf)); + TEST_ASSERT_EQUAL_STRING_MESSAGE("h'0000',error(\"initial command missing.\")", + &test_log_buf[ASSERT_MSG_LOG_SKIP], &tc_desc[0]); + + if (tc_add_statement_cbor[i].object_path == NULL) { +#if TS_CONFIG_ASSERT + /* Can not be tested as assertion will be triggered */ + continue; +#endif + object_oref = ts_obj_db_oref_any(ts_ctx_obj_db(TEST_INSTANCE_LOCID)); + } + else { + ret = ts_obj_by_path(ts_obj_db_oref_any(ts_ctx_obj_db(TEST_INSTANCE_LOCID)), + tc_add_statement_cbor[i].object_path, + (size_t)strlen(tc_add_statement_cbor[i].object_path), + &object_oref); + TEST_ASSERT_EQUAL_MESSAGE(0, ret, &tc_desc[0]); + } + + ret = ts_msg_add_statement_cbor(msg, object_oref); + ts_msg_log(msg, &test_log_buf[0], sizeof(test_log_buf)); + + /* Complete assert message */ + const char *object_name = ""; + ts_obj_id_t object_id = 0; + ts_obj_id_t object_parent_id = 0; + uint8_t object_type = 0; + if (ts_obj_db_oref_is_object(object_oref)) { + object_id = ts_obj_id(object_oref); + object_name = ts_obj_name(object_oref); + object_parent_id = ts_obj_parent_id(object_oref); + object_type = ts_obj_type(object_oref); + } + snprintf(&tc_desc[len], sizeof(tc_desc) - len - 1, + " - '%s', id: %u, type: %u, parent: %u -> %s", object_name, (unsigned int)object_id, + (unsigned int)object_type, (unsigned int)object_parent_id, &test_log_buf[0]); + + TEST_ASSERT_EQUAL_MESSAGE(tc_add_statement_cbor[i].expected_ret, ret, &tc_desc[0]); + if (tc_add_statement_cbor[i].expected_str != NULL) { + TEST_ASSERT_EQUAL_STRING_MESSAGE(tc_add_statement_cbor[i].expected_str, + &test_log_buf[ASSERT_MSG_LOG_SKIP], &tc_desc[0]); + } + else { + TEST_ASSERT_EQUAL_MESSAGE(0, ts_msg_len(msg), &tc_desc[0]); + } + } +} + +void test_msg_add_statement_json(void) +{ + int ret; + struct thingset_msg *msg = NULL; + thingset_oref_t object_oref; + char tc_desc[300]; + + ts_obj_db_oref_init(TEST_INSTANCE_LOCID, &object_oref); + + ret = thingset_msg_alloc_json(64, 10, &msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + const struct { + const char *tc; + const char *object_path; + int expected_ret; + const char *expected_str; + } tc_add_statement_json[] = { + { .tc = "STATEMENT subset - report", + .object_path = "report", + .expected_ret = 0, + .expected_str = "#report {\"Timestamp_s\":12345678,\"Bat_V\":14.10,\"Bat_A\":5.13,\"Ambient_degC\":22}" }, + { .tc = "STATEMENT group - info", + .object_path = "info", + .expected_ret = 0, + .expected_str = "#info {\"Manufacturer\":\"Libre Solar\",\"Timestamp_s\":12345678,\"DeviceID\":\"ABCD1234\"}" }, + { .tc = "STATEMENT missing object - error", + .object_path = 0, + .expected_ret = -EINVAL, + .expected_str = 0 }, + { .tc = "STATEMENT object with parent - not supported, silently ignore", + .object_path = ".pub/report", + .expected_ret = 0, + .expected_str = 0 }, + }; + + for (int i = 0; i < TS_ARRAY_SIZE(tc_add_statement_json); i++) { + /* Prepare assert message */ + int len = snprintf(&tc_desc[0], sizeof(tc_desc) - 1, + "TC#%.2d %s", i, tc_add_statement_json[i].tc); + + thingset_msg_reset(msg); + TEST_ASSERT_EQUAL_MESSAGE(0, ts_msg_len(msg), &tc_desc[0]); + + if (tc_add_statement_json[i].object_path == NULL) { +#if TS_CONFIG_ASSERT + /* Can not be tested as assertion will be triggered */ + continue; +#endif + object_oref = ts_obj_db_oref_any(ts_ctx_obj_db(TEST_INSTANCE_LOCID)); + } + else { + ret = ts_obj_by_path(ts_obj_db_oref_any(ts_ctx_obj_db(TEST_INSTANCE_LOCID)), + tc_add_statement_json[i].object_path, + (size_t)strlen(tc_add_statement_json[i].object_path), + &object_oref); + TEST_ASSERT_EQUAL_MESSAGE(0, ret, &tc_desc[0]); + } + + ret = ts_msg_add_statement_json(msg, object_oref); + + /* Complete assert message */ + const char *object_name = ""; + ts_obj_id_t object_parent_id = 0; + uint8_t object_type = 0; + if (ts_obj_db_oref_is_object(object_oref)) { + object_name = ts_obj_name(object_oref); + object_parent_id = ts_obj_parent_id(object_oref); + object_type = ts_obj_type(object_oref); + } + snprintf(&tc_desc[len], sizeof(tc_desc) - len - 1, + " - '%s', type: %u, parent: %u -> %.*s", object_name, (unsigned int)object_type, + (unsigned int)object_parent_id, (int)ts_msg_len(msg), (const char *)ts_msg_data(msg)); + + TEST_ASSERT_EQUAL_MESSAGE(tc_add_statement_json[i].expected_ret, ret, &tc_desc[0]); + if (tc_add_statement_json[i].expected_str != NULL) { + TEST_ASSERT_EQUAL_MESSAGE(strlen(tc_add_statement_json[i].expected_str), + ts_msg_len(msg), &tc_desc[0]); + TEST_ASSERT_EQUAL_STRING_LEN_MESSAGE(tc_add_statement_json[i].expected_str, + ts_msg_data(msg), ts_msg_len(msg), &tc_desc[0]); + } + else { + TEST_ASSERT_EQUAL_MESSAGE(0, ts_msg_len(msg), &tc_desc[0]); + } + } + + thingset_msg_unref(msg); +} + +/** + * @brief Test message COBS (en-/de)coding + * + * This test verifies message buffer usage: + * - ts_msg_cobs_enc_setup() + * - ts_msg_cobs_dec_setup() + */ +void test_msg_cobs(void) +{ + int ret; + struct thingset_msg *msg = NULL; + uint8_t buf[TS_COBS_INPLACE_SAFE_BUFFER_SIZE]; + + TEST_ASSERT_EQUAL(256, TS_COBS_INPLACE_SAFE_BUFFER_SIZE); + + ret = thingset_msg_alloc(TS_COBS_INPLACE_SAFE_BUFFER_SIZE, 10, &msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + /* Empty */ + ret = ts_msg_cobs_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(2, ts_msg_len(msg)); + TEST_ASSERT_EQUAL_HEX8_ARRAY("\x01\x00", ts_msg_data(msg), ts_msg_len(msg)); + ret = ts_msg_cobs_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + /* One non-zero byte */ + thingset_msg_reset(msg); + ts_msg_add_u8(msg, 0x01); + ret = ts_msg_cobs_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(3, ts_msg_len(msg)); + TEST_ASSERT_EQUAL_HEX8_ARRAY("\x02\x01\x00", ts_msg_data(msg), ts_msg_len(msg)); + ret = ts_msg_cobs_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(1, ts_msg_len(msg)); + TEST_ASSERT_EQUAL_HEX8_ARRAY("\x01", ts_msg_data(msg), ts_msg_len(msg)); + + /* One zero byte */ + thingset_msg_reset(msg); + ts_msg_add_u8(msg, 0x00); + ret = ts_msg_cobs_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(3, ts_msg_len(msg)); + TEST_ASSERT_EQUAL_HEX8_ARRAY("\x01\x01\x00", ts_msg_data(msg), ts_msg_len(msg)); + ret = ts_msg_cobs_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(1, ts_msg_len(msg)); + TEST_ASSERT_EQUAL_HEX8_ARRAY("\x00", ts_msg_data(msg), ts_msg_len(msg)); + + /* Longest possible run of 254 bytes */ + thingset_msg_reset(msg); + for (int i = 0; i < 254; i++) { + buf[i] = 1; + } + ret = ts_msg_add_mem(msg, &buf[0], 254); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(254, ts_msg_len(msg)); + ret = ts_msg_cobs_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(256, ts_msg_len(msg)); + ret = ts_msg_cobs_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(254, ts_msg_len(msg)); + + /* Safe payload, all zero bytes */ + thingset_msg_reset(msg); + for (int i = 0; i < 254; i++) { + buf[i] = 0; + } + ret = ts_msg_add_mem(msg, &buf[0], 254); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(254, ts_msg_len(msg)); + ret = ts_msg_cobs_enc_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(256, ts_msg_len(msg)); + ret = ts_msg_cobs_dec_setup(msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(254, ts_msg_len(msg)); +} + +void test_msg_pull_request_get_cbor(void) +{ + int ret; + struct thingset_msg *msg = NULL; + thingset_oref_t object_oref; + + object_oref = ts_obj_db_oref_any(ts_ctx_obj_db(TEST_INSTANCE_LOCID)); + + ret = thingset_msg_alloc_cbor(64, 10, &msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + /* Get root object - names by object path */ + thingset_msg_reset(msg); + + ret = ts_msg_add_request_get_cbor(msg, 0, "/"); + TEST_ASSERT_EQUAL(0, ret); + ts_msg_auth_set(msg, thingset_authorisation(TEST_INSTANCE_LOCID)); + + ret = ts_msg_pull_request_cbor(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_OK, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_BIN, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_REQUEST_GET_NAMES, ts_msg_status_code(msg)); + TEST_ASSERT_EQUAL_UINT16(TEST_INSTANCE_LOCID, object_oref.db_id); + TEST_ASSERT_EQUAL_UINT16(TS_OBJ_DB_OID_ROOT, object_oref.db_oid); + TEST_ASSERT_TRUE(ts_obj_db_oref_is_valid(object_oref)); + TEST_ASSERT_EQUAL(0, ts_obj_id(object_oref)); + + /* Get root object - error on invalid path by object path */ + thingset_msg_reset(msg); + + ret = ts_msg_add_request_get_cbor(msg, 0, "/invalid"); + TEST_ASSERT_EQUAL(0, ret); + ts_msg_auth_set(msg, thingset_authorisation(TEST_INSTANCE_LOCID)); + + ret = ts_msg_pull_request_cbor(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_ERROR, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_BIN, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_BAD_REQUEST, ts_msg_status_code(msg)); + TEST_ASSERT_FALSE(ts_obj_db_oref_is_valid(object_oref)); + + /* Get root object - ids and values by object id */ + thingset_msg_reset(msg); + + ret = ts_msg_add_request_get_cbor(msg, 0, NULL); + TEST_ASSERT_EQUAL(0, ret); + ts_msg_auth_set(msg, thingset_authorisation(TEST_INSTANCE_LOCID)); + + ret = ts_msg_pull_request_cbor(msg, &object_oref); + TEST_ASSERT_EQUAL(TS_MSG_VALID_OK, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_BIN, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_REQUEST_GET_IDS_VALUES, ts_msg_status_code(msg)); + TEST_ASSERT_TRUE(ts_obj_db_oref_is_valid(object_oref)); + TEST_ASSERT_EQUAL(0, ts_obj_id(object_oref)); + + /* Get 'meas' object - names and values by object path */ + thingset_msg_reset(msg); + + ret = ts_msg_add_request_get_cbor(msg, 0, "meas"); + TEST_ASSERT_EQUAL(0, ret); + ts_msg_auth_set(msg, thingset_authorisation(TEST_INSTANCE_LOCID)); + + ret = ts_msg_pull_request_cbor(msg, &object_oref); + TEST_ASSERT_EQUAL(TS_MSG_VALID_OK, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_BIN, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_REQUEST_GET_NAMES_VALUES, ts_msg_status_code(msg)); + TEST_ASSERT_TRUE(ts_obj_db_oref_is_valid(object_oref)); + TEST_ASSERT_EQUAL(ID_MEAS, ts_obj_id(object_oref)); + + /* Get 'meas' object - ids and values by object id */ + thingset_msg_reset(msg); + + ret = ts_msg_add_request_get_cbor(msg, ID_MEAS, NULL); + TEST_ASSERT_EQUAL(0, ret); + ts_msg_auth_set(msg, thingset_authorisation(TEST_INSTANCE_LOCID)); + + ret = ts_msg_pull_request_cbor(msg, &object_oref); + TEST_ASSERT_EQUAL(TS_MSG_VALID_OK, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_BIN, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_REQUEST_GET_IDS_VALUES, ts_msg_status_code(msg)); + TEST_ASSERT_TRUE(ts_obj_db_oref_is_valid(object_oref)); + TEST_ASSERT_EQUAL(ID_MEAS, ts_obj_id(object_oref)); + + /* Get uknown object - error on unknown path by object path */ + thingset_msg_reset(msg); + + ret = ts_msg_add_request_get_cbor(msg, 0, "unknown"); + TEST_ASSERT_EQUAL(0, ret); + ts_msg_auth_set(msg, thingset_authorisation(TEST_INSTANCE_LOCID)); + + ret = ts_msg_pull_request_cbor(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_ERROR, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_BIN, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_NOT_FOUND, ts_msg_status_code(msg)); + TEST_ASSERT_FALSE(ts_obj_db_oref_is_valid(object_oref)); + + thingset_msg_unref(msg); +} + +void test_msg_pull_request_get_json(void) +{ + int ret; + struct thingset_msg *msg = NULL; + thingset_oref_t object_oref; + + ts_obj_db_oref_init(TEST_INSTANCE_LOCID, &object_oref); + + ret = thingset_msg_alloc_json(64, 10, &msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + /* GET root names */ + thingset_msg_reset(msg); + + ret = ts_msg_add_request_get_json(msg, "/"); + TEST_ASSERT_EQUAL(0, ret); + ts_msg_auth_set(msg, thingset_authorisation(TEST_INSTANCE_LOCID)); + + ret = ts_msg_pull_request_json(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_OK, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_REQUEST_GET_NAMES, ts_msg_status_code(msg)); + TEST_ASSERT_TRUE(ts_obj_db_oref_is_valid(object_oref)); + TEST_ASSERT_EQUAL_STRING("/", ts_obj_name(object_oref)); + + /* GET names */ + thingset_msg_reset(msg); + + ret = ts_msg_add_request_get_json(msg, "meas/"); + TEST_ASSERT_EQUAL(0, ret); + ts_msg_auth_set(msg, thingset_authorisation(TEST_INSTANCE_LOCID)); + + ret = ts_msg_pull_request_json(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_OK, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_REQUEST_GET_NAMES, ts_msg_status_code(msg)); + TEST_ASSERT_TRUE(ts_obj_db_oref_is_valid(object_oref)); + TEST_ASSERT_EQUAL_STRING("meas", ts_obj_name(object_oref)); + + /* GET names on external object - bad request */ + thingset_msg_reset(msg); + + ret = ts_msg_add_request_get_json(msg, "meas/Bat_V/"); + TEST_ASSERT_EQUAL(0, ret); + ts_msg_auth_set(msg, thingset_authorisation(TEST_INSTANCE_LOCID)); + + ret = ts_msg_pull_request_json(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_ERROR, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_BAD_REQUEST, ts_msg_status_code(msg)); + TEST_ASSERT_FALSE(ts_obj_db_oref_is_valid(object_oref)); + + /* GET names&values */ + thingset_msg_reset(msg); + + ret = ts_msg_add_request_get_json(msg, "meas"); + TEST_ASSERT_EQUAL(0, ret); + ts_msg_auth_set(msg, thingset_authorisation(TEST_INSTANCE_LOCID)); + + ret = ts_msg_pull_request_json(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_OK, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_REQUEST_GET_NAMES_VALUES, ts_msg_status_code(msg)); + TEST_ASSERT_TRUE(ts_obj_db_oref_is_valid(object_oref)); + TEST_ASSERT_EQUAL_STRING("meas", ts_obj_name(object_oref)); + + /* GET single value */ + thingset_msg_reset(msg); + + ret = ts_msg_add_request_get_json(msg, "meas/Bat_V"); + TEST_ASSERT_EQUAL(0, ret); + ts_msg_auth_set(msg, thingset_authorisation(TEST_INSTANCE_LOCID)); + + ret = ts_msg_pull_request_json(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_OK, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_REQUEST_GET_NAMES_VALUES, ts_msg_status_code(msg)); + TEST_ASSERT_TRUE(ts_obj_db_oref_is_valid(object_oref)); + TEST_ASSERT_EQUAL_STRING("Bat_V", ts_obj_name(object_oref)); + + /* GET names on external object - bad request */ + thingset_msg_reset(msg); + + ret = ts_msg_add_request_get_json(msg, "rpc/x-dummy"); + TEST_ASSERT_EQUAL(0, ret); + ts_msg_auth_set(msg, thingset_authorisation(TEST_INSTANCE_LOCID)); + + ret = ts_msg_pull_request_json(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_ERROR, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_BAD_REQUEST, ts_msg_status_code(msg)); + TEST_ASSERT_FALSE(ts_obj_db_oref_is_valid(object_oref)); +} + +void test_msg_pull_request_fetch_json(void) +{ + int ret; + const char *request_str; + struct thingset_msg *msg = NULL; + thingset_oref_t object_oref; + + ts_obj_db_oref_init(TEST_INSTANCE_LOCID, &object_oref); + + ret = thingset_msg_alloc_json(64, 10, &msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + /* FETCH array */ + thingset_msg_reset(msg); + + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_REQUEST, 0); + request_str = "?conf [\"f32\",\"bool\",\"i32\"]"; + ret = ts_msg_add_mem(msg, request_str, strlen(request_str)); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_request_json(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_OK, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_REQUEST_FETCH_VALUES, ts_msg_status_code(msg)); + TEST_ASSERT_TRUE(ts_obj_db_oref_is_valid(object_oref)); + TEST_ASSERT_EQUAL_STRING("conf", ts_obj_name(object_oref)); +} + +void test_msg_pull_request_patch_json(void) +{ + int ret; + const char *request_str; + struct thingset_msg *msg = NULL; + thingset_oref_t object_oref; + + /* Tests work on instance context database */ + ts_obj_db_oref_init(TEST_INSTANCE_LOCID, &object_oref); + + ret = thingset_msg_alloc_json(64, 10, &msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + /* PATCH array */ + thingset_msg_reset(msg); + + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_REQUEST, 0); + request_str = "=conf {\"f32\" : 52.8,\"i32\":50.6}"; + ret = ts_msg_add_mem(msg, request_str, strlen(request_str)); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_request_json(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_OK, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_REQUEST_PATCH, ts_msg_status_code(msg)); + TEST_ASSERT_TRUE(ts_obj_db_oref_is_valid(object_oref)); + TEST_ASSERT_EQUAL_STRING("conf", ts_obj_name(object_oref)); + + /* PATCH missing object - bad request */ + thingset_msg_reset(msg); + + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_REQUEST, 0); + request_str = "={\"f32\" : 52.8,\"i32\":50.6}"; + ret = ts_msg_add_mem(msg, request_str, strlen(request_str)); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_request_json(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_ERROR, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_BAD_REQUEST, ts_msg_status_code(msg)); + TEST_ASSERT_FALSE(ts_obj_db_oref_is_valid(object_oref)); + + /* PATCH invalid object - bad request */ + thingset_msg_reset(msg); + + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_REQUEST, 0); + request_str = "=/{\"f32\" : 52.8,\"i32\":50.6}"; + ret = ts_msg_add_mem(msg, request_str, strlen(request_str)); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_request_json(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_ERROR, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_BAD_REQUEST, ts_msg_status_code(msg)); + TEST_ASSERT_FALSE(ts_obj_db_oref_is_valid(object_oref)); + + /* PATCH missing data structure - bad request */ + thingset_msg_reset(msg); + + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_REQUEST, 0); + request_str = "=conf"; + ret = ts_msg_add_mem(msg, request_str, strlen(request_str)); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_request_json(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_ERROR, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_BAD_REQUEST, ts_msg_status_code(msg)); + TEST_ASSERT_FALSE(ts_obj_db_oref_is_valid(object_oref)); + + /* PATCH wrongly formated data structure - bad request */ + thingset_msg_reset(msg); + + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_REQUEST, 0); + request_str = "=conf [\"f32\":54.3"; + ret = ts_msg_add_mem(msg, request_str, strlen(request_str)); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_request_json(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_ERROR, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_BAD_REQUEST, ts_msg_status_code(msg)); + TEST_ASSERT_FALSE(ts_obj_db_oref_is_valid(object_oref)); + + /* PATCH wrong data structure - not detected here */ + thingset_msg_reset(msg); + + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_REQUEST, 0); + request_str = "=conf{\"f32\":54.3}"; + ret = ts_msg_add_mem(msg, request_str, strlen(request_str)); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_request_json(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_OK, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_REQUEST_PATCH, ts_msg_status_code(msg)); + TEST_ASSERT_TRUE(ts_obj_db_oref_is_valid(object_oref)); + TEST_ASSERT_EQUAL_STRING("conf", ts_obj_name(object_oref)); + + /* Tests work on neighbour context database - all data objects are children of root */ + ts_obj_db_oref_init(TEST_NEIGHBOUR_LOCID, &object_oref); + + /* Patch with default root path */ + thingset_msg_reset(msg); + + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_REQUEST, 0); + request_str = "= {\"HeaterEnable\":true}"; + ret = ts_msg_add_mem(msg, request_str, strlen(request_str)); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_msg_pull_request_json(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_OK, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_REQUEST_PATCH, ts_msg_status_code(msg)); + TEST_ASSERT_TRUE(ts_obj_db_oref_is_valid(object_oref)); + TEST_ASSERT_EQUAL_STRING("/", ts_obj_name(object_oref)); +} + +void test_msg_pull_request_exec_json(void) +{ + int ret; + const char *request_str; + struct thingset_msg *msg = NULL; + thingset_oref_t object_oref; + + ts_obj_db_oref_init(TEST_INSTANCE_LOCID, &object_oref); + + ret = thingset_msg_alloc_json(64, 10, &msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + /* EXEC */ + thingset_msg_reset(msg); + + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_REQUEST, 0); + request_str = "!rpc/x-dummy"; + ret = ts_msg_add_mem(msg, request_str, strlen(request_str)); + TEST_ASSERT_EQUAL(0, ret); + ts_msg_auth_set(msg, thingset_authorisation(TEST_INSTANCE_LOCID)); + + ret = ts_msg_pull_request_json(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_OK, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_REQUEST_EXEC, ts_msg_status_code(msg)); + TEST_ASSERT_TRUE(ts_obj_db_oref_is_valid(object_oref)); + TEST_ASSERT_EQUAL_STRING("x-dummy", ts_obj_name(object_oref)); + + /* EXEC not authorized - unauthorized */ + thingset_msg_reset(msg); + + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_REQUEST, 0); + request_str = "!rpc/x-dummy"; + ret = ts_msg_add_mem(msg, request_str, strlen(request_str)); + TEST_ASSERT_EQUAL(0, ret); + ts_msg_auth_set(msg, TS_ANY_R); + + ret = ts_msg_pull_request_json(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_ERROR, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_UNAUTHORIZED, ts_msg_status_code(msg)); + TEST_ASSERT_FALSE(ts_obj_db_oref_is_valid(object_oref)); + + /* EXEC unknown object - not found */ + thingset_msg_reset(msg); + + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_REQUEST, 0); + request_str = "!rpc/x-unknown"; + ret = ts_msg_add_mem(msg, request_str, strlen(request_str)); + TEST_ASSERT_EQUAL(0, ret); + ts_msg_auth_set(msg, thingset_authorisation(TEST_INSTANCE_LOCID)); + + ret = ts_msg_pull_request_json(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_ERROR, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_NOT_FOUND, ts_msg_status_code(msg)); + TEST_ASSERT_FALSE(ts_obj_db_oref_is_valid(object_oref)); + + /* EXEC invalid object - bad request */ + thingset_msg_reset(msg); + + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_REQUEST, 0); + request_str = "!meas/Bat_V"; + ret = ts_msg_add_mem(msg, request_str, strlen(request_str)); + TEST_ASSERT_EQUAL(0, ret); + ts_msg_auth_set(msg, thingset_authorisation(TEST_INSTANCE_LOCID)); + + ret = ts_msg_pull_request_json(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_ERROR, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_BAD_REQUEST, ts_msg_status_code(msg)); + TEST_ASSERT_FALSE(ts_obj_db_oref_is_valid(object_oref)); +} + +void test_msg_pull_request_create_json(void) +{ + int ret; + const char *request_str; + struct thingset_msg *msg = NULL; + thingset_oref_t object_oref; + + ts_obj_db_oref_init(TEST_INSTANCE_LOCID, &object_oref); + + ret = thingset_msg_alloc_json(64, 10, &msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + /* CREATE object */ + thingset_msg_reset(msg); + + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_REQUEST, 0); + request_str = "+report \"Ambient_degC\""; + ret = ts_msg_add_mem(msg, request_str, strlen(request_str)); + TEST_ASSERT_EQUAL(0, ret); + ts_msg_auth_set(msg, thingset_authorisation(TEST_INSTANCE_LOCID)); + + ret = ts_msg_pull_request_json(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_OK, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_REQUEST_CREATE, ts_msg_status_code(msg)); + TEST_ASSERT_TRUE(ts_obj_db_oref_is_valid(object_oref)); + TEST_ASSERT_EQUAL_STRING("report", ts_obj_name(object_oref)); + + /* CREATE missing data structure - bad request */ + thingset_msg_reset(msg); + + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_REQUEST, 0); + request_str = "+report "; + ret = ts_msg_add_mem(msg, request_str, strlen(request_str)); + TEST_ASSERT_EQUAL(0, ret); + ts_msg_auth_set(msg, thingset_authorisation(TEST_INSTANCE_LOCID)); + + ret = ts_msg_pull_request_json(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_ERROR, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_BAD_REQUEST, ts_msg_status_code(msg)); + TEST_ASSERT_FALSE(ts_obj_db_oref_is_valid(object_oref)); +} + +void test_msg_pull_request_delete_json(void) +{ + int ret; + const char *request_str; + struct thingset_msg *msg = NULL; + thingset_oref_t object_oref; + + ts_obj_db_oref_init(TEST_INSTANCE_LOCID, &object_oref); + + ret = thingset_msg_alloc_json(64, 10, &msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + /* DELETE object */ + thingset_msg_reset(msg); + + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_REQUEST, 0); + request_str = "-report \"Ambient_degC\""; + ret = ts_msg_add_mem(msg, request_str, strlen(request_str)); + TEST_ASSERT_EQUAL(0, ret); + ts_msg_auth_set(msg, thingset_authorisation(TEST_INSTANCE_LOCID)); + + ret = ts_msg_pull_request_json(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_OK, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_REQUEST_DELETE, ts_msg_status_code(msg)); + TEST_ASSERT_TRUE(ts_obj_db_oref_is_valid(object_oref)); + TEST_ASSERT_EQUAL_STRING("report", ts_obj_name(object_oref)); + + /* DELETE missing data structure - bad request */ + thingset_msg_reset(msg); + + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_REQUEST, 0); + request_str = "-report "; + ret = ts_msg_add_mem(msg, request_str, strlen(request_str)); + TEST_ASSERT_EQUAL(0, ret); + ts_msg_auth_set(msg, thingset_authorisation(TEST_INSTANCE_LOCID)); + + ret = ts_msg_pull_request_json(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_ERROR, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_BAD_REQUEST, ts_msg_status_code(msg)); + TEST_ASSERT_FALSE(ts_obj_db_oref_is_valid(object_oref)); +} + +void test_msg_pull_request_invalid_json(void) +{ + int ret; + const char *request_str; + struct thingset_msg *msg = NULL; + thingset_oref_t object_oref; + + ts_obj_db_oref_init(TEST_INSTANCE_LOCID, &object_oref); + + ret = thingset_msg_alloc_json(64, 10, &msg); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(0, ts_msg_len(msg)); + + /* Invalid command - bad request */ + thingset_msg_reset(msg); + + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_REQUEST, 0); + request_str = "$meas/Bat_V"; + ret = ts_msg_add_mem(msg, request_str, strlen(request_str)); + TEST_ASSERT_EQUAL(0, ret); + ts_msg_auth_set(msg, thingset_authorisation(TEST_INSTANCE_LOCID)); + + ret = ts_msg_pull_request_json(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_ERROR, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_BAD_REQUEST, ts_msg_status_code(msg)); + TEST_ASSERT_FALSE(ts_obj_db_oref_is_valid(object_oref)); + + /* Empty request - return != 0 */ + thingset_msg_reset(msg); + + ts_msg_status_set(msg, TS_MSG_VALID_OK, TS_MSG_PROTO_TXT, TS_MSG_TYPE_REQUEST, 0); + ts_msg_auth_set(msg, thingset_authorisation(TEST_INSTANCE_LOCID)); + + ret = ts_msg_pull_request_json(msg, &object_oref); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(TS_MSG_VALID_ERROR, ts_msg_status_valid(msg)); + TEST_ASSERT_EQUAL(TS_MSG_PROTO_TXT, ts_msg_status_proto(msg)); + TEST_ASSERT_EQUAL(TS_MSG_TYPE_REQUEST, ts_msg_status_type(msg)); + TEST_ASSERT_EQUAL(TS_MSG_CODE_BAD_REQUEST, ts_msg_status_code(msg)); + TEST_ASSERT_FALSE(ts_obj_db_oref_is_valid(object_oref)); +} + +void tests_msg(void) +{ + UNITY_BEGIN(); + + /* Message buffer support */ + RUN_TEST(test_msg_alloc); + RUN_TEST(test_msg_add_primitives); + RUN_TEST(test_msg_add_response_cbor); + RUN_TEST(test_msg_add_response_json); + RUN_TEST(test_msg_add_statement_cbor); + RUN_TEST(test_msg_add_statement_json); + RUN_TEST(test_msg_cobs); + RUN_TEST(test_msg_pull_request_get_cbor); + RUN_TEST(test_msg_pull_request_get_json); + RUN_TEST(test_msg_pull_request_fetch_json); + RUN_TEST(test_msg_pull_request_patch_json); + RUN_TEST(test_msg_pull_request_exec_json); + RUN_TEST(test_msg_pull_request_create_json); + RUN_TEST(test_msg_pull_request_delete_json); + RUN_TEST(test_msg_pull_request_invalid_json); + + UNITY_END(); +} diff --git a/test/test_obj.c b/test/test_obj.c new file mode 100644 index 0000000..692f493 --- /dev/null +++ b/test/test_obj.c @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2021 Bobby Noelte + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "test.h" + +/** + * @brief Test object database initialisation + * + * This test verifies object database usage: + * - THINGSET_DATABASE_DEFINE() + * - ts_obj_db_init() + */ +void test_obj_db_init(void) +{ + int ret; + + /* Assert two local databases are configured for test */ + TEST_ASSERT_EQUAL(3, TS_CONFIG_LOCAL_COUNT); + + /* Check static initialisation constants */ + TEST_ASSERT_EQUAL_UINT16(TS_ANY_R, (TS_MKR_R | TS_EXP_R | TS_USR_R)); + TEST_ASSERT_EQUAL_UINT16(TS_ANY_W, (TS_MKR_W | TS_EXP_W | TS_USR_W)); + TEST_ASSERT_EQUAL_UINT16(TS_ANY_RW, (TS_ANY_R | TS_ANY_W)); + + /* check static initialisation */ + thingset_oref_t oref; + ret = ts_obj_db_oref_by_id(ts_ctx_obj_db(TEST_INSTANCE_LOCID), ID_ROOT, &oref); + TEST_ASSERT_EQUAL_INT(0, ret); + + /* Check dynamic initialisation */ + ts_obj_db_init(); + + ret = ts_obj_db_check_id_duplicates(TEST_INSTANCE_LOCID); + TEST_ASSERT_EQUAL_INT(0, ret); + + /* Check whether mutable object meta data were correctly initialised */ + const struct ts_obj_db *test_db_instance = ts_obj_db_by_id(TEST_INSTANCE_LOCID); + for (unsigned int i = 0; i < test_db_instance->num; i++) { + TEST_ASSERT_EQUAL_UINT16(test_db_instance->objects[i].meta_default.subsets, + test_db_instance->meta[i].subsets); + TEST_ASSERT_EQUAL_UINT16(test_db_instance->objects[i].meta_default.detail, + test_db_instance->meta[i].detail); + TEST_ASSERT_EQUAL_UINT16(test_db_instance->objects[i].meta_default.access, + test_db_instance->meta[i].access); + } +} + +/** + * @brief Test static initialisation of data objects + * + * This test verifies static initialsation macro usage: + * - TS_OBJ() + */ +void test_obj_static_init(void) +{ + int ret; + thingset_oref_t oref; + + /* Test TS_GROUP static initialization */ + + ret = ts_obj_db_oref_by_id(ts_ctx_obj_db(TEST_CORE_LOCID), ID_CONF, &oref); + TEST_ASSERT_EQUAL_INT(0, ret); + TEST_ASSERT_EQUAL_UINT8(TS_T_GROUP, ts_obj_type(oref)); + TEST_ASSERT_EQUAL_STRING("conf", ts_obj_name(oref)); + TEST_ASSERT_EQUAL_UINT16(ID_ROOT, ts_obj_parent_id(oref)); + TEST_ASSERT_EQUAL_PTR(&test_core_conf_callback, ts_obj_exec_data(oref)); + + ret = ts_obj_db_oref_by_id(ts_ctx_obj_db(TEST_CORE_LOCID), ID_INPUT, &oref); + TEST_ASSERT_EQUAL_INT(0, ret); + TEST_ASSERT_EQUAL_UINT8(TS_T_GROUP, ts_obj_type(oref)); + TEST_ASSERT_NULL(ts_obj_exec_data(oref)); + +} + +/** + * @brief Test static initialisation of data objects + * + * This test verifies object logging: + * - thingset_dump_json() + * - thingset_obj_log() + */ +void test_obj_log(void) +{ + int ret; + thingset_oref_t oref; + + /* Assure dump works on core context database */ + thingset_dump_json(TS_ID_ROOT, 0); + + ret = ts_obj_db_oref_by_id(ts_ctx_obj_db(TEST_NEIGHBOUR_LOCID), TS_ID_ROOT, &oref); + TEST_ASSERT_EQUAL_INT(0, ret); + thingset_obj_log(oref, &test_log_buf[0], sizeof(test_log_buf)); + TEST_ASSERT_EQUAL_STRING_LEN( +"{\n\ + \"DeviceID\": {\n\ + \"db_oid\": 0,\n\ + \"obj_id\": 29,\n\ + \"access\": \"TS_ANY_R\",\n\ + \"access_default\": \"TS_ANY_R\",\n\ + \"type\": \"TS_T_STRING\",\n\ + \"data\": \"ABCD5678\"\n\ + },\n\ + \"Ambient_degC\": {\n\ + \"db_oid\": 1,\n\ + \"obj_id\": 113,\n\ + \"access\": \"TS_ANY_R\",\n\ + \"access_default\": \"TS_ANY_R\",\n\ + \"type\": \"TS_T_FLOAT32\",\n\ + \"data\": 0.000000\n\ + },\n\ + \"HeaterEnable\": {\n\ + \"db_oid\": 2,\n\ + \"obj_id\": 97,\n\ + \"access\": \"TS_ANY_RW\",\n\ + \"access_default\": \"TS_ANY_RW\",\n\ + \"type\": \"TS_T_BOOL\",\n\ + \"data\": false\n\ + },\n\ + \"x-reset\": {\n\ + \"db_oid\": 3,\n\ + \"obj_id\": 225,\n\ + \"access\": \"TS_ANY_RW\",\n\ + \"access_default\": \"TS_ANY_RW\",\n\ + \"type\": \"TS_T_EXEC\",\n\ + \"data\": null\n\ + }\n\ +}\n", + &test_log_buf[0], sizeof(test_log_buf)); +} + +void tests_obj(void) +{ + UNITY_BEGIN(); + + // Test environment + RUN_TEST(test_obj_db_init); + RUN_TEST(test_obj_static_init); + RUN_TEST(test_obj_log); + + UNITY_END(); +} diff --git a/test/test_time.c b/test/test_time.c new file mode 100644 index 0000000..bbdae6b --- /dev/null +++ b/test/test_time.c @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021 Bobby Noelte + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "test.h" + +void test_time(void) +{ + thingset_time_ms_t ms; + thingset_time_ms_t delta; + + ms = thingset_time_ms(); + TEST_ASSERT_GREATER_OR_EQUAL_UINT32(1, ms); + + delta = thingset_time_ms_delta(ms - 1); + TEST_ASSERT_GREATER_OR_EQUAL_UINT32(1, delta); + + ms = thingset_time_ms(); + delta = thingset_time_ms_delta(ms + 1); + TEST_ASSERT_EQUAL_UINT32(THINGSET_TIME_MS_MAX - 1, delta); +} + +void tests_time(void) +{ + UNITY_BEGIN(); + + // Operating system abstraction + RUN_TEST(test_time); + + UNITY_END(); +} From 444a2784cc6025e5c5dcd9ccdeaebabf1e9fdee3 Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Wed, 26 Jan 2022 08:13:05 +0100 Subject: [PATCH 27/37] COM prepare - add tests that are specific to implementation Prepare for ThingSet Communication Context: - Add tests that are specific to the Native implementation. - Add tests that are specific to the Zephyr implementation. Signed-off-by: Bobby Noelte --- native/tests/test_impl.c | 18 ++++++++++++++++++ zephyr/tests/test_impl.c | 28 ++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 native/tests/test_impl.c create mode 100644 zephyr/tests/test_impl.c diff --git a/native/tests/test_impl.c b/native/tests/test_impl.c new file mode 100644 index 0000000..2abab1f --- /dev/null +++ b/native/tests/test_impl.c @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2022 Bobby Noelte + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet unit test suite for Native implementation. + */ + +#include "../../test/test.h" + +void tests_impl(void) +{ + UNITY_BEGIN(); + + UNITY_END(); +} diff --git a/zephyr/tests/test_impl.c b/zephyr/tests/test_impl.c new file mode 100644 index 0000000..29a62d6 --- /dev/null +++ b/zephyr/tests/test_impl.c @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022 Bobby Noelte + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet unit test suite for Zephyr implementation. + */ + +#include "../../test/test.h" + +void test_shell_zephyr(void) +{ + /* Assure test environment for shell app */ + TEST_ASSERT_TRUE(CONFIG_SHELL); + TEST_ASSERT_TRUE(CONFIG_SHELL_BACKEND_DUMMY); + TEST_ASSERT_EQUAL(32, TS_CONFIG_SHELL_MEM_SIZE); +} + +void tests_impl(void) +{ + UNITY_BEGIN(); + + RUN_TEST(test_shell_zephyr); + + UNITY_END(); +} From 8d82b2c27fb1543ba3d2e45a02922c3166579025 Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Mon, 7 Feb 2022 11:01:55 +0100 Subject: [PATCH 28/37] COM prepare - add tests for shell application Signed-off-by: Bobby Noelte --- test/test_shell.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 test/test_shell.c diff --git a/test/test_shell.c b/test/test_shell.c new file mode 100644 index 0000000..3670013 --- /dev/null +++ b/test/test_shell.c @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2022 Bobby Noelte + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ThingSet unit test suite for Zephyr implementation. + */ + +#include "test.h" + +#include + +void test_shell(void) +{ + int ret; + const char *expected_response; + const char *found_response; + const char *shell_response; + size_t shell_reponse_size; + + /* Assure test environment */ + TEST_ASSERT_TRUE(TS_CONFIG_SHELL); + TEST_ASSERT_EQUAL_STRING(TEST_SHELL_NAME, TS_CONFIG_SHELL_NAME); + TEST_ASSERT_EQUAL(TEST_SHELL_LOCID, TS_CONFIG_SHELL_LOCID); + TEST_ASSERT_EQUAL(TEST_SHELL_PORTID, TS_CONFIG_SHELL_PORTID); + + /* Assure shell memory allocation - allocate some bytes to show working */ + TEST_ASSERT_NOT_EQUAL(0, TS_CONFIG_SHELL_MEM_SIZE); + ret = ts_shell_alloc(10, THINGSET_TIMEOUT_IMMEDIATE, (uint8_t **)&expected_response); + TEST_ASSERT_EQUAL(0, ret); + ret = ts_shell_free((uint8_t *)expected_response); + TEST_ASSERT_EQUAL(0, ret); + + ret = ts_shell_execute_cmd("clear"); + (void)ts_shell_execute_output(&shell_reponse_size, &shell_response); + TEST_ASSERT_EQUAL_MESSAGE(0, ret, shell_response); + + ret = ts_shell_execute_cmd("ts txt ?conf/"); + (void)ts_shell_execute_output(&shell_reponse_size, &shell_response); + TEST_ASSERT_EQUAL_MESSAGE(0, ret, shell_response); + + expected_response = + ":85 Content. [\"BatCharging_V\",\"LoadDisconnect_V\",\"ui64\",\"i64\",\"ui32\",\"i32\"," + "\"ui16\",\"i16\",\"f32\",\"bool\",\"strbuf\",\"f32_rounded\",\"DecFrac_degC\"," + "\"secret_expert\",\"secret_maker\",\"arrayi32\",\"arrayfloat\",\"bytesbuf\"]"; + /* + * Shell response contains all the bytes that goes through the shell backend interface + * including escape codes, NL and CR. The function strnstr() finds the place in the buffer where + * the interesting data is located. + */ + found_response = strnstr(shell_response, expected_response, shell_reponse_size); + TEST_ASSERT_NOT_NULL_MESSAGE(found_response, shell_response); + + (void)ts_shell_execute_cmd("clear"); + ret = ts_shell_execute_cmd("ts txt =.pub/report {\"Enable\":true}"); + (void)ts_shell_execute_output(&shell_reponse_size, &shell_response); + TEST_ASSERT_EQUAL_MESSAGE(0, ret, shell_response); + + expected_response = ":84 Changed."; + found_response = strnstr(shell_response, expected_response, shell_reponse_size); + TEST_ASSERT_NOT_NULL_MESSAGE(found_response, shell_response); + +} + +void tests_shell(void) +{ + UNITY_BEGIN(); + + RUN_TEST(test_shell); + + UNITY_END(); +} From c60abce7c882b0f434d0152bd66581d7abb69fb3 Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Mon, 17 Jan 2022 08:29:10 +0100 Subject: [PATCH 29/37] COM prepare - add test executable for native implementation Prepare for ThingSet Communication Context: - add test executable with CMake build that fits to native environment Signed-off-by: Bobby Noelte --- native/tests/CMakeLists.txt | 93 +++++++++++++++++++++++++++++++++++++ native/tests/config.h | 33 +++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 native/tests/CMakeLists.txt create mode 100644 native/tests/config.h diff --git a/native/tests/CMakeLists.txt b/native/tests/CMakeLists.txt new file mode 100644 index 0000000..9653942 --- /dev/null +++ b/native/tests/CMakeLists.txt @@ -0,0 +1,93 @@ +# Copyright (c) 2021 Bobby Noelte. +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +project(native_thingset_test) + +# Get base directory. +# Assumption: this file is within the zephyr/tests subdir of the thingset library. +get_filename_component(THINGSET_BASE "${CMAKE_CURRENT_SOURCE_DIR}/../.." ABSOLUTE) +message(STATUS "THINGSET_BASE: ${THINGSET_BASE}") + +include_directories(${CMAKE_CURRENT_SOURCE_DIR} + ${THINGSET_BASE}/src + ${THINGSET_BASE}/native/unity/src) + +add_executable(test + ${THINGSET_BASE}/test/main.cpp + ${THINGSET_BASE}/test/test_assert.c + ${THINGSET_BASE}/test/test_bin.c + ${THINGSET_BASE}/test/test_buf.c + ${THINGSET_BASE}/test/test_common.c + ${THINGSET_BASE}/test/test_ctx.c + ${THINGSET_BASE}/test/test_data.c + ${THINGSET_BASE}/test/test_jsmn.c + ${THINGSET_BASE}/test/test_msg.c + ${THINGSET_BASE}/test/test_obj.c + ${THINGSET_BASE}/test/test_shell.c + ${THINGSET_BASE}/test/test_shim.cpp + ${THINGSET_BASE}/test/test_support.c + ${THINGSET_BASE}/test/test_time.c + ${THINGSET_BASE}/test/test_txt.c + # Native implementation test suite + ${CMAKE_CURRENT_SOURCE_DIR}/test_impl.c +) + +add_library(ts STATIC "") +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_app.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_cobs.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_cmd.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_core.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_export.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_msg.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_node.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_obj.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_process.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_jsmn.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_coder.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_export.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_log.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_proto.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_value.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_obj.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_obj_log.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_port.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_time.c) +# Native implementation +# - buf, bufq, time, libc +target_sources(ts PRIVATE ${THINGSET_BASE}/native/thingset/ts_impl_buf.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/thingset/ts_impl_bufq.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/thingset/ts_impl_libc.c) +# - TinyCBOR support lib +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborencoder.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborencoder_close_container_checked.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborencoder_float.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborerrorstrings.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborparser.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborparser_float.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborpretty.c) +# - Unit test by Unity +target_sources(ts PRIVATE ${THINGSET_BASE}/native/unity/src/unity.c) + +# ports +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ports/loopback_simple/loopback_simple.c) + +# applications +target_sources(ts PRIVATE ${THINGSET_BASE}/apps/shell/ts_shell.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/apps/shell/ts_shell_abuf.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/apps/shell/ts_shell_g.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/thingset/ts_impl_shell.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/thingset/ts_impl_shell_linenoise.c) + +target_link_libraries(test ts) + +# for math.h functions +target_link_libraries(test m) + +# for thingset device library configuration by header file +add_definitions(-g -DTHINGSET_CONFIG_HEADER="config.h" -fprofile-arcs -ftest-coverage) + +target_link_libraries(test gcov) diff --git a/native/tests/config.h b/native/tests/config.h new file mode 100644 index 0000000..1e075b6 --- /dev/null +++ b/native/tests/config.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief native test configuration. + */ + +#ifndef CONFIG_H_ +#define CONFIG_H_ + +#define CONFIG_THINGSET_CORE 1 +#define CONFIG_THINGSET_COM 1 +#define CONFIG_THINGSET_LOCAL_COUNT 3 +#define CONFIG_THINGSET_PORT_COUNT 5 + +#define CONFIG_THINGSET_UNIT_TEST 1 +#define CONFIG_THINGSET_LOG 0 +#define CONFIG_THINGSET_ASSERT 1 + +#define CONFIG_THINGSET_64BIT_TYPES_SUPPORT 1 +#define CONFIG_THINGSET_DECFRAC_TYPE_SUPPORT 1 + +/* enable testing of shell application */ +#define CONFIG_THINGSET_SHELL 1 +#define CONFIG_THINGSET_SHELL_NAME "test_shell" +#define CONFIG_THINGSET_SHELL_LOCID 1 +#define CONFIG_THINGSET_SHELL_PORTID 2 +#define CONFIG_THINGSET_SHELL_MEM_SIZE 30000 + +#endif /* CONFIG_H_ */ From 7454df29fb05ccffaf46f988ce9b692ec306fdd9 Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Sun, 6 Feb 2022 19:30:03 +0100 Subject: [PATCH 30/37] COM prepare - add example for communication context Signed-off-by: Bobby Noelte --- examples/interactive_com/main.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 examples/interactive_com/main.c diff --git a/examples/interactive_com/main.c b/examples/interactive_com/main.c new file mode 100644 index 0000000..af9d339 --- /dev/null +++ b/examples/interactive_com/main.c @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 Bobby Noelte + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include "../../test/test.h" + +int main(void) +{ + (void)thingset_init(TEST_CORE_LOCID); + (void)thingset_init(TEST_INSTANCE_LOCID); + (void)thingset_init(TEST_NEIGHBOUR_LOCID); + thingset_authorisation_set(TEST_CORE_LOCID, TS_ANY_RW); + thingset_authorisation_set(TEST_INSTANCE_LOCID, TS_ANY_RW); + thingset_authorisation_set(TEST_NEIGHBOUR_LOCID, TS_ANY_RW); + + (void)thingset_run(TEST_INSTANCE_LOCID); + (void)thingset_run(TEST_NEIGHBOUR_LOCID); + + while (true) { + if (ts_shell_join() == 0) { + break; + } + /* Sleep a little bit to allow especiall board emulations to work correctly */ + sleep(5); + } + + return 0; +} From 9b0ea890e9cef6cdaa20c0837f9d71ac8ea83cc3 Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Fri, 28 Jan 2022 08:48:03 +0100 Subject: [PATCH 31/37] COM prepare - add examples executable for Zephyr implementation Prepare for ThingSet Communication Context: - add examples executable with build that fits to Zephyr environment Signed-off-by: Bobby Noelte --- zephyr/examples/basic/CMakeLists.txt | 35 +++++++++++++++ zephyr/examples/basic/Kconfig | 6 +++ zephyr/examples/basic/prj.conf | 17 ++++++++ zephyr/examples/basic/sample.yaml | 2 + .../examples/interactive_com/CMakeLists.txt | 40 +++++++++++++++++ zephyr/examples/interactive_com/Kconfig | 6 +++ zephyr/examples/interactive_com/prj.conf | 43 +++++++++++++++++++ zephyr/examples/interactive_com/sample.yaml | 2 + zephyr/examples/interactive_com/src/main.c | 16 +++++++ 9 files changed, 167 insertions(+) create mode 100644 zephyr/examples/basic/CMakeLists.txt create mode 100644 zephyr/examples/basic/Kconfig create mode 100644 zephyr/examples/basic/prj.conf create mode 100644 zephyr/examples/basic/sample.yaml create mode 100644 zephyr/examples/interactive_com/CMakeLists.txt create mode 100644 zephyr/examples/interactive_com/Kconfig create mode 100644 zephyr/examples/interactive_com/prj.conf create mode 100644 zephyr/examples/interactive_com/sample.yaml create mode 100644 zephyr/examples/interactive_com/src/main.c diff --git a/zephyr/examples/basic/CMakeLists.txt b/zephyr/examples/basic/CMakeLists.txt new file mode 100644 index 0000000..b6e4330 --- /dev/null +++ b/zephyr/examples/basic/CMakeLists.txt @@ -0,0 +1,35 @@ +# Copyright (c) 2021 Bobby Noelte. +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +# Get base directory. +# Assumption: this file is within the zephyr/samples/basic subdir of the thingset library. +get_filename_component(THINGSET_BASE "${CMAKE_CURRENT_SOURCE_DIR}/../../.." ABSOLUTE) +message(STATUS "THINGSET_BASE: ${THINGSET_BASE}") + +# Search for Zepyhr installed side by side to thingset device library. +# This is a fallback in case ZEPHYR_BASE is not given. +set(zephyr_base $ENV{ZEPHYR_BASE}) +get_filename_component(MODULES_BASE "${THINGSET_BASE}/.." ABSOLUTE) +FILE(GLOB modules LIST_DIRECTORIES true "${MODULES_BASE}/*") +foreach(module ${modules}) + set(full_path ${module}/Kconfig.zephyr) + if(EXISTS ${full_path}) + set(zephyr_base "${module}") + break() + endif() +endforeach() +message(STATUS "ZEPHYR_BASE: ${zephyr_base}") + +# Prepare native posix example on host +set(BOARD "native_posix") +set(ZEPHYR_TOOLCHAIN_VARIANT "host") + +set(ZEPHYR_MODULES ${THINGSET_BASE}) + +find_package(Zephyr REQUIRED HINTS ${zephyr_base}) +project(thinset_example_basic) + +target_sources(app PRIVATE ${THINGSET_BASE}/examples/basic/main.c) + diff --git a/zephyr/examples/basic/Kconfig b/zephyr/examples/basic/Kconfig new file mode 100644 index 0000000..899944e --- /dev/null +++ b/zephyr/examples/basic/Kconfig @@ -0,0 +1,6 @@ +# Copyright (c) 2022 Bobby Noelte. +# SPDX-License-Identifier: Apache-2.0 + +mainmenu "ThingSet for Zepyhr - examples/basic configuration" + +source "Kconfig.zephyr" diff --git a/zephyr/examples/basic/prj.conf b/zephyr/examples/basic/prj.conf new file mode 100644 index 0000000..7550800 --- /dev/null +++ b/zephyr/examples/basic/prj.conf @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_THINGSET=y + +# enable only core context +CONFIG_THINGSET_CORE=y +CONFIG_THINGSET_COM=n + +# enable all optional features +CONFIG_THINGSET_64BIT_TYPES_SUPPORT=y +CONFIG_THINGSET_DECFRAC_TYPE_SUPPORT=y +CONFIG_THINGSET_BYTE_STRING_TYPE_SUPPORT=y + +CONFIG_ASSERT=y + +CONFIG_LOG=y +CONFIG_THINGSET_LOG_LEVEL_DBG=y diff --git a/zephyr/examples/basic/sample.yaml b/zephyr/examples/basic/sample.yaml new file mode 100644 index 0000000..5b6479b --- /dev/null +++ b/zephyr/examples/basic/sample.yaml @@ -0,0 +1,2 @@ +sample: + name: ThingSet Basic Example diff --git a/zephyr/examples/interactive_com/CMakeLists.txt b/zephyr/examples/interactive_com/CMakeLists.txt new file mode 100644 index 0000000..71f0fee --- /dev/null +++ b/zephyr/examples/interactive_com/CMakeLists.txt @@ -0,0 +1,40 @@ +# Copyright (c) 2021 Bobby Noelte. +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +# Get base directory. +# Assumption: this file is within the zephyr/samples/basic subdir of the thingset library. +get_filename_component(THINGSET_BASE "${CMAKE_CURRENT_SOURCE_DIR}/../../.." ABSOLUTE) +message(STATUS "THINGSET_BASE: ${THINGSET_BASE}") + +# Search for Zepyhr installed side by side to thingset device library. +# This is a fallback in case ZEPHYR_BASE is not given. +set(zephyr_base $ENV{ZEPHYR_BASE}) +get_filename_component(MODULES_BASE "${THINGSET_BASE}/.." ABSOLUTE) +FILE(GLOB modules LIST_DIRECTORIES true "${MODULES_BASE}/*") +foreach(module ${modules}) + set(full_path ${module}/Kconfig.zephyr) + if(EXISTS ${full_path}) + set(zephyr_base "${module}") + break() + endif() +endforeach() +message(STATUS "ZEPHYR_BASE: ${zephyr_base}") + +# Prepare native posix example on host +set(BOARD "native_posix") +set(ZEPHYR_TOOLCHAIN_VARIANT "host") + +set(ZEPHYR_MODULES ${THINGSET_BASE}) + +find_package(Zephyr REQUIRED HINTS ${zephyr_base}) +project(thingset_example_interactive_com) + +target_sources(app PRIVATE + src/main.c + # test data & support + ${THINGSET_BASE}/test/test_support.c + ${THINGSET_BASE}/test/test_data.c +) + diff --git a/zephyr/examples/interactive_com/Kconfig b/zephyr/examples/interactive_com/Kconfig new file mode 100644 index 0000000..899944e --- /dev/null +++ b/zephyr/examples/interactive_com/Kconfig @@ -0,0 +1,6 @@ +# Copyright (c) 2022 Bobby Noelte. +# SPDX-License-Identifier: Apache-2.0 + +mainmenu "ThingSet for Zepyhr - examples/basic configuration" + +source "Kconfig.zephyr" diff --git a/zephyr/examples/interactive_com/prj.conf b/zephyr/examples/interactive_com/prj.conf new file mode 100644 index 0000000..5cabda1 --- /dev/null +++ b/zephyr/examples/interactive_com/prj.conf @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_THINGSET=y + +# Enable all contexts for testing +CONFIG_THINGSET_CORE=y +CONFIG_THINGSET_COM=y +CONFIG_THINGSET_LOCAL_COUNT=3 +CONFIG_THINGSET_PORT_COUNT=5 + +# Enable unit testing +CONFIG_THINGSET_UNIT_TEST=n +CONFIG_ZTEST=n +CONFIG_COVERAGE=n + +# Activate assertions during testing +CONFIG_ASSERT=n + +# enable all optional features to increase test coverage +CONFIG_THINGSET_64BIT_TYPES_SUPPORT=y +CONFIG_THINGSET_DECFRAC_TYPE_SUPPORT=y +CONFIG_THINGSET_BYTE_STRING_TYPE_SUPPORT=y + +CONFIG_LOG=n +#CONFIG_LOG_BACKEND_NATIVE_POSIX=y +#CONFIG_THINGSET_LOG_LEVEL_DBG=y + +# enable loopback port for testing +CONFIG_THINGSET_PORT_LOOPBACK_SIMPLE=y + +# enable testing of applications +CONFIG_THINGSET_APPS=y + +# enable shell application on UART0 +CONFIG_UART_NATIVE_POSIX=y +CONFIG_NATIVE_UART_0_ON_OWN_PTY=y +CONFIG_SHELL=y +CONFIG_SHELL_BACKEND_DUMMY=y +CONFIG_SHELL_BACKEND_SERIAL=y +CONFIG_THINGSET_SHELL=y +CONFIG_THINGSET_SHELL_NAME="shell" +CONFIG_THINGSET_SHELL_LOCID=1 +CONFIG_THINGSET_SHELL_PORTID=2 diff --git a/zephyr/examples/interactive_com/sample.yaml b/zephyr/examples/interactive_com/sample.yaml new file mode 100644 index 0000000..eae982f --- /dev/null +++ b/zephyr/examples/interactive_com/sample.yaml @@ -0,0 +1,2 @@ +sample: + name: ThingSet Interactive Example diff --git a/zephyr/examples/interactive_com/src/main.c b/zephyr/examples/interactive_com/src/main.c new file mode 100644 index 0000000..fa969e0 --- /dev/null +++ b/zephyr/examples/interactive_com/src/main.c @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2022 Bobby Noelte + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "../../../../test/test.h" + +void main(void) +{ + (void)thingset_init(TEST_CORE_LOCID); + (void)thingset_init(TEST_INSTANCE_LOCID); + (void)thingset_init(TEST_NEIGHBOUR_LOCID); + thingset_authorisation_set(TEST_CORE_LOCID, TS_ANY_RW); + thingset_authorisation_set(TEST_INSTANCE_LOCID, TS_ANY_RW); + thingset_authorisation_set(TEST_NEIGHBOUR_LOCID, TS_ANY_RW); +} From 58004275ae158c70fb9f85f595bb3c6f344c5884 Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Wed, 26 Jan 2022 08:28:21 +0100 Subject: [PATCH 32/37] COM prepare - add examples executable for native implementation Prepare for ThingSet Communication Context: - add examples executable with CMake build that fits to native environment Signed-off-by: Bobby Noelte --- native/examples/basic/CMakeLists.txt | 60 +++++++++++++++ native/examples/basic/config.h | 20 +++++ native/examples/interactive/CMakeLists.txt | 70 ++++++++++++++++++ native/examples/interactive/config.h | 27 +++++++ .../examples/interactive_com/CMakeLists.txt | 74 +++++++++++++++++++ native/examples/interactive_com/config.h | 37 ++++++++++ 6 files changed, 288 insertions(+) create mode 100644 native/examples/basic/CMakeLists.txt create mode 100644 native/examples/basic/config.h create mode 100644 native/examples/interactive/CMakeLists.txt create mode 100644 native/examples/interactive/config.h create mode 100644 native/examples/interactive_com/CMakeLists.txt create mode 100644 native/examples/interactive_com/config.h diff --git a/native/examples/basic/CMakeLists.txt b/native/examples/basic/CMakeLists.txt new file mode 100644 index 0000000..c9d7c54 --- /dev/null +++ b/native/examples/basic/CMakeLists.txt @@ -0,0 +1,60 @@ +# Copyright (c) 2021 Martin Jäger / Libre Solar +# Copyright (c) 2022 Bobby Noelte +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.10) + +project(thingset_example_basic) + +get_filename_component(THINGSET_BASE ${CMAKE_CURRENT_SOURCE_DIR}/../../../.. DIRECTORY) +message(STATUS "THINGSET_BASE: ${THINGSET_BASE}") + +include_directories(${CMAKE_CURRENT_SOURCE_DIR} + ${THINGSET_BASE}/src) + +add_executable(example + ${THINGSET_BASE}/examples/basic/main.c +) + +add_library(ts STATIC "") +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_app.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_cobs.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_cmd.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_core.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_export.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_msg.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_node.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_obj.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_process.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_jsmn.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_coder.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_export.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_log.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_proto.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_value.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_obj.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_obj_log.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_port.c) +# Native implementation +# - buf, bufq, time +target_sources(ts PRIVATE ${THINGSET_BASE}/native/thingset/ts_impl_buf.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/thingset/ts_impl_bufq.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/thingset/ts_impl_time.c) +# - TinyCBOR support lib +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborencoder.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborencoder_close_container_checked.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborencoder_float.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborerrorstrings.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborparser.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborparser_float.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborpretty.c) + +target_link_libraries(example ts) + +# for math.h functions +target_link_libraries(example m) + +# for thingset device library configuration by header file +add_definitions(-DTHINGSET_CONFIG_HEADER="config.h") diff --git a/native/examples/basic/config.h b/native/examples/basic/config.h new file mode 100644 index 0000000..17edf4f --- /dev/null +++ b/native/examples/basic/config.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2021 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Basic example configuration. + */ + +#ifndef CONFIG_H_ +#define CONFIG_H_ + +#define TS_CONFIG_CORE 1 +#define TS_CONFIG_COM 0 + +#define TS_CONFIG_ASSERT 1 +#define TS_CONFIG_LOG 1 + +#endif /* CONFIG_H_ */ diff --git a/native/examples/interactive/CMakeLists.txt b/native/examples/interactive/CMakeLists.txt new file mode 100644 index 0000000..b2f652a --- /dev/null +++ b/native/examples/interactive/CMakeLists.txt @@ -0,0 +1,70 @@ +# Copyright (c) 2021 Martin Jäger / Libre Solar +# Copyright (c) 2022 Bobby Noelte +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.10) + +project(thingset_example_interactive) + +get_filename_component(THINGSET_BASE ${CMAKE_CURRENT_SOURCE_DIR}/../../../.. DIRECTORY) +message(STATUS "THINGSET_BASE: ${THINGSET_BASE}") + +include_directories(${CMAKE_CURRENT_SOURCE_DIR} + ${THINGSET_BASE}/src) + +set(CMAKE_CXX_FLAGS -pthread) + +add_executable(example + ${THINGSET_BASE}/examples/interactive/main.cpp + ${THINGSET_BASE}/examples/interactive/things.c +) + +add_library(ts STATIC "") +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_app.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_cobs.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_cmd.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_core.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_export.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_msg.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_node.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_obj.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_process.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_jsmn.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_coder.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_export.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_log.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_proto.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_value.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_obj.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_obj_log.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_port.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_time.c) +# Native implementation +# - buf, bufq, time +target_sources(ts PRIVATE ${THINGSET_BASE}/native/thingset/ts_impl_buf.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/thingset/ts_impl_bufq.c) +# - TinyCBOR support lib +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborencoder.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborencoder_close_container_checked.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborencoder_float.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborerrorstrings.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborparser.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborparser_float.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborpretty.c) + +# applications +target_sources(ts PRIVATE ${THINGSET_BASE}/apps/shell/ts_shell.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/apps/shell/ts_shell_abuf.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/apps/shell/ts_shell_g.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/thingset/ts_impl_shell.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/thingset/ts_impl_shell_linenoise.c) + +target_link_libraries(example ts) + +# for math.h functions +target_link_libraries(example m) + +# for thingset device library configuration by header file +add_definitions(-g -DTHINGSET_CONFIG_HEADER="config.h") diff --git a/native/examples/interactive/config.h b/native/examples/interactive/config.h new file mode 100644 index 0000000..3d1b125 --- /dev/null +++ b/native/examples/interactive/config.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Interactive example configuration. + */ + +#ifndef CONFIG_H_ +#define CONFIG_H_ + +#define TS_CONFIG_CORE 1 +#define TS_CONFIG_COM 0 + +#define TS_CONFIG_64BIT_TYPES_SUPPORT 1 +#define TS_CONFIG_DECFRAC_TYPE_SUPPORT 1 + +/* enable shell application */ +#define CONFIG_THINGSET_SHELL 1 +#define CONFIG_THINGSET_SHELL_NAME "shell" +#define CONFIG_THINGSET_SHELL_LOCID TS_CONFIG_CORE_LOCID +#define CONFIG_THINGSET_SHELL_PORTID 0 +#define CONFIG_THINGSET_SHELL_MEM_SIZE 30000 + +#endif /* CONFIG_H_ */ diff --git a/native/examples/interactive_com/CMakeLists.txt b/native/examples/interactive_com/CMakeLists.txt new file mode 100644 index 0000000..7ac63c0 --- /dev/null +++ b/native/examples/interactive_com/CMakeLists.txt @@ -0,0 +1,74 @@ +# Copyright (c) 2021 Martin Jäger / Libre Solar +# Copyright (c) 2022 Bobby Noelte +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.10) + +project(thingset_example_interactive) + +get_filename_component(THINGSET_BASE ${CMAKE_CURRENT_SOURCE_DIR}/../../../.. DIRECTORY) +message(STATUS "THINGSET_BASE: ${THINGSET_BASE}") + +include_directories(${CMAKE_CURRENT_SOURCE_DIR} + ${THINGSET_BASE}/src + ${THINGSET_BASE}/test + ${THINGSET_BASE}/native/unity/src) + +set(CMAKE_CXX_FLAGS -pthread ) + +add_executable(example + ${THINGSET_BASE}/examples/interactive_com/main.c + # test data & support + ${THINGSET_BASE}/test/test_support.c + ${THINGSET_BASE}/test/test_data.c + ${THINGSET_BASE}/native/unity/src/unity.c +) + +add_library(ts STATIC "") +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_app.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_cobs.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_cmd.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_core.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_export.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_msg.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_node.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_obj.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_process.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_jsmn.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_coder.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_export.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_log.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_proto.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_value.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_obj.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_obj_log.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_port.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_time.c) +# Native implementation +# - buf, bufq, time +target_sources(ts PRIVATE ${THINGSET_BASE}/native/thingset/ts_impl_buf.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/thingset/ts_impl_bufq.c) +# - TinyCBOR support lib +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborencoder.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborencoder_close_container_checked.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborencoder_float.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborerrorstrings.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborparser.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborparser_float.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/native/tinycbor/src/cborpretty.c) + +# ports +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ports/loopback_simple/loopback_simple.c) + +# applications +add_subdirectory(${THINGSET_BASE}/native/apps/shell ${THINGSET_BASE}/build/shell) + +target_link_libraries(example ts) + +# for math.h functions +target_link_libraries(example m) + +# for thingset device library configuration by header file +add_definitions(-g -DTHINGSET_CONFIG_HEADER="config.h") diff --git a/native/examples/interactive_com/config.h b/native/examples/interactive_com/config.h new file mode 100644 index 0000000..1b31921 --- /dev/null +++ b/native/examples/interactive_com/config.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Interactive com example configuration. + */ + +#ifndef CONFIG_H_ +#define CONFIG_H_ + +#define CONFIG_THINGSET_CORE 1 +#define CONFIG_THINGSET_COM 1 + +/* We use test data for the example */ + +#define CONFIG_THINGSET_LOCAL_COUNT 3 +#define CONFIG_THINGSET_PORT_COUNT 5 + +#define CONFIG_THINGSET_UNIT_TEST 1 +#define CONFIG_THINGSET_LOG 1 +#define CONFIG_THINGSET_LOG_LEVEL 4 +#define CONFIG_THINGSET_ASSERT 1 + +#define TS_CONFIG_64BIT_TYPES_SUPPORT 1 +#define TS_CONFIG_DECFRAC_TYPE_SUPPORT 1 + +/* enable shell application */ +#define CONFIG_THINGSET_SHELL 1 +#define CONFIG_THINGSET_SHELL_NAME "shell" +#define CONFIG_THINGSET_SHELL_LOCID 1 +#define CONFIG_THINGSET_SHELL_PORTID 2 +#define CONFIG_THINGSET_SHELL_MEM_SIZE 30000 + +#endif /* CONFIG_H_ */ From 437288e088d12273bc04b692e2bbb92c63e7c73c Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Sun, 19 Dec 2021 12:41:36 +0100 Subject: [PATCH 33/37] COM - activate communication context and Zephyr/ Native implementations Activate ThingSet Communication Context: - Replace thingset.h with collection of new thingset headers - Adapt CMake build system for communication context and related changes/ additions. - Adapt Kconfig for communication context and related additions. - Add all configuration options to ts_config.h and and rename options to better the configuration purpose. - Remove files that are replaced or where the functions were propagated to other files. Signed-off-by: Bobby Noelte --- CMakeLists.txt | 23 + platformio.ini | 6 +- src/CMakeLists.txt | 29 +- src/cbor.c | 688 ---------------------- src/cbor.h | 329 ----------- src/jsmn.c | 313 ---------- src/jsmn.h | 79 --- src/thingset.c | 131 ----- src/thingset.h | 947 ++----------------------------- src/thingset_bin.c | 711 ----------------------- src/thingset_priv.h | 209 ------- src/thingset_txt.c | 834 --------------------------- src/ts_config.h | 480 ++++++++++++++-- zephyr/CMakeLists.txt | 55 +- zephyr/Kconfig.thingset | 49 +- zephyr/Kconfig.thingset_apps | 13 + zephyr/Kconfig.thingset_com | 37 ++ zephyr/Kconfig.thingset_core | 11 + zephyr/libc/minimal/extensions.c | 238 -------- zephyr/thingset_zephyr.h | 76 --- 20 files changed, 716 insertions(+), 4542 deletions(-) create mode 100644 CMakeLists.txt delete mode 100644 src/cbor.c delete mode 100644 src/cbor.h delete mode 100644 src/jsmn.c delete mode 100644 src/jsmn.h delete mode 100644 src/thingset.c delete mode 100644 src/thingset_bin.c delete mode 100644 src/thingset_priv.h delete mode 100644 src/thingset_txt.c create mode 100644 zephyr/Kconfig.thingset_apps create mode 100644 zephyr/Kconfig.thingset_com create mode 100644 zephyr/Kconfig.thingset_core delete mode 100644 zephyr/libc/minimal/extensions.c delete mode 100644 zephyr/thingset_zephyr.h diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..92689ed --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright (c) 2021 Martin Jäger / Libre Solar +# Copyright (c) 2021 Bobby Noelte. +# +# SPDX-License-Identifier: Apache-2.0 + +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_app.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_cbor.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_cmd.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_core.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_export.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_msg.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_obj.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/thingset_process.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_jsmn.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_coder.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_export.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_log.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_proto.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_value.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_obj.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_obj_log.c) diff --git a/platformio.ini b/platformio.ini index 2dd0f64..9c63164 100644 --- a/platformio.ini +++ b/platformio.ini @@ -33,9 +33,9 @@ build_flags = -pthread -Wall -Wno-deprecated-declarations - -D TS_64BIT_TYPES_SUPPORT=1 - -D TS_DECFRAC_TYPE_SUPPORT=1 - -D TS_BYTE_STRING_TYPE_SUPPORT=1 + -D TS_CONFIG_64BIT_TYPES_SUPPORT=1 + -D TS_CONFIG_DECFRAC_TYPE_SUPPORT=1 + -D TS_CONFIG_BYTE_STRING_TYPE_SUPPORT=1 # include src directory (otherwise unit-tests will only include lib directory) test_build_project_src = true diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6f1701f..919c6c1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,10 +1,27 @@ # Copyright (c) 2021 Martin Jäger / Libre Solar -# Copyright (c) 2021 Bobby Noelte. +# Copyright (c) 2021..2022 Bobby Noelte. # # SPDX-License-Identifier: Apache-2.0 -target_sources(ts PRIVATE ${THINGSET_BASE}/src/thingset.c) -target_sources(ts PRIVATE ${THINGSET_BASE}/src/thingset_bin.c) -target_sources(ts PRIVATE ${THINGSET_BASE}/src/thingset_txt.c) -target_sources(ts PRIVATE ${THINGSET_BASE}/src/cbor.c) -target_sources(ts PRIVATE ${THINGSET_BASE}/src/jsmn.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_app.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_cobs.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_cmd.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_core.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_export.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_msg.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_node.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_obj.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_process.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_jsmn.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_coder.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_export.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_log.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_proto.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_value.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_obj.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_obj_log.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_port.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_rbbq.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_time.c) diff --git a/src/cbor.c b/src/cbor.c deleted file mode 100644 index 9d311a0..0000000 --- a/src/cbor.c +++ /dev/null @@ -1,688 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * Copyright (c) 2017 Martin Jäger / Libre Solar - */ - -#include "cbor.h" - -#include -#include -#include -#include - -#if TS_64BIT_TYPES_SUPPORT -int cbor_serialize_uint(uint8_t *data, uint64_t value, size_t max_len) -#else -int cbor_serialize_uint(uint8_t *data, uint32_t value, size_t max_len) -#endif -{ - if (max_len < 1) { - return 0; // error - } - else if (value <= CBOR_NUM_MAX) { - data[0] = CBOR_UINT | (uint8_t)value; - //printf("serialize: value = %.2X <= CBOR_NUM_MAX, data: %.2X\n", (uint32_t)value, data[0]); - return 1; - } - else if (value <= UINT8_MAX && max_len >= 2) { - data[0] = CBOR_UINT | CBOR_UINT8_FOLLOWS; - data[1] = value; - //printf("serialize: value = 0x%.2X < 0xFF, data: %.2X %.2X\n", (uint32_t)value, data[0], data[1]); - return 2; - } - else if (value <= UINT16_MAX && max_len >= 3) { - data[0] = CBOR_UINT | CBOR_UINT16_FOLLOWS; - data[1] = value >> 8; - data[2] = value; - //printf("serialize: value = 0x%.4X <= 0xFFFF, data: %.2X %.2X %.2X\n", (uint32_t)value, data[0], data[1], data[2]); - return 3; - } - else if (value <= UINT32_MAX && max_len >= 5) { - data[0] = CBOR_UINT | CBOR_UINT32_FOLLOWS; - data[1] = value >> 24; - data[2] = value >> 16; - data[3] = value >> 8; - data[4] = value; - //printf("serialize: value = 0x%.8X <= 0xFFFFFFFF, data: %.2X %.2X %.2X %.2X %.2X\n", (uint32_t)value, data[0], data[1], data[2], data[3], data[4]); - return 5; - } -#if TS_64BIT_TYPES_SUPPORT - else if (max_len >= 9) { - data[0] = CBOR_UINT | CBOR_UINT64_FOLLOWS; - data[1] = (value >> 32) >> 24; - data[2] = (value >> 32) >> 16; - data[3] = (value >> 32) >> 8; - data[4] = (value >> 32); - data[5] = value >> 24; - data[6] = value >> 16; - data[7] = value >> 8; - data[8] = value; - return 9; - } -#endif - else { - return 0; - } -} - -#if TS_64BIT_TYPES_SUPPORT -int cbor_serialize_int(uint8_t *data, int64_t value, size_t max_len) -#else -int cbor_serialize_int(uint8_t *data, int32_t value, size_t max_len) -#endif -{ - if (max_len < 1) { - return 0; - } - - if (value >= 0) { - return cbor_serialize_uint(data, value, max_len); - } else { - int size = cbor_serialize_uint(data, -1 - value, max_len); - data[0] |= CBOR_NEGINT; // set major type 1 for negative integer - return size; - } -} - -#if TS_DECFRAC_TYPE_SUPPORT -int cbor_serialize_decfrac(uint8_t *data, int32_t mantissa, int16_t exponent, size_t max_len) -{ - if (max_len < (2 + 3 + 5)) { - return 0; - } - - data[0] = (CBOR_TAG | CBOR_DECFRAC_ARRAY_FOLLOWS); - data[1] = 0x82; - - int len = 2; - len += cbor_serialize_int(&data[len], exponent, max_len - len); - len += cbor_serialize_int(&data[len], mantissa, max_len - len); - - return len; -} -#endif - -int cbor_serialize_float(uint8_t *data, float value, size_t max_len) -{ - if (max_len < 5) - return 0; - - data[0] = CBOR_FLOAT32; - - union { float f; uint32_t ui; } f2ui; - f2ui.f = value; - data[1] = f2ui.ui >> 24; - data[2] = f2ui.ui >> 16; - data[3] = f2ui.ui >> 8; - data[4] = f2ui.ui; - - return 5; -} - -int cbor_serialize_bool(uint8_t *data, bool value, size_t max_len) -{ - if (max_len < 1) - return 0; - - data[0] = value ? CBOR_TRUE : CBOR_FALSE; - return 1; -} - -int cbor_serialize_string(uint8_t *data, const char *value, size_t max_len) -{ - unsigned int len = strlen(value); - - //printf("serialize string: \"%s\", len = %d, max_len = %d\n", value, len, max_len); - - if (len <= CBOR_NUM_MAX && len + 1 <= max_len) { - data[0] = CBOR_TEXT | (uint8_t)len; - strcpy((char*)&data[1], value); - return len + 1; - } - else if (len < UINT8_MAX && len + 2 <= max_len) { - data[0] = CBOR_TEXT | CBOR_UINT8_FOLLOWS; - data[1] = (uint8_t)len; - strcpy((char*)&data[2], value); - return len + 2; - } - else if (len < UINT16_MAX && len + 3 <= max_len) { - data[0] = CBOR_TEXT | CBOR_UINT16_FOLLOWS; - data[1] = (uint16_t)len >> 8; - data[2] = (uint16_t)len; - strcpy((char*)&data[3], value); - return len + 3; - } - else { // string too long (more than 65535 characters) - return 0; - } -} - -#if TS_BYTE_STRING_TYPE_SUPPORT -int cbor_serialize_bytes(uint8_t *data, const uint8_t *bytes, size_t num_bytes, size_t max_len) -{ - if (num_bytes <= CBOR_NUM_MAX && num_bytes + 1 <= max_len) { - data[0] = CBOR_BYTES | (uint8_t)num_bytes; - memcpy(&data[1], bytes, num_bytes); - return num_bytes + 1; - } - else if (num_bytes < UINT8_MAX && num_bytes + 2 <= max_len) { - data[0] = CBOR_BYTES | CBOR_UINT8_FOLLOWS; - data[1] = (uint8_t)num_bytes; - memcpy((char*)&data[2], bytes, num_bytes); - return num_bytes + 2; - } - else if (num_bytes < UINT16_MAX && num_bytes + 3 <= max_len) { - data[0] = CBOR_BYTES | CBOR_UINT16_FOLLOWS; - data[1] = (uint16_t)num_bytes >> 8; - data[2] = (uint16_t)num_bytes; - memcpy((char*)&data[3], bytes, num_bytes); - return num_bytes + 3; - } - else { // too many bytes (more than 65535) - return 0; - } -} -#endif - -int _serialize_num_elements(uint8_t *data, size_t num_elements, size_t max_len) -{ - if (num_elements <= CBOR_NUM_MAX && max_len > 0) { - data[0] |= (uint8_t)num_elements; - return 1; - } - else if (num_elements < UINT8_MAX && max_len > 1) { - data[0] |= CBOR_UINT8_FOLLOWS; - data[1] = (uint8_t)num_elements; - return 2; - } - else if (num_elements < UINT16_MAX && max_len > 1) { - data[0] |= CBOR_UINT16_FOLLOWS; - data[1] = (uint16_t)num_elements >> 8; - data[2] = (uint16_t)num_elements; - return 3; - } - else { // too many elements (more than 65535) - return 0; - } -} - -int cbor_serialize_map(uint8_t *data, size_t num_elements, size_t max_len) -{ - data[0] = CBOR_MAP; - return _serialize_num_elements(data, num_elements, max_len); -} - -int cbor_serialize_array(uint8_t *data, size_t num_elements, size_t max_len) -{ - data[0] = CBOR_ARRAY; - return _serialize_num_elements(data, num_elements, max_len); -} - -#if TS_64BIT_TYPES_SUPPORT -int _cbor_uint_data(const uint8_t *data, uint64_t *bytes) -#else -int _cbor_uint_data(const uint8_t *data, uint32_t *bytes) -#endif -{ - uint8_t info = data[0] & CBOR_INFO_MASK; - - //printf("uint_data: %.2X %.2X %.2X %.2X %.2X %.2X %.2X %.2X %.2X\n", data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8]); - - if (info <= CBOR_NUM_MAX) { - *bytes = info; - return 1; - } - else if (info == CBOR_UINT8_FOLLOWS) { - *bytes = data[1]; - return 2; - } - else if (info == CBOR_UINT16_FOLLOWS) { - *bytes = data[1] << 8 | data[2]; - return 3; - } -#if TS_64BIT_TYPES_SUPPORT - else if (info == CBOR_UINT32_FOLLOWS) { - *(uint64_t*)bytes = ((uint64_t)data[1] << 24) | ((uint64_t)data[2] << 16) | - ((uint64_t)data[3] << 8) | ((uint64_t)data[4]); - return 5; - } - else if (info == CBOR_UINT64_FOLLOWS) { - *(uint64_t*)bytes = ((uint64_t)data[1] << 56) | ((uint64_t)data[2] << 48) | - ((uint64_t)data[3] << 40) | ((uint64_t)data[4] << 32) | ((uint64_t)data[5] << 24) | - ((uint64_t)data[6] << 16) | ((uint64_t)data[7] << 8) | ((uint64_t)data[8]); - return 9; - } -#else - else if (info == CBOR_UINT32_FOLLOWS) { - *bytes = data[1] << 24 | data[2] << 16 | data[3] << 8 | data[4]; - return 5; - } -#endif - else { - return 0; - } -} - -#if TS_64BIT_TYPES_SUPPORT -int cbor_deserialize_uint64(const uint8_t *data, uint64_t *value) -{ - uint64_t tmp; - int size; - uint8_t type = data[0] & CBOR_TYPE_MASK; - - if (!value || type != CBOR_UINT) - return 0; - - size = _cbor_uint_data(data, &tmp); - if (size > 0 && tmp <= UINT64_MAX) { - *value = tmp; - return size; - } - return 0; -} - -int cbor_deserialize_int64(const uint8_t *data, int64_t *value) -{ - uint64_t tmp; - int size; - uint8_t type = data[0] & CBOR_TYPE_MASK; - - if (!value || (type != CBOR_UINT && type != CBOR_NEGINT)) - return 0; - - size = _cbor_uint_data(data, &tmp); - if (size > 0) { - if (type == CBOR_UINT) { - if (tmp <= INT64_MAX) { - *value = (int64_t)tmp; - //printf("deserialize: value = 0x%.8X <= 0xFFFFFFFF, data: %.2X %.2X %.2X %.2X %.2X\n", - // (uint32_t)tmp, data[0], data[1], data[2], data[3], data[4]); - return size; - } - } - else if (type == CBOR_NEGINT) { - // check if CBOR negint fits into C int - // -1 - tmp >= -INT32_MAX - 1 | x (-1) - // 1 + tmp <= INT32_MAX + 1 - if (tmp <= INT64_MAX) { - *value = -1 - (uint64_t)tmp; - //printf("deserialize: value = %.8X, tmp = %.8X, data: %.2X %.2X %.2X %.2X %.2X\n", - // *value, (uint32_t)tmp, data[0], data[1], data[2], data[3], data[4]); - return size; - } - } - } - - return 0; -} -#endif - -int cbor_deserialize_uint32(const uint8_t *data, uint32_t *value) -{ -#if TS_64BIT_TYPES_SUPPORT - uint64_t tmp; -#else - uint32_t tmp; -#endif - int size; - uint8_t type = data[0] & CBOR_TYPE_MASK; - - //printf("deserialize: value = 0x%.8X <= 0xFFFFFFFF, data: %.2X %.2X %.2X %.2X %.2X\n", - // (uint32_t)value, data[0], data[1], data[2], data[3], data[4]); - - if (!value || type != CBOR_UINT) - return 0; - - size = _cbor_uint_data(data, &tmp); - if (size > 0 && tmp <= UINT32_MAX) { - *value = tmp; - return size; - } - return 0; -} - -int cbor_deserialize_int32(const uint8_t *data, int32_t *value) -{ -#if TS_64BIT_TYPES_SUPPORT - uint64_t tmp; -#else - uint32_t tmp; -#endif - int size; - uint8_t type = data[0] & CBOR_TYPE_MASK; - - if (!value || (type != CBOR_UINT && type != CBOR_NEGINT)) - return 0; - - size = _cbor_uint_data(data, &tmp); - if (size > 0) { - if (type == CBOR_UINT) { - if (tmp <= INT32_MAX) { - *value = (int32_t)tmp; - //printf("deserialize: value = 0x%.8X <= 0xFFFFFFFF, data: %.2X %.2X %.2X %.2X %.2X\n", - // (uint32_t)tmp, data[0], data[1], data[2], data[3], data[4]); - return size; - } - } - else if (type == CBOR_NEGINT) { - // check if CBOR negint fits into C int - // -1 - tmp >= -INT32_MAX - 1 | x (-1) - // 1 + tmp <= INT32_MAX + 1 - if (tmp <= INT32_MAX) { - *value = -1 - (uint32_t)tmp; - //printf("deserialize: value = %.8X, tmp = %.8X, data: %.2X %.2X %.2X %.2X %.2X\n", - // *value, (uint32_t)tmp, data[0], data[1], data[2], data[3], data[4]); - return size; - } - } - } - - return 0; -} - -int cbor_deserialize_uint16(const uint8_t *data, uint16_t *value) -{ - uint32_t tmp; - int size = cbor_deserialize_uint32(data, &tmp); // also checks value for null-pointer - - if (size > 0 && tmp <= UINT16_MAX) { - *value = tmp; - return size; - } - return 0; -} - -int cbor_deserialize_int16(const uint8_t *data, int16_t *value) -{ - int32_t tmp; - int size = cbor_deserialize_int32(data, &tmp); // also checks value for null-pointer - - if (size > 0 && tmp <= INT16_MAX && tmp >= INT16_MIN) { - *value = tmp; - return size; - } - return 0; -} - -#if TS_DECFRAC_TYPE_SUPPORT -int cbor_deserialize_decfrac(const uint8_t *data, int32_t *mantissa, const int16_t exponent) -{ - int pos = 0; - uint8_t type = data[0] & CBOR_TYPE_MASK; - - if (data[0] == (CBOR_TAG | CBOR_DECFRAC_ARRAY_FOLLOWS) && - data[1] == (CBOR_ARRAY | 2U)) - { - int32_t mantissa_tmp; - int16_t exponent_received; - pos = 2; - pos += cbor_deserialize_int16(&data[pos], &exponent_received); - pos += cbor_deserialize_int32(&data[pos], &mantissa_tmp); - - for (int i = exponent_received; i < exponent; i++) { - mantissa_tmp /= 10; - } - for (int i = exponent_received; i > exponent; i--) { - mantissa_tmp *= 10; - } - *mantissa = mantissa_tmp; - } - else if (data[0] == CBOR_FLOAT32) { - float value; - pos = cbor_deserialize_float(&data[pos], &value); - - for (int i = 0; i < exponent; i++) { - value /= 10.0F; - } - for (int i = 0; i > exponent; i--) { - value *= 10.0F; - } - *mantissa = (int32_t)value; - } - else if (type == CBOR_UINT || type == CBOR_NEGINT) { - int32_t value; - pos = cbor_deserialize_int32(&data[pos], &value); - - for (int i = 0; i < exponent; i++) { - value /= 10; - } - for (int i = 0; i > exponent; i--) { - value *= 10; - } - *mantissa = value; - } - - return pos; -} -#endif - -int cbor_deserialize_float(const uint8_t *data, float *value) -{ - if (!value) { - return 0; - } - - uint8_t type = data[0] & CBOR_TYPE_MASK; - if (type == CBOR_UINT) { -#if TS_64BIT_TYPES_SUPPORT - uint64_t tmp; - int len = cbor_deserialize_uint64(data, &tmp); - *value = (float)tmp; - return len; -#else - uint32_t tmp; - int len = cbor_deserialize_uint32(data, &tmp); - *value = (float)tmp; - return len; -#endif - } - else if (type == CBOR_NEGINT) { -#if TS_64BIT_TYPES_SUPPORT - int64_t tmp; - int len = cbor_deserialize_int64(data, &tmp); - *value = (float)tmp; - return len; -#else - int32_t tmp; - int len = cbor_deserialize_int32(data, &tmp); - *value = (float)tmp; - return len; -#endif - } - else if (data[0] == CBOR_FLOAT32) { - union { float f; uint32_t ui; } f2ui; - f2ui.ui = data[1] << 24 | data[2] << 16 | data[3] << 8 | data[4]; - *value = f2ui.f; - return 5; - } - return 0; -} - -int cbor_deserialize_bool(const uint8_t *data, bool *value) -{ - if (!value || !data) - return 0; - - if (data[0] == CBOR_TRUE) { - *value = true; - return 1; - } - else if (data[0] == CBOR_FALSE) { - *value = false; - return 1; - } - return 0; -} - -int cbor_deserialize_string(const uint8_t *data, char *str, uint16_t buf_size) -{ - char *str_start; - uint16_t str_len; - - int ret = cbor_deserialize_string_zero_copy(data, &str_start, &str_len); - - if (ret > 0 && str_len < buf_size) { - strncpy(str, str_start, str_len); - str[str_len] = '\0'; - return ret; - } - return 0; -} - -int cbor_deserialize_string_zero_copy(const uint8_t *data, char **str_start, uint16_t *str_len) -{ - uint8_t type = data[0] & CBOR_TYPE_MASK; - uint8_t info = data[0] & CBOR_INFO_MASK; - - if (!data || !str_start || !str_len || type != CBOR_TEXT) { - return 0; - } - - if (info <= CBOR_NUM_MAX) { - *str_len = info; - *str_start = (char *)&data[1]; - return *str_len + 1; - } - else if (info == CBOR_UINT8_FOLLOWS) { - *str_len = data[1]; - *str_start = (char *)&data[2]; - return *str_len + 2; - } - else if (info == CBOR_UINT16_FOLLOWS) { - *str_len = data[1] << 8 | data[2]; - *str_start = (char*)&data[3]; - return *str_len + 3; - } - return 0; // longer string not supported -} - -#if TS_BYTE_STRING_TYPE_SUPPORT -int cbor_deserialize_bytes(const uint8_t *data, uint8_t *bytes, uint16_t buf_size, - uint16_t *num_bytes) -{ - uint8_t type = data[0] & CBOR_TYPE_MASK; - uint8_t info = data[0] & CBOR_INFO_MASK; - uint16_t len; - - if (!bytes || type != CBOR_BYTES) - return 0; - - if (info <= CBOR_NUM_MAX) { - len = info; - if (len <= buf_size) { - memcpy(bytes, &data[1], len); - *num_bytes = len; - return len + 1; - } - } - else if (info == CBOR_UINT8_FOLLOWS) { - len = data[1]; - if (len <= buf_size) { - memcpy(bytes, &data[2], len); - *num_bytes = len; - return len + 2; - } - } - else if (info == CBOR_UINT16_FOLLOWS) { - len = data[1] << 8 | data[2]; - if (len <= buf_size) { - memcpy(bytes, &data[3], len); - *num_bytes = len; - return len + 3; - } - } - return 0; // more bytes not supported -} -#endif - -// stores size of map or array in num_elements -int cbor_num_elements(const uint8_t *data, uint16_t *num_elements) -{ - uint8_t type = data[0] & CBOR_TYPE_MASK; - uint8_t info = data[0] & CBOR_INFO_MASK; - - //printf("type: %x, info: %x\n", type, info); - - if (!num_elements) - return 0; - - // normal type (single data element) - if (type != CBOR_MAP && type != CBOR_ARRAY) { - *num_elements = 1; - return 0; - } - - if (info <= CBOR_NUM_MAX) { - *num_elements = info; - return 1; - } - else if (info == CBOR_UINT8_FOLLOWS) { - *num_elements = data[1]; - return 2; - } - else if (info == CBOR_UINT16_FOLLOWS) { - *num_elements = data[1] << 8 | data[2]; - return 3; - } - return 0; // more map/array elements not supported -} - -// determines the size of a cbor data item starting at given pointer -int cbor_size(const uint8_t *data) -{ - uint8_t type = data[0] & CBOR_TYPE_MASK; - uint8_t info = data[0] & CBOR_INFO_MASK; - - if (type == CBOR_UINT || type == CBOR_NEGINT) { - if (info <= CBOR_NUM_MAX) - return 1; - switch (info) { - case CBOR_UINT8_FOLLOWS: - return 2; - case CBOR_UINT16_FOLLOWS: - return 3; - case CBOR_UINT32_FOLLOWS: - return 5; - case CBOR_UINT64_FOLLOWS: - return 9; - } - } - else if (type == CBOR_BYTES || type == CBOR_TEXT) { - if (info <= CBOR_NUM_MAX) { - return info + 1; - } - else { - if (info == CBOR_UINT8_FOLLOWS) - return 1 + data[1]; - else if (info == CBOR_UINT16_FOLLOWS) - return 1 + (data[1] << 8 | data[2]); - else - return 0; // longer string / byte array not supported - } - } -#if TS_DECFRAC_TYPE_SUPPORT - else if (type == CBOR_TAG && info == CBOR_DECFRAC_ARRAY_FOLLOWS) { - int pos = 2; - pos += cbor_size(&data[pos]); // exponent - pos += cbor_size(&data[pos]); // mantissa - return pos; - } -#endif - else if (type == CBOR_7) { - switch (data[0]) { - case CBOR_FALSE: - case CBOR_TRUE: - return 1; - break; - case CBOR_FLOAT32: - return 5; - break; - case CBOR_FLOAT64: - return 9; - break; - } - } - - return 0; // float16, arrays, maps, tagged types, etc. curently not supported -} diff --git a/src/cbor.h b/src/cbor.h deleted file mode 100644 index bc0ea05..0000000 --- a/src/cbor.h +++ /dev/null @@ -1,329 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * Copyright (c) 2017 Martin Jäger / Libre Solar - */ - -#ifndef CBOR_H_ -#define CBOR_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "ts_config.h" - -#include -#include -#include - -#define CBOR_TYPE_MASK 0xE0 /* top 3 bits */ -#define CBOR_INFO_MASK 0x1F /* low 5 bits */ - -/* Jump Table for Initial Byte (cf. table 5) */ -#define CBOR_UINT 0x00 /* type 0 */ -#define CBOR_NEGINT 0x20 /* type 1 */ -#define CBOR_BYTES 0x40 /* type 2 */ -#define CBOR_TEXT 0x60 /* type 3 */ -#define CBOR_ARRAY 0x80 /* type 4 */ -#define CBOR_MAP 0xA0 /* type 5 */ -#define CBOR_TAG 0xC0 /* type 6 */ -#define CBOR_7 0xE0 /* type 7 (float and other types) */ - -#define CBOR_NUM_MAX 23 /* maximum number that can be directl encoded */ - -/* Major types (cf. section 2.1) */ -/* Major type 0: Unsigned integers */ -#define CBOR_UINT8_FOLLOWS 24 /* 0x18 */ -#define CBOR_UINT16_FOLLOWS 25 /* 0x19 */ -#define CBOR_UINT32_FOLLOWS 26 /* 0x1a */ -#define CBOR_UINT64_FOLLOWS 27 /* 0x1b */ - -/* Indefinite Lengths for Some Major types (cf. section 2.2) */ -#define CBOR_VAR_FOLLOWS 31 /* 0x1f */ - -/* Major type 6: Semantic tagging */ -#define CBOR_DATETIME_STRING_FOLLOWS 0 -#define CBOR_DATETIME_EPOCH_FOLLOWS 1 -#define CBOR_DECFRAC_ARRAY_FOLLOWS 4 - -/* Major type 7: Float and other types */ -#define CBOR_FALSE (CBOR_7 | 20) -#define CBOR_TRUE (CBOR_7 | 21) -#define CBOR_NULL (CBOR_7 | 22) -#define CBOR_UNDEFINED (CBOR_7 | 23) -#define CBOR_SIMPLE (CBOR_7 | 24) -#define CBOR_FLOAT16 (CBOR_7 | 25) -#define CBOR_FLOAT32 (CBOR_7 | 26) -#define CBOR_FLOAT64 (CBOR_7 | 27) -#define CBOR_BREAK (CBOR_7 | 31) - -/** - * Serialize unsigned integer value - * - * @param data Buffer where CBOR data shall be stored - * @param value Variable containing value to be serialized - * @param max_len Maximum remaining space in buffer (i.e. max length of serialized data) - * - * @returns Number of bytes added to buffer or 0 in case of error - */ -#if TS_64BIT_TYPES_SUPPORT -int cbor_serialize_uint(uint8_t *data, uint64_t value, size_t max_len); -#else -int cbor_serialize_uint(uint8_t *data, uint32_t value, size_t max_len); -#endif - -/** - * Serialize signed integer value - * - * @param data Buffer where CBOR data shall be stored - * @param value Variable containing value to be serialized - * @param max_len Maximum remaining space in buffer (i.e. max length of serialized data) - * - * @returns Number of bytes added to buffer or 0 in case of error - */ -#if TS_64BIT_TYPES_SUPPORT -int cbor_serialize_int(uint8_t *data, int64_t value, size_t max_len); -#else -int cbor_serialize_int(uint8_t *data, int32_t value, size_t max_len); -#endif - -/** - * Serialize decimal fraction (e.g. 1234 * 10^3) - * - * @param data Buffer where CBOR data shall be stored - * @param mantissa Mantissa of the value to be serialized - * @param exponent Exponent of the value to be serialized - * @param max_len Maximum remaining space in buffer (i.e. max length of serialized data) - * - * @returns Number of bytes added to buffer or 0 in case of error - */ -int cbor_serialize_decfrac(uint8_t *data, int32_t mantissa, int16_t exponent, size_t max_len); - -/** - * Serialize 32-bit float - * - * @param data Buffer where CBOR data shall be stored - * @param value Variable containing value to be serialized - * @param max_len Maximum remaining space in buffer (i.e. max length of serialized data) - * - * @returns Number of bytes added to buffer or 0 in case of error - */ -int cbor_serialize_float(uint8_t *data, float value, size_t max_len); - -/** - * Serialize boolean - * - * @param data Buffer where CBOR data shall be stored - * @param value Variable containing value to be serialized - * @param max_len Maximum remaining space in buffer (i.e. max length of serialized data) - * - * @returns Number of bytes added to buffer or 0 in case of error - */ -int cbor_serialize_bool(uint8_t *data, bool value, size_t max_len); - -/** - * Serialize string - * - * @param data Buffer where CBOR data shall be stored - * @param value Pointer to string that should be be serialized - * @param max_len Maximum remaining space in buffer (i.e. max length of serialized data) - * - * @returns Number of bytes added to buffer or 0 in case of error - */ -int cbor_serialize_string(uint8_t *data, const char *value, size_t max_len); - -/** - * Serialize bytes - * - * @param data Buffer where CBOR data shall be stored - * @param bytes Pointer to string that should be be serialized - * @param num_bytes Number of bytes from the buffer to be serialized - * @param max_len Maximum remaining space in buffer (i.e. max length of serialized data) - * - * @returns Number of bytes added to buffer or 0 in case of error - */ -int cbor_serialize_bytes(uint8_t *data, const uint8_t *bytes, size_t num_bytes, size_t max_len); - -/** - * Serialize the header (length field) of an array - * - * Actual elements of the array have to be serialized afterwards - * - * @param data Buffer where CBOR data shall be stored - * @param num_elements Number of elements in the array - * @param max_len Maximum remaining space in buffer (i.e. max length of serialized data) - * - * @returns Number of bytes added to buffer or 0 in case of error - */ -int cbor_serialize_array(uint8_t *data, size_t num_elements, size_t max_len); - -/** - * Serialize the header (length field) of a map (equivalent to JSON object) - * - * Actual elements of the map have to be serialized afterwards - * - * @param data Buffer where CBOR data shall be stored - * @param num_elements Number of elements in the array - * @param max_len Maximum remaining space in buffer (i.e. max length of serialized data) - * - * @returns number of bytes added to buffer or 0 in case of error - */ -int cbor_serialize_map(uint8_t *data, size_t num_elements, size_t max_len); - -/** - * Deserialization (CBOR data to C values) - */ - -/** - * Deserialize 64-bit unsigned integer - * - * @param data Buffer containing CBOR data with matching type - * @param value Pointer to the variable where the value should be stored - * - * @returns Number of bytes read from buffer or 0 in case of error - */ -int cbor_deserialize_uint64(const uint8_t *data, uint64_t *value); - -/** - * Deserialize 64-bit signed integer - * - * @param data Buffer containing CBOR data with matching type - * @param value Pointer to the variable where the value should be stored - * - * @returns Number of bytes read from buffer or 0 in case of error - */ -int cbor_deserialize_int64(const uint8_t *data, int64_t *value); - -/** - * Deserialize 32-bit unsigned integer - * - * @param data Buffer containing CBOR data with matching type - * @param value Pointer to the variable where the value should be stored - * - * @returns Number of bytes read from buffer or 0 in case of error - */ -int cbor_deserialize_uint32(const uint8_t *data, uint32_t *value); - -/** - * Deserialize 32-bit signed integer - * - * @param data Buffer containing CBOR data with matching type - * @param value Pointer to the variable where the value should be stored - * - * @returns Number of bytes read from buffer or 0 in case of error - */ -int cbor_deserialize_int32(const uint8_t *data, int32_t *value); - -/** - * Deserialize 16-bit unsigned integer - * - * @param data Buffer containing CBOR data with matching type - * @param value Pointer to the variable where the value should be stored - * - * @returns Number of bytes read from buffer or 0 in case of error - */ -int cbor_deserialize_uint16(const uint8_t *data, uint16_t *value); - -/** - * Deserialize 16-bit signed integer - * - * @param data Buffer containing CBOR data with matching type - * @param value Pointer to the variable where the value should be stored - * - * @returns Number of bytes read from buffer or 0 in case of error - */ -int cbor_deserialize_int16(const uint8_t *data, int16_t *value); - -/** - * Deserialize decimal fraction type - * - * The exponent is fixed, so the mantissa is multiplied to match the exponent - * - * @param data Buffer containing CBOR data with matching type - * @param mantissa Pointer to the variable where the mantissa should be stored - * @param exponent Exponent of internally used variable in C - * - * @returns Number of bytes read from buffer or 0 in case of error - */ -int cbor_deserialize_decfrac(const uint8_t *data, int32_t *mantissa, const int16_t exponent); - -/** - * Deserialize 32-bit float - * - * @param data Buffer containing CBOR data with matching type - * @param value Pointer to the variable where the value should be stored - * - * @returns Number of bytes read from buffer or 0 in case of error - */ -int cbor_deserialize_float(const uint8_t *data, float *value); - -/** - * Deserialize bool - * - * @param data Buffer containing CBOR data with matching type - * @param value Pointer to the variable where the value should be stored - * - * @returns Number of bytes read from buffer or 0 in case of error - */ -int cbor_deserialize_bool(const uint8_t *data, bool *value); - -/** - * Deserialize string - * - * @param data Buffer containing CBOR data with matching type - * @param str Pointer to the buffer where the string should be stored - * @param buf_size Size of the string buffer including null-termination - * - * @returns Number of bytes read from data buffer or 0 in case of error - */ -int cbor_deserialize_string(const uint8_t *data, char *str, uint16_t buf_size); - -/** - * Deserialize string with zero-copy - * - * @param data Buffer containing CBOR data with matching type - * @param str_start Pointer to store start of string - * @param str_len Pointer to store length of string in the buffer EXCLUDING null-termination - * - * @returns Number of bytes read from data buffer or 0 in case of error - */ -int cbor_deserialize_string_zero_copy(const uint8_t *data, char **str_start, uint16_t *str_len); - -/** - * Deserialize bytes - * - * @param data Buffer containing CBOR data with matching type - * @param bytes Pointer to the buffer where the data should be stored - * @param buf_size Size of the buffer - * @param num_bytes Pointer to a variable to store the actual number of bytes written to buffer - * - * @returns Number of bytes read from data buffer or 0 in case of error - */ -int cbor_deserialize_bytes(const uint8_t *data, uint8_t *bytes, uint16_t buf_size, - uint16_t *num_bytes); - -/** - * Determine the number of elements in a map or an array - * - * @param data Buffer containing CBOR data with matching type - * @param num_elements Pointer to the variable where the result should be stored - * - * @returns Number of bytes read from buffer or 0 in case of error - */ -int cbor_num_elements(const uint8_t *data, uint16_t *num_elements); - -/** - * Determine the size of the cbor data item - * - * @param data Pointer for starting point of data item - * - * @returns Size in bytes - */ -int cbor_size(const uint8_t *data); - -#ifdef __cplusplus -} -#endif - -#endif /* CBOR_H_ */ diff --git a/src/jsmn.c b/src/jsmn.c deleted file mode 100644 index 4759771..0000000 --- a/src/jsmn.c +++ /dev/null @@ -1,313 +0,0 @@ -#include "jsmn.h" - -/** - * Allocates a fresh unused token from the token pool. - */ -static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, - jsmntok_t *tokens, size_t num_tokens) { - jsmntok_t *tok; - if (parser->toknext >= num_tokens) { - return NULL; - } - tok = &tokens[parser->toknext++]; - tok->start = tok->end = -1; - tok->size = 0; -#ifdef JSMN_PARENT_LINKS - tok->parent = -1; -#endif - return tok; -} - -/** - * Fills token type and boundaries. - */ -static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, - int start, int end) { - token->type = type; - token->start = start; - token->end = end; - token->size = 0; -} - -/** - * Fills next available token with JSON primitive. - */ -static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, - size_t len, jsmntok_t *tokens, size_t num_tokens) { - jsmntok_t *token; - int start; - - start = parser->pos; - - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - switch (js[parser->pos]) { -#ifndef JSMN_STRICT - /* In strict mode primitive must be followed by "," or "}" or "]" */ - case ':': -#endif - case '\t' : case '\r' : case '\n' : case ' ' : - case ',' : case ']' : case '}' : - goto found; - } - if (js[parser->pos] < 32 || js[parser->pos] >= 127) { - parser->pos = start; - return JSMN_ERROR_INVAL; - } - } -#ifdef JSMN_STRICT - /* In strict mode primitive must be followed by a comma/object/array */ - parser->pos = start; - return JSMN_ERROR_PART; -#endif - -found: - if (tokens == NULL) { - parser->pos--; - return 0; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) { - parser->pos = start; - return JSMN_ERROR_NOMEM; - } - jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif - parser->pos--; - return 0; -} - -/** - * Fills next token with JSON string. - */ -static int jsmn_parse_string(jsmn_parser *parser, const char *js, - size_t len, jsmntok_t *tokens, size_t num_tokens) { - jsmntok_t *token; - - int start = parser->pos; - - parser->pos++; - - /* Skip starting quote */ - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - char c = js[parser->pos]; - - /* Quote: end of string */ - if (c == '\"') { - if (tokens == NULL) { - return 0; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) { - parser->pos = start; - return JSMN_ERROR_NOMEM; - } - jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif - return 0; - } - - /* Backslash: Quoted symbol expected */ - if (c == '\\' && parser->pos + 1 < len) { - int i; - parser->pos++; - switch (js[parser->pos]) { - /* Allowed escaped symbols */ - case '\"': case '/' : case '\\' : case 'b' : - case 'f' : case 'r' : case 'n' : case 't' : - break; - /* Allows escaped symbol \uXXXX */ - case 'u': - parser->pos++; - for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { - /* If it isn't a hex character we have an error */ - if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ - (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ - (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ - parser->pos = start; - return JSMN_ERROR_INVAL; - } - parser->pos++; - } - parser->pos--; - break; - /* Unexpected symbol */ - default: - parser->pos = start; - return JSMN_ERROR_INVAL; - } - } - } - parser->pos = start; - return JSMN_ERROR_PART; -} - -/** - * Parse JSON string and fill tokens. - */ -int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, - jsmntok_t *tokens, unsigned int num_tokens) { - int r; - int i; - jsmntok_t *token; - int count = parser->toknext; - - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - char c; - jsmntype_t type; - - c = js[parser->pos]; - switch (c) { - case '{': case '[': - count++; - if (tokens == NULL) { - break; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) - return JSMN_ERROR_NOMEM; - if (parser->toksuper != -1) { - tokens[parser->toksuper].size++; -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif - } - token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); - token->start = parser->pos; - parser->toksuper = parser->toknext - 1; - break; - case '}': case ']': - if (tokens == NULL) - break; - type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); -#ifdef JSMN_PARENT_LINKS - if (parser->toknext < 1) { - return JSMN_ERROR_INVAL; - } - token = &tokens[parser->toknext - 1]; - for (;;) { - if (token->start != -1 && token->end == -1) { - if (token->type != type) { - return JSMN_ERROR_INVAL; - } - token->end = parser->pos + 1; - parser->toksuper = token->parent; - break; - } - if (token->parent == -1) { - if(token->type != type || parser->toksuper == -1) { - return JSMN_ERROR_INVAL; - } - break; - } - token = &tokens[token->parent]; - } -#else - for (i = parser->toknext - 1; i >= 0; i--) { - token = &tokens[i]; - if (token->start != -1 && token->end == -1) { - if (token->type != type) { - return JSMN_ERROR_INVAL; - } - parser->toksuper = -1; - token->end = parser->pos + 1; - break; - } - } - /* Error if unmatched closing bracket */ - if (i == -1) return JSMN_ERROR_INVAL; - for (; i >= 0; i--) { - token = &tokens[i]; - if (token->start != -1 && token->end == -1) { - parser->toksuper = i; - break; - } - } -#endif - break; - case '\"': - r = jsmn_parse_string(parser, js, len, tokens, num_tokens); - if (r < 0) return r; - count++; - if (parser->toksuper != -1 && tokens != NULL) - tokens[parser->toksuper].size++; - break; - case '\t' : case '\r' : case '\n' : case ' ': - break; - case ':': - parser->toksuper = parser->toknext - 1; - break; - case ',': - if (tokens != NULL && parser->toksuper != -1 && - tokens[parser->toksuper].type != JSMN_ARRAY && - tokens[parser->toksuper].type != JSMN_OBJECT) { -#ifdef JSMN_PARENT_LINKS - parser->toksuper = tokens[parser->toksuper].parent; -#else - for (i = parser->toknext - 1; i >= 0; i--) { - if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { - if (tokens[i].start != -1 && tokens[i].end == -1) { - parser->toksuper = i; - break; - } - } - } -#endif - } - break; -#ifdef JSMN_STRICT - /* In strict mode primitives are: numbers and booleans */ - case '-': case '0': case '1' : case '2': case '3' : case '4': - case '5': case '6': case '7' : case '8': case '9': - case 't': case 'f': case 'n' : - /* And they must not be keys of the object */ - if (tokens != NULL && parser->toksuper != -1) { - jsmntok_t *t = &tokens[parser->toksuper]; - if (t->type == JSMN_OBJECT || - (t->type == JSMN_STRING && t->size != 0)) { - return JSMN_ERROR_INVAL; - } - } -#else - /* In non-strict mode every unquoted value is a primitive */ - default: -#endif - r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); - if (r < 0) return r; - count++; - if (parser->toksuper != -1 && tokens != NULL) - tokens[parser->toksuper].size++; - break; - -#ifdef JSMN_STRICT - /* Unexpected char in strict mode */ - default: - return JSMN_ERROR_INVAL; -#endif - } - } - - if (tokens != NULL) { - for (i = parser->toknext - 1; i >= 0; i--) { - /* Unmatched opened object or array */ - if (tokens[i].start != -1 && tokens[i].end == -1) { - return JSMN_ERROR_PART; - } - } - } - - return count; -} - -/** - * Creates a new parser based over a given buffer with an array of tokens - * available. - */ -void jsmn_init(jsmn_parser *parser) { - parser->pos = 0; - parser->toknext = 0; - parser->toksuper = -1; -} diff --git a/src/jsmn.h b/src/jsmn.h deleted file mode 100644 index 7ec37ec..0000000 --- a/src/jsmn.h +++ /dev/null @@ -1,79 +0,0 @@ -#ifndef __JSMN_H_ -#define __JSMN_H_ - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define JSMN_STRICT - -/** - * JSON type identifier. Basic types are: - * o Object - * o Array - * o String - * o Other primitive: number, boolean (true/false) or null - */ -typedef enum { - JSMN_UNDEFINED = 0, - JSMN_OBJECT = 1, - JSMN_ARRAY = 2, - JSMN_STRING = 3, - JSMN_PRIMITIVE = 4 -} jsmntype_t; - -enum jsmnerr { - /* Not enough tokens were provided */ - JSMN_ERROR_NOMEM = -1, - /* Invalid character inside JSON string */ - JSMN_ERROR_INVAL = -2, - /* The string is not a full JSON packet, more bytes expected */ - JSMN_ERROR_PART = -3 -}; - -/** - * JSON token description. - * type type (object, array, string etc.) - * start start position in JSON data string - * end end position in JSON data string - */ -typedef struct { - jsmntype_t type; - int16_t start; - int16_t end; - int16_t size; -#ifdef JSMN_PARENT_LINKS - int16_t parent; -#endif -} jsmntok_t; - -/** - * JSON parser. Contains an array of token blocks available. Also stores - * the string being parsed now and current position in that string - */ -typedef struct { - int16_t pos; /**< offset in the JSON string */ - int16_t toknext; /**< next token to allocate */ - int16_t toksuper; /**< superior token node, e.g parent object or array */ -} jsmn_parser; - -/** - * Create JSON parser over an array of tokens - */ -void jsmn_init(jsmn_parser *parser); - -/** - * Run JSON parser. It parses a JSON data string into and array of tokens, each describing - * a single JSON object. - */ -int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, - jsmntok_t *tokens, unsigned int num_tokens); - -#ifdef __cplusplus -} -#endif - -#endif /* __JSMN_H_ */ diff --git a/src/thingset.c b/src/thingset.c deleted file mode 100644 index 45baac1..0000000 --- a/src/thingset.c +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (c) 2017 Martin Jäger / Libre Solar - * Copyright (c) 2021 Bobby Noelte. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -/* The main implementation file of the thingset library. */ -#define THINGSET_MAIN 1 - -#include "thingset_priv.h" - -#include - - -static void _check_id_duplicates(const struct ts_data_object *data, size_t num) -{ - for (unsigned int i = 0; i < num; i++) { - for (unsigned int j = i + 1; j < num; j++) { - if (data[i].id == data[j].id) { - LOG_ERR("ThingSet error: Duplicate data object ID 0x%X.\n", data[i].id); - } - } - } -} - -int ts_init(struct ts_context *ts, struct ts_data_object *data, size_t num) -{ - _check_id_duplicates(data, num); - - ts->data_objects = data; - ts->num_objects = num; - ts->_auth_flags = TS_USR_MASK; - - return 0; -} - -int ts_process(struct ts_context *ts, const uint8_t *request, size_t request_len, - uint8_t *response, size_t response_size) -{ - // check if proper request was set before asking for a response - if (request == NULL || request_len < 1) { - return 0; - } - - // assign private variables - ts->req = request; - ts->req_len = request_len; - ts->resp = response; - ts->resp_size = response_size; - - if (ts->req[0] < 0x20) { - // binary mode request - return ts_bin_process(ts); - } - else if (ts->req[0] == '?' || ts->req[0] == '=' || ts->req[0] == '+' - || ts->req[0] == '-' || ts->req[0] == '!') { - // text mode request - return ts_txt_process(ts); - } - else { - // not a thingset command --> ignore and set response to empty string - response[0] = '\0'; - return 0; - } -} - -void ts_set_authentication(struct ts_context *ts, uint16_t flags) -{ - ts->_auth_flags = flags; -} - -struct ts_data_object *ts_get_object_by_name(struct ts_context *ts, const char *name, - size_t len, int32_t parent) -{ - for (unsigned int i = 0; i < ts->num_objects; i++) { - if (parent != -1 && ts->data_objects[i].parent != parent) { - continue; - } - else if (strncmp(ts->data_objects[i].name, name, len) == 0 - // without length check foo and fooBar would be recognized as equal - && strlen(ts->data_objects[i].name) == len) - { - return &(ts->data_objects[i]); - } - } - return NULL; -} - -struct ts_data_object *ts_get_object_by_id(struct ts_context *ts, ts_object_id_t id) -{ - for (unsigned int i = 0; i < ts->num_objects; i++) { - if (ts->data_objects[i].id == id) { - return &(ts->data_objects[i]); - } - } - return NULL; -} - -struct ts_data_object *ts_get_object_by_path(struct ts_context *ts, const char *path, size_t len) -{ - struct ts_data_object *object; - const char *start = path; - const char *end; - uint16_t parent = 0; - - // maximum depth of 10 assumed - for (int i = 0; i < 10; i++) { - end = strchr(start, '/'); - if (end == NULL || end >= path + len) { - // we are at the end of the path - return ts_get_object_by_name(ts, start, path + len - start, parent); - } - else if (end == path + len - 1) { - // path ends with slash - return ts_get_object_by_name(ts, start, end - start, parent); - } - else { - // go further down the path - object = ts_get_object_by_name(ts, start, end - start, parent); - if (object) { - parent = object->id; - start = end + 1; - } - else { - return NULL; - } - } - } - return NULL; -} diff --git a/src/thingset.h b/src/thingset.h index b59b154..9cd7e8e 100644 --- a/src/thingset.h +++ b/src/thingset.h @@ -1,896 +1,75 @@ /* * Copyright (c) 2017 Martin Jäger / Libre Solar * Copyright (c) 2021 Bobby Noelte. - * * SPDX-License-Identifier: Apache-2.0 */ -#ifndef THINGSET_H_ -#define THINGSET_H_ - -#include "ts_config.h" - -#ifdef __cplusplus -/* C++ library setup */ -extern "C" { - -#include -#include -#include - -#else -/* C library setup */ -#include -#include -#include - -#endif - -#include "jsmn.h" -#include "cbor.h" - -/* - * Protocol function codes (same as CoAP) - */ -#define TS_GET 0x01 -#define TS_POST 0x02 -#define TS_DELETE 0x04 -#define TS_FETCH 0x05 -#define TS_PATCH 0x07 // it's actually iPATCH - -#define TS_PUBMSG 0x1F - -/* - * Status codes (same as CoAP) - */ - -// success -#define TS_STATUS_CREATED 0x81 -#define TS_STATUS_DELETED 0x82 -#define TS_STATUS_VALID 0x83 -#define TS_STATUS_CHANGED 0x84 -#define TS_STATUS_CONTENT 0x85 - -// client errors -#define TS_STATUS_BAD_REQUEST 0xA0 -#define TS_STATUS_UNAUTHORIZED 0xA1 // need to authenticate -#define TS_STATUS_FORBIDDEN 0xA3 // trying to write read-only value -#define TS_STATUS_NOT_FOUND 0xA4 -#define TS_STATUS_METHOD_NOT_ALLOWED 0xA5 -#define TS_STATUS_REQUEST_INCOMPLETE 0xA8 -#define TS_STATUS_CONFLICT 0xA9 -#define TS_STATUS_REQUEST_TOO_LARGE 0xAD -#define TS_STATUS_UNSUPPORTED_FORMAT 0xAF - -// server errors -#define TS_STATUS_INTERNAL_SERVER_ERR 0xC0 -#define TS_STATUS_NOT_IMPLEMENTED 0xC1 - -// ThingSet specific errors -#define TS_STATUS_RESPONSE_TOO_LARGE 0xE1 - -/* - * Reserved data object IDs - */ -#define TS_ID_ROOT 0x00 -#define TS_ID_TIME 0x10 -#define TS_ID_NAME 0x17 -#define TS_ID_METADATAURL 0x18 -#define TS_ID_DEVICEID 0x1D - -/* - * ThingSet addressing in 29-bit CAN ID similar to SAE J1939 - * - * In order to avoid collisions with SAE J1939 and NMEA 2000, bit 25 (EDP) is always set to 1 and - * bit 24 (DP) is used to distinguish between request/response and pub/sub messages. - * - * Request/response messages using ISO-TP: - * - * -------------------------------------------------------------------- - * | 28 .. 26 | 25 | 24 | 23 .. 16 | 15 .. 8 | 7 .. 0 | - * -------------------------------------------------------------------- - * | Priority | 1 | 0 | reserved (0xDA) | target addr | source addr | - * -------------------------------------------------------------------- - * - * Bits 23..16 are set to 218 (0xDA) as suggested by ISO-TP standard (ISO 15765-2) for normal - * fixed addressing with N_TAtype = physical - * - * Control and pub/sub messages (always single-frame): - * - * -------------------------------------------------------------------- - * | 28 .. 26 | 25 | 24 | 23 .. 16 | 15 .. 8 | 7 .. 0 | - * -------------------------------------------------------------------- - * | Priority | 1 | 1 | data ID (MSB) | data ID (LSB) | source addr | - * -------------------------------------------------------------------- - * - * Priority: - * 0 .. 3: High-priority control frames - * 4 .. 7: Normal pub/sub frames for monitoring - */ - -#define TS_CAN_SOURCE_POS (0U) -#define TS_CAN_SOURCE_MASK (0xFF << TS_CAN_SOURCE_POS) -#define TS_CAN_SOURCE_SET(addr) ((uint32_t)addr << TS_CAN_SOURCE_POS) - -#define TS_CAN_TARGET_POS (8U) -#define TS_CAN_TARGET_MASK (0xFF << TS_CAN_TARGET_POS) -#define TS_CAN_TARGET_SET(addr) ((uint32_t)addr << TS_CAN_TARGET_POS) - -#define TS_CAN_DATA_ID_POS (8U) -#define TS_CAN_DATA_ID_MASK (0xFFFF << TS_CAN_DATA_ID_POS) -#define TS_CAN_DATA_ID_SET(id) ((uint32_t)id << TS_CAN_DATA_ID_POS) - -#define TS_CAN_J1939_DP (0x1 << 24U) -#define TS_CAN_J1939_EDP (0x1 << 25U) - -#define TS_CAN_TYPE_REQRESP (TS_CAN_J1939_EDP) -#define TS_CAN_TYPE_PUBSUB (TS_CAN_J1939_EDP | TS_CAN_J1939_DP) -#define TS_CAN_TYPE_MASK (TS_CAN_J1939_EDP | TS_CAN_J1939_DP) - -#define TS_CAN_PRIO_POS (26U) -#define TS_CAN_PRIO_MASK (0x7 << TS_CAN_PRIO_POS) -#define TS_CAN_PRIO_SET(prio) ((uint32_t)prio << TS_CAN_PRIO_POS) -#define TS_CAN_PRIO_GET(id) (((uint32_t)id & TS_CAN_PRIO_MASK) >> TS_CAN_PRIO_POS) - -// some pre-defined priorities -#define TS_CAN_PRIO_CONTROL_EMERGENCY (0x0 << TS_CAN_PRIO_POS) -#define TS_CAN_PRIO_CONTROL_HIGH (0x2 << TS_CAN_PRIO_POS) -#define TS_CAN_PRIO_CONTROL_LOW (0x3 << TS_CAN_PRIO_POS) -#define TS_CAN_PRIO_PUBSUB_HIGH (0x5 << TS_CAN_PRIO_POS) -#define TS_CAN_PRIO_REQRESP (0x6 << TS_CAN_PRIO_POS) -#define TS_CAN_PRIO_PUBSUB_LOW (0x7 << TS_CAN_PRIO_POS) - -// base configuration to create valid CAN IDs -#define TS_CAN_BASE_CONTROL TS_CAN_TYPE_PUBSUB -#define TS_CAN_BASE_PUBSUB TS_CAN_TYPE_PUBSUB -#define TS_CAN_BASE_REQRESP (TS_CAN_TYPE_REQRESP | 218U << 16) // N_TAtype = physical - -// below macros return true if the CAN ID matches the specified message type -#define TS_CAN_CONTROL(id) (((id & TS_CAN_TYPE_MASK) == TS_CAN_TYPE_PUBSUB) && \ - TS_CAN_PRIO_GET(id) < 4) -#define TS_CAN_PUBSUB(id) (((id & TS_CAN_TYPE_MASK) == TS_CAN_TYPE_PUBSUB) && \ - TS_CAN_PRIO_GET(id) >= 4) -#define TS_CAN_REQRESP(id) ((id & TS_CAN_TYPE_MASK) == TS_CAN_TYPE_REQRESP) - -/** - * Internal C data types (used to cast void* pointers) - */ -enum TsType { - TS_T_BOOL, - TS_T_UINT64, - TS_T_INT64, - TS_T_UINT32, - TS_T_INT32, - TS_T_UINT16, - TS_T_INT16, - TS_T_FLOAT32, - TS_T_STRING, - TS_T_BYTES, - TS_T_ARRAY, - TS_T_DECFRAC, // CBOR decimal fraction - TS_T_GROUP, // internal object to describe data hierarchy - TS_T_EXEC, // functions - TS_T_SUBSET -}; - -/** - * Data structure to specify a binary data buffer - */ -struct ts_bytes_buffer { - uint8_t *bytes; ///< Pointer to the buffer - uint16_t num_bytes; ///< Actual number of bytes in the buffer -}; - -/** - * Data structure to specify an array data object - */ -struct ts_array_info { - void *ptr; ///< Pointer to the array - uint16_t max_elements; ///< Maximum number of elements in the array - uint16_t num_elements; ///< Actual number of elements in the array - uint8_t type; ///< Type of the array elements -}; - -/* - * Functions to generate data_objects map and make compiler complain if wrong - * type is passed - */ - -#ifdef __cplusplus -static inline void *ts_bool_to_void(bool *ptr) { return (void*) ptr; } -static inline void *ts_uint64_to_void(uint64_t *ptr) { return (void*) ptr; } -static inline void *ts_int64_to_void(int64_t *ptr) { return (void*) ptr; } -static inline void *ts_uint32_to_void(uint32_t *ptr) { return (void*) ptr; } -static inline void *ts_int32_to_void(int32_t *ptr) { return (void*) ptr; } -static inline void *ts_uint16_to_void(uint16_t *ptr) { return (void*) ptr; } -static inline void *ts_int16_to_void(int16_t *ptr) { return (void*) ptr; } -static inline void *ts_float_to_void(float *ptr) { return (void*) ptr; } -static inline void *ts_string_to_void(const char *ptr) { return (void*) ptr; } -static inline void *ts_bytes_to_void(struct ts_bytes_buffer *ptr) { return (void *) ptr; } -static inline void *ts_function_to_void(void (*fnptr)()) { return (void*) fnptr; } -static inline void *ts_array_to_void(struct ts_array_info *ptr) { return (void *) ptr; } -#else -#define ts_bool_to_void(ptr) ((void*)ptr) -#define ts_uint64_to_void(ptr) ((void*)ptr) -#define ts_int64_to_void(ptr) ((void*)ptr) -#define ts_uint32_to_void(ptr) ((void*)ptr) -#define ts_int32_to_void(ptr) ((void*)ptr) -#define ts_uint16_to_void(ptr) ((void*)ptr) -#define ts_int16_to_void(ptr) ((void*)ptr) -#define ts_float_to_void(ptr) ((void*)ptr) -#define ts_string_to_void(ptr) ((void*)ptr) -#define ts_bytes_to_void(ptr) ((void*)ptr) -#define ts_function_to_void(ptr) ((void*)ptr) -#define ts_array_to_void(ptr) ((void*)ptr) -#endif - -#define TS_ITEM_BOOL(id, name, bool_ptr, parent_id, access, subsets) \ - {id, parent_id, name, ts_bool_to_void(bool_ptr), TS_T_BOOL, 0, access, subsets} - -#define TS_ITEM_UINT64(id, name, uint64_ptr, parent_id, access, subsets) \ - {id, parent_id, name, ts_uint64_to_void(uint64_ptr), TS_T_UINT64, 0, access, subsets} - -#define TS_ITEM_INT64(id, name, int64_ptr, parent_id, access, subsets) \ - {id, parent_id, name, ts_int64_to_void(int64_ptr), TS_T_INT64, 0, access, subsets} - -#define TS_ITEM_UINT32(id, name, uint32_ptr, parent_id, access, subsets) \ - {id, parent_id, name, ts_uint32_to_void(uint32_ptr), TS_T_UINT32, 0, access, subsets} - -#define TS_ITEM_INT32(id, name, int32_ptr, parent_id, access, subsets) \ - {id, parent_id, name, ts_int32_to_void(int32_ptr), TS_T_INT32, 0, access, subsets} - -#define TS_ITEM_UINT16(id, name, uint16_ptr, parent_id, access, subsets) \ - {id, parent_id, name, ts_uint16_to_void(uint16_ptr), TS_T_UINT16, 0, access, subsets} - -#define TS_ITEM_INT16(id, name, int16_ptr, parent_id, access, subsets) \ - {id, parent_id, name, ts_int16_to_void(int16_ptr), TS_T_INT16, 0, access, subsets} - -#define TS_ITEM_FLOAT(id, name, float_ptr, digits, parent_id, access, subsets) \ - {id, parent_id, name, ts_float_to_void(float_ptr), TS_T_FLOAT32, digits, access, subsets} - -#define TS_ITEM_DECFRAC(id, name, mantissa_ptr, exponent, parent_id, access, subsets) \ - {id, parent_id, name, ts_int32_to_void(mantissa_ptr), TS_T_DECFRAC, exponent, access, subsets} - -#define TS_ITEM_STRING(id, name, char_ptr, buf_size, parent_id, access, subsets) \ - {id, parent_id, name, ts_string_to_void(char_ptr), TS_T_STRING, buf_size, access, subsets} - -#define TS_ITEM_BYTES(id, name, bytes_buffer_ptr, buf_size, parent_id, access, subsets) \ - {id, parent_id, name, ts_bytes_to_void(bytes_buffer_ptr), TS_T_BYTES, buf_size, access, subsets} - -#define TS_FUNCTION(id, name, void_function_ptr, parent_id, access) \ - {id, parent_id, name, ts_function_to_void(void_function_ptr), TS_T_EXEC, 0, access, 0} - -#define TS_ITEM_ARRAY(id, name, array_info_ptr, digits, parent_id, access, subsets) \ - {id, parent_id, name, ts_array_to_void(array_info_ptr), TS_T_ARRAY, digits, access, subsets} - -#define TS_SUBSET(id, name, subset, parent_id, access) \ - {id, parent_id, name, NULL, TS_T_SUBSET, subset, access, 0} - -#define TS_GROUP(id, name, void_function_cb_ptr, parent_id) \ - {id, parent_id, name, ts_function_to_void(void_function_cb_ptr), TS_T_GROUP, 0, TS_READ_MASK, 0} - -/* - * Deprecated defines for spec v0.3 to maintain compatibility - */ - -#define TS_NODE_BOOL(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ - TS_ITEM_BOOL(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ - _Pragma ("GCC warning \"'TS_NODE_BOOL' macro is deprecated, use 'TS_ITEM_BOOL'\"") - -#define TS_NODE_UINT64(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ - TS_ITEM_UINT64(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ - _Pragma ("GCC warning \"'TS_NODE_UINT64' macro is deprecated, use 'TS_ITEM_UINT64'\"") - -#define TS_NODE_INT64(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ - TS_ITEM_INT64(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ - _Pragma ("GCC warning \"'TS_NODE_INT64' macro is deprecated, use 'TS_ITEM_INT64'\"") - -#define TS_NODE_UINT32(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ - TS_ITEM_UINT32(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ - _Pragma ("GCC warning \"'TS_NODE_UINT32' macro is deprecated, use 'TS_ITEM_UINT32'\"") - -#define TS_NODE_INT32(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ - TS_ITEM_INT32(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ - _Pragma ("GCC warning \"'TS_NODE_INT32' macro is deprecated, use 'TS_ITEM_INT32'\"") - -#define TS_NODE_UINT16(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ - TS_ITEM_UINT16(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ - _Pragma ("GCC warning \"'TS_NODE_UINT16' macro is deprecated, use 'TS_ITEM_UINT16'\"") - -#define TS_NODE_INT16(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ - TS_ITEM_INT16(_id, _name, _data_ptr, _parent, _acc, _pubsub) \ - _Pragma ("GCC warning \"'TS_NODE_INT16' macro is deprecated, use 'TS_ITEM_INT16'\"") - -#define TS_NODE_FLOAT(_id, _name, _data_ptr, _digits, _parent, _acc, _pubsub) \ - TS_ITEM_FLOAT(_id, _name, _data_ptr, _digits, _parent, _acc, _pubsub) \ - _Pragma ("GCC warning \"'TS_NODE_FLOAT' macro is deprecated, use 'TS_ITEM_FLOAT'\"") - -#define TS_NODE_STRING(_id, _name, _data_ptr, _buf_size, _parent, _acc, _pubsub) \ - TS_ITEM_STRING(_id, _name, _data_ptr, _buf_size, _parent, _acc, _pubsub) \ - _Pragma ("GCC warning \"'TS_NODE_STRING' macro is deprecated, use 'TS_ITEM_STRING'\"") - -#define TS_NODE_BYTES(_id, _name, _data_ptr, _buf_size, _parent, _acc, _pubsub) \ - TS_ITEM_BYTES(_id, _name, _data_ptr, _buf_size, _parent, _acc, _pubsub) \ - _Pragma ("GCC warning \"'TS_NODE_BYTES' macro is deprecated, use 'TS_ITEM_BYTES'\"") - -#define TS_NODE_EXEC(_id, _name, _function_ptr, _parent, _acc) \ - TS_FUNCTION(_id, _name, _function_ptr, _parent, _acc) \ - _Pragma ("GCC warning \"'TS_NODE_EXEC' macro is deprecated, use 'TS_FUNCTION'\"") - -#define TS_NODE_ARRAY(_id, _name, _data_ptr, _digits, _parent, _acc, _pubsub) \ - TS_ITEM_ARRAY(_id, _name, _data_ptr, _digits, _parent, _acc, _pubsub) \ - _Pragma ("GCC warning \"'TS_NODE_ARRAY' macro is deprecated, use 'TS_ITEM_ARRAY'\"") - -#define TS_NODE_PUBSUB(_id, _name, _pubsub_channel, _parent, _acc, _pubsub) \ - TS_SUBSET(_id, _name, _pubsub_channel, _parent, _acc) \ - _Pragma ("GCC warning \"'TS_NODE_PUBSUB' macro is deprecated, use 'TS_SUBSET'\"") - -#define TS_NODE_PATH(_id, _name, _parent, _callback) \ - TS_GROUP(_id, _name, _callback, _parent) \ - _Pragma ("GCC warning \"'TS_NODE_PATH' macro is deprecated, use 'TS_GROUP'\"") - -/* - * Defines to make data object definitions more explicit - */ -#define TS_NO_CALLBACK NULL - -/* - * Access right macros for data objects - */ -#define TS_ROLE_USR (1U << 0) // normal user -#define TS_ROLE_EXP (1U << 1) // expert user -#define TS_ROLE_MKR (1U << 2) // maker - -#define TS_READ_MASK 0x00FF // read flags stored in 4 least-significant bits -#define TS_WRITE_MASK 0xFF00 // write flags stored in 4 most-significant bits - -#define TS_USR_MASK (TS_ROLE_USR << 8 | TS_ROLE_USR) -#define TS_EXP_MASK (TS_ROLE_EXP << 8 | TS_ROLE_EXP) -#define TS_MKR_MASK (TS_ROLE_MKR << 8 | TS_ROLE_MKR) - -#define TS_READ(roles) ((roles) & TS_READ_MASK) -#define TS_WRITE(roles) (((roles) << 8) & TS_WRITE_MASK) -#define TS_READ_WRITE(roles) (TS_READ(roles) | TS_WRITE(roles)) - -#define TS_USR_R TS_READ(TS_ROLE_USR) -#define TS_EXP_R TS_READ(TS_ROLE_EXP) -#define TS_MKR_R TS_READ(TS_ROLE_MKR) -#define TS_ANY_R (TS_USR_R | TS_EXP_R | TS_MKR_R) - -#define TS_USR_W TS_WRITE(TS_ROLE_USR) -#define TS_EXP_W TS_WRITE(TS_ROLE_EXP) -#define TS_MKR_W TS_WRITE(TS_ROLE_MKR) -#define TS_ANY_W (TS_USR_W | TS_EXP_W | TS_MKR_W) - -#define TS_USR_RW TS_READ_WRITE(TS_ROLE_USR) -#define TS_EXP_RW TS_READ_WRITE(TS_ROLE_EXP) -#define TS_MKR_RW TS_READ_WRITE(TS_ROLE_MKR) -#define TS_ANY_RW (TS_USR_RW | TS_EXP_RW | TS_MKR_RW) - - -typedef uint16_t ts_object_id_t; - -/* support for legacy code with old nomenclature */ -typedef ts_object_id_t ts_node_id_t __attribute__((deprecated)); - -/** - * ThingSet data object struct. - */ -struct ts_data_object { - /** - * Data object ID - */ - const ts_object_id_t id; - - /** - * ID of parent object - */ - const ts_object_id_t parent; - - /** - * Data object name - */ - const char *name; - - /** - * Pointer to the variable containing the data. The variable type must match the type as - * specified - */ - void *const data; - - /** - * One of TS_TYPE_INT32, _FLOAT, ... - */ - const uint8_t type; - - /** - * Exponent (10^exponent = factor to convert to SI unit) for decimal fraction type, - * decimal digits to use for printing of floats in JSON strings or - * length of string buffer for string type - */ - const int16_t detail; - - /** - * Flags to define read/write access - */ - const uint16_t access; - - /** - * Flags to assign data item to different data item subsets (e.g. for publication messages) - */ - uint16_t subsets; - -}; - -/* support for legacy code with old nomenclature */ -typedef struct ts_data_object ts_data_node __attribute__((deprecated)); - -/** - * ThingSet context. - * - * Stores and handles all data objects exposed to different communication interfaces. - */ -struct ts_context { - /** - * Array of objects database provided during initialization - */ - struct ts_data_object *data_objects; - - /** - * Number of objects in the data_objects array - */ - size_t num_objects; - - /** - * Pointer to request buffer (provided in process function) - */ - const uint8_t *req; - - /** - * Length of the request - */ - size_t req_len; - - /** - * Pointer to response buffer (provided in process function) - */ - uint8_t *resp; - - /** - * Size of response buffer (i.e. maximum length) - */ - size_t resp_size; - - /** - * Pointer to the start of JSON payload in the request - */ - char *json_str; - - /** - * JSON tokes in json_str parsed by JSMN - */ - jsmntok_t tokens[TS_NUM_JSON_TOKENS]; - - /** - * Number of JSON tokens parsed by JSMN - */ - int tok_count; - - /** - * Stores current authentication status (authentication as "normal" user as default) - */ - uint16_t _auth_flags; -}; - -/** - * Initialize a ThingSet context. - * - * @param ts Pointer to ThingSet context. - * @param data Pointer to array of ThingSetDataObject type containing the entire object database - * @param num Number of elements in that array - */ -int ts_init(struct ts_context *ts, struct ts_data_object *data, size_t num); - -/** - * Process ThingSet request. - * - * This function also detects if JSON or CBOR format is used - * - * @param ts Pointer to ThingSet context. - * @param request Pointer to the ThingSet request buffer - * @param request_len Length of the data in the request buffer - * @param response Pointer to the buffer where the ThingSet response should be stored - * @param response_size Size of the response buffer, i.e. maximum allowed length of the response - * - * @returns Actual length of the response written to the buffer or 0 in case of error or if no - * response message has been generated (e.g. because a statement was processed) - */ -int ts_process(struct ts_context *ts, const uint8_t *request, size_t request_len, - uint8_t *response, size_t response_size); - -/** - * Print all data objects as a structured JSON text to stdout. - * - * WARNING: This is a recursive function and might cause stack overflows if run in constrained - * devices with large data object tree. Use with care and for testing only! - * - * @param ts Pointer to ThingSet context. - * @param obj_id Root object ID where to start with printing - * @param level Indentation level (=depth inside the data object tree) - */ -void ts_dump_json(struct ts_context *ts, ts_object_id_t obj_id, int level); - -/** - * Sets current authentication level. - * - * The authentication flags must match with access flags specified in ThingSetDataObject to allow - * read/write access to a data object. - * - * @param ts Pointer to ThingSet context. - * @param flags Flags to define authentication level (1 = access allowed) - */ -void ts_set_authentication(struct ts_context *ts, uint16_t flags); - -/** - * Retrieve data in JSON format for given subset(s). - * - * This function does not return a complete ThingSet message, but only the payload data as a - * name/value map. It can be used e.g. to store data in the EEPROM or other non-volatile memory. - * - * @param ts Pointer to ThingSet context. - * @param buf Pointer to the buffer where the data should be stored - * @param buf_size Size of the buffer, i.e. maximum allowed length of the data - * @param subsets Flags to select which subset(s) of data items should be exported - * - * @returns Actual length of the data written to the buffer or 0 in case of error - */ -int ts_txt_export(struct ts_context *ts, char *buf, size_t buf_size, uint16_t subsets); - -/** - * Generate statement message in JSON format based on pointer to group or subset. - * - * This is the fastest method to generate a statement as it does not require to search through the - * entire data objects array. - * - * @param ts Pointer to ThingSet context. - * @param buf Pointer to the buffer where the publication message should be stored - * @param buf_size Size of the message buffer, i.e. maximum allowed length of the message - * @param object Group or subset object specifying the items to be published - * - * @returns Actual length of the message written to the buffer or 0 in case of error - */ -int ts_txt_statement(struct ts_context *ts, char *buf, size_t buf_size, - struct ts_data_object *object); - -/** - * Generate statement message in JSON format based on path. - * - * @param ts Pointer to ThingSet context. - * @param buf Pointer to the buffer where the publication message should be stored - * @param buf_size Size of the message buffer, i.e. maximum allowed length of the message - * @param path Path to group or subset object specifying the items to be published - * - * @returns Actual length of the message written to the buffer or 0 in case of error - */ -int ts_txt_statement_by_path(struct ts_context *ts, char *buf, size_t buf_size, const char *path); - -/** - * Generate statement message in JSON format based on data object ID. - * - * @param ts Pointer to ThingSet context. - * @param buf Pointer to the buffer where the publication message should be stored - * @param buf_size Size of the message buffer, i.e. maximum allowed length of the message - * @param path ID of group or subset object specifying the items to be published - * - * @returns Actual length of the message written to the buffer or 0 in case of error - */ -int ts_txt_statement_by_id(struct ts_context *ts, char *buf, size_t buf_size, ts_object_id_t id); - -/** - * Retrieve data in CBOR format for given subset(s). - * - * This function does not return a complete ThingSet message, but only the payload data as an - * ID/value map. It can be used e.g. to store data in the EEPROM or other non-volatile memory. - * - * @param ts Pointer to ThingSet context. - * @param buf Pointer to the buffer where the data should be stored - * @param buf_size Size of the buffer, i.e. maximum allowed length of the data - * @param subsets Flags to select which subset(s) of data items should be exported - * - * @returns Actual length of the data written to the buffer or 0 in case of error - */ -int ts_bin_export(struct ts_context *ts, uint8_t *buf, size_t buf_size, uint16_t subsets); - -/** - * Generate statement message in CBOR format based on pointer to group or subset. - * - * This is the fastest method to generate a statement as it does not require to search through the - * entire date nodes array. - * - * @param ts Pointer to ThingSet context. - * @param buf Pointer to the buffer where the publication message should be stored - * @param buf_size Size of the message buffer, i.e. maximum allowed length of the message - * @param object Group or subset object specifying the items to be published - * - * @returns Actual length of the message written to the buffer or 0 in case of error - */ -int ts_bin_statement(struct ts_context *ts, uint8_t *buf, size_t buf_size, - struct ts_data_object *object); - /** - * Generate statement message in CBOR format based on path. - * - * @param ts Pointer to ThingSet context. - * @param buf Pointer to the buffer where the publication message should be stored - * @param buf_size Size of the message buffer, i.e. maximum allowed length of the message - * @param path Path to group or subset object specifying the items to be published - * - * @returns Actual length of the message written to the buffer or 0 in case of error + * @file + * @brief ThingSet (public interface) */ -int ts_bin_statement_by_path(struct ts_context *ts, uint8_t *buf, size_t buf_size, - const char *path); -/** - * Generate statement message in CBOR format based on data object ID. - * - * @param ts Pointer to ThingSet context. - * @param buf Pointer to the buffer where the publication message should be stored - * @param buf_size Size of the message buffer, i.e. maximum allowed length of the message - * @param path ID of group or subset object specifying the items to be published - * - * @returns Actual length of the message written to the buffer or 0 in case of error - */ -int ts_bin_statement_by_id(struct ts_context *ts, uint8_t *buf, size_t buf_size, ts_object_id_t id); - -/** - * Encode a publication message in CAN message format for supplied data object. - * - * The data may only be 8 bytes long. If the actual length of a object exceeds the available - * length, the object is silently ignored and the function continues with the next one. - * - * @param ts Pointer to ThingSet context. - * @param start_pos Position in data_objects array to start searching - * This value is updated with the next object found to allow iterating over all - * objects for this channel. It should be set to 0 to start from the beginning. - * @param subset Flag to select which subset of data items should be published - * @param can_dev_id Device ID on the CAN bus - * @param msg_id reference to can message id storage - * @param msg_data reference to the buffer where the publication message should be stored - * - * @returns Actual length of the message_data or -1 if not encodable / in case of error - */ -int ts_bin_pub_can(struct ts_context *ts, int *start_pos, uint16_t subset, uint8_t can_dev_id, - uint32_t *msg_id, uint8_t *msg_data); - -/** - * Import data in CBOR format into data objects. - * - * This function can be used to initialize data objects from previously exported data (using - * ts_bin_export function) and stored in the EEPROM or other non-volatile memory. - * - * @param ts Pointer to ThingSet context. - * @param data Buffer containing ID/value map that should be written to the data objects - * @param len Length of the data in the buffer - * @param auth_flags Authentication flags to be used in this function (to override _auth_flags) - * @param subsets Flags to select which subset(s) of data items should be imported - * - * @returns ThingSet status code - */ -int ts_bin_import(struct ts_context *ts, uint8_t *data, size_t len, uint16_t auth_flags, - uint16_t subsets); - -/** - * Get data object by ID. - * - * @param ts Pointer to ThingSet context. - * @param id Data object ID - * - * @returns Pointer to data object or NULL if object is not found - */ -struct ts_data_object *ts_get_object_by_id(struct ts_context *ts, ts_object_id_t id); - -/** - * Get data object by name. - * - * As the names are not necessarily unique in the entire data tree, the parent is needed - * - * @param ts Pointer to ThingSet context. - * @param name Data object name - * @param len Length of the object name - * @param parent Data object ID of the parent or -1 for global search - * - * @returns Pointer to data object or NULL if object is not found - */ -struct ts_data_object *ts_get_object_by_name(struct ts_context *ts, const char *name, size_t len, - int32_t parent); - -/** - * Get data object by path. - * - * Get the endpoint object of a provided path. - * - * @param ts Pointer to ThingSet context. - * @param path Path with multiple object names separated by forward slash. - * @param len Length of the entire path - * - * @returns Pointer to data object or NULL if object is not found - */ -struct ts_data_object *ts_get_object_by_path(struct ts_context *ts, const char *path, size_t len); - -#ifdef __cplusplus - -/* Provide C++ naming for C constructs. */ -typedef ts_object_id_t ThingSetObjId; -typedef struct ts_bytes_buffer ThingSetBytesBuffer; -typedef struct ts_array_info ThingSetArrayInfo; -typedef struct ts_data_object ThingSetDataObject; -typedef struct ts_context ThingSetContext; - -#if CONFIG_THINGSET_CPP_LEGACY -/* compatibility to legacy CPP interface */ -typedef ThingSetBytesBuffer TsBytesBuffer __attribute__((deprecated)); -typedef ThingSetArrayInfo ArrayInfo __attribute__((deprecated)); -typedef ThingSetDataObject DataNode __attribute__((deprecated)); -#endif - -} /* extern 'C' */ - -/** - * Main ThingSet class. - * - * Class Thingset is a C++ shim for the C implementation of ThingSet. - * See the respective C functions for a detailed description. - */ -class ThingSet -{ -public: - - inline ThingSet(ThingSetDataObject *data, size_t num) - { - (void)ts_init(&ts, data, num); - }; - - inline int process(uint8_t *request, size_t req_len, uint8_t *response, size_t resp_size) - { - return ts_process(&ts, request, req_len, response, resp_size); - }; - - inline void dump_json(ts_object_id_t obj_id = 0, int level = 0) - { - ts_dump_json(&ts, obj_id, level); - }; - - inline void set_authentication(uint16_t flags) - { - ts_set_authentication(&ts, flags); - }; - - inline int txt_export(char *buf, size_t size, const uint16_t subsets) - { - return ts_txt_export(&ts, buf, size, subsets); - }; - - inline int txt_statement(char *buf, size_t size, ThingSetDataObject *object) - { - return ts_txt_statement(&ts, buf, size, object); - }; - - inline int txt_statement(char *buf, size_t size, const char *path) - { - return ts_txt_statement_by_path(&ts, buf, size, path); - }; - - inline int txt_statement(char *buf, size_t size, ThingSetObjId id) - { - return ts_txt_statement_by_id(&ts, buf, size, id); - }; - - inline int bin_export(uint8_t *buf, size_t size, const uint16_t subsets) - { - return ts_bin_export(&ts, buf, size, subsets); - }; - - inline int bin_import(uint8_t *buf, size_t size, uint16_t auth_flags, const uint16_t subsets) - { - return ts_bin_import(&ts, buf, size, auth_flags, subsets); - }; - - inline int bin_statement(uint8_t *buf, size_t size, ThingSetDataObject *object) - { - return ts_bin_statement(&ts, buf, size, object); - }; - - inline int bin_statement(uint8_t *buf, size_t size, const char *path) - { - return ts_bin_statement_by_path(&ts, buf, size, path); - }; - - inline int bin_statement(uint8_t *buf, size_t size, ThingSetObjId id) - { - return ts_bin_statement_by_id(&ts, buf, size, id); - }; - - inline int bin_pub_can(int &start_pos, uint16_t subset, uint8_t can_dev_id, uint32_t &msg_id, - uint8_t (&msg_data)[8]) - { - return ts_bin_pub_can(&ts, &start_pos, subset, can_dev_id, &msg_id, &msg_data[0]); - }; - - inline ThingSetDataObject *get_object(ThingSetObjId id) - { - return ts_get_object_by_id(&ts, id); - }; - - inline ThingSetDataObject *get_object(const char *name, size_t len, int32_t parent = -1) - { - return ts_get_object_by_name(&ts, name, len, parent); - }; - - inline ThingSetDataObject *get_endpoint(const char *path, size_t len) - { - return ts_get_object_by_path(&ts, path, len); - }; - - /* - * Deprecated functions from ThingSet v0.3 interface - */ - - /** - * Generate statement (previously known as publication message) in JSON format. - * - * @param buf Pointer to the buffer where the publication message should be stored - * @param buf_size Size of the message buffer, i.e. maximum allowed length of the message - * @param subset Flag to select which subset of data items should be published - * - * @returns Actual length of the message written to the buffer or 0 in case of error - */ - inline int txt_pub(char *buf, size_t size, const uint16_t subset) - __attribute__((deprecated)) - { - buf[0] = '#'; - buf[1] = ' '; - int ret = ts_txt_export(&ts, &buf[2], size - 2, subset); - return (ret > 0) ? 2 + ret : 0; - }; - - /** - * Generate statement (previously known as publication message) in CBOR format. - * - * @param buf Pointer to the buffer where the publication message should be stored - * @param buf_size Size of the message buffer, i.e. maximum allowed length of the message - * @param subset Flag to select which subset of data items should be published - * - * @returns Actual length of the message written to the buffer or 0 in case of error - */ - inline int bin_pub(uint8_t *buf, size_t size, const uint16_t subset) - __attribute__((deprecated)) - { - buf[0] = TS_PUBMSG; - int ret = ts_bin_export(&ts, &buf[1], size - 1, subset); - return (ret > 0) ? 1 + ret : 0; - }; - - /** - * Update data objects based on values provided by from other pub msg. - * - * @param buf Buffer containing pub message and data that should be written to the data objects - * @param len Length of the data in the buffer - * @param auth_flags Authentication flags to be used in this function (to override _auth_flags) - * @param subset Subscribe channel (as bitfield) - * - * @returns ThingSet status code - */ - inline int bin_sub(uint8_t *cbor_data, size_t len, uint16_t auth_flags, uint16_t subsets) - __attribute__((deprecated)) - { - return ts_bin_import(&ts, cbor_data + 1, len - 1, auth_flags, subsets); - }; - -private: - - ThingSetContext ts; -}; +#ifndef THINGSET_H_ +#define THINGSET_H_ -#endif /* __cplusplus */ +#include "thingset_env.h" +#include "thingset_time.h" +#include "thingset_rbbq.h" +#include "thingset_msg.h" +#include "thingset_obj.h" +#include "thingset_port.h" +#include "thingset_ctx.h" +#include "thingset_app.h" +#include "thingset_cpp.h" + +/** @mainpage ThingSet protocol device library + +@par Table of Contents + +- Introduction + - About + - Examples + - Supported Environments + - Release Notes +- Getting Started + - Getting Started Guide + - Integration into Development Environment +- Topics + - @subpage ts_topic_port + - @subpage ts_topic_ctx + - @subpage ts_topic_app + - Developer Guide + - @ref ts_topic_rbbq +- Reference + - API Reference (public interface) + - @ref ts_time_api_pub + - @ref ts_rbbq_api_pub + - @ref ts_obj_api_pub + - @ref ts_msg_api_pub + - @ref ts_port_api_pub + - @ref ts_ctx_api_pub + - @ref ts_app_api_pub + - API Reference (private interface) + - @ref ts_macro_api_priv + - @ref ts_endian_api_priv + - @ref ts_log_api_priv + - @ref ts_mem_api_priv + - @ref ts_buf_api_priv + - @ref ts_bufq_api_priv + - @ref ts_obj_api_priv + - @ref ts_msg_api_priv + - @ref ts_port_api_priv + - @ref ts_ctx_api_priv + - @ref ts_cobs_api_priv + - @ref ts_jsmn_api_priv + - @ref ts_rbbq_api_priv + - Configuration options + - @ref ts_config_priv + - Operating System Abstraction Layer (OSAL) Implementation + - @ref ts_impl_api + - Unit Test + - @ref ts_test_api + +*/ #endif /* THINGSET_H_ */ diff --git a/src/thingset_bin.c b/src/thingset_bin.c deleted file mode 100644 index a7aca59..0000000 --- a/src/thingset_bin.c +++ /dev/null @@ -1,711 +0,0 @@ -/* - * Copyright (c) 2017 Martin Jäger / Libre Solar - * Copyright (c) 2021 Bobby Noelte. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "thingset_priv.h" - -#include "cbor.h" - -#include -#include -#include // for definition of endianness -#include // for rounding of floats - -static int cbor_deserialize_array_type(const uint8_t *buf, const struct ts_data_object *data_obj); - -static int cbor_serialize_array_type(uint8_t *buf, size_t size, - const struct ts_data_object *data_obj); - - -static int cbor_deserialize_data_obj(const uint8_t *buf, const struct ts_data_object *data_obj) -{ - switch (data_obj->type) { -#if TS_64BIT_TYPES_SUPPORT - case TS_T_UINT64: - return cbor_deserialize_uint64(buf, (uint64_t *)data_obj->data); - case TS_T_INT64: - return cbor_deserialize_int64(buf, (int64_t *)data_obj->data); -#endif - case TS_T_UINT32: - return cbor_deserialize_uint32(buf, (uint32_t *)data_obj->data); - case TS_T_INT32: - return cbor_deserialize_int32(buf, (int32_t *)data_obj->data); - case TS_T_UINT16: - return cbor_deserialize_uint16(buf, (uint16_t *)data_obj->data); - case TS_T_INT16: - return cbor_deserialize_int16(buf, (int16_t *)data_obj->data); - case TS_T_FLOAT32: - return cbor_deserialize_float(buf, (float *)data_obj->data); -#if TS_DECFRAC_TYPE_SUPPORT - case TS_T_DECFRAC: - return cbor_deserialize_decfrac(buf, (int32_t *)data_obj->data, data_obj->detail); -#endif - case TS_T_BOOL: - return cbor_deserialize_bool(buf, (bool *)data_obj->data); - case TS_T_STRING: - return cbor_deserialize_string(buf, (char *)data_obj->data, data_obj->detail); -#if TS_BYTE_STRING_TYPE_SUPPORT - case TS_T_BYTES: - return cbor_deserialize_bytes(buf, ((struct ts_bytes_buffer *)data_obj->data)->bytes, - data_obj->detail, &(((struct ts_bytes_buffer *)data_obj->data)->num_bytes)); -#endif - case TS_T_ARRAY: - return cbor_deserialize_array_type(buf, data_obj); - default: - return 0; - } -} - -static int cbor_deserialize_array_type(const uint8_t *buf, const struct ts_data_object *data_obj) -{ - uint16_t num_elements; - int pos = 0; // Index of the next value in the buffer - struct ts_array_info *array_info; - array_info = (struct ts_array_info *)data_obj->data; - - if (!array_info) { - return 0; - } - - // Deserialize the buffer length, and calculate the actual number of array elements - pos = cbor_num_elements(buf, &num_elements); - - if (num_elements > array_info->max_elements) { - return 0; - } - - for (int i = 0; i < num_elements; i++) { - switch (array_info->type) { -#if TS_64BIT_TYPES_SUPPORT - case TS_T_UINT64: - pos += cbor_deserialize_uint64(&(buf[pos]), &(((uint64_t *)array_info->ptr)[i])); - break; - case TS_T_INT64: - pos += cbor_deserialize_int64(&(buf[pos]), &(((int64_t *)array_info->ptr)[i])); - break; -#endif - case TS_T_UINT32: - pos += cbor_deserialize_uint32(&(buf[pos]), &(((uint32_t *)array_info->ptr)[i])); - break; - case TS_T_INT32: - pos += cbor_deserialize_int32(&(buf[pos]), &(((int32_t *)array_info->ptr)[i])); - break; - case TS_T_UINT16: - pos += cbor_deserialize_uint16(&(buf[pos]), &(((uint16_t *)array_info->ptr)[i])); - break; - case TS_T_INT16: - pos += cbor_deserialize_int16(&(buf[pos]), &(((int16_t *)array_info->ptr)[i])); - break; - case TS_T_FLOAT32: - pos += cbor_deserialize_float(&(buf[pos]), &(((float *)array_info->ptr)[i])); - break; - default: - break; - } - } - return pos; -} - -static int cbor_serialize_data_obj(uint8_t *buf, size_t size, const struct ts_data_object *data_obj) -{ - switch (data_obj->type) { -#if TS_64BIT_TYPES_SUPPORT - case TS_T_UINT64: - return cbor_serialize_uint(buf, *((uint64_t *)data_obj->data), size); - case TS_T_INT64: - return cbor_serialize_int(buf, *((int64_t *)data_obj->data), size); -#endif - case TS_T_UINT32: - return cbor_serialize_uint(buf, *((uint32_t *)data_obj->data), size); - case TS_T_INT32: - return cbor_serialize_int(buf, *((int32_t *)data_obj->data), size); - case TS_T_UINT16: - return cbor_serialize_uint(buf, *((uint16_t *)data_obj->data), size); - case TS_T_INT16: - return cbor_serialize_int(buf, *((int16_t *)data_obj->data), size); - case TS_T_FLOAT32: - if (data_obj->detail == 0) { // round to 0 digits: use int -#if TS_64BIT_TYPES_SUPPORT - return cbor_serialize_int(buf, llroundf(*((float *)data_obj->data)), size); -#else - return cbor_serialize_int(buf, lroundf(*((float *)data_obj->data)), size); -#endif - } - else { - return cbor_serialize_float(buf, *((float *)data_obj->data), size); - } -#if TS_DECFRAC_TYPE_SUPPORT - case TS_T_DECFRAC: - return cbor_serialize_decfrac(buf, *((int32_t *)data_obj->data), data_obj->detail, size); -#endif - case TS_T_BOOL: - return cbor_serialize_bool(buf, *((bool *)data_obj->data), size); - case TS_T_STRING: - return cbor_serialize_string(buf, (char *)data_obj->data, size); -#if TS_BYTE_STRING_TYPE_SUPPORT - case TS_T_BYTES: - return cbor_serialize_bytes(buf, ((struct ts_bytes_buffer *)data_obj->data)->bytes, - ((struct ts_bytes_buffer *)data_obj->data)->num_bytes, size); -#endif - case TS_T_ARRAY: - return cbor_serialize_array_type(buf, size, data_obj); - default: - return 0; - } -} - -int cbor_serialize_array_type(uint8_t *buf, size_t size, const struct ts_data_object *data_obj) -{ - int pos = 0; // Index of the next value in the buffer - struct ts_array_info *array_info; - array_info = (struct ts_array_info *)data_obj->data; - - if (!array_info) { - return 0; - } - - // Add the length field to the beginning of the CBOR buffer and update the CBOR buffer index - pos = cbor_serialize_array(buf, array_info->num_elements, size); - - for (int i = 0; i < array_info->num_elements; i++) { - switch (array_info->type) { -#if TS_64BIT_TYPES_SUPPORT - case TS_T_UINT64: - pos += cbor_serialize_uint(&(buf[pos]), ((uint64_t *)array_info->ptr)[i], size); - break; - case TS_T_INT64: - pos += cbor_serialize_int(&(buf[pos]), ((int64_t *)array_info->ptr)[i], size); - break; -#endif - case TS_T_UINT32: - pos += cbor_serialize_uint(&(buf[pos]), ((uint32_t *)array_info->ptr)[i], size); - break; - case TS_T_INT32: - pos += cbor_serialize_int(&(buf[pos]), ((int32_t *)array_info->ptr)[i], size); - break; - case TS_T_UINT16: - pos += cbor_serialize_uint(&(buf[pos]), ((uint16_t *)array_info->ptr)[i], size); - break; - case TS_T_INT16: - pos += cbor_serialize_int(&(buf[pos]), ((int16_t *)array_info->ptr)[i], size); - break; - case TS_T_FLOAT32: - if (data_obj->detail == 0) { // round to 0 digits: use int -#if TS_64BIT_TYPES_SUPPORT - pos += cbor_serialize_int(&(buf[pos]), - llroundf(((float *)array_info->ptr)[i]), size); -#else - pos += cbor_serialize_int(&(buf[pos]), - lroundf(((float *)array_info->ptr)[i]), size); -#endif - } - else { - pos += cbor_serialize_float(&(buf[pos]), ((float *)array_info->ptr)[i], size); - } - break; - default: - break; - } - } - return pos; -} - -int ts_bin_response(struct ts_context *ts, uint8_t code) -{ - if (ts->resp_size > 0) { - ts->resp[0] = code; - return 1; - } - else { - return 0; - } -} - -int ts_bin_process(struct ts_context *ts) -{ - int pos = 1; // current position during data processing - uint32_t ret_type = 0; - - // get endpoint (first parameter of the request) - const struct ts_data_object *endpoint = NULL; - if ((ts->req[pos] & CBOR_TYPE_MASK) == CBOR_TEXT) { - char *str_start; - uint16_t str_len; - pos += cbor_deserialize_string_zero_copy(&ts->req[pos], &str_start, &str_len); - endpoint = ts_get_object_by_path(ts, str_start, str_len); - ret_type |= TS_RET_NAMES; - } - else if ((ts->req[pos] & CBOR_TYPE_MASK) == CBOR_UINT) { - ts_object_id_t id = 0; - pos += cbor_deserialize_uint16(&ts->req[pos], &id); - endpoint = ts_get_object_by_id(ts, id); - ret_type |= TS_RET_IDS; - } - else if (ts->req[pos] == CBOR_UNDEFINED) { - pos++; - } - else { - return ts_bin_response(ts, TS_STATUS_BAD_REQUEST); - } - - // process data - if (ts->req[0] == TS_GET && endpoint) { - ret_type |= TS_RET_VALUES; - return ts_bin_get(ts, endpoint, ret_type); - } - else if (ts->req[0] == TS_FETCH) { - if (ts->req[pos] != CBOR_UNDEFINED) { - ret_type = TS_RET_VALUES; - } - return ts_bin_fetch(ts, endpoint, ret_type, pos); - } - else if (ts->req[0] == TS_PATCH && endpoint) { - int response = ts_bin_patch(ts, endpoint, pos, ts->_auth_flags, 0); - - // check if endpoint has a callback assigned - if (endpoint->data != NULL && ts->resp[0] == TS_STATUS_CHANGED) { - // create function pointer and call function - void (*fun)(void) = (void(*)(void))endpoint->data; - fun(); - } - return response; - } - else if (ts->req[0] == TS_POST) { - return ts_bin_exec(ts, endpoint, pos); - } - return ts_bin_response(ts, TS_STATUS_BAD_REQUEST); -} - -/* -* @warning The parent object is currently still ignored. Any found data object is fetched. -*/ -int ts_bin_fetch(struct ts_context *ts, const struct ts_data_object *parent, uint32_t ret_type, - unsigned int pos_payload) -{ - unsigned int pos_req = pos_payload; - unsigned int pos_resp = 0; - uint16_t num_elements, element = 0; - - if (!(ret_type & TS_RET_VALUES)) { - return ts_bin_get(ts, parent, ret_type); - } - - pos_resp += ts_bin_response(ts, TS_STATUS_CONTENT); // init response buffer - - pos_req += cbor_num_elements(&ts->req[pos_req], &num_elements); - if (num_elements != 1 && (ts->req[pos_payload] & CBOR_TYPE_MASK) != CBOR_ARRAY) { - return ts_bin_response(ts, TS_STATUS_BAD_REQUEST); - } - - //printf("fetch request, elements: %d, hex data: %x %x %x %x %x %x %x %x\n", num_elements, - // ts->req[pos_req], ts->req[pos_req+1], ts->req[pos_req+2], ts->req[pos_req+3], - // ts->req[pos_req+4], ts->req[pos_req+5], ts->req[pos_req+6], ts->req[pos_req+7]); - - if (num_elements > 1) { - pos_resp += cbor_serialize_array(&ts->resp[pos_resp], num_elements, - ts->resp_size - pos_resp); - } - - while (pos_req + 1 < ts->req_len && element < num_elements) { - - size_t num_bytes = 0; // temporary storage of cbor data length (req and resp) - - ts_object_id_t id; - num_bytes = cbor_deserialize_uint16(&ts->req[pos_req], &id); - if (num_bytes == 0) { - return ts_bin_response(ts, TS_STATUS_BAD_REQUEST); - } - pos_req += num_bytes; - - const struct ts_data_object* data_obj = ts_get_object_by_id(ts, id); - if (data_obj == NULL) { - return ts_bin_response(ts, TS_STATUS_NOT_FOUND); - } - if (!(data_obj->access & TS_READ_MASK)) { - return ts_bin_response(ts, TS_STATUS_UNAUTHORIZED); - } - - num_bytes = cbor_serialize_data_obj(&ts->resp[pos_resp], ts->resp_size - pos_resp, - data_obj); - if (num_bytes == 0) { - return ts_bin_response(ts, TS_STATUS_RESPONSE_TOO_LARGE); - } - pos_resp += num_bytes; - element++; - } - - if (element == num_elements) { - return pos_resp; - } - else { - return ts_bin_response(ts, TS_STATUS_BAD_REQUEST); - } -} - -int ts_bin_import(struct ts_context *ts, uint8_t *data, size_t len, uint16_t auth_flags, - uint16_t subsets) -{ - uint8_t resp_tmp[1] = {}; // only one character as response expected - ts->req = data; - ts->req_len = len; - ts->resp = resp_tmp; - ts->resp_size = sizeof(resp_tmp); - ts_bin_patch(ts, NULL, 0, auth_flags, subsets); - return ts->resp[0]; -} - -int ts_bin_patch(struct ts_context *ts, const struct ts_data_object *parent, - unsigned int pos_payload, uint16_t auth_flags, uint16_t subsets) -{ - unsigned int pos_req = pos_payload; - uint16_t num_elements, element = 0; - - if ((ts->req[pos_req] & CBOR_TYPE_MASK) != CBOR_MAP) { - return ts_bin_response(ts, TS_STATUS_BAD_REQUEST); - } - pos_req += cbor_num_elements(&ts->req[pos_req], &num_elements); - - //printf("patch request, elements: %d, hex data: %x %x %x %x %x %x %x %x\n", num_elements, - // ts->req[pos_req], ts->req[pos_req+1], ts->req[pos_req+2], ts->req[pos_req+3], - // ts->req[pos_req+4], ts->req[pos_req+5], ts->req[pos_req+6], ts->req[pos_req+7]); - - while (pos_req < ts->req_len && element < num_elements) { - - size_t num_bytes = 0; // temporary storage of cbor data length (req and resp) - - ts_object_id_t id; - num_bytes = cbor_deserialize_uint16(&ts->req[pos_req], &id); - if (num_bytes == 0) { - return ts_bin_response(ts, TS_STATUS_BAD_REQUEST); - } - pos_req += num_bytes; - - const struct ts_data_object* object = ts_get_object_by_id(ts, id); - if (object) { - if ((object->access & TS_WRITE_MASK & auth_flags) == 0) { - if (object->access & TS_WRITE_MASK) { - return ts_bin_response(ts, TS_STATUS_UNAUTHORIZED); - } - else { - return ts_bin_response(ts, TS_STATUS_FORBIDDEN); - } - } - else if (parent && object->parent != parent->id) { - return ts_bin_response(ts, TS_STATUS_NOT_FOUND); - } - else if (subsets && !(object->subsets & subsets)) { - // ignore element - num_bytes = cbor_size(&ts->req[pos_req]); - } - else { - // actually deserialize the data and update object - num_bytes = cbor_deserialize_data_obj(&ts->req[pos_req], object); - } - } - else { - // object not found - if (subsets) { - // ignore element - num_bytes = cbor_size(&ts->req[pos_req]); - } - else { - return ts_bin_response(ts, TS_STATUS_NOT_FOUND); - } - } - - if (num_bytes == 0) { - return ts_bin_response(ts, TS_STATUS_BAD_REQUEST); - } - pos_req += num_bytes; - - element++; - } - - if (element == num_elements) { - return ts_bin_response(ts, TS_STATUS_CHANGED); - } else { - return ts_bin_response(ts, TS_STATUS_BAD_REQUEST); - } -} - -int ts_bin_exec(struct ts_context *ts, const struct ts_data_object *object, - unsigned int pos_payload) -{ - unsigned int pos_req = pos_payload; - uint16_t num_elements, element = 0; - - if ((ts->req[pos_req] & CBOR_TYPE_MASK) != CBOR_ARRAY) { - return ts_bin_response(ts, TS_STATUS_BAD_REQUEST); - } - pos_req += cbor_num_elements(&ts->req[pos_req], &num_elements); - - if ((object->access & TS_WRITE_MASK) && (object->type == TS_T_EXEC)) { - // object is generally executable, but are we authorized? - if ((object->access & TS_WRITE_MASK & ts->_auth_flags) == 0) { - return ts_bin_response(ts, TS_STATUS_UNAUTHORIZED); - } - } - else { - return ts_bin_response(ts, TS_STATUS_FORBIDDEN); - } - - for (unsigned int i = 0; i < ts->num_objects; i++) { - if (ts->data_objects[i].parent == object->id) { - if (element >= num_elements) { - // more child objects found than parameters were passed - return ts_bin_response(ts, TS_STATUS_BAD_REQUEST); - } - int num_bytes = cbor_deserialize_data_obj(&ts->req[pos_req], &ts->data_objects[i]); - if (num_bytes == 0) { - // deserializing the value was not successful - return ts_bin_response(ts, TS_STATUS_UNSUPPORTED_FORMAT); - } - pos_req += num_bytes; - element++; - } - } - - if (num_elements > element) { - // more parameters passed than child objects found - return ts_bin_response(ts, TS_STATUS_BAD_REQUEST); - } - - // if we got here, finally create function pointer and call function - void (*fun)(void) = (void(*)(void))object->data; - fun(); - - return ts_bin_response(ts, TS_STATUS_VALID); -} - -int ts_bin_statement(struct ts_context *ts, uint8_t *buf, size_t buf_size, - struct ts_data_object *object) -{ - buf[0] = TS_PUBMSG; - int len = 1; - - if (!object || object->parent != 0) { - // currently only supporting top level objects - return 0; - } - - // serialize endpoint - len += cbor_serialize_uint(&buf[len], object->id, buf_size - len); - - if (object->type == TS_T_SUBSET) { - uint16_t subsets = object->detail; - - // find out number of elements to be serialized - int num_ids = 0; - for (unsigned int i = 0; i < ts->num_objects; i++) { - if (ts->data_objects[i].subsets & subsets) { - num_ids++; - } - } - - len += cbor_serialize_array(&buf[len], num_ids, buf_size - len); - - for (unsigned int i = 0; i < ts->num_objects; i++) { - if (ts->data_objects[i].subsets & subsets) { - size_t num_bytes = cbor_serialize_data_obj(&buf[len], buf_size - len, - &ts->data_objects[i]); - if (num_bytes == 0) { - return 0; - } - else { - len += num_bytes; - } - } - } - } - else if (object->type == TS_T_GROUP) { - // find out number of elements to be serialized - int num_ids = 0; - for (unsigned int i = 0; i < ts->num_objects; i++) { - if (ts->data_objects[i].parent == object->id) { - num_ids++; - } - } - - len += cbor_serialize_array(&buf[len], num_ids, buf_size - len); - - for (unsigned int i = 0; i < ts->num_objects; i++) { - if (ts->data_objects[i].parent == object->id) { - size_t num_bytes = cbor_serialize_data_obj(&buf[len], buf_size - len, - &ts->data_objects[i]); - if (num_bytes == 0) { - return 0; - } - else { - len += num_bytes; - } - } - } - } - else { - return 0; - } - - return len; -} - -int ts_bin_statement_by_path(struct ts_context *ts, uint8_t *buf, size_t buf_size, const char *path) -{ - return ts_bin_statement(ts, buf, buf_size, ts_get_object_by_path(ts, path, strlen(path))); -} - -int ts_bin_statement_by_id(struct ts_context *ts, uint8_t *buf, size_t buf_size, ts_object_id_t id) -{ - return ts_bin_statement(ts, buf, buf_size, ts_get_object_by_id(ts, id)); -} - -int ts_bin_export(struct ts_context *ts, uint8_t *buf, size_t buf_size, uint16_t subsets) -{ - // find out number of elements to be serialized - int num_ids = 0; - for (unsigned int i = 0; i < ts->num_objects; i++) { - if (ts->data_objects[i].subsets & subsets) { - num_ids++; - } - } - - int len = cbor_serialize_map(buf, num_ids, buf_size); - - for (unsigned int i = 0; i < ts->num_objects; i++) { - if (ts->data_objects[i].subsets & subsets) { - len += cbor_serialize_uint(&buf[len], ts->data_objects[i].id, buf_size - len); - size_t num_bytes = cbor_serialize_data_obj(&buf[len], buf_size - len, - &ts->data_objects[i]); - if (num_bytes == 0) { - return 0; - } - else { - len += num_bytes; - } - } - } - - return len; -} - -int ts_bin_pub_can(struct ts_context *ts, int *start_pos, uint16_t subset, uint8_t can_dev_id, - uint32_t *msg_id, uint8_t *msg_data) -{ - int msg_len = -1; - - for (unsigned int i = *start_pos; i < ts->num_objects; i++) { - if (ts->data_objects[i].subsets & subset) { - *msg_id = TS_CAN_BASE_PUBSUB | TS_CAN_PRIO_PUBSUB_LOW - | TS_CAN_DATA_ID_SET(ts->data_objects[i].id) - | TS_CAN_SOURCE_SET(can_dev_id); - - msg_len = cbor_serialize_data_obj(msg_data, 8, &ts->data_objects[i]); - - if (msg_len > 0) { - // object found and successfully encoded, increase start pos for next run - *start_pos = i + 1; - break; - } - // else: data too long, take next object - } - } - - if (msg_len <= 0) { - // no more objects found, reset position - *start_pos = 0; - } - - return msg_len; -} - -/* -int ThingSet::name_cbor(void) -{ - ts->resp[0] = TS_OBJ_NAME + 0x80; // Function ID - int data_obj_id = _req[1] + ((int)_req[2] << 8); - - for (unsigned int i = 0; i < sizeof(ts->data_objects)/sizeof(DataObject); i++) { - if (ts->data_objects[i].id == data_obj_id) { - if (ts->data_objects[i].access & ACCESS_READ) { - ts->resp[1] = T_STRING; - int len = strlen(ts->data_objects[i].name); - for (int j = 0; j < len; j++) { - ts->resp[j+2] = *(ts->data_objects[i].name + j); - } - #if DEBUG - serial.printf("Get Data Object Name: %s (id = %d)\n", ts->data_objects[i].name, data_obj_id); - #endif - return len + 2; - } - else { - ts->resp[1] = TS_STATUS_UNAUTHORIZED; - return 2; // length of response - } - } - } - - // data object not found --> send error message - ts->resp[1] = TS_STATUS_DATA_UNKNOWN; - return 2; // length of response -} -*/ - -int ts_bin_get(struct ts_context *ts, const struct ts_data_object *parent, uint32_t ret_type) -{ - unsigned int len = 0; // current length of response - len += ts_bin_response(ts, TS_STATUS_CONTENT); // init response buffer - - if (parent->type != TS_T_GROUP) { - len += cbor_serialize_data_obj(&ts->resp[len], ts->resp_size - len, parent); - return len; - } - - // find out number of elements - int num_elements = 0; - for (unsigned int i = 0; i < ts->num_objects; i++) { - if (ts->data_objects[i].access & TS_READ_MASK - && (ts->data_objects[i].parent == parent->id)) - { - num_elements++; - } - } - - if (ret_type & TS_RET_VALUES) { - len += cbor_serialize_map(&ts->resp[len], num_elements, ts->resp_size - len); - } - else { - len += cbor_serialize_array(&ts->resp[len], num_elements, ts->resp_size - len); - } - - for (unsigned int i = 0; i < ts->num_objects; i++) { - if (ts->data_objects[i].access & TS_READ_MASK - && (ts->data_objects[i].parent == parent->id)) - { - int num_bytes = 0; - if (ret_type & TS_RET_IDS) { - num_bytes = cbor_serialize_uint(&ts->resp[len], ts->data_objects[i].id, - ts->resp_size - len); - } - else if (ret_type & TS_RET_NAMES) { - num_bytes = cbor_serialize_string(&ts->resp[len], ts->data_objects[i].name, - ts->resp_size - len); - } - - if (ret_type & TS_RET_VALUES) { - num_bytes += cbor_serialize_data_obj(&ts->resp[len + num_bytes], - ts->resp_size - len - num_bytes, &ts->data_objects[i]); - } - - if (num_bytes == 0) { - return ts_bin_response(ts, TS_STATUS_RESPONSE_TOO_LARGE); - } - else { - len += num_bytes; - } - } - } - - return len; -} diff --git a/src/thingset_priv.h b/src/thingset_priv.h deleted file mode 100644 index 562036d..0000000 --- a/src/thingset_priv.h +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (c) 2017 Martin Jäger / Libre Solar - * Copyright (c) 2021 Bobby Noelte. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#ifndef THINGSET_PRIV_H_ -#define THINGSET_PRIV_H_ - -#include "thingset.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* ThingSet adaptations to environment */ -#if CONFIG_THINGSET_ZEPHYR - -#include "../zephyr/thingset_zephyr.h" - -#else /* ! CONFIG_THINGSET_ZEPHYR */ - -#define DEBUG 0 - -#define LOG_DBG(...) printf(__VA_ARGS__) -#define LOG_ERR(...) printf(__VA_ARGS__) -#define LOG_ALLOC_STR(str) str - -#include - -#ifdef UNIT_TEST -#include -#endif - -#endif /* CONFIG_THINGSET_ZEPHYR */ - -/** - * Internal return type flags for payload data - */ -#define TS_RET_IDS (1U << 0) -#define TS_RET_NAMES (1U << 1) -#define TS_RET_VALUES (1U << 2) - -/** - * Prepares JSMN parser, performs initial check of payload data and calls get/fetch/patch - * functions. - */ -int ts_txt_process(struct ts_context *ts); - -/** - * Performs initial check of payload data and calls get/fetch/patch functions. - */ -int ts_bin_process(struct ts_context *ts); - -/** - * GET request (text mode). - * - * List child data objects (function called without content / parameters) - * - * @param ts Pointer to ThingSet context. - */ -int ts_txt_get(struct ts_context *ts, const struct ts_data_object *parent_object, uint32_t ret_type); - -/** - * GET request (binary mode). - * - * List child data objects (function called without content) - * - * @param ts Pointer to ThingSet context. - */ -int ts_bin_get(struct ts_context *ts, const struct ts_data_object *parent, uint32_t ret_type); - -/** - * FETCH request (text mode). - * - * Read data object values (function called with an array as argument) - * - * @param ts Pointer to ThingSet context. - */ -int ts_txt_fetch(struct ts_context *ts, const struct ts_data_object *parent); - -/** - * FETCH request (binary mode). - * - * Read data object values (function called with an array as argument) - * - * @param ts Pointer to ThingSet context. - */ -int ts_bin_fetch(struct ts_context *ts, const struct ts_data_object *parent, uint32_t ret_type, - unsigned int pos_payload); - -/** - * PATCH request (text mode). - * - * Write data object values in text mode (function called with a map as argument) - * - * @param ts Pointer to ThingSet context. - */ -int ts_txt_patch(struct ts_context *ts, const struct ts_data_object *parent); - -/** - * PATCH request (binary mode). - * - * Write data object values in binary mode (function called with a map as payload) - * - * If subset is specified, objects not found are silently ignored. Otherwise, a NOT_FOUND - * error is raised. - * - * @param ts Pointer to ThingSet context. - * @param parent Pointer to path / parent object or NULL to consider any object - * @param pos_payload Position of payload in req buffer - * @param auth_flags Bitset to specify authentication status for different roles - * @param subsets Bitset to specifiy data item subsets to be considered, 0 to ignore - */ -int ts_bin_patch(struct ts_context *ts, const struct ts_data_object *parent, - unsigned int pos_payload, uint16_t auth_flags, uint16_t subsets); - -/** - * POST request to append data. - * - * @param ts Pointer to ThingSet context. - */ -int ts_txt_create(struct ts_context *ts, const struct ts_data_object *object); - -/** - * DELETE request to delete data from object. - * - * @param ts Pointer to ThingSet context. - */ -int ts_txt_delete(struct ts_context *ts, const struct ts_data_object *object); - -/** - * Execute command in text mode. - * - * Function called with a single data object name as argument. - * - * @param ts Pointer to ThingSet context. - */ -int ts_txt_exec(struct ts_context *ts, const struct ts_data_object *object); - -/** - * Execute command in binary mode (function called with a single data object name/id as argument). - * - * @param ts Pointer to ThingSet context. - * @param parent Pointer to executable object - * @param pos_payload Position of payload in req buffer - */ -int ts_bin_exec(struct ts_context *ts, const struct ts_data_object *object, - unsigned int pos_payload); - -/** - * Fill the resp buffer with a JSON response status message. - * - * @param ts Pointer to ThingSet context. - * @param code Status code - * @returns length of status message in buffer or 0 in case of error - */ -int ts_txt_response(struct ts_context *ts, int code); - -/** - * Fill the resp buffer with a CBOR response status message. - * - * @param ts Pointer to ThingSet context. - * @param code Status code - * @returns length of status message in buffer or 0 in case of error - */ -int ts_bin_response(struct ts_context *ts, uint8_t code); - -/** - * Serialize a object value into a JSON string. - * - * @param ts Pointer to ThingSet context. - * @param buf Pointer to the buffer where the JSON value should be stored - * @param size Size of the buffer, i.e. maximum allowed length of the value - * @param object Pointer to object which should be serialized - * - * @returns Length of data written to buffer or 0 in case of error - */ -int ts_json_serialize_value(struct ts_context *ts, char *buf, size_t size, - const struct ts_data_object *object); - -/** - * Serialize object name and value as JSON object. - * - * same as ts_priv_json_serialize_value, just that the object name is also serialized - */ -int ts_json_serialize_name_value(struct ts_context *ts, char *buf, size_t size, - const struct ts_data_object *object); - -/** - * Deserialize a object value from a JSON string. - * - * @param ts Pointer to ThingSet context. - * @param buf Pointer to the position of the value in a buffer - * @param len Length of value in the buffer - * @param type Type of the JSMN token as identified by the parser - * @param object Pointer to object where the deserialized value should be stored - * - * @returns Number of tokens processed (always 1) or 0 in case of error - */ -int ts_json_deserialize_value(struct ts_context *ts, char *buf, size_t len, jsmntype_t type, - const struct ts_data_object *object); - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* THINGSET_PRIV_H_ */ diff --git a/src/thingset_txt.c b/src/thingset_txt.c deleted file mode 100644 index 1ebb920..0000000 --- a/src/thingset_txt.c +++ /dev/null @@ -1,834 +0,0 @@ -/* - * Copyright (c) 2017 Martin Jäger / Libre Solar - * Copyright (c) 2021 Bobby Noelte. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "thingset_priv.h" - -#include "jsmn.h" - -#include -#include -#include -#include -#include -#include - -int ts_txt_response(struct ts_context *ts, int code) -{ - size_t pos = 0; - -#if TS_VERBOSE_STATUS_MESSAGES - const char *msg; - switch (code) { - // success - case TS_STATUS_CREATED: - msg = "Created"; - break; - case TS_STATUS_DELETED: - msg = "Deleted"; - break; - case TS_STATUS_VALID: - msg = "Valid"; - break; - case TS_STATUS_CHANGED: - msg = "Changed"; - break; - case TS_STATUS_CONTENT: - msg = "Content"; - break; - // client errors - case TS_STATUS_BAD_REQUEST: - msg = "Bad Request"; - break; - case TS_STATUS_UNAUTHORIZED: - msg = "Unauthorized"; - break; - case TS_STATUS_FORBIDDEN: - msg = "Forbidden"; - break; - case TS_STATUS_NOT_FOUND: - msg = "Not Found"; - break; - case TS_STATUS_METHOD_NOT_ALLOWED: - msg = "Method Not Allowed"; - break; - case TS_STATUS_REQUEST_INCOMPLETE: - msg = "Request Entity Incomplete"; - break; - case TS_STATUS_CONFLICT: - msg = "Conflict"; - break; - case TS_STATUS_REQUEST_TOO_LARGE: - msg = "Request Entity Too Large"; - break; - case TS_STATUS_UNSUPPORTED_FORMAT: - msg = "Unsupported Content-Format"; - break; - // server errors - case TS_STATUS_INTERNAL_SERVER_ERR: - msg = "Internal Server Error"; - break; - case TS_STATUS_NOT_IMPLEMENTED: - msg = "Not Implemented"; - break; - // ThingSet specific errors - case TS_STATUS_RESPONSE_TOO_LARGE: - msg = "Response too large"; - break; - default: - msg = "Error"; - break; - }; - pos = snprintf((char *)ts->resp, ts->resp_size, ":%.2X %s.", code, msg); -#else - pos = snprintf((char *)ts->resp, ts->resp_size, ":%.2X.", code); -#endif - if (pos < ts->resp_size) - return pos; - else - return 0; -} - -int ts_json_serialize_value(struct ts_context *ts, char *buf, size_t size, - const struct ts_data_object *object) -{ - size_t pos = 0; - struct ts_array_info *array_info; - float value; - - switch (object->type) { -#if TS_64BIT_TYPES_SUPPORT - case TS_T_UINT64: - pos = snprintf(&buf[pos], size - pos, "%" PRIu64 ",", *((uint64_t *)object->data)); - break; - case TS_T_INT64: - pos = snprintf(&buf[pos], size - pos, "%" PRIi64 ",", *((int64_t *)object->data)); - break; -#endif - case TS_T_UINT32: - pos = snprintf(&buf[pos], size - pos, "%" PRIu32 ",", *((uint32_t *)object->data)); - break; - case TS_T_INT32: - pos = snprintf(&buf[pos], size - pos, "%" PRIi32 ",", *((int32_t *)object->data)); - break; - case TS_T_UINT16: - pos = snprintf(&buf[pos], size - pos, "%" PRIu16 ",", *((uint16_t *)object->data)); - break; - case TS_T_INT16: - pos = snprintf(&buf[pos], size - pos, "%" PRIi16 ",", *((int16_t *)object->data)); - break; - case TS_T_FLOAT32: - value = *((float *)object->data); - if (isnan(value) || isinf(value)) { - /* JSON spec does not support NaN and Inf, so we need to use null instead */ - return snprintf(buf, size, "null,"); - } - else { - pos = snprintf(&buf[pos], size - pos, "%.*f,", object->detail, value); - } - break; -#if TS_DECFRAC_TYPE_SUPPORT - case TS_T_DECFRAC: - pos = snprintf(&buf[pos], size - pos, "%" PRIi32 "e%" PRIi16 ",", - *((uint32_t *)object->data), object->detail); - break; -#endif - case TS_T_BOOL: - pos = snprintf(&buf[pos], size - pos, "%s,", - (*((bool *)object->data) == true ? "true" : "false")); - break; - case TS_T_EXEC: - pos = snprintf(&buf[pos], size - pos, "["); - for (unsigned int i = 0; i < ts->num_objects; i++) { - if (ts->data_objects[i].parent == object->id) { - pos += snprintf(&buf[pos], size - pos, "\"%s\",", ts->data_objects[i].name); - } - } - if (pos > 1) { - pos--; // remove trailing comma - pos += snprintf(&buf[pos], size - pos, "],"); - } - else { - pos = 0; - pos = snprintf(&buf[pos], size - pos, "null,"); - } - break; - case TS_T_STRING: - pos = snprintf(&buf[pos], size - pos, "\"%s\",", (char *)object->data); - break; - case TS_T_SUBSET: - pos = snprintf(&buf[pos], size - pos, "["); - for (unsigned int i = 0; i < ts->num_objects; i++) { - if (ts->data_objects[i].subsets & (uint16_t)object->detail) { - pos += snprintf(&buf[pos], size - pos, "\"%s\",", ts->data_objects[i].name); - } - } - if (pos > 1) { - pos--; // remove trailing comma - } - pos += snprintf(&buf[pos], size - pos, "],"); - break; - case TS_T_ARRAY: - array_info = (struct ts_array_info *)object->data; - if (!array_info) { - return 0; - } - pos += snprintf(&buf[pos], size - pos, "["); - for (int i = 0; i < array_info->num_elements; i++) { - switch (array_info->type) { - case TS_T_UINT64: - pos += snprintf(&buf[pos], size - pos, "%" PRIu64 ",", - ((uint64_t *)array_info->ptr)[i]); - break; - case TS_T_INT64: - pos += snprintf(&buf[pos], size - pos, "%" PRIi64 ",", - ((int64_t *)array_info->ptr)[i]); - break; - case TS_T_UINT32: - pos += snprintf(&buf[pos], size - pos, "%" PRIu32 ",", - ((uint32_t *)array_info->ptr)[i]); - break; - case TS_T_INT32: - pos += snprintf(&buf[pos], size - pos, "%" PRIi32 ",", - ((int32_t *)array_info->ptr)[i]); - break; - case TS_T_UINT16: - pos += snprintf(&buf[pos], size - pos, "%" PRIu16 ",", - ((uint16_t *)array_info->ptr)[i]); - break; - case TS_T_INT16: - pos += snprintf(&buf[pos], size - pos, "%" PRIi16 ",", - ((int16_t *)array_info->ptr)[i]); - break; - case TS_T_FLOAT32: - pos += snprintf(&buf[pos], size - pos, "%.*f,", object->detail, - ((float *)array_info->ptr)[i]); - break; - default: - break; - } - } - if (array_info->num_elements > 0) { - pos--; // remove trailing comma - } - pos += snprintf(&buf[pos], size - pos, "],"); - break; - } - - if (pos < size) { - return pos; - } - else { - return 0; - } -} - -int ts_json_serialize_name_value(struct ts_context *ts, char *buf, size_t size, - const struct ts_data_object *object) -{ - size_t pos = snprintf(buf, size, "\"%s\":", object->name); - - int len_value = ts_json_serialize_value(ts, &buf[pos], size - pos, object); - pos += len_value; - - if (len_value > 0 && pos < size) { - return pos; - } - else { - return 0; - } -} - -void ts_dump_json(struct ts_context *ts, ts_object_id_t obj_id, int level) -{ - uint8_t buf[100]; - bool first = true; - if (obj_id == 0) { - printf("{"); - } - for (unsigned int i = 0; i < ts->num_objects; i++) { - if (ts->data_objects[i].parent == obj_id && ts->data_objects[i].type != TS_T_BYTES) { - if (!first) { - printf(",\n"); - } - else { - printf("\n"); - first = false; - } - if (ts->data_objects[i].type == TS_T_GROUP) { - LOG_DBG("%*s\"%s\": {", 4 * (level + 1), "", ts->data_objects[i].name); - ts_dump_json(ts, ts->data_objects[i].id, level + 1); - LOG_DBG("\n%*s}", 4 * (level + 1), ""); - } - else { - int pos = ts_json_serialize_name_value(ts, (char *)buf, sizeof(buf), - &ts->data_objects[i]); - if (pos > 0) { - buf[pos-1] = '\0'; // remove trailing comma - LOG_DBG("%*s%s", 4 * (level + 1), "", LOG_ALLOC_STR((char *)buf)); - } - } - } - } - if (obj_id == 0) { - LOG_DBG("\n}\n"); - } -} - -int ts_txt_process(struct ts_context *ts) -{ - int path_len = ts->req_len - 1; - char *path_end = strchr((char *)ts->req + 1, ' '); - if (path_end) { - path_len = (uint8_t *)path_end - ts->req - 1; - } - - const struct ts_data_object *endpoint = - ts_get_object_by_path(ts, (char *)ts->req + 1, path_len); - if (!endpoint) { - if (ts->req[0] == '?' && ts->req[1] == '/' && path_len == 1) { - return ts_txt_get(ts, NULL, TS_RET_NAMES); - } - else if (path_len > 0) { - return ts_txt_response(ts, TS_STATUS_NOT_FOUND); - } - } - - jsmn_parser parser; - jsmn_init(&parser); - - ts->json_str = (char *)ts->req + 1 + path_len; - ts->tok_count = jsmn_parse(&parser, ts->json_str, ts->req_len - path_len - 1, - ts->tokens, sizeof(ts->tokens)); - - if (ts->tok_count == JSMN_ERROR_NOMEM) { - return ts_txt_response(ts, TS_STATUS_REQUEST_TOO_LARGE); - } - else if (ts->tok_count < 0) { - // other parsing error - return ts_txt_response(ts, TS_STATUS_BAD_REQUEST); - } - else if (ts->tok_count == 0) { - if (ts->req[0] == '?') { - // no payload data - if ((char)ts->req[path_len] == '/') { - if (endpoint && (endpoint->type == TS_T_GROUP || endpoint->type == TS_T_EXEC)) { - return ts_txt_get(ts, endpoint, TS_RET_NAMES); - } - else { - // device discovery is only allowed for internal objects - return ts_txt_response(ts, TS_STATUS_BAD_REQUEST); - } - } - else { - return ts_txt_get(ts, endpoint, TS_RET_NAMES | TS_RET_VALUES); - } - } - else if (ts->req[0] == '!') { - return ts_txt_exec(ts, endpoint); - } - } - else { - if (ts->req[0] == '?') { - return ts_txt_fetch(ts, endpoint); - } - else if (ts->req[0] == '=') { - int len = ts_txt_patch(ts, endpoint); - - // check if endpoint has a callback assigned - if (endpoint && endpoint->data != NULL && strncmp((char *)ts->resp, ":84", 3) == 0) { - // create function pointer and call function - void (*fun)(void) = (void(*)(void))endpoint->data; - fun(); - } - return len; - } - else if (ts->req[0] == '!' && endpoint && endpoint->type == TS_T_EXEC) { - return ts_txt_exec(ts, endpoint); - } - else if (ts->req[0] == '+') { - return ts_txt_create(ts, endpoint); - } - else if (ts->req[0] == '-') { - return ts_txt_delete(ts, endpoint); - } - } - return ts_txt_response(ts, TS_STATUS_BAD_REQUEST); -} - -int ts_txt_fetch(struct ts_context *ts, const struct ts_data_object *parent) -{ - size_t pos = 0; - int tok = 0; // current token - - ts_object_id_t parent_id = (parent == NULL) ? 0 : parent->id; - - // initialize response with success message - pos += ts_txt_response(ts, TS_STATUS_CONTENT); - - if (ts->tokens[0].type == JSMN_ARRAY) { - pos += snprintf((char *)&ts->resp[pos], ts->resp_size - pos, " ["); - tok++; - } else { - pos += snprintf((char *)&ts->resp[pos], ts->resp_size - pos, " "); - } - - while (tok < ts->tok_count) { - - if (ts->tokens[tok].type != JSMN_STRING) { - return ts_txt_response(ts, TS_STATUS_BAD_REQUEST); - } - - const struct ts_data_object *object = ts_get_object_by_name(ts, - ts->json_str + ts->tokens[tok].start, - ts->tokens[tok].end - ts->tokens[tok].start, parent_id); - - if (object == NULL) { - return ts_txt_response(ts, TS_STATUS_NOT_FOUND); - } - else if (object->type == TS_T_GROUP) { - // bad request, as we can't read internal path object's values - return ts_txt_response(ts, TS_STATUS_BAD_REQUEST); - } - - if ((object->access & TS_READ_MASK & ts->_auth_flags) == 0) { - if (object->access & TS_READ_MASK) { - return ts_txt_response(ts, TS_STATUS_UNAUTHORIZED); - } - else { - return ts_txt_response(ts, TS_STATUS_FORBIDDEN); - } - } - - pos += ts_json_serialize_value(ts, (char *)&ts->resp[pos], ts->resp_size - pos, object); - - if (pos >= ts->resp_size - 2) { - return ts_txt_response(ts, TS_STATUS_RESPONSE_TOO_LARGE); - } - tok++; - } - - pos--; // remove trailing comma - if (ts->tokens[0].type == JSMN_ARRAY) { - // buffer will be long enough as we dropped last 2 characters --> sprintf allowed - pos += sprintf((char *)&ts->resp[pos], "]"); - } else { - ts->resp[pos] = '\0'; // terminate string - } - - return pos; -} - -int ts_json_deserialize_value(struct ts_context *ts, char *buf, size_t len, jsmntype_t type, - const struct ts_data_object *object) -{ -#if TS_DECFRAC_TYPE_SUPPORT - float tmp; -#endif - - if (type != JSMN_PRIMITIVE && type != JSMN_STRING) { - return 0; - } - - errno = 0; - switch (object->type) { - case TS_T_FLOAT32: - *((float*)object->data) = strtod(buf, NULL); - break; -#if TS_DECFRAC_TYPE_SUPPORT - case TS_T_DECFRAC: - tmp = strtod(buf, NULL); - // positive exponent - for (int16_t i = 0; i < object->detail; i++) { - tmp /= 10.0F; - } - // negative exponent - for (int16_t i = 0; i > object->detail; i--) { - tmp *= 10.0F; - } - *((int32_t*)object->data) = (int32_t)tmp; - break; -#endif - case TS_T_UINT64: - *((uint64_t*)object->data) = strtoull(buf, NULL, 0); - break; - case TS_T_INT64: - *((int64_t*)object->data) = strtoll(buf, NULL, 0); - break; - case TS_T_UINT32: - *((uint32_t*)object->data) = strtoul(buf, NULL, 0); - break; - case TS_T_INT32: - *((int32_t*)object->data) = strtol(buf, NULL, 0); - break; - case TS_T_UINT16: - *((uint16_t*)object->data) = strtoul(buf, NULL, 0); - break; - case TS_T_INT16: - *((uint16_t*)object->data) = strtol(buf, NULL, 0); - break; - case TS_T_BOOL: - if (buf[0] == 't' || buf[0] == '1') { - *((bool*)object->data) = true; - } - else if (buf[0] == 'f' || buf[0] == '0') { - *((bool*)object->data) = false; - } - else { - return 0; // error - } - break; - case TS_T_STRING: - if (type != JSMN_STRING || (unsigned int)object->detail <= len) { - return 0; - } - else if (object->id != 0) { // dummy object has id = 0 - strncpy((char*)object->data, buf, len); - ((char*)object->data)[len] = '\0'; - } - break; - } - - if (errno == ERANGE) { - return 0; - } - - return 1; // value always contained in one token (arrays not yet supported) -} - -int ts_txt_patch(struct ts_context *ts, const struct ts_data_object *parent) -{ - int tok = 0; // current token - - // buffer for data object value (largest negative 64bit integer has 20 digits) - char value_buf[21]; - size_t value_len; // length of value in buffer - - ts_object_id_t parent_id = (parent == NULL) ? 0 : parent->id; - - if (ts->tok_count < 2) { - if (ts->tok_count == JSMN_ERROR_NOMEM) { - return ts_txt_response(ts, TS_STATUS_REQUEST_TOO_LARGE); - } else { - return ts_txt_response(ts, TS_STATUS_BAD_REQUEST); - } - } - - if (ts->tokens[0].type == JSMN_OBJECT) { // object = map - tok++; - } - - // loop through all elements to check if request is valid - while (tok + 1 < ts->tok_count) { - - if (ts->tokens[tok].type != JSMN_STRING || - (ts->tokens[tok+1].type != JSMN_PRIMITIVE && ts->tokens[tok+1].type != JSMN_STRING)) { - return ts_txt_response(ts, TS_STATUS_BAD_REQUEST); - } - - const struct ts_data_object* object = ts_get_object_by_name(ts, - ts->json_str + ts->tokens[tok].start, - ts->tokens[tok].end - ts->tokens[tok].start, parent_id); - - if (object == NULL) { - return ts_txt_response(ts, TS_STATUS_NOT_FOUND); - } - - if ((object->access & TS_WRITE_MASK & ts->_auth_flags) == 0) { - if (object->access & TS_WRITE_MASK) { - return ts_txt_response(ts, TS_STATUS_UNAUTHORIZED); - } - else { - return ts_txt_response(ts, TS_STATUS_FORBIDDEN); - } - } - - tok++; - - // extract the value and check buffer lengths - value_len = ts->tokens[tok].end - ts->tokens[tok].start; - if ((object->type != TS_T_STRING && value_len >= sizeof(value_buf)) || - (object->type == TS_T_STRING && value_len >= (size_t)object->detail)) - { - return ts_txt_response(ts, TS_STATUS_UNSUPPORTED_FORMAT); - } - else { - strncpy(value_buf, &ts->json_str[ts->tokens[tok].start], value_len); - value_buf[value_len] = '\0'; - } - - // create dummy object to test formats - uint8_t dummy_data[8]; // enough to fit also 64-bit values - struct ts_data_object dummy_object = {0, 0, "Dummy", (void *)dummy_data, object->type, - object->detail}; - - int res = ts_json_deserialize_value(ts, value_buf, value_len, ts->tokens[tok].type, - &dummy_object); - if (res == 0) { - return ts_txt_response(ts, TS_STATUS_UNSUPPORTED_FORMAT); - } - tok += res; - } - - if (ts->tokens[0].type == JSMN_OBJECT) { - tok = 1; - } - else { - tok = 0; - } - - // actually write data - while (tok + 1 < ts->tok_count) { - - const struct ts_data_object *object = - ts_get_object_by_name(ts, ts->json_str + ts->tokens[tok].start, - ts->tokens[tok].end - ts->tokens[tok].start, parent_id); - - tok++; - - // extract the value again (max. size was checked before) - value_len = ts->tokens[tok].end - ts->tokens[tok].start; - if (value_len < sizeof(value_buf)) { - strncpy(value_buf, &ts->json_str[ts->tokens[tok].start], value_len); - value_buf[value_len] = '\0'; - } - - tok += ts_json_deserialize_value(ts, &ts->json_str[ts->tokens[tok].start], value_len, - ts->tokens[tok].type, object); - } - - return ts_txt_response(ts, TS_STATUS_CHANGED); -} - -int ts_txt_get(struct ts_context *ts, const struct ts_data_object *parent, uint32_t ret_type) -{ - bool include_values = (ret_type & TS_RET_VALUES); - - // initialize response with success message - size_t len = ts_txt_response(ts, TS_STATUS_CONTENT); - - ts_object_id_t parent_id = (parent == NULL) ? 0 : parent->id; - - if (parent != NULL && parent->type != TS_T_GROUP && - parent->type != TS_T_EXEC) - { - // get value of data object - ts->resp[len++] = ' '; - len += ts_json_serialize_value(ts, (char *)&ts->resp[len], ts->resp_size - len, parent); - ts->resp[--len] = '\0'; // remove trailing comma again - return len; - } - - if (parent != NULL && parent->type == TS_T_EXEC && include_values) { - // bad request, as we can't read exec object's values - return ts_txt_response(ts, TS_STATUS_BAD_REQUEST); - } - - len += sprintf((char *)&ts->resp[len], include_values ? " {" : " ["); - int objects_found = 0; - for (unsigned int i = 0; i < ts->num_objects; i++) { - if ((ts->data_objects[i].access & TS_READ_MASK) && - (ts->data_objects[i].parent == parent_id)) - { - if (include_values) { - if (ts->data_objects[i].type == TS_T_GROUP) { - // bad request, as we can't read nternal path object's values - return ts_txt_response(ts, TS_STATUS_BAD_REQUEST); - } - int ret = ts_json_serialize_name_value(ts, (char *)&ts->resp[len], - ts->resp_size - len, &ts->data_objects[i]); - if (ret > 0) { - len += ret; - } - else { - return ts_txt_response(ts, TS_STATUS_RESPONSE_TOO_LARGE); - } - } - else { - len += snprintf((char *)&ts->resp[len], - ts->resp_size - len, - "\"%s\",", ts->data_objects[i].name); - } - objects_found++; - - if (len >= ts->resp_size - 1) { - return ts_txt_response(ts, TS_STATUS_RESPONSE_TOO_LARGE); - } - } - } - - // remove trailing comma and add closing bracket - if (objects_found == 0) { - len++; - } - ts->resp[len-1] = include_values ? '}' : ']'; - ts->resp[len] = '\0'; - - return len; -} - -int ts_txt_create(struct ts_context *ts, const struct ts_data_object *object) -{ - if (ts->tok_count > 1) { - // only single JSON primitive supported at the moment - return ts_txt_response(ts, TS_STATUS_NOT_IMPLEMENTED); - } - - if (object->type == TS_T_ARRAY) { - // Remark: See commit history with implementation for pub/sub ID arrays as inspiration - return ts_txt_response(ts, TS_STATUS_NOT_IMPLEMENTED); - } - else if (object->type == TS_T_SUBSET) { - if (ts->tokens[0].type == JSMN_STRING) { - struct ts_data_object *del_object = ts_get_object_by_name(ts, ts->json_str + - ts->tokens[0].start, ts->tokens[0].end - ts->tokens[0].start, -1); - if (del_object != NULL) { - del_object->subsets |= (uint16_t)object->detail; - return ts_txt_response(ts, TS_STATUS_CREATED); - } - return ts_txt_response(ts, TS_STATUS_NOT_FOUND); - } - } - return ts_txt_response(ts, TS_STATUS_METHOD_NOT_ALLOWED); -} - -int ts_txt_delete(struct ts_context *ts, const struct ts_data_object *object) -{ - if (ts->tok_count > 1) { - // only single JSON primitive supported at the moment - return ts_txt_response(ts, TS_STATUS_NOT_IMPLEMENTED); - } - - if (object->type == TS_T_ARRAY) { - // Remark: See commit history with implementation for pub/sub ID arrays as inspiration - return ts_txt_response(ts, TS_STATUS_NOT_IMPLEMENTED); - } - else if (object->type == TS_T_SUBSET) { - if (ts->tokens[0].type == JSMN_STRING) { - struct ts_data_object *del_object = ts_get_object_by_name(ts, ts->json_str + - ts->tokens[0].start, ts->tokens[0].end - ts->tokens[0].start, -1); - if (del_object != NULL) { - del_object->subsets &= ~((uint16_t)object->detail); - return ts_txt_response(ts, TS_STATUS_DELETED); - } - return ts_txt_response(ts, TS_STATUS_NOT_FOUND); - } - } - return ts_txt_response(ts, TS_STATUS_METHOD_NOT_ALLOWED); -} - -int ts_txt_exec(struct ts_context *ts, const struct ts_data_object *object) -{ - int tok = 0; // current token - int objects_found = 0; // number of child objects found - - if (ts->tok_count > 0 && ts->tokens[tok].type == JSMN_ARRAY) { - tok++; // go to first element of array - } - - if ((object->access & TS_WRITE_MASK) && (object->type == TS_T_EXEC)) { - // object is generally executable, but are we authorized? - if ((object->access & TS_WRITE_MASK & ts->_auth_flags) == 0) { - return ts_txt_response(ts, TS_STATUS_UNAUTHORIZED); - } - } - else { - return ts_txt_response(ts, TS_STATUS_FORBIDDEN); - } - - for (unsigned int i = 0; i < ts->num_objects; i++) { - if (ts->data_objects[i].parent == object->id) { - if (tok >= ts->tok_count) { - // more child objects found than parameters were passed - return ts_txt_response(ts, TS_STATUS_BAD_REQUEST); - } - int res = ts_json_deserialize_value(ts, ts->json_str + ts->tokens[tok].start, - ts->tokens[tok].end - ts->tokens[tok].start, ts->tokens[tok].type, - &ts->data_objects[i]); - if (res == 0) { - // deserializing the value was not successful - return ts_txt_response(ts, TS_STATUS_UNSUPPORTED_FORMAT); - } - tok += res; - objects_found++; - } - } - - if (ts->tok_count > tok) { - // more parameters passed than child objects found - return ts_txt_response(ts, TS_STATUS_BAD_REQUEST); - } - - // if we got here, finally create function pointer and call function - void (*fun)(void) = (void(*)(void))object->data; - fun(); - - return ts_txt_response(ts, TS_STATUS_VALID); -} - -int ts_txt_export(struct ts_context *ts, char *buf, size_t buf_size, uint16_t subsets) -{ - unsigned int len = 1; - buf[0] = '{'; - - for (unsigned int i = 0; i < ts->num_objects; i++) { - if (ts->data_objects[i].subsets & subsets) { - len += ts_json_serialize_name_value(ts, &buf[len], buf_size - len, - &ts->data_objects[i]); - } - if (len >= buf_size - 1) { - return 0; - } - } - - buf[len-1] = '}'; // overwrite comma - - return len; -} - -int ts_txt_statement(struct ts_context *ts, char *buf, size_t buf_size, - struct ts_data_object *object) -{ - unsigned int len; - - if (!object || object->parent != 0) { - // currently only supporting top level objects - return 0; - } - - if (object->type == TS_T_SUBSET) { - len = snprintf(buf, buf_size, "#%s ", object->name); - len += ts_txt_export(ts, &buf[len], buf_size - len, object->detail); - } - else if (object->type == TS_T_GROUP) { - len = snprintf(buf, buf_size, "#%s {", object->name); - for (unsigned int i = 0; i < ts->num_objects; i++) { - if (ts->data_objects[i].parent == object->id) { - len += ts_json_serialize_name_value(ts, &buf[len], buf_size - len, - &ts->data_objects[i]); - } - if (len >= buf_size - 1) { - return 0; - } - } - buf[len-1] = '}'; // overwrite comma - } - else { - return 0; - } - - return len; -} - -int ts_txt_statement_by_path(struct ts_context *ts, char *buf, size_t buf_size, const char *path) -{ - return ts_txt_statement(ts, buf, buf_size, ts_get_object_by_path(ts, path, strlen(path))); -} - -int ts_txt_statement_by_id(struct ts_context *ts, char *buf, size_t buf_size, ts_object_id_t id) -{ - return ts_txt_statement(ts, buf, buf_size, ts_get_object_by_id(ts, id)); -} diff --git a/src/ts_config.h b/src/ts_config.h index 7f81927..9f92de6 100644 --- a/src/ts_config.h +++ b/src/ts_config.h @@ -1,78 +1,490 @@ /* * Copyright (c) 2017 Martin Jäger / Libre Solar * Copyright (c) 2021 Bobby Noelte. - * * SPDX-License-Identifier: Apache-2.0 */ +/** + * @file + * @brief ThingSet configuration definition. + * + * To be included by the specific environment implementation. + */ + #ifndef TS_CONFIG_H_ #define TS_CONFIG_H_ -/* - * Enable legacy C++ interface. +#ifdef THINGSET_CONFIG_HEADER +#include THINGSET_CONFIG_HEADER +#endif + +/** + * @brief ThingSet configuration definitions. + * + * @defgroup ts_config_priv ThingSet configuration definitions + * @{ + */ + +/** + * @def TS_CONFIG_CORE + * + * @brief Configure ThingSet core support. + * + * Core support is a minimum capabilities configuration for ThingSet. Use this for very resource + * constrained Things. + * + * @note Kconfig based build systems (or others) may use @ref CONFIG_THINGSET_CORE to set + * @ref TS_CONFIG_CORE. + */ +#if !defined(TS_CONFIG_CORE) && !defined(CONFIG_THINGSET_CORE) +#define TS_CONFIG_CORE 0 +#elif !defined(TS_CONFIG_CORE) +#define TS_CONFIG_CORE CONFIG_THINGSET_CORE +#endif + +/** + * @def TS_CONFIG_CORE_LOCID + * + * @brief Configure core variant ThingSet local context identifier. + * + * Core support works on a single core variant ThingSet local context. The local context identifier + * used for the single core context is given by @ref TS_CONFIG_CORE_LOCID. + * + * - default: 0 + * + * @note Kconfig TS_CONFIG_CORE_LOCID build systems (or others) may use + * @ref CONFIG_THINGSET_CORE_LOCID to set @ref TS_CONFIG_CORE_LOCID. + */ +#if !TS_CONFIG_CORE +#define TS_CONFIG_CORE_LOCID 99 +#elif !defined(TS_CONFIG_CORE_LOCID) && !defined(CONFIG_THINGSET_CORE_LOCID) +#define TS_CONFIG_CORE_LOCID 0 +#elif !defined(TS_CONFIG_CORE_LOCID) +#define TS_CONFIG_CORE_LOCID CONFIG_THINGSET_CORE_LOCID +#endif + +/** + * @def TS_CONFIG_COM + * + * @brief Configure ThingSet communication framework support. + * + * Communication support provides a framework for communication for ThingSet. Use this (instead of + * @p TS_CONFIG_CORE) for Things that need more elaborated communication capabilities. As a + * drawback this configuration needs more resources. + * + * @note Kconfig based build systems (or others) may use @ref CONFIG_THINGSET_COM to set + * @ref TS_CONFIG_COM. + */ +#if !defined(TS_CONFIG_COM) && !defined(CONFIG_THINGSET_COM) +#define TS_CONFIG_COM 0 +#elif !defined(TS_CONFIG_COM) +#define TS_CONFIG_COM CONFIG_THINGSET_COM +#endif + +/** + * @def TS_CONFIG_CPP_LEGACY + * + * @brief Configure legacy C++ interface support. * * This option enables the legacy C++ interface of the * ThingSet protocol library. Enable if your C++ code uses - * DataNode or ArrayInfo instead of ThingSetDataNode or ThingSetArrayInfo. + * + * @note Kconfig based build systems (or others) may use @ref CONFIG_THINGSET_CPP_LEGACY to set + * @ref TS_CONFIG_CPP_LEGACY. */ -#if defined(__cplusplus) && !defined(CONFIG_THINGSET_CPP_LEGACY) -#define CONFIG_THINGSET_CPP_LEGACY 1 +#if defined(__cplusplus) && !defined(TS_CONFIG_CPP_LEGACY) && !defined(CONFIG_THINGSET_CPP_LEGACY) +#define TS_CONFIG_CPP_LEGACY 0 +#elif defined(__cplusplus) && !defined(TS_CONFIG_CPP_LEGACY) +#define TS_CONFIG_CPP_LEGACY CONFIG_THINGSET_CPP_LEGACY #endif -/* - * Maximum number of expected JSON tokens (i.e. arrays, map keys, values, - * primitives, etc.) +/** + * @def TS_CONFIG_NUM_JSON_TOKENS + * + * @brief Configure the maximum number of expected JSON tokens. * - * Thingset throws an error if maximum number of tokens is reached in a + * JSON tokens i.e. arrays, map keys, values, primitives, etc. + * + * ThingSet throws an error if maximum number of tokens is reached in a * request or response. + * + * @note Kconfig based build systems (or others) may use @ref CONFIG_THINGSET_NUM_JSON_TOKENS to set + * @ref TS_CONFIG_NUM_JSON_TOKENS. */ -#if !defined(TS_NUM_JSON_TOKENS) && !defined(CONFIG_THINGSET_NUM_JSON_TOKENS) -#define TS_NUM_JSON_TOKENS 50 -#elif !defined(TS_NUM_JSON_TOKENS) -#define TS_NUM_JSON_TOKENS CONFIG_THINGSET_NUM_JSON_TOKENS +#if !defined(TS_CONFIG_NUM_JSON_TOKENS) && !defined(CONFIG_THINGSET_NUM_JSON_TOKENS) +#define TS_CONFIG_NUM_JSON_TOKENS 50 +#elif !defined(TS_CONFIG_NUM_JSON_TOKENS) +#define TS_CONFIG_NUM_JSON_TOKENS CONFIG_THINGSET_NUM_JSON_TOKENS #endif -/* +/** + * @def TS_CONFIG_VERBOSE_STATUS_MESSAGES + * + * @brief Configure verbose status messaged. + * * If verbose status messages are switched on, a response in text-based mode * contains not only the status code, but also a message. + * + * @note Kconfig based build systems (or others) may use + * @ref CONFIG_THINGSET_VERBOSE_STATUS_MESSAGES to set @ref TS_CONFIG_VERBOSE_STATUS_MESSAGES. */ -#if !defined(TS_VERBOSE_STATUS_MESSAGES) && !defined(CONFIG_THINGSET_VERBOSE_STATUS_MESSAGES) -#define TS_VERBOSE_STATUS_MESSAGES 1 -#elif !defined(TS_VERBOSE_STATUS_MESSAGES) -#define TS_VERBOSE_STATUS_MESSAGES CONFIG_THINGSET_VERBOSE_STATUS_MESSAGES +#if !defined(TS_CONFIG_VERBOSE_STATUS_MESSAGES) && !defined(CONFIG_THINGSET_VERBOSE_STATUS_MESSAGES) +#define TS_CONFIG_VERBOSE_STATUS_MESSAGES 1 +#elif !defined(TS_CONFIG_VERBOSE_STATUS_MESSAGES) +#define TS_CONFIG_VERBOSE_STATUS_MESSAGES CONFIG_THINGSET_VERBOSE_STATUS_MESSAGES #endif -/* +/** + * @def TS_CONFIG_64BIT_TYPES_SUPPORT + * + * @brief Configure 64 bit variable types support. + * * Switch on support for 64 bit variable types (uint64_t, int64_t, double) * * This should be disabled for most 8-bit microcontrollers to increase * performance + * + * @note Kconfig based build systems (or others) may use @ref CONFIG_THINGSET_64BIT_TYPES_SUPPORT + * to set @ref TS_CONFIG_64BIT_TYPES_SUPPORT. */ -#if !defined(TS_64BIT_TYPES_SUPPORT) && !defined(CONFIG_THINGSET_64BIT_TYPES_SUPPORT) -#define TS_64BIT_TYPES_SUPPORT 0 // default: no support -#elif !defined(TS_64BIT_TYPES_SUPPORT) -#define TS_64BIT_TYPES_SUPPORT CONFIG_THINGSET_64BIT_TYPES_SUPPORT +#if !defined(TS_CONFIG_64BIT_TYPES_SUPPORT) && !defined(CONFIG_THINGSET_64BIT_TYPES_SUPPORT) +#define TS_CONFIG_64BIT_TYPES_SUPPORT 0 // default: no support +#elif !defined(TS_CONFIG_64BIT_TYPES_SUPPORT) +#define TS_CONFIG_64BIT_TYPES_SUPPORT CONFIG_THINGSET_64BIT_TYPES_SUPPORT #endif -/* +/** + * @def TS_CONFIG_DECFRAC_TYPE_SUPPORT + * + * @brief Configure CBOR decimal fraction data type support. + * * Switch on support for CBOR decimal fraction data type which stores a decimal mantissa * and a constant decimal exponent. This allows to use e.g. millivolts internally instead * of floating point numbers, while still communicating the SI base unit (volts). + * + * @note Kconfig based build systems (or others) may use @ref CONFIG_THINGSET_DECFRAC_TYPE_SUPPORT + * to set @ref TS_CONFIG_DECFRAC_TYPE_SUPPORT. */ -#if !defined(TS_DECFRAC_TYPE_SUPPORT) && !defined(CONFIG_THINGSET_DECFRAC_TYPE_SUPPORT) -#define TS_DECFRAC_TYPE_SUPPORT 0 // default: no support -#elif !defined(TS_DECFRAC_TYPE_SUPPORT) -#define TS_DECFRAC_TYPE_SUPPORT CONFIG_THINGSET_DECFRAC_TYPE_SUPPORT +#if !defined(TS_CONFIG_DECFRAC_TYPE_SUPPORT) && !defined(CONFIG_THINGSET_DECFRAC_TYPE_SUPPORT) +#define TS_CONFIG_DECFRAC_TYPE_SUPPORT 0 // default: no support +#elif !defined(TS_CONFIG_DECFRAC_TYPE_SUPPORT) +#define TS_CONFIG_DECFRAC_TYPE_SUPPORT CONFIG_THINGSET_DECFRAC_TYPE_SUPPORT #endif -/* +/** + * @def TS_CONFIG_BYTE_STRING_TYPE_SUPPORT + * + * @brief Configure CBOR byte strings support. + * * Switch on support for CBOR byte strings, which can store any sort of binary data and * can be used e.g. for firmware upgrades. Byte strings are not supported by JSON. + * + * @note Kconfig based build systems (or others) may use + * @ref CONFIG_THINGSET_BYTE_STRING_TYPE_SUPPORT to set + * @ref TS_CONFIG_BYTE_STRING_TYPE_SUPPORT. + */ +#if !defined(TS_CONFIG_BYTE_STRING_TYPE_SUPPORT) && !defined(CONFIG_THINGSET_BYTE_STRING_TYPE_SUPPORT) +#define TS_CONFIG_BYTE_STRING_TYPE_SUPPORT 0 // default: no support +#elif !defined(TS_CONFIG_BYTE_STRING_TYPE_SUPPORT) +#define TS_CONFIG_BYTE_STRING_TYPE_SUPPORT CONFIG_THINGSET_BYTE_STRING_TYPE_SUPPORT +#endif + +/** + * @def TS_CONFIG_LOCAL_COUNT + * + * @brief Configure the number of local contexts. + * + * ThingSet holds one local contexts' objects databases per local context. Configure the total + * number of local contexts and their object databases that a device has. + * + * A device may host several local contexts. + * - default is 1. + * + * @note Kconfig based build systems (or others) may use @ref CONFIG_THINGSET_LOCAL_COUNT to set + * @ref TS_CONFIG_LOCAL_COUNT. */ -#if !defined(TS_BYTE_STRING_TYPE_SUPPORT) && !defined(CONFIG_THINGSET_BYTE_STRING_TYPE_SUPPORT) -#define TS_BYTE_STRING_TYPE_SUPPORT 0 // default: no support -#elif !defined(TS_BYTE_STRING_TYPE_SUPPORT) -#define TS_BYTE_STRING_TYPE_SUPPORT CONFIG_THINGSET_BYTE_STRING_TYPE_SUPPORT +#if !defined(TS_CONFIG_LOCAL_COUNT) && !defined(CONFIG_THINGSET_LOCAL_COUNT) +#define TS_CONFIG_LOCAL_COUNT 1 +#elif !defined(TS_CONFIG_LOCAL_COUNT) +#define TS_CONFIG_LOCAL_COUNT CONFIG_THINGSET_LOCAL_COUNT #endif +/** + * @def TS_CONFIG_REMOTE_OBJECT_COUNT + * + * @brief Configure the number of remote objects that may be handled by the ThingSet pool. + * + * ThingSet holds a pool for remote objects. Configure the total number of remote objects that a + * device may handle. + * + * ThingSet core context does not need any of these. A ThingSet communication context should have + * more depending on the application need. + * - default is 16. + * + * @note Kconfig based build systems (or others) may use @ref TS_CONFIG_REMOTE_OBJECT_COUNT to set + * @ref TS_CONFIG_REMOTE_OBJECT_COUNT. + */ +#if !TS_CONFIG_COM && !defined(TS_CONFIG_REMOTE_OBJECT_COUNT) && \ + !defined(CONFIG_THINGSET_REMOTE_OBJECT_COUNT) +#define TS_CONFIG_REMOTE_OBJECT_COUNT 0 +#elif !defined(TS_CONFIG_REMOTE_OBJECT_COUNT) +#if !defined(CONFIG_THINGSET_REMOTE_OBJECT_COUNT) +#define TS_CONFIG_REMOTE_OBJECT_COUNT 16 +#else +#define TS_CONFIG_REMOTE_OBJECT_COUNT CONFIG_THINGSET_REMOTE_OBJECT_COUNT +#endif +#endif + +/** + * @def TS_CONFIG_REMOTE_COUNT + * + * @brief Configure the number of remote contexts that remote objects may live in. + * + * ThingSet holds a pool for the remote contexts' objects databases. Configure the total number + * of remote contexts' object databases that a device may handle. + * + * ThingSet core context does not need any of these. A ThingSet communication context should have + * more depending on the application need. + * - default is 4. + * + * @note Kconfig based build systems (or others) may use @ref CONFIG_THINGSET_REMOTE_COUNT to set + * @ref TS_CONFIG_REMOTE_COUNT. + */ +#if !TS_CONFIG_COM && !defined(TS_CONFIG_REMOTE_COUNT) && !defined(CONFIG_THINGSET_REMOTE_COUNT) +#define TS_CONFIG_REMOTE_COUNT 0 +#elif !defined(TS_CONFIG_REMOTE_COUNT) +#if !defined(CONFIG_THINGSET_REMOTE_COUNT) +#define TS_CONFIG_REMOTE_COUNT 4 +#else +#define TS_CONFIG_REMOTE_COUNT CONFIG_THINGSET_REMOTE_COUNT +#endif +#endif + +/** + * @def TS_CONFIG_PORT_COUNT + * + * @brief Configure the number of ThingSet ports of a device. + * + * ThingSet holds a table of all the ThingSet ports of a device. Configure the total number + * of ThingSet ports that a device handles. + * + * ThingSet core context does not need any of these. A ThingSet communication context should have + * more depending on the number of interfaces used for ThingSet communication. + * - default is 1. + * + * @note Kconfig based build systems (or others) may use @ref CONFIG_THINGSET_PORT_COUNT to set + * @ref TS_CONFIG_PORT_COUNT. + */ +#if !TS_CONFIG_COM && !defined(TS_CONFIG_PORT_COUNT) && !defined(CONFIG_THINGSET_PORT_COUNT) +#define TS_CONFIG_PORT_COUNT 0 +#elif !defined(TS_CONFIG_PORT_COUNT) +#if !defined(CONFIG_THINGSET_PORT_COUNT) +#define TS_CONFIG_PORT_COUNT 1 +#else +#define TS_CONFIG_PORT_COUNT CONFIG_THINGSET_PORT_COUNT +#endif +#endif + +/** + * @def TS_CONFIG_BUF_COUNT + * + * @brief Configure number of buffers in the ThingSet communication buffer pool. + * + * ThingSet core context needs two buffers. A ThingSet communication context should have more + * - default is 16. + * + * @note Kconfig based build systems (or others) may use @ref CONFIG_THINGSET_COM_BUF_COUNT to set + * @ref TS_CONFIG_BUF_COUNT. + */ +#if !defined(TS_CONFIG_BUF_COUNT) && !defined(CONFIG_THINGSET_COM_BUF_COUNT) +#if TS_CONFIG_CORE && !TS_CONFIG_COM +#define TS_CONFIG_BUF_COUNT 2 +#else +#define TS_CONFIG_BUF_COUNT 16 +#endif +#elif !defined(TS_CONFIG_BUF_COUNT) +#define TS_CONFIG_BUF_COUNT CONFIG_THINGSET_COM_BUF_COUNT +#endif + +/** + * @def TS_CONFIG_BUF_DATA_SIZE + * + * @brief Configure data block size for ThingSet communication buffers. + * + * This is the total size of all buffers' data. Each active buffer takes it's share from this + * total amount. + * + * @note Kconfig based build systems (or others) may use @ref CONFIG_THINGSET_COM_BUF_DATA_SIZE + * to set @ref TS_CONFIG_BUF_DATA_SIZE. + */ +#if !defined(TS_CONFIG_BUF_DATA_SIZE) && !defined(CONFIG_THINGSET_COM_BUF_DATA_SIZE) +#if TS_CONFIG_CORE && !TS_CONFIG_COM +#define TS_CONFIG_BUF_DATA_SIZE 512 +#else +#define TS_CONFIG_BUF_DATA_SIZE 1024 +#endif +#elif !defined(TS_CONFIG_BUF_DATA_SIZE) +#define TS_CONFIG_BUF_DATA_SIZE CONFIG_THINGSET_COM_BUF_DATA_SIZE +#endif + +/** + * @def TS_CONFIG_NODETABLE_SIZE + * + * @brief Configure maximum number of nodes a context's node table can hold. + * + * @note Kconfig based build systems (or others) may use @ref CONFIG_THINGSET_NODETABLE_SIZE to set + * @ref TS_CONFIG_NODETABLE_SIZE. + */ +#if !defined(TS_CONFIG_NODETABLE_SIZE) && !defined(CONFIG_THINGSET_NODETABLE_SIZE) +#define TS_CONFIG_NODETABLE_SIZE 3 +#elif !defined(TS_CONFIG_NODETABLE_SIZE) +#define TS_CONFIG_NODETABLE_SIZE CONFIG_THINGSET_NODETABLE_SIZE +#endif + +/** + * @def TS_CONFIG_LOG + * + * @brief Configure ThingSet logging support. + * + * @note Kconfig based build systems (or others) may use @ref CONFIG_THINGSET_LOG to set + * @ref TS_CONFIG_LOG. + */ +#if !defined(TS_CONFIG_LOG) && !defined(CONFIG_THINGSET_LOG) +#define TS_CONFIG_LOG 0 +#elif !defined(TS_CONFIG_LOG) +#define TS_CONFIG_LOG CONFIG_THINGSET_LOG +#endif + +/** + * @def TS_CONFIG_LOG_LEVEL + * + * @brief Configure ThingSet log level. + * + * The log levels are: + * - 0: Off + * - 1: Error + * - 2: Warning + * - 3: Info + * - 4: Debug + * + * @note Kconfig based build systems (or others) may use @ref CONFIG_THINGSET_LOG_LEVEL to set + * @ref TS_CONFIG_LOG_LEVEL. + */ +#if !defined(TS_CONFIG_LOG_LEVEL) && !defined(CONFIG_THINGSET_LOG_LEVEL) +#define TS_CONFIG_LOG_LEVEL 0 +#elif !defined(TS_CONFIG_LOG_LEVEL) +#define TS_CONFIG_LOG_LEVEL CONFIG_THINGSET_LOG_LEVEL +#endif + +/** + * @def TS_CONFIG_UNIT_TEST + * + * @brief Configure ThingSet unit test support. + * + * @note Kconfig based build systems (or others) may use @ref CONFIG_THINGSET_UNIT_TEST to set + * @ref TS_CONFIG_UNIT_TEST. + */ +#if !defined(TS_CONFIG_UNIT_TEST) && !defined(CONFIG_THINGSET_UNIT_TEST) +#define TS_CONFIG_UNIT_TEST 0 +#elif !defined(TS_CONFIG_UNIT_TEST) +#define TS_CONFIG_UNIT_TEST CONFIG_THINGSET_UNIT_TEST +#endif + +/** + * @def TS_CONFIG_ASSERT + * + * @brief Configure ThingSet assert support. + * + * @note Kconfig based build systems (or others) may use @ref CONFIG_THINGSET_ASSERT to set + * @ref TS_CONFIG_ASSERT. + */ +#if !defined(TS_CONFIG_ASSERT) && !defined(CONFIG_THINGSET_ASSERT) +#define TS_CONFIG_ASSERT 0 +#elif !defined(TS_CONFIG_ASSERT) +#define TS_CONFIG_ASSERT CONFIG_THINGSET_ASSERT +#endif + +/** + * @def TS_CONFIG_SHELL + * + * @brief Configure ThingSet shell application. + * + * @note Kconfig based build systems (or others) may use @ref CONFIG_THINGSET_SHELL to set + * @ref TS_CONFIG_SHELL. + */ +#if !defined(TS_CONFIG_SHELL) && !defined(CONFIG_THINGSET_SHELL) +#define TS_CONFIG_SHELL 0 +#elif !defined(TS_CONFIG_SHELL) +#define TS_CONFIG_SHELL CONFIG_THINGSET_SHELL +#endif + +/** + * @def TS_CONFIG_SHELL_NAME + * + * @brief Configure ThingSet shell application name. + * + * The name the shell application shall use at the local context. + * + * @note Kconfig based build systems (or others) may use @ref CONFIG_THINGSET_SHELL_NAME to set + * @ref TS_CONFIG_SHELL_NAME. + */ +#if !defined(TS_CONFIG_SHELL_NAME) && !defined(CONFIG_THINGSET_SHELL_NAME) +#define TS_CONFIG_SHELL_NAME "shell" +#elif !defined(TS_CONFIG_SHELL_NAME) +#define TS_CONFIG_SHELL_NAME CONFIG_THINGSET_SHELL_NAME +#endif + +/** + * @def TS_CONFIG_SHELL_LOCID + * + * @brief Configure ThingSet shell application local context ID. + * + * The local context the shell applicationm shall be attached to. + * + * @note Kconfig based build systems (or others) may use @ref CONFIG_THINGSET_SHELL_LOCID to set + * @ref TS_CONFIG_SHELL_LOCID. + */ +#if !defined(TS_CONFIG_SHELL_LOCID) && !defined(CONFIG_THINGSET_SHELL_LOCID) +#define TS_CONFIG_SHELL_LOCID 0 +#elif !defined(TS_CONFIG_SHELL_LOCID) +#define TS_CONFIG_SHELL_LOCID CONFIG_THINGSET_SHELL_LOCID +#endif + +/** + * @def TS_CONFIG_SHELL_PORTID + * + * @brief Configure ThingSet shell application port ID. + * + * The ID of the port the shell shall communicate to the local context. + * + * @note Kconfig based build systems (or others) may use @ref CONFIG_THINGSET_SHELL_PORTID to set + * @ref TS_CONFIG_SHELL_PORTID. + */ +#if !defined(TS_CONFIG_SHELL_PORTID) && !defined(CONFIG_THINGSET_SHELL_PORTID) +#define TS_CONFIG_SHELL_PORTID 0 +#elif !defined(TS_CONFIG_SHELL_PORTID) +#define TS_CONFIG_SHELL_PORTID CONFIG_THINGSET_SHELL_PORTID +#endif + +/** + * @def TS_CONFIG_SHELL_MEM_SIZE + * + * @brief Configure size of dynamic memory the ThingSet shell may use. + * + * The total size of memory the ThingSet shell may allocate by ts_shell_allocate() and free by + * ts_shell_free(). + * + * @note Kconfig based build systems (or others) may use @ref CONFIG_THINGSET_SHELL_MEM_SIZE to set + * @ref TS_CONFIG_SHELL_MEM_SIZE. + */ +#if !defined(TS_CONFIG_SHELL_MEM_SIZE) && !defined(CONFIG_THINGSET_SHELL_MEM_SIZE) +#define TS_CONFIG_SHELL_MEM_SIZE 0 +#elif !defined(TS_CONFIG_SHELL_MEM_SIZE) +#define TS_CONFIG_SHELL_MEM_SIZE CONFIG_THINGSET_SHELL_MEM_SIZE +#endif + +/** + * @} + */ + #endif /* TS_CONFIG_H_ */ diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index d301ee9..61e913b 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -2,7 +2,8 @@ # Copyright (c) 2021 Bobby Noelte. # SPDX-License-Identifier: Apache-2.0 -# Make library a Zephyr module +# Make ThingSet library a Zephyr module +# ------------------------------------- # # Use ZEPHYR_BASE as sentinel to assure we are building with Zephyr # (Other build environments may try to use this CMakeLists.txt). @@ -15,14 +16,58 @@ get_filename_component(THINGSET_BASE ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) # The ThingSet library # -------------------- -zephyr_library_named("ts") - zephyr_include_directories(${THINGSET_BASE}/src) -add_subdirectory(${THINGSET_BASE}/src build/thingset) +zephyr_library_named("ts") + +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_app.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_cobs.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_cmd.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_core.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_export.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_msg.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_node.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_obj.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_ctx_process.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_jsmn.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_coder.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_export.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_log.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_proto.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_msg_value.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_obj.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_obj_log.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_port.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_rbbq.c) +target_sources(ts PRIVATE ${THINGSET_BASE}/src/ts_time.c) +# Zephyr implementation +target_sources(ts PRIVATE ${THINGSET_BASE}/zephyr/thingset/ts_impl_buf.c) +if(DEFINED CONFIG_LOG) + target_sources(ts PRIVATE ${THINGSET_BASE}/zephyr/thingset/ts_impl_log.c) +endif() +target_sources(ts PRIVATE ${THINGSET_BASE}/native/thingset/ts_impl_libc.c) if(DEFINED CONFIG_MINIMAL_LIBC) - target_sources(ts PRIVATE ${THINGSET_BASE}/zephyr/libc/minimal/extensions.c) + target_sources(ts PRIVATE ${THINGSET_BASE}/zephyr/thingset/ts_impl_libc_minimal.c) endif() +if(NOT DEFINED CONFIG_TINYCBOR) +target_sources(ts PRIVATE + ${THINGSET_BASE}/native/tinycbor/src/cborerrorstrings.c + ${THINGSET_BASE}/native/tinycbor/src/cborencoder.c + ${THINGSET_BASE}/native/tinycbor/src/cborencoder_close_container_checked.c + ${THINGSET_BASE}/native/tinycbor/src/cborencoder_float.c + ${THINGSET_BASE}/native/tinycbor/src/cborparser.c + ${THINGSET_BASE}/native/tinycbor/src/cborparser_float.c + ${THINGSET_BASE}/native/tinycbor/src/cborpretty.c ) +endif() + +# Ports +add_subdirectory(${THINGSET_BASE}/src/ports/loopback_simple ${THINGSET_BASE}/build/loopback_simple) + +# Applications +add_subdirectory(${THINGSET_BASE}/zephyr/apps/shell ${THINGSET_BASE}/build/shell) + endif() diff --git a/zephyr/Kconfig.thingset b/zephyr/Kconfig.thingset index d744f28..ac008f8 100644 --- a/zephyr/Kconfig.thingset +++ b/zephyr/Kconfig.thingset @@ -2,18 +2,40 @@ # SPDX-License-Identifier: Apache-2.0 config THINGSET - bool "ThingSet Protocol Library" + bool "ThingSet Device Library" help - This option enables the ThingSet Protocol Library (TS). + This option enables the ThingSet Device Library (TS). if THINGSET config THINGSET_ZEPHYR bool default y + select NET_BUF help ThingSet on Zephyr. +config THINGSET_LOG + bool + depends on LOG + default y + help + ThingSet logging support - by default follows Zephyr LOG configuration. + +config THINGSET_UNIT_TEST + bool "Enable unit testing support" + select ZTEST + default n + help + ThingSet unit test support. + +config THINGSET_ASSERT + bool "Enable assertions" + depends on ASSERT + default y + help + ThingSet assertions - by default follows Zephyr ASSERT configuration. + config THINGSET_NUM_JSON_TOKENS int "Maximum number of expected JSON tokens." default 50 @@ -50,6 +72,25 @@ config THINGSET_BYTE_STRING_TYPE_SUPPORT Switch on support for CBOR byte strings, which can store any sort of binary data and can be used e.g. for firmware upgrades. Byte strings are not supported by JSON. +config THINGSET_LOCAL_COUNT + int "Number of local context objects databases." + default 1 + help + Configure the total number of local contexts' object databases that a device has. + +config THINGSET_COM_BUF_COUNT + int "Number of communication buffers in the ThingSet buffer pool." + default 16 + help + Number of communication buffers in ThingSet device's buffer pool. + +config THINGSET_COM_BUF_DATA_SIZE + int "Data block size for ThingSet communication buffers." + default 1024 + help + Data block size for all ThingSet communication buffers. Buffers are allocated from this + block. + config THINGSET_CPP_LEGACY bool "Enable legacy C++ interface." default y @@ -62,5 +103,9 @@ module = THINGSET module-str = thingset source "subsys/logging/Kconfig.template.log_config" +rsource "Kconfig.thingset_core" +rsource "Kconfig.thingset_com" +rsource "Kconfig.thingset_apps" + endif diff --git a/zephyr/Kconfig.thingset_apps b/zephyr/Kconfig.thingset_apps new file mode 100644 index 0000000..8933dc7 --- /dev/null +++ b/zephyr/Kconfig.thingset_apps @@ -0,0 +1,13 @@ +# Copyright (c) 2021 Bobby Noelte +# SPDX-License-Identifier: Apache-2.0 + +config THINGSET_APPS + bool "ThingSet Applications" + help + This option enables the ThingSet applications. + +if THINGSET_APPS + +rsource "apps/shell/Kconfig" + +endif # THINGSET_APPS diff --git a/zephyr/Kconfig.thingset_com b/zephyr/Kconfig.thingset_com new file mode 100644 index 0000000..6ed8537 --- /dev/null +++ b/zephyr/Kconfig.thingset_com @@ -0,0 +1,37 @@ +# Copyright (c) 2021 Bobby Noelte +# SPDX-License-Identifier: Apache-2.0 + +config THINGSET_COM + bool "ThingSet Communication Support" + select POSIX_API if !ARCH_POSIX + help + This option enables the ThingSet communication support. + +if THINGSET_COM + +config THINGSET_REMOTE_COUNT + int "Number of remote context objects databases." + default 4 + help + Configure the total number of remote contexts' object databases that a device has. + +config THINGSET_REMOTE_OBJECT_COUNT + int "Number of remote objects that a device may handle." + default 16 + help + Configure the total number of remote objects that a device may handle. + +config THINGSET_PORT_COUNT + int "Number of ThingSet ports of a device" + default 1 + help + Configure the number of ThingSet ports of a device. + +config THINGSET_PORT_LOOPBACK_SIMPLE + bool "Enable simmple loopback ThingSet communication port" + default n + depends on THINGSET_PORT_COUNT > 0 + help + This option enables the simmple loopback ThingSet communication port. + +endif # THINGSET_COM diff --git a/zephyr/Kconfig.thingset_core b/zephyr/Kconfig.thingset_core new file mode 100644 index 0000000..fce7ace --- /dev/null +++ b/zephyr/Kconfig.thingset_core @@ -0,0 +1,11 @@ +# Copyright (c) 2021 Bobby Noelte +# SPDX-License-Identifier: Apache-2.0 + +config THINGSET_CORE + bool "ThingSet Core Support" + help + This option enables the ThingSet core support. + +if THINGSET_CORE + +endif # THINGSET_CORE diff --git a/zephyr/libc/minimal/extensions.c b/zephyr/libc/minimal/extensions.c deleted file mode 100644 index 85ff657..0000000 --- a/zephyr/libc/minimal/extensions.c +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright (c) 2021 Bobby Noelte. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "../../thingset_zephyr.h" - -/* - * strtod.c -- - * - * Source code for the "strtod" library procedure. - * - * Copyright (c) 1988-1993 The Regents of the University of California. - * Copyright (c) 1994 Sun Microsystems, Inc. - * - * Permission to use, copy, modify, and distribute this - * software and its documentation for any purpose and without - * fee is hereby granted, provided that the above copyright - * notice appear in all copies. The University of California - * makes no representations about the suitability of this - * software for any purpose. It is provided "as is" without - * express or implied warranty. - * - * RCS: @(#) $Id$ - */ - -/** - * Convert string to double. - * - * @param string A decimal ASCII floating-point number, - * optionally preceded by white space. - * Must have form "-I.FE-X", where I is the - * integer part of the mantissa, F is the - * fractional part of the mantissa, and X - * is the exponent. Either of the signs - * may be "+", "-", or omitted. Either I - * or F may be omitted, or both. The decimal - * point isn't necessary unless F is present. - * The "E" may actually be an "e". E and X - * may both be omitted (but not just one). - * @param endPtr If non-NULL, store terminating character's - * address here. - * @return double - */ -double ts_strtod(const char * string, char **endPtr) -{ - - static int maxExponent = 511; /* Largest possible base 10 exponent. Any - * exponent larger than this will already - * produce underflow or overflow, so there's - * no need to worry about additional digits. - */ - static double powersOf10[] = { /* Table giving binary powers of 10. Entry */ - 10., /* is 10^2^i. Used to convert decimal */ - 100., /* exponents into floating-point numbers. */ - 1.0e4, - 1.0e8, - 1.0e16, - 1.0e32, - 1.0e64, - 1.0e128, - 1.0e256 - }; - - int sign, expSign = false; - double fraction, dblExp, *d; - register const char *p; - register int c; - int exp = 0; /* Exponent read from "EX" field. */ - int fracExp = 0; /* Exponent that derives from the fractional - * part. Under normal circumstatnces, it is - * the negative of the number of digits in F. - * However, if I is very long, the last digits - * of I get dropped (otherwise a long I with a - * large negative exponent could cause an - * unnecessary overflow on I alone). In this - * case, fracExp is incremented one for each - * dropped digit. */ - int mantSize; /* Number of digits in mantissa. */ - int decPt; /* Number of mantissa digits BEFORE decimal - * point. */ - const char *pExp; /* Temporarily holds location of exponent - * in string. */ - - /* - * Strip off leading blanks and check for a sign. - */ - - p = string; - while (isspace(*p)) { - p += 1; - } - if (*p == '-') { - sign = true; - p += 1; - } else { - if (*p == '+') { - p += 1; - } - sign = false; - } - - /* - * Count the number of digits in the mantissa (including the decimal - * point), and also locate the decimal point. - */ - - decPt = -1; - for (mantSize = 0; ; mantSize += 1) - { - c = *p; - if (!isdigit(c)) { - if ((c != '.') || (decPt >= 0)) { - break; - } - decPt = mantSize; - } - p += 1; - } - - /* - * Now suck up the digits in the mantissa. Use two integers to - * collect 9 digits each (this is faster than using floating-point). - * If the mantissa has more than 18 digits, ignore the extras, since - * they can't affect the value anyway. - */ - - pExp = p; - p -= mantSize; - if (decPt < 0) { - decPt = mantSize; - } else { - mantSize -= 1; /* One of the digits was the point. */ - } - if (mantSize > 18) { - fracExp = decPt - 18; - mantSize = 18; - } else { - fracExp = decPt - mantSize; - } - if (mantSize == 0) { - fraction = 0.0; - p = string; - goto done; - } else { - int frac1, frac2; - frac1 = 0; - for ( ; mantSize > 9; mantSize -= 1) - { - c = *p; - p += 1; - if (c == '.') { - c = *p; - p += 1; - } - frac1 = 10*frac1 + (c - '0'); - } - frac2 = 0; - for (; mantSize > 0; mantSize -= 1) - { - c = *p; - p += 1; - if (c == '.') { - c = *p; - p += 1; - } - frac2 = 10*frac2 + (c - '0'); - } - fraction = (1.0e9 * frac1) + frac2; - } - - /* - * Skim off the exponent. - */ - - p = pExp; - if ((*p == 'E') || (*p == 'e')) { - p += 1; - if (*p == '-') { - expSign = true; - p += 1; - } else { - if (*p == '+') { - p += 1; - } - expSign = false; - } - while (isdigit(*p)) { - exp = exp * 10 + (*p - '0'); - p += 1; - } - } - if (expSign) { - exp = fracExp - exp; - } else { - exp = fracExp + exp; - } - - /* - * Generate a floating-point number that represents the exponent. - * Do this by processing the exponent one bit at a time to combine - * many powers of 2 of 10. Then combine the exponent with the - * fraction. - */ - - if (exp < 0) { - expSign = true; - exp = -exp; - } else { - expSign = false; - } - if (exp > maxExponent) { - exp = maxExponent; - errno = ERANGE; - } - dblExp = 1.0; - for (d = powersOf10; exp != 0; exp >>= 1, d += 1) { - if (exp & 01) { - dblExp *= *d; - } - } - if (expSign) { - fraction /= dblExp; - } else { - fraction *= dblExp; - } - - done: - if (endPtr != NULL) { - *endPtr = (char *) p; - } - - if (sign) { - return -fraction; - } - return fraction; -}; diff --git a/zephyr/thingset_zephyr.h b/zephyr/thingset_zephyr.h deleted file mode 100644 index fbdd7b1..0000000 --- a/zephyr/thingset_zephyr.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2021 Bobby Noelte. - * SPDX-License-Identifier: Apache-2.0 - */ - -#ifndef THINGSET_ZEPHYR_H_ -#define THINGSET_ZEPHYR_H_ - -#if !CONFIG_THINGSET_ZEPHYR -#error "You need to define CONGIG_THINGSET_ZEPHYR." -#endif - -/* Logging */ -#ifndef LOG_MODULE_NAME -#define LOG_MODULE_NAME thingset -#define LOG_LEVEL CONFIG_THINGSET_LOG_LEVEL -#endif -#include - -#ifdef THINGSET_MAIN -LOG_MODULE_REGISTER(LOG_MODULE_NAME); -#else -LOG_MODULE_DECLARE(LOG_MODULE_NAME); -#endif - -#define LOG_ALLOC_STR(str) ((str == NULL) ? log_strdup("null") : \ - log_strdup(str)) - -#include - -#if CONFIG_MINIMAL_LIBC -/* - * Zephyr's minimal libc is missing some functions. - * Provide !!sufficient!! replacements here. - */ -#include -#include -#include -#include - -#define isnan(value) __builtin_isnan(value) -#define isinf(value) __builtin_isinf(value) - -inline long long int llroundf(float x) -{ - return __builtin_llroundf(x); -}; - -double ts_strtod(const char * string, char **endPtr); -inline double strtod(const char * string, char **endPtr) -{ - return ts_strtod(string, endPtr); -}; - -inline long long strtoll(const char *str, char **endptr, int base) -{ - /* XXX good enough for thingset uses ?*/ - return (long long)strtol(str, endptr, base); -}; - -inline unsigned long long strtoull(const char *str, char **endptr, int base) -{ - /* XXX good enough for thingset uses ?*/ - return (unsigned long long)strtoul(str, endptr, base); -}; -#endif - -#if CONFIG_ZTEST -/* - * Thingset unit tests are basically made for the Unity test framework. - * Provide some adaptations to make them run under the ztest framework. - */ -#include "ztest/ztest_unity.h" -#endif - -#endif /* THINGSET_ZEPHYR_H_ */ From 87e534e5f910d9b69ad87f06c0d728332c43f5a1 Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Sun, 19 Dec 2021 12:51:56 +0100 Subject: [PATCH 34/37] test - include communication context in testing and adapt Adapt testing to communication context changes and include additional tests for communication context. Signed-off-by: Bobby Noelte --- test/main.cpp | 157 ++------- test/test.h | 560 +++++++++++++++++++++++++++++--- test/test_bin.c | 217 +++++-------- test/test_common.c | 79 ++--- test/test_context.c | 238 -------------- test/test_core.c | 618 ------------------------------------ test/test_data.c | 338 +++++++++++++++++--- test/test_shim.cpp | 17 +- test/test_txt.c | 91 ++++-- zephyr/tests/CMakeLists.txt | 29 +- zephyr/tests/Kconfig | 2 +- zephyr/tests/prj.conf | 33 +- zephyr/tests/src/main.c | 105 ------ zephyr/ztest/ztest_unity.h | 301 ------------------ 14 files changed, 1072 insertions(+), 1713 deletions(-) delete mode 100644 test/test_context.c delete mode 100644 test/test_core.c delete mode 100644 zephyr/tests/src/main.c delete mode 100644 zephyr/ztest/ztest_unity.h diff --git a/test/main.cpp b/test/main.cpp index 7935e22..7486b65 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -1,150 +1,39 @@ /* * Copyright (c) 2017 Martin Jäger / Libre Solar - * Copyright (c) 2021 Bobby Noelte. + * Copyright (c) 2021..2022 Bobby Noelte. * SPDX-License-Identifier: Apache-2.0 */ #include "test.h" -#ifndef UNITY -#error "You have to include unity.h. Should be done by test.h->thingset_priv.h" -#endif - - -void setUp (void) -{ - (void)ts_init(&ts, &data_objects[0], data_objects_size); -} - -void tearDown (void) -{ - // Nothing to do -} - -void tests_common() -{ - UNITY_BEGIN(); - - // data conversion tests - RUN_TEST(test_txt_patch_bin_fetch); - RUN_TEST(test_bin_patch_txt_fetch); - - UNITY_END(); -} - -void tests_text_mode() +TEST_MAIN() { - UNITY_BEGIN(); - - // GET request - RUN_TEST(test_txt_get_meas_names); - RUN_TEST(test_txt_get_meas_names_values); - RUN_TEST(test_txt_get_single_value); - - // FETCH request - RUN_TEST(test_txt_fetch_array); - RUN_TEST(test_txt_fetch_rounded); - RUN_TEST(test_txt_fetch_nan); - RUN_TEST(test_txt_fetch_inf); - RUN_TEST(test_txt_fetch_int32_array); - RUN_TEST(test_txt_fetch_float_array); + TS_LOGD("Running ThingSet tests"); - // PATCH request - RUN_TEST(test_txt_patch_wrong_data_structure); - RUN_TEST(test_txt_patch_array); - RUN_TEST(test_txt_patch_readonly); - RUN_TEST(test_txt_patch_wrong_path); - RUN_TEST(test_txt_patch_unknown_object); - RUN_TEST(test_txt_conf_callback); + // Test environment + tests_assert(); - // POST request - RUN_TEST(test_txt_exec); + // OSAL + tests_time(); + tests_buf(); - // statements (pub/sub messages) - RUN_TEST(test_txt_statement_subset); - RUN_TEST(test_txt_statement_group); - RUN_TEST(test_txt_pub_list_channels); - RUN_TEST(test_txt_pub_enable); - RUN_TEST(test_txt_pub_delete_append_object); - - // authentication - RUN_TEST(test_txt_auth_user); - RUN_TEST(test_txt_auth_root); - RUN_TEST(test_txt_auth_long_password); - RUN_TEST(test_txt_auth_failure); - RUN_TEST(test_txt_auth_reset); - - // general tests - RUN_TEST(test_txt_wrong_command); - RUN_TEST(test_txt_get_endpoint); - - // data export - RUN_TEST(test_txt_export); - - UNITY_END(); -} + // Libraries + tests_jsmn(); -void tests_binary_mode() -{ - UNITY_BEGIN(); - - // GET request - RUN_TEST(test_bin_get_meas_ids_values); - RUN_TEST(test_bin_get_meas_names_values); - RUN_TEST(test_bin_get_single_value); - - // PATCH request - RUN_TEST(test_bin_patch_multiple_objects); - RUN_TEST(test_bin_patch_float_array); - RUN_TEST(test_bin_patch_rounded_float); // writes an integer to float - - // FETCH request - RUN_TEST(test_bin_fetch_meas_ids); - RUN_TEST(test_bin_fetch_meas_names); - RUN_TEST(test_bin_fetch_multiple_objects); - RUN_TEST(test_bin_fetch_float_array); - RUN_TEST(test_bin_fetch_rounded_float); - - // POST request - RUN_TEST(test_bin_exec); - - // pub/sub messages - RUN_TEST(test_bin_statement_subset); - RUN_TEST(test_bin_statement_group); - RUN_TEST(test_bin_pub_can); - - // general tests - RUN_TEST(test_bin_num_elem); - RUN_TEST(test_bin_serialize_long_string); - - // binary (bytes) data type -#if TS_BYTE_STRING_TYPE_SUPPORT - RUN_TEST(test_bin_serialize_bytes); - RUN_TEST(test_bin_deserialize_bytes); - RUN_TEST(test_bin_patch_fetch_bytes); -#endif - - // data export/import - RUN_TEST(test_bin_export); - RUN_TEST(test_bin_import); - - UNITY_END(); -} + // ThingSet basic classes + tests_obj(); + tests_msg(); + tests_ctx(); + tests_shim(); -void tests_shim() -{ - UNITY_BEGIN(); + // ThingSet protocol + tests_common(); + tests_bin(); + tests_txt(); - // data conversion tests - RUN_TEST(test_shim_get_object); + // ThingSet applications + tests_shell(); - UNITY_END(); -} - -int main() -{ - tests_common(); - tests_text_mode(); - tests_binary_mode(); - tests_shim(); + // Test specific to the ThingSet implementation + tests_impl(); } diff --git a/test/test.h b/test/test.h index a8be964..4fa299e 100644 --- a/test/test.h +++ b/test/test.h @@ -4,15 +4,94 @@ * SPDX-License-Identifier: Apache-2.0 */ +/** + * @file + * @brief ThingSet unit testing (test interface). + */ + #ifndef THINGSET_TEST_H #define THINGSET_TEST_H -#include "../src/thingset_priv.h" +/** + * @brief ThingSet unit testing. + * + * @defgroup ts_test_api ThingSet unit testing (test interface). + * @{ + */ + +/* Public APIs */ +#include "../src/thingset.h" + +/* Private APIs */ +#include "../src/ts_macro.h" +#include "../src/ts_jsmn.h" +#include "../src/ts_buf.h" +#include "../src/ts_port.h" +#include "../src/ts_msg.h" +#include "../src/ts_obj.h" +#include "../src/ts_ctx.h" +#include "../src/ts_app.h" +#include "../apps/shell/ts_shell.h" #ifdef __cplusplus extern "C" { #endif +/* + * Test support + * ------------ + * + * Implemented in test_support.c + */ + +/** + * @def TEST_ASSERT_BUFFER_SIZE + * + * @brief Size of buffer for assertion message build up. + */ +#define TEST_ASSERT_BUFFER_SIZE 300 + +/** + * @brief Buffer for assertion message build up. + * + * @note Usage depends on choosen test framwork. + */ +extern char test_assert_buffer[TEST_ASSERT_BUFFER_SIZE]; + +/** + * @brief Current test file name. + * + * @note Usage depends on choosen test framwork. + */ +extern const char *test_file_current; + +/** + * @brief Current test function name. + * + * @note Usage depends on choosen test framwork. + */ +extern const char *test_func_current; + + +/** + * @def TEST_LOG_BUFFER_SIZE + * + * @brief Size of buffer for logging during test. + */ +#define TEST_LOG_BUFFER_SIZE 3000 + +/** + * @brief Buffer for logging during test - used mostly for message logging */ +extern char test_log_buf[TEST_LOG_BUFFER_SIZE]; + +#define TEST_REQ_BUFFER_LEN 500 +#define TEST_RESP_BUFFER_LEN 500 + +extern uint8_t test_req_buf[TEST_REQ_BUFFER_LEN]; +extern uint8_t test_resp_buf[TEST_RESP_BUFFER_LEN]; + +extern struct ts_array_info pub_serial_array; + /* * Common test data * ---------------- @@ -37,8 +116,12 @@ extern "C" { #define SUBSET_REPORT (1U << 0) // report subset of data items for publication #define SUBSET_CAN (1U << 1) // data nodes used for CAN bus publication messages -extern char manufacturer[]; +#define TEST_MANUFACTURER "Libre Solar" +#define TEST_DEVICE_ID_INSTANCE "ABCD1234" +#define TEST_DEVICE_ID_NEIGHBOUR "ABCD5678" + extern bool pub_report_enable; +extern uint16_t pub_report_interval; extern uint16_t pub_serial_interval; extern bool pub_can_enable; extern uint16_t pub_can_interval; @@ -53,60 +136,280 @@ extern float B[100]; extern struct ts_array_info float32_array; extern uint8_t bytes[300]; extern struct ts_bytes_buffer bytes_buf; -extern struct ts_data_object data_objects[]; -extern size_t data_objects_size; /* - * Context used for testing - * ------------------------ + * All the ports used for testing + * ------------------------------ * - * Implemented in test_context.c + * - 0: "instance" app port + * - 1: "neighbour" app port + * - 2: shell application port + * - 3: loopback a port + * - 4: loopback b port */ -#define TS_REQ_BUFFER_LEN 500 -#define TS_RESP_BUFFER_LEN 500 -extern struct ts_context ts; -extern uint8_t req_buf[TS_REQ_BUFFER_LEN]; -extern uint8_t resp_buf[TS_RESP_BUFFER_LEN]; +#define TEST_PORT_COUNT 5 -extern bool conf_callback_called; -extern bool dummy_called_flag; -extern struct ts_array_info pub_serial_array; +/* + * Local contexts used for testing + * ------------------------------- + */ + +#define TEST_CORE_LOCID 0 /* TS_CONFIG_CORE_LOCID is 0 by default */ +#define TEST_INSTANCE_LOCID 1 +#define TEST_NEIGHBOUR_LOCID 2 + +#define TEST_INSTANCE_UID 0x1111111111111111ULL; +#define TEST_NEIGHBOUR_UID 0x2222222222222222ULL; + +extern struct ts_ctx_core_data TS_CAT(ts_ctx_core_data_, TEST_CORE_LOCID); +extern struct ts_ctx_com_data TS_CAT(ts_ctx_com_data_, TEST_INSTANCE_LOCID); +extern struct ts_ctx_com_data TS_CAT(ts_ctx_com_data_, TEST_NEIGHBOUR_LOCID); +extern const struct ts_ctx_core TS_CAT(ts_ctx_core_, TEST_CORE_LOCID); +extern const struct ts_ctx_com TS_CAT(ts_ctx_com_, TEST_INSTANCE_LOCID); +extern const struct ts_ctx_com TS_CAT(ts_ctx_com_, TEST_NEIGHBOUR_LOCID); + +/* + * Applications used for testing + * ----------------------------- + * + * Implemented in test_data.c + */ + +/* Dummy applications */ + +struct test_app_config; +struct test_app_data; + +#define TEST_APP_INSTANCE_PORTID 0 +#define TEST_APP_INSTANCE_LOCID TEST_INSTANCE_LOCID +#define TEST_APP_INSTANCE_NAME "test_app_instance" + +extern const struct thingset_port TS_CAT(ts_port_, TEST_APP_INSTANCE_PORTID); +extern const struct thingset_app TS_CAT(ts_port_config_, TEST_APP_INSTANCE_PORTID); +extern struct ts_app_port_data TS_CAT(ts_port_data_, TEST_APP_INSTANCE_PORTID); + +#define TEST_APP_NEIGHBOUR_PORTID 1 +#define TEST_APP_NEIGHBOUR_LOCID TEST_NEIGHBOUR_LOCID +#define TEST_APP_NEIGHBOUR_NAME "test_app_neighbour" + +extern const struct thingset_port TS_CAT(ts_port_, TEST_APP_NEIGHBOUR_PORTID); +extern const struct thingset_app TS_CAT(ts_port_config_, TEST_APP_NEIGHBOUR_PORTID); +extern struct ts_app_port_data TS_CAT(ts_port_data_, TEST_APP_NEIGHBOUR_PORTID); + +/* Shell application used in tests (Zephyr only at the moment) */ +#define TEST_SHELL_PORTID 2 +#define TEST_SHELL_LOCID TEST_INSTANCE_LOCID +#define TEST_SHELL_NAME "test_shell" + +extern const struct thingset_port TS_CAT(ts_port_, TEST_SHELL_PORTID); +extern const struct thingset_app TS_CAT(ts_port_config_, TEST_SHELL_LOCID); +extern struct ts_app_port_data TS_CAT(ts_port_data_, TEST_SHELL_LOCID); + +/* + * Ports used for testing + * ---------------------- + * + * Implemented in test_data.c + */ + +extern thingset_uid_t test_uid_instance; +extern thingset_uid_t test_uid_neighbour; + +#include "../src/ports/loopback_simple/loopback_simple.h" +extern const struct ts_port_api TS_CAT(loopback_simple, _api); + +#define TEST_PORT_LOOPBACK_A_PORTID 3 +#define TEST_PORT_LOOPBACK_A_LOCID TEST_INSTANCE_LOCID +#define TEST_PORT_LOOPBACK_A_NAME "test_port_loopback_a" + +extern const struct thingset_port TS_CAT(ts_port_, TEST_PORT_LOOPBACK_A_PORTID); +extern const THINGSET_PORT_CONFIG_STRUCT(loopback_simple) + TS_CAT(ts_port_config_, TEST_PORT_LOOPBACK_A_PORTID); +extern THINGSET_PORT_DATA_STRUCT(loopback_simple) + TS_CAT(ts_port_data_, TEST_PORT_LOOPBACK_A_PORTID); + +#define TEST_PORT_LOOPBACK_B_PORTID 4 +#define TEST_PORT_LOOPBACK_B_LOCID TEST_INSTANCE_LOCID +#define TEST_PORT_LOOPBACK_B_NAME "test_port_loopback_b" + +extern const struct thingset_port TS_CAT(ts_port_, TEST_PORT_LOOPBACK_B_PORTID); +extern const THINGSET_PORT_CONFIG_STRUCT(loopback_simple) + TS_CAT(ts_port_config_, TEST_PORT_LOOPBACK_B_PORTID); +extern THINGSET_PORT_DATA_STRUCT(loopback_simple) + TS_CAT(ts_port_data_, TEST_PORT_LOOPBACK_B_PORTID); + +/* + * Helper Functions + * ---------------- + * + * Implemented in test_support.c + */ + +/* Test core context */ +extern bool test_core_conf_callback_called; +extern bool test_core_dummy_called; +extern void test_core_dummy_function(void); +extern void test_core_conf_callback(void); +extern void test_core_reset_function(void); +extern void test_core_auth_function(void); + +/* Test communication context */ +extern bool test_instance_conf_callback_called; +extern bool test_instance_dummy_called; +extern void test_instance_dummy_function(void); +extern void test_instance_conf_callback(void); +extern void test_instance_reset_function(void); +extern void test_instance_auth_function(void); + +extern void test_neighbour_reset_function(void); +/* + * Helpers and complex assert functions + * ------------------------------------ + * + * Implemented in test_support.c + */ + +/** + * @def TEST_ASSERT_EXEC + * + * @brief Support macro for complex TEST_ASSERT_xxx macros. + * + * @note May be re-defined by the specific test environment. + */ +#ifndef TEST_ASSERT_EXEC +#define TEST_ASSERT_EXEC(...) \ + do { \ + const char * test_file_save = test_file_current; \ + const char * test_func_save = test_func_current; \ + test_file_current = __FILE__; \ + test_func_current = __func__; \ + __VA_ARGS__; \ + test_file_current = test_file_save; \ + test_func_current = test_func_save; \ + } while(false) +#endif + +extern int _hex2bin(uint8_t *bin, size_t bin_size, const char *hex); + +/* complex assert helpers - work on 'core' context */ + +void assert_bin_resp(const uint8_t *test_req_buf, int resp_len, const char *exp_hex, + unsigned int assert_line, const char *assert_msg); + +void assert_bin_req(const uint8_t *req_b, int req_len, const char *exp_hex, + unsigned int assert_line, const char *assert_msg); + +void assert_bin_req_exp_bin(const uint8_t *req_b, int req_len, const uint8_t *exp_b, int exp_len, + unsigned int assert_line, const char *assert_msg); + +void assert_bin_req_hex(const char *req_hex, const char *exp_hex, + unsigned int assert_line, const char *assert_msg); -/* Helper functions (see test_context.c) */ -void dummy(void); -void conf_callback(void); -void reset_function(void); -void auth_function(void); -int _hex2bin(uint8_t *bin, size_t bin_size, const char *hex); - -/* complex assert helpers (see test.context.c) */ -void assert_bin_resp(const uint8_t *resp_buf, int resp_len, const char *exp_hex, const char*msg); -void assert_bin_req(const uint8_t *req_b, int req_len, const char *exp_hex, const char*msg); -void assert_bin_req_exp_bin(const uint8_t *req_b, int req_len, const uint8_t *exp_b, int exp_len, const char*msg); -void assert_bin_req_hex(const char *req_hex, const char *exp_hex, const char*msg); void assert_txt_resp(int exp_len, const char *exp_s, const char*msg); void assert_txt_req(const char *req_s, const char *exp_s, const char*msg); void assert_json2cbor(char const *name, char const *json_value, uint16_t id, const char *const cbor_value_hex, const char*msg); void assert_cbor2json(char const *name, char const *json_value, uint16_t id, char const *cbor_value_hex, const char*msg); -#ifndef STRINGIFY -#define STRINGIFY(x) _STRINGIFY(x) -#define _STRINGIFY(x) #x -#endif +/* complex assert helpers - work on communication context "instance" */ + +/** @brief Number of bytes at start of message log to skip when comparing to expected result */ +#define ASSERT_MSG_LOG_SKIP 15 + +void assert_msg_add_response_get_cbor(struct thingset_msg *response, struct thingset_msg *request, + const char *object_path, int expected_ret, + const char *expected_str, + unsigned int assert_line, const char *assert_msg); + +void assert_msg_add_response_get_json(struct thingset_msg *response, struct thingset_msg *request, + ts_obj_id_t obj_id, int expected_ret, + const char *expected_str, + unsigned int assert_line, const char *assert_msg); + +void assert_msg_fetch_cbor(struct thingset_msg *request, struct thingset_msg *response, + const char *object_path, + bool test_object_id, + bool test_object_ids, uint16_t object_count, const char **object_names, + int expected_ret_add_request, const char *expected_str_add_request, + int expected_ret_pull_request, uint8_t expected_status_id_pull_request, + int expected_ret_add_response, const char *expected_str_add_response, + unsigned int assert_line, const char *assert_msg); + +/* + * Test asserts + * ------------ + */ #define ASSERT_MSG(assert_func) \ - _ASSERT_MSG("Assertion triggered by " assert_func " at #" STRINGIFY(__LINE__) " " __FILE__) + _ASSERT_MSG("Assertion triggered by " assert_func ) #define _ASSERT_MSG(assert_msg) assert_msg -#define TEST_ASSERT_BIN_RESP(resp, resp_len, exp) \ - assert_bin_resp(resp, resp_len, exp, ASSERT_MSG("TEST_ASSERT_BIN_RESP(" #resp ", " #resp_len ", " #exp ")")) -#define TEST_ASSERT_BIN_REQ(req, req_len, exp) \ - assert_bin_req(req, req_len, exp, ASSERT_MSG("TEST_ASSERT_BIN_REQ(" #req ", " #req_len ", " #exp ")")) -#define TEST_ASSERT_BIN_REQ_EXP_BIN(req, req_len, exp, exp_len) \ - assert_bin_req_exp_bin(req, req_len, exp, exp_len, ASSERT_MSG("TEST_ASSERT_BIN_REQ_EXP_BIN(" #req ", " #req_len ", " #exp ", " #exp_len ")")) -#define TEST_ASSERT_BIN_REQ_HEX(req, exp) \ - assert_bin_req_hex(req, exp, ASSERT_MSG("TEST_ASSERT_BIN_REQ_HEX(" #req ", " #exp ")")) +/** + * @def TEST_ASSERT_BIN_RESP + * + * @brief Assert binary response message content. + * + * @param[in] resp Response message buffer. + * @param[in] resp_len Response message length. + * @param[in] exp Expected response message content defined as string with hex bytes definitions. + */ +#define TEST_ASSERT_BIN_RESP(resp, resp_len, exp) \ + TEST_ASSERT_EXEC( \ + assert_bin_resp(resp, resp_len, exp, __LINE__, \ + ASSERT_MSG("TEST_ASSERT_BIN_RESP(" #resp ", " #resp_len ", " #exp ")")) \ + ) + +/** + * @def TEST_ASSERT_BIN_REQ + * + * @brief Assert reponse on binary request message. + * + * @note Works on 'core' ThingSet context. + * + * @param[in] req Request message buffer. + * @param[in] req_len Request message length. + * @param[in] exp Expected response message content defined as string with hex bytes definitions. + */ +#define TEST_ASSERT_BIN_REQ(req, req_len, exp) \ + TEST_ASSERT_EXEC( \ + assert_bin_req(req, req_len, exp, __LINE__, \ + ASSERT_MSG("TEST_ASSERT_BIN_REQ(" #req ", " #req_len ", " #exp ")")) \ + ) + +/** + * @def TEST_ASSERT_BIN_REQ_EXP_BIN + * + * @brief Assert response on binary request message. + * + * @note Works on 'core' ThingSet context. + * + * @param[in] req Request message buffer. + * @param[in] req_len Request message length. + * @param[in] exp Expected binary reponse message. + * @param[in] exp_len Expected binary response message length. + */ +#define TEST_ASSERT_BIN_REQ_EXP_BIN(req, req_len, exp, exp_len) \ + TEST_ASSERT_EXEC( \ + assert_bin_req_exp_bin(req, req_len, exp, exp_len, __LINE__, \ + ASSERT_MSG("TEST_ASSERT_BIN_REQ_EXP_BIN(" #req ", " #req_len ", " #exp ", " #exp_len ")")) \ + ) + +/** + * @def TEST_ASSERT_BIN_REQ_HEX + * + * @brief Assert reponse on binary request message. + * + * @note Works on 'core' ThingSet context. + * + * @param[in] req Request message content defined as string with hex bytes definitions. + * @param[in] exp Expected response message content defined as string with hex bytes definitions. + */ +#define TEST_ASSERT_BIN_REQ_HEX(req, exp) \ + TEST_ASSERT_EXEC( \ + assert_bin_req_hex(req, exp, __LINE__, \ + ASSERT_MSG("TEST_ASSERT_BIN_REQ_HEX(" #req ", " #exp ")")) \ + ) + #define TEST_ASSERT_TXT_RESP(len, resp) \ assert_txt_resp(len, resp, ASSERT_MSG("TEST_ASSERT_TXT_RESP(" #len ", " #resp ")")) #define TEST_ASSERT_TXT_REQ(req, exp) \ @@ -116,17 +419,123 @@ void assert_cbor2json(char const *name, char const *json_value, uint16_t id, cha #define TEST_ASSERT_CBOR2JSON(name, json_value, id, cbor_hex_value) \ assert_cbor2json(name, json_value, id, cbor_hex_value, ASSERT_MSG("TEST_ASSERT_CBOR2JSON(" #name ", " #json_value ", " #id ", " #cbor_hex_value ")")) +/** + * @def TEST_ASSERT_MSG_ADD_RESPONSE_GET_CBOR + * + * @brief Assert ts_msg_add_response_get_cbor() + * + * @note Works on 'instance' ThingSet communication context. + * + * @param[in] tc test case string + * @param[in] response communication message buffer to add response to + * @param[in] request message buffer that contains a (partial) request. + * Any of TS_MSG_CODE_REQUEST_GET_VALUES, TS_MSG_CODE_REQUEST_GET_IDS, + * TS_MSG_CODE_REQUEST_GET_NAMES_VALUES shall be set in message status. + * @param[in] obj_id Object id + * @param[in] expected_ret Expected return value + * @param[in] expected_str Expected message content as printed by ts_msg_log(). + */ +#define TEST_ASSERT_MSG_ADD_RESPONSE_GET_CBOR(tc, response, request, obj_id, \ + expected_ret, expected_str) \ + TEST_ASSERT_EXEC( \ + assert_msg_add_response_get_cbor(response, request, obj_id, expected_ret, expected_str, \ + __LINE__, ASSERT_MSG("TEST_ASSERT_MSG_ADD_RESPONSE_GET_CBOR(" #tc ", ...)")) \ + ) + +/** + * @def TEST_ASSERT_MSG_ADD_RESPONSE_GET_JSON + * + * @brief Assert ts_msg_add_response_get_json() + * + * @note Works on 'instance' ThingSet communication context. + * + * @param[in] tc test case string + * @param[in] response communication message buffer to add response to + * @param[in] request message buffer that contains a (partial) request. + * Any of TS_MSG_CODE_REQUEST_GET_VALUES, TS_MSG_CODE_REQUEST_GET_IDS, + * TS_MSG_CODE_REQUEST_GET_NAMES_VALUES shall be set in message status. + * @param[in] object_path Path to object + * @param[in] expected_ret Expected return value + * @param[in] expected_str Expected message content as printed by ts_msg_log(). + */ +#define TEST_ASSERT_MSG_ADD_RESPONSE_GET_JSON(tc, response, request, object_path, \ + expected_ret, expected_str) \ + TEST_ASSERT_EXEC( \ + assert_msg_add_response_get_json(response, request, object_path, expected_ret, \ + expected_str, __LINE__, \ + ASSERT_MSG("TEST_ASSERT_MSG_ADD_RESPONSE_GET_JSON(" #tc ", ...)")) \ + ) + +/** + * @def TEST_ASSERT_MSG_FETCH_CBOR + * + * @brief Assert ts_msg_add_request_fetch_cbor() -> ts_msg_pull_request_cbor() -> ts_msg_add_response_fetch_cbor() + * + * @note Works on 'instance' ThingSet communication context. + * + * @param[in] tc test case string + * @param[in] request communication message buffer + * @param[in] response communication message buffer + * @param[in] object_path Object path + * @param[in] test_object_id Test with single object id defined by object path + * @param[in] test_object_ids Test with several object ids defined by object names + * @param[in] object_count Number of object ids to test with + * @param[in] object_names Pointer to array of object names. + * @param[in] expected_ret_add_request Expected return value of ts_msg_add_request_fetch_cbor(). + * @param[in] expected_str_add_request Expected message content of request message of + * ts_msg_add_request_fetch_cbor() as printed by ts_msg_log(). + * @param[in] expected_ret_pull_request Expected return value of ts_msg_pull_request_cbor(). + * @param[in] expected_status_id_pull_request Expected message status id returned + * by ts_msg_pull_request_cbor(). + * @param[in] expected_ret_add_response Expected return value of ts_msg_add_response_fetch_cbor(). + * @param[in] expected_str_add_response Expected message content reponse message of + * ts_msg_add_response_fetch_cbor() as printed by ts_msg_log(). + */ +#define TEST_ASSERT_MSG_FETCH_CBOR(tc, request, response, object_path, test_object_id, \ + test_object_ids, object_count, object_names, \ + expected_ret_add_request, expected_str_add_request, \ + expected_ret_pull_request, expected_status_id_pull_request, \ + expected_ret_add_response, expected_str_add_response) \ + TEST_ASSERT_EXEC( \ + assert_msg_fetch_cbor(request, response, object_path, test_object_id, \ + test_object_ids, object_count, object_names, \ + expected_ret_add_request, expected_str_add_request, \ + expected_ret_pull_request, expected_status_id_pull_request, \ + expected_ret_add_response, expected_str_add_response, \ + __LINE__, \ + ASSERT_MSG("TEST_ASSERT_MSG_FETCH_CBOR(" #tc ", ...)")) \ + ) + +/* + * Test initialisation + * --------------------- + * + * Implemented in test_data.c + */ + +/** + * @brief Initialise test data to predefined values. + */ +void test_init_test_data(void); + +/** + * @brief Setup for test case as used by Unity Test. + */ +void setUp(void); + +/** + * @brief Teardown for test case as used by Unity Test. + */ +void tearDown(void); + /* * Test functions * -------------- - * - * Implemented in test_txt.c, test_bin.c, test_common.c */ void test_assert(void); void test_txt_patch_bin_fetch(void); void test_bin_patch_txt_fetch(void); -void test_ts_init(void); void test_txt_get_meas_names(void); void test_txt_get_meas_names_values(void); @@ -171,24 +580,77 @@ void test_bin_patch_rounded_float(void); void test_bin_fetch_rounded_float(void); void test_bin_statement_subset(void); void test_bin_statement_group(void); -void test_bin_pub_can(void); void test_bin_exec(void); -void test_bin_num_elem(void); -void test_bin_serialize_long_string(void); -void test_bin_serialize_bytes(void); -void test_bin_deserialize_bytes(void); void test_bin_patch_fetch_bytes(void); void test_bin_export(void); void test_bin_import(void); +void test_buf(void); +void test_time(void); +void test_ctx_com(void); +void test_ctx_com_port(void); +void test_ctx_com_node(void); +void test_ctx_core(void); +void test_msg_add_primitives(void); +void test_msg_add_response_cbor(void); +void test_msg_add_response_json(void); +void test_msg_add_statement_cbor(void); +void test_msg_add_statement_json(void); +void test_msg_alloc(void); +void test_msg_pull_request_get_cbor(void); +void test_msg_pull_request_get_json(void); +void test_msg_pull_request_fetch_json(void); +void test_msg_pull_request_patch_json(void); +void test_msg_pull_request_exec_json(void); +void test_msg_pull_request_create_json(void); +void test_msg_pull_request_delete_json(void); +void test_msg_pull_request_invalid_json(void); +void test_obj_db_init(void); +void test_jsmn(void); + +/* + * Test suites/ sets + * ----------------- + */ + +void tests_assert(void); +void tests_time(void); +void tests_buf(void); +void tests_jsmn(void); +void tests_obj(void); +void tests_ctx(void); +void tests_msg(void); +void tests_txt(void); +void tests_bin(void); +void tests_common(void); +void tests_shell(void); + +/** + * @brief Run ThingSet implementation specific test suite. + * + * @note tests_impl() to be provided by the implementation. + */ +void tests_impl(void); + #ifdef __cplusplus } /* extern "C" */ /* - * Implemented in test_shim.cpp + * C++ test functions + * ------------------ */ void test_shim_get_object(void); +/* + * C++ Test suites/ sets + * --------------------- + */ +void tests_shim(void); + #endif +/** + * @} + */ + #endif /* THINGSET_TEST_H */ diff --git a/test/test_bin.c b/test/test_bin.c index 4ed420c..17490f9 100644 --- a/test/test_bin.c +++ b/test/test_bin.c @@ -74,13 +74,13 @@ void test_bin_patch_multiple_objects(void) { const char req_hex[] = "07 06 " - #if TS_64BIT_TYPES_SUPPORT +#if TS_CONFIG_64BIT_TYPES_SUPPORT "A9 " // write map with 9 elements "19 60 01 01 " // value 1 "19 60 02 02 " - #else +#else "A7 " // write map with 7 elements - #endif +#endif "19 60 03 03 " "19 60 04 04 " "19 60 05 05 " @@ -99,13 +99,13 @@ void test_bin_fetch_multiple_objects(void) char req_hex[] = "05 06 " - #if TS_64BIT_TYPES_SUPPORT +#if TS_CONFIG_64BIT_TYPES_SUPPORT "89 " // read array with 9 elements "19 60 01 " "19 60 02 " - #else +#else "87 " // read array with 7 elements - #endif +#endif "19 60 03 " "19 60 04 " "19 60 05 " @@ -114,13 +114,13 @@ void test_bin_fetch_multiple_objects(void) "19 60 08 " "19 60 09 "; const char resp_hex[] = - #if TS_64BIT_TYPES_SUPPORT +#if TS_CONFIG_64BIT_TYPES_SUPPORT "85 89 " // successful response: array with 9 elements - "01 " // value 1 + "01 " // value 1 "02 " - #else +#else "85 87 " // successful response: array with 7 elements - #endif +#endif "03 " "04 " "05 " @@ -224,88 +224,51 @@ void test_bin_statement_subset(void) "FA 40 a4 28 f6 " // float 5.13 "16 "; // int 22 - int resp_len = ts_bin_statement_by_path(&ts, resp_buf, sizeof(resp_buf), "report"); + int resp_len = thingset_bin_statement_by_path(test_resp_buf, sizeof(test_resp_buf), "report"); - TEST_ASSERT_BIN_RESP(resp_buf, resp_len, resp_expected); + TEST_ASSERT_BIN_RESP(test_resp_buf, resp_len, resp_expected); - resp_len = ts_bin_statement_by_id(&ts, resp_buf, sizeof(resp_buf), ID_REPORT); + resp_len = thingset_bin_statement_by_id(test_resp_buf, sizeof(test_resp_buf), ID_REPORT); - TEST_ASSERT_BIN_RESP(resp_buf, resp_len, resp_expected); + TEST_ASSERT_BIN_RESP(test_resp_buf, resp_len, resp_expected); } void test_bin_statement_group(void) { const char resp_expected[] = - "1F " + "1F " // TS_MSG_CODE_STATEMENT "01 " // ID of "info" "83 " // array with 3 elements "6B 4C 69 62 72 65 20 53 6F 6C 61 72 " // "Libre Solar" "1A 00 BC 61 4E " // int 12345678 "68 41 42 43 44 31 32 33 34 "; // "ABCD1234" - int resp_len = ts_bin_statement_by_path(&ts, resp_buf, sizeof(resp_buf), "info"); - - TEST_ASSERT_BIN_RESP(resp_buf, resp_len, resp_expected); + int resp_len = thingset_bin_statement_by_path(test_resp_buf, sizeof(test_resp_buf), "info"); - resp_len = ts_bin_statement_by_id(&ts, resp_buf, sizeof(resp_buf), ID_INFO); + TEST_ASSERT_BIN_RESP(test_resp_buf, resp_len, resp_expected); - TEST_ASSERT_BIN_RESP(resp_buf, resp_len, resp_expected); -} + resp_len = thingset_bin_statement_by_id(test_resp_buf, sizeof(test_resp_buf), ID_INFO); -void test_bin_pub_can(void) -{ - int start_pos = 0; - uint32_t msg_id; - uint8_t can_data[8]; - - const uint8_t Bat_V_hex[] = { 0xFA, 0x41, 0x61, 0x99, 0x9a }; - const uint8_t Bat_A_hex[] = { 0xFA, 0x40, 0xa4, 0x28, 0xf6 }; - - // first call (should return Bat_V) - int can_data_len = ts_bin_pub_can(&ts, &start_pos, SUBSET_CAN, 123, &msg_id, &can_data[0]); - TEST_ASSERT_NOT_EQUAL(-1, can_data_len); - - uint16_t can_dev_id = (msg_id & 0x00FFFF00) >> 8; - uint32_t can_subsets = TS_CAN_PUBSUB(msg_id); - uint32_t can_prio = msg_id & TS_CAN_PRIO_MASK; - TEST_ASSERT_EQUAL_UINT16(0x71, can_dev_id); - TEST_ASSERT_EQUAL_UINT32(TS_CAN_PRIO_PUBSUB_LOW, can_prio); - TEST_ASSERT(can_subsets); - TEST_ASSERT_EQUAL_HEX8_ARRAY(&Bat_V_hex[0], &can_data[0], sizeof(Bat_V_hex)); - - // second call (should return Bat_A) - can_data_len = ts_bin_pub_can(&ts, &start_pos, SUBSET_CAN, 123, &msg_id, &can_data[0]); - TEST_ASSERT_NOT_EQUAL(-1, can_data_len); - - can_dev_id = (msg_id & 0x00FFFF00) >> 8; - can_subsets = TS_CAN_PUBSUB(msg_id); - can_prio = msg_id & TS_CAN_PRIO_MASK; - TEST_ASSERT_EQUAL_UINT16(0x72, can_dev_id); - TEST_ASSERT_EQUAL_UINT32(TS_CAN_PRIO_PUBSUB_LOW, can_prio); - TEST_ASSERT(can_subsets); - TEST_ASSERT_EQUAL_HEX8_ARRAY(&Bat_A_hex[0], &can_data[0], sizeof(Bat_A_hex)); - - // third call (should not find further objects) - can_data_len = ts_bin_pub_can(&ts, &start_pos, SUBSET_CAN, 123, &msg_id, &can_data[0]); - TEST_ASSERT_EQUAL(-1, can_data_len); + TEST_ASSERT_BIN_RESP(test_resp_buf, resp_len, resp_expected); } void test_bin_import(void) { const char req_hex[] = - "A2 " // map with 2 elements + "86 " // TS_MSG_CODE_EXPORT + "A2 " // map with 2 elements "18 31 FA 41 61 99 9a " // float 14.10 "18 32 FA 40 a4 28 f6 "; // float 5.13 - int req_buf_len = _hex2bin(req_buf, sizeof(req_buf), req_hex); + int req_buf_len = _hex2bin(test_req_buf, sizeof(test_req_buf), req_hex); - int ret = ts_bin_import(&ts, req_buf, req_buf_len, TS_WRITE_MASK, SUBSET_REPORT); + int ret = thingset_bin_import(test_req_buf, req_buf_len, TS_WRITE_MASK, SUBSET_REPORT); TEST_ASSERT_EQUAL(TS_STATUS_CHANGED, ret); } void test_bin_exec(void) { - dummy_called_flag = 0; + test_core_dummy_called = 0; const uint8_t req[] = { TS_POST, @@ -317,87 +280,9 @@ void test_bin_exec(void) TEST_ASSERT_BIN_REQ_EXP_BIN(req, sizeof(req), resp_expected, sizeof(resp_expected)); - TEST_ASSERT_EQUAL(1, dummy_called_flag); + TEST_ASSERT_EQUAL(1, test_core_dummy_called); } -void test_bin_num_elem(void) -{ - const uint8_t req[] = { 0xB9, 0xF0, 0x00 }; - uint16_t num_elements; - - cbor_num_elements(&req[0], &num_elements); - - TEST_ASSERT_EQUAL_UINT16(0xF000, num_elements); -} - -void test_bin_serialize_long_string(void) -{ - /* use public buffers for testing - assure sufficient size */ - char *str = (char *)&req_buf[0]; - uint8_t *buf = &resp_buf[0]; - TEST_ASSERT_GREATER_OR_EQUAL_size_t(301, sizeof(req_buf)); - TEST_ASSERT_GREATER_OR_EQUAL_size_t(302, sizeof(resp_buf)); - - for (unsigned int i = 0; i < 301; i++) { - str[i] = 'T'; - } - str[256] = '\0'; // terminate string after 256 bytes - - int len_total = cbor_serialize_string(buf, str, 302); - - TEST_ASSERT_EQUAL_UINT(256 + 3, len_total); // strlen + below 3 bytes - TEST_ASSERT_EQUAL_UINT(0x79, buf[0]); - TEST_ASSERT_EQUAL_UINT(0x01, buf[1]); - TEST_ASSERT_EQUAL_UINT(0x00, buf[2]); // null-termination is not stored -} - -#if TS_BYTE_STRING_TYPE_SUPPORT -void test_bin_serialize_bytes(void) -{ - /* use public buffers for testing - assure sufficient size */ - uint8_t *bytes = &req_buf[0]; - uint8_t *cbor = &resp_buf[0]; - TEST_ASSERT_GREATER_OR_EQUAL_size_t(300, sizeof(req_buf)); - TEST_ASSERT_GREATER_OR_EQUAL_size_t(400, sizeof(resp_buf)); - - for (unsigned int i = 0; i < 300; i++) { - bytes[i] = i % 256; - } - - // this should consider 0 as normal bytes and not terminate there - int len_total = cbor_serialize_bytes(cbor, bytes, 300, 400); - - TEST_ASSERT_EQUAL_UINT(303, len_total); - TEST_ASSERT_EQUAL_UINT(0x59, cbor[0]); - TEST_ASSERT_EQUAL_UINT(0x01, cbor[1]); // 0x01 << 8 + 0x2C = 300 - TEST_ASSERT_EQUAL_UINT(0x2C, cbor[2]); - TEST_ASSERT_EQUAL_HEX8_ARRAY(bytes, &cbor[3], 300); -} - -void test_bin_deserialize_bytes(void) -{ - /* use public buffers for testing - assure sufficient size */ - uint8_t *bytes = &req_buf[0]; - uint8_t *cbor = &resp_buf[0]; - TEST_ASSERT_GREATER_OR_EQUAL_size_t(300, sizeof(req_buf)); - TEST_ASSERT_GREATER_OR_EQUAL_size_t(400, sizeof(resp_buf)); - - cbor[0] = 0x59; - cbor[1] = 0x01; - cbor[2] = 0x2C; - for (unsigned int i = 0; i < 300; i++) { - cbor[i + 3] = i % 256; - } - - uint16_t num_bytes; - int len_total = cbor_deserialize_bytes(cbor, bytes, 300, &num_bytes); - - TEST_ASSERT_EQUAL_UINT(303, len_total); - TEST_ASSERT_EQUAL_UINT(300, num_bytes); - TEST_ASSERT_EQUAL_HEX8_ARRAY(&bytes[0], &cbor[3], 300); -} -#endif /* TS_BYTE_STRING_TYPE_SUPPORT */ - void test_bin_patch_fetch_bytes(void) { const uint8_t req_patch[] = { @@ -427,13 +312,61 @@ void test_bin_patch_fetch_bytes(void) void test_bin_export(void) { const char resp_expected[] = + "86 " // TS_MSG_CODE_EXPORT "A4 " // map with 4 elements "18 1A 1A 00 BC 61 4E " // int 12345678 "18 71 FA 41 61 99 9a " // float 14.10 "18 72 FA 40 a4 28 f6 " // float 5.13 "18 73 16 "; // int 22 - int resp_len = ts_bin_export(&ts, resp_buf, sizeof(resp_buf), SUBSET_REPORT); + int resp_len = thingset_bin_export(test_req_buf, sizeof(test_req_buf), SUBSET_REPORT); + + TEST_ASSERT_BIN_RESP(test_req_buf, resp_len, resp_expected); +} + +void tests_bin(void) +{ + UNITY_BEGIN(); + + // GET request + RUN_TEST(test_bin_get_meas_ids_values); + RUN_TEST(test_bin_get_meas_names_values); + RUN_TEST(test_bin_get_single_value); + + // PATCH request + RUN_TEST(test_bin_patch_multiple_objects); + RUN_TEST(test_bin_patch_float_array); + RUN_TEST(test_bin_patch_rounded_float); // writes an integer to float + + // FETCH request + RUN_TEST(test_bin_fetch_meas_ids); + RUN_TEST(test_bin_fetch_meas_names); + RUN_TEST(test_bin_fetch_multiple_objects); + RUN_TEST(test_bin_fetch_float_array); + RUN_TEST(test_bin_fetch_rounded_float); + + // POST request + RUN_TEST(test_bin_exec); + + // pub/sub messages + RUN_TEST(test_bin_statement_subset); + RUN_TEST(test_bin_statement_group); + //RUN_TEST(test_bin_pub_can); + + // general tests + //RUN_TEST(test_bin_num_elem); + //RUN_TEST(test_bin_serialize_long_string); + + // binary (bytes) data type +#if TS_BYTE_STRING_TYPE_SUPPORT + RUN_TEST(test_bin_serialize_bytes); + RUN_TEST(test_bin_deserialize_bytes); + RUN_TEST(test_bin_patch_fetch_bytes); +#endif + + // data export/import + RUN_TEST(test_bin_export); + RUN_TEST(test_bin_import); - TEST_ASSERT_BIN_RESP(resp_buf, resp_len, resp_expected); + UNITY_END(); } diff --git a/test/test_common.c b/test/test_common.c index 4c66104..10a2679 100644 --- a/test/test_common.c +++ b/test/test_common.c @@ -6,52 +6,16 @@ #include "test.h" -/** - * @brief Test Asserts - * - * This test verifies various assert macros used in the unit tests. - * - */ -void test_assert(void) -{ - TEST_ASSERT(1); - TEST_ASSERT_TRUE(1); - TEST_ASSERT_TRUE_MESSAGE(1, "test_assert"); - TEST_ASSERT_FALSE(0); - TEST_ASSERT_NULL(NULL); - TEST_ASSERT_NOT_NULL("foo"); - TEST_ASSERT_EQUAL(1, 1); - TEST_ASSERT_EQUAL_MESSAGE(1, 1, "test_assert"); - TEST_ASSERT_EQUAL_FLOAT(123.4567890123456789, 123.4567890123456789); - TEST_ASSERT_EQUAL_HEX(0x1234, 0x1234); - TEST_ASSERT_EQUAL_HEX8(0x34, 0x34); - TEST_ASSERT_EQUAL_HEX8_MESSAGE(0x34, 0x34, "test_assert"); - TEST_ASSERT_EQUAL_HEX8_ARRAY("1234", "1234", 4); - TEST_ASSERT_EQUAL_HEX8_ARRAY_MESSAGE("1234", "1234", 4, "test_assert"); - TEST_ASSERT_EQUAL_INT(2, 2); - TEST_ASSERT_EQUAL_INT32(-32, -32); - TEST_ASSERT_EQUAL_UINT(3U, 3U); - TEST_ASSERT_EQUAL_UINT8(0x103U, 0x203U); - TEST_ASSERT_EQUAL_UINT16(0x303U, 0x303U); - TEST_ASSERT_EQUAL_UINT32(0x12345678U, 0x12345678U); - TEST_ASSERT_EQUAL_PTR(NULL, NULL); - TEST_ASSERT_EQUAL_STRING("ttt", "ttt"); - TEST_ASSERT_EQUAL_STRING_MESSAGE("ttt", "ttt", "test_assert"); - TEST_ASSERT_GREATER_OR_EQUAL_size_t(100, 100); - TEST_ASSERT_GREATER_OR_EQUAL_size_t(100, 101); - TEST_ASSERT_LESS_THAN_size_t(100, 99); - TEST_ASSERT_LESS_THAN_size_t_MESSAGE(100, 99, "test_assert"); - TEST_ASSERT_LESS_OR_EQUAL_size_t(100, 100); - TEST_ASSERT_LESS_OR_EQUAL_size_t(100, 99); - TEST_ASSERT_LESS_OR_EQUAL_size_t_MESSAGE(100, 99, "test_assert"); - TEST_ASSERT_NOT_EQUAL(1, 2); -} - /** * @brief Test conversion json to cbor. */ void test_txt_patch_bin_fetch(void) { + /* Assure core context correctly initialized for testing */ + TEST_ASSERT_EQUAL_UINT8(0, TS_CONFIG_CORE_LOCID); + TEST_ASSERT_EQUAL_UINT8(0, TEST_CORE_LOCID); + TEST_ASSERT_EQUAL_UINT16(TS_ANY_RW, thingset_authorisation(TEST_CORE_LOCID)); + // uint16 TEST_ASSERT_JSON2CBOR("ui16", "0", 0x6005, "00"); TEST_ASSERT_JSON2CBOR("ui16", "23", 0x6005, "17"); @@ -70,7 +34,7 @@ void test_txt_patch_bin_fetch(void) TEST_ASSERT_JSON2CBOR("ui32", "65536", 0x6003, "1A 00 01 00 00"); TEST_ASSERT_JSON2CBOR("ui32", "4294967295", 0x6003, "1A FF FF FF FF"); -#if TS_64BIT_TYPES_SUPPORT +#if TS_CONFIG_64BIT_TYPES_SUPPORT // uint64 TEST_ASSERT_JSON2CBOR("ui64", "4294967295", 0x6001, "1A FF FF FF FF"); TEST_ASSERT_JSON2CBOR("ui64", "4294967296", 0x6001, "1B 00 00 00 01 00 00 00 00"); @@ -95,7 +59,7 @@ void test_txt_patch_bin_fetch(void) TEST_ASSERT_JSON2CBOR("i32", "65536", 0x6004, "1A 00 01 00 00"); TEST_ASSERT_JSON2CBOR("i32", "2147483647", 0x6004, "1A 7F FF FF FF"); // maximum value for int32 -#if TS_64BIT_TYPES_SUPPORT +#if TS_CONFIG_64BIT_TYPES_SUPPORT // int64 (positive values) TEST_ASSERT_JSON2CBOR("i64", "4294967295", 0x6002, "1A FF FF FF FF"); TEST_ASSERT_JSON2CBOR("i64", "4294967296", 0x6002, "1B 00 00 00 01 00 00 00 00"); @@ -120,7 +84,7 @@ void test_txt_patch_bin_fetch(void) TEST_ASSERT_JSON2CBOR("i32", "-65537", 0x6004, "3A 00 01 00 00"); TEST_ASSERT_JSON2CBOR("i32", "-2147483648", 0x6004, "3A 7F FF FF FF"); // maximum value for int32 -#if TS_64BIT_TYPES_SUPPORT +#if TS_CONFIG_64BIT_TYPES_SUPPORT // int64 (negative values) TEST_ASSERT_JSON2CBOR("i64", "-4294967296", 0x6002, "3A FF FF FF FF"); TEST_ASSERT_JSON2CBOR("i64", "-4294967297", 0x6002, "3B 00 00 00 01 00 00 00 00"); @@ -132,8 +96,9 @@ void test_txt_patch_bin_fetch(void) TEST_ASSERT_JSON2CBOR("f32", "-12.340", 0x6007, "fa c1 45 70 a4"); TEST_ASSERT_JSON2CBOR("f32", "12.345", 0x6007, "fa 41 45 85 1f"); -#if TS_DECFRAC_TYPE_SUPPORT +#if TS_CONFIG_DECFRAC_TYPE_SUPPORT // decimal fraction + TEST_ASSERT_JSON2CBOR("DecFrac_degC", "27315e-2", 0x600B, "c4 82 21 19 6a b3"); TEST_ASSERT_JSON2CBOR("DecFrac_degC", "273.15", 0x600B, "c4 82 21 19 6a b3"); #endif @@ -151,6 +116,9 @@ void test_txt_patch_bin_fetch(void) */ void test_bin_patch_txt_fetch(void) { + /* Assure core context correctly initialized for testing */ + TEST_ASSERT_EQUAL_UINT16(TS_ANY_RW, thingset_authorisation(TEST_CORE_LOCID)); + // uint16 TEST_ASSERT_CBOR2JSON("ui16", "0", 0x6005, "00"); TEST_ASSERT_CBOR2JSON("ui16", "23", 0x6005, "17"); @@ -174,7 +142,7 @@ void test_bin_patch_txt_fetch(void) TEST_ASSERT_CBOR2JSON("ui32", "65536", 0x6003, "1A 00 01 00 00"); TEST_ASSERT_CBOR2JSON("ui32", "4294967295", 0x6003, "1A FF FF FF FF"); -#if TS_64BIT_TYPES_SUPPORT +#if TS_CONFIG_64BIT_TYPES_SUPPORT // uint64 TEST_ASSERT_CBOR2JSON("ui64", "4294967295", 0x6001, "1A FF FF FF FF"); TEST_ASSERT_CBOR2JSON("ui64", "4294967295", 0x6001, "1B 00 00 00 00 FF FF FF FF"); // less compact format @@ -194,7 +162,7 @@ void test_bin_patch_txt_fetch(void) TEST_ASSERT_CBOR2JSON("i32", "65536", 0x6004, "1A 00 01 00 00"); TEST_ASSERT_CBOR2JSON("i32", "2147483647", 0x6004, "1A 7F FF FF FF"); // maximum value for int32 -#if TS_64BIT_TYPES_SUPPORT +#if TS_CONFIG_64BIT_TYPES_SUPPORT // int64 (positive values) TEST_ASSERT_CBOR2JSON("i64", "4294967295", 0x6002, "1A FF FF FF FF"); TEST_ASSERT_CBOR2JSON("i64", "4294967296", 0x6002, "1B 00 00 00 01 00 00 00 00"); @@ -219,7 +187,7 @@ void test_bin_patch_txt_fetch(void) TEST_ASSERT_CBOR2JSON("i32", "-65537", 0x6004, "3A 00 01 00 00"); TEST_ASSERT_CBOR2JSON("i32", "-2147483648", 0x6004, "3A 7F FF FF FF"); // maximum value for int32 -#if TS_64BIT_TYPES_SUPPORT +#if TS_CONFIG_64BIT_TYPES_SUPPORT // int64 (negative values) TEST_ASSERT_CBOR2JSON("i64", "-4294967296", 0x6002, "3A FF FF FF FF"); TEST_ASSERT_CBOR2JSON("i64", "-4294967297", 0x6002, "3B 00 00 00 01 00 00 00 00"); @@ -232,7 +200,7 @@ void test_bin_patch_txt_fetch(void) TEST_ASSERT_CBOR2JSON("f32", "12.34", 0x6007, "fa 41 45 81 06"); // 12.344 TEST_ASSERT_CBOR2JSON("f32", "12.35", 0x6007, "fa 41 45 85 1f"); // 12.345 (should be rounded to 12.35) -#if TS_DECFRAC_TYPE_SUPPORT +#if TS_CONFIG_DECFRAC_TYPE_SUPPORT // decimal fraction TEST_ASSERT_CBOR2JSON("DecFrac_degC", "27315e-2", 0x600B, "c4 82 21 19 6a b3"); // decfrac 27315e-2 TEST_ASSERT_CBOR2JSON("DecFrac_degC", "27315e-2", 0x600B, "c4 82 22 1a 00 04 2A FE"); // decfrac 273150e-3 @@ -250,12 +218,13 @@ void test_bin_patch_txt_fetch(void) TEST_ASSERT_CBOR2JSON("strbuf", "\"Hello World!\"", 0x6009, "6c 48 65 6c 6c 6f 20 57 6f 72 6c 64 21"); } -/** - * @brief Test ts_init - */ -void test_ts_init(void) +void tests_common(void) { - int ret = ts_init(&ts, &data_objects[0], data_objects_size); + UNITY_BEGIN(); + + // data conversion tests + RUN_TEST(test_txt_patch_bin_fetch); + RUN_TEST(test_bin_patch_txt_fetch); - TEST_ASSERT_EQUAL(0, ret); + UNITY_END(); } diff --git a/test/test_context.c b/test/test_context.c deleted file mode 100644 index 3efffce..0000000 --- a/test/test_context.c +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright (c) 2020 Martin Jäger / Libre Solar - * Copyright (c) 2021 Bobby Noelte. - * SPDX-License-Identifier: Apache-2.0 - */ - -#include - -#include "test.h" - -struct ts_context ts; -uint8_t req_buf[TS_REQ_BUFFER_LEN]; -uint8_t resp_buf[TS_RESP_BUFFER_LEN]; - -bool conf_callback_called; -bool dummy_called_flag; -struct ts_array_info pub_serial_array; - - -void dummy(void) -{ - dummy_called_flag = 1; -} - -void conf_callback(void) -{ - conf_callback_called = 1; -} - -void reset_function() -{ - LOG_DBG("Reset function called!\n"); -} - -void auth_function() -{ - const char pass_exp[] = "expert123"; - const char pass_mkr[] = "maker456"; - - if (strlen(pass_exp) == strlen(auth_password) && - strncmp(auth_password, pass_exp, strlen(pass_exp)) == 0) - { - ts_set_authentication(&ts, TS_EXP_MASK | TS_USR_MASK); - } - else if (strlen(pass_mkr) == strlen(auth_password) && - strncmp(auth_password, pass_mkr, strlen(pass_mkr)) == 0) - { - ts_set_authentication(&ts, TS_MKR_MASK | TS_USR_MASK); - } - else { - ts_set_authentication(&ts, TS_USR_MASK); - } - - LOG_DBG("Auth function called, password: %s\n", auth_password); -} - -int _hex2bin(uint8_t *bin, size_t bin_size, const char *hex) -{ - int len = strlen(hex); - unsigned int pos = 0; - for (int i = 0; i < len; i += 3) { - if (pos < bin_size) { - bin[pos++] = (char)strtoul(&hex[i], NULL, 16); - } - else { - return 0; - } - } - return pos; -} - -static int _bin2hex(char *hex, size_t hex_size, const uint8_t *bin, size_t bin_size) -{ - size_t bin_idx, hex_idx; - - for (bin_idx = hex_idx = 0; bin_idx < bin_size; bin_idx++) { - uint8_t b = bin[bin_idx] >> 4; - if (hex_idx >= hex_size) { - return -1; - } - hex[hex_idx++] = (char) (87 + b + (((b - 10) >> 31) & -39)); - b = bin[bin_idx] & 0xf; - if (hex_idx >= hex_size) { - return -1; - } - hex[hex_idx++] = (char) (87 + b + (((b - 10) >> 31) & -39)); - if (hex_idx < hex_size) { - hex[hex_idx++] = ' '; - } - } - if (hex_idx >= hex_size) { - return -1; - } - hex[hex_idx] = '\0'; - - return hex_idx; -} - -void assert_bin_resp(const uint8_t *resp_buf, int resp_len, const char *exp_hex, const char *msg) -{ - char resp_hex[100]; - uint8_t exp_b[100]; - - int exp_len = _hex2bin(exp_b, sizeof(exp_b), exp_hex); - (void)_bin2hex(resp_hex, sizeof(resp_hex), resp_buf, resp_len); - - TEST_ASSERT_EQUAL_MESSAGE(exp_len, resp_len, msg); - TEST_ASSERT_EQUAL_HEX8_ARRAY_MESSAGE(exp_b, resp_buf, resp_len, msg); -} - -void assert_bin_req(const uint8_t *req_b, int req_len, const char *exp_hex, const char* msg) -{ - int resp_len = ts_process(&ts, req_b, req_len, resp_buf, TS_RESP_BUFFER_LEN); - - assert_bin_resp(resp_buf, resp_len, exp_hex, msg); -} - -void assert_bin_req_exp_bin(const uint8_t *req_b, int req_len, const uint8_t *exp_b, int exp_len, const char* msg) -{ - char exp_hex[exp_len * 3 + 1]; - (void)_bin2hex(exp_hex, sizeof(exp_hex), exp_b, exp_len); - - assert_bin_req(req_b, req_len, exp_hex, msg); -} - -void assert_bin_req_hex(const char *req_hex, const char *exp_hex, const char *msg) -{ - int req_len = _hex2bin(req_buf, TS_REQ_BUFFER_LEN, req_hex); - - TEST_ASSERT_LESS_OR_EQUAL_size_t_MESSAGE(TS_REQ_BUFFER_LEN, req_len, msg); - - assert_bin_req(req_buf, req_len, exp_hex, msg); -} - -void assert_txt_resp(int exp_len, const char *exp_s, const char *msg) -{ - int resp_buf_len = strlen((char *)resp_buf); - - TEST_ASSERT_EQUAL_MESSAGE(exp_len, resp_buf_len, msg); - TEST_ASSERT_EQUAL_STRING_MESSAGE(exp_s, (char *)resp_buf, msg); -} - -void assert_txt_req(const char *req_s, const char *exp_s, const char *msg) -{ - int req_len = strlen(req_s); - TEST_ASSERT_LESS_OR_EQUAL_size_t_MESSAGE(TS_REQ_BUFFER_LEN - 1, req_len, msg); - - strncpy((char *)req_buf, req_s, TS_REQ_BUFFER_LEN); - int resp_len = ts_process(&ts, req_buf, req_len, resp_buf, TS_RESP_BUFFER_LEN); - assert_txt_resp(resp_len, exp_s, msg); -} - -static void _txt_patch(char const *name, char const *value, const char *msg) -{ - int req_len = snprintf((char *)req_buf, TS_REQ_BUFFER_LEN, "=conf {\"%s\":%s}", name, value); - int resp_len = ts_process(&ts, req_buf, req_len, resp_buf, TS_RESP_BUFFER_LEN); - int resp_buf_len = strlen((char *)resp_buf); - - TEST_ASSERT_EQUAL_MESSAGE(resp_len, resp_buf_len, msg); - TEST_ASSERT_EQUAL_STRING_MESSAGE(":84 Changed.", (char *)resp_buf, msg); -} - -static int _txt_fetch(char const *name, char *value_read, const char *msg) -{ - int req_len = snprintf((char *)req_buf, TS_REQ_BUFFER_LEN, "?conf \"%s\"", name); - int resp_len = ts_process(&ts, req_buf, req_len, resp_buf, TS_RESP_BUFFER_LEN); - int resp_buf_len = strlen((char *)resp_buf); - - TEST_ASSERT_EQUAL_MESSAGE(resp_len, resp_buf_len, msg); - - int pos_dot = strchr((char *)resp_buf, '.') - (char *)resp_buf + 1; - char buf[100]; - strncpy(buf, (char *)resp_buf, pos_dot); - buf[pos_dot] = '\0'; - - TEST_ASSERT_EQUAL_STRING_MESSAGE(":85 Content.", buf, msg); - - return snprintf(value_read, strlen((char *)resp_buf) - pos_dot, "%s", resp_buf + pos_dot + 1); -} - -// returns length of read value -static int _bin_fetch(uint16_t id, char *value_read, const char *msg) -{ - uint8_t req[] = { - TS_FETCH, - 0x18, ID_CONF, - 0x19, (uint8_t)(id >> 8), (uint8_t)id - }; - ts_process(&ts, req, sizeof(req), resp_buf, TS_RESP_BUFFER_LEN); - - TEST_ASSERT_EQUAL_HEX8_MESSAGE(TS_STATUS_CONTENT, resp_buf[0], msg); - - int value_len = cbor_size((uint8_t*)resp_buf + 1); - memcpy(value_read, resp_buf + 1, value_len); - return value_len; -} - -// returns length of read value -static void _bin_patch(uint16_t id, char *value, const char *msg) -{ - uint8_t req[100] = { - TS_PATCH, - 0x18, ID_CONF, - 0xA1, - 0x19, (uint8_t)(id >> 8), (uint8_t)id - }; - unsigned int len = cbor_size((uint8_t*)value); - TEST_ASSERT_LESS_THAN_size_t_MESSAGE(sizeof(req) - 7, len, msg); - - memcpy(req + 7, value, len); - ts_process(&ts, req, len + 7, resp_buf, TS_RESP_BUFFER_LEN); - - TEST_ASSERT_EQUAL_HEX8_MESSAGE(TS_STATUS_CHANGED, resp_buf[0], msg); -} - -void assert_json2cbor(char const *name, char const *json_value, uint16_t id, const char *const cbor_value_hex, const char *msg) -{ - char buf[100]; // temporary data storage (JSON or CBOR) - uint8_t cbor_value[100]; - int len = _hex2bin(cbor_value, sizeof(cbor_value), (char *)cbor_value_hex); - - _txt_patch(name, json_value, msg); - len = _bin_fetch(id, buf, msg); - - TEST_ASSERT_EQUAL_HEX8_ARRAY_MESSAGE(cbor_value, buf, len, msg); -} - -void assert_cbor2json(char const *name, char const *json_value, uint16_t id, char const *cbor_value_hex, const char *msg) -{ - char buf[100]; // temporary data storage (JSON or CBOR) - char cbor_value[100]; - _hex2bin((uint8_t *)cbor_value, sizeof(cbor_value), (char *)cbor_value_hex); - - _bin_patch(id, cbor_value, msg); - _txt_fetch(name, buf, msg); - - TEST_ASSERT_EQUAL_STRING_MESSAGE(json_value, buf, msg); -} diff --git a/test/test_core.c b/test/test_core.c deleted file mode 100644 index a1f77d0..0000000 --- a/test/test_core.c +++ /dev/null @@ -1,618 +0,0 @@ -/* - * Copyright (c) 2020 Martin Jäger / Libre Solar - * Copyright (c) 2021 Bobby Noelte. - * SPDX-License-Identifier: Apache-2.0 - */ - -#include - -#include "test.h" - -char test_log_buf[300]; -uint8_t test_req_buf[TEST_REQ_BUFFER_LEN]; -uint8_t test_resp_buf[TEST_RESP_BUFFER_LEN]; - -bool conf_callback_called; -bool dummy_called_flag; -struct ts_array_info pub_serial_array; - -void dummy(void) -{ - dummy_called_flag = 1; -} - -void conf_callback(void) -{ - conf_callback_called = 1; -} - -void reset_function() -{ - LOG_DBG("Reset function called!\n"); -} - -void auth_function() -{ - const char pass_exp[] = "expert123"; - const char pass_mkr[] = "maker456"; - - if (strlen(pass_exp) == strlen(auth_password) && - strncmp(auth_password, pass_exp, strlen(pass_exp)) == 0) - { - thingset_authorisation_set(&test_ts_core, TS_EXP_RW | TS_USR_RW); - } - else if (strlen(pass_mkr) == strlen(auth_password) && - strncmp(auth_password, pass_mkr, strlen(pass_mkr)) == 0) - { - thingset_authorisation_set(&test_ts_core, TS_MKR_RW | TS_EXP_RW | TS_USR_RW); - } - else { - thingset_authorisation_set(&test_ts_core, TS_USR_RW); - } - - LOG_DBG("Auth function called, password: %s\n", auth_password); -} - -int _hex2bin(uint8_t *bin, size_t bin_size, const char *hex) -{ - int len = strlen(hex); - unsigned int pos = 0; - for (int i = 0; i < len; i += 3) { - if (pos < bin_size) { - bin[pos++] = (char)strtoul(&hex[i], NULL, 16); - } - else { - return 0; - } - } - return pos; -} - -// determines the size of a cbor data item starting at given pointer -#define CBOR_TYPE_MASK 0xE0 /* top 3 bits */ -#define CBOR_INFO_MASK 0x1F /* low 5 bits */ - -/* Jump Table for Initial Byte (cf. table 5) */ -#define CBOR_UINT 0x00 /* type 0 */ -#define CBOR_NEGINT 0x20 /* type 1 */ -#define CBOR_BYTES 0x40 /* type 2 */ -#define CBOR_TEXT 0x60 /* type 3 */ -#define CBOR_ARRAY 0x80 /* type 4 */ -#define CBOR_MAP 0xA0 /* type 5 */ -#define CBOR_TAG 0xC0 /* type 6 */ -#define CBOR_7 0xE0 /* type 7 (float and other types) */ - -#define CBOR_NUM_MAX 23 /* maximum number that can be directl encoded */ - -/* Major types (cf. section 2.1) */ -/* Major type 0: Unsigned integers */ -#define CBOR_UINT8_FOLLOWS 24 /* 0x18 */ -#define CBOR_UINT16_FOLLOWS 25 /* 0x19 */ -#define CBOR_UINT32_FOLLOWS 26 /* 0x1a */ -#define CBOR_UINT64_FOLLOWS 27 /* 0x1b */ - -/* Indefinite Lengths for Some Major types (cf. section 2.2) */ -#define CBOR_VAR_FOLLOWS 31 /* 0x1f */ - -/* Major type 6: Semantic tagging */ -#define CBOR_DATETIME_STRING_FOLLOWS 0 -#define CBOR_DATETIME_EPOCH_FOLLOWS 1 -#define CBOR_DECFRAC_ARRAY_FOLLOWS 4 - -/* Major type 7: Float and other types */ -#define CBOR_FALSE (CBOR_7 | 20) -#define CBOR_TRUE (CBOR_7 | 21) -#define CBOR_NULL (CBOR_7 | 22) -#define CBOR_UNDEFINED (CBOR_7 | 23) -#define CBOR_SIMPLE (CBOR_7 | 24) -#define CBOR_FLOAT16 (CBOR_7 | 25) -#define CBOR_FLOAT32 (CBOR_7 | 26) -#define CBOR_FLOAT64 (CBOR_7 | 27) -#define CBOR_BREAK (CBOR_7 | 31) - -static int _cbor_size(const uint8_t *data) -{ - uint8_t type = data[0] & CBOR_TYPE_MASK; - uint8_t info = data[0] & CBOR_INFO_MASK; - - if (type == CBOR_UINT || type == CBOR_NEGINT) { - if (info <= CBOR_NUM_MAX) - return 1; - switch (info) { - case CBOR_UINT8_FOLLOWS: - return 2; - case CBOR_UINT16_FOLLOWS: - return 3; - case CBOR_UINT32_FOLLOWS: - return 5; - case CBOR_UINT64_FOLLOWS: - return 9; - } - } - else if (type == CBOR_BYTES || type == CBOR_TEXT) { - if (info <= CBOR_NUM_MAX) { - return info + 1; - } - else { - if (info == CBOR_UINT8_FOLLOWS) - return 1 + data[1]; - else if (info == CBOR_UINT16_FOLLOWS) - return 1 + (data[1] << 8 | data[2]); - else - return 0; // longer string / byte array not supported - } - } -#if TS_CONFIG_DECFRAC_TYPE_SUPPORT - else if (type == CBOR_TAG && info == CBOR_DECFRAC_ARRAY_FOLLOWS) { - int pos = 2; - pos += _cbor_size(&data[pos]); // exponent - pos += _cbor_size(&data[pos]); // mantissa - return pos; - } -#endif - else if (type == CBOR_7) { - switch (data[0]) { - case CBOR_FALSE: - case CBOR_TRUE: - return 1; - break; - case CBOR_FLOAT32: - return 5; - break; - case CBOR_FLOAT64: - return 9; - break; - } - } - - return 0; // float16, arrays, maps, tagged types, etc. curently not supported -} - -static int _bin2hex(char *hex, size_t hex_size, const uint8_t *bin, size_t bin_size) -{ - size_t bin_idx, hex_idx; - - for (bin_idx = hex_idx = 0; bin_idx < bin_size; bin_idx++) { - uint8_t b = bin[bin_idx] >> 4; - if (hex_idx >= hex_size) { - return -1; - } - hex[hex_idx++] = (char) (87 + b + (((b - 10) >> 31) & -39)); - b = bin[bin_idx] & 0xf; - if (hex_idx >= hex_size) { - return -1; - } - hex[hex_idx++] = (char) (87 + b + (((b - 10) >> 31) & -39)); - if (hex_idx < hex_size) { - hex[hex_idx++] = ' '; - } - } - if (hex_idx >= hex_size) { - return -1; - } - hex[hex_idx] = '\0'; - - return hex_idx; -} - -void assert_bin_resp(const uint8_t *resp_buf, int resp_len, const char *exp_hex, const char *msg) -{ - char resp_hex[100]; - uint8_t exp_b[100]; - - int exp_len = _hex2bin(exp_b, sizeof(exp_b), exp_hex); - (void)_bin2hex(resp_hex, sizeof(resp_hex), resp_buf, resp_len); - - TEST_ASSERT_EQUAL_MESSAGE(exp_len, resp_len, msg); - TEST_ASSERT_EQUAL_HEX8_ARRAY_MESSAGE(exp_b, resp_buf, exp_len, msg); -} - -void assert_bin_req(const uint8_t *req_b, int req_len, const char *exp_hex, const char* msg) -{ - int resp_len = thingset_process_buf(&test_ts_core, req_b, req_len, test_resp_buf, TEST_RESP_BUFFER_LEN); - - assert_bin_resp(test_resp_buf, resp_len, exp_hex, msg); -} - -void assert_bin_req_exp_bin(const uint8_t *req_b, int req_len, const uint8_t *exp_b, int exp_len, const char* msg) -{ - char exp_hex[exp_len * 3 + 1]; - (void)_bin2hex(exp_hex, sizeof(exp_hex), exp_b, exp_len); - - assert_bin_req(req_b, req_len, exp_hex, msg); -} - -void assert_bin_req_hex(const char *req_hex, const char *exp_hex, const char *msg) -{ - int req_len = _hex2bin(test_req_buf, TEST_REQ_BUFFER_LEN, req_hex); - - TEST_ASSERT_LESS_OR_EQUAL_size_t_MESSAGE(TEST_REQ_BUFFER_LEN, req_len, msg); - - assert_bin_req(test_req_buf, req_len, exp_hex, msg); -} - -void assert_txt_resp(int exp_len, const char *exp_s, const char *msg) -{ - int resp_buf_len = strlen((char *)test_resp_buf); - - TEST_ASSERT_EQUAL_STRING_MESSAGE(exp_s, (char *)test_resp_buf, msg); - TEST_ASSERT_EQUAL_MESSAGE(exp_len, resp_buf_len, msg); -} - -void assert_txt_req(const char *req_s, const char *exp_s, const char *msg) -{ - int req_len = strlen(req_s); - TEST_ASSERT_LESS_OR_EQUAL_size_t_MESSAGE(TEST_REQ_BUFFER_LEN - 1, req_len, msg); - - strncpy((char *)test_req_buf, req_s, req_len); - int resp_len = thingset_process_buf(&test_ts_core, test_req_buf, req_len, test_resp_buf, - TEST_RESP_BUFFER_LEN); - TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(0, resp_len, msg); - - assert_txt_resp(resp_len, exp_s, msg); -} - -static void _txt_patch(char const *name, char const *value, const char *msg) -{ - int req_len = snprintf((char *)test_req_buf, TEST_REQ_BUFFER_LEN, "=conf {\"%s\":%s}", name, value); - int resp_len = thingset_process_buf(&test_ts_core, test_req_buf, req_len, test_resp_buf, - TEST_RESP_BUFFER_LEN); - TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(0, resp_len, msg); - - int resp_buf_len = strlen((char *)test_resp_buf); - - TEST_ASSERT_EQUAL_MESSAGE(resp_len, resp_buf_len, msg); - TEST_ASSERT_EQUAL_STRING_MESSAGE(":84 Changed.", (char *)test_resp_buf, msg); -} - -static int _txt_fetch(char const *name, char *value_read, const char *msg) -{ - int req_len = snprintf((char *)test_req_buf, TEST_REQ_BUFFER_LEN, "?conf \"%s\"", name); - int resp_len = thingset_process_buf(&test_ts_core, test_req_buf, req_len, test_resp_buf, - TEST_RESP_BUFFER_LEN); - TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(0, resp_len, msg); - - int resp_buf_len = strlen((char *)test_resp_buf); - - TEST_ASSERT_EQUAL_MESSAGE(resp_len, resp_buf_len, msg); - - int pos_dot = strchr((char *)test_resp_buf, '.') - (char *)test_resp_buf + 1; - char buf[100]; - strncpy(buf, (char *)test_resp_buf, pos_dot); - buf[pos_dot] = '\0'; - - TEST_ASSERT_EQUAL_STRING_MESSAGE(":85 Content.", buf, msg); - - return snprintf(value_read, strlen((char *)test_resp_buf) - pos_dot, "%s", test_resp_buf + pos_dot + 1); -} - -// returns length of read value -static int _bin_fetch(uint16_t id, char *value_read, const char *msg) -{ - uint8_t req[] = { - TS_FETCH, - 0x18, ID_CONF, - 0x19, (uint8_t)(id >> 8), (uint8_t)id - }; - int ret = thingset_process_buf(&test_ts_core, req, sizeof(req), test_resp_buf, - TEST_RESP_BUFFER_LEN); - TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(0, ret, msg); - - TEST_ASSERT_EQUAL_HEX8_MESSAGE(TS_STATUS_CONTENT, test_resp_buf[0], msg); - - int value_len = _cbor_size((uint8_t*)test_resp_buf + 1); - TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(0, value_len, msg); - - memcpy(value_read, test_resp_buf + 1, value_len); - return value_len; -} - -// returns length of read value -static void _bin_patch(uint16_t id, char *value, const char *msg) -{ - uint8_t req[100] = { - TS_PATCH, - 0x18, ID_CONF, - 0xA1, - 0x19, (uint8_t)(id >> 8), (uint8_t)id - }; - unsigned int len = _cbor_size((uint8_t*)value); - TEST_ASSERT_LESS_THAN_size_t_MESSAGE(sizeof(req) - 7, len, msg); - - memcpy(req + 7, value, len); - int ret = thingset_process_buf(&test_ts_core, req, len + 7, test_resp_buf, - TEST_RESP_BUFFER_LEN); - TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(0, ret, msg); - - TEST_ASSERT_EQUAL_HEX8_MESSAGE(TS_STATUS_CHANGED, test_resp_buf[0], msg); -} - -void assert_json2cbor(char const *name, char const *json_value, uint16_t id, - const char *const cbor_value_hex, const char *msg) -{ - char buf[100]; // temporary data storage (JSON or CBOR) - uint8_t cbor_value[100]; - int len = _hex2bin(cbor_value, sizeof(cbor_value), (char *)cbor_value_hex); - - TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(0, len, msg); - TEST_ASSERT_LESS_THAN_INT_MESSAGE(100, len, msg); - - _txt_patch(name, json_value, msg); - len = _bin_fetch(id, buf, msg); - - TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(0, len, msg); - TEST_ASSERT_LESS_THAN_INT_MESSAGE(100, len, msg); - - //TEST_ASSERT_EQUAL_HEX8_ARRAY_MESSAGE(&cbor_value[0], &buf[0], len, msg); -} - -void assert_cbor2json(char const *name, char const *json_value, uint16_t id, char const *cbor_value_hex, const char *msg) -{ - char buf[100]; // temporary data storage (JSON or CBOR) - char cbor_value[100]; - _hex2bin((uint8_t *)cbor_value, sizeof(cbor_value), (char *)cbor_value_hex); - - _bin_patch(id, cbor_value, msg); - _txt_fetch(name, buf, msg); - - TEST_ASSERT_EQUAL_STRING_MESSAGE(json_value, buf, msg); -} - -void assert_msg_add_response_get_cbor(struct thingset_msg *response, struct thingset_msg *request, - const char *object_path, int expected_ret, - const char *expected_str, const char *assert_msg) -{ - int ret; - thingset_oref_t object_oref; - char tc_desc[300]; - - /* Prepare assert message */ - int len = snprintf(&tc_desc[0], sizeof(tc_desc) - 1, "%s: ", assert_msg); - - LOG_DBG("%s", &tc_desc[sizeof("Assertion triggered by")]); - - ts_obj_db_oref_init(TEST_DB_ID_INSTANCE, &object_oref); - - thingset_msg_reset(response); - TEST_ASSERT_EQUAL_MESSAGE(0, ts_msg_len(response), &tc_desc[0]); - - if (object_path == NULL) { - ts_obj_db_oref_init(TEST_DB_ID_INSTANCE, &object_oref); - } - else { - ret = ts_obj_db_oref_by_path(TEST_DB_ID_INSTANCE, object_path, - (size_t)strlen(object_path), - &object_oref); - TEST_ASSERT_EQUAL_MESSAGE(0, ret, &tc_desc[0]); - } - - ret = ts_msg_add_response_get_cbor(response, object_oref, request); - ts_msg_log(response, &test_log_buf[0], sizeof(test_log_buf)); - - /* Complete assert message */ - const char *object_name = ""; - ts_obj_id_t object_id = 0; - ts_obj_id_t object_parent_id = 0; - uint8_t object_type = 0; - if (ts_obj_db_oref_is_valid(object_oref)) { - object_id = ts_obj_id(object_oref); - object_name = ts_obj_name(object_oref); - object_parent_id = ts_obj_parent_id(object_oref); - object_type = ts_obj_type(object_oref); - } - snprintf(&tc_desc[len], sizeof(tc_desc) - len - 1, - " - '%s', id: %u, type: %u, parent: %u -> %s", object_name, (unsigned int)object_id, - (unsigned int)object_type, (unsigned int) object_parent_id, &test_log_buf[0]); - - TEST_ASSERT_EQUAL_MESSAGE(expected_ret, ret, &tc_desc[0]); - if (expected_str != NULL) { - TEST_ASSERT_EQUAL_STRING_MESSAGE(expected_str, &test_log_buf[0], &tc_desc[0]); - } - else { - TEST_ASSERT_EQUAL_MESSAGE(0, ts_msg_len(response), &tc_desc[0]); - } -} - -void assert_msg_fetch_cbor(struct thingset_msg *request, struct thingset_msg *response, - const char *object_path, - bool test_object_id, - bool test_object_ids, uint16_t object_count, const char **object_names, - /* returns of ts_msg_add_request_fetch_cbor */ - int expected_ret_add_request, const char *expected_str_add_request, - /* returns of ts_msg_pull_request_cbor */ - int expected_ret_pull_request, uint8_t expected_status_id_pull_request, - /* returns of ts_msg_add_response_fetch_cbor */ - int expected_ret_add_response, const char *expected_str_add_response, - const char *assert_msg) -{ - int ret; - thingset_oref_t object_oref; - ts_obj_id_t object_id; - char tc_desc[300]; - ts_obj_id_t names_object_ids[object_count]; - struct ts_msg_stat expected_status_pull_request; - - /* Prepare assert message */ - int len = snprintf(&tc_desc[0], sizeof(tc_desc) - 1, "%s: ", assert_msg); - - LOG_DBG("%s", &tc_desc[sizeof("Assertion triggered by")]); - - expected_status_pull_request.type = TS_MSG_TYPE_REQUEST; - expected_status_pull_request.code = expected_status_id_pull_request; - - thingset_msg_reset(request); - TEST_ASSERT_EQUAL_MESSAGE(0, ts_msg_len(request), &tc_desc[0]); - thingset_msg_reset(response); - TEST_ASSERT_EQUAL_MESSAGE(0, ts_msg_len(response), &tc_desc[0]); - - /* - * Create fetch request - * -------------------- - */ - if (object_path == NULL) { - ts_obj_db_oref_init(TEST_DB_ID_INSTANCE, &object_oref); - } - else { - ret = ts_obj_db_oref_by_path(TEST_DB_ID_INSTANCE, object_path, - (size_t)strlen(object_path), - &object_oref); - TEST_ASSERT_EQUAL_MESSAGE(0, ret, &tc_desc[0]); - } - if (test_object_id) { - TEST_ASSERT_TRUE_MESSAGE(ts_obj_db_oref_is_valid(object_oref), &tc_desc[0]); - object_id = ts_obj_id(object_oref); - object_path = 0; - } - else { - object_id = 0; - } - ts_obj_id_t *object_ids = NULL; - if (test_object_ids) { - for (uint16_t j = 0; j < object_count; j++) { - const char *name = object_names[j]; - size_t name_len = strlen(name); - int32_t parent_id = -1; - if (ts_obj_db_oref_is_valid(object_oref)) { - parent_id = (int32_t)ts_obj_id(object_oref); - } - thingset_oref_t fetch_oref; - ret = ts_obj_db_oref_by_name(TEST_DB_ID_INSTANCE, name, name_len, parent_id, - &fetch_oref); - TEST_ASSERT_EQUAL_MESSAGE(0, ret, &tc_desc[0]); - names_object_ids[j] = ts_obj_id(fetch_oref); - } - object_ids = &names_object_ids[0]; - object_names = NULL; - } - ret = ts_msg_add_request_fetch_cbor(request, object_id, object_path, - object_count, object_ids, object_names); - ts_msg_log(request, &test_log_buf[0], sizeof(test_log_buf)); - - /* Complete assert message */ - const char *object_name = ""; - ts_obj_id_t object_parent_id = 0; - uint8_t object_type = 0; - if (ts_obj_db_oref_is_valid(object_oref)) { - object_id = ts_obj_id(object_oref); - object_name = ts_obj_name(object_oref); - object_parent_id = ts_obj_parent_id(object_oref); - object_type = ts_obj_type(object_oref); - } - snprintf(&tc_desc[len], sizeof(tc_desc) - len - 1, - " - '%s', id: %u, type: %u, parent: %u -> %s", object_name, (unsigned int)object_id, - (unsigned int)object_type, (unsigned int)object_parent_id, &test_log_buf[0]); - - TEST_ASSERT_EQUAL_MESSAGE(expected_ret_add_request, ret, &tc_desc[0]); - if (expected_str_add_request != NULL) { - TEST_ASSERT_EQUAL_STRING_MESSAGE(expected_str_add_request, - &test_log_buf[0], &tc_desc[0]); - } - else { - TEST_ASSERT_EQUAL_MESSAGE(0, ts_msg_len(request), &tc_desc[0]); - } - - /* - * Pull fetch request - * ------------------ - */ - - /* Prepare assert message (again) */ - len = snprintf(&tc_desc[0], sizeof(tc_desc) - 1, "%s: ", assert_msg); - - thingset_oref_t request_oref = { .db_id = TEST_DB_ID_INSTANCE }; - ts_msg_auth_set(request, thingset_authorisation(&test_ts_instance)); - ret = ts_msg_pull_request_cbor(request, &request_oref); - - /* Complete assert message */ - object_name = ""; - if (ts_obj_db_oref_is_valid(request_oref)) { - object_id = ts_obj_id(request_oref); - object_name = ts_obj_name(request_oref); - object_parent_id = ts_obj_parent_id(request_oref); - object_type = ts_obj_type(request_oref); - } - snprintf(&tc_desc[len], sizeof(tc_desc) - len - 1, - " - '%s', id: %u, type: %u, parent: %u, status: 0x%02x", - object_name, (unsigned int)object_id, (unsigned int)object_type, - (unsigned int)object_parent_id, (unsigned int)ts_msg_status_code(request)); - - TEST_ASSERT_EQUAL_MESSAGE(expected_ret_pull_request, ret, &tc_desc[0]); - TEST_ASSERT_EQUAL_MESSAGE(expected_status_pull_request.type, ts_msg_status_type(request), - &tc_desc[0]); - TEST_ASSERT_EQUAL_MESSAGE(expected_status_pull_request.code, ts_msg_status_code(request), - &tc_desc[0]); - if (test_object_id) { - TEST_ASSERT_EQUAL_MESSAGE(object_id, ts_obj_id(object_oref), &tc_desc[0]); - } - TEST_ASSERT_EQUAL_UINT16_MESSAGE(thingset_authorisation(&test_ts_instance), - ts_msg_auth(request), &tc_desc[0]); - - /* - * Create fetch response - * --------------------- - */ - - /* Prepare assert message (again) */ - len = snprintf(&tc_desc[0], sizeof(tc_desc) - 1, "%s: ", assert_msg); - - ret = ts_msg_add_response_fetch_cbor(response, request_oref, request); - ts_msg_log(response, &test_log_buf[0], sizeof(test_log_buf)); - - /* Complete assert message */ - object_name = ""; - if (ts_obj_db_oref_is_valid(request_oref)) { - object_id = ts_obj_id(request_oref); - object_name = ts_obj_name(request_oref); - object_parent_id = ts_obj_parent_id(request_oref); - object_type = ts_obj_type(request_oref); - } - snprintf(&tc_desc[len], sizeof(tc_desc) - len - 1, - " - '%s', id: %u, type: %u, parent: %u, status: %u -> %s", - object_name, (unsigned int)object_id, (unsigned int)object_type, - (unsigned int)object_parent_id, (unsigned int)ts_msg_status_code(request), - &test_log_buf[0]); - - TEST_ASSERT_EQUAL_MESSAGE(expected_ret_add_response, ret, &tc_desc[0]); - if (expected_str_add_response != NULL) { - TEST_ASSERT_EQUAL_STRING_MESSAGE(expected_str_add_response, &test_log_buf[0], &tc_desc[0]); - } - else { - TEST_ASSERT_EQUAL_MESSAGE(0, ts_msg_len(response), &tc_desc[0]); - } -} - -void assert_msg_add_response_get_json(struct thingset_msg *response, struct thingset_msg *request, - ts_obj_id_t obj_id, int expected_ret, - const char *expected_str, const char *assert_msg) -{ - int ret; - thingset_oref_t object_oref; - char tc_desc[300]; - - /* Prepare assert message */ - int len = snprintf(&tc_desc[0], sizeof(tc_desc) - 1, "%s: ", assert_msg); - - LOG_DBG("%s", &tc_desc[sizeof("Assertion triggered by")]); - - ts_obj_db_oref_init(TEST_DB_ID_INSTANCE, &object_oref); - - thingset_msg_reset(response); - TEST_ASSERT_EQUAL_MESSAGE(0, ts_msg_len(response), &tc_desc[0]); - - ret = ts_obj_db_oref_by_id(TEST_DB_ID_INSTANCE, obj_id, &object_oref ); - TEST_ASSERT_EQUAL_MESSAGE(0, ret, &tc_desc[0]); - - ret = ts_msg_add_response_get_json(response, object_oref, request); - - /* Complete assert message */ - ts_msg_log(response, &test_log_buf[0], sizeof(test_log_buf)); - snprintf(&tc_desc[len], sizeof(tc_desc) - len - 1, - " -> ret: %d, oref: %u, name: '%s', >%s<", - ret, (unsigned int)object_oref.db_oid, ts_obj_name(object_oref), - &test_log_buf[0]); - - TEST_ASSERT_EQUAL_MESSAGE(expected_ret, ret, &tc_desc[0]); - TEST_ASSERT_EQUAL_STRING_LEN_MESSAGE(expected_str, ts_msg_data(response), - ts_msg_len(response), &tc_desc[0]); - TEST_ASSERT_EQUAL_MESSAGE(strlen(expected_str), ts_msg_len(response), &tc_desc[0]); -} diff --git a/test/test_data.c b/test/test_data.c index 71ba011..958887e 100644 --- a/test/test_data.c +++ b/test/test_data.c @@ -11,36 +11,39 @@ extern "C" { #endif +/* context unique identifier */ +thingset_uid_t test_uid_instance = TEST_INSTANCE_UID; +thingset_uid_t test_uid_neighbour = TEST_NEIGHBOUR_UID; + // info -char manufacturer[] = "Libre Solar"; -static uint32_t timestamp = 12345678; -static char device_id[] = "ABCD1234"; +const char manufacturer[] = TEST_MANUFACTURER; +const char test_device_id_instance[] = TEST_DEVICE_ID_INSTANCE; +const char test_device_id_neighbour[] = TEST_DEVICE_ID_NEIGHBOUR; +static uint32_t timestamp; // conf -static float bat_charging_voltage = 14.4; -static float load_disconnect_voltage = 10.8; +static float bat_charging_voltage; +static float load_disconnect_voltage; // input -static bool enable_switch = false; +static bool enable_switch; // meas -static float battery_voltage = 14.1; -static float battery_current = 5.13; -static int16_t ambient_temp = 22; +static float battery_voltage; +static float battery_current; +static int16_t ambient_temp; // rec -static float bat_energy_hour = 32.2; -static float bat_energy_day = 123; -static int16_t ambient_temp_max_day = 28; +static float bat_energy_hour; +static float bat_energy_day; +static int16_t ambient_temp_max_day; // pub -bool pub_report_enable = false; -uint16_t pub_report_interval = 1000; -bool pub_info_enable = true; +bool pub_report_enable; +uint16_t pub_report_interval; +bool pub_info_enable; // exec -void reset_function(void); -void auth_function(void); char auth_password[11]; char strbuf[300]; @@ -48,7 +51,7 @@ char strbuf[300]; float f32; int32_t decfrac; -#if TS_64BIT_TYPES_SUPPORT +#if TS_CONFIG_64BIT_TYPES_SUPPORT static uint64_t ui64; static int64_t i64; #endif @@ -61,21 +64,144 @@ static int16_t i16; bool b; -int32_t A[100] = {4, 2, 8, 4}; +int32_t A[100]; struct ts_array_info int32_array = {A, sizeof(A)/sizeof(int32_t), 4, TS_T_INT32}; -float B[100] = {2.27, 3.44}; +float B[100]; struct ts_array_info float32_array = {B, sizeof(B)/sizeof(float), 2, TS_T_FLOAT32}; -uint8_t bytes[300] = {}; +uint8_t bytes[300]; struct ts_bytes_buffer bytes_buf = { bytes, 0 }; -void dummy(void); -void conf_callback(void); +THINGSET_CORE_DATABASE_DEFINE(test_uid_instance, + // DEVICE INFORMATION ///////////////////////////////////////////////////// + + TS_GROUP(ID_INFO, "info", TS_NO_CALLBACK, ID_ROOT), + + TS_ITEM_STRING(0x19, "Manufacturer", manufacturer, 0, + ID_INFO, TS_ANY_R, 0), + + TS_ITEM_UINT32(0x1A, "Timestamp_s", ×tamp, + ID_INFO, TS_ANY_RW, SUBSET_REPORT), + + TS_ITEM_STRING(0x1B, "DeviceID", test_device_id_instance, sizeof( test_device_id_instance ), + ID_INFO, TS_ANY_R | TS_MKR_W, 0), + + // CONFIGURATION ////////////////////////////////////////////////////////// + + TS_GROUP(ID_CONF, "conf", &test_core_conf_callback, ID_ROOT), + + TS_ITEM_FLOAT(0x31, "BatCharging_V", &bat_charging_voltage, 2, + ID_CONF, TS_ANY_RW, 0), + + TS_ITEM_FLOAT(0x32, "LoadDisconnect_V", &load_disconnect_voltage, 2, + ID_CONF, TS_ANY_RW, 0), + + // INPUT DATA ///////////////////////////////////////////////////////////// + + TS_GROUP(ID_INPUT, "input", TS_NO_CALLBACK, ID_ROOT), + + TS_ITEM_BOOL(0x61, "EnableCharging", &enable_switch, + ID_INPUT, TS_ANY_RW, 0), + + // MEASUREMENT DATA /////////////////////////////////////////////////////// + + TS_GROUP(ID_MEAS, "meas", TS_NO_CALLBACK, ID_ROOT), + + TS_ITEM_FLOAT(0x71, "Bat_V", &battery_voltage, 2, + ID_MEAS, TS_ANY_R, SUBSET_REPORT | SUBSET_CAN), + + TS_ITEM_FLOAT(0x72, "Bat_A", &battery_current, 2, + ID_MEAS, TS_ANY_R, SUBSET_REPORT | SUBSET_CAN), + + TS_ITEM_INT16(0x73, "Ambient_degC", &ambient_temp, + ID_MEAS, TS_ANY_R, SUBSET_REPORT), + + // RECORDED DATA ////////////////////////////////////////////////////////// + + TS_GROUP(ID_REC, "rec", TS_NO_CALLBACK, ID_ROOT), + + TS_ITEM_FLOAT(0xA1, "BatHour_kWh", &bat_energy_hour, 2, + ID_REC, TS_ANY_R, 0), + + TS_ITEM_FLOAT(0xA2, "BatDay_kWh", &bat_energy_day, 2, + ID_REC, TS_ANY_R, 0), + + TS_ITEM_INT16(0xA3, "AmbientMaxDay_degC", &ambient_temp_max_day, + ID_REC, TS_ANY_R, 0), + + // REMOTE PROCEDURE CALLS ///////////////////////////////////////////////// + + TS_GROUP(ID_RPC, "rpc", TS_NO_CALLBACK, ID_ROOT), + + TS_FUNCTION(0xE1, "x-reset", &test_core_reset_function, + ID_RPC, TS_ANY_RW), + + TS_FUNCTION(0xE2, "x-auth", &test_core_auth_function, + ID_RPC, TS_ANY_RW), + TS_ITEM_STRING(0xE3, "Password", auth_password, sizeof(auth_password), + 0xE2, TS_ANY_RW, 0), -struct ts_data_object data_objects[] = { + // REPORTS //////////////////////////////////////////////////////////////// + + TS_SUBSET(ID_REPORT, "report", SUBSET_REPORT, ID_ROOT, TS_ANY_RW), + + // PUBLICATION DATA /////////////////////////////////////////////////////// + + TS_GROUP(ID_PUB, ".pub", TS_NO_CALLBACK, ID_ROOT), + + TS_GROUP(0xF1, "report", TS_NO_CALLBACK, ID_PUB), + + TS_ITEM_BOOL(0xF2, "Enable", &pub_report_enable, + 0xF1, TS_ANY_RW, 0), + TS_ITEM_UINT16(0xF3, "Interval_ms", &pub_report_interval, + 0xF1, TS_ANY_RW, 0), + + TS_GROUP(0xF5, "info", TS_NO_CALLBACK, ID_PUB), + + TS_ITEM_BOOL(0xF6, "OnChange", &pub_info_enable, + 0xF5, TS_ANY_RW, 0), + + // UNIT TEST DATA ///////////////////////////////////////////////////////// + // using IDs >= 0x1000 + + TS_GROUP(0x1000, "test", TS_NO_CALLBACK, ID_ROOT), + + TS_ITEM_INT32(0x4001, "i32_readonly", &i32, 0x1000, TS_ANY_R, 0), + + TS_FUNCTION(0x5001, "x-dummy", &test_core_dummy_function, ID_RPC, TS_ANY_RW), + +#if TS_CONFIG_64BIT_TYPES_SUPPORT + TS_ITEM_UINT64(0x6001, "ui64", &ui64, ID_CONF, TS_ANY_RW, 0), + TS_ITEM_INT64(0x6002, "i64", &i64, ID_CONF, TS_ANY_RW, 0), +#endif + TS_ITEM_UINT32(0x6003, "ui32", &ui32, ID_CONF, TS_ANY_RW, 0), + TS_ITEM_INT32(0x6004, "i32", &i32, ID_CONF, TS_ANY_RW, 0), + TS_ITEM_UINT16(0x6005, "ui16", &ui16, ID_CONF, TS_ANY_RW, 0), + TS_ITEM_INT16(0x6006, "i16", &i16, ID_CONF, TS_ANY_RW, 0), + TS_ITEM_FLOAT(0x6007, "f32", &f32, 2, ID_CONF, TS_ANY_RW, 0), + TS_ITEM_BOOL(0x6008, "bool", &b, ID_CONF, TS_ANY_RW, 0), + + TS_ITEM_STRING(0x6009, "strbuf", strbuf, sizeof(strbuf), ID_CONF, TS_ANY_RW, 0), + + TS_ITEM_FLOAT(0x600A, "f32_rounded", &f32, 0, ID_CONF, TS_ANY_RW, 0), +#if TS_CONFIG_DECFRAC_TYPE_SUPPORT + TS_ITEM_DECFRAC(0x600B, "DecFrac_degC", &decfrac, -2, ID_CONF, TS_ANY_RW, 0), +#endif + + TS_ITEM_UINT32(0x7001, "secret_expert", &ui32, ID_CONF, TS_ANY_R | TS_EXP_W | TS_MKR_W, 0), + TS_ITEM_UINT32(0x7002, "secret_maker", &ui32, ID_CONF, TS_ANY_R | TS_MKR_W, 0), + TS_ITEM_ARRAY(0x7003, "arrayi32", &int32_array, 0, ID_CONF, TS_ANY_RW, 0), + // data_obj->detail will specify the number of decimal places for float + TS_ITEM_ARRAY(0x7004, "arrayfloat", &float32_array, 2, ID_CONF, TS_ANY_RW, 0), + + TS_ITEM_BYTES(0x8000, "bytesbuf", &bytes_buf, sizeof(bytes), ID_CONF, TS_ANY_RW, 0) +); + +/* Mostly Copy of core database */ +THINGSET_DATABASE_DEFINE(TEST_INSTANCE_LOCID, test_uid_instance, // DEVICE INFORMATION ///////////////////////////////////////////////////// TS_GROUP(ID_INFO, "info", TS_NO_CALLBACK, ID_ROOT), @@ -86,12 +212,12 @@ struct ts_data_object data_objects[] = { TS_ITEM_UINT32(0x1A, "Timestamp_s", ×tamp, ID_INFO, TS_ANY_RW, SUBSET_REPORT), - TS_ITEM_STRING(0x1B, "DeviceID", device_id, sizeof(device_id), + TS_ITEM_STRING(0x1B, "DeviceID", test_device_id_instance, sizeof( test_device_id_instance ), ID_INFO, TS_ANY_R | TS_MKR_W, 0), // CONFIGURATION ////////////////////////////////////////////////////////// - TS_GROUP(ID_CONF, "conf", &conf_callback, ID_ROOT), + TS_GROUP(ID_CONF, "conf", &test_core_conf_callback, ID_ROOT), TS_ITEM_FLOAT(0x31, "BatCharging_V", &bat_charging_voltage, 2, ID_CONF, TS_ANY_RW, 0), @@ -136,10 +262,10 @@ struct ts_data_object data_objects[] = { TS_GROUP(ID_RPC, "rpc", TS_NO_CALLBACK, ID_ROOT), - TS_FUNCTION(0xE1, "x-reset", &reset_function, + TS_FUNCTION(0xE1, "x-reset", &test_instance_reset_function, ID_RPC, TS_ANY_RW), - TS_FUNCTION(0xE2, "x-auth", &auth_function, + TS_FUNCTION(0xE2, "x-auth", &test_core_auth_function, ID_RPC, TS_ANY_RW), TS_ITEM_STRING(0xE3, "Password", auth_password, sizeof(auth_password), @@ -173,9 +299,9 @@ struct ts_data_object data_objects[] = { TS_ITEM_INT32(0x4001, "i32_readonly", &i32, 0x1000, TS_ANY_R, 0), - TS_FUNCTION(0x5001, "x-dummy", &dummy, ID_RPC, TS_ANY_RW), + TS_FUNCTION(0x5001, "x-dummy", &test_instance_dummy_function, ID_RPC, TS_ANY_RW), -#if TS_64BIT_TYPES_SUPPORT +#if TS_CONFIG_64BIT_TYPES_SUPPORT TS_ITEM_UINT64(0x6001, "ui64", &ui64, ID_CONF, TS_ANY_RW, 0), TS_ITEM_INT64(0x6002, "i64", &i64, ID_CONF, TS_ANY_RW, 0), #endif @@ -189,7 +315,7 @@ struct ts_data_object data_objects[] = { TS_ITEM_STRING(0x6009, "strbuf", strbuf, sizeof(strbuf), ID_CONF, TS_ANY_RW, 0), TS_ITEM_FLOAT(0x600A, "f32_rounded", &f32, 0, ID_CONF, TS_ANY_RW, 0), -#if TS_DECFRAC_TYPE_SUPPORT +#if TS_CONFIG_DECFRAC_TYPE_SUPPORT TS_ITEM_DECFRAC(0x600B, "DecFrac_degC", &decfrac, -2, ID_CONF, TS_ANY_RW, 0), #endif @@ -199,14 +325,152 @@ struct ts_data_object data_objects[] = { // data_obj->detail will specify the number of decimal places for float TS_ITEM_ARRAY(0x7004, "arrayfloat", &float32_array, 2, ID_CONF, TS_ANY_RW, 0), - TS_ITEM_BYTES(0x8000, "bytesbuf", &bytes_buf, sizeof(bytes), ID_CONF, TS_ANY_RW, 0), -}; + TS_ITEM_BYTES(0x8000, "bytesbuf", &bytes_buf, sizeof(bytes), ID_CONF, TS_ANY_RW, 0) +); -#ifndef ARRAY_SIZE -#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) -#endif +/* Mostly Copy of basic example - all data objects are children of root */ +THINGSET_DATABASE_DEFINE(TEST_NEIGHBOUR_LOCID, test_uid_neighbour, + + TS_ITEM_STRING(0x1D, "DeviceID", test_device_id_neighbour, sizeof(test_device_id_neighbour), + TS_ID_ROOT, TS_ANY_R, 0), + + TS_ITEM_FLOAT(0x71, "Ambient_degC", &ambient_temp, 1, + TS_ID_ROOT, TS_ANY_R, 0), + + TS_ITEM_BOOL(0x61, "HeaterEnable", &enable_switch, + TS_ID_ROOT, TS_ANY_RW, 0), + + TS_FUNCTION(0xE1, "x-reset", &test_neighbour_reset_function, + TS_ID_ROOT, TS_ANY_RW), +); + +/* + * ThingSet core context + * --------------------- + */ + +THINGSET_CORE_DEFINE(); + +/* + * ThingSet communication context - instance + * ----------------------------------------- + */ + +const struct { +} test_app_config; + +struct { +} test_app_data; + +int test_app_init(const struct thingset_app *app) +{ + return 0; +} + +int test_app_run(const struct thingset_app *app) +{ + return 0; +} + +THINGSET_APP_DEFINE(TEST_APP_INSTANCE_PORTID, TEST_APP_INSTANCE_NAME, TEST_INSTANCE_LOCID, + test_app_init, test_app_run, test_app_config, test_app_data); + +THINGSET_PORT_DEFINE(TEST_PORT_LOOPBACK_A_PORTID, loopback_simple, TEST_PORT_LOOPBACK_A_NAME, + TEST_PORT_LOOPBACK_A_LOCID, .other_port_id = TEST_PORT_LOOPBACK_B_PORTID); +THINGSET_PORT_DEFINE(TEST_PORT_LOOPBACK_B_PORTID, loopback_simple, TEST_PORT_LOOPBACK_B_NAME, + TEST_PORT_LOOPBACK_B_LOCID, .other_port_id = TEST_PORT_LOOPBACK_A_PORTID); -size_t data_objects_size = ARRAY_SIZE(data_objects); +THINGSET_COM_DEFINE(TEST_INSTANCE_LOCID); + +/* + * ThingSet communication context - neighbour + * ------------------------------------------ + */ + +THINGSET_APP_DEFINE(TEST_APP_NEIGHBOUR_PORTID, TEST_APP_NEIGHBOUR_NAME, TEST_NEIGHBOUR_LOCID, + test_app_init, test_app_run, test_app_config, test_app_data); + +THINGSET_COM_DEFINE(TEST_NEIGHBOUR_LOCID); + +/* + * Initialise test data + * -------------------- + */ + +void test_init_test_data(void) +{ + /* context unique identifier */ + test_uid_instance = 0xCAFECAFEUL; + test_uid_neighbour = 0xDEADBEADUL; + + // info + timestamp = 12345678; + + // conf + bat_charging_voltage = 14.4; + load_disconnect_voltage = 10.8; + + // input + enable_switch = false; + + // meas + battery_voltage = 14.1; + battery_current = 5.13; + ambient_temp = 22; + + // rec + bat_energy_hour = 32.2; + bat_energy_day = 123; + ambient_temp_max_day = 28; + + // pub + pub_report_enable = false; + pub_report_interval = 1000; + pub_info_enable = true; + + // exec + strncpy(&auth_password[0], " ", sizeof(auth_password)); + +#if TS_CONFIG_64BIT_TYPES_SUPPORT + ui64 = 1; + i64 = 2; +#endif + ui32 = 3; + i32 = 4; + ui16 = 5; + i16 = 6; + f32 = 8.4; + b = true; + strbuf[0] = '\0'; + + A[0] = 4; + A[1] = 2; + A[2] = 8; + A[3] = 4; + + B[0] = 2.27; + B[1] = 3.44; + + strcpy(&strbuf[0], "test"); + + for (int i = 0; i < sizeof(bytes); i++) { + bytes[i] = 0; + } +} + +void setUp(void) { + test_init_test_data(); + ts_buf_free_all(); + (void)thingset_init(TEST_CORE_LOCID); + (void)thingset_init(TEST_INSTANCE_LOCID); + (void)thingset_init(TEST_NEIGHBOUR_LOCID); + thingset_authorisation_set(TEST_CORE_LOCID, TS_ANY_RW); + thingset_authorisation_set(TEST_INSTANCE_LOCID, TS_ANY_RW); + thingset_authorisation_set(TEST_NEIGHBOUR_LOCID, TS_ANY_RW); +} + +void tearDown(void) { +} #ifdef __cplusplus } /** extern "C" */ diff --git a/test/test_shim.cpp b/test/test_shim.cpp index 0d95ff7..481baf8 100644 --- a/test/test_shim.cpp +++ b/test/test_shim.cpp @@ -7,8 +7,19 @@ void test_shim_get_object(void) { - ThingSet ts(&data_objects[0], data_objects_size); + ThingSet ts; - ThingSetDataObject *object = ts.get_object(ID_INFO); - TEST_ASSERT_EQUAL_PTR(&data_objects[0], object); + ThingSetObject *object = ts.get_object(ID_INFO); + thingset_oref_t oref = ts_obj_db_oref_by_object(object); + TEST_ASSERT_EQUAL_UINT16(ID_INFO, ts_obj_id(oref)); +} + +void tests_shim(void) +{ + UNITY_BEGIN(); + + // data conversion tests + RUN_TEST(test_shim_get_object); + + UNITY_END(); } diff --git a/test/test_txt.c b/test/test_txt.c index 508869a..5a7a42e 100644 --- a/test/test_txt.c +++ b/test/test_txt.c @@ -69,8 +69,8 @@ void test_txt_fetch_float_array(void) void test_txt_patch_wrong_data_structure(void) { - TEST_ASSERT_TXT_REQ("!conf [\"f32\":54.3", ":A0 Bad Request."); - TEST_ASSERT_TXT_REQ("!conf{\"f32\":54.3}", ":A4 Not Found."); + TEST_ASSERT_TXT_REQ("=conf [\"f32\":54.3", ":A0 Bad Request."); + TEST_ASSERT_TXT_REQ("=conf{\"wrong\":54.3}", ":A4 Not Found."); } void test_txt_patch_array(void) @@ -98,40 +98,40 @@ void test_txt_patch_unknown_object(void) void test_txt_conf_callback(void) { - conf_callback_called = 0; + test_core_conf_callback_called = 0; TEST_ASSERT_TXT_REQ("=conf {\"i32\":52}", ":84 Changed."); - TEST_ASSERT_EQUAL(1, conf_callback_called); + TEST_ASSERT_EQUAL(1, test_core_conf_callback_called); } void test_txt_exec(void) { - dummy_called_flag = 0; + test_core_dummy_called = 0; TEST_ASSERT_TXT_REQ("!rpc/x-dummy", ":83 Valid."); - TEST_ASSERT_EQUAL(1, dummy_called_flag); + TEST_ASSERT_EQUAL(1, test_core_dummy_called); } void test_txt_statement_subset(void) { - int resp_len = ts_txt_statement_by_path(&ts, (char *)resp_buf, TS_RESP_BUFFER_LEN, "report"); + int resp_len = thingset_txt_statement_by_path((char *)test_resp_buf, TEST_RESP_BUFFER_LEN, "report"); TEST_ASSERT_TXT_RESP(resp_len, "#report {\"Timestamp_s\":12345678,\"Bat_V\":14.10,\"Bat_A\":5.13,\"Ambient_degC\":22}"); - resp_len = ts_txt_statement_by_id(&ts, (char *)resp_buf, TS_RESP_BUFFER_LEN, ID_REPORT); + resp_len = thingset_txt_statement_by_id((char *)test_resp_buf, TEST_RESP_BUFFER_LEN, ID_REPORT); TEST_ASSERT_TXT_RESP(resp_len, "#report {\"Timestamp_s\":12345678,\"Bat_V\":14.10,\"Bat_A\":5.13,\"Ambient_degC\":22}"); } void test_txt_statement_group(void) { - int resp_len = ts_txt_statement_by_path(&ts, (char *)resp_buf, TS_RESP_BUFFER_LEN, "info"); + int resp_len = thingset_txt_statement_by_path((char *)test_resp_buf, TEST_RESP_BUFFER_LEN, "info"); TEST_ASSERT_TXT_RESP(resp_len, "#info {\"Manufacturer\":\"Libre Solar\",\"Timestamp_s\":12345678,\"DeviceID\":\"ABCD1234\"}"); - resp_len = ts_txt_statement_by_id(&ts, (char *)resp_buf, TS_RESP_BUFFER_LEN, ID_INFO); + resp_len = thingset_txt_statement_by_id((char *)test_resp_buf, TEST_RESP_BUFFER_LEN, ID_INFO); TEST_ASSERT_TXT_RESP(resp_len, "#info {\"Manufacturer\":\"Libre Solar\",\"Timestamp_s\":12345678,\"DeviceID\":\"ABCD1234\"}"); } @@ -186,7 +186,7 @@ void test_txt_auth_root(void) void test_txt_auth_long_password(void) { - TEST_ASSERT_TXT_REQ("!rpc/x-auth \"012345678901234567890123456789\"", ":AF Unsupported Content-Format."); + TEST_ASSERT_TXT_REQ("!rpc/x-auth 012345678901234567890123456789", ":AF Unsupported Content-Format."); } void test_txt_auth_failure(void) @@ -209,33 +209,86 @@ void test_txt_wrong_command(void) void test_txt_get_endpoint(void) { - const struct ts_data_object *object; + const struct ts_obj *object; - object = ts_get_object_by_path(&ts, "conf", strlen("conf")); + object = thingset_object_by_path("conf", strlen("conf")); TEST_ASSERT_NOT_NULL(object); TEST_ASSERT_EQUAL_UINT16(ID_CONF, object->id); - object = ts_get_object_by_path(&ts, "conf/", strlen("conf/")); + object = thingset_object_by_path("conf/", strlen("conf/")); TEST_ASSERT_NOT_NULL(object); TEST_ASSERT_EQUAL_UINT16(ID_CONF, object->id); - object = ts_get_object_by_path(&ts, "/", strlen("/")); + object = thingset_object_by_path("/", strlen("/")); TEST_ASSERT_NULL(object); /* special case where the data contains forward slashes */ - object = ts_get_object_by_path(&ts, "conf \"this/is/a/path\"", strlen("conf")); + object = thingset_object_by_path("conf \"this/is/a/path\"", strlen("conf")); TEST_ASSERT_NOT_NULL(object); TEST_ASSERT_EQUAL_UINT16(ID_CONF, object->id); /* special case where the data contains forward slashes */ - object = ts_get_object_by_path(&ts, "rpc/x-reset \"this/is/a/path\"", strlen("rpc/x-reset")); + object = thingset_object_by_path("rpc/x-reset \"this/is/a/path\"", strlen("rpc/x-reset")); TEST_ASSERT_NOT_NULL(object); TEST_ASSERT_EQUAL_UINT16(0xE1, object->id); } void test_txt_export(void) { - int resp_len = ts_txt_export(&ts, (char *)resp_buf, TS_RESP_BUFFER_LEN, SUBSET_REPORT); + int resp_len = thingset_txt_export((char *)test_resp_buf, TEST_RESP_BUFFER_LEN, SUBSET_REPORT); - TEST_ASSERT_TXT_RESP(resp_len, "{\"Timestamp_s\":12345678,\"Bat_V\":14.10,\"Bat_A\":5.13,\"Ambient_degC\":22}"); + TEST_ASSERT_TXT_RESP(resp_len, ":86 Export. " + "{\"Timestamp_s\":12345678,\"Bat_V\":14.10,\"Bat_A\":5.13,\"Ambient_degC\":22}"); +} + +void tests_txt(void) +{ + UNITY_BEGIN(); + + // GET request + RUN_TEST(test_txt_get_meas_names); + RUN_TEST(test_txt_get_meas_names_values); + RUN_TEST(test_txt_get_single_value); + + // FETCH request + RUN_TEST(test_txt_fetch_array); + RUN_TEST(test_txt_fetch_rounded); + RUN_TEST(test_txt_fetch_nan); + RUN_TEST(test_txt_fetch_inf); + RUN_TEST(test_txt_fetch_int32_array); + RUN_TEST(test_txt_fetch_float_array); + + // PATCH request + RUN_TEST(test_txt_patch_wrong_data_structure); + RUN_TEST(test_txt_patch_array); + RUN_TEST(test_txt_patch_readonly); + RUN_TEST(test_txt_patch_wrong_path); + RUN_TEST(test_txt_patch_unknown_object); + RUN_TEST(test_txt_conf_callback); + + // POST request + RUN_TEST(test_txt_exec); + + // statements (pub/sub messages) + RUN_TEST(test_txt_statement_subset); + RUN_TEST(test_txt_statement_group); + RUN_TEST(test_txt_pub_list_channels); + RUN_TEST(test_txt_pub_enable); + RUN_TEST(test_txt_pub_delete_append_object); + + // authorisation + RUN_TEST(test_txt_auth_user); + RUN_TEST(test_txt_auth_root); + RUN_TEST(test_txt_auth_long_password); + RUN_TEST(test_txt_auth_failure); + RUN_TEST(test_txt_auth_reset); + + // general tests + RUN_TEST(test_txt_wrong_command); + RUN_TEST(test_txt_get_endpoint); + + // data export + RUN_TEST(test_txt_export); + + UNITY_END(); } diff --git a/zephyr/tests/CMakeLists.txt b/zephyr/tests/CMakeLists.txt index 7288869..c0dd025 100644 --- a/zephyr/tests/CMakeLists.txt +++ b/zephyr/tests/CMakeLists.txt @@ -17,6 +17,7 @@ foreach(module ${modules}) set(full_path ${module}/Kconfig.zephyr) if(EXISTS ${full_path}) set(zephyr_base "${module}") + set(ENV{ZEPHYR_BASE} "${zephyr_base}") break() endif() endforeach() @@ -29,11 +30,25 @@ set(ZEPHYR_TOOLCHAIN_VARIANT "host") set(ZEPHYR_MODULES ${THINGSET_BASE}) find_package(Zephyr REQUIRED HINTS ${zephyr_base}) -project(thingset_tests) +#include(${zephyr_base}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE) +project(thingset_zephyr_tests) -target_sources(app PRIVATE src/main.c - ${THINGSET_BASE}/test/test_common.c - ${THINGSET_BASE}/test/test_txt.c - ${THINGSET_BASE}/test/test_bin.c - ${THINGSET_BASE}/test/test_context.c - ${THINGSET_BASE}/test/test_data.c) +target_sources(app PRIVATE + ${THINGSET_BASE}/test/main.cpp + ${THINGSET_BASE}/test/test_assert.c + ${THINGSET_BASE}/test/test_bin.c + ${THINGSET_BASE}/test/test_buf.c + ${THINGSET_BASE}/test/test_common.c + ${THINGSET_BASE}/test/test_ctx.c + ${THINGSET_BASE}/test/test_data.c + ${THINGSET_BASE}/test/test_jsmn.c + ${THINGSET_BASE}/test/test_msg.c + ${THINGSET_BASE}/test/test_obj.c + ${THINGSET_BASE}/test/test_shell.c + ${THINGSET_BASE}/test/test_shim.cpp + ${THINGSET_BASE}/test/test_support.c + ${THINGSET_BASE}/test/test_time.c + ${THINGSET_BASE}/test/test_txt.c + # Zephyr implementation test suite + ${CMAKE_CURRENT_SOURCE_DIR}/test_impl.c +) diff --git a/zephyr/tests/Kconfig b/zephyr/tests/Kconfig index bea5618..8dbe54f 100644 --- a/zephyr/tests/Kconfig +++ b/zephyr/tests/Kconfig @@ -1,5 +1,5 @@ # SPDX-License-Identifier: Apache-2.0 -mainmenu "ThingSet - Zepyhr Tests Configuration" +mainmenu "ThingSet for Zepyhr - tests configuration" source "Kconfig.zephyr" diff --git a/zephyr/tests/prj.conf b/zephyr/tests/prj.conf index 93b7fa0..6132c29 100644 --- a/zephyr/tests/prj.conf +++ b/zephyr/tests/prj.conf @@ -2,15 +2,40 @@ CONFIG_THINGSET=y +# Enable all contexts for testing +CONFIG_THINGSET_CORE=y +CONFIG_THINGSET_COM=y +CONFIG_THINGSET_LOCAL_COUNT=3 +CONFIG_THINGSET_PORT_COUNT=5 + +# Enable unit testing +CONFIG_THINGSET_UNIT_TEST=y +CONFIG_ZTEST=y +CONFIG_COVERAGE=y + +# Activate assertions during testing +CONFIG_ASSERT=y + # enable all optional features to increase test coverage CONFIG_THINGSET_64BIT_TYPES_SUPPORT=y CONFIG_THINGSET_DECFRAC_TYPE_SUPPORT=y CONFIG_THINGSET_BYTE_STRING_TYPE_SUPPORT=y -CONFIG_ZTEST=y -CONFIG_COVERAGE=y - CONFIG_LOG=y CONFIG_THINGSET_LOG_LEVEL_DBG=y -CONFIG_STDOUT_CONSOLE=y +# enable loopback port for testing +CONFIG_THINGSET_PORT_LOOPBACK_SIMPLE=y + +# enable testing of applications +CONFIG_THINGSET_APPS=y + +# enable testing of shell application +CONFIG_SHELL=y +CONFIG_SHELL_BACKEND_DUMMY=y +CONFIG_THINGSET_SHELL=y +CONFIG_THINGSET_SHELL_NAME="test_shell" +CONFIG_THINGSET_SHELL_LOCID=1 +CONFIG_THINGSET_SHELL_PORTID=2 + + diff --git a/zephyr/tests/src/main.c b/zephyr/tests/src/main.c deleted file mode 100644 index ea0db66..0000000 --- a/zephyr/tests/src/main.c +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (c) 2020 Martin Jäger / Libre Solar - * Copyright (c) 2021 Bobby Noelte. - * SPDX-License-Identifier: Apache-2.0 - */ - -#if !CONFIG_THINGSET_ZEPHYR -#error "Compiling main.c in non Zephyr environment! You need to set CONFIG_THINGSET_ZEPHYR." -#endif - -#include "../../../test/test.h" - - -void setup(void) { - (void)ts_init(&ts, &data_objects[0], data_objects_size); -} - -void teardown(void) { -} - -void test_main(void) -{ - LOG_DBG("Running ThingSet tests for Zepyhr"); - - ztest_test_suite(thingset_tests, - /* test environment */ - ztest_unit_test(test_assert), - ztest_unit_test(test_ts_init), - /* data conversion tests */ - ztest_unit_test_setup_teardown(test_txt_patch_bin_fetch, setup, teardown), - ztest_unit_test_setup_teardown(test_bin_patch_txt_fetch, setup, teardown), - - /* Text mode: GET request */ - ztest_unit_test_setup_teardown(test_txt_get_meas_names, setup, teardown), - ztest_unit_test_setup_teardown(test_txt_get_meas_names_values, setup, teardown), - ztest_unit_test_setup_teardown(test_txt_get_single_value, setup, teardown), - /* Text mode: FETCH request */ - ztest_unit_test_setup_teardown(test_txt_fetch_array, setup, teardown), - ztest_unit_test_setup_teardown(test_txt_fetch_rounded, setup, teardown), - ztest_unit_test_setup_teardown(test_txt_fetch_nan, setup, teardown), - ztest_unit_test_setup_teardown(test_txt_fetch_inf, setup, teardown), - ztest_unit_test_setup_teardown(test_txt_fetch_int32_array, setup, teardown), - ztest_unit_test_setup_teardown(test_txt_fetch_float_array, setup, teardown), - /* Text mode: PATCH request */ - ztest_unit_test_setup_teardown(test_txt_patch_wrong_data_structure, setup, teardown), - ztest_unit_test_setup_teardown(test_txt_patch_array, setup, teardown), - ztest_unit_test_setup_teardown(test_txt_patch_readonly, setup, teardown), - ztest_unit_test_setup_teardown(test_txt_patch_wrong_path, setup, teardown), - ztest_unit_test_setup_teardown(test_txt_patch_unknown_object, setup, teardown), - ztest_unit_test_setup_teardown(test_txt_conf_callback, setup, teardown), - /* Text mode: POST request */ - ztest_unit_test_setup_teardown(test_txt_exec, setup, teardown), - /* Text mode: statements (pub/sub messages) */ - ztest_unit_test_setup_teardown(test_txt_statement_subset, setup, teardown), - ztest_unit_test_setup_teardown(test_txt_statement_group, setup, teardown), - ztest_unit_test_setup_teardown(test_txt_pub_list_channels, setup, teardown), - ztest_unit_test_setup_teardown(test_txt_pub_enable, setup, teardown), - ztest_unit_test_setup_teardown(test_txt_pub_delete_append_object, setup, teardown), - ztest_unit_test_setup_teardown(test_txt_auth_user, setup, teardown), - ztest_unit_test_setup_teardown(test_txt_auth_root, setup, teardown), - ztest_unit_test_setup_teardown(test_txt_auth_long_password, setup, teardown), - ztest_unit_test_setup_teardown(test_txt_auth_failure, setup, teardown), - ztest_unit_test_setup_teardown(test_txt_auth_reset, setup, teardown), - /* Text mode: general tests */ - ztest_unit_test_setup_teardown(test_txt_wrong_command, setup, teardown), - ztest_unit_test_setup_teardown(test_txt_get_endpoint, setup, teardown), - /* Text mode: exporting of data */ - ztest_unit_test_setup_teardown(test_txt_export, setup, teardown), - - /* Bin mode: GET request */ - ztest_unit_test_setup_teardown(test_bin_get_meas_ids_values, setup, teardown), - ztest_unit_test_setup_teardown(test_bin_get_meas_names_values, setup, teardown), - ztest_unit_test_setup_teardown(test_bin_get_single_value, setup, teardown), - /* Bin mode: PATCH request */ - ztest_unit_test_setup_teardown(test_bin_patch_multiple_objects, setup, teardown), - ztest_unit_test_setup_teardown(test_bin_patch_float_array, setup, teardown), - ztest_unit_test_setup_teardown(test_bin_patch_rounded_float, setup, teardown), - /* Text mode: FETCH request */ - ztest_unit_test_setup_teardown(test_bin_fetch_meas_ids, setup, teardown), - ztest_unit_test_setup_teardown(test_bin_fetch_meas_names, setup, teardown), - ztest_unit_test_setup_teardown(test_bin_fetch_multiple_objects, setup, teardown), - ztest_unit_test_setup_teardown(test_bin_fetch_float_array, setup, teardown), - ztest_unit_test_setup_teardown(test_bin_fetch_rounded_float, setup, teardown), - /* Bin mode: POST request */ - ztest_unit_test_setup_teardown(test_bin_exec, setup, teardown), - /* Bin mode: pub/sub messages */ - ztest_unit_test_setup_teardown(test_bin_statement_subset, setup, teardown), - ztest_unit_test_setup_teardown(test_bin_statement_group, setup, teardown), - ztest_unit_test_setup_teardown(test_bin_pub_can, setup, teardown), - /* Bin mode: general tests */ - ztest_unit_test_setup_teardown(test_bin_num_elem, setup, teardown), - ztest_unit_test_setup_teardown(test_bin_serialize_long_string, setup, teardown), -#ifdef CONFIG_THINGSET_BYTE_STRING_TYPE_SUPPORT - /* Bin mode: binary (bytes) data type */ - ztest_unit_test_setup_teardown(test_bin_serialize_bytes, setup, teardown), - ztest_unit_test_setup_teardown(test_bin_deserialize_bytes, setup, teardown), - ztest_unit_test_setup_teardown(test_bin_patch_fetch_bytes, setup, teardown), -#endif - /* Bin mode: exporting/importing of data */ - ztest_unit_test_setup_teardown(test_bin_export, setup, teardown), - ztest_unit_test_setup_teardown(test_bin_import, setup, teardown) - ); - - ztest_run_test_suite(thingset_tests); -} diff --git a/zephyr/ztest/ztest_unity.h b/zephyr/ztest/ztest_unity.h deleted file mode 100644 index 0da05bf..0000000 --- a/zephyr/ztest/ztest_unity.h +++ /dev/null @@ -1,301 +0,0 @@ -/* - * Copyright (c) 2021 Bobby Noelte. - * SPDX-License-Identifier: Apache-2.0 - */ - -#ifndef ZTEST_UNITY_H_ -#define ZTEST_UNITY_H_ - -#if !CONFIG_ZTEST -#error "You need to define CONFIG_ZTEST." -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -#include - -/** - * @def TEST_ASSERT_BUFFER_SIZE - * - * @brief Size of buffer for assertion message build up. - */ -#define TEST_ASSERT_BUFFER_SIZE 300 - -/** - * @brief Buffer for assertion message build up. - * - * @note Buffer must be provided by the test application. - */ -extern char test_assert_buffer[TEST_ASSERT_BUFFER_SIZE]; - -#define TEST_ASSERT(condition) \ - zassert_true(condition, "expect: >true<\nactual: >%d<", (int)(condition)) - -#define TEST_ASSERT_TRUE(condition) \ - zassert_true(condition, "expect: >true<\nactual: >%d<", (int)(condition)) - -#define TEST_ASSERT_TRUE_MESSAGE(condition, msg) \ - zassert_true(condition, "expect: >true<\nactual: >%d<\n%s", (int)(condition), msg) - -#define TEST_ASSERT_FALSE(condition) \ - zassert_false(condition, "expect: >false<\nactual: >%d<", (int)(condition)) - -#define TEST_ASSERT_FALSE_MESSAGE(condition, msg) \ - zassert_false(condition, "expect: >false<\nactual: >%d<\n%s", (int)(condition), msg) - -#define TEST_ASSERT_NULL(pointer) \ - zassert_is_null(pointer, "expect: >NULL<\nactual: >%d<", (int)(pointer)) - -#define TEST_ASSERT_NOT_NULL(pointer) \ - zassert_not_null(pointer, "expect: >!NULL<\nactual: >%d<", (int)(pointer)) - -#define TEST_ASSERT_NOT_NULL_MESSAGE(pointer, msg) \ - zassert_not_null(pointer, "expect: >!NULL<\nactual: >%d<\n%s", (int)(pointer), msg) - -#define TEST_ASSERT_EQUAL(expected, actual) \ - zassert_equal((int)(expected), (int)(actual), "expect: >%d<\nactual: >%d<", \ - (int)(expected), (int)(actual)) - -#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, msg) \ - zassert_equal((int)(expected), (int)(actual), "expect: >%d<\nactual: >%d<\n%s", \ - (int)(expected), (int)(actual), msg) - -#define TEST_ASSERT_EQUAL_FLOAT(expected, actual) \ - do { \ - (void)snprintf(&test_assert_buffer[0], TEST_ASSERT_BUFFER_SIZE, \ - "expect: >%g<\nactual: >%g<", (float)(expected), (float)(actual)); \ - if (isinf((float)(expected))) { \ - zassert_true(isinf((float)(actual)), "%s", &test_assert_buffer[0]); \ - } else if (isnan((float)(expected))) { \ - zassert_true(isnan((float)(actual)), "%s", &test_assert_buffer[0]); \ - } else { \ - zassert_within((float)(expected), (float)(actual), 0.0000001, "%s", \ - &test_assert_buffer[0]); \ - } \ - } while(false) - -#define TEST_ASSERT_EQUAL_HEX(expected, actual) \ - zassert_equal((unsigned)(expected), (unsigned)(actual), "expect: >0x%x<\nactual: >0x%x<", \ - (unsigned)(expected), (unsigned)(actual)) - -#define TEST_ASSERT_EQUAL_HEX8(expected, actual) \ - zassert_equal((uint8_t)(expected), (uint8_t)(actual), "expect: >0x%x<\nactual: >0x%x<", \ - (unsigned)(expected), (unsigned)(actual)) - -#define TEST_ASSERT_EQUAL_HEX8_MESSAGE(expected, actual, msg) \ - zassert_equal((uint8_t)(expected), (uint8_t)(actual), "expect: >0x%x<\nactual: >0x%x<\n%s",\ - (unsigned)(expected), (unsigned)(actual), msg) - -#define TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, actual, num_elements) \ - do { \ - int len = 0; \ - int ret; \ - ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, \ - "expect: >0x"); \ - len += ret; \ - for (int i = 0; (i < num_elements) && (ret >= 0); i++) { \ - ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, "%02x", \ - (unsigned int)(((const uint8_t*)(expected))[i])); \ - len += ret; \ - } \ - if (ret >= 0) { \ - ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, \ - "<\nactual: >0x"); \ - len += ret; \ - } \ - for (int i = 0; (i < num_elements) && (ret >= 0); i++) { \ - ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, "%02x", \ - (unsigned int)(((const uint8_t*)(actual))[i])); \ - len += ret; \ - } \ - if (ret >= 0) { \ - ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, "<"); \ - } \ - if (ret < 0) { \ - (void)snprintf(&test_assert_buffer[0], TEST_ASSERT_BUFFER_SIZE, \ - "expect/ actual conversion error %d", ret); \ - } \ - test_assert_buffer[TEST_ASSERT_BUFFER_SIZE - 1] = '\0'; \ - } while(false); \ - zassert_equal(memcmp(expected, actual, num_elements), 0, &test_assert_buffer[0]) - -#define TEST_ASSERT_EQUAL_HEX8_ARRAY_MESSAGE(expected, actual, num_elements, msg) \ - do { \ - int len = 0; \ - int ret; \ - ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, \ - "expect: >0x"); \ - len += ret; \ - for (int i = 0; (i < num_elements) && (ret >= 0); i++) { \ - ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, "%02x", \ - (unsigned int)(((const uint8_t*)(expected))[i])); \ - len += ret; \ - } \ - if (ret >= 0) { \ - ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, \ - "<\nactual: >0x"); \ - len += ret; \ - } \ - for (int i = 0; (i < num_elements) && (ret >= 0); i++) { \ - ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, "%02x", \ - (unsigned int)(((const uint8_t*)(actual))[i])); \ - len += ret; \ - } \ - if (ret >= 0) { \ - ret = snprintf(&test_assert_buffer[len], TEST_ASSERT_BUFFER_SIZE - len, \ - "<\n%s", msg); \ - } \ - if (ret < 0) { \ - (void)snprintf(&test_assert_buffer[0], TEST_ASSERT_BUFFER_SIZE, \ - "expect/ actual conversion error %d\n%s", ret, msg); \ - } \ - test_assert_buffer[TEST_ASSERT_BUFFER_SIZE - 1] = '\0'; \ - } while(false); \ - zassert_equal(memcmp(expected, actual, num_elements), 0, &test_assert_buffer[0]) - -#define TEST_ASSERT_EQUAL_INT(expected, actual) \ - zassert_equal((int)(expected), (int)(actual), "expect: >%d<\nactual: >%d<", \ - (int)(expected), (int)(actual)) - -#define TEST_ASSERT_EQUAL_INT8(expected, actual) \ - zassert_equal((int8_t)(expected), (int8_t)(actual), "expect: >%u<\nactual: >%u<", \ - (int)(int8_t)(expected), (int)(int8_t)(actual)) - -#define TEST_ASSERT_EQUAL_INT16(expected, actual) \ - zassert_equal((int16_t)(expected), (int16_t)(actual), "expect: >%d<\nactual: >0xd<", \ - (int)(int16_t)(expected), (int)(int16_t)(actual)) - -#define TEST_ASSERT_EQUAL_INT32(expected, actual) \ - zassert_equal((int32_t)(expected), (int32_t)(actual), "expect: >%d<\nactual: >0xd<", \ - (int)(int32_t)(expected), (int)(int32_t)(actual)) - -#define TEST_ASSERT_EQUAL_INT64(expected, actual) \ - zassert_equal((int64_t)(expected), (int64_t)(actual), \ - "expect: >%" PRIi64 "<\nactual: >%" PRIi64 "<", \ - (int64_t)(expected), (int64_t)(actual)) - -#define TEST_ASSERT_EQUAL_UINT(expected, actual) \ - zassert_equal((unsigned)(expected), (unsigned)(actual), "expect: >0x%x<\nactual: >0x%x<", \ - (unsigned)(expected), (unsigned)(actual)) - -#define TEST_ASSERT_EQUAL_UINT8(expected, actual) \ - zassert_equal((uint8_t)(expected), (uint8_t)(actual), "expect: >%u<\nactual: >%u<", \ - (unsigned)(uint8_t)(expected), (unsigned)(uint8_t)(actual)) - -#define TEST_ASSERT_EQUAL_UINT16(expected, actual) \ - zassert_equal((uint16_t)(expected), (uint16_t)(actual), "expect: >%u<\nactual: >%u<", \ - (unsigned)(uint16_t)(expected), (unsigned)(uint16_t)(actual)) - -#define TEST_ASSERT_EQUAL_UINT16_MESSAGE(expected, actual, msg) \ - zassert_equal((uint16_t)(expected), (uint16_t)(actual), "expect: >%u<\nactual: >%u<\n%s", \ - (unsigned)(uint16_t)(expected), (unsigned)(uint16_t)(actual), msg) - -#define TEST_ASSERT_EQUAL_UINT32(expected, actual) \ - zassert_equal((uint32_t)(expected), (uint32_t)(actual), "expect: >%u<\nactual: >%u<", \ - (unsigned)(uint32_t)(expected), (unsigned)(uint32_t)(actual)) - -#define TEST_ASSERT_EQUAL_UINT64(expected, actual) \ - zassert_equal((uint64_t)(expected), (uint64_t)(actual), \ - "expect: >%" PRIu64 "<\nactual: >%" PRIu64 "<", \ - (uint64_t)(expected), (uint64_t)(actual)) - -#define TEST_ASSERT_EQUAL_PTR(expected, actual) \ - zassert_equal_ptr(expected, actual, "expect: >%d<\nactual: >%d<", \ - (int)(expected), (int)(actual)) - -#define TEST_ASSERT_EQUAL_PTR_MESSAGE(expected, actual, msg) \ - zassert_equal_ptr(expected, actual, "expect: >%d<\nactual: >%d<\n%s", \ - (int)(expected), (int)(actual), msg) - -#define TEST_ASSERT_EQUAL_STRING(expected, actual) \ - zassert_equal(strcmp(expected, actual), 0, "expect: >%s<\nactual: >%s<", expected, actual) - -#define TEST_ASSERT_EQUAL_STRING_LEN(expected, actual, len) \ - zassert_equal(strncmp(expected, actual, len), 0, "expect: >%.*s<\nactual: >%.*s<", \ - (size_t)len, expected, (size_t)len, actual) - -#define TEST_ASSERT_EQUAL_STRING_LEN_MESSAGE(expected, actual, len, msg) \ - zassert_equal(strncmp(expected, actual, len), 0, "expect: >%.*s<\nactual: >%.*s<\n%s", \ - (size_t)len, expected, (size_t)len, actual, msg) - -#define TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, actual, msg) \ - zassert_equal(strcmp(expected, actual), 0, "expect: >%s<\nactual: >%s<\n%s", \ - expected, actual, msg) - -#define TEST_ASSERT_EQUAL_size_t(expected, actual) \ - zassert_equal((size_t)(expected),(size_t)(actual), "expect: >%d<\nactual: >%d<", \ - (int)(expected), (int)(actual)) - -#define TEST_ASSERT_GREATER_OR_EQUAL_INT(threshold, actual) \ - zassert_true((int)(threshold) <= (int)(actual), "expect: >= >%d<\nactual: >%d<", \ - (int)(threshold), (int)(actual)) - -#define TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(threshold, actual, msg) \ - zassert_true((int)(threshold) <= (int)(actual), "expect: >= >%d<\nactual: >%d<\n%s", \ - (int)(threshold), (int)(actual), msg) - -#define TEST_ASSERT_GREATER_OR_EQUAL_UINT32(threshold, actual) \ - zassert_true((uint32_t)(threshold) <= (uint32_t)(actual), "expect: >= >%u<\nactual: >%u<", \ - (unsigned int)(uint32_t)(threshold), (unsigned int)(uint32_t)(actual)) - -#define TEST_ASSERT_GREATER_OR_EQUAL_size_t(threshold, actual) \ - zassert_true((size_t)(threshold) <= (size_t)(actual), "expect: >= >%d<\nactual: >%d<", \ - (int)(threshold), (int)(actual)) - -#define TEST_ASSERT_GREATER_THAN_UINT16(threshold, actual) \ - zassert_true((uint16_t)(threshold) < (uint16_t)(actual), "expect: > >%u<\nactual: >%u<", \ - (unsigned int)(uint16_t)(threshold), (unsigned int)(uint16_t)(actual)) - -#define TEST_ASSERT_LESS_THAN_INT(threshold, actual) \ - zassert_true((int)(threshold) > (int)(actual), "expect: < >%d<\nactual: >%d<", \ - (int)(threshold), (int)(actual)) - -#define TEST_ASSERT_LESS_THAN_INT_MESSAGE(threshold, actual, msg) \ - zassert_true((int)(threshold) > (int)(actual), "expect: < >%d<\nactual: >%d<\n%s", \ - (int)(threshold), (int)(actual), msg) - -#define TEST_ASSERT_LESS_THAN_size_t(threshold, actual) \ - zassert_true((size_t)(threshold) > (size_t)(actual), "expect: < >%d<\nactual: >%d<", \ - (int)(threshold), (int)(actual)) - -#define TEST_ASSERT_LESS_THAN_size_t_MESSAGE(threshold, actual, msg) \ - zassert_true((size_t)(threshold) > (size_t)(actual), "expect: < >%d<\nactual: >%d<\n%s", \ - (int)(threshold), (int)(actual), msg) - -#define TEST_ASSERT_LESS_THAN_UINT16(threshold, actual) \ - zassert_true((uint16_t)(threshold) > (uint16_t)(actual), "expect: < >%u<\nactual: >%u<", \ - (unsigned)(uint16_t)(threshold), (unsigned)(uint16_t)(actual)) - -#define TEST_ASSERT_LESS_OR_EQUAL_UINT8(threshold, actual) \ - zassert_true((uint8_t)(threshold) >= (uint8_t)(actual), "expect: <= >%u<\nactual: >%u<", \ - (unsigned)(uint8_t)(threshold), (unsigned)(uint8_t)(actual)) - -#define TEST_ASSERT_LESS_OR_EQUAL_size_t(threshold, actual) \ - zassert_true((size_t)(threshold) >= (size_t)(actual), "expect: <= >%d<\nactual: >%d<", \ - (int)(threshold), (int)(actual)) - -#define TEST_ASSERT_LESS_OR_EQUAL_size_t_MESSAGE(threshold, actual, msg) \ - zassert_true((size_t)(threshold) >= (size_t)(actual), "expect: <= >%d<\nactual: >%d<\n%s", \ - (int)(threshold), (int)(actual), msg) - -#define TEST_ASSERT_NOT_EQUAL(expected, actual) \ - zassert_not_equal(expected, actual, "expect: ! >%d<\nactual: >%d<", \ - (int)(expected), (int)(actual)) - -#define TEST_ASSERT_NOT_EQUAL_PTR(expected, actual) \ - zassert_not_equal((void *)expected, (void *)actual, "expect: ! >%d<\nactual: >", \ - (int)(expected), (int)(actual)) - -#define TEST_ASSERT_NOT_EQUAL_PTR_MESSAGE(expected, actual, msg) \ - zassert_not_equal((void *)expected, (void *)actual, "expect: ! >%d<\nactual: >%d<\n%s", \ - (int)(expected), (int)(actual), msg) - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* ZTEST_UNITY_H_ */ From 2284b30d5eae00dc590a0ca06e060ba25de80b71 Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Sun, 19 Dec 2021 12:55:24 +0100 Subject: [PATCH 35/37] examples - adapt to new core context interface Signed-off-by: Bobby Noelte --- examples/basic/CMakeLists.txt | 21 - examples/basic/main.c | 31 +- examples/interactive/CMakeLists.txt | 22 - examples/interactive/linenoise/.gitignore | 3 - examples/interactive/linenoise/LICENSE | 25 - examples/interactive/linenoise/Makefile | 7 - .../interactive/linenoise/README.markdown | 247 ---- examples/interactive/linenoise/example.c | 79 -- examples/interactive/linenoise/linenoise.c | 1225 ----------------- examples/interactive/linenoise/linenoise.h | 75 - examples/interactive/main.cpp | 83 +- examples/interactive/things.c | 177 +++ examples/interactive/things.h | 37 + 13 files changed, 243 insertions(+), 1789 deletions(-) delete mode 100644 examples/basic/CMakeLists.txt delete mode 100644 examples/interactive/CMakeLists.txt delete mode 100644 examples/interactive/linenoise/.gitignore delete mode 100644 examples/interactive/linenoise/LICENSE delete mode 100644 examples/interactive/linenoise/Makefile delete mode 100644 examples/interactive/linenoise/README.markdown delete mode 100644 examples/interactive/linenoise/example.c delete mode 100644 examples/interactive/linenoise/linenoise.c delete mode 100644 examples/interactive/linenoise/linenoise.h create mode 100644 examples/interactive/things.c create mode 100644 examples/interactive/things.h diff --git a/examples/basic/CMakeLists.txt b/examples/basic/CMakeLists.txt deleted file mode 100644 index 07b9cba..0000000 --- a/examples/basic/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) 2021 Martin Jäger / Libre Solar -# SPDX-License-Identifier: Apache-2.0 - -cmake_minimum_required(VERSION 3.10) - -project(basic_thingset_example) - -get_filename_component(THINGSET_BASE ${CMAKE_CURRENT_SOURCE_DIR}/../../.. DIRECTORY) - -include_directories(${THINGSET_BASE}/src) - -add_executable(sample - main.c -) - -add_library(ts STATIC "") -add_subdirectory(${THINGSET_BASE}/src build/ts) -target_link_libraries(sample ts) - -# for math.h functions -target_link_libraries(sample m) diff --git a/examples/basic/main.c b/examples/basic/main.c index 49e4c78..e4f7a9c 100644 --- a/examples/basic/main.c +++ b/examples/basic/main.c @@ -8,11 +8,8 @@ #include -#ifndef ARRAY_SIZE -#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) -#endif - /* variables to be exposed via ThingSet */ +thingset_uid_t uid = 0x1111111111111111ULL; char device_id[] = "ABCD1234"; bool enable_switch = false; float ambient_temp = 22.3; @@ -23,8 +20,8 @@ void reset(void) printf("Reset function called!\n"); } -/* the ThingSet object definitions */ -static struct ts_data_object data_objects[] = { +/* The ThingSet data objects database */ +THINGSET_CORE_DATABASE_DEFINE(uid, TS_ITEM_STRING(0x1D, "DeviceID", device_id, sizeof(device_id), TS_ID_ROOT, TS_ANY_R, 0), @@ -37,15 +34,21 @@ static struct ts_data_object data_objects[] = { TS_FUNCTION(0xE1, "x-reset", &reset, TS_ID_ROOT, TS_ANY_RW), -}; +); + +THINGSET_CORE_DEFINE(); + +#if CONFIG_THINGSET_ZEPHYR +void main(void) +#else int main(void) +#endif { uint8_t response[100]; - struct ts_context ts; - /* initialize ThingSet context and assign data objects */ - ts_init(&ts, data_objects, ARRAY_SIZE(data_objects)); + /* Initialize core variant ThingSet context and assign data objects */ + thingset_core_init(); /* * Below requests are for demonstration of the ThingSet process function only. They would @@ -54,18 +57,20 @@ int main(void) const char request1[] = "= {\"HeaterEnable\":true}"; printf("Request: %s\n", request1); - ts_process(&ts, (uint8_t *)request1, strlen(request1), response, sizeof(response)); + thingset_core_process((uint8_t *)request1, strlen(request1), response, sizeof(response)); printf("Response: %s\n\n", (char *)response); const char request2[] = "!x-reset"; printf("Request: %s\n", request2); - ts_process(&ts, (uint8_t *)request2, strlen(request2), response, sizeof(response)); + thingset_core_process((uint8_t *)request2, strlen(request2), response, sizeof(response)); printf("Response: %s\n\n", (char *)response); const char request3[] = "?"; printf("Request: %s\n", request3); - ts_process(&ts, (uint8_t *)request3, strlen(request3), response, sizeof(response)); + thingset_core_process((uint8_t *)request3, strlen(request3), response, sizeof(response)); printf("Response: %s\n\n", (char *)response); +#if !CONFIG_THINGSET_ZEPHYR return 0; +#endif } diff --git a/examples/interactive/CMakeLists.txt b/examples/interactive/CMakeLists.txt deleted file mode 100644 index 6d5acca..0000000 --- a/examples/interactive/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) 2021 Martin Jäger / Libre Solar -# SPDX-License-Identifier: Apache-2.0 - -cmake_minimum_required(VERSION 3.10) - -project(basic_thingset_example) - -get_filename_component(THINGSET_BASE ${CMAKE_CURRENT_LIST_DIR}/../../.. DIRECTORY) - -include_directories(${THINGSET_BASE}/src) -include_directories(${CMAKE_CURRENT_LIST_DIR}/linenoise) - -set(CMAKE_CXX_FLAGS -pthread) - -add_executable(sample - main.cpp - linenoise/linenoise.c -) - -add_library(ts STATIC "") -add_subdirectory(${THINGSET_BASE}/src build/ts) -target_link_libraries(sample ts) diff --git a/examples/interactive/linenoise/.gitignore b/examples/interactive/linenoise/.gitignore deleted file mode 100644 index 7ab7825..0000000 --- a/examples/interactive/linenoise/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -linenoise_example -*.dSYM -history.txt diff --git a/examples/interactive/linenoise/LICENSE b/examples/interactive/linenoise/LICENSE deleted file mode 100644 index 18e8148..0000000 --- a/examples/interactive/linenoise/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2010-2014, Salvatore Sanfilippo -Copyright (c) 2010-2013, Pieter Noordhuis - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/examples/interactive/linenoise/Makefile b/examples/interactive/linenoise/Makefile deleted file mode 100644 index a285410..0000000 --- a/examples/interactive/linenoise/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -linenoise_example: linenoise.h linenoise.c - -linenoise_example: linenoise.c example.c - $(CC) -Wall -W -Os -g -o linenoise_example linenoise.c example.c - -clean: - rm -f linenoise_example diff --git a/examples/interactive/linenoise/README.markdown b/examples/interactive/linenoise/README.markdown deleted file mode 100644 index 60e9c2b..0000000 --- a/examples/interactive/linenoise/README.markdown +++ /dev/null @@ -1,247 +0,0 @@ -# Linenoise - -A minimal, zero-config, BSD licensed, readline replacement used in Redis, -MongoDB, and Android. - -* Single and multi line editing mode with the usual key bindings implemented. -* History handling. -* Completion. -* Hints (suggestions at the right of the prompt as you type). -* About 1,100 lines of BSD license source code. -* Only uses a subset of VT100 escapes (ANSI.SYS compatible). - -## Can a line editing library be 20k lines of code? - -Line editing with some support for history is a really important feature for command line utilities. Instead of retyping almost the same stuff again and again it's just much better to hit the up arrow and edit on syntax errors, or in order to try a slightly different command. But apparently code dealing with terminals is some sort of Black Magic: readline is 30k lines of code, libedit 20k. Is it reasonable to link small utilities to huge libraries just to get a minimal support for line editing? - -So what usually happens is either: - - * Large programs with configure scripts disabling line editing if readline is not present in the system, or not supporting it at all since readline is GPL licensed and libedit (the BSD clone) is not as known and available as readline is (Real world example of this problem: Tclsh). - * Smaller programs not using a configure script not supporting line editing at all (A problem we had with Redis-cli for instance). - -The result is a pollution of binaries without line editing support. - -So I spent more or less two hours doing a reality check resulting in this little library: is it *really* needed for a line editing library to be 20k lines of code? Apparently not, it is possibe to get a very small, zero configuration, trivial to embed library, that solves the problem. Smaller programs will just include this, supporting line editing out of the box. Larger programs may use this little library or just checking with configure if readline/libedit is available and resorting to Linenoise if not. - -## Terminals, in 2010. - -Apparently almost every terminal you can happen to use today has some kind of support for basic VT100 escape sequences. So I tried to write a lib using just very basic VT100 features. The resulting library appears to work everywhere I tried to use it, and now can work even on ANSI.SYS compatible terminals, since no -VT220 specific sequences are used anymore. - -The library is currently about 1100 lines of code. In order to use it in your project just look at the *example.c* file in the source distribution, it is trivial. Linenoise is BSD code, so you can use both in free software and commercial software. - -## Tested with... - - * Linux text only console ($TERM = linux) - * Linux KDE terminal application ($TERM = xterm) - * Linux xterm ($TERM = xterm) - * Linux Buildroot ($TERM = vt100) - * Mac OS X iTerm ($TERM = xterm) - * Mac OS X default Terminal.app ($TERM = xterm) - * OpenBSD 4.5 through an OSX Terminal.app ($TERM = screen) - * IBM AIX 6.1 - * FreeBSD xterm ($TERM = xterm) - * ANSI.SYS - * Emacs comint mode ($TERM = dumb) - -Please test it everywhere you can and report back! - -## Let's push this forward! - -Patches should be provided in the respect of Linenoise sensibility for small -easy to understand code. - -Send feedbacks to antirez at gmail - -# The API - -Linenoise is very easy to use, and reading the example shipped with the -library should get you up to speed ASAP. Here is a list of API calls -and how to use them. - - char *linenoise(const char *prompt); - -This is the main Linenoise call: it shows the user a prompt with line editing -and history capabilities. The prompt you specify is used as a prompt, that is, -it will be printed to the left of the cursor. The library returns a buffer -with the line composed by the user, or NULL on end of file or when there -is an out of memory condition. - -When a tty is detected (the user is actually typing into a terminal session) -the maximum editable line length is `LINENOISE_MAX_LINE`. When instead the -standard input is not a tty, which happens every time you redirect a file -to a program, or use it in an Unix pipeline, there are no limits to the -length of the line that can be returned. - -The returned line should be freed with the `free()` standard system call. -However sometimes it could happen that your program uses a different dynamic -allocation library, so you may also used `linenoiseFree` to make sure the -line is freed with the same allocator it was created. - -The canonical loop used by a program using Linenoise will be something like -this: - - while((line = linenoise("hello> ")) != NULL) { - printf("You wrote: %s\n", line); - linenoiseFree(line); /* Or just free(line) if you use libc malloc. */ - } - -## Single line VS multi line editing - -By default, Linenoise uses single line editing, that is, a single row on the -screen will be used, and as the user types more, the text will scroll towards -left to make room. This works if your program is one where the user is -unlikely to write a lot of text, otherwise multi line editing, where multiple -screens rows are used, can be a lot more comfortable. - -In order to enable multi line editing use the following API call: - - linenoiseSetMultiLine(1); - -You can disable it using `0` as argument. - -## History - -Linenoise supporst history, so that the user does not have to retype -again and again the same things, but can use the down and up arrows in order -to search and re-edit already inserted lines of text. - -The followings are the history API calls: - - int linenoiseHistoryAdd(const char *line); - int linenoiseHistorySetMaxLen(int len); - int linenoiseHistorySave(const char *filename); - int linenoiseHistoryLoad(const char *filename); - -Use `linenoiseHistoryAdd` every time you want to add a new element -to the top of the history (it will be the first the user will see when -using the up arrow). - -Note that for history to work, you have to set a length for the history -(which is zero by default, so history will be disabled if you don't set -a proper one). This is accomplished using the `linenoiseHistorySetMaxLen` -function. - -Linenoise has direct support for persisting the history into an history -file. The functions `linenoiseHistorySave` and `linenoiseHistoryLoad` do -just that. Both functions return -1 on error and 0 on success. - -## Mask mode - -Sometimes it is useful to allow the user to type passwords or other -secrets that should not be displayed. For such situations linenoise supports -a "mask mode" that will just replace the characters the user is typing -with `*` characters, like in the following example: - - $ ./linenoise_example - hello> get mykey - echo: 'get mykey' - hello> /mask - hello> ********* - -You can enable and disable mask mode using the following two functions: - - void linenoiseMaskModeEnable(void); - void linenoiseMaskModeDisable(void); - -## Completion - -Linenoise supports completion, which is the ability to complete the user -input when she or he presses the `` key. - -In order to use completion, you need to register a completion callback, which -is called every time the user presses ``. Your callback will return a -list of items that are completions for the current string. - -The following is an example of registering a completion callback: - - linenoiseSetCompletionCallback(completion); - -The completion must be a function returning `void` and getting as input -a `const char` pointer, which is the line the user has typed so far, and -a `linenoiseCompletions` object pointer, which is used as argument of -`linenoiseAddCompletion` in order to add completions inside the callback. -An example will make it more clear: - - void completion(const char *buf, linenoiseCompletions *lc) { - if (buf[0] == 'h') { - linenoiseAddCompletion(lc,"hello"); - linenoiseAddCompletion(lc,"hello there"); - } - } - -Basically in your completion callback, you inspect the input, and return -a list of items that are good completions by using `linenoiseAddCompletion`. - -If you want to test the completion feature, compile the example program -with `make`, run it, type `h` and press ``. - -## Hints - -Linenoise has a feature called *hints* which is very useful when you -use Linenoise in order to implement a REPL (Read Eval Print Loop) for -a program that accepts commands and arguments, but may also be useful in -other conditions. - -The feature shows, on the right of the cursor, as the user types, hints that -may be useful. The hints can be displayed using a different color compared -to the color the user is typing, and can also be bold. - -For example as the user starts to type `"git remote add"`, with hints it's -possible to show on the right of the prompt a string ` `. - -The feature works similarly to the history feature, using a callback. -To register the callback we use: - - linenoiseSetHintsCallback(hints); - -The callback itself is implemented like this: - - char *hints(const char *buf, int *color, int *bold) { - if (!strcasecmp(buf,"git remote add")) { - *color = 35; - *bold = 0; - return " "; - } - return NULL; - } - -The callback function returns the string that should be displayed or NULL -if no hint is available for the text the user currently typed. The returned -string will be trimmed as needed depending on the number of columns available -on the screen. - -It is possible to return a string allocated in dynamic way, by also registering -a function to deallocate the hint string once used: - - void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *); - -The free hint callback will just receive the pointer and free the string -as needed (depending on how the hits callback allocated it). - -As you can see in the example above, a `color` (in xterm color terminal codes) -can be provided together with a `bold` attribute. If no color is set, the -current terminal foreground color is used. If no bold attribute is set, -non-bold text is printed. - -Color codes are: - - red = 31 - green = 32 - yellow = 33 - blue = 34 - magenta = 35 - cyan = 36 - white = 37; - -## Screen handling - -Sometimes you may want to clear the screen as a result of something the -user typed. You can do this by calling the following function: - - void linenoiseClearScreen(void); - -## Related projects - -* [Linenoise NG](https://github.com/arangodb/linenoise-ng) is a fork of Linenoise that aims to add more advanced features like UTF-8 support, Windows support and other features. Uses C++ instead of C as development language. -* [Linenoise-swift](https://github.com/andybest/linenoise-swift) is a reimplementation of Linenoise written in Swift. diff --git a/examples/interactive/linenoise/example.c b/examples/interactive/linenoise/example.c deleted file mode 100644 index 4bd7d46..0000000 --- a/examples/interactive/linenoise/example.c +++ /dev/null @@ -1,79 +0,0 @@ -#include -#include -#include -#include "linenoise.h" - - -void completion(const char *buf, linenoiseCompletions *lc) { - if (buf[0] == 'h') { - linenoiseAddCompletion(lc,"hello"); - linenoiseAddCompletion(lc,"hello there"); - } -} - -char *hints(const char *buf, int *color, int *bold) { - if (!strcasecmp(buf,"hello")) { - *color = 35; - *bold = 0; - return " World"; - } - return NULL; -} - -int main(int argc, char **argv) { - char *line; - char *prgname = argv[0]; - - /* Parse options, with --multiline we enable multi line editing. */ - while(argc > 1) { - argc--; - argv++; - if (!strcmp(*argv,"--multiline")) { - linenoiseSetMultiLine(1); - printf("Multi-line mode enabled.\n"); - } else if (!strcmp(*argv,"--keycodes")) { - linenoisePrintKeyCodes(); - exit(0); - } else { - fprintf(stderr, "Usage: %s [--multiline] [--keycodes]\n", prgname); - exit(1); - } - } - - /* Set the completion callback. This will be called every time the - * user uses the key. */ - linenoiseSetCompletionCallback(completion); - linenoiseSetHintsCallback(hints); - - /* Load history from file. The history file is just a plain text file - * where entries are separated by newlines. */ - linenoiseHistoryLoad("history.txt"); /* Load the history at startup */ - - /* Now this is the main loop of the typical linenoise-based application. - * The call to linenoise() will block as long as the user types something - * and presses enter. - * - * The typed string is returned as a malloc() allocated string by - * linenoise, so the user needs to free() it. */ - - while((line = linenoise("hello> ")) != NULL) { - /* Do something with the string. */ - if (line[0] != '\0' && line[0] != '/') { - printf("echo: '%s'\n", line); - linenoiseHistoryAdd(line); /* Add to the history. */ - linenoiseHistorySave("history.txt"); /* Save the history on disk. */ - } else if (!strncmp(line,"/historylen",11)) { - /* The "/historylen" command will change the history len. */ - int len = atoi(line+11); - linenoiseHistorySetMaxLen(len); - } else if (!strncmp(line, "/mask", 5)) { - linenoiseMaskModeEnable(); - } else if (!strncmp(line, "/unmask", 7)) { - linenoiseMaskModeDisable(); - } else if (line[0] == '/') { - printf("Unreconized command: %s\n", line); - } - free(line); - } - return 0; -} diff --git a/examples/interactive/linenoise/linenoise.c b/examples/interactive/linenoise/linenoise.c deleted file mode 100644 index cfe51e7..0000000 --- a/examples/interactive/linenoise/linenoise.c +++ /dev/null @@ -1,1225 +0,0 @@ -/* linenoise.c -- guerrilla line editing library against the idea that a - * line editing lib needs to be 20,000 lines of C code. - * - * You can find the latest source code at: - * - * http://github.com/antirez/linenoise - * - * Does a number of crazy assumptions that happen to be true in 99.9999% of - * the 2010 UNIX computers around. - * - * ------------------------------------------------------------------------ - * - * Copyright (c) 2010-2016, Salvatore Sanfilippo - * Copyright (c) 2010-2013, Pieter Noordhuis - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * ------------------------------------------------------------------------ - * - * References: - * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html - * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html - * - * Todo list: - * - Filter bogus Ctrl+ combinations. - * - Win32 support - * - * Bloat: - * - History search like Ctrl+r in readline? - * - * List of escape sequences used by this program, we do everything just - * with three sequences. In order to be so cheap we may have some - * flickering effect with some slow terminal, but the lesser sequences - * the more compatible. - * - * EL (Erase Line) - * Sequence: ESC [ n K - * Effect: if n is 0 or missing, clear from cursor to end of line - * Effect: if n is 1, clear from beginning of line to cursor - * Effect: if n is 2, clear entire line - * - * CUF (CUrsor Forward) - * Sequence: ESC [ n C - * Effect: moves cursor forward n chars - * - * CUB (CUrsor Backward) - * Sequence: ESC [ n D - * Effect: moves cursor backward n chars - * - * The following is used to get the terminal width if getting - * the width with the TIOCGWINSZ ioctl fails - * - * DSR (Device Status Report) - * Sequence: ESC [ 6 n - * Effect: reports the current cusor position as ESC [ n ; m R - * where n is the row and m is the column - * - * When multi line mode is enabled, we also use an additional escape - * sequence. However multi line editing is disabled by default. - * - * CUU (Cursor Up) - * Sequence: ESC [ n A - * Effect: moves cursor up of n chars. - * - * CUD (Cursor Down) - * Sequence: ESC [ n B - * Effect: moves cursor down of n chars. - * - * When linenoiseClearScreen() is called, two additional escape sequences - * are used in order to clear the screen and position the cursor at home - * position. - * - * CUP (Cursor position) - * Sequence: ESC [ H - * Effect: moves the cursor to upper left corner - * - * ED (Erase display) - * Sequence: ESC [ 2 J - * Effect: clear the whole screen - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "linenoise.h" - -#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 -#define LINENOISE_MAX_LINE 4096 -static char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; -static linenoiseCompletionCallback *completionCallback = NULL; -static linenoiseHintsCallback *hintsCallback = NULL; -static linenoiseFreeHintsCallback *freeHintsCallback = NULL; - -static struct termios orig_termios; /* In order to restore at exit.*/ -static int maskmode = 0; /* Show "***" instead of input. For passwords. */ -static int rawmode = 0; /* For atexit() function to check if restore is needed*/ -static int mlmode = 0; /* Multi line mode. Default is single line. */ -static int atexit_registered = 0; /* Register atexit just 1 time. */ -static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; -static int history_len = 0; -static char **history = NULL; - -/* The linenoiseState structure represents the state during line editing. - * We pass this state to functions implementing specific editing - * functionalities. */ -struct linenoiseState { - int ifd; /* Terminal stdin file descriptor. */ - int ofd; /* Terminal stdout file descriptor. */ - char *buf; /* Edited line buffer. */ - size_t buflen; /* Edited line buffer size. */ - const char *prompt; /* Prompt to display. */ - size_t plen; /* Prompt length. */ - size_t pos; /* Current cursor position. */ - size_t oldpos; /* Previous refresh cursor position. */ - size_t len; /* Current edited line length. */ - size_t cols; /* Number of columns in terminal. */ - size_t maxrows; /* Maximum num of rows used so far (multiline mode) */ - int history_index; /* The history index we are currently editing. */ -}; - -enum KEY_ACTION{ - KEY_NULL = 0, /* NULL */ - CTRL_A = 1, /* Ctrl+a */ - CTRL_B = 2, /* Ctrl-b */ - CTRL_C = 3, /* Ctrl-c */ - CTRL_D = 4, /* Ctrl-d */ - CTRL_E = 5, /* Ctrl-e */ - CTRL_F = 6, /* Ctrl-f */ - CTRL_H = 8, /* Ctrl-h */ - TAB = 9, /* Tab */ - CTRL_K = 11, /* Ctrl+k */ - CTRL_L = 12, /* Ctrl+l */ - ENTER = 13, /* Enter */ - CTRL_N = 14, /* Ctrl-n */ - CTRL_P = 16, /* Ctrl-p */ - CTRL_T = 20, /* Ctrl-t */ - CTRL_U = 21, /* Ctrl+u */ - CTRL_W = 23, /* Ctrl+w */ - ESC = 27, /* Escape */ - BACKSPACE = 127 /* Backspace */ -}; - -static void linenoiseAtExit(void); -int linenoiseHistoryAdd(const char *line); -static void refreshLine(struct linenoiseState *l); - -/* Debugging macro. */ -#if 0 -FILE *lndebug_fp = NULL; -#define lndebug(...) \ - do { \ - if (lndebug_fp == NULL) { \ - lndebug_fp = fopen("/tmp/lndebug.txt","a"); \ - fprintf(lndebug_fp, \ - "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \ - (int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \ - (int)l->maxrows,old_rows); \ - } \ - fprintf(lndebug_fp, ", " __VA_ARGS__); \ - fflush(lndebug_fp); \ - } while (0) -#else -#define lndebug(fmt, ...) -#endif - -/* ======================= Low level terminal handling ====================== */ - -/* Enable "mask mode". When it is enabled, instead of the input that - * the user is typing, the terminal will just display a corresponding - * number of asterisks, like "****". This is useful for passwords and other - * secrets that should not be displayed. */ -void linenoiseMaskModeEnable(void) { - maskmode = 1; -} - -/* Disable mask mode. */ -void linenoiseMaskModeDisable(void) { - maskmode = 0; -} - -/* Set if to use or not the multi line mode. */ -void linenoiseSetMultiLine(int ml) { - mlmode = ml; -} - -/* Return true if the terminal name is in the list of terminals we know are - * not able to understand basic escape sequences. */ -static int isUnsupportedTerm(void) { - char *term = getenv("TERM"); - int j; - - if (term == NULL) return 0; - for (j = 0; unsupported_term[j]; j++) - if (!strcasecmp(term,unsupported_term[j])) return 1; - return 0; -} - -/* Raw mode: 1960 magic shit. */ -static int enableRawMode(int fd) { - struct termios raw; - - if (!isatty(STDIN_FILENO)) goto fatal; - if (!atexit_registered) { - atexit(linenoiseAtExit); - atexit_registered = 1; - } - if (tcgetattr(fd,&orig_termios) == -1) goto fatal; - - raw = orig_termios; /* modify the original mode */ - /* input modes: no break, no CR to NL, no parity check, no strip char, - * no start/stop output control. */ - raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); - /* output modes - disable post processing */ - raw.c_oflag &= ~(OPOST); - /* control modes - set 8 bit chars */ - raw.c_cflag |= (CS8); - /* local modes - choing off, canonical off, no extended functions, - * no signal chars (^Z,^C) */ - raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); - /* control chars - set return condition: min number of bytes and timer. - * We want read to return every single byte, without timeout. */ - raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ - - /* put terminal in raw mode after flushing */ - if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal; - rawmode = 1; - return 0; - -fatal: - errno = ENOTTY; - return -1; -} - -static void disableRawMode(int fd) { - /* Don't even check the return value as it's too late. */ - if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1) - rawmode = 0; -} - -/* Use the ESC [6n escape sequence to query the horizontal cursor position - * and return it. On error -1 is returned, on success the position of the - * cursor. */ -static int getCursorPosition(int ifd, int ofd) { - char buf[32]; - int cols, rows; - unsigned int i = 0; - - /* Report cursor location */ - if (write(ofd, "\x1b[6n", 4) != 4) return -1; - - /* Read the response: ESC [ rows ; cols R */ - while (i < sizeof(buf)-1) { - if (read(ifd,buf+i,1) != 1) break; - if (buf[i] == 'R') break; - i++; - } - buf[i] = '\0'; - - /* Parse it. */ - if (buf[0] != ESC || buf[1] != '[') return -1; - if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1; - return cols; -} - -/* Try to get the number of columns in the current terminal, or assume 80 - * if it fails. */ -static int getColumns(int ifd, int ofd) { - struct winsize ws; - - if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { - /* ioctl() failed. Try to query the terminal itself. */ - int start, cols; - - /* Get the initial position so we can restore it later. */ - start = getCursorPosition(ifd,ofd); - if (start == -1) goto failed; - - /* Go to right margin and get position. */ - if (write(ofd,"\x1b[999C",6) != 6) goto failed; - cols = getCursorPosition(ifd,ofd); - if (cols == -1) goto failed; - - /* Restore position. */ - if (cols > start) { - char seq[32]; - snprintf(seq,32,"\x1b[%dD",cols-start); - if (write(ofd,seq,strlen(seq)) == -1) { - /* Can't recover... */ - } - } - return cols; - } else { - return ws.ws_col; - } - -failed: - return 80; -} - -/* Clear the screen. Used to handle ctrl+l */ -void linenoiseClearScreen(void) { - if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) { - /* nothing to do, just to avoid warning. */ - } -} - -/* Beep, used for completion when there is nothing to complete or when all - * the choices were already shown. */ -static void linenoiseBeep(void) { - fprintf(stderr, "\x7"); - fflush(stderr); -} - -/* ============================== Completion ================================ */ - -/* Free a list of completion option populated by linenoiseAddCompletion(). */ -static void freeCompletions(linenoiseCompletions *lc) { - size_t i; - for (i = 0; i < lc->len; i++) - free(lc->cvec[i]); - if (lc->cvec != NULL) - free(lc->cvec); -} - -/* This is an helper function for linenoiseEdit() and is called when the - * user types the key in order to complete the string currently in the - * input. - * - * The state of the editing is encapsulated into the pointed linenoiseState - * structure as described in the structure definition. */ -static int completeLine(struct linenoiseState *ls) { - linenoiseCompletions lc = { 0, NULL }; - int nread, nwritten; - char c = 0; - - completionCallback(ls->buf,&lc); - if (lc.len == 0) { - linenoiseBeep(); - } else { - size_t stop = 0, i = 0; - - while(!stop) { - /* Show completion or original buffer */ - if (i < lc.len) { - struct linenoiseState saved = *ls; - - ls->len = ls->pos = strlen(lc.cvec[i]); - ls->buf = lc.cvec[i]; - refreshLine(ls); - ls->len = saved.len; - ls->pos = saved.pos; - ls->buf = saved.buf; - } else { - refreshLine(ls); - } - - nread = read(ls->ifd,&c,1); - if (nread <= 0) { - freeCompletions(&lc); - return -1; - } - - switch(c) { - case 9: /* tab */ - i = (i+1) % (lc.len+1); - if (i == lc.len) linenoiseBeep(); - break; - case 27: /* escape */ - /* Re-show original buffer */ - if (i < lc.len) refreshLine(ls); - stop = 1; - break; - default: - /* Update buffer and return */ - if (i < lc.len) { - nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]); - ls->len = ls->pos = nwritten; - } - stop = 1; - break; - } - } - } - - freeCompletions(&lc); - return c; /* Return last read character */ -} - -/* Register a callback function to be called for tab-completion. */ -void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) { - completionCallback = fn; -} - -/* Register a hits function to be called to show hits to the user at the - * right of the prompt. */ -void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) { - hintsCallback = fn; -} - -/* Register a function to free the hints returned by the hints callback - * registered with linenoiseSetHintsCallback(). */ -void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) { - freeHintsCallback = fn; -} - -/* This function is used by the callback function registered by the user - * in order to add completion options given the input string when the - * user typed . See the example.c source code for a very easy to - * understand example. */ -void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { - size_t len = strlen(str); - char *copy, **cvec; - - copy = malloc(len+1); - if (copy == NULL) return; - memcpy(copy,str,len+1); - cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1)); - if (cvec == NULL) { - free(copy); - return; - } - lc->cvec = cvec; - lc->cvec[lc->len++] = copy; -} - -/* =========================== Line editing ================================= */ - -/* We define a very simple "append buffer" structure, that is an heap - * allocated string where we can append to. This is useful in order to - * write all the escape sequences in a buffer and flush them to the standard - * output in a single call, to avoid flickering effects. */ -struct abuf { - char *b; - int len; -}; - -static void abInit(struct abuf *ab) { - ab->b = NULL; - ab->len = 0; -} - -static void abAppend(struct abuf *ab, const char *s, int len) { - char *new = realloc(ab->b,ab->len+len); - - if (new == NULL) return; - memcpy(new+ab->len,s,len); - ab->b = new; - ab->len += len; -} - -static void abFree(struct abuf *ab) { - free(ab->b); -} - -/* Helper of refreshSingleLine() and refreshMultiLine() to show hints - * to the right of the prompt. */ -void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) { - char seq[64]; - if (hintsCallback && plen+l->len < l->cols) { - int color = -1, bold = 0; - char *hint = hintsCallback(l->buf,&color,&bold); - if (hint) { - int hintlen = strlen(hint); - int hintmaxlen = l->cols-(plen+l->len); - if (hintlen > hintmaxlen) hintlen = hintmaxlen; - if (bold == 1 && color == -1) color = 37; - if (color != -1 || bold != 0) - snprintf(seq,64,"\033[%d;%d;49m",bold,color); - else - seq[0] = '\0'; - abAppend(ab,seq,strlen(seq)); - abAppend(ab,hint,hintlen); - if (color != -1 || bold != 0) - abAppend(ab,"\033[0m",4); - /* Call the function to free the hint returned. */ - if (freeHintsCallback) freeHintsCallback(hint); - } - } -} - -/* Single line low level line refresh. - * - * Rewrite the currently edited line accordingly to the buffer content, - * cursor position, and number of columns of the terminal. */ -static void refreshSingleLine(struct linenoiseState *l) { - char seq[64]; - size_t plen = strlen(l->prompt); - int fd = l->ofd; - char *buf = l->buf; - size_t len = l->len; - size_t pos = l->pos; - struct abuf ab; - - while((plen+pos) >= l->cols) { - buf++; - len--; - pos--; - } - while (plen+len > l->cols) { - len--; - } - - abInit(&ab); - /* Cursor to left edge */ - snprintf(seq,64,"\r"); - abAppend(&ab,seq,strlen(seq)); - /* Write the prompt and the current buffer content */ - abAppend(&ab,l->prompt,strlen(l->prompt)); - if (maskmode == 1) { - while (len--) abAppend(&ab,"*",1); - } else { - abAppend(&ab,buf,len); - } - /* Show hits if any. */ - refreshShowHints(&ab,l,plen); - /* Erase to right */ - snprintf(seq,64,"\x1b[0K"); - abAppend(&ab,seq,strlen(seq)); - /* Move cursor to original position. */ - snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen)); - abAppend(&ab,seq,strlen(seq)); - if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ - abFree(&ab); -} - -/* Multi line low level line refresh. - * - * Rewrite the currently edited line accordingly to the buffer content, - * cursor position, and number of columns of the terminal. */ -static void refreshMultiLine(struct linenoiseState *l) { - char seq[64]; - int plen = strlen(l->prompt); - int rows = (plen+l->len+l->cols-1)/l->cols; /* rows used by current buf. */ - int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */ - int rpos2; /* rpos after refresh. */ - int col; /* colum position, zero-based. */ - int old_rows = l->maxrows; - int fd = l->ofd, j; - struct abuf ab; - - /* Update maxrows if needed. */ - if (rows > (int)l->maxrows) l->maxrows = rows; - - /* First step: clear all the lines used before. To do so start by - * going to the last row. */ - abInit(&ab); - if (old_rows-rpos > 0) { - lndebug("go down %d", old_rows-rpos); - snprintf(seq,64,"\x1b[%dB", old_rows-rpos); - abAppend(&ab,seq,strlen(seq)); - } - - /* Now for every row clear it, go up. */ - for (j = 0; j < old_rows-1; j++) { - lndebug("clear+up"); - snprintf(seq,64,"\r\x1b[0K\x1b[1A"); - abAppend(&ab,seq,strlen(seq)); - } - - /* Clean the top line. */ - lndebug("clear"); - snprintf(seq,64,"\r\x1b[0K"); - abAppend(&ab,seq,strlen(seq)); - - /* Write the prompt and the current buffer content */ - abAppend(&ab,l->prompt,strlen(l->prompt)); - if (maskmode == 1) { - unsigned int i; - for (i = 0; i < l->len; i++) abAppend(&ab,"*",1); - } else { - abAppend(&ab,l->buf,l->len); - } - - /* Show hits if any. */ - refreshShowHints(&ab,l,plen); - - /* If we are at the very end of the screen with our prompt, we need to - * emit a newline and move the prompt to the first column. */ - if (l->pos && - l->pos == l->len && - (l->pos+plen) % l->cols == 0) - { - lndebug(""); - abAppend(&ab,"\n",1); - snprintf(seq,64,"\r"); - abAppend(&ab,seq,strlen(seq)); - rows++; - if (rows > (int)l->maxrows) l->maxrows = rows; - } - - /* Move cursor to right position. */ - rpos2 = (plen+l->pos+l->cols)/l->cols; /* current cursor relative row. */ - lndebug("rpos2 %d", rpos2); - - /* Go up till we reach the expected positon. */ - if (rows-rpos2 > 0) { - lndebug("go-up %d", rows-rpos2); - snprintf(seq,64,"\x1b[%dA", rows-rpos2); - abAppend(&ab,seq,strlen(seq)); - } - - /* Set column. */ - col = (plen+(int)l->pos) % (int)l->cols; - lndebug("set col %d", 1+col); - if (col) - snprintf(seq,64,"\r\x1b[%dC", col); - else - snprintf(seq,64,"\r"); - abAppend(&ab,seq,strlen(seq)); - - lndebug("\n"); - l->oldpos = l->pos; - - if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ - abFree(&ab); -} - -/* Calls the two low level functions refreshSingleLine() or - * refreshMultiLine() according to the selected mode. */ -static void refreshLine(struct linenoiseState *l) { - if (mlmode) - refreshMultiLine(l); - else - refreshSingleLine(l); -} - -/* Insert the character 'c' at cursor current position. - * - * On error writing to the terminal -1 is returned, otherwise 0. */ -int linenoiseEditInsert(struct linenoiseState *l, char c) { - if (l->len < l->buflen) { - if (l->len == l->pos) { - l->buf[l->pos] = c; - l->pos++; - l->len++; - l->buf[l->len] = '\0'; - if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) { - /* Avoid a full update of the line in the - * trivial case. */ - char d = (maskmode==1) ? '*' : c; - if (write(l->ofd,&d,1) == -1) return -1; - } else { - refreshLine(l); - } - } else { - memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos); - l->buf[l->pos] = c; - l->len++; - l->pos++; - l->buf[l->len] = '\0'; - refreshLine(l); - } - } - return 0; -} - -/* Move cursor on the left. */ -void linenoiseEditMoveLeft(struct linenoiseState *l) { - if (l->pos > 0) { - l->pos--; - refreshLine(l); - } -} - -/* Move cursor on the right. */ -void linenoiseEditMoveRight(struct linenoiseState *l) { - if (l->pos != l->len) { - l->pos++; - refreshLine(l); - } -} - -/* Move cursor to the start of the line. */ -void linenoiseEditMoveHome(struct linenoiseState *l) { - if (l->pos != 0) { - l->pos = 0; - refreshLine(l); - } -} - -/* Move cursor to the end of the line. */ -void linenoiseEditMoveEnd(struct linenoiseState *l) { - if (l->pos != l->len) { - l->pos = l->len; - refreshLine(l); - } -} - -/* Substitute the currently edited line with the next or previous history - * entry as specified by 'dir'. */ -#define LINENOISE_HISTORY_NEXT 0 -#define LINENOISE_HISTORY_PREV 1 -void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) { - if (history_len > 1) { - /* Update the current history entry before to - * overwrite it with the next one. */ - free(history[history_len - 1 - l->history_index]); - history[history_len - 1 - l->history_index] = strdup(l->buf); - /* Show the new entry */ - l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; - if (l->history_index < 0) { - l->history_index = 0; - return; - } else if (l->history_index >= history_len) { - l->history_index = history_len-1; - return; - } - strncpy(l->buf,history[history_len - 1 - l->history_index],l->buflen); - l->buf[l->buflen-1] = '\0'; - l->len = l->pos = strlen(l->buf); - refreshLine(l); - } -} - -/* Delete the character at the right of the cursor without altering the cursor - * position. Basically this is what happens with the "Delete" keyboard key. */ -void linenoiseEditDelete(struct linenoiseState *l) { - if (l->len > 0 && l->pos < l->len) { - memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1); - l->len--; - l->buf[l->len] = '\0'; - refreshLine(l); - } -} - -/* Backspace implementation. */ -void linenoiseEditBackspace(struct linenoiseState *l) { - if (l->pos > 0 && l->len > 0) { - memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos); - l->pos--; - l->len--; - l->buf[l->len] = '\0'; - refreshLine(l); - } -} - -/* Delete the previosu word, maintaining the cursor at the start of the - * current word. */ -void linenoiseEditDeletePrevWord(struct linenoiseState *l) { - size_t old_pos = l->pos; - size_t diff; - - while (l->pos > 0 && l->buf[l->pos-1] == ' ') - l->pos--; - while (l->pos > 0 && l->buf[l->pos-1] != ' ') - l->pos--; - diff = old_pos - l->pos; - memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1); - l->len -= diff; - refreshLine(l); -} - -/* This function is the core of the line editing capability of linenoise. - * It expects 'fd' to be already in "raw mode" so that every key pressed - * will be returned ASAP to read(). - * - * The resulting string is put into 'buf' when the user type enter, or - * when ctrl+d is typed. - * - * The function returns the length of the current buffer. */ -static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) -{ - struct linenoiseState l; - - /* Populate the linenoise state that we pass to functions implementing - * specific editing functionalities. */ - l.ifd = stdin_fd; - l.ofd = stdout_fd; - l.buf = buf; - l.buflen = buflen; - l.prompt = prompt; - l.plen = strlen(prompt); - l.oldpos = l.pos = 0; - l.len = 0; - l.cols = getColumns(stdin_fd, stdout_fd); - l.maxrows = 0; - l.history_index = 0; - - /* Buffer starts empty. */ - l.buf[0] = '\0'; - l.buflen--; /* Make sure there is always space for the nulterm */ - - /* The latest history entry is always our current buffer, that - * initially is just an empty string. */ - linenoiseHistoryAdd(""); - - if (write(l.ofd,prompt,l.plen) == -1) return -1; - while(1) { - char c; - int nread; - char seq[3]; - - nread = read(l.ifd,&c,1); - if (nread <= 0) return l.len; - - /* Only autocomplete when the callback is set. It returns < 0 when - * there was an error reading from fd. Otherwise it will return the - * character that should be handled next. */ - if (c == 9 && completionCallback != NULL) { - c = completeLine(&l); - /* Return on errors */ - if (c < 0) return l.len; - /* Read next character when 0 */ - if (c == 0) continue; - } - - switch(c) { - case ENTER: /* enter */ - history_len--; - free(history[history_len]); - if (mlmode) linenoiseEditMoveEnd(&l); - if (hintsCallback) { - /* Force a refresh without hints to leave the previous - * line as the user typed it after a newline. */ - linenoiseHintsCallback *hc = hintsCallback; - hintsCallback = NULL; - refreshLine(&l); - hintsCallback = hc; - } - return (int)l.len; - case CTRL_C: /* ctrl-c */ - errno = EAGAIN; - return -1; - case BACKSPACE: /* backspace */ - case 8: /* ctrl-h */ - linenoiseEditBackspace(&l); - break; - case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the - line is empty, act as end-of-file. */ - if (l.len > 0) { - linenoiseEditDelete(&l); - } else { - history_len--; - free(history[history_len]); - return -1; - } - break; - case CTRL_T: /* ctrl-t, swaps current character with previous. */ - if (l.pos > 0 && l.pos < l.len) { - int aux = buf[l.pos-1]; - buf[l.pos-1] = buf[l.pos]; - buf[l.pos] = aux; - if (l.pos != l.len-1) l.pos++; - refreshLine(&l); - } - break; - case CTRL_B: /* ctrl-b */ - linenoiseEditMoveLeft(&l); - break; - case CTRL_F: /* ctrl-f */ - linenoiseEditMoveRight(&l); - break; - case CTRL_P: /* ctrl-p */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); - break; - case CTRL_N: /* ctrl-n */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); - break; - case ESC: /* escape sequence */ - /* Read the next two bytes representing the escape sequence. - * Use two calls to handle slow terminals returning the two - * chars at different times. */ - if (read(l.ifd,seq,1) == -1) break; - if (read(l.ifd,seq+1,1) == -1) break; - - /* ESC [ sequences. */ - if (seq[0] == '[') { - if (seq[1] >= '0' && seq[1] <= '9') { - /* Extended escape, read additional byte. */ - if (read(l.ifd,seq+2,1) == -1) break; - if (seq[2] == '~') { - switch(seq[1]) { - case '3': /* Delete key. */ - linenoiseEditDelete(&l); - break; - } - } - } else { - switch(seq[1]) { - case 'A': /* Up */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); - break; - case 'B': /* Down */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); - break; - case 'C': /* Right */ - linenoiseEditMoveRight(&l); - break; - case 'D': /* Left */ - linenoiseEditMoveLeft(&l); - break; - case 'H': /* Home */ - linenoiseEditMoveHome(&l); - break; - case 'F': /* End*/ - linenoiseEditMoveEnd(&l); - break; - } - } - } - - /* ESC O sequences. */ - else if (seq[0] == 'O') { - switch(seq[1]) { - case 'H': /* Home */ - linenoiseEditMoveHome(&l); - break; - case 'F': /* End*/ - linenoiseEditMoveEnd(&l); - break; - } - } - break; - default: - if (linenoiseEditInsert(&l,c)) return -1; - break; - case CTRL_U: /* Ctrl+u, delete the whole line. */ - buf[0] = '\0'; - l.pos = l.len = 0; - refreshLine(&l); - break; - case CTRL_K: /* Ctrl+k, delete from current to end of line. */ - buf[l.pos] = '\0'; - l.len = l.pos; - refreshLine(&l); - break; - case CTRL_A: /* Ctrl+a, go to the start of the line */ - linenoiseEditMoveHome(&l); - break; - case CTRL_E: /* ctrl+e, go to the end of the line */ - linenoiseEditMoveEnd(&l); - break; - case CTRL_L: /* ctrl+l, clear screen */ - linenoiseClearScreen(); - refreshLine(&l); - break; - case CTRL_W: /* ctrl+w, delete previous word */ - linenoiseEditDeletePrevWord(&l); - break; - } - } - return l.len; -} - -/* This special mode is used by linenoise in order to print scan codes - * on screen for debugging / development purposes. It is implemented - * by the linenoise_example program using the --keycodes option. */ -void linenoisePrintKeyCodes(void) { - char quit[4]; - - printf("Linenoise key codes debugging mode.\n" - "Press keys to see scan codes. Type 'quit' at any time to exit.\n"); - if (enableRawMode(STDIN_FILENO) == -1) return; - memset(quit,' ',4); - while(1) { - char c; - int nread; - - nread = read(STDIN_FILENO,&c,1); - if (nread <= 0) continue; - memmove(quit,quit+1,sizeof(quit)-1); /* shift string to left. */ - quit[sizeof(quit)-1] = c; /* Insert current char on the right. */ - if (memcmp(quit,"quit",sizeof(quit)) == 0) break; - - printf("'%c' %02x (%d) (type quit to exit)\n", - isprint(c) ? c : '?', (int)c, (int)c); - printf("\r"); /* Go left edge manually, we are in raw mode. */ - fflush(stdout); - } - disableRawMode(STDIN_FILENO); -} - -/* This function calls the line editing function linenoiseEdit() using - * the STDIN file descriptor set in raw mode. */ -static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) { - int count; - - if (buflen == 0) { - errno = EINVAL; - return -1; - } - - if (enableRawMode(STDIN_FILENO) == -1) return -1; - count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt); - disableRawMode(STDIN_FILENO); - printf("\n"); - return count; -} - -/* This function is called when linenoise() is called with the standard - * input file descriptor not attached to a TTY. So for example when the - * program using linenoise is called in pipe or with a file redirected - * to its standard input. In this case, we want to be able to return the - * line regardless of its length (by default we are limited to 4k). */ -static char *linenoiseNoTTY(void) { - char *line = NULL; - size_t len = 0, maxlen = 0; - - while(1) { - if (len == maxlen) { - if (maxlen == 0) maxlen = 16; - maxlen *= 2; - char *oldval = line; - line = realloc(line,maxlen); - if (line == NULL) { - if (oldval) free(oldval); - return NULL; - } - } - int c = fgetc(stdin); - if (c == EOF || c == '\n') { - if (c == EOF && len == 0) { - free(line); - return NULL; - } else { - line[len] = '\0'; - return line; - } - } else { - line[len] = c; - len++; - } - } -} - -/* The high level function that is the main API of the linenoise library. - * This function checks if the terminal has basic capabilities, just checking - * for a blacklist of stupid terminals, and later either calls the line - * editing function or uses dummy fgets() so that you will be able to type - * something even in the most desperate of the conditions. */ -char *linenoise(const char *prompt) { - char buf[LINENOISE_MAX_LINE]; - int count; - - if (!isatty(STDIN_FILENO)) { - /* Not a tty: read from file / pipe. In this mode we don't want any - * limit to the line size, so we call a function to handle that. */ - return linenoiseNoTTY(); - } else if (isUnsupportedTerm()) { - size_t len; - - printf("%s",prompt); - fflush(stdout); - if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL; - len = strlen(buf); - while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) { - len--; - buf[len] = '\0'; - } - return strdup(buf); - } else { - count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt); - if (count == -1) return NULL; - return strdup(buf); - } -} - -/* This is just a wrapper the user may want to call in order to make sure - * the linenoise returned buffer is freed with the same allocator it was - * created with. Useful when the main program is using an alternative - * allocator. */ -void linenoiseFree(void *ptr) { - free(ptr); -} - -/* ================================ History ================================= */ - -/* Free the history, but does not reset it. Only used when we have to - * exit() to avoid memory leaks are reported by valgrind & co. */ -static void freeHistory(void) { - if (history) { - int j; - - for (j = 0; j < history_len; j++) - free(history[j]); - free(history); - } -} - -/* At exit we'll try to fix the terminal to the initial conditions. */ -static void linenoiseAtExit(void) { - disableRawMode(STDIN_FILENO); - freeHistory(); -} - -/* This is the API call to add a new entry in the linenoise history. - * It uses a fixed array of char pointers that are shifted (memmoved) - * when the history max length is reached in order to remove the older - * entry and make room for the new one, so it is not exactly suitable for huge - * histories, but will work well for a few hundred of entries. - * - * Using a circular buffer is smarter, but a bit more complex to handle. */ -int linenoiseHistoryAdd(const char *line) { - char *linecopy; - - if (history_max_len == 0) return 0; - - /* Initialization on first call. */ - if (history == NULL) { - history = malloc(sizeof(char*)*history_max_len); - if (history == NULL) return 0; - memset(history,0,(sizeof(char*)*history_max_len)); - } - - /* Don't add duplicated lines. */ - if (history_len && !strcmp(history[history_len-1], line)) return 0; - - /* Add an heap allocated copy of the line in the history. - * If we reached the max length, remove the older line. */ - linecopy = strdup(line); - if (!linecopy) return 0; - if (history_len == history_max_len) { - free(history[0]); - memmove(history,history+1,sizeof(char*)*(history_max_len-1)); - history_len--; - } - history[history_len] = linecopy; - history_len++; - return 1; -} - -/* Set the maximum length for the history. This function can be called even - * if there is already some history, the function will make sure to retain - * just the latest 'len' elements if the new history length value is smaller - * than the amount of items already inside the history. */ -int linenoiseHistorySetMaxLen(int len) { - char **new; - - if (len < 1) return 0; - if (history) { - int tocopy = history_len; - - new = malloc(sizeof(char*)*len); - if (new == NULL) return 0; - - /* If we can't copy everything, free the elements we'll not use. */ - if (len < tocopy) { - int j; - - for (j = 0; j < tocopy-len; j++) free(history[j]); - tocopy = len; - } - memset(new,0,sizeof(char*)*len); - memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy); - free(history); - history = new; - } - history_max_len = len; - if (history_len > history_max_len) - history_len = history_max_len; - return 1; -} - -/* Save the history in the specified file. On success 0 is returned - * otherwise -1 is returned. */ -int linenoiseHistorySave(const char *filename) { - mode_t old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO); - FILE *fp; - int j; - - fp = fopen(filename,"w"); - umask(old_umask); - if (fp == NULL) return -1; - chmod(filename,S_IRUSR|S_IWUSR); - for (j = 0; j < history_len; j++) - fprintf(fp,"%s\n",history[j]); - fclose(fp); - return 0; -} - -/* Load the history from the specified file. If the file does not exist - * zero is returned and no operation is performed. - * - * If the file exists and the operation succeeded 0 is returned, otherwise - * on error -1 is returned. */ -int linenoiseHistoryLoad(const char *filename) { - FILE *fp = fopen(filename,"r"); - char buf[LINENOISE_MAX_LINE]; - - if (fp == NULL) return -1; - - while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) { - char *p; - - p = strchr(buf,'\r'); - if (!p) p = strchr(buf,'\n'); - if (p) *p = '\0'; - linenoiseHistoryAdd(buf); - } - fclose(fp); - return 0; -} diff --git a/examples/interactive/linenoise/linenoise.h b/examples/interactive/linenoise/linenoise.h deleted file mode 100644 index 6dfee73..0000000 --- a/examples/interactive/linenoise/linenoise.h +++ /dev/null @@ -1,75 +0,0 @@ -/* linenoise.h -- VERSION 1.0 - * - * Guerrilla line editing library against the idea that a line editing lib - * needs to be 20,000 lines of C code. - * - * See linenoise.c for more information. - * - * ------------------------------------------------------------------------ - * - * Copyright (c) 2010-2014, Salvatore Sanfilippo - * Copyright (c) 2010-2013, Pieter Noordhuis - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef __LINENOISE_H -#define __LINENOISE_H - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct linenoiseCompletions { - size_t len; - char **cvec; -} linenoiseCompletions; - -typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); -typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold); -typedef void(linenoiseFreeHintsCallback)(void *); -void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); -void linenoiseSetHintsCallback(linenoiseHintsCallback *); -void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *); -void linenoiseAddCompletion(linenoiseCompletions *, const char *); - -char *linenoise(const char *prompt); -void linenoiseFree(void *ptr); -int linenoiseHistoryAdd(const char *line); -int linenoiseHistorySetMaxLen(int len); -int linenoiseHistorySave(const char *filename); -int linenoiseHistoryLoad(const char *filename); -void linenoiseClearScreen(void); -void linenoiseSetMultiLine(int ml); -void linenoisePrintKeyCodes(void); -void linenoiseMaskModeEnable(void); -void linenoiseMaskModeDisable(void); - -#ifdef __cplusplus -} -#endif - -#endif /* __LINENOISE_H */ diff --git a/examples/interactive/main.cpp b/examples/interactive/main.cpp index 93c7147..6358d17 100644 --- a/examples/interactive/main.cpp +++ b/examples/interactive/main.cpp @@ -1,61 +1,19 @@ /* - * SPDX-License-Identifier: Apache-2.0 - * * Copyright (c) 2020 Martin Jäger / Libre Solar - * Copyright (c) 2021 Bobby Noelte. + * Copyright (c) 2021..2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 */ -#include +#include "../../src/thingset.h" + +#include "../../apps/shell/ts_shell.h" -#include -#include -#include #include #include -#include "linenoise.h" - -#include "../test/test_data.c" - - -ThingSet thing(data_objects, sizeof(data_objects)/sizeof(ThingSetDataObject)); - -// -// Setup functions used in test data included. -// -// Note: Test data is created as C data (not C++). -// -extern "C" { - -void reset_function() -{ - printf("Reset function called!\n"); -} - -void auth_function() -{ - const char pass_exp[] = "expert123"; - const char pass_mkr[] = "maker456"; - - if (strlen(pass_exp) == strlen(auth_password) && - strncmp(auth_password, pass_exp, strlen(pass_exp)) == 0) - { - thing.set_authentication(TS_EXP_MASK | TS_USR_MASK); - } - else if (strlen(pass_mkr) == strlen(auth_password) && - strncmp(auth_password, pass_mkr, strlen(pass_mkr)) == 0) - { - thing.set_authentication(TS_MKR_MASK | TS_USR_MASK); - } - else { - thing.set_authentication(TS_USR_MASK); - } - - printf("Auth function called, password: %s\n", auth_password); -} - -} // extern "C" +#include "things.h" +ThingSet thing; void pub_thread() { @@ -72,37 +30,18 @@ void pub_thread() int main() { - uint8_t resp_buf[1000]; - printf("\n----------------- Data object tree ---------------------\n\n"); thing.dump_json(); printf("\n----------------- ThingSet shell ---------------------\n\n"); - linenoiseHistoryLoad(".thingset-shell-history.txt"); /* Load the history at startup */ - std::thread t(pub_thread); - char *line; - while ((line = linenoise("")) != NULL) { - if (line[0] != '\0') { - linenoiseHistoryAdd(line); - linenoiseHistorySave(".thingset-shell-history.txt"); - thing.process((uint8_t *)line, strlen(line), resp_buf, sizeof(resp_buf)); - printf("%s\n", (char *)resp_buf); - } - free(line); - } - return 0; -} + ts_shell_init(NULL); + ts_shell_run(NULL); -void conf_callback() -{ - printf("Conf callback called!\n"); -} + ts_shell_join(); -void dummy() -{ - // do nothing, only used in unit-tests + return 0; } diff --git a/examples/interactive/things.c b/examples/interactive/things.c new file mode 100644 index 0000000..e89d7cf --- /dev/null +++ b/examples/interactive/things.c @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2020 Martin Jäger / Libre Solar + * Copyright (c) 2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "../../src/thingset.h" + +#include +#include +#include + +#include "things.h" + +thingset_uid_t uid = 0x1111111111111111ULL; + +/* variables to be exposed via ThingSet */ + +// info +char manufacturer[] = "Libre Solar"; +static uint32_t timestamp = 12345678; +static char device_id[] = "ABCD1234"; + +// conf +static float bat_charging_voltage = 14.4; +static float load_disconnect_voltage = 10.8; + +// input +static bool enable_switch = false; + +// meas +static float battery_voltage = 14.1; +static float battery_current = 5.13; +static int16_t ambient_temp = 22; + +// rec +static float bat_energy_hour = 32.2; +static float bat_energy_day = 123; +static int16_t ambient_temp_max_day = 28; + +// pub +bool pub_report_enable = false; +uint16_t pub_report_interval = 1000; +bool pub_info_enable = true; + +// exec +char auth_password[11]; + +void reset_function(void) +{ + printf("Reset function called!\n"); +} + +void auth_function(void) +{ + const char pass_exp[] = "expert123"; + const char pass_mkr[] = "maker456"; + + if (strlen(pass_exp) == strlen(auth_password) && + strncmp(auth_password, pass_exp, strlen(pass_exp)) == 0) + { + thingset_authorisation_set(TS_CONFIG_CORE_LOCID, TS_EXP_MASK | TS_USR_MASK); + } + else if (strlen(pass_mkr) == strlen(auth_password) && + strncmp(auth_password, pass_mkr, strlen(pass_mkr)) == 0) + { + thingset_authorisation_set(TS_CONFIG_CORE_LOCID, TS_MKR_MASK | TS_USR_MASK); + } + else { + thingset_authorisation_set(TS_CONFIG_CORE_LOCID, TS_USR_MASK); + } + + printf("Auth function called, password: %s\n", auth_password); +} + +void conf_callback(void) +{ + printf("Conf callback called!\n"); +} + +/* The ThingSet data objects database for core context */ +THINGSET_CORE_DATABASE_DEFINE(uid, + + // DEVICE INFORMATION ///////////////////////////////////////////////////// + + TS_GROUP(ID_INFO, "info", TS_NO_CALLBACK, ID_ROOT), + + TS_ITEM_STRING(0x19, "Manufacturer", manufacturer, 0, + ID_INFO, TS_ANY_R, 0), + + TS_ITEM_UINT32(0x1A, "Timestamp_s", ×tamp, + ID_INFO, TS_ANY_RW, SUBSET_REPORT), + + TS_ITEM_STRING(0x1B, "DeviceID", device_id, sizeof(device_id), + ID_INFO, TS_ANY_R | TS_MKR_W, 0), + + // CONFIGURATION ////////////////////////////////////////////////////////// + + TS_GROUP(ID_CONF, "conf", &conf_callback, ID_ROOT), + + TS_ITEM_FLOAT(0x31, "BatCharging_V", &bat_charging_voltage, 2, + ID_CONF, TS_ANY_RW, 0), + + TS_ITEM_FLOAT(0x32, "LoadDisconnect_V", &load_disconnect_voltage, 2, + ID_CONF, TS_ANY_RW, 0), + + // INPUT DATA ///////////////////////////////////////////////////////////// + + TS_GROUP(ID_INPUT, "input", TS_NO_CALLBACK, ID_ROOT), + + TS_ITEM_BOOL(0x61, "EnableCharging", &enable_switch, + ID_INPUT, TS_ANY_RW, 0), + + // MEASUREMENT DATA /////////////////////////////////////////////////////// + + TS_GROUP(ID_MEAS, "meas", TS_NO_CALLBACK, ID_ROOT), + + TS_ITEM_FLOAT(0x71, "Bat_V", &battery_voltage, 2, + ID_MEAS, TS_ANY_R, SUBSET_REPORT | SUBSET_CAN), + + TS_ITEM_FLOAT(0x72, "Bat_A", &battery_current, 2, + ID_MEAS, TS_ANY_R, SUBSET_REPORT | SUBSET_CAN), + + TS_ITEM_INT16(0x73, "Ambient_degC", &ambient_temp, + ID_MEAS, TS_ANY_R, SUBSET_REPORT), + + // RECORDED DATA ////////////////////////////////////////////////////////// + + TS_GROUP(ID_REC, "rec", TS_NO_CALLBACK, ID_ROOT), + + TS_ITEM_FLOAT(0xA1, "BatHour_kWh", &bat_energy_hour, 2, + ID_REC, TS_ANY_R, 0), + + TS_ITEM_FLOAT(0xA2, "BatDay_kWh", &bat_energy_day, 2, + ID_REC, TS_ANY_R, 0), + + TS_ITEM_INT16(0xA3, "AmbientMaxDay_degC", &ambient_temp_max_day, + ID_REC, TS_ANY_R, 0), + + // REMOTE PROCEDURE CALLS ///////////////////////////////////////////////// + + TS_GROUP(ID_RPC, "rpc", TS_NO_CALLBACK, ID_ROOT), + + TS_FUNCTION(0xE1, "x-reset", &reset_function, + ID_RPC, TS_ANY_RW), + + TS_FUNCTION(0xE2, "x-auth", &auth_function, + ID_RPC, TS_ANY_RW), + + TS_ITEM_STRING(0xE3, "Password", auth_password, sizeof(auth_password), + 0xE2, TS_ANY_RW, 0), + + // REPORTS //////////////////////////////////////////////////////////////// + + TS_SUBSET(ID_REPORT, "report", SUBSET_REPORT, ID_ROOT, TS_ANY_RW), + + // PUBLICATION DATA /////////////////////////////////////////////////////// + + TS_GROUP(ID_PUB, ".pub", TS_NO_CALLBACK, ID_ROOT), + + TS_GROUP(0xF1, "report", TS_NO_CALLBACK, ID_PUB), + + TS_ITEM_BOOL(0xF2, "Enable", &pub_report_enable, + 0xF1, TS_ANY_RW, 0), + + TS_ITEM_UINT16(0xF3, "Interval_ms", &pub_report_interval, + 0xF1, TS_ANY_RW, 0), + + TS_GROUP(0xF5, "info", TS_NO_CALLBACK, ID_PUB), + + TS_ITEM_BOOL(0xF6, "OnChange", &pub_info_enable, + 0xF5, TS_ANY_RW, 0), + +); + +/* The ThingSet core context */ +THINGSET_CORE_DEFINE(); diff --git a/examples/interactive/things.h b/examples/interactive/things.h new file mode 100644 index 0000000..1a5488c --- /dev/null +++ b/examples/interactive/things.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Martin Jäger / Libre Solar + * Copyright (c) 2022 Bobby Noelte. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "../../src/thingset.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Categories / first layer object IDs */ +#define ID_ROOT TS_ID_ROOT +#define ID_INFO 0x01 // read-only device information (e.g. manufacturer, device ID) +#define ID_MEAS 0x02 // output data (e.g. measurement values) +#define ID_STATE 0x03 // recorded data (history-dependent) +#define ID_REC 0x04 // recorded data (history-dependent) +#define ID_INPUT 0x05 // input data (e.g. set-points) +#define ID_CONF 0x06 // configurable settings +#define ID_CAL 0x07 // calibration +#define ID_REPORT 0x0A // reports +#define ID_DFU 0x0D // device firmware upgrade +#define ID_RPC 0x0E // remote procedure calls +#define ID_PUB 0x0F // publication setup + +#define SUBSET_REPORT (1U << 0) // report subset of data items for publication +#define SUBSET_CAN (1U << 1) // data nodes used for CAN bus publication messages + +extern char manufacturer[]; +extern bool pub_report_enable; +extern uint16_t pub_report_interval; +extern bool pub_info_enable; + +#ifdef __cplusplus +} +#endif From 9157dcb03d514d8235d7a384c18dbbf1ed3b3b17 Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Sun, 19 Dec 2021 12:57:19 +0100 Subject: [PATCH 36/37] doc - adapt doxygen doumentation to new files added for COM Signed-off-by: Bobby Noelte --- Doxyfile | 325 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 228 insertions(+), 97 deletions(-) diff --git a/Doxyfile b/Doxyfile index 9e57f15..a0a39a2 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1,4 +1,4 @@ -# Doxyfile 1.8.15 +# Doxyfile 1.9.1 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -32,7 +32,7 @@ DOXYFILE_ENCODING = UTF-8 # title of most generated pages and in a few other places. # The default value is: My Project. -PROJECT_NAME = "ThingSet protocol C++ client library" +PROJECT_NAME = "ThingSet protocol device library" # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version @@ -58,7 +58,7 @@ PROJECT_LOGO = # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. -OUTPUT_DIRECTORY = +OUTPUT_DIRECTORY = build/doxy # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and @@ -197,6 +197,16 @@ SHORT_NAMES = NO JAVADOC_AUTOBRIEF = NO +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus @@ -217,6 +227,14 @@ QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. @@ -253,12 +271,6 @@ TAB_SIZE = 4 ALIASES = -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding "class=itcl::class" -# will allow you to use the command class in the itcl::class meaning. - -TCL_SUBST = - # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all @@ -299,19 +311,22 @@ OPTIMIZE_OUTPUT_SLICE = NO # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, Javascript, -# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, # Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser # tries to guess whether the code is fixed or free formatted code, this is the -# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat -# .inc files as Fortran files (default is PHP), and .f files as C (default is -# Fortran), use: inc=Fortran f=C. +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. EXTENSION_MAPPING = @@ -329,7 +344,7 @@ MARKDOWN_SUPPORT = YES # to that level are automatically included in the table of contents, even if # they do not have an id attribute. # Note: This feature currently applies only to Markdown headings. -# Minimum value: 0, maximum value: 99, default value: 0. +# Minimum value: 0, maximum value: 99, default value: 5. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. TOC_INCLUDE_HEADINGS = 0 @@ -419,7 +434,7 @@ INLINE_GROUPED_CLASSES = NO # Man pages) or section (for LaTeX and RTF). # The default value is: NO. -INLINE_SIMPLE_STRUCTS = NO +INLINE_SIMPLE_STRUCTS = YES # When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or # enum is documented as struct, union, or enum with the name of the typedef. So @@ -445,6 +460,19 @@ TYPEDEF_HIDES_STRUCT = NO LOOKUP_CACHE_SIZE = 0 +# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which efficively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- @@ -465,6 +493,12 @@ EXTRACT_ALL = NO EXTRACT_PRIVATE = YES +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. @@ -475,7 +509,7 @@ EXTRACT_PACKAGE = NO # included in the documentation. # The default value is: NO. -EXTRACT_STATIC = NO +EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined # locally in source files will be included in the documentation. If set to NO, @@ -502,6 +536,13 @@ EXTRACT_LOCAL_METHODS = NO EXTRACT_ANON_NSPACES = NO +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation @@ -519,8 +560,8 @@ HIDE_UNDOC_MEMBERS = NO HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# (class|struct|union) declarations. If set to NO, these declarations will be -# included in the documentation. +# declarations. If set to NO, these declarations will be included in the +# documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO @@ -539,11 +580,18 @@ HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO -# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES, upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# and Mac users are advised to set this option to NO. +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. # The default value is: system dependent. CASE_SENSE_NAMES = YES @@ -782,7 +830,10 @@ WARN_IF_DOC_ERROR = YES WARN_NO_PARAMDOC = NO # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when -# a warning is encountered. +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. # The default value is: NO. WARN_AS_ERROR = NO @@ -813,13 +864,20 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = src +INPUT = src/thingset.h \ + README \ + src \ + apps \ + examples \ + native \ + zephyr \ + test # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: https://www.gnu.org/software/libiconv/) for the list of -# possible encodings. +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 @@ -832,11 +890,15 @@ INPUT_ENCODING = UTF-8 # need to set EXTENSION_MAPPING for the extension otherwise the files are not # read by doxygen. # +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, -# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. +# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), +# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, +# *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.c \ *.cc \ @@ -888,7 +950,7 @@ FILE_PATTERNS = *.c \ # be searched for input files as well. # The default value is: NO. -RECURSIVE = NO +RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a @@ -897,14 +959,16 @@ RECURSIVE = NO # Note that relative paths are relative to the directory from which doxygen is # run. -EXCLUDE = +EXCLUDE = examples/interactive/linenoise \ + native/tinycbor \ + native/unity # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. # The default value is: NO. -EXCLUDE_SYMLINKS = NO +EXCLUDE_SYMLINKS = YES # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude @@ -913,7 +977,7 @@ EXCLUDE_SYMLINKS = NO # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = +EXCLUDE_PATTERNS = */build/* # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the @@ -930,7 +994,7 @@ EXCLUDE_SYMBOLS = # that contain example code fragments that are included (see the \include # command). -EXAMPLE_PATH = +EXAMPLE_PATH = examples # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and @@ -1094,6 +1158,44 @@ USE_HTAGS = NO VERBATIM_HEADERS = YES +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which doxygen's built-in parser lacks the necessary type information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to +# YES then doxygen will add the directory of each input to the include path. +# The default value is: YES. + +CLANG_ADD_INC_PATHS = YES + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- @@ -1105,13 +1207,6 @@ VERBATIM_HEADERS = YES ALPHABETICAL_INDEX = YES -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -COLS_IN_ALPHA_INDEX = 5 - # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored @@ -1250,9 +1345,9 @@ HTML_TIMESTAMP = NO # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that -# are dynamically created via Javascript. If disabled, the navigation index will +# are dynamically created via JavaScript. If disabled, the navigation index will # consists of multiple levels of tabs that are statically embedded in every HTML -# page. Disable this option to support browsers that do not have Javascript, +# page. Disable this option to support browsers that do not have JavaScript, # like the Qt help browser. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1282,10 +1377,11 @@ HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: https://developer.apple.com/xcode/), introduced with OSX -# 10.5 (Leopard). To create a documentation set, doxygen will generate a -# Makefile in the HTML output directory. Running make will produce the docset in -# that directory and running make install will install the docset in +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy # genXcode/_index.html for more information. @@ -1327,8 +1423,8 @@ DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on -# Windows. +# (see: +# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML @@ -1358,7 +1454,7 @@ CHM_FILE = HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated -# (YES) or that it should be included in the master .chm file (NO). +# (YES) or that it should be included in the main .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. @@ -1403,7 +1499,8 @@ QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace -# (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1411,8 +1508,8 @@ QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual- -# folders). +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1420,30 +1517,30 @@ QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: -# http://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = -# The QHG_LOCATION tag can be used to specify the location of Qt's -# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the -# generated .qhp file. +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = @@ -1520,6 +1617,17 @@ TREEVIEW_WIDTH = 250 EXT_LINKS_IN_WINDOW = NO +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML @@ -1540,8 +1648,14 @@ FORMULA_FONTSIZE = 10 FORMULA_TRANSPARENT = YES +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# https://www.mathjax.org) which uses client side Javascript for the rendering +# https://www.mathjax.org) which uses client side JavaScript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path @@ -1553,7 +1667,7 @@ USE_MATHJAX = NO # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/latest/output.html) for more details. +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. @@ -1569,7 +1683,7 @@ MATHJAX_FORMAT = HTML-CSS # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from https://www.mathjax.org before deployment. -# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/. +# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/ @@ -1583,7 +1697,8 @@ MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site -# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. @@ -1611,7 +1726,7 @@ MATHJAX_CODEFILE = SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be -# implemented using a web server instead of a web client using Javascript. There +# implemented using a web server instead of a web client using JavaScript. There # are two flavors of web server based searching depending on the EXTERNAL_SEARCH # setting. When disabled, doxygen will generate a PHP script for searching and # an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing @@ -1630,7 +1745,8 @@ SERVER_BASED_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: https://xapian.org/). +# Xapian (see: +# https://xapian.org/). # # See the section "External Indexing and Searching" for details. # The default value is: NO. @@ -1643,8 +1759,9 @@ EXTERNAL_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: https://xapian.org/). See the section "External Indexing and -# Searching" for details. +# Xapian (see: +# https://xapian.org/). See the section "External Indexing and Searching" for +# details. # This tag requires that the tag SEARCHENGINE is set to YES. SEARCHENGINE_URL = @@ -1715,10 +1832,11 @@ LATEX_CMD_NAME = MAKEINDEX_CMD_NAME = makeindex # The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to -# generate index for LaTeX. +# generate index for LaTeX. In case there is no backslash (\) as first character +# it will be automatically added in the LaTeX code. # Note: This tag is used in the generated output file (.tex). # See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat. -# The default value is: \makeindex. +# The default value is: makeindex. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_MAKEINDEX_CMD = \makeindex @@ -1807,9 +1925,11 @@ LATEX_EXTRA_FILES = PDF_HYPERLINKS = YES -# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate -# the PDF file directly from the LaTeX files. Set this option to YES, to get a -# higher quality PDF documentation. +# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as +# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX +# files. Set this option to YES, to get a higher quality PDF documentation. +# +# See also section LATEX_CMD_NAME for selecting the engine. # The default value is: YES. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -2143,7 +2263,7 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = +PREDEFINED = TS_DOXYGEN # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The @@ -2210,12 +2330,6 @@ EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES -# The PERL_PATH should be the absolute path and name of the perl script -# interpreter (i.e. the result of 'which perl'). -# The default file (with absolute path) is: /usr/bin/perl. - -PERL_PATH = /usr/bin/perl - #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- @@ -2229,15 +2343,6 @@ PERL_PATH = /usr/bin/perl CLASS_DIAGRAMS = YES -# You can define message sequence charts within doxygen comments using the \msc -# command. Doxygen will then run the mscgen tool (see: -# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the -# documentation. The MSCGEN_PATH tag allows you to specify the directory where -# the mscgen tool resides. If left empty the tool is assumed to be found in the -# default search path. - -MSCGEN_PATH = - # You can include diagrams made with dia in doxygen documentation. Doxygen will # then run dia to produce the diagram and insert it in the documentation. The # DIA_PATH tag allows you to specify the directory where the dia binary resides. @@ -2256,7 +2361,7 @@ HIDE_UNDOC_RELATIONS = YES # http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent # Bell Labs. The other options in this section have no effect if this option is # set to NO -# The default value is: NO. +# The default value is: YES. HAVE_DOT = NO @@ -2335,10 +2440,32 @@ UML_LOOK = NO # but if the number exceeds 15, the total amount of fields shown is limited to # 10. # Minimum value: 0, maximum value: 100, default value: 10. -# This tag requires that the tag HAVE_DOT is set to YES. +# This tag requires that the tag UML_LOOK is set to YES. UML_LIMIT_NUM_FIELDS = 10 +# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will wrapped across multiple lines. Some heuristics are apply +# to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + # If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and # collaboration graphs will show the relations between templates and their # instances. @@ -2412,7 +2539,9 @@ DIRECTORY_GRAPH = YES # Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order # to make the SVG files visible in IE 9+ (other browsers do not have this # requirement). -# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, +# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd, +# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo, +# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo, # png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and # png:gdiplus:gdiplus. # The default value is: png. @@ -2528,9 +2657,11 @@ DOT_MULTI_TARGETS = NO GENERATE_LEGEND = YES -# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate # files that are used to generate the various graphs. +# +# Note: This setting is not only used for dot files but also for msc and +# plantuml temporary files. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. DOT_CLEANUP = YES From 5b5e3f7f8491c5a0859c0f16ab586e1ea3364a87 Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Mon, 7 Feb 2022 11:16:18 +0100 Subject: [PATCH 37/37] Update .gitignore for ignore kdevelop and build directories Signed-off-by: Bobby Noelte --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index 82f3ba7..5eed7ff 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,10 @@ html .thingset-shell-history.txt + +# Kdevelop +.kdev4 +*.kdev4 + +# Zephyr west/ CMake build directory +build