diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index 26aab9b33..7890c419b 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -18,7 +18,7 @@ jobs: env: # libraries to install for all boards - UNIVERSAL_LIBRARIES: '"ArduinoCloudThing" "Arduino_ConnectionHandler" "Arduino_DebugUtils" "ArduinoMqttClient"' + UNIVERSAL_LIBRARIES: '"Arduino_ConnectionHandler" "Arduino_DebugUtils" "ArduinoMqttClient"' # sketch paths to compile (recursive) for all boards UNIVERSAL_SKETCH_PATHS: '"examples/ArduinoIoTCloud-Advanced" "examples/ArduinoIoTCloud-Basic" "examples/utility/ArduinoIoTCloud_Travis_CI"' diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index f4ba99f01..9eb35c8f7 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -38,7 +38,7 @@ jobs: cd "$BUILD_PATH" sudo apt-get --assume-yes install lcov > /dev/null lcov --directory . --capture --output-file coverage.info - lcov --quiet --remove coverage.info '*/extras/test/*' '/usr/*' --output-file coverage.info + lcov --quiet --remove coverage.info '*/extras/test/*' '/usr/*' '*/src/cbor/lib/*' --output-file coverage.info lcov --list coverage.info - name: Upload coverage report to Codecov diff --git a/extras/test/CMakeLists.txt b/extras/test/CMakeLists.txt index 1dbc7103c..153f81b62 100644 --- a/extras/test/CMakeLists.txt +++ b/extras/test/CMakeLists.txt @@ -9,6 +9,7 @@ project(testArduinoIoTCloud) ########################################################################## include_directories(include) +include_directories(../../src/cbor) include_directories(../../src/utility/ota) include_directories(external/catch/v2.12.1/include) include_directories(external/fakeit/v2.0.5/include) @@ -21,14 +22,57 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) ########################################################################## -set(TEST_TARGET testArduinoIoTCloud) +set(TEST_TARGET ${CMAKE_PROJECT_NAME}) + +########################################################################## set(TEST_SRCS - src/test_main.cpp src/test_OTALogic.cpp - src/OTATestData.cpp + + src/test_addPropertyReal.cpp + src/test_callback.cpp + src/test_CloudColor.cpp + src/test_CloudLocation.cpp + src/test_decode.cpp + src/test_encode.cpp + src/test_publishEvery.cpp + src/test_publishOnChange.cpp + src/test_publishOnChangeRateLimit.cpp + src/test_readOnly.cpp + src/test_writeOnly.cpp +) + +set(TEST_UTIL_SRCS + src/util/CBORTestUtil.cpp + src/util/OTATestUtil.cpp +) + +set(TEST_DUT_SRCS ../../src/utility/ota/crc.cpp ../../src/utility/ota/OTALogic.cpp + + ../../src/cbor/ArduinoCloudThing.cpp + ../../src/cbor/ArduinoCloudProperty.cpp + ../../src/cbor/lib/tinycbor/src/cborencoder.c + ../../src/cbor/lib/tinycbor/src/cborencoder_close_container_checked.c + ../../src/cbor/lib/tinycbor/src/cborerrorstrings.c + ../../src/cbor/lib/tinycbor/src/cborparser.c + ../../src/cbor/lib/tinycbor/src/cborparser_dup_string.c + ../../src/cbor/lib/tinycbor/src/cborpretty.c + ../../src/cbor/lib/tinycbor/src/cborpretty_stdio.c + ../../src/cbor/lib/tinycbor/src/cbortojson.c + ../../src/cbor/lib/tinycbor/src/cborvalidation.c + ../../src/cbor/lib/tinycbor/src/open_memstream.c +) + +########################################################################## + +set(TEST_TARGET_SRCS + src/Arduino.cpp + src/test_main.cpp + ${TEST_SRCS} + ${TEST_UTIL_SRCS} + ${TEST_DUT_SRCS} ) ########################################################################## @@ -43,7 +87,7 @@ set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "--coverage") add_executable( ${TEST_TARGET} - ${TEST_SRCS} + ${TEST_TARGET_SRCS} ) ########################################################################## diff --git a/extras/test/include/Arduino.h b/extras/test/include/Arduino.h new file mode 100644 index 000000000..6e66167a3 --- /dev/null +++ b/extras/test/include/Arduino.h @@ -0,0 +1,27 @@ +/* + Copyright (c) 2019 Arduino. All rights reserved. +*/ + +#ifndef TEST_ARDUINO_H_ +#define TEST_ARDUINO_H_ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include + +/****************************************************************************** + TYPEDEF + ******************************************************************************/ + +typedef std::string String; + +/****************************************************************************** + FUNCTION PROTOTYPES + ******************************************************************************/ + +void set_millis(unsigned long const millis); +unsigned long millis(); + +#endif /* TEST_ARDUINO_H_ */ diff --git a/extras/test/include/util/CBORTestUtil.h b/extras/test/include/util/CBORTestUtil.h new file mode 100644 index 000000000..8aace645e --- /dev/null +++ b/extras/test/include/util/CBORTestUtil.h @@ -0,0 +1,36 @@ +/* + Copyright (c) 2019 Arduino. All rights reserved. +*/ + +#ifndef INCLUDE_CBOR_TESTUTIL_H_ +#define INCLUDE_CBOR_TESTUTIL_H_ + +/************************************************************************************** + INCLUDE + **************************************************************************************/ + +#include + +#include + +/************************************************************************************** + NAMESPACE + **************************************************************************************/ + +namespace cbor +{ + +/************************************************************************************** + PROTOTYPES + **************************************************************************************/ + +std::vector encode(ArduinoCloudThing & thing, bool lightPayload = false); +void print(std::vector const & vect); + +/************************************************************************************** + NAMESPACE + **************************************************************************************/ + +} /* cbor */ + +#endif /* INCLUDE_CBOR_TESTUTIL_H_ */ diff --git a/extras/test/include/OTATestData.h b/extras/test/include/util/OTATestUtil.h similarity index 65% rename from extras/test/include/OTATestData.h rename to extras/test/include/util/OTATestUtil.h index 92f117680..640d422a2 100644 --- a/extras/test/include/OTATestData.h +++ b/extras/test/include/util/OTATestUtil.h @@ -2,8 +2,8 @@ * Copyright (c) 2020 Arduino. All rights reserved. */ -#ifndef OTA_TEST_DATA_H_ -#define OTA_TEST_DATA_H_ +#ifndef OTA_TEST_DATA_GENERATOR_H_ +#define OTA_TEST_DATA_GENERATOR_H_ /************************************************************************************** INCLUDE @@ -11,6 +11,13 @@ #include +/************************************************************************************** + NAMESPACE + **************************************************************************************/ + +namespace ota +{ + /************************************************************************************** TYPEDEF **************************************************************************************/ @@ -33,5 +40,10 @@ union OTAData void generate_valid_ota_data(OTAData & ota_data); void generate_invalid_ota_data_crc_wrong(OTAData & ota_data); +/************************************************************************************** + NAMESPACE + **************************************************************************************/ + +} /* ota */ -#endif /* OTA_TEST_DATA_H_ */ +#endif /* OTA_TEST_DATA_GENERATOR_H_ */ diff --git a/extras/test/src/Arduino.cpp b/extras/test/src/Arduino.cpp new file mode 100644 index 000000000..9287ff399 --- /dev/null +++ b/extras/test/src/Arduino.cpp @@ -0,0 +1,27 @@ +/* + Copyright (c) 2019 Arduino. All rights reserved. +*/ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include + +/****************************************************************************** + GLOBAL VARIABLES + ******************************************************************************/ + +static unsigned long current_millis = 0; + +/****************************************************************************** + PUBLIC FUNCTIONS + ******************************************************************************/ + +void set_millis(unsigned long const millis) { + current_millis = millis; +} + +unsigned long millis() { + return current_millis; +} diff --git a/extras/test/src/test_CloudColor.cpp b/extras/test/src/test_CloudColor.cpp new file mode 100644 index 000000000..8d3f2d873 --- /dev/null +++ b/extras/test/src/test_CloudColor.cpp @@ -0,0 +1,123 @@ +/* + Copyright (c) 2019 Arduino. All rights reserved. +*/ + +/************************************************************************************** + INCLUDE + **************************************************************************************/ + +#include + +#include +#include + +/************************************************************************************** + TEST CODE + **************************************************************************************/ + +/************************************************************************************/ +SCENARIO("Arduino Cloud Properties ", "[ArduinoCloudThing::CloudColor]") { + WHEN("Set invalid color HSB") { + GIVEN("CloudProtocol::V2") { + + + CloudColor color_test = CloudColor(0.0, 0.0, 0.0); + + Color value_color_test = color_test.getValue(); + REQUIRE(value_color_test.setColorHSB(500.0, 20.0, 30.0) == false); + + } + } + + WHEN("Set and Get different RGB colors") { + GIVEN("CloudProtocol::V2") { + + uint8_t r, g, b; + + CloudColor color_test = CloudColor(0.0, 0.0, 0.0); + + Color value_color_test = color_test.getValue(); + + value_color_test.setColorRGB(128, 64, 64); + value_color_test.getRGB(r, g, b); + + REQUIRE(r == 128); + REQUIRE(g == 64); + REQUIRE(b == 64); + + value_color_test.setColorRGB(126, 128, 64); + value_color_test.getRGB(r, g, b); + + REQUIRE(r == 126); + REQUIRE(g == 128); + REQUIRE(b == 64); + + value_color_test.setColorRGB(64, 128, 64); + value_color_test.getRGB(r, g, b); + + REQUIRE(r == 64); + REQUIRE(g == 128); + REQUIRE(b == 64); + + value_color_test.setColorRGB(64, 64, 128); + value_color_test.getRGB(r, g, b); + + REQUIRE(r == 64); + REQUIRE(g == 64); + REQUIRE(b == 128); + + value_color_test.setColorRGB(255, 0, 255); + value_color_test.getRGB(r, g, b); + + REQUIRE(r == 255); + REQUIRE(g == 0); + REQUIRE(b == 255); + + value_color_test.setColorRGB(0, 0, 0); + value_color_test.getRGB(r, g, b); + + REQUIRE(r == 0); + REQUIRE(g == 0); + REQUIRE(b == 0); + + value_color_test.setColorRGB(50, 100, 20); + value_color_test.getRGB(r, g, b); + + REQUIRE(r == 50); + REQUIRE(g == 100); + REQUIRE(b == 20); + + value_color_test.setColorRGB(20, 50, 70); + value_color_test.getRGB(r, g, b); + + REQUIRE(r == 20); + REQUIRE(g == 50); + REQUIRE(b == 70); + + } + } + + WHEN("Set HSB colors and get RGB") { + GIVEN("CloudProtocol::V2") { + bool verify; + uint8_t r, g, b; + + CloudColor color_test = CloudColor(0.0, 0.0, 0.0); + + Color value_color_test = color_test.getValue(); + + value_color_test.setColorHSB(240, 50, 50); + value_color_test.getRGB(r, g, b); + verify = r == 64 && g == 64 && b == 128; + + REQUIRE(verify); + + value_color_test.setColorHSB(120, 50, 50); + value_color_test.getRGB(r, g, b); + verify = r == 64 && g == 128 && b == 64; + + REQUIRE(verify); + + } + } +} \ No newline at end of file diff --git a/extras/test/src/test_CloudLocation.cpp b/extras/test/src/test_CloudLocation.cpp new file mode 100644 index 000000000..63c71fd88 --- /dev/null +++ b/extras/test/src/test_CloudLocation.cpp @@ -0,0 +1,102 @@ +/* + Copyright (c) 2019 Arduino. All rights reserved. +*/ + +/************************************************************************************** + INCLUDE + **************************************************************************************/ + +#include + +#include + +/************************************************************************************** + TEST CODE + **************************************************************************************/ + +SCENARIO("Tesing cloud type 'Location' Ctor", "[Location::Location]") { + WHEN("A Location(1.0f, 2.0f) is being instantiated") { + Location loc(1.0f, 2.0f); + THEN("The member variable 'lat' should be 1.0f") { + REQUIRE(loc.lat == 1.0f); + } + THEN("The member variable 'lon' should be 2.0f") { + REQUIRE(loc.lon == 2.0f); + } + } +} + +/**************************************************************************************/ + +SCENARIO("Tesing cloud type 'Location' assignment operator", "[Location::operator =]") { + Location loc1(1.0f, 2.0f), + loc2(3.0f, 4.0f); + loc1 = loc2; + WHEN("One location is assigned to the other") { + THEN("The coordinates of the second location should be assigned to the first") { + REQUIRE(loc1.lat == 3.0f); + REQUIRE(loc1.lon == 4.0f); + } + } +} + +/**************************************************************************************/ + +SCENARIO("Tesing cloud type 'Location' operator -", "[Location::operator -]") { + Location loc1(1.0f, 2.0f), + loc2(3.0f, 4.0f); + Location loc3 = loc1 - loc2; + WHEN("One location is subtracted from the other") { + THEN("The result should be calculated according the rule lon3 = lon1 - lon2, lat3 = lat1 - lat2") { + REQUIRE(loc3.lat == loc1.lat - loc2.lat); + REQUIRE(loc3.lon == loc1.lon - loc2.lon); + } + } +} + +/**************************************************************************************/ + +SCENARIO("Tesing cloud type 'Location' comparison operator ==", "[Location::operator ==]") { + Location loc1(1.0f, 2.0f), + loc2(3.0f, 4.0f), + loc3(1.0f, 2.0f); + WHEN("Two locations are identical (lat as well as lon)") { + THEN("The comparison operation should return true") { + REQUIRE((loc1 == loc3) == true); + } + } + + WHEN("Two locations are not identical (either lat or lon do not match)") { + THEN("The comparison operation should return false") { + REQUIRE((loc1 == loc2) == false); + } + } +} + +/**************************************************************************************/ + +SCENARIO("Tesing cloud type 'Location' comparison operator !=", "[Location::operator !=]") { + Location loc1(1.0f, 2.0f), + loc2(3.0f, 4.0f), + loc3(1.0f, 2.0f); + WHEN("Two locations are identical (lat as well as lon)") { + THEN("The comparison operation should return false") { + REQUIRE((loc1 != loc3) == false); + } + } + + WHEN("Two locations are not identical (either lat or lon do not match)") { + THEN("The comparison operation should return true") { + REQUIRE((loc1 != loc2) == true); + } + } +} + +/**************************************************************************************/ + +SCENARIO("Tesing cloud type 'Location' function distance for calculating euclidean 2d distance between two points", "[Location::distance]") { + Location loc1(0.0f, 0.0f), + loc2(1.0f, 1.0f); + + REQUIRE(Location::distance(loc1, loc2) == sqrt(2.0f)); +} \ No newline at end of file diff --git a/extras/test/src/test_OTALogic.cpp b/extras/test/src/test_OTALogic.cpp index 59c3103f4..ba3833623 100644 --- a/extras/test/src/test_OTALogic.cpp +++ b/extras/test/src/test_OTALogic.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include @@ -27,7 +27,7 @@ using namespace fakeit; TEST HELPER **************************************************************************************/ -void simulateOTABinaryReception(OTALogic & ota_logic, OTAData const & ota_test_data) +void simulateOTABinaryReception(OTALogic & ota_logic, ota::OTAData const & ota_test_data) { uint32_t bytes_written = 0; uint32_t const bytes_to_write = sizeof(uint32_t) + sizeof(uint32_t) + ota_test_data.data.len; @@ -212,8 +212,8 @@ TEST_CASE("Valid OTA data is received ", "[OTALogic]") /* Generate test data */ - OTAData valid_ota_test_data; - generate_valid_ota_data(valid_ota_test_data); + ota::OTAData valid_ota_test_data; + ota::generate_valid_ota_data(valid_ota_test_data); /* Perform test */ @@ -259,8 +259,8 @@ TEST_CASE("Invalid OTA data is received ", "[OTALogic - CRC wrong]") /* Generate test data */ - OTAData invalid_valid_ota_test_data_crc_wrong; - generate_invalid_ota_data_crc_wrong(invalid_valid_ota_test_data_crc_wrong); + ota::OTAData invalid_valid_ota_test_data_crc_wrong; + ota::generate_invalid_ota_data_crc_wrong(invalid_valid_ota_test_data_crc_wrong); /* Perform test */ diff --git a/extras/test/src/test_addPropertyReal.cpp b/extras/test/src/test_addPropertyReal.cpp new file mode 100644 index 000000000..6d60f8982 --- /dev/null +++ b/extras/test/src/test_addPropertyReal.cpp @@ -0,0 +1,78 @@ +/* + Copyright (c) 2019 Arduino. All rights reserved. +*/ + +/************************************************************************************** + INCLUDE + **************************************************************************************/ + +#include + +#include + +/************************************************************************************** + TEST CODE + **************************************************************************************/ + +SCENARIO("The same arduino cloud properties are added multiple times", "[ArduinoCloudThing::addPropertyReal]") { + WHEN("The same bool property is added multiple times") { + ArduinoCloudThing thing; + thing.begin(); + + CloudBool bool_property = false; + + ArduinoCloudProperty * bool_property_ptr_1 = &thing.addPropertyReal(bool_property, "bool_property", Permission::ReadWrite); + ArduinoCloudProperty * bool_property_ptr_2 = &thing.addPropertyReal(bool_property, "bool_property", Permission::ReadWrite); + THEN("No new property is added and the first added property is returned instead of a new one") { + REQUIRE(bool_property_ptr_1 == bool_property_ptr_2); + } + } + + /**************************************************************************************/ + + WHEN("the same int property is added multiple times") { + ArduinoCloudThing thing; + thing.begin(); + + CloudInt int_property = 1; + + ArduinoCloudProperty * int_property_ptr_1 = &thing.addPropertyReal(int_property, "int_property", Permission::ReadWrite); + ArduinoCloudProperty * int_property_ptr_2 = &thing.addPropertyReal(int_property, "int_property", Permission::ReadWrite); + + THEN("No new property is added and the first added property is returned instead of a new one") { + REQUIRE(int_property_ptr_1 == int_property_ptr_2); + } + } + + /**************************************************************************************/ + + WHEN("the same float property is added multiple times") { + ArduinoCloudThing thing; + thing.begin(); + + CloudFloat float_property = 1.0f; + + ArduinoCloudProperty * float_property_ptr_1 = &thing.addPropertyReal(float_property, "float_property", Permission::ReadWrite); + ArduinoCloudProperty * float_property_ptr_2 = &thing.addPropertyReal(float_property, "float_property", Permission::ReadWrite); + + THEN("No new property is added and the first added property is returned instead of a new one") { + REQUIRE(float_property_ptr_1 == float_property_ptr_2); + } + } + + /**************************************************************************************/ + + WHEN("the same String property is added multiple times") { + ArduinoCloudThing thing; + thing.begin(); + + CloudString str_property; + + ArduinoCloudProperty * str_property_ptr_1 = &thing.addPropertyReal(str_property, "str_property", Permission::ReadWrite); + ArduinoCloudProperty * str_property_ptr_2 = &thing.addPropertyReal(str_property, "str_property", Permission::ReadWrite); + + THEN("No new property is added and the first added property is returned instead of a new one") { + REQUIRE(str_property_ptr_1 == str_property_ptr_2); + } + } +} \ No newline at end of file diff --git a/extras/test/src/test_callback.cpp b/extras/test/src/test_callback.cpp new file mode 100644 index 000000000..f95e4c6da --- /dev/null +++ b/extras/test/src/test_callback.cpp @@ -0,0 +1,367 @@ +/* + Copyright (c) 2019 Arduino. All rights reserved. +*/ + +/************************************************************************************** + INCLUDE + **************************************************************************************/ + +#include + +#include +#include +#include "types/CloudWrapperBool.h" + +/************************************************************************************** + GLOBAL CONSTANTS + **************************************************************************************/ + +static bool callback_called_protocol_v1 = false; +static bool callback_called_protocol_v2 = false; + +/************************************************************************************** + TEST HELPER FUNCTIONS + **************************************************************************************/ + +void externalCallbackV1() { + callback_called_protocol_v1 = true; +} + +void externalCallbackV2() { + callback_called_protocol_v2 = true; +} + +/************************************************************************************** + TEST CODE + **************************************************************************************/ + +SCENARIO("A callback is registered via 'onUpdate' to be called on property change", "[ArduinoCloudThing::decode]") { + /************************************************************************************/ + + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudInt test = 10; + thing.addPropertyReal(test, "test", Permission::ReadWrite).onUpdate(externalCallbackV2); + + /* [{0: "test", 2: 7}] = 81 A2 00 64 74 65 73 74 02 07 */ + uint8_t const payload[] = {0x81, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0x07}; + int const payload_length = sizeof(payload) / sizeof(uint8_t); + thing.decode(payload, payload_length); + + REQUIRE(callback_called_protocol_v2 == true); + } + + /************************************************************************************/ +} + +/**************************************************************************************/ + +static CloudBool switch_turned_on = false; +static CloudBool switch_callback_called = false; + +void switch_callback() { + switch_turned_on = false; + switch_callback_called = true; +} + +SCENARIO("A (boolean) property is manipulated in the callback to its origin state", "[ArduinoCloudThing::decode]") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + cbor::encode(thing); + + thing.addPropertyReal(switch_turned_on, "switch_turned_on", Permission::ReadWrite).onUpdate(switch_callback); + + /* [{0: "switch_turned_on", 4: true}] = 81 A2 00 70 73 77 69 74 63 68 5F 74 75 72 6E 65 64 5F 6F 6E 04 F5 */ + uint8_t const payload[] = {0x81, 0xA2, 0x00, 0x70, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x5F, 0x74, 0x75, 0x72, 0x6E, 0x65, 0x64, 0x5F, 0x6F, 0x6E, 0x04, 0xF5}; + int const payload_length = sizeof(payload) / sizeof(uint8_t); + thing.decode(payload, payload_length); + + REQUIRE(switch_callback_called == true); + + /* Since the property was reset to its origin state in the callback we + expect that on the next call to encode this change is propagated to + the cloud. + */ + + /* [{0: "switch_turned_on", 4: false}] = 9F A2 00 70 73 77 69 74 63 68 5F 74 75 72 6E 65 64 5F 6F 6E 04 F4 FF*/ + std::vector const expected = {0x9F, 0xA2, 0x00, 0x70, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x5F, 0x74, 0x75, 0x72, 0x6E, 0x65, 0x64, 0x5F, 0x6F, 0x6E, 0x04, 0xF4, 0xFF}; + std::vector const actual = cbor::encode(thing); + REQUIRE(actual == expected); + } +} + +/**************************************************************************************/ + +static bool sync_callback_called = false; +static bool change_callback_called = false; + +void auto_sync_callback(ArduinoCloudProperty& property) { + MOST_RECENT_WINS(property); + sync_callback_called = true; +} + +void change_callback() { + change_callback_called = true; +} + +SCENARIO("After a connection/reconnection an incoming cbor payload is processed and the synchronization callback is executed. The sync callback applies the AUTO_SYNC policy (the most recent value between the local one and the cloud one is finally assigned to the property). The onUpdate function is called if the cloud value is the most recent one. In this scenario the most updated value is the cloud one.") { + GIVEN("CloudProtocol::V2") { + CloudBool test = false; + sync_callback_called = false; + change_callback_called = false; + + ArduinoCloudThing thing; + thing.begin(); + + thing.addPropertyReal(test, "test", Permission::ReadWrite).onUpdate(change_callback).onSync(auto_sync_callback); + + test.setLastLocalChangeTimestamp(1550138809); + + /* [{-3: 1550138810.00, 0: "test", 4: true}] = 81 A3 22 FB 41 D7 19 4F 6E 80 00 00 00 64 74 65 73 74 04 F5 */ + uint8_t const payload[] = {0x81, 0xA3, 0x22, 0xFB, 0x41, 0xD7, 0x19, 0x4F, 0x6E, 0x80, 0x00, 0x00, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x04, 0xF5}; + int const payload_length = sizeof(payload) / sizeof(uint8_t); + thing.decode(payload, payload_length, true); + + REQUIRE(sync_callback_called == true); + REQUIRE(change_callback_called == true); + + REQUIRE(test == true); + } +} + +/**************************************************************************************/ + +SCENARIO("After a connection/reconnection an incoming cbor payload is processed and the synchronization callback is executed. The sync callback apply the AUTO_SYNC policy (the most recent value between the local one and the cloud one is finally assigned to the property). The onUpdate function is called if the cloud value is the most recent one. In this scenario the most updated value is the local one.") { + GIVEN("CloudProtocol::V2") { + CloudBool test = true; + sync_callback_called = false; + change_callback_called = false; + + ArduinoCloudThing thing; + thing.begin(); + + thing.addPropertyReal(test, "test", Permission::ReadWrite).onUpdate(change_callback).onSync(auto_sync_callback); + test = false; + test.setLastLocalChangeTimestamp(1550138811); + + /* [{-3: 1550138810.00, 0: "test", 4: true}] = 81 A3 22 FB 41 D7 19 4F 6E 80 00 00 00 64 74 65 73 74 04 F5 */ + uint8_t const payload[] = {0x81, 0xA3, 0x22, 0xFB, 0x41, 0xD7, 0x19, 0x4F, 0x6E, 0x80, 0x00, 0x00, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x04, 0xF5}; + int const payload_length = sizeof(payload) / sizeof(uint8_t); + thing.decode(payload, payload_length, true); + + REQUIRE(sync_callback_called == true); + REQUIRE(change_callback_called == false); + + REQUIRE(test == false); + } +} + +SCENARIO("Primitive property: After a connection/reconnection an incoming cbor payload is processed and the synchronization callback is executed. The sync callback applies the AUTO_SYNC policy (the most recent value between the local one and the cloud one is finally assigned to the property). The onUpdate function is called if the cloud value is the most recent one. In this scenario the most updated value is the cloud one.") { + GIVEN("CloudProtocol::V2") { + bool test = true; + ArduinoCloudProperty *p = new CloudWrapperBool(test); + sync_callback_called = false; + change_callback_called = false; + + ArduinoCloudThing thing; + thing.begin(); + + thing.addPropertyReal(*p, "test", Permission::ReadWrite).onUpdate(change_callback).onSync(auto_sync_callback); + test = false; + thing.updateTimestampOnLocallyChangedProperties(); + //There is no RTC on test execution environment so we force the local timestamp + p->setLastLocalChangeTimestamp(1550138809); + + /* [{-3: 1550138810.00, 0: "test", 4: true}] = 81 A3 22 FB 41 D7 19 4F 6E 80 00 00 00 64 74 65 73 74 04 F5 */ + uint8_t const payload[] = {0x81, 0xA3, 0x22, 0xFB, 0x41, 0xD7, 0x19, 0x4F, 0x6E, 0x80, 0x00, 0x00, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x04, 0xF5}; + int const payload_length = sizeof(payload) / sizeof(uint8_t); + thing.decode(payload, payload_length, true); + + REQUIRE(sync_callback_called == true); + REQUIRE(change_callback_called == true); + + REQUIRE(test == true); + } +} + +/**************************************************************************************/ + +SCENARIO("Primitive property: After a connection/reconnection an incoming cbor payload is processed and the synchronization callback is executed. The sync callback apply the AUTO_SYNC policy (the most recent value between the local one and the cloud one is finally assigned to the property). The onUpdate function is called if the cloud value is the most recent one. In this scenario the most updated value is the local one.") { + GIVEN("CloudProtocol::V2") { + bool test = true; + ArduinoCloudProperty *p = new CloudWrapperBool(test); + sync_callback_called = false; + change_callback_called = false; + + ArduinoCloudThing thing; + thing.begin(); + + thing.addPropertyReal(*p, "test", Permission::ReadWrite).onUpdate(change_callback).onSync(auto_sync_callback); + test = false; + thing.updateTimestampOnLocallyChangedProperties(); + //There is no RTC on test execution environment so we force the local timestamp + p->setLastLocalChangeTimestamp(1550138811); + + /* [{-3: 1550138810.00, 0: "test", 4: true}] = 81 A3 22 FB 41 D7 19 4F 6E 80 00 00 00 64 74 65 73 74 04 F5 */ + uint8_t const payload[] = {0x81, 0xA3, 0x22, 0xFB, 0x41, 0xD7, 0x19, 0x4F, 0x6E, 0x80, 0x00, 0x00, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x04, 0xF5}; + int const payload_length = sizeof(payload) / sizeof(uint8_t); + thing.decode(payload, payload_length, true); + + REQUIRE(sync_callback_called == true); + REQUIRE(change_callback_called == false); + + REQUIRE(test == false); + } +} + +SCENARIO("Object property: After a connection/reconnection an incoming cbor payload is processed and the synchronization callback is executed. The sync callback applies the AUTO_SYNC policy (the most recent value between the local one and the cloud one is finally assigned to the property). The onUpdate function is called if the cloud value is the most recent one. In this scenario the most updated value is the cloud one.") { + GIVEN("CloudProtocol::V2") { + CloudLocation location_test = CloudLocation(0, 1); + sync_callback_called = false; + change_callback_called = false; + + ArduinoCloudThing thing; + thing.begin(); + + thing.addPropertyReal(location_test, "test", Permission::ReadWrite).onUpdate(change_callback).onSync(auto_sync_callback); + location_test.setLastLocalChangeTimestamp(1550138809); + + /* [{-3: 1550138810.00, 0: "test:lat", 3: 2},{0: "test:lon", 3: 3}] = 82 A3 22 FB 41 D7 19 4F 6E 80 00 00 00 68 74 65 73 74 3A 6C 61 74 02 02 A2 00 68 74 65 73 74 3A 6C 6F 6E 02 03*/ + uint8_t const payload[] = { 0x82, 0xA3, 0x22, 0xFB, 0x41, 0xD7, 0x19, 0x4F, 0x6E, 0x80, 0x00, 0x00, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x6C, 0x61, 0x74, 0x02, 0x02, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x6C, 0x6F, 0x6E, 0x02, 0x03 }; + + int const payload_length = sizeof(payload) / sizeof(uint8_t); + thing.decode(payload, payload_length, true); + + REQUIRE(sync_callback_called == true); + REQUIRE(change_callback_called == true); + + Location location_compare = Location(2, 3); + Location value_location_test = location_test.getValue(); + bool verify = (value_location_test == location_compare); + + REQUIRE(verify); + } +} + +/**************************************************************************************/ + +SCENARIO("Object property: After a connection/reconnection an incoming cbor payload is processed and the synchronization callback is executed. The sync callback apply the AUTO_SYNC policy (the most recent value between the local one and the cloud one is finally assigned to the property). The onUpdate function is called if the cloud value is the most recent one. In this scenario the most updated value is the local one.") { + GIVEN("CloudProtocol::V2") { + CloudLocation location_test = CloudLocation(0, 1); + sync_callback_called = false; + change_callback_called = false; + + ArduinoCloudThing thing; + thing.begin(); + + thing.addPropertyReal(location_test, "test", Permission::ReadWrite).onUpdate(change_callback).onSync(auto_sync_callback); + location_test.setLastLocalChangeTimestamp(1550138811); + + /* [{-3: 1550138810.00, 0: "test:lat", 3: 2},{0: "test:lon", 3: 3}] = 82 A3 22 FB 41 D7 19 4F 6E 80 00 00 00 68 74 65 73 74 3A 6C 61 74 02 02 A2 00 68 74 65 73 74 3A 6C 6F 6E 02 03*/ + uint8_t const payload[] = { 0x82, 0xA3, 0x22, 0xFB, 0x41, 0xD7, 0x19, 0x4F, 0x6E, 0x80, 0x00, 0x00, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x6C, 0x61, 0x74, 0x02, 0x02, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x6C, 0x6F, 0x6E, 0x02, 0x03 }; + + int const payload_length = sizeof(payload) / sizeof(uint8_t); + thing.decode(payload, payload_length, true); + + REQUIRE(sync_callback_called == true); + REQUIRE(change_callback_called == false); + + Location location_compare = Location(0, 1); + Location value_location_test = location_test.getValue(); + bool verify = (value_location_test == location_compare); + + REQUIRE(verify); + } +} + +/**************************************************************************************/ + +void force_device_sync_callback(ArduinoCloudProperty& property) { + DEVICE_WINS(property); + sync_callback_called = true; +} + +SCENARIO("After a connection/reconnection an incoming cbor payload is processed and the synchronization callback is executed. The sync callback applies the FORCE_DEVICE_SYNC policy (the property keeps the local value and, if the cloud value is different from the local one, the value is propagated to the cloud). The onUpdate function is not executed") { + GIVEN("CloudProtocol::V2") { + + CloudBool test = false; + sync_callback_called = false; + change_callback_called = false; + + ArduinoCloudThing thing; + thing.begin(); + + thing.addPropertyReal(test, "test", Permission::ReadWrite).onUpdate(change_callback).onSync(force_device_sync_callback); + + /* [{-3: 1550138810.00, 0: "test", 4: true}] = 81 A3 22 FB 41 D7 19 4F 6E 80 00 00 00 64 74 65 73 74 04 F5 */ + uint8_t const payload[] = {0x81, 0xA3, 0x22, 0xFB, 0x41, 0xD7, 0x19, 0x4F, 0x6E, 0x80, 0x00, 0x00, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x04, 0xF5}; + int const payload_length = sizeof(payload) / sizeof(uint8_t); + thing.decode(payload, payload_length, true); + + REQUIRE(sync_callback_called == true); + REQUIRE(change_callback_called == false); + + REQUIRE(test == false); + } +} + + +/**************************************************************************************/ + +void force_cloud_sync_callback(ArduinoCloudProperty& property) { + CLOUD_WINS(property); + sync_callback_called = true; +} + +SCENARIO("After a connection/reconnection an incoming cbor payload is processed and the synchronization callback is executed. The sync callback applies the FORCE_CLOUD_SYNC policy (the property always assumes the value incoming from the broker message). The onUpdate function is executed only if the local value of the property was different from the one taken from the incoming message") { + GIVEN("CloudProtocol::V2") { + + CloudBool test = false; + sync_callback_called = false; + change_callback_called = false; + + ArduinoCloudThing thing; + thing.begin(); + + thing.addPropertyReal(test, "test", Permission::ReadWrite).onUpdate(change_callback).onSync(force_cloud_sync_callback); + + /* [{-3: 1550138810.00, 0: "test", 4: true}] = 81 A3 22 FB 41 D7 19 4F 6E 80 00 00 00 64 74 65 73 74 04 F5 */ + uint8_t const payload[] = {0x81, 0xA3, 0x22, 0xFB, 0x41, 0xD7, 0x19, 0x4F, 0x6E, 0x80, 0x00, 0x00, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x04, 0xF5}; + int const payload_length = sizeof(payload) / sizeof(uint8_t); + thing.decode(payload, payload_length, true); + + REQUIRE(sync_callback_called == true); + REQUIRE(change_callback_called == true); + + REQUIRE(test == true); + } +} + +/**************************************************************************************/ + +SCENARIO("After a connection/reconnection an incoming cbor payload is processed. Any synchronization function is passed to the property so the value in the incoming message is discarded") { + GIVEN("CloudProtocol::V2") { + + CloudBool test = false; + sync_callback_called = false; + change_callback_called = false; + + ArduinoCloudThing thing; + thing.begin(); + + thing.addPropertyReal(test, "test", Permission::ReadWrite).onUpdate(change_callback); + + /* [{-3: 1550138810.00, 0: "test", 4: true}] = 81 A3 22 FB 41 D7 19 4F 6E 80 00 00 00 64 74 65 73 74 04 F5 */ + uint8_t const payload[] = {0x81, 0xA3, 0x22, 0xFB, 0x41, 0xD7, 0x19, 0x4F, 0x6E, 0x80, 0x00, 0x00, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x04, 0xF5}; + int const payload_length = sizeof(payload) / sizeof(uint8_t); + thing.decode(payload, payload_length, true); + + REQUIRE(sync_callback_called == false); + REQUIRE(change_callback_called == false); + + REQUIRE(test == false); + } +} diff --git a/extras/test/src/test_decode.cpp b/extras/test/src/test_decode.cpp new file mode 100644 index 000000000..9b89bb177 --- /dev/null +++ b/extras/test/src/test_decode.cpp @@ -0,0 +1,651 @@ +/* + Copyright (c) 2019 Arduino. All rights reserved. +*/ + +/************************************************************************************** + INCLUDE + **************************************************************************************/ + +#include + +#include +#include +#include "types/CloudWrapperBool.h" +#include "types/CloudWrapperFloat.h" +#include "types/CloudWrapperInt.h" +#include "types/CloudWrapperString.h" +#include "types/automation/CloudColoredLight.h" +#include "types/automation/CloudContactSensor.h" +#include "types/automation/CloudDimmedLight.h" +#include "types/automation/CloudLight.h" +#include "types/automation/CloudMotionSensor.h" +#include "types/automation/CloudSmartPlug.h" +#include "types/automation/CloudSwitch.h" +#include "types/automation/CloudTemperature.h" +#include "types/automation/CloudTelevision.h" + +/************************************************************************************** + TEST CODE + **************************************************************************************/ + +SCENARIO("Arduino Cloud Properties are decoded", "[ArduinoCloudThing::decode]") { + /************************************************************************************/ + + WHEN("A boolean property is changed via CBOR message") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudBool test = true; + thing.addPropertyReal(test, "test", Permission::ReadWrite); + + /* [{0: "test", 4: false}] = 81 A2 00 64 74 65 73 74 04 F4 */ + uint8_t const payload[] = {0x81, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x04, 0xF4}; + int const payload_length = sizeof(payload) / sizeof(uint8_t); + thing.decode(payload, payload_length); + + REQUIRE(test == false); + } + } + + /************************************************************************************/ + + WHEN("A boolean property is changed via CBOR message - light payload") { + /*An integer identifier has been encoded instead of the name of the property in order to have a shorter payload*/ + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudBool test = true; + /*The property is added with identifier 1 that will be used instead of the string "test" as property identifier*/ + thing.addPropertyReal(test, "test", Permission::ReadWrite, 1); + + /* [{0: 1, 4: false}] = 81 A2 00 01 04 F4 */ + uint8_t const payload[] = {0x81, 0xA2, 0x00, 0x01, 0x04, 0xF4}; + int const payload_length = sizeof(payload) / sizeof(uint8_t); + thing.decode(payload, payload_length); + + REQUIRE(test == false); + } + } + + /************************************************************************************/ + + WHEN("A int property is changed via CBOR message") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudInt test = 0; + thing.addPropertyReal(test, "test", Permission::ReadWrite); + + /* [{0: "test", 2: 7}] = 81 A2 00 64 74 65 73 74 02 07 */ + uint8_t const payload[] = {0x81, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0x07}; + int const payload_length = sizeof(payload) / sizeof(uint8_t); + thing.decode(payload, payload_length); + + REQUIRE(test == 7); + } + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudInt test = 0; + thing.addPropertyReal(test, "test", Permission::ReadWrite); + + /* [{0: "test", 2: -7}] = 81 A2 00 64 74 65 73 74 02 26 */ + uint8_t const payload[] = {0x81, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0x26}; + int const payload_length = sizeof(payload) / sizeof(uint8_t); + thing.decode(payload, payload_length); + + REQUIRE(test == -7); + } + } + + /************************************************************************************/ + + WHEN("A float property is changed via CBOR message") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudFloat test = 0.0f; + thing.addPropertyReal(test, "test", Permission::ReadWrite); + + /* [{0: "test", 2: 3.1459}] = 81 A2 00 64 74 65 73 74 02 FB 40 09 2A CD 9E 83 E4 26 */ + uint8_t const payload[] = {0x81, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0xFB, 0x40, 0x09, 0x2A, 0xCD, 0x9E, 0x83, 0xE4, 0x26}; + int const payload_length = sizeof(payload) / sizeof(uint8_t); + thing.decode(payload, payload_length); + + REQUIRE(test == Approx(3.1459).epsilon(0.01)); + } + } + + /************************************************************************************/ + + WHEN("A String property is changed via CBOR message") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudString str_test; + str_test = "test"; + thing.addPropertyReal(str_test, "test", Permission::ReadWrite); + + /* [{0: "test", 3: "testtt"}] = 81 A2 00 64 74 65 73 74 03 66 74 65 73 74 74 74 */ + uint8_t const payload[] = {0x81, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x03, 0x66, 0x74, 0x65, 0x73, 0x74, 0x74, 0x74}; + thing.decode(payload, sizeof(payload) / sizeof(uint8_t)); + + REQUIRE(str_test == "testtt"); + } + } + + /************************************************************************************/ + + WHEN("A Location property is changed via CBOR message") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudLocation location_test = CloudLocation(0, 1); + thing.addPropertyReal(location_test, "test", Permission::ReadWrite); + + /* [{0: "test:lat", 3: 2},{0: "test:lon", 3: 3}] = 82 A2 00 68 74 65 73 74 3A 6C 61 74 02 02 A2 00 68 74 65 73 74 3A 6C 6F 6E 02 03*/ + uint8_t const payload[] = { 0x82, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x6C, 0x61, 0x74, 0x02, 0x02, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x6C, 0x6F, 0x6E, 0x02, 0x03 }; + thing.decode(payload, sizeof(payload) / sizeof(uint8_t)); + Location location_compare = Location(2, 3); + Location value_location_test = location_test.getValue(); + REQUIRE(value_location_test.lat == location_compare.lat); + REQUIRE(value_location_test.lon == location_compare.lon); + } + } + + /************************************************************************************/ + + WHEN("A Color property is changed via CBOR message") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudColor color_test = CloudColor(0.0, 0.0, 0.0); + + thing.addPropertyReal(color_test, "test", Permission::ReadWrite); + + /* [{0: "test:hue", 2: 2.0},{0: "test:sat", 2: 2.0},{0: "test:bri", 2: 2.0}] = 83 A2 00 68 74 65 73 74 3A 68 75 65 02 FA 40 00 00 00 A2 00 68 74 65 73 74 3A 73 61 74 02 FA 40 00 00 00 A2 00 68 74 65 73 74 3A 62 72 69 02 FA 40 00 00 00 */ + uint8_t const payload[] = {0x83, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x68, 0x75, 0x65, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x73, 0x61, 0x74, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x62, 0x72, 0x69, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00 }; + thing.decode(payload, sizeof(payload) / sizeof(uint8_t)); + + Color color_compare = Color(2.0, 2.0, 2.0); + Color value_color_test = color_test.getValue(); + bool verify = (value_color_test == color_compare); + REQUIRE(verify); + REQUIRE(value_color_test.hue == color_compare.hue); + REQUIRE(value_color_test.sat == color_compare.sat); + REQUIRE(value_color_test.bri == color_compare.bri); + } + } + + /************************************************************************************/ + + WHEN("A Color property is changed via CBOR message - light payload") { + /*An integer identifier has been encoded instead of the name of the property in order to have a shorter payload*/ + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudColor color_test = CloudColor(0.0, 0.0, 0.0); + + /*The property is added with identifier 1 that will be used instead of the string "test" as property identifier*/ + thing.addPropertyReal(color_test, "test", Permission::ReadWrite, 1); + + /* [{0: 257, 2: 2.0},{0: 513, 2: 2.0},{0: 769, 2: 2.0}] = 83 A2 00 19 01 01 02 FA 40 00 00 00 A2 00 19 02 01 02 FA 40 00 00 00 A2 00 19 03 01 02 FA 40 00 00 00 */ + uint8_t const payload[] = {0x83, 0xA2, 0x00, 0x19, 0x01, 0x01, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00, 0xA2, 0x00, 0x19, 0x02, 0x01, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00, 0xA2, 0x00, 0x19, 0x03, 0x01, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00 }; + thing.decode(payload, sizeof(payload) / sizeof(uint8_t)); + + Color color_compare = Color(2.0, 2.0, 2.0); + Color value_color_test = color_test.getValue(); + bool verify = (value_color_test == color_compare); + REQUIRE(verify); + REQUIRE(value_color_test.hue == color_compare.hue); + REQUIRE(value_color_test.sat == color_compare.sat); + REQUIRE(value_color_test.bri == color_compare.bri); + } + } + + /************************************************************************************/ + + WHEN("A ColoredLight property is changed via CBOR message") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudColoredLight color_test = CloudColoredLight(false, 0.0, 0.0, 0.0); + + thing.addPropertyReal(color_test, "test", Permission::ReadWrite); + + /* [{0: "test:swi", 4: true},{0: "test:hue", 2: 2.0},{0: "test:sat", 2: 2.0},{0: "test:bri", 2: 2.0}] = 83 A2 00 68 74 65 73 74 3A 73 77 69 04 F5 //A2 00 68 74 65 73 74 3A 68 75 65 02 FA 40 00 00 00 A2 00 68 74 65 73 74 3A 73 61 74 02 FA 40 00 00 00 A2 00 68 74 65 73 74 3A 62 72 69 02 FA 40 00 00 00 */ + uint8_t const payload[] = {0x84, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x73, 0x77, 0x69, 0x04, 0xF5, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x68, 0x75, 0x65, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x73, 0x61, 0x74, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x62, 0x72, 0x69, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00 }; + thing.decode(payload, sizeof(payload) / sizeof(uint8_t)); + + ColoredLight color_compare = ColoredLight(true, 2.0, 2.0, 2.0); + ColoredLight value_color_test = color_test.getValue(); + bool verify = (value_color_test == color_compare); + REQUIRE(verify); + REQUIRE(value_color_test.swi == color_compare.swi); + REQUIRE(value_color_test.hue == color_compare.hue); + REQUIRE(value_color_test.sat == color_compare.sat); + REQUIRE(value_color_test.bri == color_compare.bri); + } + } + + /************************************************************************************/ + + WHEN("A Television property is changed via CBOR message") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudTelevision tv_test = CloudTelevision(false, 0, false, PlaybackCommands::Stop, InputValue::AUX1, 0); + + + thing.addPropertyReal(tv_test, "test", Permission::ReadWrite); + + /* [{0: "test:swi", 4: true},{0: "test:vol", 2: 50},{0: "test:mut", 2: false},{0: "test:pbc", 2: 3},{0: "test:inp", 2: 55},{0: "test:cha", 2: 7}] = 9F A2 00 68 74 65 73 74 3A 73 77 69 04 F5 A2 00 68 74 65 73 74 3A 76 6F 6C 02 18 32 A2 00 68 74 65 73 74 3A 6D 75 74 04 F4 A2 00 68 74 65 73 74 3A 70 62 63 02 03 A2 00 68 74 65 73 74 3A 69 6E 70 02 18 37 A2 00 68 74 65 73 74 3A 63 68 61 02 07 FF */ + uint8_t const payload[] = {0x9F, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x73, 0x77, 0x69, 0x04, 0xF5, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x76, 0x6F, 0x6C, 0x02, 0x18, 0x32, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x6D, 0x75, 0x74, 0x04, 0xF4, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x70, 0x62, 0x63, 0x02, 0x03, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x69, 0x6E, 0x70, 0x02, 0x18, 0x37, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x63, 0x68, 0x61, 0x02, 0x07, 0xFF}; + thing.decode(payload, sizeof(payload) / sizeof(uint8_t)); + + Television tv_compare = Television(true, 50, false, PlaybackCommands::Play, InputValue::TV, 7); + Television value_tv_test = tv_test.getValue(); + bool verify = (value_tv_test == tv_compare); + REQUIRE(verify); + REQUIRE(value_tv_test.swi == tv_compare.swi); + REQUIRE(value_tv_test.vol == tv_compare.vol); + REQUIRE(value_tv_test.mut == tv_compare.mut); + REQUIRE(value_tv_test.pbc == tv_compare.pbc); + REQUIRE(value_tv_test.inp == tv_compare.inp); + REQUIRE(value_tv_test.cha == tv_compare.cha); + } + } + + /************************************************************************************/ + + WHEN("A DimmedLight property is changed via CBOR message") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudDimmedLight light_test = CloudDimmedLight(false, 0.0); + + thing.addPropertyReal(light_test, "test", Permission::ReadWrite); + + /* [{0: "test:swi", 4: true},{0: "test:bri", 2: 2.0}] = 83 A2 00 68 74 65 73 74 3A 73 77 69 04 F5 //A2 00 68 74 65 73 74 3A 62 72 69 02 FA 40 00 00 00 */ + uint8_t const payload[] = {0x82, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x73, 0x77, 0x69, 0x04, 0xF5, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x62, 0x72, 0x69, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00}; + thing.decode(payload, sizeof(payload) / sizeof(uint8_t)); + + DimmedLight light_compare = DimmedLight(true, 2.0); + DimmedLight value_light_test = light_test.getValue(); + bool verify = (value_light_test == light_compare); + REQUIRE(verify); + REQUIRE(value_light_test.swi == light_compare.swi); + REQUIRE(value_light_test.bri == light_compare.bri); + } + } + + /************************************************************************************/ + + WHEN("A Light property is changed via CBOR message") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudLight light_test; + light_test = false; + + thing.addPropertyReal(light_test, "test", Permission::ReadWrite); + + /* [{0: "test", 4: true}] = 81 A2 00 64 74 65 73 74 04 F5 */ + uint8_t const payload[] = {0x81, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x04, 0xF5}; + thing.decode(payload, sizeof(payload) / sizeof(uint8_t)); + + REQUIRE(light_test == true); + } + } + + /************************************************************************************/ + + WHEN("A ContactSensor property is changed via CBOR message") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudContactSensor contact_test; + contact_test = false; + + thing.addPropertyReal(contact_test, "test", Permission::ReadWrite); + + /* [{0: "test", 4: true}] = 81 A2 00 64 74 65 73 74 04 F5 */ + uint8_t const payload[] = {0x81, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x04, 0xF5}; + thing.decode(payload, sizeof(payload) / sizeof(uint8_t)); + + REQUIRE(contact_test == true); + } + } + + /************************************************************************************/ + + WHEN("A MotionSensor property is changed via CBOR message") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudMotionSensor motion_test; + motion_test = false; + + thing.addPropertyReal(motion_test, "test", Permission::ReadWrite); + + /* [{0: "test", 4: true}] = 81 A2 00 64 74 65 73 74 04 F5 */ + uint8_t const payload[] = {0x81, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x04, 0xF5}; + thing.decode(payload, sizeof(payload) / sizeof(uint8_t)); + + REQUIRE(motion_test == true); + } + } + + /************************************************************************************/ + + WHEN("A SmartPlug property is changed via CBOR message") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudSmartPlug plug_test; + plug_test = false; + + thing.addPropertyReal(plug_test, "test", Permission::ReadWrite); + + /* [{0: "test", 4: true}] = 81 A2 00 64 74 65 73 74 04 F5 */ + uint8_t const payload[] = {0x81, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x04, 0xF5}; + thing.decode(payload, sizeof(payload) / sizeof(uint8_t)); + + REQUIRE(plug_test == true); + } + } + + /************************************************************************************/ + + WHEN("A Switch property is changed via CBOR message") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudSwitch switch_test; + switch_test = false; + + thing.addPropertyReal(switch_test, "test", Permission::ReadWrite); + + /* [{0: "test", 4: true}] = 81 A2 00 64 74 65 73 74 04 F5 */ + uint8_t const payload[] = {0x81, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x04, 0xF5}; + thing.decode(payload, sizeof(payload) / sizeof(uint8_t)); + + REQUIRE(switch_test == true); + } + } + + /************************************************************************************/ + + WHEN("A Temperature property is changed via CBOR message") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudTemperature test; + test = 0.0f; + thing.addPropertyReal(test, "test", Permission::ReadWrite); + + /* [{0: "test", 2: 3.1459}] = 81 A2 00 64 74 65 73 74 02 FB 40 09 2A CD 9E 83 E4 26 */ + uint8_t const payload[] = {0x81, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0xFB, 0x40, 0x09, 0x2A, 0xCD, 0x9E, 0x83, 0xE4, 0x26}; + int const payload_length = sizeof(payload) / sizeof(uint8_t); + thing.decode(payload, payload_length); + + REQUIRE(test == Approx(3.1459).epsilon(0.01)); + } + } + + /************************************************************************************/ + + WHEN("Multiple properties is changed via CBOR message") { + GIVEN("CloudProtocol::V2") { + WHEN("Multiple properties of different type are changed via CBOR message") { + ArduinoCloudThing thing; + thing.begin(); + + CloudBool bool_test = false; + CloudInt int_test = 1; + CloudFloat float_test = 2.0f; + CloudString str_test; + str_test = ("str_test"); + + thing.addPropertyReal(bool_test, "bool_test", Permission::ReadWrite); + thing.addPropertyReal(int_test, "int_test", Permission::ReadWrite); + thing.addPropertyReal(float_test, "float_test", Permission::ReadWrite); + thing.addPropertyReal(str_test, "str_test", Permission::ReadWrite); + + /* [{0: "bool_test", 4: true}, {0: "int_test", 2: 10}, {0: "float_test", 2: 20.0}, {0: "str_test", 3: "hello arduino"}] + = 84 A2 00 69 62 6F 6F 6C 5F 74 65 73 74 04 F5 A2 00 68 69 6E 74 5F 74 65 73 74 02 0A A2 00 6A 66 6C 6F 61 74 5F 74 65 73 74 02 F9 4D 00 A2 00 68 73 74 72 5F 74 65 73 74 03 6D 68 65 6C 6C 6F 20 61 72 64 75 69 6E 6F + */ + uint8_t const payload[] = {0x84, 0xA2, 0x00, 0x69, 0x62, 0x6F, 0x6F, 0x6C, 0x5F, 0x74, 0x65, 0x73, 0x74, 0x04, 0xF5, 0xA2, 0x00, 0x68, 0x69, 0x6E, 0x74, 0x5F, 0x74, 0x65, 0x73, 0x74, 0x02, 0x0A, 0xA2, 0x00, 0x6A, 0x66, 0x6C, 0x6F, 0x61, 0x74, 0x5F, 0x74, 0x65, 0x73, 0x74, 0x02, 0xF9, 0x4D, 0x00, 0xA2, 0x00, 0x68, 0x73, 0x74, 0x72, 0x5F, 0x74, 0x65, 0x73, 0x74, 0x03, 0x6D, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6E, 0x6F}; + thing.decode(payload, sizeof(payload) / sizeof(uint8_t)); + + REQUIRE(bool_test == true); + REQUIRE(int_test == 10); + REQUIRE(float_test == Approx(20.0).epsilon(0.01)); + REQUIRE(str_test == "hello arduino"); + } + + /********************************************************************************/ + + WHEN("Multiple properties of different type are synchronized via CBOR message. FORCE_CLOUD_SYNC is passed as synchronization function and as a consequence values contained in the incoming message are stored in the properties") { + ArduinoCloudThing thing; + thing.begin(); + + CloudBool bool_test = false; + CloudInt int_test = 1; + CloudFloat float_test = 2.0f; + CloudString str_test; + str_test = ("str_test"); + + thing.addPropertyReal(bool_test, "bool_test", Permission::ReadWrite).onSync(CLOUD_WINS); + thing.addPropertyReal(int_test, "int_test", Permission::ReadWrite).onSync(CLOUD_WINS); + thing.addPropertyReal(float_test, "float_test", Permission::ReadWrite).onSync(CLOUD_WINS); + thing.addPropertyReal(str_test, "str_test", Permission::ReadWrite).onSync(CLOUD_WINS); + + /* [{0: "bool_test", 4: true}, {0: "int_test", 2: 10}, {0: "float_test", 2: 20.0}, {0: "str_test", 3: "hello arduino"}] + = 84 A2 00 69 62 6F 6F 6C 5F 74 65 73 74 04 F5 A2 00 68 69 6E 74 5F 74 65 73 74 02 0A A2 00 6A 66 6C 6F 61 74 5F 74 65 73 74 02 F9 4D 00 A2 00 68 73 74 72 5F 74 65 73 74 03 6D 68 65 6C 6C 6F 20 61 72 64 75 69 6E 6F + */ + uint8_t const payload[] = {0x84, 0xA2, 0x00, 0x69, 0x62, 0x6F, 0x6F, 0x6C, 0x5F, 0x74, 0x65, 0x73, 0x74, 0x04, 0xF5, 0xA2, 0x00, 0x68, 0x69, 0x6E, 0x74, 0x5F, 0x74, 0x65, 0x73, 0x74, 0x02, 0x0A, 0xA2, 0x00, 0x6A, 0x66, 0x6C, 0x6F, 0x61, 0x74, 0x5F, 0x74, 0x65, 0x73, 0x74, 0x02, 0xF9, 0x4D, 0x00, 0xA2, 0x00, 0x68, 0x73, 0x74, 0x72, 0x5F, 0x74, 0x65, 0x73, 0x74, 0x03, 0x6D, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6E, 0x6F}; + thing.decode(payload, sizeof(payload) / sizeof(uint8_t), true); + + REQUIRE(bool_test == true); + REQUIRE(int_test == 10); + REQUIRE(float_test == Approx(20.0).epsilon(0.01)); + REQUIRE(str_test == "hello arduino"); + } + + /********************************************************************************/ + + WHEN("Multiple primitive properties of different type are synchronized via CBOR message. FORCE_CLOUD_SYNC is passed as synchronization function and as a consequence values contained in the incoming message are stored in the properties") { + ArduinoCloudThing thing; + thing.begin(); + + int int_test = 1; + bool bool_test = false; + float float_test = 2.0f; + String str_test; + str_test = "str_test"; + + ArduinoCloudProperty *i = new CloudWrapperInt(int_test); + ArduinoCloudProperty *b = new CloudWrapperBool(bool_test); + ArduinoCloudProperty *f = new CloudWrapperFloat(float_test); + ArduinoCloudProperty *s = new CloudWrapperString(str_test); + + thing.addPropertyReal(*b, "bool_test", Permission::ReadWrite).onSync(CLOUD_WINS); + thing.addPropertyReal(*i, "int_test", Permission::ReadWrite).onSync(CLOUD_WINS); + thing.addPropertyReal(*f, "float_test", Permission::ReadWrite).onSync(CLOUD_WINS); + thing.addPropertyReal(*s, "str_test", Permission::ReadWrite).onSync(CLOUD_WINS); + + thing.updateTimestampOnLocallyChangedProperties(); + + /* [{0: "bool_test", 4: true}, {0: "int_test", 2: 10}, {0: "float_test", 2: 20.0}, {0: "str_test", 3: "hello arduino"}] + = 84 A2 00 69 62 6F 6F 6C 5F 74 65 73 74 04 F5 A2 00 68 69 6E 74 5F 74 65 73 74 02 0A A2 00 6A 66 6C 6F 61 74 5F 74 65 73 74 02 F9 4D 00 A2 00 68 73 74 72 5F 74 65 73 74 03 6D 68 65 6C 6C 6F 20 61 72 64 75 69 6E 6F + */ + uint8_t const payload[] = {0x84, 0xA2, 0x00, 0x69, 0x62, 0x6F, 0x6F, 0x6C, 0x5F, 0x74, 0x65, 0x73, 0x74, 0x04, 0xF5, 0xA2, 0x00, 0x68, 0x69, 0x6E, 0x74, 0x5F, 0x74, 0x65, 0x73, 0x74, 0x02, 0x0A, 0xA2, 0x00, 0x6A, 0x66, 0x6C, 0x6F, 0x61, 0x74, 0x5F, 0x74, 0x65, 0x73, 0x74, 0x02, 0xF9, 0x4D, 0x00, 0xA2, 0x00, 0x68, 0x73, 0x74, 0x72, 0x5F, 0x74, 0x65, 0x73, 0x74, 0x03, 0x6D, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6E, 0x6F}; + thing.decode(payload, sizeof(payload) / sizeof(uint8_t), true); + + REQUIRE(bool_test == true); + REQUIRE(int_test == 10); + REQUIRE(float_test == Approx(20.0).epsilon(0.01)); + REQUIRE(str_test == "hello arduino"); + } + + /********************************************************************************/ + + WHEN("Multiple String properties are changed via CBOR message") { + ArduinoCloudThing thing; + thing.begin(); + + CloudString str_1("hello"), + str_2("arduino"), + str_3("cloud"), + str_4("test"); + + thing.addPropertyReal(str_1, "str_1", Permission::ReadWrite); + thing.addPropertyReal(str_2, "str_2", Permission::ReadWrite); + thing.addPropertyReal(str_3, "str_3", Permission::ReadWrite); + thing.addPropertyReal(str_4, "str_4", Permission::ReadWrite); + + /* [{0: "str_1", 3: "I'd like"}, {0: "str_2", 3: "a"}, {0: "str_3", 3: "cup"}, {0: "str_4", 3: "of coffee"}] + = 84 A2 00 65 73 74 72 5F 31 03 68 49 27 64 20 6C 69 6B 65 A2 00 65 73 74 72 5F 32 03 61 61 A2 00 65 73 74 72 5F 33 03 63 63 75 70 A2 00 65 73 74 72 5F 34 03 69 6F 66 20 63 6F 66 66 65 65 + */ + uint8_t const payload[] = {0x84, 0xA2, 0x00, 0x65, 0x73, 0x74, 0x72, 0x5F, 0x31, 0x03, 0x68, 0x49, 0x27, 0x64, 0x20, 0x6C, 0x69, 0x6B, 0x65, 0xA2, 0x00, 0x65, 0x73, 0x74, 0x72, 0x5F, 0x32, 0x03, 0x61, 0x61, 0xA2, 0x00, 0x65, 0x73, 0x74, 0x72, 0x5F, 0x33, 0x03, 0x63, 0x63, 0x75, 0x70, 0xA2, 0x00, 0x65, 0x73, 0x74, 0x72, 0x5F, 0x34, 0x03, 0x69, 0x6F, 0x66, 0x20, 0x63, 0x6F, 0x66, 0x66, 0x65, 0x65}; + thing.decode(payload, sizeof(payload) / sizeof(uint8_t)); + + REQUIRE(str_1 == "I'd like"); + REQUIRE(str_2 == "a"); + REQUIRE(str_3 == "cup"); + REQUIRE(str_4 == "of coffee"); + } + } + } + + /************************************************************************************/ + + WHEN("A payload containing a CBOR base name is parsed") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudString str = "hello"; + thing.addPropertyReal(str, "test", Permission::ReadWrite); + + /* [{-2: "some-test-base-name", 0: "test", 3: "test"}] = 81 A3 21 73 73 6F 6D 65 2D 74 65 73 74 2D 62 61 73 65 2D 6E 61 6D 65 00 64 74 65 73 74 03 64 74 65 73 74 */ + uint8_t const payload[] = {0x81, 0xA3, 0x21, 0x73, 0x73, 0x6F, 0x6D, 0x65, 0x2D, 0x74, 0x65, 0x73, 0x74, 0x2D, 0x62, 0x61, 0x73, 0x65, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x03, 0x64, 0x74, 0x65, 0x73, 0x74}; + thing.decode(payload, sizeof(payload) / sizeof(uint8_t)); + + REQUIRE(str == "test"); + } + } + + /************************************************************************************/ + + WHEN("A payload containing a CBOR base time is parsed") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudInt test = 0; + thing.addPropertyReal(test, "test", Permission::ReadWrite); + + /* [{-3: 123.456, 0: "test", 2: 1}] = 81 A3 22 FB 40 5E DD 2F 1A 9F BE 77 00 64 74 65 73 74 02 01 */ + uint8_t const payload[] = {0x81, 0xA3, 0x22, 0xFB, 0x40, 0x5E, 0xDD, 0x2F, 0x1A, 0x9F, 0xBE, 0x77, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0x01}; + thing.decode(payload, sizeof(payload) / sizeof(uint8_t)); + + REQUIRE(test == 1); + } + } + + /************************************************************************************/ + + WHEN("A payload containing a CBOR time is parsed") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudInt test = 0; + thing.addPropertyReal(test, "test", Permission::ReadWrite); + + /* [{6: 123.456, 0: "test", 2: 1}] = 81 A3 06 FB 40 5E DD 2F 1A 9F BE 77 00 64 74 65 73 74 02 01 */ + uint8_t const payload[] = {0x81, 0xA3, 0x06, 0xFB, 0x40, 0x5E, 0xDD, 0x2F, 0x1A, 0x9F, 0xBE, 0x77, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0x01}; + thing.decode(payload, sizeof(payload) / sizeof(uint8_t)); + + REQUIRE(test == 1); + } + } + + /************************************************************************************/ + + WHEN("A payload containing a CBOR BaseVersion is parsed") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudInt test = 0; + thing.addPropertyReal(test, "test", Permission::ReadWrite); + + /* [{-1: 1, 0: "test", 2: 1}] = 81 A3 20 01 00 64 74 65 73 74 02 01 */ + uint8_t const payload[] = {0x81, 0xA3, 0x20, 0x01, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0x01}; + thing.decode(payload, sizeof(payload) / sizeof(uint8_t)); + + REQUIRE(test == 1); + } + } + + /************************************************************************************/ + + WHEN("A payload containing a CBOR BaseName, BaseTime and Time is parsed") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudInt test = 0; + thing.addPropertyReal(test, "test", Permission::ReadWrite); + + /* [{-2: "base-name", -3: 654.321, 6: 123.456, 0: "test", 2: 1}] = + 81 A5 21 69 62 61 73 65 2D 6E 61 6D 65 22 FB 40 84 72 91 68 72 B0 21 06 FB 40 5E DD 2F 1A 9F BE 77 00 64 74 65 73 74 02 01 + */ + uint8_t const payload[] = {0x81, 0xA5, 0x21, 0x69, 0x62, 0x61, 0x73, 0x65, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0xFB, 0x40, 0x84, 0x72, 0x91, 0x68, 0x72, 0xB0, 0x21, 0x06, 0xFB, 0x40, 0x5E, 0xDD, 0x2F, 0x1A, 0x9F, 0xBE, 0x77, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0x01}; + thing.decode(payload, sizeof(payload) / sizeof(uint8_t)); + + REQUIRE(test == 1); + } + } + + /************************************************************************************/ + + WHEN("A payload containing a invalid CBOR key is parsed") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudInt test = 0; + thing.addPropertyReal(test, "test", Permission::ReadWrite); + + /* [{123: 123, 0: "test", 2: 1}] = 81 A3 18 7B 18 7B 00 64 74 65 73 74 02 01 */ + uint8_t const payload[] = {0x81, 0xA3, 0x18, 0x7B, 0x18, 0x7B, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0x01}; + thing.decode(payload, sizeof(payload) / sizeof(uint8_t)); + + REQUIRE(test == 1); + } + } + + /************************************************************************************/ +} diff --git a/extras/test/src/test_encode.cpp b/extras/test/src/test_encode.cpp new file mode 100644 index 000000000..f81d6701c --- /dev/null +++ b/extras/test/src/test_encode.cpp @@ -0,0 +1,402 @@ +/* + Copyright (c) 2019 Arduino. All rights reserved. +*/ + +/************************************************************************************** + INCLUDE + **************************************************************************************/ + +#include + +#include +#include +#include "types/CloudWrapperBool.h" +#include "types/CloudWrapperFloat.h" +#include "types/CloudWrapperInt.h" +#include "types/CloudWrapperString.h" + +/************************************************************************************** + TEST CODE + **************************************************************************************/ + +SCENARIO("Arduino Cloud Properties are encoded", "[ArduinoCloudThing::encode]") { + /************************************************************************************/ + + WHEN("A 'bool' property is added") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + cbor::encode(thing); + + CloudBool test = true; + thing.addPropertyReal(test, "test", Permission::ReadWrite); + + /* [{0: "test", 4: true}] = 9F A2 00 64 74 65 73 74 04 F5 FF*/ + std::vector const expected = {0x9F, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x04, 0xF5, 0xFF}; + std::vector const actual = cbor::encode(thing); + REQUIRE(actual == expected); + } + } + + /************************************************************************************/ + + WHEN("A 'bool' property is added - light payload") { + /*An integer identifier must be instead of the name of the property in order to have a shorter payload*/ + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + cbor::encode(thing); + + CloudBool test = true; + /*The property is added with identifier 1 that will be used instead of the string "test" as property identifier*/ + thing.addPropertyReal(test, "test", Permission::ReadWrite, 1); + + /* [{0: 1, 4: true}] = 9F A2 00 01 04 F5 FF*/ + std::vector const expected = {0x9F, 0xA2, 0x00, 0x01, 0x04, 0xF5, 0xFF}; + std::vector const actual = cbor::encode(thing, true); + REQUIRE(actual == expected); + } + } + + /************************************************************************************/ + + WHEN("A 'int' property is added") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + cbor::encode(thing); + + CloudInt int_test = 123; + thing.addPropertyReal(int_test, "test", Permission::ReadWrite); + + /* [{0: "test", 3: 123}] = 9F A2 00 64 74 65 73 74 02 18 7B FF */ + std::vector const expected = {0x9F, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0x18, 0x7B, 0xFF}; + std::vector const actual = cbor::encode(thing); + REQUIRE(actual == expected); + } + } + + /************************************************************************************/ + + WHEN("A 'float' property is added") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + cbor::encode(thing); + + CloudFloat float_test = 3.14159; + thing.addPropertyReal(float_test, "test", Permission::ReadWrite); + + /* [{0: "test", 2: 3.141590118408203}] = 9F A2 00 64 74 65 73 74 02 FA 40 49 0F D0 FF */ + std::vector const expected = {0x9F, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0xFA, 0x40, 0x49, 0x0F, 0xD0, 0xFF}; + std::vector const actual = cbor::encode(thing); + REQUIRE(actual == expected); + } + } + + /************************************************************************************/ + + WHEN("A 'String' property is added") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + cbor::encode(thing); + + CloudString string_test; + string_test = "test"; + thing.addPropertyReal(string_test, "test", Permission::ReadWrite); + + /* [{0: "test", 3: "test"}] = 9F A2 00 64 74 65 73 74 03 64 74 65 73 74 FF*/ + std::vector const expected = {0x9F, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x03, 0x64, 0x74, 0x65, 0x73, 0x74, 0xFF}; + std::vector const actual = cbor::encode(thing); + REQUIRE(actual == expected); + } + } + + /************************************************************************************/ + + WHEN("A 'Location' property is added") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + cbor::encode(thing); + + CloudLocation location_test = CloudLocation(2.0f, 3.0f); + thing.addPropertyReal(location_test, "test", Permission::ReadWrite); + + /* [{0: "test:lat", 3: 2},{0: "test:lon", 3: 3}] = 9F A2 00 68 74 65 73 74 3A 6C 61 74 02 FA 40 00 00 00 A2 00 68 74 65 73 74 3A 6C 6F 6E 02 FA 40 40 00 00 FF*/ + std::vector const expected = { 0x9F, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x6C, 0x61, 0x74, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x6C, 0x6F, 0x6E, 0x02, 0xFA, 0x40, 0x40, 0x00, 0x00, 0xFF }; + std::vector const actual = cbor::encode(thing); + REQUIRE(actual == expected); + } + } + + /************************************************************************************/ + + WHEN("A 'Color' property is added") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + cbor::encode(thing); + + CloudColor color_test = CloudColor(2.0, 2.0, 2.0); + thing.addPropertyReal(color_test, "test", Permission::ReadWrite); + + /* [{0: "test:hue", 2: 2.0},{0: "test:sat", 2: 2.0},{0: "test:bri", 2: 2.0}] = 9F A2 00 68 74 65 73 74 3A 68 75 65 02 FA 40 00 00 00 A2 00 68 74 65 73 74 3A 73 61 74 02 FA 40 00 00 00 A2 00 68 74 65 73 74 3A 62 72 69 02 FA 40 00 00 00 FF*/ + std::vector const expected = {0x9F, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x68, 0x75, 0x65, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x73, 0x61, 0x74, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x62, 0x72, 0x69, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00, 0xFF }; + std::vector const actual = cbor::encode(thing); + REQUIRE(actual == expected); + } + } + + /************************************************************************************/ + + WHEN("A 'Color' property is added - light payload") { + /*An integer identifier must be encoded instead of the name of the property in order to have a shorter payload*/ + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + cbor::encode(thing); + + CloudColor color_test = CloudColor(2.0, 2.0, 2.0); + /*The property is added with identifier 1 that will be used instead of the string "name" as property identifier */ + thing.addPropertyReal(color_test, "test", Permission::ReadWrite, 1); + + /* [{0: 257, 2: 2.0},{0: 513, 2: 2.0},{0: 769, 2: 2.0}] = 9F A2 00 19 01 01 02 FA 40 00 00 00 A2 00 19 02 01 02 FA 40 00 00 00 A2 00 19 03 01 02 FA 40 00 00 00 FF*/ + std::vector const expected = {0x9F, 0xA2, 0x00, 0x19, 0x01, 0x01, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00, 0xA2, 0x00, 0x19, 0x02, 0x01, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00, 0xA2, 0x00, 0x19, 0x03, 0x01, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00, 0xFF }; + std::vector const actual = cbor::encode(thing, true); + REQUIRE(actual == expected); + } + } + + /************************************************************************************/ + + WHEN("A 'ColoredLight' property is added") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + cbor::encode(thing); + + CloudColoredLight color_test = CloudColoredLight(true, 2.0, 2.0, 2.0); + thing.addPropertyReal(color_test, "test", Permission::ReadWrite); + + /* [{0: "test:swi", 4: true},{0: "test:hue", 2: 2.0},{0: "test:sat", 2: 2.0},{0: "test:bri", 2: 2.0}] = 83 A2 00 68 74 65 73 74 3A 73 77 69 04 F5 //A2 00 68 74 65 73 74 3A 68 75 65 02 FA 40 00 00 00 A2 00 68 74 65 73 74 3A 73 61 74 02 FA 40 00 00 00 A2 00 68 74 65 73 74 3A 62 72 69 02 FA 40 00 00 00 FF*/ + std::vector const expected = {0x9F, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x73, 0x77, 0x69, 0x04, 0xF5, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x68, 0x75, 0x65, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x73, 0x61, 0x74, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x62, 0x72, 0x69, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00, 0xFF }; + std::vector const actual = cbor::encode(thing); + REQUIRE(actual == expected); + } + } + + /************************************************************************************/ + + WHEN("A 'Television' property is added") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + cbor::encode(thing); + + CloudTelevision tv_test = CloudTelevision(true, 50, false, PlaybackCommands::Play, InputValue::TV, 7); + thing.addPropertyReal(tv_test, "test", Permission::ReadWrite); + + /* [{0: "test:swi", 4: true},{0: "test:vol", 2: 50},{0: "test:mut", 2: false},{0: "test:pbc", 2: 3},{0: "test:inp", 2: 55},{0: "test:cha", 2: 7}] = 9F A2 00 68 74 65 73 74 3A 73 77 69 04 F5 A2 00 68 74 65 73 74 3A 76 6F 6C 02 18 32 A2 00 68 74 65 73 74 3A 6D 75 74 04 F4 A2 00 68 74 65 73 74 3A 70 62 63 02 03 A2 00 68 74 65 73 74 3A 69 6E 70 02 18 37 A2 00 68 74 65 73 74 3A 63 68 61 02 07 FF */ + std::vector const expected = {0x9F, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x73, 0x77, 0x69, 0x04, 0xF5, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x76, 0x6F, 0x6C, 0x02, 0x18, 0x32, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x6D, 0x75, 0x74, 0x04, 0xF4, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x70, 0x62, 0x63, 0x02, 0x03, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x69, 0x6E, 0x70, 0x02, 0x18, 0x37, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x63, 0x68, 0x61, 0x02, 0x07, 0xFF}; + std::vector const actual = cbor::encode(thing); + REQUIRE(actual == expected); + } + } + + /************************************************************************************/ + + WHEN("A 'DimmedLight' property is added") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + cbor::encode(thing); + + CloudDimmedLight color_test = CloudDimmedLight(true, 2.0); + thing.addPropertyReal(color_test, "test", Permission::ReadWrite); + + /* [{0: "test:swi", 4: true},{0: "test:hue", 2: 0.0},{0: "test:sat", 2: 0.0},{0: "test:bri", 2: 2.0}] = 83 A2 00 68 74 65 73 74 3A 73 77 69 04 F5 //A2 00 68 74 65 73 74 3A 68 75 65 02 FA 00 00 00 00 A2 00 68 74 65 73 74 3A 73 61 74 02 FA 00 00 00 00 A2 00 68 74 65 73 74 3A 62 72 69 02 FA 40 00 00 00 FF*/ + std::vector const expected = {0x9F, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x73, 0x77, 0x69, 0x04, 0xF5, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x68, 0x75, 0x65, 0x02, 0xFA, 0x00, 0x00, 0x00, 0x00, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x73, 0x61, 0x74, 0x02, 0xFA, 0x00, 0x00, 0x00, 0x00, 0xA2, 0x00, 0x68, 0x74, 0x65, 0x73, 0x74, 0x3A, 0x62, 0x72, 0x69, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00, 0xFF }; + std::vector const actual = cbor::encode(thing); + REQUIRE(actual == expected); + } + } + + /************************************************************************************/ + + WHEN("A light property is added") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + cbor::encode(thing); + + CloudLight test; + test = true; + thing.addPropertyReal(test, "test", Permission::ReadWrite); + + /* [{0: "test", 4: true}] = 9F A2 00 64 74 65 73 74 04 F5 FF*/ + std::vector const expected = {0x9F, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x04, 0xF5, 0xFF}; + std::vector const actual = cbor::encode(thing); + REQUIRE(actual == expected); + } + } + + /************************************************************************************/ + + WHEN("A contact sensor property is added") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + cbor::encode(thing); + + CloudContactSensor test; + test = true; + thing.addPropertyReal(test, "test", Permission::ReadWrite); + + /* [{0: "test", 4: true}] = 9F A2 00 64 74 65 73 74 04 F5 FF*/ + std::vector const expected = {0x9F, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x04, 0xF5, 0xFF}; + std::vector const actual = cbor::encode(thing); + REQUIRE(actual == expected); + } + } + + /************************************************************************************/ + + WHEN("A motion sensor property is added") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + cbor::encode(thing); + + CloudMotionSensor test; + test = true; + thing.addPropertyReal(test, "test", Permission::ReadWrite); + + /* [{0: "test", 4: true}] = 9F A2 00 64 74 65 73 74 04 F5 FF*/ + std::vector const expected = {0x9F, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x04, 0xF5, 0xFF}; + std::vector const actual = cbor::encode(thing); + REQUIRE(actual == expected); + } + } + + /************************************************************************************/ + + WHEN("A smart plug property is added") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + cbor::encode(thing); + + CloudSmartPlug test; + test = true; + thing.addPropertyReal(test, "test", Permission::ReadWrite); + + /* [{0: "test", 4: true}] = 9F A2 00 64 74 65 73 74 04 F5 FF*/ + std::vector const expected = {0x9F, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x04, 0xF5, 0xFF}; + std::vector const actual = cbor::encode(thing); + REQUIRE(actual == expected); + } + } + + /************************************************************************************/ + + WHEN("A Temperature property is added") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + cbor::encode(thing); + + CloudTemperature float_test; + float_test = 3.14159; + thing.addPropertyReal(float_test, "test", Permission::ReadWrite); + + /* [{0: "test", 2: 3.141590118408203}] = 9F A2 00 64 74 65 73 74 02 FA 40 49 0F D0 FF */ + std::vector const expected = {0x9F, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0xFA, 0x40, 0x49, 0x0F, 0xD0, 0xFF}; + std::vector const actual = cbor::encode(thing); + REQUIRE(actual == expected); + } + } + + /************************************************************************************/ + + WHEN("A switch property is added") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + cbor::encode(thing); + + CloudSwitch test; + test = true; + thing.addPropertyReal(test, "test", Permission::ReadWrite); + + /* [{0: "test", 4: true}] = 9F A2 00 64 74 65 73 74 04 F5 FF*/ + std::vector const expected = {0x9F, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x04, 0xF5, 0xFF}; + std::vector const actual = cbor::encode(thing); + REQUIRE(actual == expected); + } + } + + /************************************************************************************/ + + WHEN("Multiple properties are added") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudInt int_test = 1; + CloudBool bool_test = false; + CloudFloat float_test = 2.0f; + CloudString str_test; + str_test = "str_test"; + + thing.addPropertyReal(int_test, "int_test", Permission::ReadWrite); + thing.addPropertyReal(bool_test, "bool_test", Permission::ReadWrite); + thing.addPropertyReal(float_test, "float_test", Permission::ReadWrite); + thing.addPropertyReal(str_test, "str_test", Permission::ReadWrite); + + /* [{0: "int_test", 2: 1}, {0: "bool_test", 4: false}, {0: "float_test", 2: 2.0}, {0: "str_test", 3: "str_test"}] + = 9F A2 00 68 69 6E 74 5F 74 65 73 74 02 01 A2 00 6A 66 6C 6F 61 74 5F 74 65 73 74 02 FA A2 00 6A 66 6C 6F 61 74 5F 74 65 73 74 02 FA 40 00 00 00 A2 00 68 73 74 72 5F 74 65 73 74 03 68 73 74 72 5F 74 65 73 74 FF + */ + std::vector const expected = {0x9F, 0xA2, 0x00, 0x68, 0x69, 0x6E, 0x74, 0x5F, 0x74, 0x65, 0x73, 0x74, 0x02, 0x01, 0xA2, 0x00, 0x69, 0x62, 0x6F, 0x6F, 0x6C, 0x5F, 0x74, 0x65, 0x73, 0x74, 0x04, 0xF4, 0xA2, 0x00, 0x6A, 0x66, 0x6C, 0x6F, 0x61, 0x74, 0x5F, 0x74, 0x65, 0x73, 0x74, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00, 0xA2, 0x00, 0x68, 0x73, 0x74, 0x72, 0x5F, 0x74, 0x65, 0x73, 0x74, 0x03, 0x68, 0x73, 0x74, 0x72, 0x5F, 0x74, 0x65, 0x73, 0x74, 0xFF}; + std::vector const actual = cbor::encode(thing); + REQUIRE(actual == expected); + } + } + + /************************************************************************************/ + + WHEN("Multiple primitive properties are added") { + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + int int_test = 1; + bool bool_test = false; + float float_test = 2.0f; + String str_test; + str_test = "str_test"; + + ArduinoCloudProperty *i = new CloudWrapperInt(int_test); + ArduinoCloudProperty *b = new CloudWrapperBool(bool_test); + ArduinoCloudProperty *f = new CloudWrapperFloat(float_test); + ArduinoCloudProperty *s = new CloudWrapperString(str_test); + + thing.addPropertyReal(*i, "int_test", Permission::ReadWrite); + thing.addPropertyReal(*b, "bool_test", Permission::ReadWrite); + thing.addPropertyReal(*f, "float_test", Permission::ReadWrite); + thing.addPropertyReal(*s, "str_test", Permission::ReadWrite); + + thing.updateTimestampOnLocallyChangedProperties(); + + /* [{0: "int_test", 2: 1}, {0: "bool_test", 4: false}, {0: "float_test", 2: 2.0}, {0: "str_test", 3: "str_test"}] + = 9F A2 00 68 69 6E 74 5F 74 65 73 74 02 01 A2 00 6A 66 6C 6F 61 74 5F 74 65 73 74 02 FA A2 00 6A 66 6C 6F 61 74 5F 74 65 73 74 02 FA 40 00 00 00 A2 00 68 73 74 72 5F 74 65 73 74 03 68 73 74 72 5F 74 65 73 74 FF + */ + std::vector const expected = {0x9F, 0xA2, 0x00, 0x68, 0x69, 0x6E, 0x74, 0x5F, 0x74, 0x65, 0x73, 0x74, 0x02, 0x01, 0xA2, 0x00, 0x69, 0x62, 0x6F, 0x6F, 0x6C, 0x5F, 0x74, 0x65, 0x73, 0x74, 0x04, 0xF4, 0xA2, 0x00, 0x6A, 0x66, 0x6C, 0x6F, 0x61, 0x74, 0x5F, 0x74, 0x65, 0x73, 0x74, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00, 0xA2, 0x00, 0x68, 0x73, 0x74, 0x72, 0x5F, 0x74, 0x65, 0x73, 0x74, 0x03, 0x68, 0x73, 0x74, 0x72, 0x5F, 0x74, 0x65, 0x73, 0x74, 0xFF}; + std::vector const actual = cbor::encode(thing); + REQUIRE(actual == expected); + } + } + + /************************************************************************************/ +} diff --git a/extras/test/src/test_publishEvery.cpp b/extras/test/src/test_publishEvery.cpp new file mode 100644 index 000000000..0de63ad41 --- /dev/null +++ b/extras/test/src/test_publishEvery.cpp @@ -0,0 +1,63 @@ +/* + Copyright (c) 2019 Arduino. All rights reserved. +*/ + +/************************************************************************************** + INCLUDE + **************************************************************************************/ + +#include + +#include +#include + +/************************************************************************************** + TEST CODE + **************************************************************************************/ + +SCENARIO("A Arduino cloud property is published periodically", "[ArduinoCloudThing::publishEvery]") { + /************************************************************************************/ + + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudBool test = true; + unsigned long const PUBLISH_INTERVAL_SEC = 1 * SECONDS; + + thing.addPropertyReal(test, "test", Permission::ReadWrite).publishEvery(PUBLISH_INTERVAL_SEC); + + WHEN("t = 0 ms, publish interval = 1000 ms, 1st call to 'encode'") { + set_millis(0); + THEN("'encode' should encode the property") { + REQUIRE(cbor::encode(thing).size() != 0); + WHEN("t = 999 ms") { + set_millis(999); + THEN("'encode' should not encode the property") { + REQUIRE(cbor::encode(thing).size() == 0); + WHEN("t = 1000 ms") { + set_millis(1000); + THEN("'encode' should encode the property") { + REQUIRE(cbor::encode(thing).size() != 0); + WHEN("t = 1999 ms") { + set_millis(1999); + THEN("'encode' should not encode the property") { + REQUIRE(cbor::encode(thing).size() == 0); + WHEN("t = 2000 ms") { + set_millis(2000); + THEN("'encode' should encode the property") { + REQUIRE(cbor::encode(thing).size() != 0); + } + } + } + } + } + } + } + } + } + } + } + + /************************************************************************************/ +} diff --git a/extras/test/src/test_publishOnChange.cpp b/extras/test/src/test_publishOnChange.cpp new file mode 100644 index 000000000..a96f508c1 --- /dev/null +++ b/extras/test/src/test_publishOnChange.cpp @@ -0,0 +1,50 @@ +/* + Copyright (c) 2019 Arduino. All rights reserved. +*/ + +/************************************************************************************** + INCLUDE + **************************************************************************************/ + +#include + +#include +#include + +/************************************************************************************** + TEST CODE + **************************************************************************************/ + +SCENARIO("A Arduino cloud property is published on value change", "[ArduinoCloudThing::publishOnChange]") { + /************************************************************************************/ + + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudInt test = 10; + int const DELTA = 6; + + thing.addPropertyReal(test, "test", Permission::ReadWrite).publishOnChange(DELTA); + + WHEN("test = 10, delta = 6, the property is encoded for the 1st time") { + THEN("The property should be encoded") { + REQUIRE(cbor::encode(thing).size() != 0); + WHEN("test +=4 -> test = 14") { + test += 4; + THEN("Since the increment since the last update (4) is smaller than the delta of 6 the property should not be encoded") { + REQUIRE(cbor::encode(thing).size() == 0); + WHEN("test +=4 -> test = 18") { + test += 4; + THEN("Since the increment since the last update (8) is greater than the delta of 6 the property should be encoded") { + REQUIRE(cbor::encode(thing).size() != 0); + } + } + } + } + } + } + } + + /************************************************************************************/ +} diff --git a/extras/test/src/test_publishOnChangeRateLimit.cpp b/extras/test/src/test_publishOnChangeRateLimit.cpp new file mode 100644 index 000000000..ab52afa3c --- /dev/null +++ b/extras/test/src/test_publishOnChangeRateLimit.cpp @@ -0,0 +1,68 @@ +/* + Copyright (c) 2019 Arduino. All rights reserved. +*/ + +/************************************************************************************** + INCLUDE + **************************************************************************************/ + +#include + +#include +#include + +/************************************************************************************** + TEST CODE + **************************************************************************************/ + +SCENARIO("A Arduino cloud property is published on value change but the update rate is limited", "[ArduinoCloudThing::publishOnChange]") { + /************************************************************************************/ + + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudInt test = 0; + int const MIN_DELTA = 0; + unsigned long const MIN_TIME_BETWEEN_UPDATES_ms = 500; /* No updates faster than 500 ms */ + + thing.addPropertyReal(test, "test", Permission::ReadWrite).publishOnChange(MIN_DELTA, MIN_TIME_BETWEEN_UPDATES_ms); + + WHEN("t = 0 ms, min time between updates = 500 ms, property not modified, 1st call to 'encode'") { + set_millis(0); + THEN("'encode' should encode the property") { + REQUIRE(cbor::encode(thing).size() != 0); + WHEN("t = 499 ms, property modified") { + test++; + set_millis(499); + THEN("'encode' should not encode any property") { + REQUIRE(cbor::encode(thing).size() == 0); + WHEN("t = 500 ms, property modified") { + test++; + set_millis(500); + THEN("'encode' should encode the property") { + REQUIRE(cbor::encode(thing).size() != 0); + WHEN("t = 999 ms, property modified") { + test++; + set_millis(999); + THEN("'encode' should not encode any property") { + REQUIRE(cbor::encode(thing).size() == 0); + WHEN("t = 1000 ms, property modified") { + test++; + set_millis(1000); + THEN("'encode' should encode the property") { + REQUIRE(cbor::encode(thing).size() != 0); + } + } + } + } + } + } + } + } + } + } + } + + /************************************************************************************/ +} diff --git a/extras/test/src/test_readOnly.cpp b/extras/test/src/test_readOnly.cpp new file mode 100644 index 000000000..a090d512e --- /dev/null +++ b/extras/test/src/test_readOnly.cpp @@ -0,0 +1,37 @@ +/* + Copyright (c) 2019 Arduino. All rights reserved. +*/ + +/************************************************************************************** + INCLUDE + **************************************************************************************/ + +#include + +#include +#include + +/************************************************************************************** + TEST CODE + **************************************************************************************/ + +SCENARIO("A Arduino cloud property is marked 'read only'", "[ArduinoCloudThing::decode]") { + /************************************************************************************/ + + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudInt test = 0; + thing.addPropertyReal(test, "test", Permission::Read); + + /* [{0: "test", 2: 7}] = 81 A2 00 64 74 65 73 74 02 07 */ + uint8_t const payload[] = {0x81, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0x07}; + int const payload_length = sizeof(payload) / sizeof(uint8_t); + thing.decode(payload, payload_length); + + REQUIRE(test == 0); + } + + /************************************************************************************/ +} diff --git a/extras/test/src/test_writeOnly.cpp b/extras/test/src/test_writeOnly.cpp new file mode 100644 index 000000000..187cab4bb --- /dev/null +++ b/extras/test/src/test_writeOnly.cpp @@ -0,0 +1,30 @@ +/* + Copyright (c) 2019 Arduino. All rights reserved. +*/ + +/************************************************************************************** + INCLUDE + **************************************************************************************/ + +#include + +#include +#include + +/************************************************************************************** + TEST CODE + **************************************************************************************/ + +SCENARIO("A Arduino cloud property is marked 'write only'", "[ArduinoCloudThing::encode]") { + /************************************************************************************/ + + ArduinoCloudThing thing; + thing.begin(); + + CloudInt test = 0; + thing.addPropertyReal(test, "test", Permission::Write); + + REQUIRE(cbor::encode(thing).size() == 0); /* Since 'test' is 'write only' it should not be encoded */ + + /************************************************************************************/ +} diff --git a/extras/test/src/util/CBORTestUtil.cpp b/extras/test/src/util/CBORTestUtil.cpp new file mode 100644 index 000000000..2599fc453 --- /dev/null +++ b/extras/test/src/util/CBORTestUtil.cpp @@ -0,0 +1,53 @@ +/* + Copyright (c) 2019 Arduino. All rights reserved. +*/ + +/************************************************************************************** + INCLUDE + **************************************************************************************/ + +#include + +#include +#include + +/************************************************************************************** + NAMESPACE + **************************************************************************************/ + +namespace cbor +{ + +/************************************************************************************** + PUBLIC FUNCTIONS + **************************************************************************************/ + +std::vector encode(ArduinoCloudThing & thing, bool lightPayload) { + uint8_t buf[200] = {0}; + int const bytes_buf = thing.encode(buf, 200, lightPayload); + if (bytes_buf == -1) { + return std::vector(); + } else { + return std::vector(buf, buf + bytes_buf); + } +} + +void print(std::vector const & vect) { + for (auto i = vect.begin(); i != vect.end(); i++) { + std::cout << std::uppercase + << std::hex + << std::setw(2) + << std::setfill('0') + << static_cast(*i) + << std::dec + << std::nouppercase + << " "; + } + std::cout << std::endl; +} + +/************************************************************************************** + NAMESPACE + **************************************************************************************/ + +} /* cbor */ diff --git a/extras/test/src/OTATestData.cpp b/extras/test/src/util/OTATestUtil.cpp similarity index 80% rename from extras/test/src/OTATestData.cpp rename to extras/test/src/util/OTATestUtil.cpp index e229e3bec..ac7e26026 100644 --- a/extras/test/src/OTATestData.cpp +++ b/extras/test/src/util/OTATestUtil.cpp @@ -6,12 +6,19 @@ INCLUDE **************************************************************************************/ -#include +#include #include #include +/************************************************************************************** + NAMESPACE + **************************************************************************************/ + +namespace ota +{ + /************************************************************************************** FUNCTION DEFINITION **************************************************************************************/ @@ -58,3 +65,9 @@ void generate_invalid_ota_data_crc_wrong(OTAData & ota_data) /* Generate CRC */ ota_data.data.crc32 = 0xDEADBEEF; } + +/************************************************************************************** + NAMESPACE + **************************************************************************************/ + +} /* ota */ diff --git a/library.properties b/library.properties index 181336ec6..716cb1be1 100644 --- a/library.properties +++ b/library.properties @@ -8,4 +8,4 @@ category=Communication url=https://github.com/arduino-libraries/ArduinoIoTCloud architectures=samd,esp8266 includes=ArduinoIoTCloud.h -depends=Arduino_ConnectionHandler,Arduino_DebugUtils,ArduinoCloudThing,ArduinoMqttClient,ArduinoBearSSL,ArduinoECCX08,RTCZero +depends=Arduino_ConnectionHandler,Arduino_DebugUtils,ArduinoMqttClient,ArduinoBearSSL,ArduinoECCX08,RTCZero diff --git a/src/ArduinoIoTCloud.h b/src/ArduinoIoTCloud.h index 23234986c..7933b167c 100644 --- a/src/ArduinoIoTCloud.h +++ b/src/ArduinoIoTCloud.h @@ -24,14 +24,15 @@ #include -#include #include #include -#include "types/CloudWrapperBool.h" -#include "types/CloudWrapperFloat.h" -#include "types/CloudWrapperInt.h" -#include "types/CloudWrapperString.h" +#include "cbor/ArduinoCloudThing.h" + +#include "cbor/types/CloudWrapperBool.h" +#include "cbor/types/CloudWrapperFloat.h" +#include "cbor/types/CloudWrapperInt.h" +#include "cbor/types/CloudWrapperString.h" #include "CloudSerial.h" diff --git a/src/cbor/ArduinoCloudProperty.cpp b/src/cbor/ArduinoCloudProperty.cpp new file mode 100644 index 000000000..6dfdd8997 --- /dev/null +++ b/src/cbor/ArduinoCloudProperty.cpp @@ -0,0 +1,278 @@ +// +// This file is part of ArduinoCloudThing +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of ArduinoCloudThing. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +#include "ArduinoCloudProperty.h" + +#ifndef ARDUINO_ARCH_SAMD + #pragma message "No RTC available on this architecture - ArduinoIoTCloud will not keep track of local change timestamps ." +#endif + +/****************************************************************************** + CTOR/DTOR + ******************************************************************************/ +ArduinoCloudProperty::ArduinoCloudProperty() + : _name(""), + _min_delta_property(0.0f), + _min_time_between_updates_millis(0), + _permission(Permission::Read), + _get_time_func{nullptr}, + _update_callback_func(nullptr), + _sync_callback_func(nullptr), + _has_been_updated_once(false), + _has_been_modified_in_callback(false), + _last_updated_millis(0), + _update_interval_millis(0), + _last_local_change_timestamp(0), + _last_cloud_change_timestamp(0), + _map_data_list(nullptr), + _identifier(0), + _attributeIdentifier(0), + _lightPayload(false) { +} + +/****************************************************************************** + PUBLIC MEMBER FUNCTIONS + ******************************************************************************/ +void ArduinoCloudProperty::init(String const name, Permission const permission, GetTimeCallbackFunc func) { + _name = name; + _permission = permission; + _get_time_func = func; +} + +ArduinoCloudProperty & ArduinoCloudProperty::onUpdate(UpdateCallbackFunc func) { + _update_callback_func = func; + return (*this); +} + +ArduinoCloudProperty & ArduinoCloudProperty::onSync(SyncCallbackFunc func) { + _sync_callback_func = func; + return (*this); +} + +ArduinoCloudProperty & ArduinoCloudProperty::publishOnChange(float const min_delta_property, unsigned long const min_time_between_updates_millis) { + _update_policy = UpdatePolicy::OnChange; + _min_delta_property = min_delta_property; + _min_time_between_updates_millis = min_time_between_updates_millis; + return (*this); +} + +ArduinoCloudProperty & ArduinoCloudProperty::publishEvery(unsigned long const seconds) { + _update_policy = UpdatePolicy::TimeInterval; + _update_interval_millis = (seconds * 1000); + return (*this); +} + +bool ArduinoCloudProperty::shouldBeUpdated() { + if (!_has_been_updated_once) { + return true; + } + + if (_has_been_modified_in_callback) { + _has_been_modified_in_callback = false; + return true; + } + + if (_update_policy == UpdatePolicy::OnChange) { + return (isDifferentFromCloud() && ((millis() - _last_updated_millis) >= (_min_time_between_updates_millis))); + } else if (_update_policy == UpdatePolicy::TimeInterval) { + return ((millis() - _last_updated_millis) >= _update_interval_millis); + } else { + return false; + } +} + +void ArduinoCloudProperty::execCallbackOnChange() { + if (_update_callback_func != NULL) { + _update_callback_func(); + } + if (!isDifferentFromCloud()) { + _has_been_modified_in_callback = true; + } +} + +void ArduinoCloudProperty::execCallbackOnSync() { + if (_sync_callback_func != NULL) { + _sync_callback_func(*this); + } +} + +void ArduinoCloudProperty::append(CborEncoder *encoder, bool lightPayload) { + _lightPayload = lightPayload; + _attributeIdentifier = 0; + appendAttributesToCloudReal(encoder); + fromLocalToCloud(); + _has_been_updated_once = true; + _last_updated_millis = millis(); +} + +void ArduinoCloudProperty::appendAttributeReal(bool value, String attributeName, CborEncoder *encoder) { + appendAttributeName(attributeName, [value](CborEncoder & mapEncoder) { + cbor_encode_int(&mapEncoder, static_cast(CborIntegerMapKey::BooleanValue)); + cbor_encode_boolean(&mapEncoder, value); + }, encoder); +} + +void ArduinoCloudProperty::appendAttributeReal(int value, String attributeName, CborEncoder *encoder) { + appendAttributeName(attributeName, [value](CborEncoder & mapEncoder) { + cbor_encode_int(&mapEncoder, static_cast(CborIntegerMapKey::Value)); + cbor_encode_int(&mapEncoder, value); + }, encoder); +} + +void ArduinoCloudProperty::appendAttributeReal(float value, String attributeName, CborEncoder *encoder) { + appendAttributeName(attributeName, [value](CborEncoder & mapEncoder) { + cbor_encode_int(&mapEncoder, static_cast(CborIntegerMapKey::Value)); + cbor_encode_float(&mapEncoder, value); + }, encoder); +} + +void ArduinoCloudProperty::appendAttributeReal(String value, String attributeName, CborEncoder *encoder) { + appendAttributeName(attributeName, [value](CborEncoder & mapEncoder) { + cbor_encode_int(&mapEncoder, static_cast(CborIntegerMapKey::StringValue)); + cbor_encode_text_stringz(&mapEncoder, value.c_str()); + }, encoder); +} + +void ArduinoCloudProperty::appendAttributeName(String attributeName, std::functionappendValue, CborEncoder *encoder) { + if (attributeName != "") { + // when the attribute name string is not empty, the attribute identifier is incremented in order to be encoded in the message if the _lightPayload flag is set + _attributeIdentifier++; + } + CborEncoder mapEncoder; + cbor_encoder_create_map(encoder, &mapEncoder, 2); + cbor_encode_int(&mapEncoder, static_cast(CborIntegerMapKey::Name)); + + // if _lightPayload is true, the property and attribute identifiers will be encoded instead of the property name + if (_lightPayload) { + // the most significant byte of the identifier to be encoded represent the property identifier + int completeIdentifier = _attributeIdentifier * 256; + // the least significant byte of the identifier to be encoded represent the attribute identifier + completeIdentifier += _identifier; + cbor_encode_int(&mapEncoder, completeIdentifier); + } else { + String completeName = _name; + if (attributeName != "") { + completeName += ":" + attributeName; + } + cbor_encode_text_stringz(&mapEncoder, completeName.c_str()); + } + appendValue(mapEncoder); + cbor_encoder_close_container(encoder, &mapEncoder); +} + +void ArduinoCloudProperty::setAttributesFromCloud(LinkedList *map_data_list) { + _map_data_list = map_data_list; + _attributeIdentifier = 0; + setAttributesFromCloud(); +} + +void ArduinoCloudProperty::setAttributeReal(bool& value, String attributeName) { + setAttributeReal(attributeName, [&value](CborMapData * md) { + // Manage the case to have boolean values received as integers 0/1 + if (md->bool_val.isSet()) { + value = md->bool_val.get(); + } else if (md->val.isSet()) { + if (md->val.get() == 0) { + value = false; + } else if (md->val.get() == 1) { + value = true; + } else { + /* This should not happen. Leave the previous value */ + } + } + }); +} + +void ArduinoCloudProperty::setAttributeReal(int& value, String attributeName) { + setAttributeReal(attributeName, [&value](CborMapData * md) { + value = md->val.get(); + }); +} + +void ArduinoCloudProperty::setAttributeReal(float& value, String attributeName) { + setAttributeReal(attributeName, [&value](CborMapData * md) { + value = md->val.get(); + }); +} + +void ArduinoCloudProperty::setAttributeReal(String& value, String attributeName) { + setAttributeReal(attributeName, [&value](CborMapData * md) { + value = md->str_val.get(); + }); +} + +void ArduinoCloudProperty::setAttributeReal(String attributeName, std::functionsetValue) { + if (attributeName != "") { + _attributeIdentifier++; + } + for (int i = 0; i < _map_data_list->size(); i++) { + CborMapData *map = _map_data_list->get(i); + if (map != nullptr) { + if (map->light_payload.isSet() && map->light_payload.get()) { + // if a light payload is detected, the attribute identifier is retrieved from the cbor map and the corresponding attribute is updated + int attid = map->attribute_identifier.get(); + if (attid == _attributeIdentifier) { + setValue(map); + break; + } + } else { + // if a normal payload is detected, the name of the attribute to be updated is extracted directly from the cbor map + String an = map->attribute_name.get(); + if (an == attributeName) { + setValue(map); + break; + } + } + } + } + +} + +String ArduinoCloudProperty::getAttributeName(String propertyName, char separator) { + int colonPos; + String attributeName = ""; + (colonPos = propertyName.indexOf(separator)) != -1 ? attributeName = propertyName.substring(colonPos + 1) : ""; + return attributeName; +} + +void ArduinoCloudProperty::updateLocalTimestamp() { + if (isReadableByCloud()) { + if (_get_time_func) { + _last_local_change_timestamp = _get_time_func(); + } + } +} + +void ArduinoCloudProperty::setLastCloudChangeTimestamp(unsigned long cloudChangeEventTime) { + _last_cloud_change_timestamp = cloudChangeEventTime; +} + +void ArduinoCloudProperty::setLastLocalChangeTimestamp(unsigned long localChangeTime) { + _last_local_change_timestamp = localChangeTime; +} + +unsigned long ArduinoCloudProperty::getLastCloudChangeTimestamp() { + return _last_cloud_change_timestamp; +} + +unsigned long ArduinoCloudProperty::getLastLocalChangeTimestamp() { + return _last_local_change_timestamp; +} + +void ArduinoCloudProperty::setIdentifier(int identifier) { + _identifier = identifier; +} diff --git a/src/cbor/ArduinoCloudProperty.h b/src/cbor/ArduinoCloudProperty.h new file mode 100644 index 000000000..8d961f465 --- /dev/null +++ b/src/cbor/ArduinoCloudProperty.h @@ -0,0 +1,225 @@ +// +// This file is part of ArduinoCloudThing +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of ArduinoCloudThing. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +#ifndef ARDUINO_CLOUD_PROPERTY_HPP_ +#define ARDUINO_CLOUD_PROPERTY_HPP_ + +#ifdef HOST + #define substring(...) substr(__VA_ARGS__) + #define indexOf(x) find(x) +#endif + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + + +#include +// in order to allow to define its own max and min functions +#undef max +#undef min +#include + +#include "lib/tinycbor/cbor-lib.h" +#include "lib/LinkedList/LinkedList.h" + +#define appendAttributesToCloud() appendAttributesToCloudReal(CborEncoder *encoder) +#define appendAttribute(x) appendAttributeReal(x, getAttributeName(#x, '.'), encoder) +#define setAttribute(x) setAttributeReal(x, getAttributeName(#x, '.')) + +/****************************************************************************** + ENUM + ******************************************************************************/ +/* Source: https://tools.ietf.org/html/rfc8428#section-6 */ +enum class CborIntegerMapKey : int { + BaseVersion = -1, /* bver */ + BaseName = -2, /* bn */ + BaseTime = -3, /* bt */ + BaseUnit = -4, /* bu */ + BaseValue = -5, /* bv */ + BaseSum = -6, /* bs */ + Name = 0, /* n */ + Unit = 1, /* u */ + Value = 2, /* v */ + StringValue = 3, /* vs */ + BooleanValue = 4, /* vb */ + Sum = 5, /* s */ + Time = 6, /* t */ + UpdateTime = 7, /* ut */ + DataValue = 8 /* vd */ +}; + +template +class MapEntry { + public: + + MapEntry() : _is_set(false) { } + + inline void set(T const & entry) { + _entry = entry; + _is_set = true; + } + inline T const get() const { + return _entry; + } + + inline bool isSet() const { + return _is_set; + } + inline void reset() { + _is_set = false; + } + + private: + + T _entry; + bool _is_set; + +}; + +class CborMapData { + + public: + MapEntry base_version; + MapEntry base_name; + MapEntry base_time; + MapEntry name; + MapEntry name_identifier; + MapEntry light_payload; + MapEntry attribute_name; + MapEntry attribute_identifier; + MapEntry property_identifier; + MapEntry val; + MapEntry str_val; + MapEntry bool_val; + MapEntry time; +}; + +enum class Permission { + Read, Write, ReadWrite +}; + +enum class Type { + Bool, Int, Float, String +}; + +enum class UpdatePolicy { + OnChange, TimeInterval +}; + +typedef void(*UpdateCallbackFunc)(void); +typedef unsigned long(*GetTimeCallbackFunc)(); + +/****************************************************************************** + CLASS DECLARATION + ******************************************************************************/ + +class ArduinoCloudProperty { + typedef void(*SyncCallbackFunc)(ArduinoCloudProperty &property); + public: + ArduinoCloudProperty(); + void init(String const name, Permission const permission, GetTimeCallbackFunc func); + + /* Composable configuration of the ArduinoCloudProperty class */ + ArduinoCloudProperty & onUpdate(UpdateCallbackFunc func); + ArduinoCloudProperty & onSync(SyncCallbackFunc func); + ArduinoCloudProperty & publishOnChange(float const min_delta_property, unsigned long const min_time_between_updates_millis = 0); + ArduinoCloudProperty & publishEvery(unsigned long const seconds); + + inline String name() const { + return _name; + } + inline int identifier() const { + return _identifier; + } + inline bool isReadableByCloud() const { + return (_permission == Permission::Read) || (_permission == Permission::ReadWrite); + } + inline bool isWriteableByCloud() const { + return (_permission == Permission::Write) || (_permission == Permission::ReadWrite); + } + + bool shouldBeUpdated(); + void execCallbackOnChange(); + void execCallbackOnSync(); + void setLastCloudChangeTimestamp(unsigned long cloudChangeTime); + void setLastLocalChangeTimestamp(unsigned long localChangeTime); + unsigned long getLastCloudChangeTimestamp(); + unsigned long getLastLocalChangeTimestamp(); + void setIdentifier(int identifier); + + void updateLocalTimestamp(); + void append(CborEncoder * encoder, bool lightPayload); + void appendAttributeReal(bool value, String attributeName = "", CborEncoder *encoder = nullptr); + void appendAttributeReal(int value, String attributeName = "", CborEncoder *encoder = nullptr); + void appendAttributeReal(float value, String attributeName = "", CborEncoder *encoder = nullptr); + void appendAttributeReal(String value, String attributeName = "", CborEncoder *encoder = nullptr); + void appendAttributeName(String attributeName, std::functionf, CborEncoder *encoder); + void setAttributesFromCloud(LinkedList *map_data_list); + void setAttributeReal(bool& value, String attributeName = ""); + void setAttributeReal(int& value, String attributeName = ""); + void setAttributeReal(float& value, String attributeName = ""); + void setAttributeReal(String& value, String attributeName = ""); + void setAttributeReal(String attributeName, std::functionsetValue); + String getAttributeName(String propertyName, char separator); + + virtual bool isDifferentFromCloud() = 0; + virtual void fromCloudToLocal() = 0; + virtual void fromLocalToCloud() = 0; + virtual void appendAttributesToCloudReal(CborEncoder *encoder) = 0; + virtual void setAttributesFromCloud() = 0; + virtual bool isPrimitive() { + return false; + }; + protected: + /* Variables used for UpdatePolicy::OnChange */ + String _name; + float _min_delta_property; + unsigned long _min_time_between_updates_millis; + + private: + Permission _permission; + GetTimeCallbackFunc _get_time_func; + UpdateCallbackFunc _update_callback_func; + void (*_sync_callback_func)(ArduinoCloudProperty &property); + + UpdatePolicy _update_policy; + bool _has_been_updated_once, + _has_been_modified_in_callback; + /* Variables used for UpdatePolicy::TimeInterval */ + unsigned long _last_updated_millis, + _update_interval_millis; + /* Variables used for reconnection sync*/ + unsigned long _last_local_change_timestamp; + unsigned long _last_cloud_change_timestamp; + LinkedList * _map_data_list; + /* Store the identifier of the property in the array list */ + int _identifier; + int _attributeIdentifier; + /* Indicates if the property shall be encoded using the identifier instead of the name */ + bool _lightPayload; +}; + +/****************************************************************************** + PROTOTYPE FREE FUNCTIONs + ******************************************************************************/ + +inline bool operator == (ArduinoCloudProperty const & lhs, ArduinoCloudProperty const & rhs) { + return (lhs.name() == rhs.name()); +} + +#endif /* ARDUINO_CLOUD_PROPERTY_HPP_ */ diff --git a/src/cbor/ArduinoCloudThing.cpp b/src/cbor/ArduinoCloudThing.cpp new file mode 100644 index 000000000..e52b7e097 --- /dev/null +++ b/src/cbor/ArduinoCloudThing.cpp @@ -0,0 +1,574 @@ +// +// This file is part of ArduinoCloudThing +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of ArduinoCloudThing. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include + +#include "ArduinoCloudThing.h" + +/****************************************************************************** + DEBUG FUNCTIONS + ******************************************************************************/ + +#if defined(DEBUG_MEMORY) && defined(ARDUINO_ARCH_SAMD) +extern "C" char *sbrk(int i); +void PrintFreeRam(void) { + char stack_dummy = 0; + //Serial.print("Free RAM: "); //Serial.println(&stack_dummy - sbrk(0)); +} +#endif + +#ifdef ARDUINO_ARCH_MRAA + #define Serial DebugSerial +#endif + +/****************************************************************************** + CTOR/DTOR + ******************************************************************************/ + +ArduinoCloudThing::ArduinoCloudThing() : + _get_time_func{nullptr}, + _numPrimitivesProperties(0), + _numProperties(0), + _isSyncMessage(false), + _currentPropertyName(""), + _currentPropertyBaseTime(0), + _currentPropertyTime(0) +{} + +/****************************************************************************** + PUBLIC MEMBER FUNCTIONS + ******************************************************************************/ + +void ArduinoCloudThing::begin() { +} + +void ArduinoCloudThing::registerGetTimeCallbackFunc(GetTimeCallbackFunc func) { + _get_time_func = func; +} + +int ArduinoCloudThing::encode(uint8_t * data, size_t const size, bool lightPayload) { + + // check if backing storage and cloud has diverged + // time interval may be elapsed or property may be changed + CborEncoder encoder, arrayEncoder; + + cbor_encoder_init(&encoder, data, size, 0); + + if (cbor_encoder_create_array(&encoder, &arrayEncoder, CborIndefiniteLength) != CborNoError) { + return -1; + } + + if (appendChangedProperties(&arrayEncoder, lightPayload) < 1) { + return -1; + } + + if (cbor_encoder_close_container(&encoder, &arrayEncoder) != CborNoError) { + return -1; + } + + #if defined(DEBUG_MEMORY) && defined(ARDUINO_ARCH_SAMD) + PrintFreeRam(); + #endif + int const bytes_encoded = cbor_encoder_get_buffer_size(&encoder, data); + return bytes_encoded; +} + +ArduinoCloudProperty& ArduinoCloudThing::addPropertyReal(ArduinoCloudProperty & property, String const & name, Permission const permission, int propertyIdentifier) { + property.init(name, permission, _get_time_func); + if (isPropertyInContainer(name)) { + return (*getProperty(name)); + } else { + if (property.isPrimitive()) { + _numPrimitivesProperties++; + } + _numProperties++; + addProperty(&property, propertyIdentifier); + return (property); + } + +} + +void ArduinoCloudThing::decode(uint8_t const * const payload, size_t const length, bool isSyncMessage) { + _isSyncMessage = isSyncMessage; + + CborParser parser; + CborValue array_iter, + map_iter, + value_iter; + + if (cbor_parser_init(payload, length, 0, &parser, &array_iter) != CborNoError) { + return; + } + + if (array_iter.type != CborArrayType) { + return; + } + + if (cbor_value_enter_container(&array_iter, &map_iter) != CborNoError) { + return; + } + + CborMapData *map_data = nullptr; + + _map_data_list.clear(); + _currentPropertyName = ""; + + MapParserState current_state = MapParserState::EnterMap, + next_state = MapParserState::Error; + + while (current_state != MapParserState::Complete) { + + switch (current_state) { + case MapParserState::EnterMap : next_state = handle_EnterMap(&map_iter, &value_iter, &map_data); break; + case MapParserState::MapKey : next_state = handle_MapKey(&value_iter); break; + case MapParserState::UndefinedKey : next_state = handle_UndefinedKey(&value_iter); break; + case MapParserState::BaseVersion : next_state = handle_BaseVersion(&value_iter, map_data); break; + case MapParserState::BaseName : next_state = handle_BaseName(&value_iter, map_data); break; + case MapParserState::BaseTime : next_state = handle_BaseTime(&value_iter, map_data); break; + case MapParserState::Time : next_state = handle_Time(&value_iter, map_data); break; + case MapParserState::Name : next_state = handle_Name(&value_iter, map_data); break; + case MapParserState::Value : next_state = handle_Value(&value_iter, map_data); break; + case MapParserState::StringValue : next_state = handle_StringValue(&value_iter, map_data); break; + case MapParserState::BooleanValue : next_state = handle_BooleanValue(&value_iter, map_data); break; + case MapParserState::LeaveMap : next_state = handle_LeaveMap(&map_iter, &value_iter, map_data); break; + case MapParserState::Complete : /* Nothing to do */ break; + case MapParserState::Error : return; break; + } + + current_state = next_state; + } +} + +bool ArduinoCloudThing::isPropertyInContainer(String const & name) { + for (int i = 0; i < _property_list.size(); i++) { + ArduinoCloudProperty * p = _property_list.get(i); + if (p->name() == name) { + return true; + } + } + return false; +} + +int ArduinoCloudThing::appendChangedProperties(CborEncoder * arrayEncoder, bool lightPayload) { + int appendedProperties = 0; + for (int i = 0; i < _property_list.size(); i++) { + ArduinoCloudProperty * p = _property_list.get(i); + if (p->shouldBeUpdated() && p->isReadableByCloud()) { + p->append(arrayEncoder, lightPayload); + appendedProperties++; + } + } + return appendedProperties; +} + +//retrieve property by name +ArduinoCloudProperty * ArduinoCloudThing::getProperty(String const & name) { + for (int i = 0; i < _property_list.size(); i++) { + ArduinoCloudProperty * p = _property_list.get(i); + if (p->name() == name) { + return p; + } + } + return NULL; +} + +//retrieve property by identifier +ArduinoCloudProperty * ArduinoCloudThing::getProperty(int const & pos) { + for (int i = 0; i < _property_list.size(); i++) { + ArduinoCloudProperty * p = _property_list.get(i); + if (p->identifier() == pos) { + return p; + } + } + return NULL; +} + +// this function updates the timestamps on the primitive properties that have been modified locally since last cloud synchronization +void ArduinoCloudThing::updateTimestampOnLocallyChangedProperties() { + if (_numPrimitivesProperties == 0) { + return; + } else { + for (int i = 0; i < _property_list.size(); i++) { + CloudWrapperBase * p = (CloudWrapperBase *)_property_list.get(i); + if (p->isPrimitive() && p->isChangedLocally() && p->isReadableByCloud()) { + p->updateLocalTimestamp(); + } + } + } +} + +/****************************************************************************** + PRIVATE MEMBER FUNCTIONS + ******************************************************************************/ + +ArduinoCloudThing::MapParserState ArduinoCloudThing::handle_EnterMap(CborValue * map_iter, CborValue * value_iter, CborMapData **map_data) { + MapParserState next_state = MapParserState::Error; + + if (cbor_value_get_type(map_iter) == CborMapType) { + if (cbor_value_enter_container(map_iter, value_iter) == CborNoError) { + *map_data = new CborMapData(); + next_state = MapParserState::MapKey; + } + } + + return next_state; +} + +ArduinoCloudThing::MapParserState ArduinoCloudThing::handle_MapKey(CborValue * value_iter) { + MapParserState next_state = MapParserState::Error; + + if (cbor_value_at_end(value_iter)) { + next_state = MapParserState::LeaveMap; + } + /* The Map use the CBOR Label (protocol V2) + Example [{0: "temperature", 2: 25}] + */ + else if (cbor_value_is_integer(value_iter)) { + int val = 0; + if (cbor_value_get_int(value_iter, &val) == CborNoError) { + if (cbor_value_advance(value_iter) == CborNoError) { + if (val == static_cast(CborIntegerMapKey::Name)) { + next_state = MapParserState::Name; + } else if (val == static_cast(CborIntegerMapKey::BaseVersion)) { + next_state = MapParserState::BaseVersion; + } else if (val == static_cast(CborIntegerMapKey::BaseName)) { + next_state = MapParserState::BaseName; + } else if (val == static_cast(CborIntegerMapKey::BaseTime)) { + next_state = MapParserState::BaseTime; + } else if (val == static_cast(CborIntegerMapKey::Value)) { + next_state = MapParserState::Value; + } else if (val == static_cast(CborIntegerMapKey::StringValue)) { + next_state = MapParserState::StringValue; + } else if (val == static_cast(CborIntegerMapKey::BooleanValue)) { + next_state = MapParserState::BooleanValue; + } else if (val == static_cast(CborIntegerMapKey::Time)) { + next_state = MapParserState::Time; + } else { + next_state = MapParserState::UndefinedKey; + } + } + } + } + + return next_state; +} + +ArduinoCloudThing::MapParserState ArduinoCloudThing::handle_UndefinedKey(CborValue * value_iter) { + MapParserState next_state = MapParserState::Error; + + if (cbor_value_advance(value_iter) == CborNoError) { + next_state = MapParserState::MapKey; + } + + return next_state; +} + +ArduinoCloudThing::MapParserState ArduinoCloudThing::handle_BaseVersion(CborValue * value_iter, CborMapData * map_data) { + MapParserState next_state = MapParserState::Error; + + if (cbor_value_is_integer(value_iter)) { + int val = 0; + if (cbor_value_get_int(value_iter, &val) == CborNoError) { + map_data->base_version.set(val); + + if (cbor_value_advance(value_iter) == CborNoError) { + next_state = MapParserState::MapKey; + } + } + } + + return next_state; +} + +ArduinoCloudThing::MapParserState ArduinoCloudThing::handle_BaseName(CborValue * value_iter, CborMapData * map_data) { + MapParserState next_state = MapParserState::Error; + + if (cbor_value_is_text_string(value_iter)) { + char * val = 0; + size_t val_size = 0; + if (cbor_value_dup_text_string(value_iter, &val, &val_size, value_iter) == CborNoError) { + map_data->base_name.set(String(val)); + free(val); + next_state = MapParserState::MapKey; + } + } + + return next_state; +} + +ArduinoCloudThing::MapParserState ArduinoCloudThing::handle_BaseTime(CborValue * value_iter, CborMapData * map_data) { + MapParserState next_state = MapParserState::Error; + + double val = 0.0; + if (ifNumericConvertToDouble(value_iter, &val)) { + map_data->base_time.set(val); + + if (cbor_value_advance(value_iter) == CborNoError) { + next_state = MapParserState::MapKey; + } + } + + return next_state; +} + +ArduinoCloudThing::MapParserState ArduinoCloudThing::handle_Name(CborValue * value_iter, CborMapData * map_data) { + MapParserState next_state = MapParserState::Error; + + if (cbor_value_is_text_string(value_iter)) { + // if the value in the cbor message is a string, it corresponds to the name of the property to be updated (int the form [property_name]:[attribute_name]) + char * val = nullptr; + size_t val_size = 0; + if (cbor_value_dup_text_string(value_iter, &val, &val_size, value_iter) == CborNoError) { + String name = val; + free(val); + map_data->name.set(name); + int colonPos = name.indexOf(":"); + String attribute_name = ""; + if (colonPos != -1) { + attribute_name = name.substring(colonPos + 1); + } + map_data->attribute_name.set(attribute_name); + next_state = MapParserState::MapKey; + } + } else if (cbor_value_is_integer(value_iter)) { + // if the value in the cbor message is an integer, a light payload has been used and an integer identifier should be decode in order to retrieve the corresponding property and attribute name to be updated + int val = 0; + if (cbor_value_get_int(value_iter, &val) == CborNoError) { + map_data->light_payload.set(true); + map_data->name_identifier.set(val & 255); + map_data->attribute_identifier.set(val >> 8); + map_data->light_payload.set(true); + String name = getPropertyNameByIdentifier(val); + map_data->name.set(name); + + + if (cbor_value_advance(value_iter) == CborNoError) { + next_state = MapParserState::MapKey; + } + } + } + + + + return next_state; +} + +ArduinoCloudThing::MapParserState ArduinoCloudThing::handle_Value(CborValue * value_iter, CborMapData * map_data) { + MapParserState next_state = MapParserState::Error; + + double val = 0.0; + if (ifNumericConvertToDouble(value_iter, &val)) { + map_data->val.set(val); + + if (cbor_value_advance(value_iter) == CborNoError) { + next_state = MapParserState::MapKey; + } + } + + return next_state; +} + +ArduinoCloudThing::MapParserState ArduinoCloudThing::handle_StringValue(CborValue * value_iter, CborMapData * map_data) { + MapParserState next_state = MapParserState::Error; + + if (cbor_value_is_text_string(value_iter)) { + char * val = 0; + size_t val_size = 0; + if (cbor_value_dup_text_string(value_iter, &val, &val_size, value_iter) == CborNoError) { + map_data->str_val.set(String(val)); + free(val); + next_state = MapParserState::MapKey; + } + } + + return next_state; +} + +ArduinoCloudThing::MapParserState ArduinoCloudThing::handle_BooleanValue(CborValue * value_iter, CborMapData * map_data) { + MapParserState next_state = MapParserState::Error; + + bool val = false; + if (cbor_value_get_boolean(value_iter, &val) == CborNoError) { + map_data->bool_val.set(val); + + if (cbor_value_advance(value_iter) == CborNoError) { + next_state = MapParserState::MapKey; + } + } + + return next_state; +} + +ArduinoCloudThing::MapParserState ArduinoCloudThing::handle_Time(CborValue * value_iter, CborMapData * map_data) { + MapParserState next_state = MapParserState::Error; + + double val = 0.0; + if (ifNumericConvertToDouble(value_iter, &val)) { + map_data->time.set(val); + + if (cbor_value_advance(value_iter) == CborNoError) { + next_state = MapParserState::MapKey; + } + } + + return next_state; +} + +ArduinoCloudThing::MapParserState ArduinoCloudThing::handle_LeaveMap(CborValue * map_iter, CborValue * value_iter, CborMapData * map_data) { + MapParserState next_state = MapParserState::Error; + if (map_data->name.isSet()) { + String propertyName; + int colonPos = map_data->name.get().indexOf(":"); + if (colonPos != -1) { + propertyName = map_data->name.get().substring(0, colonPos); + } else { + propertyName = map_data->name.get(); + } + + if (_currentPropertyName != "" && propertyName != _currentPropertyName) { + /* Update the property containers depending on the parsed data */ + updateProperty(_currentPropertyName, _currentPropertyBaseTime + _currentPropertyTime); + /* Reset current property data */ + freeMapDataList(&_map_data_list); + _map_data_list.clear(); + _currentPropertyBaseTime = 0; + _currentPropertyTime = 0; + } + /* Compute the cloud change event baseTime and Time */ + if (map_data->base_time.isSet()) { + _currentPropertyBaseTime = (unsigned long)(map_data->base_time.get()); + } + if (map_data->time.isSet() && (map_data->time.get() > _currentPropertyTime)) { + _currentPropertyTime = (unsigned long)map_data->time.get(); + } + _map_data_list.add(map_data); + _currentPropertyName = propertyName; + } + + /* Transition into the next map if available, otherwise finish */ + if (cbor_value_leave_container(map_iter, value_iter) == CborNoError) { + if (!cbor_value_at_end(map_iter)) { + next_state = MapParserState::EnterMap; + } else { + /* Update the property containers depending on the parsed data */ + updateProperty(_currentPropertyName, _currentPropertyBaseTime + _currentPropertyTime); + /* Reset last property data */ + freeMapDataList(&_map_data_list); + _map_data_list.clear(); + next_state = MapParserState::Complete; + } + } + + return next_state; +} + +void ArduinoCloudThing::freeMapDataList(LinkedList *map_data_list) { + while (map_data_list->size() > 0) { + CborMapData const * mapData = map_data_list->pop(); + delete mapData; + } +} + +void ArduinoCloudThing::updateProperty(String propertyName, unsigned long cloudChangeEventTime) { + ArduinoCloudProperty* property = getProperty(propertyName); + if (property && property->isWriteableByCloud()) { + property->setLastCloudChangeTimestamp(cloudChangeEventTime); + property->setAttributesFromCloud(&_map_data_list); + if (_isSyncMessage) { + property->execCallbackOnSync(); + } else { + property->fromCloudToLocal(); + property->execCallbackOnChange(); + } + } +} + +// retrieve the property name by the identifier +String ArduinoCloudThing::getPropertyNameByIdentifier(int propertyIdentifier) { + ArduinoCloudProperty* property; + if (propertyIdentifier > 255) { + property = getProperty(propertyIdentifier & 255); + } else { + property = getProperty(propertyIdentifier); + } + return property->name(); +} + +bool ArduinoCloudThing::ifNumericConvertToDouble(CborValue * value_iter, double * numeric_val) { + + if (cbor_value_is_integer(value_iter)) { + int64_t val = 0; + if (cbor_value_get_int64(value_iter, &val) == CborNoError) { + *numeric_val = static_cast(val); + return true; + } + } else if (cbor_value_is_double(value_iter)) { + double val = 0.0; + if (cbor_value_get_double(value_iter, &val) == CborNoError) { + *numeric_val = val; + return true; + } + } else if (cbor_value_is_float(value_iter)) { + float val = 0.0; + if (cbor_value_get_float(value_iter, &val) == CborNoError) { + *numeric_val = static_cast(val); + return true; + } + } else if (cbor_value_is_half_float(value_iter)) { + uint16_t val = 0; + if (cbor_value_get_half_float(value_iter, &val) == CborNoError) { + *numeric_val = static_cast(convertCborHalfFloatToDouble(val)); + return true; + } + } + + return false; +} + +/* Source Idea from https://tools.ietf.org/html/rfc7049 : Page: 50 */ +double ArduinoCloudThing::convertCborHalfFloatToDouble(uint16_t const half_val) { + int exp = (half_val >> 10) & 0x1f; + int mant = half_val & 0x3ff; + double val; + if (exp == 0) { + val = ldexp(mant, -24); + } else if (exp != 31) { + val = ldexp(mant + 1024, exp - 25); + } else { + val = mant == 0 ? INFINITY : NAN; + } + return half_val & 0x8000 ? -val : val; +} + +void onAutoSync(ArduinoCloudProperty & property) { + if (property.getLastCloudChangeTimestamp() > property.getLastLocalChangeTimestamp()) { + property.fromCloudToLocal(); + property.execCallbackOnChange(); + } +} + +void onForceCloudSync(ArduinoCloudProperty & property) { + property.fromCloudToLocal(); + property.execCallbackOnChange(); +} + +void onForceDeviceSync(ArduinoCloudProperty & /* property */) { +} diff --git a/src/cbor/ArduinoCloudThing.h b/src/cbor/ArduinoCloudThing.h new file mode 100644 index 000000000..3bd6b31ef --- /dev/null +++ b/src/cbor/ArduinoCloudThing.h @@ -0,0 +1,158 @@ +// +// This file is part of ArduinoCloudThing +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of ArduinoCloudThing. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +#ifndef ARDUINO_CLOUD_THING_H_ +#define ARDUINO_CLOUD_THING_H_ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include "ArduinoCloudProperty.h" +#include "lib/LinkedList/LinkedList.h" +#include "types/CloudBool.h" +#include "types/CloudFloat.h" +#include "types/CloudInt.h" +#include "types/CloudString.h" +#include "types/CloudLocation.h" +#include "types/CloudColor.h" +#include "types/CloudWrapperBase.h" + +#include "types/automation/CloudColoredLight.h" +#include "types/automation/CloudContactSensor.h" +#include "types/automation/CloudDimmedLight.h" +#include "types/automation/CloudLight.h" +#include "types/automation/CloudMotionSensor.h" +#include "types/automation/CloudSmartPlug.h" +#include "types/automation/CloudSwitch.h" +#include "types/automation/CloudTemperature.h" +#include "types/automation/CloudTelevision.h" + + +/****************************************************************************** + CONSTANTS + ******************************************************************************/ + +static bool const ON = true; +static bool const OFF = false; + +static long const ON_CHANGE = -1; +static long const SECONDS = 1; +static long const MINUTES = 60; +static long const HOURS = 3600; +static long const DAYS = 86400; + +/****************************************************************************** + SYNCHRONIZATION CALLBACKS + ******************************************************************************/ + +void onAutoSync(ArduinoCloudProperty & property); +#define MOST_RECENT_WINS onAutoSync +void onForceCloudSync(ArduinoCloudProperty & property); +#define CLOUD_WINS onForceCloudSync +void onForceDeviceSync(ArduinoCloudProperty & property); +#define DEVICE_WINS onForceDeviceSync // The device property value is already the correct one. The cloud property value will be synchronized at the next update cycle. + +/****************************************************************************** + CLASS DECLARATION + ******************************************************************************/ + +class ArduinoCloudThing { + + public: + ArduinoCloudThing(); + + void begin(); + void registerGetTimeCallbackFunc(GetTimeCallbackFunc func); + //if propertyIdentifier is different from -1, an integer identifier is associated to the added property to be use instead of the property name when the parameter lightPayload is true in the encode method + ArduinoCloudProperty & addPropertyReal(ArduinoCloudProperty & property, String const & name, Permission const permission, int propertyIdentifier = -1); + + /* encode return > 0 if a property has changed and encodes the changed properties in CBOR format into the provided buffer */ + /* if lightPayload is true the integer identifier of the property will be encoded in the message instead of the property name in order to reduce the size of the message payload*/ + int encode(uint8_t * data, size_t const size, bool lightPayload = false); + /* decode a CBOR payload received from the cloud */ + void decode(uint8_t const * const payload, size_t const length, bool isSyncMessage = false); + + bool isPropertyInContainer(String const & name); + int appendChangedProperties(CborEncoder * arrayEncoder, bool lightPayload); + void updateTimestampOnLocallyChangedProperties(); + void updateProperty(String propertyName, unsigned long cloudChangeEventTime); + String getPropertyNameByIdentifier(int propertyIdentifier); + + private: + GetTimeCallbackFunc _get_time_func; + LinkedList _property_list; + /* Keep track of the number of primitive properties in the Thing. If 0 it allows the early exit in updateTimestampOnLocallyChangedProperties() */ + int _numPrimitivesProperties; + int _numProperties; + /* Indicates the if the message received to be decoded is a response to the getLastValues inquiry */ + bool _isSyncMessage; + /* List of map data that will hold all the attributes of a property */ + LinkedList _map_data_list; + /* Current property name during decoding: use to look for a new property in the senml value array */ + String _currentPropertyName; + unsigned long _currentPropertyBaseTime, + _currentPropertyTime; + + enum class MapParserState { + EnterMap, + MapKey, + UndefinedKey, + BaseVersion, + BaseName, + BaseTime, + Name, + Value, + StringValue, + BooleanValue, + Time, + LeaveMap, + Complete, + Error + }; + + MapParserState handle_EnterMap(CborValue * map_iter, CborValue * value_iter, CborMapData **map_data); + MapParserState handle_MapKey(CborValue * value_iter); + MapParserState handle_UndefinedKey(CborValue * value_iter); + MapParserState handle_BaseVersion(CborValue * value_iter, CborMapData * map_data); + MapParserState handle_BaseName(CborValue * value_iter, CborMapData * map_data); + MapParserState handle_BaseTime(CborValue * value_iter, CborMapData * map_data); + MapParserState handle_Name(CborValue * value_iter, CborMapData * map_data); + MapParserState handle_Value(CborValue * value_iter, CborMapData * map_data); + MapParserState handle_StringValue(CborValue * value_iter, CborMapData * map_data); + MapParserState handle_BooleanValue(CborValue * value_iter, CborMapData * map_data); + MapParserState handle_Time(CborValue * value_iter, CborMapData * map_data); + MapParserState handle_LeaveMap(CborValue * map_iter, CborValue * value_iter, CborMapData * map_data); + + static bool ifNumericConvertToDouble(CborValue * value_iter, double * numeric_val); + static double convertCborHalfFloatToDouble(uint16_t const half_val); + void freeMapDataList(LinkedList * map_data_list); + inline void addProperty(ArduinoCloudProperty * property_obj, int propertyIdentifier) { + if (propertyIdentifier != -1) { + property_obj->setIdentifier(propertyIdentifier); + } else { + // if property identifier is -1, an incremental value will be assigned as identifier. + property_obj->setIdentifier(_numProperties); + } + _property_list.add(property_obj); + } + ArduinoCloudProperty * getProperty(String const & name); + ArduinoCloudProperty * getProperty(int const & identifier); + +}; + +#endif /* ARDUINO_CLOUD_THING_H_ */ diff --git a/src/cbor/lib/LinkedList/LICENSE.txt b/src/cbor/lib/LinkedList/LICENSE.txt new file mode 100644 index 000000000..5c02604a0 --- /dev/null +++ b/src/cbor/lib/LinkedList/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Ivan Seidel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/cbor/lib/LinkedList/LinkedList.h b/src/cbor/lib/LinkedList/LinkedList.h new file mode 100644 index 000000000..237849285 --- /dev/null +++ b/src/cbor/lib/LinkedList/LinkedList.h @@ -0,0 +1,325 @@ +/* + LinkedList.h - V1.1 - Generic LinkedList implementation + Works better with FIFO, because LIFO will need to + search the entire List to find the last one; + + For instructions, go to https://github.com/ivanseidel/LinkedList + + Created by Ivan Seidel Gomes, March, 2013. + Released into the public domain. +*/ + + +#ifndef LinkedList_h +#define LinkedList_h + +#include + +template +struct ListNode +{ + T data; + ListNode *next; +}; + +template +class LinkedList{ + +protected: + int _size; + ListNode *root; + ListNode *last; + + // Helps "get" method, by saving last position + ListNode *lastNodeGot; + int lastIndexGot; + // isCached should be set to FALSE + // everytime the list suffer changes + bool isCached; + + ListNode* getNode(int index); + +public: + LinkedList(); + ~LinkedList(); + + /* + Returns current size of LinkedList + */ + virtual int size(); + /* + Adds a T object in the specified index; + Unlink and link the LinkedList correcly; + Increment _size + */ + virtual bool add(int index, T); + /* + Adds a T object in the end of the LinkedList; + Increment _size; + */ + virtual bool add(T); + /* + Adds a T object in the start of the LinkedList; + Increment _size; + */ + virtual bool unshift(T); + /* + Set the object at index, with T; + Increment _size; + */ + virtual bool set(int index, T); + /* + Remove object at index; + If index is not reachable, returns false; + else, decrement _size + */ + virtual T remove(int index); + /* + Remove last object; + */ + virtual T pop(); + /* + Remove first object; + */ + virtual T shift(); + /* + Get the index'th element on the list; + Return Element if accessible, + else, return false; + */ + virtual T get(int index); + + /* + Clear the entire array + */ + virtual void clear(); + +}; + +// Initialize LinkedList with false values +template +LinkedList::LinkedList() +{ + root=NULL; + last=NULL; + _size=0; + + lastNodeGot = root; + lastIndexGot = 0; + isCached = false; +} + +// Clear Nodes and free Memory +template +LinkedList::~LinkedList() +{ + ListNode* tmp; + while(root!=NULL) + { + tmp=root; + root=root->next; + delete tmp; + } + last = NULL; + _size=0; + isCached = false; +} + +/* + Actualy "logic" coding +*/ + +template +ListNode* LinkedList::getNode(int index){ + + int _pos = 0; + ListNode* current = root; + + // Check if the node trying to get is + // immediatly AFTER the previous got one + if(isCached && lastIndexGot <= index){ + _pos = lastIndexGot; + current = lastNodeGot; + } + + while(_pos < index && current){ + current = current->next; + + _pos++; + } + + // Check if the object index got is the same as the required + if(_pos == index){ + isCached = true; + lastIndexGot = index; + lastNodeGot = current; + + return current; + } + + return NULL; +} + +template +int LinkedList::size(){ + return _size; +} + +template +bool LinkedList::add(int index, T _t){ + + if(index >= _size) + return add(_t); + + if(index == 0) + return unshift(_t); + + ListNode *tmp = new ListNode(), + *_prev = getNode(index-1); + tmp->data = _t; + tmp->next = _prev->next; + _prev->next = tmp; + + _size++; + isCached = false; + + return true; +} + +template +bool LinkedList::add(T _t){ + + ListNode *tmp = new ListNode(); + tmp->data = _t; + tmp->next = NULL; + + if(root){ + // Already have elements inserted + last->next = tmp; + last = tmp; + }else{ + // First element being inserted + root = tmp; + last = tmp; + } + + _size++; + isCached = false; + + return true; +} + +template +bool LinkedList::unshift(T _t){ + + if(_size == 0) + return add(_t); + + ListNode *tmp = new ListNode(); + tmp->next = root; + tmp->data = _t; + root = tmp; + + _size++; + isCached = false; + + return true; +} + +template +bool LinkedList::set(int index, T _t){ + // Check if index position is in bounds + if(index < 0 || index >= _size) + return false; + + getNode(index)->data = _t; + return true; +} + +template +T LinkedList::pop(){ + if(_size <= 0) + return T(); + + isCached = false; + + if(_size >= 2){ + ListNode *tmp = getNode(_size - 2); + T ret = tmp->next->data; + delete(tmp->next); + tmp->next = NULL; + last = tmp; + _size--; + return ret; + }else{ + // Only one element left on the list + T ret = root->data; + delete(root); + root = NULL; + last = NULL; + _size = 0; + return ret; + } +} + +template +T LinkedList::shift(){ + if(_size <= 0) + return T(); + + if(_size > 1){ + ListNode *_next = root->next; + T ret = root->data; + delete(root); + root = _next; + _size --; + isCached = false; + + return ret; + }else{ + // Only one left, then pop() + return pop(); + } + +} + +template +T LinkedList::remove(int index){ + if (index < 0 || index >= _size) + { + return T(); + } + + if(index == 0) + return shift(); + + if (index == _size-1) + { + return pop(); + } + + ListNode *tmp = getNode(index - 1); + ListNode *toDelete = tmp->next; + T ret = toDelete->data; + tmp->next = tmp->next->next; + delete(toDelete); + _size--; + isCached = false; + return ret; +} + + +template +T LinkedList::get(int index){ + ListNode *tmp = getNode(index); + + return (tmp ? tmp->data : T()); +} + +template +void LinkedList::clear(){ + while(size() > 0) + shift(); +} + +#endif diff --git a/src/cbor/lib/LinkedList/README.md b/src/cbor/lib/LinkedList/README.md new file mode 100644 index 000000000..bdb16fdbd --- /dev/null +++ b/src/cbor/lib/LinkedList/README.md @@ -0,0 +1,171 @@ +# LinkedList + +This library was developed targeting **`Arduino`** applications. However, works just great with any C++. + +Implementing a buffer for objects takes time. If we are not in the mood, we just create an `array[1000]` with enough size. + +The objective of this library is to create a pattern for projects. +If you need to use a List of: `int`, `float`, `objects`, `Lists` or `Wales`. **This is what you are looking for.** + +With a simple but powerful caching algorithm, you can get subsequent objects much faster than usual. Tested without any problems with Lists bigger than 2000 members. + +## Installation + +1. [Download](https://github.com/ivanseidel/LinkedList/archive/master.zip) the Latest release from gitHub. +2. Unzip and modify the Folder name to "LinkedList" (Remove the '-version') +3. Paste the modified folder on your Library folder (On your `Libraries` folder inside Sketchbooks or Arduino software). +4. Reopen the Arduino software. + +**If you are here, because another Library requires this class, just don't waste time reading bellow. Install and ready.** + +------------------------- + +## Getting started + +### The `LinkedList` class + +In case you don't know what a LinkedList is and what it's used for, take a quick look at [Wikipedia::LinkedList](https://en.wikipedia.org/wiki/Linked_list) before continuing. + +#### To declare a LinkedList object +```c++ +// Instantiate a LinkedList that will hold 'integer' +LinkedList myLinkedList = LinkedList(); + +// Or just this +LinkedList myLinkedList; + +// But if you are instantiating a pointer LinkedList... +LinkedList *myLinkedList = new LinkedList(); + +// If you want a LinkedList with any other type such as 'MyClass' +// Make sure you call delete(MyClass) when you remove! +LinkedList *myLinkedList = new LinkedList(); +``` + +#### Getting the size of the linked list +```c++ +// To get the size of a linked list, make use of the size() method +int theSize = myList.size(); + +// Notice that if it's pointer to the linked list, you should use -> instead +int theSize = myList->size(); +``` + +#### Adding elements + +```c++ +// add(obj) method will insert at the END of the list +myList.add(myObject); + +// add(index, obj) method will try to insert the object at the specified index +myList.add(0, myObject); // Add at the beginning +myList.add(3, myObject); // Add at index 3 + +// unshift(obj) method will insert the object at the beginning +myList.unshift(myObject); +``` + +#### Getting elements + +```c++ +// get(index) will return the element at index +// (notice that the start element is 0, not 1) + +// Get the FIRST element +myObject = myList.get(0); + +// Get the third element +myObject = myList.get(2); + +// Get the LAST element +myObject = myList.get(myList.size() - 1); +``` + +#### Changing elements +```c++ +// set(index, obj) method will change the object at index to obj + +// Change the first element to myObject +myList.set(0, myObject); + +// Change the third element to myObject +myList.set(2, myObject); + +// Change the LAST element of the list +myList.set(myList.size() - 1, myObject); +``` + +#### Removing elements +```c++ +// remove(index) will remove and return the element at index + +// Remove the first object +myList.remove(0); + +// Get and Delete the third element +myDeletedObject = myList.remove(2); + +// pop() will remove and return the LAST element +myDeletedObject = myList.pop(); + +// shift() will remove and return the FIRST element +myDeletedObject = myList.shift(); + +// clear() will erase the entire list, leaving it with 0 elements +// NOTE: Clear wont DELETE/FREE memory from Pointers, if you +// are using Classes/Poiners, manualy delete and free those. +myList.clear(); +``` + +------------------------ + +## Library Reference + +### `ListNode` struct + +- `T` `ListNode::data` - The object data + +- `ListNode` `*next` - Pointer to the next Node + +### `LinkedList` class + +**`boolean` methods returns if succeeded** + +- `LinkedList::LinkedList()` - Constructor. + +- `LinkedList::~LinkedList()` - Destructor. Clear Nodes to minimize memory. Does not free pointer memory. + +- `int` `LinkedList::size()` - Returns the current size of the list. + +- `bool` `LinkedList::add(T)` - Add element T at the END of the list. + +- `bool` `LinkedList::add(int index, T)` - Add element T at `index` of the list. + +- `bool` `LinkedList::unshift(T)` - Add element T at the BEGINNING of the list. + +- `bool` `LinkedList::set(int index, T)` - Set the element at `index` to T. + +- `T` `LinkedList::remove(int index)` - Remove element at `index`. Return the removed element. Does not free pointer memory + +- `T` `LinkedList::pop()` - Remove the LAST element. Return the removed element. + +- `T` `LinkedList::shift()` - Remove the FIRST element. Return the removed element. + +- `T` `LinkedList::get(int index)` - Return the element at `index`. + +- `void` `LinkedList::clear()` - Removes all elements. Does not free pointer memory. + +- **protected** `int` `LinkedList::_size` - Holds the cached size of the list. + +- **protected** `ListNode` `LinkedList::*root` - Holds the root node of the list. + +- **protected** `ListNode` `LinkedList::*last` - Holds the last node of the list. + +- **protected** `ListNode*` `LinkedList::getNode(int index)` - Returns the `index` node of the list. + +### Version History + +* `1.1 (2013-07-20)`: Cache implemented. Getting subsequent objects is now O(N). Before, O(N^2). +* `1.0 (2013-07-20)`: Original release + +![LinkedList](https://d2weczhvl823v0.cloudfront.net/ivanseidel/LinkedList/trend.png) diff --git a/src/cbor/lib/LinkedList/keywords.txt b/src/cbor/lib/LinkedList/keywords.txt new file mode 100644 index 000000000..3ae496859 --- /dev/null +++ b/src/cbor/lib/LinkedList/keywords.txt @@ -0,0 +1,28 @@ +####################################### +# Syntax Coloring +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +LinkedList KEYWORD1 +ListNode KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +size KEYWORD2 +add KEYWORD2 +unshift KEYWORD2 +set KEYWORD2 +remove KEYWORD2 +pop KEYWORD2 +shift KEYWORD2 +get KEYWORD2 +clear KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/src/cbor/lib/LinkedList/library.json b/src/cbor/lib/LinkedList/library.json new file mode 100644 index 000000000..4179b248d --- /dev/null +++ b/src/cbor/lib/LinkedList/library.json @@ -0,0 +1,12 @@ +{ + "name": "LinkedList", + "keywords": "pattern", + "description": "A fully implemented LinkedList (int, float, objects, Lists or Wales) made to work with Arduino projects", + "repository": + { + "type": "git", + "url": "https://github.com/ivanseidel/LinkedList.git" + }, + "frameworks": "arduino", + "platforms": "*" +} diff --git a/src/cbor/lib/LinkedList/library.properties b/src/cbor/lib/LinkedList/library.properties new file mode 100644 index 000000000..77b1423c0 --- /dev/null +++ b/src/cbor/lib/LinkedList/library.properties @@ -0,0 +1,9 @@ +name=LinkedList +version=1.2.3 +author=Ivan Seidel +maintainer=Ivan Seidel +sentence=A fully implemented LinkedList made to work with Arduino projects +paragraph=The objective of this library is to create a pattern for projects. If you need to use a List of: int, float, objects, Lists or Wales. This is what you are looking for. +category=Data Processing +url=https://github.com/ivanseidel/LinkedList +architectures=* diff --git a/src/cbor/lib/tinycbor/cbor-lib.h b/src/cbor/lib/tinycbor/cbor-lib.h new file mode 100644 index 000000000..0ac58a17d --- /dev/null +++ b/src/cbor/lib/tinycbor/cbor-lib.h @@ -0,0 +1,6 @@ +#ifndef CBOR_LIB_H +#define CBOR_LIB_H + +#include "src/cbor.h" + +#endif \ No newline at end of file diff --git a/src/cbor/lib/tinycbor/src/cbor.dox b/src/cbor/lib/tinycbor/src/cbor.dox new file mode 100644 index 000000000..0bf5c54c2 --- /dev/null +++ b/src/cbor/lib/tinycbor/src/cbor.dox @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +/** + * \mainpage + * The TinyCBOR $(VERSION) library is a small CBOR encoder and decoder library, + * optimized for very fast operation with very small footprint. The main encoder + * and decoder functions do not allocate memory. + * + * TinyCBOR is divided into the following groups of functions and structures: + * - \ref CborGlobals + * - \ref CborEncoding + * - \ref CborParsing + * - \ref CborPretty + * - \ref CborToJson + */ + +/** + * \file + * The is the main header in TinyCBOR and defines the constants used by most functions + * as well as the structures for encoding (CborEncoder) and decoding (CborValue). + * + * \sa + */ + +/** + * \file + * The file contains the routines that are used to convert a CBOR + * data stream into JSON. + * + * \sa + */ + +/** + * \defgroup CborGlobals Global constants + * \brief Constants used by all TinyCBOR function groups. + */ + +/** + * \addtogroup CborGlobals + * @{ + */ + +/** + * \var size_t CborIndefiniteLength + * + * This variable is a constant used to indicate that the length of the map or + * array is not yet determined. It is used in functions + * cbor_encoder_create_map() and cbor_encoder_create_array() + */ + +/** + * \enum CborType + * The CborType enum contains the types known to TinyCBOR. + * + * \value CborIntegerType Type is an integer value, positive, negative or zero + * \value CborByteStringType Type is a string of arbitrary raw bytes + * \value CborTextStringType Type is a text string encoded in UTF-8 + * \value CborArrayType Type is a CBOR array + * \value CborMapType Type is a CBOR map (an associative container with key and value pairs) + * \value CborTagType Type is a CBOR tag (a 64-bit integer describing the item that follows, see CborKnownTags) + * \value CborSimpleType Type is one of the CBOR Simple Types + * \value CborBooleanType Type is a boolean (true or false) + * \value CborNullType Type encodes a null + * \value CborUndefinedType Type encodes an undefined value + * \value CborHalfFloatType Type is an IEEE 754 half precision (16-bit) floating point type + * \value CborFloatType Type is an IEEE 754 single precision (32-bit) floating point type + * \value CborDoubleType Type is an IEEE 754 double precision (64-bit) floating point type + * \value CborInvalidType Type is not valid (this value is used to indicate error conditions) + */ + +/** + * \enum CborKnownTags + * The CborKnownTags enum contains known tags specified in RFC 7049, for use by the application. + * TinyCBOR does not usually interpret the meaning of these tags and does not add them to the + * output stream, unless specifically instructed to do so in functions for that effect. + * + * \value CborDateTimeStringTag Text string contains a date-time encoded in RFC 3339 format, "YYYY-MM-DD hh:mm:ss+zzzz" + * \value CborUnixTime_tTag Number is a Unix time_t quantity, the number of seconds since 1970-01-01 midnight UTC + * \value CborPositiveBignumTag Item is a CBOR byte string encoding a positive integer of arbitrary precision + * \value CborNegativeBignumTag Item is a CBOR byte string encoding a negative integer of arbitrary precision + * \value CborDecimalTag Item is a CBOR array of two integers encoding a fixed-point decimal + * \value CborBigfloatTag Item is a bigfloat + * \value CborExpectedBase64urlTag Item is a CBOR byte string that is expected to be encoded as Base64Url + * \value CborExpectedBase64Tag Item is a CBOR byte string that is expected to be encoded as Base64 + * \value CborExpectedBase16Tag Item is a CBOR byte string that is expected to be encoded as Base16 (also known as "hexdump") + * \value CborUriTag Item is a CBOR text string containing a URI (RFC 3986) or IRI (RFC 3987) + * \value CborBase64urlTag Item is a CBOR text string that was encoded as Base64Url + * \value CborBase64Tag Item is a CBOR text string that was encoded as Base64 + * \value CborRegularExpressionTag Item is a CBOR text string containing a regular expression + * \value CborMimeMessageTag Item is a CBOR text string containing a MIME message (RFC 2045, 2046, 2047, 2822) + * \value CborSignatureTag Item contains CBOR-encoded data. + * This tag is also used as "file magic," marking a file as containing CBOR + */ + +/** + * \typedef CborTag + * This typedef is an unsigned 64-bit integer. Known CBOR tags can be used from the CborKnownTags enum + * but the user application may use other tag values than the ones specified in RFC 7049. + */ + +/** @} */ diff --git a/src/cbor/lib/tinycbor/src/cbor.h b/src/cbor/lib/tinycbor/src/cbor.h new file mode 100644 index 000000000..e81df0ec3 --- /dev/null +++ b/src/cbor/lib/tinycbor/src/cbor.h @@ -0,0 +1,638 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#ifndef CBOR_H +#define CBOR_H + +#ifndef assert +#include +#endif +#include +#include +#include +#include +#include + +#include "tinycbor-version.h" + +#define TINYCBOR_VERSION ((TINYCBOR_VERSION_MAJOR << 16) | (TINYCBOR_VERSION_MINOR << 8) | TINYCBOR_VERSION_PATCH) + +#ifdef __cplusplus +extern "C" { +#else +#include +#endif + +#ifndef SIZE_MAX +/* Some systems fail to define SIZE_MAX in , even though C99 requires it... + * Conversion from signed to unsigned is defined in 6.3.1.3 (Signed and unsigned integers) p2, + * which says: "the value is converted by repeatedly adding or subtracting one more than the + * maximum value that can be represented in the new type until the value is in the range of the + * new type." + * So -1 gets converted to size_t by adding SIZE_MAX + 1, which results in SIZE_MAX. + */ +# define SIZE_MAX ((size_t)-1) +#endif + +#ifndef CBOR_API +# define CBOR_API +#endif +#ifndef CBOR_PRIVATE_API +# define CBOR_PRIVATE_API +#endif +#ifndef CBOR_INLINE_API +# if defined(__cplusplus) +# define CBOR_INLINE inline +# define CBOR_INLINE_API inline +# else +# define CBOR_INLINE_API static CBOR_INLINE +# if defined(_MSC_VER) +# define CBOR_INLINE __inline +# elif defined(__GNUC__) +# define CBOR_INLINE __inline__ +# elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +# define CBOR_INLINE inline +# else +# define CBOR_INLINE +# endif +# endif +#endif + +typedef enum CborType { + CborIntegerType = 0x00, + CborByteStringType = 0x40, + CborTextStringType = 0x60, + CborArrayType = 0x80, + CborMapType = 0xa0, + CborTagType = 0xc0, + CborSimpleType = 0xe0, + CborBooleanType = 0xf5, + CborNullType = 0xf6, + CborUndefinedType = 0xf7, + CborHalfFloatType = 0xf9, + CborFloatType = 0xfa, + CborDoubleType = 0xfb, + + CborInvalidType = 0xff /* equivalent to the break byte, so it will never be used */ +} CborType; + +typedef uint64_t CborTag; +typedef enum CborKnownTags { + CborDateTimeStringTag = 0, + CborUnixTime_tTag = 1, + CborPositiveBignumTag = 2, + CborNegativeBignumTag = 3, + CborDecimalTag = 4, + CborBigfloatTag = 5, + CborCOSE_Encrypt0Tag = 16, + CborCOSE_Mac0Tag = 17, + CborCOSE_Sign1Tag = 18, + CborExpectedBase64urlTag = 21, + CborExpectedBase64Tag = 22, + CborExpectedBase16Tag = 23, + CborEncodedCborTag = 24, + CborUrlTag = 32, + CborBase64urlTag = 33, + CborBase64Tag = 34, + CborRegularExpressionTag = 35, + CborMimeMessageTag = 36, + CborCOSE_EncryptTag = 96, + CborCOSE_MacTag = 97, + CborCOSE_SignTag = 98, + CborSignatureTag = 55799 +} CborKnownTags; + +/* #define the constants so we can check with #ifdef */ +#define CborDateTimeStringTag CborDateTimeStringTag +#define CborUnixTime_tTag CborUnixTime_tTag +#define CborPositiveBignumTag CborPositiveBignumTag +#define CborNegativeBignumTag CborNegativeBignumTag +#define CborDecimalTag CborDecimalTag +#define CborBigfloatTag CborBigfloatTag +#define CborCOSE_Encrypt0Tag CborCOSE_Encrypt0Tag +#define CborCOSE_Mac0Tag CborCOSE_Mac0Tag +#define CborCOSE_Sign1Tag CborCOSE_Sign1Tag +#define CborExpectedBase64urlTag CborExpectedBase64urlTag +#define CborExpectedBase64Tag CborExpectedBase64Tag +#define CborExpectedBase16Tag CborExpectedBase16Tag +#define CborEncodedCborTag CborEncodedCborTag +#define CborUrlTag CborUrlTag +#define CborBase64urlTag CborBase64urlTag +#define CborBase64Tag CborBase64Tag +#define CborRegularExpressionTag CborRegularExpressionTag +#define CborMimeMessageTag CborMimeMessageTag +#define CborCOSE_EncryptTag CborCOSE_EncryptTag +#define CborCOSE_MacTag CborCOSE_MacTag +#define CborCOSE_SignTag CborCOSE_SignTag +#define CborSignatureTag CborSignatureTag + +/* Error API */ + +typedef enum CborError { + CborNoError = 0, + + /* errors in all modes */ + CborUnknownError, + CborErrorUnknownLength, /* request for length in array, map, or string with indeterminate length */ + CborErrorAdvancePastEOF, + CborErrorIO, + + /* parser errors streaming errors */ + CborErrorGarbageAtEnd = 256, + CborErrorUnexpectedEOF, + CborErrorUnexpectedBreak, + CborErrorUnknownType, /* can only happen in major type 7 */ + CborErrorIllegalType, /* type not allowed here */ + CborErrorIllegalNumber, + CborErrorIllegalSimpleType, /* types of value less than 32 encoded in two bytes */ + + /* parser errors in strict mode parsing only */ + CborErrorUnknownSimpleType = 512, + CborErrorUnknownTag, + CborErrorInappropriateTagForType, + CborErrorDuplicateObjectKeys, + CborErrorInvalidUtf8TextString, + CborErrorExcludedType, + CborErrorExcludedValue, + CborErrorImproperValue, + CborErrorOverlongEncoding, + CborErrorMapKeyNotString, + CborErrorMapNotSorted, + CborErrorMapKeysNotUnique, + + /* encoder errors */ + CborErrorTooManyItems = 768, + CborErrorTooFewItems, + + /* internal implementation errors */ + CborErrorDataTooLarge = 1024, + CborErrorNestingTooDeep, + CborErrorUnsupportedType, + + /* errors in converting to JSON */ + CborErrorJsonObjectKeyIsAggregate = 1280, + CborErrorJsonObjectKeyNotString, + CborErrorJsonNotImplemented, + + CborErrorOutOfMemory = (int) (~0U / 2 + 1), + CborErrorInternalError = (int) (~0U / 2) /* INT_MAX on two's complement machines */ +} CborError; + +CBOR_API const char *cbor_error_string(CborError error); + +/* Encoder API */ +struct CborEncoder +{ + union { + uint8_t *ptr; + ptrdiff_t bytes_needed; + } data; + const uint8_t *end; + size_t remaining; + int flags; +}; +typedef struct CborEncoder CborEncoder; + +static const size_t CborIndefiniteLength = SIZE_MAX; + +CBOR_API void cbor_encoder_init(CborEncoder *encoder, uint8_t *buffer, size_t size, int flags); +CBOR_API CborError cbor_encode_uint(CborEncoder *encoder, uint64_t value); +CBOR_API CborError cbor_encode_int(CborEncoder *encoder, int64_t value); +CBOR_API CborError cbor_encode_negative_int(CborEncoder *encoder, uint64_t absolute_value); +CBOR_API CborError cbor_encode_simple_value(CborEncoder *encoder, uint8_t value); +CBOR_API CborError cbor_encode_tag(CborEncoder *encoder, CborTag tag); +CBOR_API CborError cbor_encode_text_string(CborEncoder *encoder, const char *string, size_t length); +CBOR_INLINE_API CborError cbor_encode_text_stringz(CborEncoder *encoder, const char *string) +{ return cbor_encode_text_string(encoder, string, strlen(string)); } +CBOR_API CborError cbor_encode_byte_string(CborEncoder *encoder, const uint8_t *string, size_t length); +CBOR_API CborError cbor_encode_floating_point(CborEncoder *encoder, CborType fpType, const void *value); + +CBOR_INLINE_API CborError cbor_encode_boolean(CborEncoder *encoder, bool value) +{ return cbor_encode_simple_value(encoder, (int)value - 1 + (CborBooleanType & 0x1f)); } +CBOR_INLINE_API CborError cbor_encode_null(CborEncoder *encoder) +{ return cbor_encode_simple_value(encoder, CborNullType & 0x1f); } +CBOR_INLINE_API CborError cbor_encode_undefined(CborEncoder *encoder) +{ return cbor_encode_simple_value(encoder, CborUndefinedType & 0x1f); } + +CBOR_INLINE_API CborError cbor_encode_half_float(CborEncoder *encoder, const void *value) +{ return cbor_encode_floating_point(encoder, CborHalfFloatType, value); } +CBOR_INLINE_API CborError cbor_encode_float(CborEncoder *encoder, float value) +{ return cbor_encode_floating_point(encoder, CborFloatType, &value); } +CBOR_INLINE_API CborError cbor_encode_double(CborEncoder *encoder, double value) +{ return cbor_encode_floating_point(encoder, CborDoubleType, &value); } + +CBOR_API CborError cbor_encoder_create_array(CborEncoder *encoder, CborEncoder *arrayEncoder, size_t length); +CBOR_API CborError cbor_encoder_create_map(CborEncoder *encoder, CborEncoder *mapEncoder, size_t length); +CBOR_API CborError cbor_encoder_close_container(CborEncoder *encoder, const CborEncoder *containerEncoder); +CBOR_API CborError cbor_encoder_close_container_checked(CborEncoder *encoder, const CborEncoder *containerEncoder); + +CBOR_INLINE_API uint8_t *_cbor_encoder_get_buffer_pointer(const CborEncoder *encoder) +{ + return encoder->data.ptr; +} + +CBOR_INLINE_API size_t cbor_encoder_get_buffer_size(const CborEncoder *encoder, const uint8_t *buffer) +{ + return (size_t)(encoder->data.ptr - buffer); +} + +CBOR_INLINE_API size_t cbor_encoder_get_extra_bytes_needed(const CborEncoder *encoder) +{ + return encoder->end ? 0 : (size_t)encoder->data.bytes_needed; +} + +/* Parser API */ + +enum CborParserIteratorFlags +{ + CborIteratorFlag_IntegerValueTooLarge = 0x01, + CborIteratorFlag_NegativeInteger = 0x02, + CborIteratorFlag_IteratingStringChunks = 0x02, + CborIteratorFlag_UnknownLength = 0x04, + CborIteratorFlag_ContainerIsMap = 0x20 +}; + +struct CborParser +{ + const uint8_t *end; + uint32_t flags; +}; +typedef struct CborParser CborParser; + +struct CborValue +{ + const CborParser *parser; + const uint8_t *ptr; + uint32_t remaining; + uint16_t extra; + uint8_t type; + uint8_t flags; +}; +typedef struct CborValue CborValue; + +CBOR_API CborError cbor_parser_init(const uint8_t *buffer, size_t size, uint32_t flags, CborParser *parser, CborValue *it); + +CBOR_API CborError cbor_value_validate_basic(const CborValue *it); + +CBOR_INLINE_API bool cbor_value_at_end(const CborValue *it) +{ return it->remaining == 0; } +CBOR_INLINE_API const uint8_t *cbor_value_get_next_byte(const CborValue *it) +{ return it->ptr; } +CBOR_API CborError cbor_value_advance_fixed(CborValue *it); +CBOR_API CborError cbor_value_advance(CborValue *it); +CBOR_INLINE_API bool cbor_value_is_container(const CborValue *it) +{ return it->type == CborArrayType || it->type == CborMapType; } +CBOR_API CborError cbor_value_enter_container(const CborValue *it, CborValue *recursed); +CBOR_API CborError cbor_value_leave_container(CborValue *it, const CborValue *recursed); + +CBOR_PRIVATE_API uint64_t _cbor_value_decode_int64_internal(const CborValue *value); +CBOR_INLINE_API uint64_t _cbor_value_extract_int64_helper(const CborValue *value) +{ + return value->flags & CborIteratorFlag_IntegerValueTooLarge ? + _cbor_value_decode_int64_internal(value) : value->extra; +} + +CBOR_INLINE_API bool cbor_value_is_valid(const CborValue *value) +{ return value && value->type != CborInvalidType; } +CBOR_INLINE_API CborType cbor_value_get_type(const CborValue *value) +{ return (CborType)value->type; } + +/* Null & undefined type */ +CBOR_INLINE_API bool cbor_value_is_null(const CborValue *value) +{ return value->type == CborNullType; } +CBOR_INLINE_API bool cbor_value_is_undefined(const CborValue *value) +{ return value->type == CborUndefinedType; } + +/* Booleans */ +CBOR_INLINE_API bool cbor_value_is_boolean(const CborValue *value) +{ return value->type == CborBooleanType; } +CBOR_INLINE_API CborError cbor_value_get_boolean(const CborValue *value, bool *result) +{ +#ifdef HOST + assert(cbor_value_is_boolean(value)); +#endif + *result = !!value->extra; + return CborNoError; +} + +/* Simple types */ +CBOR_INLINE_API bool cbor_value_is_simple_type(const CborValue *value) +{ return value->type == CborSimpleType; } +CBOR_INLINE_API CborError cbor_value_get_simple_type(const CborValue *value, uint8_t *result) +{ +#ifdef HOST + assert(cbor_value_is_simple_type(value)); +#endif + *result = (uint8_t)value->extra; + return CborNoError; +} + +/* Integers */ +CBOR_INLINE_API bool cbor_value_is_integer(const CborValue *value) +{ return value->type == CborIntegerType; } +CBOR_INLINE_API bool cbor_value_is_unsigned_integer(const CborValue *value) +{ return cbor_value_is_integer(value) && (value->flags & CborIteratorFlag_NegativeInteger) == 0; } +CBOR_INLINE_API bool cbor_value_is_negative_integer(const CborValue *value) +{ return cbor_value_is_integer(value) && (value->flags & CborIteratorFlag_NegativeInteger); } + +CBOR_INLINE_API CborError cbor_value_get_raw_integer(const CborValue *value, uint64_t *result) +{ +#ifdef HOST + assert(cbor_value_is_integer(value)); +#endif + *result = _cbor_value_extract_int64_helper(value); + return CborNoError; +} + +CBOR_INLINE_API CborError cbor_value_get_uint64(const CborValue *value, uint64_t *result) +{ +#ifdef HOST + assert(cbor_value_is_unsigned_integer(value)); +#endif + *result = _cbor_value_extract_int64_helper(value); + return CborNoError; +} + +CBOR_INLINE_API CborError cbor_value_get_int64(const CborValue *value, int64_t *result) +{ +#ifdef HOST + assert(cbor_value_is_integer(value)); +#endif + *result = (int64_t) _cbor_value_extract_int64_helper(value); + if (value->flags & CborIteratorFlag_NegativeInteger) + *result = -*result - 1; + return CborNoError; +} + +CBOR_INLINE_API CborError cbor_value_get_int(const CborValue *value, int *result) +{ +#ifdef HOST + assert(cbor_value_is_integer(value)); +#endif + *result = (int) _cbor_value_extract_int64_helper(value); + if (value->flags & CborIteratorFlag_NegativeInteger) + *result = -*result - 1; + return CborNoError; +} + +CBOR_API CborError cbor_value_get_int64_checked(const CborValue *value, int64_t *result); +CBOR_API CborError cbor_value_get_int_checked(const CborValue *value, int *result); + +CBOR_INLINE_API bool cbor_value_is_length_known(const CborValue *value) +{ return (value->flags & CborIteratorFlag_UnknownLength) == 0; } + +/* Tags */ +CBOR_INLINE_API bool cbor_value_is_tag(const CborValue *value) +{ return value->type == CborTagType; } +CBOR_INLINE_API CborError cbor_value_get_tag(const CborValue *value, CborTag *result) +{ +#ifdef HOST + assert(cbor_value_is_tag(value)); +#endif + *result = _cbor_value_extract_int64_helper(value); + return CborNoError; +} +CBOR_API CborError cbor_value_skip_tag(CborValue *it); + +/* Strings */ +CBOR_INLINE_API bool cbor_value_is_byte_string(const CborValue *value) +{ return value->type == CborByteStringType; } +CBOR_INLINE_API bool cbor_value_is_text_string(const CborValue *value) +{ return value->type == CborTextStringType; } + +CBOR_INLINE_API CborError cbor_value_get_string_length(const CborValue *value, size_t *length) +{ + uint64_t v; +#ifdef HOST + assert(cbor_value_is_byte_string(value) || cbor_value_is_text_string(value)); +#endif + if (!cbor_value_is_length_known(value)) + return CborErrorUnknownLength; + v = _cbor_value_extract_int64_helper(value); + *length = (size_t)v; + if (*length != v) + return CborErrorDataTooLarge; + return CborNoError; +} + +CBOR_PRIVATE_API CborError _cbor_value_copy_string(const CborValue *value, void *buffer, + size_t *buflen, CborValue *next); +CBOR_PRIVATE_API CborError _cbor_value_dup_string(const CborValue *value, void **buffer, + size_t *buflen, CborValue *next); + +CBOR_API CborError cbor_value_calculate_string_length(const CborValue *value, size_t *length); + +CBOR_INLINE_API CborError cbor_value_copy_text_string(const CborValue *value, char *buffer, + size_t *buflen, CborValue *next) +{ +#ifdef HOST + assert(cbor_value_is_text_string(value)); +#endif + return _cbor_value_copy_string(value, buffer, buflen, next); +} +CBOR_INLINE_API CborError cbor_value_copy_byte_string(const CborValue *value, uint8_t *buffer, + size_t *buflen, CborValue *next) +{ +#ifdef HOST + assert(cbor_value_is_byte_string(value)); +#endif + return _cbor_value_copy_string(value, buffer, buflen, next); +} + +CBOR_INLINE_API CborError cbor_value_dup_text_string(const CborValue *value, char **buffer, + size_t *buflen, CborValue *next) +{ +#ifdef HOST + assert(cbor_value_is_text_string(value)); +#endif + return _cbor_value_dup_string(value, (void **)buffer, buflen, next); +} +CBOR_INLINE_API CborError cbor_value_dup_byte_string(const CborValue *value, uint8_t **buffer, + size_t *buflen, CborValue *next) +{ +#ifdef HOST + assert(cbor_value_is_byte_string(value)); +#endif + return _cbor_value_dup_string(value, (void **)buffer, buflen, next); +} + +CBOR_API CborError cbor_value_text_string_equals(const CborValue *value, const char *string, bool *result); + +/* Maps and arrays */ +CBOR_INLINE_API bool cbor_value_is_array(const CborValue *value) +{ return value->type == CborArrayType; } +CBOR_INLINE_API bool cbor_value_is_map(const CborValue *value) +{ return value->type == CborMapType; } + +CBOR_INLINE_API CborError cbor_value_get_array_length(const CborValue *value, size_t *length) +{ + uint64_t v; +#ifdef HOST + assert(cbor_value_is_array(value)); +#endif + if (!cbor_value_is_length_known(value)) + return CborErrorUnknownLength; + v = _cbor_value_extract_int64_helper(value); + *length = (size_t)v; + if (*length != v) + return CborErrorDataTooLarge; + return CborNoError; +} + +CBOR_INLINE_API CborError cbor_value_get_map_length(const CborValue *value, size_t *length) +{ + uint64_t v; +#ifdef HOST + assert(cbor_value_is_map(value)); +#endif + if (!cbor_value_is_length_known(value)) + return CborErrorUnknownLength; + v = _cbor_value_extract_int64_helper(value); + *length = (size_t)v; + if (*length != v) + return CborErrorDataTooLarge; + return CborNoError; +} + +CBOR_API CborError cbor_value_map_find_value(const CborValue *map, const char *string, CborValue *element); + +/* Floating point */ +CBOR_INLINE_API bool cbor_value_is_half_float(const CborValue *value) +{ return value->type == CborHalfFloatType; } +CBOR_API CborError cbor_value_get_half_float(const CborValue *value, void *result); + +CBOR_INLINE_API bool cbor_value_is_float(const CborValue *value) +{ return value->type == CborFloatType; } +CBOR_INLINE_API CborError cbor_value_get_float(const CborValue *value, float *result) +{ + uint32_t data; +#ifdef HOST + assert(cbor_value_is_float(value)); + assert(value->flags & CborIteratorFlag_IntegerValueTooLarge); +#endif + data = (uint32_t)_cbor_value_decode_int64_internal(value); + memcpy(result, &data, sizeof(*result)); + return CborNoError; +} + +CBOR_INLINE_API bool cbor_value_is_double(const CborValue *value) +{ return value->type == CborDoubleType; } +CBOR_INLINE_API CborError cbor_value_get_double(const CborValue *value, double *result) +{ + uint64_t data; +#ifdef HOST + assert(cbor_value_is_double(value)); + assert(value->flags & CborIteratorFlag_IntegerValueTooLarge); +#endif + data = _cbor_value_decode_int64_internal(value); + memcpy(result, &data, sizeof(*result)); + return CborNoError; +} + +/* Validation API */ + +enum CborValidationFlags { + /* Bit mapping: + * bits 0-7 (8 bits): canonical format + * bits 8-11 (4 bits): canonical format & strict mode + * bits 12-20 (8 bits): strict mode + * bits 21-31 (10 bits): other + */ + + CborValidateShortestIntegrals = 0x0001, + CborValidateShortestFloatingPoint = 0x0002, + CborValidateShortestNumbers = CborValidateShortestIntegrals | CborValidateShortestFloatingPoint, + CborValidateNoIndeterminateLength = 0x0100, + CborValidateMapIsSorted = 0x0200 | CborValidateNoIndeterminateLength, + + CborValidateCanonicalFormat = 0x0fff, + + CborValidateMapKeysAreUnique = 0x1000 | CborValidateMapIsSorted, + CborValidateTagUse = 0x2000, + CborValidateUtf8 = 0x4000, + + CborValidateStrictMode = 0xfff00, + + CborValidateMapKeysAreString = 0x100000, + CborValidateNoUndefined = 0x200000, + CborValidateNoTags = 0x400000, + CborValidateFiniteFloatingPoint = 0x800000, + /* unused = 0x1000000, */ + /* unused = 0x2000000, */ + + CborValidateNoUnknownSimpleTypesSA = 0x4000000, + CborValidateNoUnknownSimpleTypes = 0x8000000 | CborValidateNoUnknownSimpleTypesSA, + CborValidateNoUnknownTagsSA = 0x10000000, + CborValidateNoUnknownTagsSR = 0x20000000 | CborValidateNoUnknownTagsSA, + CborValidateNoUnknownTags = 0x40000000 | CborValidateNoUnknownTagsSR, + + CborValidateCompleteData = (int)0x80000000, + + CborValidateStrictest = (int)~0U, + CborValidateBasic = 0 +}; + +CBOR_API CborError cbor_value_validate(const CborValue *it, uint32_t flags); + +/* Human-readable (dump) API */ + +enum CborPrettyFlags { + CborPrettyNumericEncodingIndicators = 0x01, + CborPrettyTextualEncodingIndicators = 0, + + CborPrettyIndicateIndeterminateLength = 0x02, + CborPrettyIndicateIndetermineLength = CborPrettyIndicateIndeterminateLength, /* deprecated */ + CborPrettyIndicateOverlongNumbers = 0x04, + + CborPrettyShowStringFragments = 0x100, + CborPrettyMergeStringFragments = 0, + + CborPrettyDefaultFlags = CborPrettyIndicateIndeterminateLength +}; + +typedef CborError (*CborStreamFunction)(void *token, const char *fmt, ...) +#ifdef __GNUC__ + __attribute__((__format__(printf, 2, 3))) +#endif +; + +CBOR_API CborError cbor_value_to_pretty_stream(CborStreamFunction streamFunction, void *token, CborValue *value, int flags); + +/* The following API requires a hosted C implementation (uses FILE*) */ +#if !defined(__STDC_HOSTED__) || __STDC_HOSTED__-0 == 1 +CBOR_API CborError cbor_value_to_pretty_advance_flags(FILE *out, CborValue *value, int flags); +CBOR_API CborError cbor_value_to_pretty_advance(FILE *out, CborValue *value); +CBOR_INLINE_API CborError cbor_value_to_pretty(FILE *out, const CborValue *value) +{ + CborValue copy = *value; + return cbor_value_to_pretty_advance_flags(out, ©, CborPrettyDefaultFlags); +} +#endif /* __STDC_HOSTED__ check */ + +#ifdef __cplusplus +} +#endif + +#endif /* CBOR_H */ + diff --git a/src/cbor/lib/tinycbor/src/cborencoder.c b/src/cbor/lib/tinycbor/src/cborencoder.c new file mode 100644 index 000000000..23a9721ad --- /dev/null +++ b/src/cbor/lib/tinycbor/src/cborencoder.c @@ -0,0 +1,650 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#ifndef _BSD_SOURCE +#define _BSD_SOURCE 1 +#endif +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE 1 +#endif +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" +#include "cborinternal_p.h" +#include "compilersupport_p.h" + +#include +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + +/** + * \defgroup CborEncoding Encoding to CBOR + * \brief Group of functions used to encode data to CBOR. + * + * CborEncoder is used to encode data into a CBOR stream. The outermost + * CborEncoder is initialized by calling cbor_encoder_init(), with the buffer + * where the CBOR stream will be stored. The outermost CborEncoder is usually + * used to encode exactly one item, most often an array or map. It is possible + * to encode more than one item, but care must then be taken on the decoder + * side to ensure the state is reset after each item was decoded. + * + * Nested CborEncoder objects are created using cbor_encoder_create_array() and + * cbor_encoder_create_map(), later closed with cbor_encoder_close_container() + * or cbor_encoder_close_container_checked(). The pairs of creation and closing + * must be exactly matched and their parameters are always the same. + * + * CborEncoder writes directly to the user-supplied buffer, without extra + * buffering. CborEncoder does not allocate memory and CborEncoder objects are + * usually created on the stack of the encoding functions. + * + * The example below initializes a CborEncoder object with a buffer and encodes + * a single integer. + * + * \code + * uint8_t buf[16]; + * CborEncoder encoder; + * cbor_encoder_init(&encoder, &buf, sizeof(buf), 0); + * cbor_encode_int(&encoder, some_value); + * \endcode + * + * As explained before, usually the outermost CborEncoder object is used to add + * one array or map, which in turn contains multiple elements. The example + * below creates a CBOR map with one element: a key "foo" and a boolean value. + * + * \code + * uint8_t buf[16]; + * CborEncoder encoder, mapEncoder; + * cbor_encoder_init(&encoder, &buf, sizeof(buf), 0); + * cbor_encoder_create_map(&encoder, &mapEncoder, 1); + * cbor_encode_text_stringz(&mapEncoder, "foo"); + * cbor_encode_boolean(&mapEncoder, some_value); + * cbor_encoder_close_container(&encoder, &mapEncoder); + * \endcode + * + *

Error checking and buffer size

+ * + * All functions operating on CborEncoder return a condition of type CborError. + * If the encoding was successful, they return CborNoError. Some functions do + * extra checking on the input provided and may return some other error + * conditions (for example, cbor_encode_simple_value() checks that the type is + * of the correct type). + * + * In addition, all functions check whether the buffer has enough bytes to + * encode the item being appended. If that is not possible, they return + * CborErrorOutOfMemory. + * + * It is possible to continue with the encoding of data past the first function + * that returns CborErrorOutOfMemory. CborEncoder functions will not overrun + * the buffer, but will instead count how many more bytes are needed to + * complete the encoding. At the end, you can obtain that count by calling + * cbor_encoder_get_extra_bytes_needed(). + * + * \section1 Finalizing the encoding + * + * Once all items have been appended and the containers have all been properly + * closed, the user-supplied buffer will contain the CBOR stream and may be + * immediately used. To obtain the size of the buffer, call + * cbor_encoder_get_buffer_size() with the original buffer pointer. + * + * The example below illustrates how one can encode an item with error checking + * and then pass on the buffer for network sending. + * + * \code + * uint8_t buf[16]; + * CborError err; + * CborEncoder encoder, mapEncoder; + * cbor_encoder_init(&encoder, &buf, sizeof(buf), 0); + * err = cbor_encoder_create_map(&encoder, &mapEncoder, 1); + * if (!err) + * return err; + * err = cbor_encode_text_stringz(&mapEncoder, "foo"); + * if (!err) + * return err; + * err = cbor_encode_boolean(&mapEncoder, some_value); + * if (!err) + * return err; + * err = cbor_encoder_close_container_checked(&encoder, &mapEncoder); + * if (!err) + * return err; + * + * size_t len = cbor_encoder_get_buffer_size(&encoder, buf); + * send_payload(buf, len); + * return CborNoError; + * \endcode + * + * Finally, the example below expands on the one above and also + * deals with dynamically growing the buffer if the initial allocation wasn't + * big enough. Note the two places where the error checking was replaced with + * an cbor_assertion, showing where the author assumes no error can occur. + * + * \code + * uint8_t *encode_string_array(const char **strings, int n, size_t *bufsize) + * { + * CborError err; + * CborEncoder encoder, arrayEncoder; + * size_t size = 256; + * uint8_t *buf = NULL; + * + * while (1) { + * int i; + * size_t more_bytes; + * uint8_t *nbuf = realloc(buf, size); + * if (nbuf == NULL) + * goto error; + * buf = nbuf; + * + * cbor_encoder_init(&encoder, &buf, size, 0); + * err = cbor_encoder_create_array(&encoder, &arrayEncoder, n); + * cbor_assert(err); // can't fail, the buffer is always big enough + * + * for (i = 0; i < n; ++i) { + * err = cbor_encode_text_stringz(&arrayEncoder, strings[i]); + * if (err && err != CborErrorOutOfMemory) + * goto error; + * } + * + * err = cbor_encoder_close_container_checked(&encoder, &arrayEncoder); + * cbor_assert(err); // shouldn't fail! + * + * more_bytes = cbor_encoder_get_extra_bytes_needed(encoder); + * if (more_size) { + * // buffer wasn't big enough, try again + * size += more_bytes; + * continue; + * } + * + * *bufsize = cbor_encoder_get_buffer_size(encoder, buf); + * return buf; + * } + * error: + * free(buf); + * return NULL; + * } + * \endcode + */ + +/** + * \addtogroup CborEncoding + * @{ + */ + +/** + * \struct CborEncoder + * Structure used to encode to CBOR. + */ + +/** + * Initializes a CborEncoder structure \a encoder by pointing it to buffer \a + * buffer of size \a size. The \a flags field is currently unused and must be + * zero. + */ +void cbor_encoder_init(CborEncoder *encoder, uint8_t *buffer, size_t size, int flags) +{ + encoder->data.ptr = buffer; + encoder->end = buffer + size; + encoder->remaining = 2; + encoder->flags = flags; +} + +static inline void put16(void *where, uint16_t v) +{ + v = cbor_htons(v); + memcpy(where, &v, sizeof(v)); +} + +/* Note: Since this is currently only used in situations where OOM is the only + * valid error, we KNOW this to be true. Thus, this function now returns just 'true', + * but if in the future, any function starts returning a non-OOM error, this will need + * to be changed to the test. At the moment, this is done to prevent more branches + * being created in the tinycbor output */ +static inline bool isOomError(CborError err) +{ + (void) err; + return true; +} + +static inline void put32(void *where, uint32_t v) +{ + v = cbor_htonl(v); + memcpy(where, &v, sizeof(v)); +} + +static inline void put64(void *where, uint64_t v) +{ + v = cbor_htonll(v); + memcpy(where, &v, sizeof(v)); +} + +static inline bool would_overflow(CborEncoder *encoder, size_t len) +{ + ptrdiff_t remaining = (ptrdiff_t)encoder->end; + remaining -= remaining ? (ptrdiff_t)encoder->data.ptr : encoder->data.bytes_needed; + remaining -= (ptrdiff_t)len; + return unlikely(remaining < 0); +} + +static inline void advance_ptr(CborEncoder *encoder, size_t n) +{ + if (encoder->end) + encoder->data.ptr += n; + else + encoder->data.bytes_needed += n; +} + +static inline CborError append_to_buffer(CborEncoder *encoder, const void *data, size_t len) +{ + if (would_overflow(encoder, len)) { + if (encoder->end != NULL) { + len -= encoder->end - encoder->data.ptr; + encoder->end = NULL; + encoder->data.bytes_needed = 0; + } + + advance_ptr(encoder, len); + return CborErrorOutOfMemory; + } + + memcpy(encoder->data.ptr, data, len); + encoder->data.ptr += len; + return CborNoError; +} + +static inline CborError append_byte_to_buffer(CborEncoder *encoder, uint8_t byte) +{ + return append_to_buffer(encoder, &byte, 1); +} + +static inline CborError encode_number_no_update(CborEncoder *encoder, uint64_t ui, uint8_t shiftedMajorType) +{ + /* Little-endian would have been so much more convenient here: + * We could just write at the beginning of buf but append_to_buffer + * only the necessary bytes. + * Since it has to be big endian, do it the other way around: + * write from the end. */ + uint64_t buf[2]; + uint8_t *const bufend = (uint8_t *)buf + sizeof(buf); + uint8_t *bufstart = bufend - 1; + put64(buf + 1, ui); /* we probably have a bunch of zeros in the beginning */ + + if (ui < Value8Bit) { + *bufstart += shiftedMajorType; + } else { + uint8_t more = 0; + if (ui > 0xffU) + ++more; + if (ui > 0xffffU) + ++more; + if (ui > 0xffffffffU) + ++more; + bufstart -= (size_t)1 << more; + *bufstart = shiftedMajorType + Value8Bit + more; + } + + return append_to_buffer(encoder, bufstart, bufend - bufstart); +} + +static inline void saturated_decrement(CborEncoder *encoder) +{ + if (encoder->remaining) + --encoder->remaining; +} + +static inline CborError encode_number(CborEncoder *encoder, uint64_t ui, uint8_t shiftedMajorType) +{ + saturated_decrement(encoder); + return encode_number_no_update(encoder, ui, shiftedMajorType); +} + +/** + * Appends the unsigned 64-bit integer \a value to the CBOR stream provided by + * \a encoder. + * + * \sa cbor_encode_negative_int, cbor_encode_int + */ +CborError cbor_encode_uint(CborEncoder *encoder, uint64_t value) +{ + return encode_number(encoder, value, UnsignedIntegerType << MajorTypeShift); +} + +/** + * Appends the negative 64-bit integer whose absolute value is \a + * absolute_value to the CBOR stream provided by \a encoder. + * + * If the value \a absolute_value is zero, this function encodes -2^64. + * + * \sa cbor_encode_uint, cbor_encode_int + */ +CborError cbor_encode_negative_int(CborEncoder *encoder, uint64_t absolute_value) +{ + return encode_number(encoder, absolute_value - 1, NegativeIntegerType << MajorTypeShift); +} + +/** + * Appends the signed 64-bit integer \a value to the CBOR stream provided by + * \a encoder. + * + * \sa cbor_encode_negative_int, cbor_encode_uint + */ +CborError cbor_encode_int(CborEncoder *encoder, int64_t value) +{ + /* adapted from code in RFC 7049 appendix C (pseudocode) */ + uint64_t ui = value >> 63; /* extend sign to whole length */ + uint8_t majorType = ui & 0x20; /* extract major type */ + ui ^= value; /* complement negatives */ + return encode_number(encoder, ui, majorType); +} + +/** + * Appends the CBOR Simple Type of value \a value to the CBOR stream provided by + * \a encoder. + * + * This function may return error CborErrorIllegalSimpleType if the \a value + * variable contains a number that is not a valid simple type. + */ +CborError cbor_encode_simple_value(CborEncoder *encoder, uint8_t value) +{ +#ifndef CBOR_ENCODER_NO_CHECK_USER + /* check if this is a valid simple type */ + if (value >= HalfPrecisionFloat && value <= Break) + return CborErrorIllegalSimpleType; +#endif + return encode_number(encoder, value, SimpleTypesType << MajorTypeShift); +} + +/** + * Appends the floating-point value of type \a fpType and pointed to by \a + * value to the CBOR stream provided by \a encoder. The value of \a fpType must + * be one of CborHalfFloatType, CborFloatType or CborDoubleType, otherwise the + * behavior of this function is undefined. + * + * This function is useful for code that needs to pass through floating point + * values but does not wish to have the actual floating-point code. + * + * \sa cbor_encode_half_float, cbor_encode_float, cbor_encode_double + */ +CborError cbor_encode_floating_point(CborEncoder *encoder, CborType fpType, const void *value) +{ + unsigned size; + uint8_t buf[1 + sizeof(uint64_t)]; + cbor_assert(fpType == CborHalfFloatType || fpType == CborFloatType || fpType == CborDoubleType); + buf[0] = fpType; + + size = 2U << (fpType - CborHalfFloatType); + if (size == 8) + put64(buf + 1, *(const uint64_t*)value); + else if (size == 4) + put32(buf + 1, *(const uint32_t*)value); + else + put16(buf + 1, *(const uint16_t*)value); + saturated_decrement(encoder); + return append_to_buffer(encoder, buf, size + 1); +} + +/** + * Appends the CBOR tag \a tag to the CBOR stream provided by \a encoder. + * + * \sa CborTag + */ +CborError cbor_encode_tag(CborEncoder *encoder, CborTag tag) +{ + /* tags don't count towards the number of elements in an array or map */ + return encode_number_no_update(encoder, tag, TagType << MajorTypeShift); +} + +static CborError encode_string(CborEncoder *encoder, size_t length, uint8_t shiftedMajorType, const void *string) +{ + CborError err = encode_number(encoder, length, shiftedMajorType); + if (err && !isOomError(err)) + return err; + return append_to_buffer(encoder, string, length); +} + +/** + * \fn CborError cbor_encode_text_stringz(CborEncoder *encoder, const char *string) + * + * Appends the null-terminated text string \a string to the CBOR stream + * provided by \a encoder. CBOR requires that \a string be valid UTF-8, but + * TinyCBOR makes no verification of correctness. The terminating null is not + * included in the stream. + * + * \sa cbor_encode_text_string, cbor_encode_byte_string + */ + +/** + * Appends the text string \a string of length \a length to the CBOR stream + * provided by \a encoder. CBOR requires that \a string be valid UTF-8, but + * TinyCBOR makes no verification of correctness. + * + * \sa CborError cbor_encode_text_stringz, cbor_encode_byte_string + */ +CborError cbor_encode_byte_string(CborEncoder *encoder, const uint8_t *string, size_t length) +{ + return encode_string(encoder, length, ByteStringType << MajorTypeShift, string); +} + +/** + * Appends the byte string \a string of length \a length to the CBOR stream + * provided by \a encoder. CBOR byte strings are arbitrary raw data. + * + * \sa cbor_encode_text_stringz, cbor_encode_text_string + */ +CborError cbor_encode_text_string(CborEncoder *encoder, const char *string, size_t length) +{ + return encode_string(encoder, length, TextStringType << MajorTypeShift, string); +} + +#ifdef __GNUC__ +__attribute__((noinline)) +#endif +static CborError create_container(CborEncoder *encoder, CborEncoder *container, size_t length, uint8_t shiftedMajorType) +{ + CborError err; + container->data.ptr = encoder->data.ptr; + container->end = encoder->end; + saturated_decrement(encoder); + container->remaining = length + 1; /* overflow ok on CborIndefiniteLength */ + + cbor_static_assert(((MapType << MajorTypeShift) & CborIteratorFlag_ContainerIsMap) == CborIteratorFlag_ContainerIsMap); + cbor_static_assert(((ArrayType << MajorTypeShift) & CborIteratorFlag_ContainerIsMap) == 0); + container->flags = shiftedMajorType & CborIteratorFlag_ContainerIsMap; + + if (length == CborIndefiniteLength) { + container->flags |= CborIteratorFlag_UnknownLength; + err = append_byte_to_buffer(container, shiftedMajorType + IndefiniteLength); + } else { + if (shiftedMajorType & CborIteratorFlag_ContainerIsMap) + container->remaining += length; + err = encode_number_no_update(container, length, shiftedMajorType); + } + return err; +} + +/** + * Creates a CBOR array in the CBOR stream provided by \a encoder and + * initializes \a arrayEncoder so that items can be added to the array using + * the CborEncoder functions. The array must be terminated by calling either + * cbor_encoder_close_container() or cbor_encoder_close_container_checked() + * with the same \a encoder and \a arrayEncoder parameters. + * + * The number of items inserted into the array must be exactly \a length items, + * otherwise the stream is invalid. If the number of items is not known when + * creating the array, the constant \ref CborIndefiniteLength may be passed as + * length instead. + * + * \sa cbor_encoder_create_map + */ +CborError cbor_encoder_create_array(CborEncoder *encoder, CborEncoder *arrayEncoder, size_t length) +{ + return create_container(encoder, arrayEncoder, length, ArrayType << MajorTypeShift); +} + +/** + * Creates a CBOR map in the CBOR stream provided by \a encoder and + * initializes \a mapEncoder so that items can be added to the map using + * the CborEncoder functions. The map must be terminated by calling either + * cbor_encoder_close_container() or cbor_encoder_close_container_checked() + * with the same \a encoder and \a mapEncoder parameters. + * + * The number of pair of items inserted into the map must be exactly \a length + * items, otherwise the stream is invalid. If the number is not known + * when creating the map, the constant \ref CborIndefiniteLength may be passed as + * length instead. + * + * \b{Implementation limitation:} TinyCBOR cannot encode more than SIZE_MAX/2 + * key-value pairs in the stream. If the length \a length is larger than this + * value (and is not \ref CborIndefiniteLength), this function returns error + * CborErrorDataTooLarge. + * + * \sa cbor_encoder_create_array + */ +CborError cbor_encoder_create_map(CborEncoder *encoder, CborEncoder *mapEncoder, size_t length) +{ + if (length != CborIndefiniteLength && length > SIZE_MAX / 2) + return CborErrorDataTooLarge; + return create_container(encoder, mapEncoder, length, MapType << MajorTypeShift); +} + +/** + * Closes the CBOR container (array or map) provided by \a containerEncoder and + * updates the CBOR stream provided by \a encoder. Both parameters must be the + * same as were passed to cbor_encoder_create_array() or + * cbor_encoder_create_map(). + * + * Since version 0.5, this function verifies that the number of items (or pairs + * of items, in the case of a map) was correct. It is no longer necessary to call + * cbor_encoder_close_container_checked() instead. + * + * \sa cbor_encoder_create_array(), cbor_encoder_create_map() + */ +CborError cbor_encoder_close_container(CborEncoder *encoder, const CborEncoder *containerEncoder) +{ + if (encoder->end) + encoder->data.ptr = containerEncoder->data.ptr; + else + encoder->data.bytes_needed = containerEncoder->data.bytes_needed; + encoder->end = containerEncoder->end; + if (containerEncoder->flags & CborIteratorFlag_UnknownLength) + return append_byte_to_buffer(encoder, BreakByte); + + if (containerEncoder->remaining != 1) + return containerEncoder->remaining == 0 ? CborErrorTooManyItems : CborErrorTooFewItems; + + if (!encoder->end) + return CborErrorOutOfMemory; /* keep the state */ + return CborNoError; +} + +/** + * \fn CborError cbor_encode_boolean(CborEncoder *encoder, bool value) + * + * Appends the boolean value \a value to the CBOR stream provided by \a encoder. + */ + +/** + * \fn CborError cbor_encode_null(CborEncoder *encoder) + * + * Appends the CBOR type representing a null value to the CBOR stream provided + * by \a encoder. + * + * \sa cbor_encode_undefined() + */ + +/** + * \fn CborError cbor_encode_undefined(CborEncoder *encoder) + * + * Appends the CBOR type representing an undefined value to the CBOR stream + * provided by \a encoder. + * + * \sa cbor_encode_null() + */ + +/** + * \fn CborError cbor_encode_half_float(CborEncoder *encoder, const void *value) + * + * Appends the IEEE 754 half-precision (16-bit) floating point value pointed to + * by \a value to the CBOR stream provided by \a encoder. + * + * \sa cbor_encode_floating_point(), cbor_encode_float(), cbor_encode_double() + */ + +/** + * \fn CborError cbor_encode_float(CborEncoder *encoder, float value) + * + * Appends the IEEE 754 single-precision (32-bit) floating point value \a value + * to the CBOR stream provided by \a encoder. + * + * \sa cbor_encode_floating_point(), cbor_encode_half_float(), cbor_encode_double() + */ + +/** + * \fn CborError cbor_encode_double(CborEncoder *encoder, double value) + * + * Appends the IEEE 754 double-precision (64-bit) floating point value \a value + * to the CBOR stream provided by \a encoder. + * + * \sa cbor_encode_floating_point(), cbor_encode_half_float(), cbor_encode_float() + */ + +/** + * \fn size_t cbor_encoder_get_buffer_size(const CborEncoder *encoder, const uint8_t *buffer) + * + * Returns the total size of the buffer starting at \a buffer after the + * encoding finished without errors. The \a encoder and \a buffer arguments + * must be the same as supplied to cbor_encoder_init(). + * + * If the encoding process had errors, the return value of this function is + * meaningless. If the only errors were CborErrorOutOfMemory, instead use + * cbor_encoder_get_extra_bytes_needed() to find out by how much to grow the + * buffer before encoding again. + * + * See \ref CborEncoding for an example of using this function. + * + * \sa cbor_encoder_init(), cbor_encoder_get_extra_bytes_needed(), CborEncoding + */ + +/** + * \fn size_t cbor_encoder_get_extra_bytes_needed(const CborEncoder *encoder) + * + * Returns how many more bytes the original buffer supplied to + * cbor_encoder_init() needs to be extended by so that no CborErrorOutOfMemory + * condition will happen for the encoding. If the buffer was big enough, this + * function returns 0. The \a encoder must be the original argument as passed + * to cbor_encoder_init(). + * + * This function is usually called after an encoding sequence ended with one or + * more CborErrorOutOfMemory errors, but no other error. If any other error + * happened, the return value of this function is meaningless. + * + * See \ref CborEncoding for an example of using this function. + * + * \sa cbor_encoder_init(), cbor_encoder_get_buffer_size(), CborEncoding + */ + +#pragma GCC diagnostic pop + +/** @} */ diff --git a/src/cbor/lib/tinycbor/src/cborencoder_close_container_checked.c b/src/cbor/lib/tinycbor/src/cborencoder_close_container_checked.c new file mode 100644 index 000000000..1165d370f --- /dev/null +++ b/src/cbor/lib/tinycbor/src/cborencoder_close_container_checked.c @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#define _BSD_SOURCE 1 +#define _DEFAULT_SOURCE 1 +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + +/** + * \addtogroup CborEncoding + * @{ + */ + +/** + * @deprecated + * + * Closes the CBOR container (array or map) provided by \a containerEncoder and + * updates the CBOR stream provided by \a encoder. Both parameters must be the + * same as were passed to cbor_encoder_create_array() or + * cbor_encoder_create_map(). + * + * Prior to version 0.5, cbor_encoder_close_container() did not check the + * number of items added. Since that version, it does and now + * cbor_encoder_close_container_checked() is no longer needed. + * + * \sa cbor_encoder_create_array(), cbor_encoder_create_map() + */ +CborError cbor_encoder_close_container_checked(CborEncoder *encoder, const CborEncoder *containerEncoder) +{ + return cbor_encoder_close_container(encoder, containerEncoder); +} + +#pragma GCC diagnostic pop + +/** @} */ diff --git a/src/cbor/lib/tinycbor/src/cborerrorstrings.c b/src/cbor/lib/tinycbor/src/cborerrorstrings.c new file mode 100644 index 000000000..e24be536f --- /dev/null +++ b/src/cbor/lib/tinycbor/src/cborerrorstrings.c @@ -0,0 +1,187 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#include "cbor.h" + +#ifndef _ +# define _(msg) msg +#endif + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + +/** + * \enum CborError + * \ingroup CborGlobals + * The CborError enum contains the possible error values used by the CBOR encoder and decoder. + * + * TinyCBOR functions report success by returning CborNoError, or one error + * condition by returning one of the values below. One exception is the + * out-of-memory condition (CborErrorOutOfMemory), which the functions for \ref + * CborEncoding may report in bit-wise OR with other conditions. + * + * This technique allows code to determine whether the only error condition was + * a lack of buffer space, which may not be a fatal condition if the buffer can + * be resized. Additionally, the functions for \ref CborEncoding may continue + * to be used even after CborErrorOutOfMemory is returned, and instead they + * will simply calculate the extra space needed. + * + * \value CborNoError No error occurred + * \omitvalue CborUnknownError + * \value CborErrorUnknownLength Request for the length of an array, map or string whose length is not provided in the CBOR stream + * \value CborErrorAdvancePastEOF Not enough data in the stream to decode item (decoding would advance past end of stream) + * \value CborErrorIO An I/O error occurred, probably due to an out-of-memory situation + * \value CborErrorGarbageAtEnd Bytes exist past the end of the CBOR stream + * \value CborErrorUnexpectedEOF End of stream reached unexpectedly + * \value CborErrorUnexpectedBreak A CBOR break byte was found where not expected + * \value CborErrorUnknownType An unknown type (future extension to CBOR) was found in the stream + * \value CborErrorIllegalType An invalid type was found while parsing a chunked CBOR string + * \value CborErrorIllegalNumber An illegal initial byte (encoding unspecified additional information) was found + * \value CborErrorIllegalSimpleType An illegal encoding of a CBOR Simple Type of value less than 32 was found + * \omitvalue CborErrorUnknownSimpleType + * \omitvalue CborErrorUnknownTag + * \omitvalue CborErrorInappropriateTagForType + * \omitvalue CborErrorDuplicateObjectKeys + * \value CborErrorInvalidUtf8TextString Illegal UTF-8 encoding found while parsing CBOR Text String + * \value CborErrorTooManyItems Too many items were added to CBOR map or array of pre-determined length + * \value CborErrorTooFewItems Too few items were added to CBOR map or array of pre-determined length + * \value CborErrorDataTooLarge Data item size exceeds TinyCBOR's implementation limits + * \value CborErrorNestingTooDeep Data item nesting exceeds TinyCBOR's implementation limits + * \omitvalue CborErrorUnsupportedType + * \value CborErrorJsonObjectKeyIsAggregate Conversion to JSON failed because the key in a map is a CBOR map or array + * \value CborErrorJsonObjectKeyNotString Conversion to JSON failed because the key in a map is not a text string + * \value CborErrorOutOfMemory During CBOR encoding, the buffer provided is insufficient for encoding the data item; + * in other situations, TinyCBOR failed to allocate memory + * \value CborErrorInternalError An internal error occurred in TinyCBOR + */ + +/** + * \ingroup CborGlobals + * Returns the error string corresponding to the CBOR error condition \a error. + */ +const char *cbor_error_string(CborError error) +{ + switch (error) { + case CborNoError: + return ""; + + case CborUnknownError: + return _("unknown error"); + + case CborErrorOutOfMemory: + return _("out of memory/need more memory"); + + case CborErrorUnknownLength: + return _("unknown length (attempted to get the length of a map/array/string of indeterminate length"); + + case CborErrorAdvancePastEOF: + return _("attempted to advance past EOF"); + + case CborErrorIO: + return _("I/O error"); + + case CborErrorGarbageAtEnd: + return _("garbage after the end of the content"); + + case CborErrorUnexpectedEOF: + return _("unexpected end of data"); + + case CborErrorUnexpectedBreak: + return _("unexpected 'break' byte"); + + case CborErrorUnknownType: + return _("illegal byte (encodes future extension type)"); + + case CborErrorIllegalType: + return _("mismatched string type in chunked string"); + + case CborErrorIllegalNumber: + return _("illegal initial byte (encodes unspecified additional information)"); + + case CborErrorIllegalSimpleType: + return _("illegal encoding of simple type smaller than 32"); + + case CborErrorUnknownSimpleType: + return _("unknown simple type"); + + case CborErrorUnknownTag: + return _("unknown tag"); + + case CborErrorInappropriateTagForType: + return _("inappropriate tag for type"); + + case CborErrorDuplicateObjectKeys: + return _("duplicate keys in object"); + + case CborErrorInvalidUtf8TextString: + return _("invalid UTF-8 content in string"); + + case CborErrorExcludedType: + return _("excluded type found"); + + case CborErrorExcludedValue: + return _("excluded value found"); + + case CborErrorImproperValue: + case CborErrorOverlongEncoding: + return _("value encoded in non-canonical form"); + + case CborErrorMapKeyNotString: + case CborErrorJsonObjectKeyNotString: + return _("key in map is not a string"); + + case CborErrorMapNotSorted: + return _("map is not sorted"); + + case CborErrorMapKeysNotUnique: + return _("map keys are not unique"); + + case CborErrorTooManyItems: + return _("too many items added to encoder"); + + case CborErrorTooFewItems: + return _("too few items added to encoder"); + + case CborErrorDataTooLarge: + return _("internal error: data too large"); + + case CborErrorNestingTooDeep: + return _("internal error: too many nested containers found in recursive function"); + + case CborErrorUnsupportedType: + return _("unsupported type"); + + case CborErrorJsonObjectKeyIsAggregate: + return _("conversion to JSON failed: key in object is an array or map"); + + case CborErrorJsonNotImplemented: + return _("conversion to JSON failed: open_memstream unavailable"); + + case CborErrorInternalError: + return _("internal error"); + } + return cbor_error_string(CborUnknownError); +} + +#pragma GCC diagnostic pop diff --git a/src/cbor/lib/tinycbor/src/cborinternal_p.h b/src/cbor/lib/tinycbor/src/cborinternal_p.h new file mode 100644 index 000000000..a85a92972 --- /dev/null +++ b/src/cbor/lib/tinycbor/src/cborinternal_p.h @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#ifndef CBORINTERNAL_P_H +#define CBORINTERNAL_P_H + +#include "compilersupport_p.h" + +#ifndef CBOR_NO_FLOATING_POINT +# include +# include +#else +# ifndef CBOR_NO_HALF_FLOAT_TYPE +# define CBOR_NO_HALF_FLOAT_TYPE 1 +# endif +#endif + +#ifndef CBOR_NO_HALF_FLOAT_TYPE +# ifdef __F16C__ +# include +static inline unsigned short encode_half(double val) +{ + return _cvtss_sh((float)val, 3); +} +static inline double decode_half(unsigned short half) +{ + return _cvtsh_ss(half); +} +# else +/* software implementation of float-to-fp16 conversions */ +static inline unsigned short encode_half(double val) +{ + uint64_t v; + int sign, exp, mant; + memcpy(&v, &val, sizeof(v)); + sign = v >> 63 << 15; + exp = (v >> 52) & 0x7ff; + mant = v << 12 >> 12 >> (53-11); /* keep only the 11 most significant bits of the mantissa */ + exp -= 1023; + if (exp == 1024) { + /* infinity or NaN */ + exp = 16; + mant >>= 1; + } else if (exp >= 16) { + /* overflow, as largest number */ + exp = 15; + mant = 1023; + } else if (exp >= -14) { + /* regular normal */ + } else if (exp >= -24) { + /* subnormal */ + mant |= 1024; + mant >>= -(exp + 14); + exp = -15; + } else { + /* underflow, make zero */ + return 0; + } + + /* safe cast here as bit operations above guarantee not to overflow */ + return (unsigned short)(sign | ((exp + 15) << 10) | mant); +} + +/* this function was copied & adapted from RFC 7049 Appendix D */ +static inline double decode_half(unsigned short half) +{ + int exp = (half >> 10) & 0x1f; + int mant = half & 0x3ff; + double val; + if (exp == 0) val = ldexp(mant, -24); + else if (exp != 31) val = ldexp(mant + 1024, exp - 25); + else val = mant == 0 ? INFINITY : NAN; + return half & 0x8000 ? -val : val; +} +# endif +#endif /* CBOR_NO_HALF_FLOAT_TYPE */ + +#ifndef CBOR_INTERNAL_API +# define CBOR_INTERNAL_API +#endif + +#ifndef CBOR_PARSER_MAX_RECURSIONS +# define CBOR_PARSER_MAX_RECURSIONS 1024 +#endif + +/* + * CBOR Major types + * Encoded in the high 3 bits of the descriptor byte + * See http://tools.ietf.org/html/rfc7049#section-2.1 + */ +typedef enum CborMajorTypes { + UnsignedIntegerType = 0U, + NegativeIntegerType = 1U, + ByteStringType = 2U, + TextStringType = 3U, + ArrayType = 4U, + MapType = 5U, /* a.k.a. object */ + TagType = 6U, + SimpleTypesType = 7U +} CborMajorTypes; + +/* + * CBOR simple and floating point types + * Encoded in the low 8 bits of the descriptor byte when the + * Major Type is 7. + */ +typedef enum CborSimpleTypes { + FalseValue = 20, + TrueValue = 21, + NullValue = 22, + UndefinedValue = 23, + SimpleTypeInNextByte = 24, /* not really a simple type */ + HalfPrecisionFloat = 25, /* ditto */ + SinglePrecisionFloat = 26, /* ditto */ + DoublePrecisionFloat = 27, /* ditto */ + Break = 31 +} CborSimpleTypes; + +enum { + SmallValueBitLength = 5U, + SmallValueMask = (1U << SmallValueBitLength) - 1, /* 31 */ + Value8Bit = 24U, + Value16Bit = 25U, + Value32Bit = 26U, + Value64Bit = 27U, + IndefiniteLength = 31U, + + MajorTypeShift = SmallValueBitLength, + MajorTypeMask = (int) (~0U << MajorTypeShift), + + BreakByte = (unsigned)Break | (SimpleTypesType << MajorTypeShift) +}; + +CBOR_INTERNAL_API CborError CBOR_INTERNAL_API_CC _cbor_value_extract_number(const uint8_t **ptr, const uint8_t *end, uint64_t *len); +CBOR_INTERNAL_API CborError CBOR_INTERNAL_API_CC _cbor_value_prepare_string_iteration(CborValue *it); +CBOR_INTERNAL_API CborError CBOR_INTERNAL_API_CC _cbor_value_get_string_chunk(const CborValue *value, const void **bufferptr, + size_t *len, CborValue *next); + + +#endif /* CBORINTERNAL_P_H */ diff --git a/src/cbor/lib/tinycbor/src/cborjson.h b/src/cbor/lib/tinycbor/src/cborjson.h new file mode 100644 index 000000000..8ff27b920 --- /dev/null +++ b/src/cbor/lib/tinycbor/src/cborjson.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#ifndef CBORJSON_H +#define CBORJSON_H + +#include "cbor.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Conversion to JSON */ +enum CborToJsonFlags +{ + CborConvertAddMetadata = 1, + CborConvertTagsToObjects = 2, + CborConvertIgnoreTags = 0, + + CborConvertObeyByteStringTags = 0, + CborConvertByteStringsToBase64Url = 4, + + CborConvertRequireMapStringKeys = 0, + CborConvertStringifyMapKeys = 8, + + CborConvertDefaultFlags = 0 +}; + +CBOR_API CborError cbor_value_to_json_advance(FILE *out, CborValue *value, int flags); +CBOR_INLINE_API CborError cbor_value_to_json(FILE *out, const CborValue *value, int flags) +{ + CborValue copy = *value; + return cbor_value_to_json_advance(out, ©, flags); +} + +#ifdef __cplusplus +} +#endif + +#endif /* CBORJSON_H */ + diff --git a/src/cbor/lib/tinycbor/src/cborparser.c b/src/cbor/lib/tinycbor/src/cborparser.c new file mode 100644 index 000000000..428159231 --- /dev/null +++ b/src/cbor/lib/tinycbor/src/cborparser.c @@ -0,0 +1,1435 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#ifndef _BSD_SOURCE +#define _BSD_SOURCE 1 +#endif +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE 1 +#endif +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" +#include "cborinternal_p.h" +#include "compilersupport_p.h" + +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + +/** + * \defgroup CborParsing Parsing CBOR streams + * \brief Group of functions used to parse CBOR streams. + * + * TinyCBOR provides functions for pull-based stream parsing of a CBOR-encoded + * payload. The main data type for the parsing is a CborValue, which behaves + * like an iterator and can be used to extract the encoded data. It is first + * initialized with a call to cbor_parser_init() and is usually used to extract + * exactly one item, most often an array or map. + * + * Nested CborValue objects can be parsed using cbor_value_enter_container(). + * Each call to cbor_value_enter_container() must be matched by a call to + * cbor_value_leave_container(), with the exact same parameters. + * + * The example below initializes a CborParser object, begins the parsing with a + * CborValue and decodes a single integer: + * + * \code + * int extract_int(const uint8_t *buffer, size_t len) + * { + * CborParser parser; + * CborValue value; + * int result; + * cbor_parser_init(buffer, len, 0, &parser, &value); + * cbor_value_get_int(&value, &result); + * return result; + * } + * \endcode + * + * The code above does no error checking, which means it assumes the data comes + * from a source trusted to send one properly-encoded integer. The following + * example does the exact same operation, but includes error checking and + * returns 0 on parsing failure: + * + * \code + * int extract_int(const uint8_t *buffer, size_t len) + * { + * CborParser parser; + * CborValue value; + * int result; + * if (cbor_parser_init(buffer, len, 0, &parser, &value) != CborNoError) + * return 0; + * if (!cbor_value_is_integer(&value) || + * cbor_value_get_int(&value, &result) != CborNoError) + * return 0; + * return result; + * } + * \endcode + * + * Note, in the example above, that one can't distinguish a parsing failure + * from an encoded value of zero. Reporting a parsing error is left as an + * exercise to the reader. + * + * The code above does not execute a range-check either: it is possible that + * the value decoded from the CBOR stream encodes a number larger than what can + * be represented in a variable of type \c{int}. If detecting that case is + * important, the code should call cbor_value_get_int_checked() instead. + * + *

Memory and parsing constraints

+ * + * TinyCBOR is designed to run with little memory and with minimal overhead. + * Except where otherwise noted, the parser functions always run on constant + * time (O(1)), do not recurse and never allocate memory (thus, stack usage is + * bounded and is O(1)). + * + *

Error handling and preconditions

+ * + * All functions operating on a CborValue return a CborError condition, with + * CborNoError standing for the normal situation in which no parsing error + * occurred. All functions may return parsing errors in case the stream cannot + * be decoded properly, be it due to corrupted data or due to reaching the end + * of the input buffer. + * + * Error conditions must not be ignored. All decoder functions have undefined + * behavior if called after an error has been reported, and may crash. + * + * Some functions are also documented to have preconditions, like + * cbor_value_get_int() requiring that the input be an integral value. + * Violation of preconditions also results in undefined behavior and the + * program may crash. + */ + +/** + * \addtogroup CborParsing + * @{ + */ + +/** + * \struct CborValue + * + * This type contains one value parsed from the CBOR stream. Each CborValue + * behaves as an iterator in a StAX-style parser. + * + * \if privatedocs + * Implementation details: the CborValue contains these fields: + * \list + * \li ptr: pointer to the actual data + * \li flags: flags from the decoder + * \li extra: partially decoded integer value (0, 1 or 2 bytes) + * \li remaining: remaining items in this collection after this item or UINT32_MAX if length is unknown + * \endlist + * \endif + */ + +static inline uint16_t get16(const uint8_t *ptr) +{ + uint16_t result; + memcpy(&result, ptr, sizeof(result)); + return cbor_ntohs(result); +} + +static inline uint32_t get32(const uint8_t *ptr) +{ + uint32_t result; + memcpy(&result, ptr, sizeof(result)); + return cbor_ntohl(result); +} + +static inline uint64_t get64(const uint8_t *ptr) +{ + uint64_t result; + memcpy(&result, ptr, sizeof(result)); + return cbor_ntohll(result); +} + +CborError CBOR_INTERNAL_API_CC _cbor_value_extract_number(const uint8_t **ptr, const uint8_t *end, uint64_t *len) +{ + size_t bytesNeeded; + uint8_t additional_information = **ptr & SmallValueMask; + ++*ptr; + if (additional_information < Value8Bit) { + *len = additional_information; + return CborNoError; + } + if (unlikely(additional_information > Value64Bit)) + return CborErrorIllegalNumber; + + bytesNeeded = (size_t)(1 << (additional_information - Value8Bit)); + if (unlikely(bytesNeeded > (size_t)(end - *ptr))) { + return CborErrorUnexpectedEOF; + } else if (bytesNeeded == 1) { + *len = (uint8_t)(*ptr)[0]; + } else if (bytesNeeded == 2) { + *len = get16(*ptr); + } else if (bytesNeeded == 4) { + *len = get32(*ptr); + } else { + *len = get64(*ptr); + } + *ptr += bytesNeeded; + return CborNoError; +} + +static CborError extract_length(const CborParser *parser, const uint8_t **ptr, size_t *len) +{ + uint64_t v; + CborError err = _cbor_value_extract_number(ptr, parser->end, &v); + if (err) { + *len = 0; + return err; + } + + *len = (size_t)v; + if (v != *len) + return CborErrorDataTooLarge; + return CborNoError; +} + +static bool is_fixed_type(uint8_t type) +{ + return type != CborTextStringType && type != CborByteStringType && type != CborArrayType && + type != CborMapType; +} + +static CborError preparse_value(CborValue *it) +{ + const CborParser *parser = it->parser; + it->type = CborInvalidType; + + /* are we at the end? */ + if (it->ptr == parser->end) + return CborErrorUnexpectedEOF; + + uint8_t descriptor = *it->ptr; + uint8_t type = descriptor & MajorTypeMask; + it->type = type; + it->flags = 0; + it->extra = (descriptor &= SmallValueMask); + + if (descriptor > Value64Bit) { + if (unlikely(descriptor != IndefiniteLength)) + return type == CborSimpleType ? CborErrorUnknownType : CborErrorIllegalNumber; + if (likely(!is_fixed_type(type))) { + /* special case */ + it->flags |= CborIteratorFlag_UnknownLength; + it->type = type; + return CborNoError; + } + return type == CborSimpleType ? CborErrorUnexpectedBreak : CborErrorIllegalNumber; + } + + size_t bytesNeeded = descriptor < Value8Bit ? 0 : (1 << (descriptor - Value8Bit)); + if (bytesNeeded + 1 > (size_t)(parser->end - it->ptr)) + return CborErrorUnexpectedEOF; + + uint8_t majortype = type >> MajorTypeShift; + if (majortype == NegativeIntegerType) { + it->flags |= CborIteratorFlag_NegativeInteger; + it->type = CborIntegerType; + } else if (majortype == SimpleTypesType) { + switch (descriptor) { + case FalseValue: + it->extra = false; + it->type = CborBooleanType; + break; + + case SinglePrecisionFloat: + case DoublePrecisionFloat: + it->flags |= CborIteratorFlag_IntegerValueTooLarge; + /* fall through */ + case TrueValue: + case NullValue: + case UndefinedValue: + case HalfPrecisionFloat: + it->type = *it->ptr; + break; + + case SimpleTypeInNextByte: + it->extra = (uint8_t)it->ptr[1]; +#ifndef CBOR_PARSER_NO_STRICT_CHECKS + if (unlikely(it->extra < 32)) { + it->type = CborInvalidType; + return CborErrorIllegalSimpleType; + } +#endif + break; + + case 28: + case 29: + case 30: + case Break: + cbor_assert(false); /* these conditions can't be reached */ + return CborErrorUnexpectedBreak; + } + return CborNoError; + } + + /* try to decode up to 16 bits */ + if (descriptor < Value8Bit) + return CborNoError; + + if (descriptor == Value8Bit) + it->extra = (uint8_t)it->ptr[1]; + else if (descriptor == Value16Bit) + it->extra = get16(it->ptr + 1); + else + it->flags |= CborIteratorFlag_IntegerValueTooLarge; /* Value32Bit or Value64Bit */ + return CborNoError; +} + +static CborError preparse_next_value_nodecrement(CborValue *it) +{ + if (it->remaining == UINT32_MAX && it->ptr != it->parser->end && *it->ptr == (uint8_t)BreakByte) { + /* end of map or array */ + ++it->ptr; + it->type = CborInvalidType; + it->remaining = 0; + return CborNoError; + } + + return preparse_value(it); +} + +static CborError preparse_next_value(CborValue *it) +{ + if (it->remaining != UINT32_MAX) { + /* don't decrement the item count if the current item is tag: they don't count */ + if (it->type != CborTagType && --it->remaining == 0) { + it->type = CborInvalidType; + return CborNoError; + } + } + return preparse_next_value_nodecrement(it); +} + +static CborError advance_internal(CborValue *it) +{ + uint64_t length; + CborError err = _cbor_value_extract_number(&it->ptr, it->parser->end, &length); + cbor_assert(err == CborNoError); + + if (it->type == CborByteStringType || it->type == CborTextStringType) { + cbor_assert(length == (size_t)length); + cbor_assert((it->flags & CborIteratorFlag_UnknownLength) == 0); + it->ptr += length; + } + + return preparse_next_value(it); +} + +/** \internal + * + * Decodes the CBOR integer value when it is larger than the 16 bits available + * in value->extra. This function requires that value->flags have the + * CborIteratorFlag_IntegerValueTooLarge flag set. + * + * This function is also used to extract single- and double-precision floating + * point values (SinglePrecisionFloat == Value32Bit and DoublePrecisionFloat == + * Value64Bit). + */ +uint64_t _cbor_value_decode_int64_internal(const CborValue *value) +{ + cbor_assert(value->flags & CborIteratorFlag_IntegerValueTooLarge || + value->type == CborFloatType || value->type == CborDoubleType); + + /* since the additional information can only be Value32Bit or Value64Bit, + * we just need to test for the one bit those two options differ */ + cbor_assert((*value->ptr & SmallValueMask) == Value32Bit || (*value->ptr & SmallValueMask) == Value64Bit); + if ((*value->ptr & 1) == (Value32Bit & 1)) + return get32(value->ptr + 1); + + cbor_assert((*value->ptr & SmallValueMask) == Value64Bit); + return get64(value->ptr + 1); +} + +/** + * Initializes the CBOR parser for parsing \a size bytes beginning at \a + * buffer. Parsing will use flags set in \a flags. The iterator to the first + * element is returned in \a it. + * + * The \a parser structure needs to remain valid throughout the decoding + * process. It is not thread-safe to share one CborParser among multiple + * threads iterating at the same time, but the object can be copied so multiple + * threads can iterate. + */ +CborError cbor_parser_init(const uint8_t *buffer, size_t size, uint32_t flags, CborParser *parser, CborValue *it) +{ + memset(parser, 0, sizeof(*parser)); + parser->end = buffer + size; + parser->flags = flags; + it->parser = parser; + it->ptr = buffer; + it->remaining = 1; /* there's one type altogether, usually an array or map */ + return preparse_value(it); +} + +/** + * \fn bool cbor_value_at_end(const CborValue *it) + * + * Returns true if \a it has reached the end of the iteration, usually when + * advancing after the last item in an array or map. + * + * In the case of the outermost CborValue object, this function returns true + * after decoding a single element. A pointer to the first byte of the + * remaining data (if any) can be obtained with cbor_value_get_next_byte(). + * + * \sa cbor_value_advance(), cbor_value_is_valid(), cbor_value_get_next_byte() + */ + +/** + * \fn const uint8_t *cbor_value_get_next_byte(const CborValue *it) + * + * Returns a pointer to the next byte that would be decoded if this CborValue + * object were advanced. + * + * This function is useful if cbor_value_at_end() returns true for the + * outermost CborValue: the pointer returned is the first byte of the data + * remaining in the buffer, if any. Code can decide whether to begin decoding a + * new CBOR data stream from this point, or parse some other data appended to + * the same buffer. + * + * This function may be used even after a parsing error. If that occurred, + * then this function returns a pointer to where the parsing error occurred. + * Note that the error recovery is not precise and the pointer may not indicate + * the exact byte containing bad data. + * + * \sa cbor_value_at_end() + */ + +/** + * \fn bool cbor_value_is_valid(const CborValue *it) + * + * Returns true if the iterator \a it contains a valid value. Invalid iterators + * happen when iteration reaches the end of a container (see \ref + * cbor_value_at_end()) or when a search function resulted in no matches. + * + * \sa cbor_value_advance(), cbor_value_at_end(), cbor_value_get_type() + */ + +/** + * Performs a basic validation of the CBOR stream pointed by \a it and returns + * the error it found. If no error was found, it returns CborNoError and the + * application can iterate over the items with certainty that no other errors + * will appear during parsing. + * + * A basic validation checks for: + * \list + * \li absence of undefined additional information bytes; + * \li well-formedness of all numbers, lengths, and simple values; + * \li string contents match reported sizes; + * \li arrays and maps contain the number of elements they are reported to have; + * \endlist + * + * For further checks, see cbor_value_validate(). + * + * This function has the same timing and memory requirements as + * cbor_value_advance(). + * + * \sa cbor_value_validate(), cbor_value_advance() + */ +CborError cbor_value_validate_basic(const CborValue *it) +{ + CborValue value = *it; + return cbor_value_advance(&value); +} + +/** + * Advances the CBOR value \a it by one fixed-size position. Fixed-size types + * are: integers, tags, simple types (including boolean, null and undefined + * values) and floating point types. + * + * If the type is not of fixed size, this function has undefined behavior. Code + * must be sure that the current type is one of the fixed-size types before + * calling this function. This function is provided because it can guarantee + * that it runs in constant time (O(1)). + * + * If the caller is not able to determine whether the type is fixed or not, code + * can use the cbor_value_advance() function instead. + * + * \sa cbor_value_at_end(), cbor_value_advance(), cbor_value_enter_container(), cbor_value_leave_container() + */ +CborError cbor_value_advance_fixed(CborValue *it) +{ + cbor_assert(it->type != CborInvalidType); + cbor_assert(is_fixed_type(it->type)); + if (!it->remaining) + return CborErrorAdvancePastEOF; + return advance_internal(it); +} + +static CborError advance_recursive(CborValue *it, int nestingLevel) +{ + CborError err; + CborValue recursed; + + if (is_fixed_type(it->type)) + return advance_internal(it); + + if (!cbor_value_is_container(it)) { + size_t len = SIZE_MAX; + return _cbor_value_copy_string(it, NULL, &len, it); + } + + /* map or array */ + if (nestingLevel == 0) + return CborErrorNestingTooDeep; + + err = cbor_value_enter_container(it, &recursed); + if (err) + return err; + while (!cbor_value_at_end(&recursed)) { + err = advance_recursive(&recursed, nestingLevel - 1); + if (err) + return err; + } + return cbor_value_leave_container(it, &recursed); +} + + +/** + * Advances the CBOR value \a it by one element, skipping over containers. + * Unlike cbor_value_advance_fixed(), this function can be called on a CBOR + * value of any type. However, if the type is a container (map or array) or a + * string with a chunked payload, this function will not run in constant time + * and will recurse into itself (it will run on O(n) time for the number of + * elements or chunks and will use O(n) memory for the number of nested + * containers). + * + * The number of recursions can be limited at compile time to avoid stack + * exhaustion in constrained systems. + * + * \sa cbor_value_at_end(), cbor_value_advance_fixed(), cbor_value_enter_container(), cbor_value_leave_container() + */ +CborError cbor_value_advance(CborValue *it) +{ + cbor_assert(it->type != CborInvalidType); + if (!it->remaining) + return CborErrorAdvancePastEOF; + return advance_recursive(it, CBOR_PARSER_MAX_RECURSIONS); +} + +/** + * \fn bool cbor_value_is_tag(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR tag. + * + * \sa cbor_value_get_tag(), cbor_value_skip_tag() + */ + +/** + * \fn CborError cbor_value_get_tag(const CborValue *value, CborTag *result) + * + * Retrieves the CBOR tag value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to a CBOR tag value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_tag is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_tag() + */ + +/** + * Advances the CBOR value \a it until it no longer points to a tag. If \a it is + * already not pointing to a tag, then this function returns it unchanged. + * + * This function does not run in constant time: it will run on O(n) for n being + * the number of tags. It does use constant memory (O(1) memory requirements). + * + * \sa cbor_value_advance_fixed(), cbor_value_advance() + */ +CborError cbor_value_skip_tag(CborValue *it) +{ + while (cbor_value_is_tag(it)) { + CborError err = cbor_value_advance_fixed(it); + if (err) + return err; + } + return CborNoError; +} + +/** + * \fn bool cbor_value_is_container(const CborValue *it) + * + * Returns true if the \a it value is a container and requires recursion in + * order to decode (maps and arrays), false otherwise. + */ + +/** + * Creates a CborValue iterator pointing to the first element of the container + * represented by \a it and saves it in \a recursed. The \a it container object + * needs to be kept and passed again to cbor_value_leave_container() in order + * to continue iterating past this container. + * + * The \a it CborValue iterator must point to a container. + * + * \sa cbor_value_is_container(), cbor_value_leave_container(), cbor_value_advance() + */ +CborError cbor_value_enter_container(const CborValue *it, CborValue *recursed) +{ + cbor_assert(cbor_value_is_container(it)); + *recursed = *it; + + if (it->flags & CborIteratorFlag_UnknownLength) { + recursed->remaining = UINT32_MAX; + ++recursed->ptr; + } else { + uint64_t len; + CborError err = _cbor_value_extract_number(&recursed->ptr, recursed->parser->end, &len); + cbor_assert(err == CborNoError); + + recursed->remaining = (uint32_t)len; + if (recursed->remaining != len || len == UINT32_MAX) { + /* back track the pointer to indicate where the error occurred */ + recursed->ptr = it->ptr; + return CborErrorDataTooLarge; + } + if (recursed->type == CborMapType) { + /* maps have keys and values, so we need to multiply by 2 */ + if (recursed->remaining > UINT32_MAX / 2) { + /* back track the pointer to indicate where the error occurred */ + recursed->ptr = it->ptr; + return CborErrorDataTooLarge; + } + recursed->remaining *= 2; + } + if (len == 0) { + /* the case of the empty container */ + recursed->type = CborInvalidType; + return CborNoError; + } + } + return preparse_next_value_nodecrement(recursed); +} + +/** + * Updates \a it to point to the next element after the container. The \a + * recursed object needs to point to the element obtained either by advancing + * the last element of the container (via cbor_value_advance(), + * cbor_value_advance_fixed(), a nested cbor_value_leave_container(), or the \c + * next pointer from cbor_value_copy_string() or cbor_value_dup_string()). + * + * The \a it and \a recursed parameters must be the exact same as passed to + * cbor_value_enter_container(). + * + * \sa cbor_value_enter_container(), cbor_value_at_end() + */ +CborError cbor_value_leave_container(CborValue *it, const CborValue *recursed) +{ + cbor_assert(cbor_value_is_container(it)); + cbor_assert(recursed->type == CborInvalidType); + it->ptr = recursed->ptr; + return preparse_next_value(it); +} + + +/** + * \fn CborType cbor_value_get_type(const CborValue *value) + * + * Returns the type of the CBOR value that the iterator \a value points to. If + * \a value does not point to a valid value, this function returns \ref + * CborInvalidType. + * + * TinyCBOR also provides functions to test directly if a given CborValue object + * is of a given type, like cbor_value_is_text_string() and cbor_value_is_null(). + * + * \sa cbor_value_is_valid() + */ + +/** + * \fn bool cbor_value_is_null(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR null type. + * + * \sa cbor_value_is_valid(), cbor_value_is_undefined() + */ + +/** + * \fn bool cbor_value_is_undefined(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR undefined type. + * + * \sa cbor_value_is_valid(), cbor_value_is_null() + */ + +/** + * \fn bool cbor_value_is_boolean(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR boolean + * type (true or false). + * + * \sa cbor_value_is_valid(), cbor_value_get_boolean() + */ + +/** + * \fn CborError cbor_value_get_boolean(const CborValue *value, bool *result) + * + * Retrieves the boolean value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to a boolean value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_boolean is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_boolean() + */ + +/** + * \fn bool cbor_value_is_simple_type(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR Simple Type + * type (other than true, false, null and undefined). + * + * \sa cbor_value_is_valid(), cbor_value_get_simple_type() + */ + +/** + * \fn CborError cbor_value_get_simple_type(const CborValue *value, uint8_t *result) + * + * Retrieves the CBOR Simple Type value that \a value points to and stores it + * in \a result. If the iterator \a value does not point to a simple_type + * value, the behavior is undefined, so checking with \ref cbor_value_get_type + * or with \ref cbor_value_is_simple_type is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_simple_type() + */ + +/** + * \fn bool cbor_value_is_integer(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR integer + * type. + * + * \sa cbor_value_is_valid(), cbor_value_get_int, cbor_value_get_int64, cbor_value_get_uint64, cbor_value_get_raw_integer + */ + +/** + * \fn bool cbor_value_is_unsigned_integer(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR unsigned + * integer type (positive values or zero). + * + * \sa cbor_value_is_valid(), cbor_value_get_uint64() + */ + +/** + * \fn bool cbor_value_is_negative_integer(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR negative + * integer type. + * + * \sa cbor_value_is_valid(), cbor_value_get_int, cbor_value_get_int64, cbor_value_get_raw_integer + */ + +/** + * \fn CborError cbor_value_get_int(const CborValue *value, int *result) + * + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an integer value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_integer is recommended. + * + * Note that this function does not do range-checking: integral values that do + * not fit in a variable of type \c{int} are silently truncated to fit. Use + * cbor_value_get_int_checked() if that is not acceptable. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer() + */ + +/** + * \fn CborError cbor_value_get_int64(const CborValue *value, int64_t *result) + * + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an integer value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_integer is recommended. + * + * Note that this function does not do range-checking: integral values that do + * not fit in a variable of type \c{int64_t} are silently truncated to fit. Use + * cbor_value_get_int64_checked() that is not acceptable. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer() + */ + +/** + * \fn CborError cbor_value_get_uint64(const CborValue *value, uint64_t *result) + * + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an unsigned integer + * value, the behavior is undefined, so checking with \ref cbor_value_get_type + * or with \ref cbor_value_is_unsigned_integer is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_unsigned_integer() + */ + +/** + * \fn CborError cbor_value_get_raw_integer(const CborValue *value, uint64_t *result) + * + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an integer value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_integer is recommended. + * + * This function is provided because CBOR negative integers can assume values + * that cannot be represented with normal 64-bit integer variables. + * + * If the integer is unsigned (that is, if cbor_value_is_unsigned_integer() + * returns true), then \a result will contain the actual value. If the integer + * is negative, then \a result will contain the absolute value of that integer, + * minus one. That is, \c {actual = -result - 1}. On architectures using two's + * complement for representation of negative integers, it is equivalent to say + * that \a result will contain the bitwise negation of the actual value. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer() + */ + +/** + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an integer value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_integer is recommended. + * + * Unlike \ref cbor_value_get_int64(), this function performs a check to see if the + * stored integer fits in \a result without data loss. If the number is outside + * the valid range for the data type, this function returns the recoverable + * error CborErrorDataTooLarge. In that case, use either + * cbor_value_get_uint64() (if the number is positive) or + * cbor_value_get_raw_integer(). + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer(), cbor_value_get_int64() + */ +CborError cbor_value_get_int64_checked(const CborValue *value, int64_t *result) +{ + uint64_t v; + cbor_assert(cbor_value_is_integer(value)); + v = _cbor_value_extract_int64_helper(value); + + /* Check before converting, as the standard says (C11 6.3.1.3 paragraph 3): + * "[if] the new type is signed and the value cannot be represented in it; either the + * result is implementation-defined or an implementation-defined signal is raised." + * + * The range for int64_t is -2^63 to 2^63-1 (int64_t is required to be + * two's complement, C11 7.20.1.1 paragraph 3), which in CBOR is + * represented the same way, differing only on the "sign bit" (the major + * type). + */ + + if (unlikely(v > (uint64_t)INT64_MAX)) + return CborErrorDataTooLarge; + + *result = v; + if (value->flags & CborIteratorFlag_NegativeInteger) + *result = -*result - 1; + return CborNoError; +} + +/** + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an integer value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_integer is recommended. + * + * Unlike \ref cbor_value_get_int(), this function performs a check to see if the + * stored integer fits in \a result without data loss. If the number is outside + * the valid range for the data type, this function returns the recoverable + * error CborErrorDataTooLarge. In that case, use one of the other integer + * functions to obtain the value. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer(), cbor_value_get_int64(), + * cbor_value_get_uint64(), cbor_value_get_int64_checked(), cbor_value_get_raw_integer() + */ +CborError cbor_value_get_int_checked(const CborValue *value, int *result) +{ + uint64_t v; + cbor_assert(cbor_value_is_integer(value)); + v = _cbor_value_extract_int64_helper(value); + + /* Check before converting, as the standard says (C11 6.3.1.3 paragraph 3): + * "[if] the new type is signed and the value cannot be represented in it; either the + * result is implementation-defined or an implementation-defined signal is raised." + * + * But we can convert from signed to unsigned without fault (paragraph 2). + * + * The range for int is implementation-defined and int is not guaranteed to use + * two's complement representation (although int32_t is). + */ + + if (value->flags & CborIteratorFlag_NegativeInteger) { + if (unlikely(v > (unsigned) -(INT_MIN + 1))) + return CborErrorDataTooLarge; + + *result = (int)v; + *result = -*result - 1; + } else { + if (unlikely(v > (uint64_t)INT_MAX)) + return CborErrorDataTooLarge; + + *result = (int)v; + } + return CborNoError; + +} + +/** + * \fn bool cbor_value_is_length_known(const CborValue *value) + * + * Returns true if the length of this type is known without calculation. That + * is, if the length of this CBOR string, map or array is encoded in the data + * stream, this function returns true. If the length is not encoded, it returns + * false. + * + * If the length is known, code can call cbor_value_get_string_length(), + * cbor_value_get_array_length() or cbor_value_get_map_length() to obtain the + * length. If the length is not known but is necessary, code can use the + * cbor_value_calculate_string_length() function (no equivalent function is + * provided for maps and arrays). + */ + +/** + * \fn bool cbor_value_is_text_string(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR text + * string. CBOR text strings are UTF-8 encoded and usually contain + * human-readable text. + * + * \sa cbor_value_is_valid(), cbor_value_get_string_length(), cbor_value_calculate_string_length(), + * cbor_value_copy_text_string(), cbor_value_dup_text_string() + */ + +/** + * \fn bool cbor_value_is_byte_string(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR text + * string. CBOR byte strings are binary data with no specified encoding or + * format. + * + * \sa cbor_value_is_valid(), cbor_value_get_string_length(), cbor_value_calculate_string_length(), + * cbor_value_copy_byte_string(), cbor_value_dup_byte_string() + */ + +/** + * \fn CborError cbor_value_get_string_length(const CborValue *value, size_t *length) + * + * Extracts the length of the byte or text string that \a value points to and + * stores it in \a result. If the iterator \a value does not point to a text + * string or a byte string, the behaviour is undefined, so checking with \ref + * cbor_value_get_type, with \ref cbor_value_is_text_string or \ref + * cbor_value_is_byte_string is recommended. + * + * If the length of this string is not encoded in the CBOR data stream, this + * function will return the recoverable error CborErrorUnknownLength. You may + * also check whether that is the case by using cbor_value_is_length_known(). + * + * If the length of the string is required but the length was not encoded, use + * cbor_value_calculate_string_length(), but note that that function does not + * run in constant time. + * + * \note On 32-bit platforms, this function will return error condition of \ref + * CborErrorDataTooLarge if the stream indicates a length that is too big to + * fit in 32-bit. + * + * \sa cbor_value_is_valid(), cbor_value_is_length_known(), cbor_value_calculate_string_length() + */ + +/** + * Calculates the length of the byte or text string that \a value points to and + * stores it in \a len. If the iterator \a value does not point to a text + * string or a byte string, the behaviour is undefined, so checking with \ref + * cbor_value_get_type, with \ref cbor_value_is_text_string or \ref + * cbor_value_is_byte_string is recommended. + * + * This function is different from cbor_value_get_string_length() in that it + * calculates the length even for strings sent in chunks. For that reason, this + * function may not run in constant time (it will run in O(n) time on the + * number of chunks). It does use constant memory (O(1)). + * + * \note On 32-bit platforms, this function will return error condition of \ref + * CborErrorDataTooLarge if the stream indicates a length that is too big to + * fit in 32-bit. + * + * \sa cbor_value_get_string_length(), cbor_value_copy_text_string(), cbor_value_copy_byte_string(), cbor_value_is_length_known() + */ +CborError cbor_value_calculate_string_length(const CborValue *value, size_t *len) +{ + *len = SIZE_MAX; + return _cbor_value_copy_string(value, NULL, len, NULL); +} + +static inline void prepare_string_iteration(CborValue *it) +{ + if (!cbor_value_is_length_known(it)) { + /* chunked string: we're before the first chunk; + * advance to the first chunk */ + ++it->ptr; + it->flags |= CborIteratorFlag_IteratingStringChunks; + } +} + +CborError CBOR_INTERNAL_API_CC _cbor_value_prepare_string_iteration(CborValue *it) +{ + cbor_assert((it->flags & CborIteratorFlag_IteratingStringChunks) == 0); + prepare_string_iteration(it); + + /* are we at the end? */ + if (it->ptr == it->parser->end) + return CborErrorUnexpectedEOF; + return CborNoError; +} + +static CborError get_string_chunk(CborValue *it, const void **bufferptr, size_t *len) +{ + CborError err; + + /* Possible states: + * length known | iterating | meaning + * no | no | before the first chunk of a chunked string + * yes | no | at a non-chunked string + * no | yes | second or later chunk + * yes | yes | after a non-chunked string + */ + if (it->flags & CborIteratorFlag_IteratingStringChunks) { + /* already iterating */ + if (cbor_value_is_length_known(it)) { + /* if the length was known, it wasn't chunked, so finish iteration */ + goto last_chunk; + } + } else { + prepare_string_iteration(it); + } + + /* are we at the end? */ + if (it->ptr == it->parser->end) + return CborErrorUnexpectedEOF; + + if (*it->ptr == BreakByte) { + /* last chunk */ + ++it->ptr; +last_chunk: + *bufferptr = NULL; + *len = 0; + return preparse_next_value(it); + } else if ((uint8_t)(*it->ptr & MajorTypeMask) == it->type) { + err = extract_length(it->parser, &it->ptr, len); + if (err) + return err; + if (*len > (size_t)(it->parser->end - it->ptr)) + return CborErrorUnexpectedEOF; + + *bufferptr = it->ptr; + it->ptr += *len; + } else { + return CborErrorIllegalType; + } + + it->flags |= CborIteratorFlag_IteratingStringChunks; + return CborNoError; +} + +CborError CBOR_INTERNAL_API_CC +_cbor_value_get_string_chunk(const CborValue *value, const void **bufferptr, + size_t *len, CborValue *next) +{ + CborValue tmp; + if (!next) + next = &tmp; + *next = *value; + return get_string_chunk(next, bufferptr, len); +} + +/* We return uintptr_t so that we can pass memcpy directly as the iteration + * function. The choice is to optimize for memcpy, which is used in the base + * parser API (cbor_value_copy_string), while memcmp is used in convenience API + * only. */ +typedef uintptr_t (*IterateFunction)(char *, const uint8_t *, size_t); + +static uintptr_t iterate_noop(char *dest, const uint8_t *src, size_t len) +{ + (void)dest; + (void)src; + (void)len; + return true; +} + +static uintptr_t iterate_memcmp(char *s1, const uint8_t *s2, size_t len) +{ + return memcmp(s1, (const char *)s2, len) == 0; +} + +static uintptr_t iterate_memcpy(char *dest, const uint8_t *src, size_t len) +{ + return (uintptr_t)memcpy(dest, src, len); +} + +static CborError iterate_string_chunks(const CborValue *value, char *buffer, size_t *buflen, + bool *result, CborValue *next, IterateFunction func) +{ + CborError err; + CborValue tmp; + size_t total = 0; + const void *ptr; + + cbor_assert(cbor_value_is_byte_string(value) || cbor_value_is_text_string(value)); + if (!next) + next = &tmp; + *next = *value; + *result = true; + + while (1) { + size_t newTotal; + size_t chunkLen; + err = get_string_chunk(next, &ptr, &chunkLen); + if (err) + return err; + if (!ptr) + break; + + if (unlikely(add_check_overflow(total, chunkLen, &newTotal))) + return CborErrorDataTooLarge; + + if (*result && *buflen >= newTotal) + *result = !!func(buffer + total, (const uint8_t *)ptr, chunkLen); + else + *result = false; + + total = newTotal; + } + + /* is there enough room for the ending NUL byte? */ + if (*result && *buflen > total) { + uint8_t nul[] = { 0 }; + *result = !!func(buffer + total, nul, 1); + } + *buflen = total; + return CborNoError; +} + +/** + * \fn CborError cbor_value_copy_text_string(const CborValue *value, char *buffer, size_t *buflen, CborValue *next) + * + * Copies the string pointed to by \a value into the buffer provided at \a buffer + * of \a buflen bytes. If \a buffer is a NULL pointer, this function will not + * copy anything and will only update the \a next value. + * + * If the iterator \a value does not point to a text string, the behaviour is + * undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_text_string is recommended. + * + * If the provided buffer length was too small, this function returns an error + * condition of \ref CborErrorOutOfMemory. If you need to calculate the length + * of the string in order to preallocate a buffer, use + * cbor_value_calculate_string_length(). + * + * On success, this function sets the number of bytes copied to \c{*buflen}. If + * the buffer is large enough, this function will insert a null byte after the + * last copied byte, to facilitate manipulation of text strings. That byte is + * not included in the returned value of \c{*buflen}. If there was no space for + * the terminating null, no error is returned, so callers must check the value + * of *buflen after the call, before relying on the '\0'; if it has not been + * changed by the call, there is no '\0'-termination on the buffer's contents. + * + * The \a next pointer, if not null, will be updated to point to the next item + * after this string. If \a value points to the last item, then \a next will be + * invalid. + * + * This function may not run in constant time (it will run in O(n) time on the + * number of chunks). It requires constant memory (O(1)). + * + * \note This function does not perform UTF-8 validation on the incoming text + * string. + * + * \sa cbor_value_get_text_string_chunk() cbor_value_dup_text_string(), cbor_value_copy_byte_string(), cbor_value_get_string_length(), cbor_value_calculate_string_length() + */ + +/** + * \fn CborError cbor_value_copy_byte_string(const CborValue *value, uint8_t *buffer, size_t *buflen, CborValue *next) + * + * Copies the string pointed by \a value into the buffer provided at \a buffer + * of \a buflen bytes. If \a buffer is a NULL pointer, this function will not + * copy anything and will only update the \a next value. + * + * If the iterator \a value does not point to a byte string, the behaviour is + * undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_byte_string is recommended. + * + * If the provided buffer length was too small, this function returns an error + * condition of \ref CborErrorOutOfMemory. If you need to calculate the length + * of the string in order to preallocate a buffer, use + * cbor_value_calculate_string_length(). + * + * On success, this function sets the number of bytes copied to \c{*buflen}. If + * the buffer is large enough, this function will insert a null byte after the + * last copied byte, to facilitate manipulation of null-terminated strings. + * That byte is not included in the returned value of \c{*buflen}. + * + * The \a next pointer, if not null, will be updated to point to the next item + * after this string. If \a value points to the last item, then \a next will be + * invalid. + * + * This function may not run in constant time (it will run in O(n) time on the + * number of chunks). It requires constant memory (O(1)). + * + * \sa cbor_value_get_byte_string_chunk(), cbor_value_dup_text_string(), cbor_value_copy_text_string(), cbor_value_get_string_length(), cbor_value_calculate_string_length() + */ + +CborError _cbor_value_copy_string(const CborValue *value, void *buffer, + size_t *buflen, CborValue *next) +{ + bool copied_all; + CborError err = iterate_string_chunks(value, (char*)buffer, buflen, &copied_all, next, + buffer ? iterate_memcpy : iterate_noop); + return err ? err : + copied_all ? CborNoError : CborErrorOutOfMemory; +} + +/** + * Compares the entry \a value with the string \a string and stores the result + * in \a result. If the value is different from \a string \a result will + * contain \c false. + * + * The entry at \a value may be a tagged string. If \a value is not a string or + * a tagged string, the comparison result will be false. + * + * CBOR requires text strings to be encoded in UTF-8, but this function does + * not validate either the strings in the stream or the string \a string to be + * matched. Moreover, comparison is done on strict codepoint comparison, + * without any Unicode normalization. + * + * This function may not run in constant time (it will run in O(n) time on the + * number of chunks). It requires constant memory (O(1)). + * + * \sa cbor_value_skip_tag(), cbor_value_copy_text_string() + */ +CborError cbor_value_text_string_equals(const CborValue *value, const char *string, bool *result) +{ + size_t len; + CborValue copy = *value; + CborError err = cbor_value_skip_tag(©); + if (err) + return err; + if (!cbor_value_is_text_string(©)) { + *result = false; + return CborNoError; + } + + len = strlen(string); + return iterate_string_chunks(©, CONST_CAST(char *, string), &len, result, NULL, iterate_memcmp); +} + +/** + * \fn bool cbor_value_is_array(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR array. + * + * \sa cbor_value_is_valid(), cbor_value_is_map() + */ + +/** + * \fn CborError cbor_value_get_array_length(const CborValue *value, size_t *length) + * + * Extracts the length of the CBOR array that \a value points to and stores it + * in \a result. If the iterator \a value does not point to a CBOR array, the + * behaviour is undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_array is recommended. + * + * If the length of this array is not encoded in the CBOR data stream, this + * function will return the recoverable error CborErrorUnknownLength. You may + * also check whether that is the case by using cbor_value_is_length_known(). + * + * \note On 32-bit platforms, this function will return error condition of \ref + * CborErrorDataTooLarge if the stream indicates a length that is too big to + * fit in 32-bit. + * + * \sa cbor_value_is_valid(), cbor_value_is_length_known() + */ + +/** + * \fn bool cbor_value_is_map(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR map. + * + * \sa cbor_value_is_valid(), cbor_value_is_array() + */ + +/** + * \fn CborError cbor_value_get_map_length(const CborValue *value, size_t *length) + * + * Extracts the length of the CBOR map that \a value points to and stores it in + * \a result. If the iterator \a value does not point to a CBOR map, the + * behaviour is undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_map is recommended. + * + * If the length of this map is not encoded in the CBOR data stream, this + * function will return the recoverable error CborErrorUnknownLength. You may + * also check whether that is the case by using cbor_value_is_length_known(). + * + * \note On 32-bit platforms, this function will return error condition of \ref + * CborErrorDataTooLarge if the stream indicates a length that is too big to + * fit in 32-bit. + * + * \sa cbor_value_is_valid(), cbor_value_is_length_known() + */ + +/** + * Attempts to find the value in map \a map that corresponds to the text string + * entry \a string. If the iterator \a value does not point to a CBOR map, the + * behaviour is undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_map is recommended. + * + * If the item is found, it is stored in \a result. If no item is found + * matching the key, then \a result will contain an element of type \ref + * CborInvalidType. Matching is performed using + * cbor_value_text_string_equals(), so tagged strings will also match. + * + * This function has a time complexity of O(n) where n is the number of + * elements in the map to be searched. In addition, this function is has O(n) + * memory requirement based on the number of nested containers (maps or arrays) + * found as elements of this map. + * + * \sa cbor_value_is_valid(), cbor_value_text_string_equals(), cbor_value_advance() + */ +CborError cbor_value_map_find_value(const CborValue *map, const char *string, CborValue *element) +{ + CborError err; + size_t len = strlen(string); + cbor_assert(cbor_value_is_map(map)); + err = cbor_value_enter_container(map, element); + if (err) + goto error; + + while (!cbor_value_at_end(element)) { + /* find the non-tag so we can compare */ + err = cbor_value_skip_tag(element); + if (err) + goto error; + if (cbor_value_is_text_string(element)) { + bool equals; + size_t dummyLen = len; + err = iterate_string_chunks(element, CONST_CAST(char *, string), &dummyLen, + &equals, element, iterate_memcmp); + if (err) + goto error; + if (equals) + return preparse_value(element); + } else { + /* skip this key */ + err = cbor_value_advance(element); + if (err) + goto error; + } + + /* skip this value */ + err = cbor_value_skip_tag(element); + if (err) + goto error; + err = cbor_value_advance(element); + if (err) + goto error; + } + + /* not found */ + element->type = CborInvalidType; + return CborNoError; + +error: + element->type = CborInvalidType; + return err; +} + +/** + * \fn bool cbor_value_is_float(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR + * single-precision floating point (32-bit). + * + * \sa cbor_value_is_valid(), cbor_value_is_double(), cbor_value_is_half_float() + */ + +/** + * \fn CborError cbor_value_get_float(const CborValue *value, float *result) + * + * Retrieves the CBOR single-precision floating point (32-bit) value that \a + * value points to and stores it in \a result. If the iterator \a value does + * not point to a single-precision floating point value, the behavior is + * undefined, so checking with \ref cbor_value_get_type or with \ref + * cbor_value_is_float is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_float(), cbor_value_get_double() + */ + +/** + * \fn bool cbor_value_is_double(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR + * double-precision floating point (64-bit). + * + * \sa cbor_value_is_valid(), cbor_value_is_float(), cbor_value_is_half_float() + */ + +/** + * \fn CborError cbor_value_get_double(const CborValue *value, float *result) + * + * Retrieves the CBOR double-precision floating point (64-bit) value that \a + * value points to and stores it in \a result. If the iterator \a value does + * not point to a double-precision floating point value, the behavior is + * undefined, so checking with \ref cbor_value_get_type or with \ref + * cbor_value_is_double is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_double(), cbor_value_get_float() + */ + +/** + * \fn bool cbor_value_is_half_float(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR + * single-precision floating point (16-bit). + * + * \sa cbor_value_is_valid(), cbor_value_is_double(), cbor_value_is_float() + */ + +/** + * Retrieves the CBOR half-precision floating point (16-bit) value that \a + * value points to and stores it in \a result. If the iterator \a value does + * not point to a half-precision floating point value, the behavior is + * undefined, so checking with \ref cbor_value_get_type or with \ref + * cbor_value_is_half_float is recommended. + * + * Note: since the C language does not have a standard type for half-precision + * floating point, this function takes a \c{void *} as a parameter for the + * storage area, which must be at least 16 bits wide. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_half_float(), cbor_value_get_float() + */ +CborError cbor_value_get_half_float(const CborValue *value, void *result) +{ + uint16_t v; + cbor_assert(cbor_value_is_half_float(value)); + + /* size has been computed already */ + v = get16(value->ptr + 1); + memcpy(result, &v, sizeof(v)); + return CborNoError; +} + +#pragma GCC diagnostic pop + +/** @} */ diff --git a/src/cbor/lib/tinycbor/src/cborparser_dup_string.c b/src/cbor/lib/tinycbor/src/cborparser_dup_string.c new file mode 100644 index 000000000..1360d8b1a --- /dev/null +++ b/src/cbor/lib/tinycbor/src/cborparser_dup_string.c @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#ifndef _BSD_SOURCE +#define _BSD_SOURCE 1 +#endif +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE 1 +#endif +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" +#include "compilersupport_p.h" +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + +/** + * \fn CborError cbor_value_dup_text_string(const CborValue *value, char **buffer, size_t *buflen, CborValue *next) + * + * Allocates memory for the string pointed by \a value and copies it into this + * buffer. The pointer to the buffer is stored in \a buffer and the number of + * bytes copied is stored in \a buflen (those variables must not be NULL). + * + * If the iterator \a value does not point to a text string, the behaviour is + * undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_text_string is recommended. + * + * If \c malloc returns a NULL pointer, this function will return error + * condition \ref CborErrorOutOfMemory. + * + * On success, \c{*buffer} will contain a valid pointer that must be freed by + * calling \c{free()}. This is the case even for zero-length strings. + * + * The \a next pointer, if not null, will be updated to point to the next item + * after this string. If \a value points to the last item, then \a next will be + * invalid. + * + * This function may not run in constant time (it will run in O(n) time on the + * number of chunks). It requires constant memory (O(1)) in addition to the + * malloc'ed block. + * + * \note This function does not perform UTF-8 validation on the incoming text + * string. + * + * \sa cbor_value_get_text_string_chunk(), cbor_value_copy_text_string(), cbor_value_dup_byte_string() + */ + +/** + * \fn CborError cbor_value_dup_byte_string(const CborValue *value, uint8_t **buffer, size_t *buflen, CborValue *next) + * + * Allocates memory for the string pointed by \a value and copies it into this + * buffer. The pointer to the buffer is stored in \a buffer and the number of + * bytes copied is stored in \a buflen (those variables must not be NULL). + * + * If the iterator \a value does not point to a byte string, the behaviour is + * undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_byte_string is recommended. + * + * If \c malloc returns a NULL pointer, this function will return error + * condition \ref CborErrorOutOfMemory. + * + * On success, \c{*buffer} will contain a valid pointer that must be freed by + * calling \c{free()}. This is the case even for zero-length strings. + * + * The \a next pointer, if not null, will be updated to point to the next item + * after this string. If \a value points to the last item, then \a next will be + * invalid. + * + * This function may not run in constant time (it will run in O(n) time on the + * number of chunks). It requires constant memory (O(1)) in addition to the + * malloc'ed block. + * + * \sa cbor_value_get_text_string_chunk(), cbor_value_copy_byte_string(), cbor_value_dup_text_string() + */ +CborError _cbor_value_dup_string(const CborValue *value, void **buffer, size_t *buflen, CborValue *next) +{ + CborError err; + cbor_assert(buffer); + cbor_assert(buflen); + *buflen = SIZE_MAX; + err = _cbor_value_copy_string(value, NULL, buflen, NULL); + if (err) + return err; + + ++*buflen; + *buffer = malloc(*buflen); + if (!*buffer) { + /* out of memory */ + return CborErrorOutOfMemory; + } + err = _cbor_value_copy_string(value, *buffer, buflen, next); + if (err) { + free(*buffer); + return err; + } + return CborNoError; +} + +#pragma GCC diagnostic pop diff --git a/src/cbor/lib/tinycbor/src/cborpretty.c b/src/cbor/lib/tinycbor/src/cborpretty.c new file mode 100644 index 000000000..2db4b253a --- /dev/null +++ b/src/cbor/lib/tinycbor/src/cborpretty.c @@ -0,0 +1,583 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#define _BSD_SOURCE 1 +#define _DEFAULT_SOURCE 1 +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" +#include "cborinternal_p.h" +#include "compilersupport_p.h" +#include "utf8_p.h" + +#include +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + +/** + * \defgroup CborPretty Converting CBOR to text + * \brief Group of functions used to convert CBOR to text form. + * + * This group contains two functions that can be used to convert a \ref + * CborValue object to a text representation. This module attempts to follow + * the recommendations from RFC 7049 section 6 "Diagnostic Notation", though it + * has a few differences. They are noted below. + * + * TinyCBOR does not provide a way to convert from the text representation back + * to encoded form. To produce a text form meant to be parsed, CborToJson is + * recommended instead. + * + * Either of the functions in this section will attempt to convert exactly one + * CborValue object to text. Those functions may return any error documented + * for the functions for CborParsing. In addition, if the C standard library + * stream functions return with error, the text conversion will return with + * error CborErrorIO. + * + * These functions also perform UTF-8 validation in CBOR text strings. If they + * encounter a sequence of bytes that is not permitted in UTF-8, they will return + * CborErrorInvalidUtf8TextString. That includes encoding of surrogate points + * in UTF-8. + * + * \warning The output type produced by these functions is not guaranteed to + * remain stable. A future update of TinyCBOR may produce different output for + * the same input and parsers may be unable to handle it. + * + * \sa CborParsing, CborToJson, cbor_parser_init() + */ + +/** + * \addtogroup CborPretty + * @{ + *

Text format

+ * + * As described in RFC 7049 section 6 "Diagnostic Notation", the format is + * largely borrowed from JSON, but modified to suit CBOR's different data + * types. TinyCBOR makes further modifications to distinguish different, but + * similar values. + * + * CBOR values are currently encoded as follows: + * \par Integrals (unsigned and negative) + * Base-10 (decimal) text representation of the value + * \par Byte strings: + * "h'" followed by the Base16 (hex) representation of the binary data, followed by an ending quote (') + * \par Text strings: + * C-style escaped string in quotes, with C11/C++11 escaping of Unicode codepoints above U+007F. + * \par Tags: + * Tag value, with the tagged value in parentheses. No special encoding of the tagged value is performed. + * \par Simple types: + * "simple(nn)" where \c nn is the simple value + * \par Null: + * \c null + * \par Undefined: + * \c undefined + * \par Booleans: + * \c true or \c false + * \par Floating point: + * If NaN or infinite, the actual words \c NaN or \c infinite. + * Otherwise, the decimal representation with as many digits as necessary to ensure no loss of information. + * By default, float values are suffixed by "f" and half-float values suffixed by "f16" (doubles have no suffix). + * If the CborPrettyNumericEncodingIndicators flag is active, the values instead are encoded following the + * Section 6 recommended encoding indicators: float values are suffixed with "_2" and half-float with "_1". + * A decimal point is always present. + * \par Arrays: + * Comma-separated list of elements, enclosed in square brackets ("[" and "]"). + * \par Maps: + * Comma-separated list of key-value pairs, with the key and value separated + * by a colon (":"), enclosed in curly braces ("{" and "}"). + * + * The CborPrettyFlags enumerator contains flags to control some aspects of the + * encoding: + * \par String fragmentation + * When the CborPrettyShowStringFragments option is active, text and byte + * strings that are transmitted in fragments are shown instead inside + * parentheses ("(" and ")") with no preceding number and each fragment is + * displayed individually. If a tag precedes the string, then the output + * will contain a double set of parentheses. If the option is not active, + * the fragments are merged together and the display will not show any + * difference from a string transmitted with determinate length. + * \par Encoding indicators + * Numbers and lengths in CBOR can be encoded in multiple representations. + * If the CborPrettyIndicateOverlongNumbers option is active, numbers + * and lengths that are transmitted in a longer encoding than necessary + * will be indicated, by appending an underscore ("_") to either the + * number or the opening bracket or brace, followed by a number + * indicating the CBOR additional information: 0 for 1 byte, 1 for 2 + * bytes, 2 for 4 bytes and 3 for 8 bytes. + * If the CborPrettyIndicateIndeterminateLength option is active, maps, + * arrays and strings encoded with indeterminate length will be marked by + * an underscore after the opening bracket or brace or the string (if not + * showing fragments), without a number after it. + */ + +/** + * \enum CborPrettyFlags + * The CborPrettyFlags enum contains flags that control the conversion of CBOR to text format. + * + * \value CborPrettyNumericEncodingIndicators Use numeric encoding indicators instead of textual for float and half-float. + * \value CborPrettyTextualEncodingIndicators Use textual encoding indicators for float ("f") and half-float ("f16"). + * \value CborPrettyIndicateIndeterminateLength (default) Indicate when a map or array has indeterminate length. + * \value CborPrettyIndicateOverlongNumbers Indicate when a number or length was encoded with more bytes than needed. + * \value CborPrettyShowStringFragments If the byte or text string is transmitted in chunks, show each individually. + * \value CborPrettyMergeStringFragment Merge all chunked byte or text strings and display them in a single entry. + * \value CborPrettyDefaultFlags Default conversion flags. + */ + +#ifndef CBOR_NO_FLOATING_POINT +static inline bool convertToUint64(double v, uint64_t *absolute) +{ + double supremum; + v = fabs(v); + + /* C11 standard section 6.3.1.4 "Real floating and integer" says: + * + * 1 When a finite value of real floating type is converted to an integer + * type other than _Bool, the fractional part is discarded (i.e., the + * value is truncated toward zero). If the value of the integral part + * cannot be represented by the integer type, the behavior is undefined. + * + * So we must perform a range check that v <= UINT64_MAX, but we can't use + * UINT64_MAX + 1.0 because the standard continues: + * + * 2 When a value of integer type is converted to a real floating type, if + * the value being converted can be represented exactly in the new type, + * it is unchanged. If the value being converted is in the range of + * values that can be represented but cannot be represented exactly, the + * result is either the nearest higher or nearest lower representable + * value, chosen in an implementation-defined manner. + */ + supremum = -2.0 * INT64_MIN; /* -2 * (- 2^63) == 2^64 */ + if (v >= supremum) + return false; + + /* Now we can convert, these two conversions cannot be UB */ + *absolute = v; + return *absolute == v; +} +#endif + +static void printRecursionLimit(CborStreamFunction stream, void *out) +{ + stream(out, ""); +} + +static CborError hexDump(CborStreamFunction stream, void *out, const void *ptr, size_t n) +{ + const uint8_t *buffer = (const uint8_t *)ptr; + CborError err = CborNoError; + while (n-- && !err) + err = stream(out, "%02" PRIx8, *buffer++); + + return err; +} + +/* This function decodes buffer as UTF-8 and prints as escaped UTF-16. + * On UTF-8 decoding error, it returns CborErrorInvalidUtf8TextString */ +static CborError utf8EscapedDump(CborStreamFunction stream, void *out, const void *ptr, size_t n) +{ + const uint8_t *buffer = (const uint8_t *)ptr; + const uint8_t * const end = buffer + n; + CborError err = CborNoError; + + while (buffer < end && !err) { + uint32_t uc = get_utf8(&buffer, end); + if (uc == ~0U) + return CborErrorInvalidUtf8TextString; + + if (uc < 0x80) { + /* single-byte UTF-8 */ + unsigned char escaped = (unsigned char)uc; + if (uc < 0x7f && uc >= 0x20 && uc != '\\' && uc != '"') { + err = stream(out, "%c", (char)uc); + continue; + } + + /* print as an escape sequence */ + switch (uc) { + case '"': + case '\\': + break; + case '\b': + escaped = 'b'; + break; + case '\f': + escaped = 'f'; + break; + case '\n': + escaped = 'n'; + break; + case '\r': + escaped = 'r'; + break; + case '\t': + escaped = 't'; + break; + default: + goto print_utf16; + } + err = stream(out, "\\%c", escaped); + continue; + } + + /* now print the sequence */ + if (uc > 0xffffU) { + /* needs surrogate pairs */ + err = stream(out, "\\u%04" PRIX32 "\\u%04" PRIX32, + (uc >> 10) + 0xd7c0, /* high surrogate */ + (uc % 0x0400) + 0xdc00); + } else { +print_utf16: + /* no surrogate pair needed */ + err = stream(out, "\\u%04" PRIX32, uc); + } + } + return err; +} + +static const char *resolve_indicator(const uint8_t *ptr, const uint8_t *end, int flags) +{ + static const char indicators[8][3] = { + "_0", "_1", "_2", "_3", + "", "", "", /* these are not possible */ + "_" + }; + const char *no_indicator = indicators[5]; /* empty string */ + uint8_t additional_information; + uint8_t expected_information; + uint64_t value; + CborError err; + + if (ptr == end) + return NULL; /* CborErrorUnexpectedEOF */ + + additional_information = (*ptr & SmallValueMask); + if (additional_information < Value8Bit) + return no_indicator; + + /* determine whether to show anything */ + if ((flags & CborPrettyIndicateIndeterminateLength) && + additional_information == IndefiniteLength) + return indicators[IndefiniteLength - Value8Bit]; + if ((flags & CborPrettyIndicateOverlongNumbers) == 0) + return no_indicator; + + err = _cbor_value_extract_number(&ptr, end, &value); + if (err) + return NULL; /* CborErrorUnexpectedEOF */ + + expected_information = Value8Bit - 1; + if (value >= Value8Bit) + ++expected_information; + if (value > 0xffU) + ++expected_information; + if (value > 0xffffU) + ++expected_information; + if (value > 0xffffffffU) + ++expected_information; + return expected_information == additional_information ? + no_indicator : + indicators[additional_information - Value8Bit]; +} + +static const char *get_indicator(const CborValue *it, int flags) +{ + return resolve_indicator(it->ptr, it->parser->end, flags); +} + +static CborError value_to_pretty(CborStreamFunction stream, void *out, CborValue *it, int flags, int recursionsLeft); +static CborError container_to_pretty(CborStreamFunction stream, void *out, CborValue *it, CborType containerType, + int flags, int recursionsLeft) +{ + const char *comma = ""; + CborError err = CborNoError; + + if (!recursionsLeft) { + printRecursionLimit(stream, out); + return err; /* do allow the dumping to continue */ + } + + while (!cbor_value_at_end(it) && !err) { + err = stream(out, "%s", comma); + comma = ", "; + + if (!err) + err = value_to_pretty(stream, out, it, flags, recursionsLeft); + + if (containerType == CborArrayType) + continue; + + /* map: that was the key, so get the value */ + if (!err) + err = stream(out, ": "); + if (!err) + err = value_to_pretty(stream, out, it, flags, recursionsLeft); + } + return err; +} + +static CborError value_to_pretty(CborStreamFunction stream, void *out, CborValue *it, int flags, int recursionsLeft) +{ + CborError err = CborNoError; + CborType type = cbor_value_get_type(it); + switch (type) { + case CborArrayType: + case CborMapType: { + /* recursive type */ + CborValue recursed; + const char *indicator = get_indicator(it, flags); + const char *space = *indicator ? " " : indicator; + + err = stream(out, "%c%s%s", type == CborArrayType ? '[' : '{', indicator, space); + if (err) + return err; + + err = cbor_value_enter_container(it, &recursed); + if (err) { + it->ptr = recursed.ptr; + return err; /* parse error */ + } + err = container_to_pretty(stream, out, &recursed, type, flags, recursionsLeft - 1); + if (err) { + it->ptr = recursed.ptr; + return err; /* parse error */ + } + err = cbor_value_leave_container(it, &recursed); + if (err) + return err; /* parse error */ + + return stream(out, type == CborArrayType ? "]" : "}"); + } + + case CborIntegerType: { + uint64_t val; + cbor_value_get_raw_integer(it, &val); /* can't fail */ + + if (cbor_value_is_unsigned_integer(it)) { + err = stream(out, "%" PRIu64, val); + } else { + /* CBOR stores the negative number X as -1 - X + * (that is, -1 is stored as 0, -2 as 1 and so forth) */ + if (++val) { /* unsigned overflow may happen */ + err = stream(out, "-%" PRIu64, val); + } else { + /* overflown + * 0xffff`ffff`ffff`ffff + 1 = + * 0x1`0000`0000`0000`0000 = 18446744073709551616 (2^64) */ + err = stream(out, "-18446744073709551616"); + } + } + if (!err) + err = stream(out, "%s", get_indicator(it, flags)); + break; + } + + case CborByteStringType: + case CborTextStringType: { + size_t n = 0; + const void *ptr; + bool showingFragments = (flags & CborPrettyShowStringFragments) && !cbor_value_is_length_known(it); + const char *separator = ""; + char close = '\''; + char open[3] = "h'"; + const char *indicator = NULL; + + if (type == CborTextStringType) { + close = open[0] = '"'; + open[1] = '\0'; + } + + if (showingFragments) { + err = stream(out, "(_ "); + if (!err) + err = _cbor_value_prepare_string_iteration(it); + } else { + err = stream(out, "%s", open); + } + + while (!err) { + if (showingFragments || indicator == NULL) { + /* any iteration, except the second for a non-chunked string */ + indicator = resolve_indicator(it->ptr, it->parser->end, flags); + } + + err = _cbor_value_get_string_chunk(it, &ptr, &n, it); + if (!ptr) + break; + + if (!err && showingFragments) + err = stream(out, "%s%s", separator, open); + if (!err) + err = (type == CborByteStringType ? + hexDump(stream, out, ptr, n) : + utf8EscapedDump(stream, out, ptr, n)); + if (!err && showingFragments) { + err = stream(out, "%c%s", close, indicator); + separator = ", "; + } + } + + if (!err) { + if (showingFragments) + err = stream(out, ")"); + else + err = stream(out, "%c%s", close, indicator); + } + return err; + } + + case CborTagType: { + CborTag tag; + cbor_value_get_tag(it, &tag); /* can't fail */ + err = stream(out, "%" PRIu64 "%s(", tag, get_indicator(it, flags)); + if (!err) + err = cbor_value_advance_fixed(it); + if (!err && recursionsLeft) + err = value_to_pretty(stream, out, it, flags, recursionsLeft - 1); + else if (!err) + printRecursionLimit(stream, out); + if (!err) + err = stream(out, ")"); + return err; + } + + case CborSimpleType: { + /* simple types can't fail and can't have overlong encoding */ + uint8_t simple_type; + cbor_value_get_simple_type(it, &simple_type); + err = stream(out, "simple(%" PRIu8 ")", simple_type); + break; + } + + case CborNullType: + err = stream(out, "null"); + break; + + case CborUndefinedType: + err = stream(out, "undefined"); + break; + + case CborBooleanType: { + bool val; + cbor_value_get_boolean(it, &val); /* can't fail */ + err = stream(out, val ? "true" : "false"); + break; + } + +#ifndef CBOR_NO_FLOATING_POINT + case CborDoubleType: { + const char *suffix; + double val; + int r; + uint64_t ival; + + if (false) { + float f; + case CborFloatType: + cbor_value_get_float(it, &f); + val = f; + suffix = flags & CborPrettyNumericEncodingIndicators ? "_2" : "f"; + } else if (false) { + uint16_t f16; + case CborHalfFloatType: +#ifndef CBOR_NO_HALF_FLOAT_TYPE + cbor_value_get_half_float(it, &f16); + val = decode_half(f16); + suffix = flags & CborPrettyNumericEncodingIndicators ? "_1" : "f16"; +#else + (void)f16; + err = CborErrorUnsupportedType; + break; +#endif + } else { + cbor_value_get_double(it, &val); + suffix = ""; + } + + if ((flags & CborPrettyNumericEncodingIndicators) == 0) { + r = fpclassify(val); + if (r == FP_NAN || r == FP_INFINITE) + suffix = ""; + } + + if (convertToUint64(val, &ival)) { + /* this double value fits in a 64-bit integer, so show it as such + * (followed by a floating point suffix, to disambiguate) */ + err = stream(out, "%s%" PRIu64 ".%s", val < 0 ? "-" : "", ival, suffix); + } else { + /* this number is definitely not a 64-bit integer */ + err = stream(out, "%." DBL_DECIMAL_DIG_STR "g%s", val, suffix); + } + break; + } +#else + case CborDoubleType: + case CborFloatType: + case CborHalfFloatType: + err = CborErrorUnsupportedType; + break; +#endif /* !CBOR_NO_FLOATING_POINT */ + + case CborInvalidType: + err = stream(out, "invalid"); + if (err) + return err; + return CborErrorUnknownType; + } + + if (!err) + err = cbor_value_advance_fixed(it); + return err; +} + +/** + * Converts the current CBOR type pointed by \a value to its textual + * representation and writes it to the stream by calling the \a streamFunction. + * If an error occurs, this function returns an error code similar to + * \ref CborParsing. + * + * The textual representation can be controlled by the \a flags parameter (see + * \ref CborPrettyFlags for more information). + * + * If no error ocurred, this function advances \a value to the next element. + * Often, concatenating the text representation of multiple elements can be + * done by appending a comma to the output stream in between calls to this + * function. + * + * The \a streamFunction function will be called with the \a token value as the + * first parameter and a printf-style format string as the second, with a variable + * number of further parameters. + * + * \sa cbor_value_to_pretty(), cbor_value_to_json_advance() + */ +CborError cbor_value_to_pretty_stream(CborStreamFunction streamFunction, void *token, CborValue *value, int flags) +{ + return value_to_pretty(streamFunction, token, value, flags, CBOR_PARSER_MAX_RECURSIONS); +} + +#pragma GCC diagnostic pop + +/** @} */ diff --git a/src/cbor/lib/tinycbor/src/cborpretty_stdio.c b/src/cbor/lib/tinycbor/src/cborpretty_stdio.c new file mode 100644 index 000000000..0953e5e8d --- /dev/null +++ b/src/cbor/lib/tinycbor/src/cborpretty_stdio.c @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#include "cbor.h" +#include +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + +static CborError cbor_fprintf(void *out, const char *fmt, ...) +{ + int n; + + va_list list; + va_start(list, fmt); + n = vfprintf((FILE *)out, fmt, list); + va_end(list); + + return n < 0 ? CborErrorIO : CborNoError; +} + +/** + * \fn CborError cbor_value_to_pretty(FILE *out, const CborValue *value) + * + * Converts the current CBOR type pointed to by \a value to its textual + * representation and writes it to the \a out stream. If an error occurs, this + * function returns an error code similar to CborParsing. + * + * \sa cbor_value_to_pretty_advance(), cbor_value_to_json_advance() + */ + +/** + * Converts the current CBOR type pointed to by \a value to its textual + * representation and writes it to the \a out stream. If an error occurs, this + * function returns an error code similar to CborParsing. + * + * If no error ocurred, this function advances \a value to the next element. + * Often, concatenating the text representation of multiple elements can be + * done by appending a comma to the output stream in between calls to this + * function. + * + * \sa cbor_value_to_pretty(), cbor_value_to_pretty_stream(), cbor_value_to_json_advance() + */ +CborError cbor_value_to_pretty_advance(FILE *out, CborValue *value) +{ + return cbor_value_to_pretty_stream(cbor_fprintf, out, value, CborPrettyDefaultFlags); +} + +/** + * Converts the current CBOR type pointed to by \a value to its textual + * representation and writes it to the \a out stream. If an error occurs, this + * function returns an error code similar to CborParsing. + * + * The textual representation can be controlled by the \a flags parameter (see + * CborPrettyFlags for more information). + * + * If no error ocurred, this function advances \a value to the next element. + * Often, concatenating the text representation of multiple elements can be + * done by appending a comma to the output stream in between calls to this + * function. + * + * \sa cbor_value_to_pretty_stream(), cbor_value_to_pretty(), cbor_value_to_json_advance() + */ +CborError cbor_value_to_pretty_advance_flags(FILE *out, CborValue *value, int flags) +{ + return cbor_value_to_pretty_stream(cbor_fprintf, out, value, flags); +} + +#pragma GCC diagnostic pop diff --git a/src/cbor/lib/tinycbor/src/cbortojson.c b/src/cbor/lib/tinycbor/src/cbortojson.c new file mode 100644 index 000000000..ed6a16666 --- /dev/null +++ b/src/cbor/lib/tinycbor/src/cbortojson.c @@ -0,0 +1,704 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#define _BSD_SOURCE 1 +#define _DEFAULT_SOURCE 1 +#define _GNU_SOURCE 1 +#define _POSIX_C_SOURCE 200809L +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" +#include "cborjson.h" +#include "cborinternal_p.h" +#include "compilersupport_p.h" + +#include +#include +#include +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + +/** + * \defgroup CborToJson Converting CBOR to JSON + * \brief Group of functions used to convert CBOR to JSON. + * + * This group contains two functions that can be used to convert a \ref + * CborValue object to an equivalent JSON representation. This module attempts + * to follow the recommendations from RFC 7049 section 4.1 "Converting from + * CBOR to JSON", though it has a few differences. They are noted below. + * + * These functions produce a "minified" JSON output, with no spacing, + * indentation or line breaks. If those are necessary, they need to be applied + * in a post-processing phase. + * + * Note that JSON cannot support all CBOR types with fidelity, so the + * conversion is usually lossy. For that reason, TinyCBOR supports adding a set + * of metadata JSON values that can be used by a JSON-to-CBOR converter to + * restore the original data types. + * + * The TinyCBOR library does not provide a way to convert from JSON + * representation back to encoded form. However, it provides a tool called + * \c json2cbor which can be used for that purpose. That tool supports the + * metadata format that these functions may produce. + * + * Either of the functions in this section will attempt to convert exactly one + * CborValue object to JSON. Those functions may return any error documented + * for the functions for CborParsing. In addition, if the C standard library + * stream functions return with error, the text conversion will return with + * error CborErrorIO. + * + * These functions also perform UTF-8 validation in CBOR text strings. If they + * encounter a sequence of bytes that is not permitted in UTF-8, they will return + * CborErrorInvalidUtf8TextString. That includes encoding of surrogate points + * in UTF-8. + * + * \warning The metadata produced by these functions is not guaranteed to + * remain stable. A future update of TinyCBOR may produce different output for + * the same input and parsers may be unable to handle it. + * + * \sa CborParsing, CborPretty, cbor_parser_init() + */ + +/** + * \addtogroup CborToJson + * @{ + *

Conversion limitations

+ * + * When converting from CBOR to JSON, there may be information loss. This + * section lists the possible scenarios. + * + * \par Number precision: + * ALL JSON numbers, due to its JavaScript heritage, are IEEE 754 + * double-precision floating point. This means JSON is not capable of + * representing all integers numbers outside the range [-(253)+1, + * 253-1] and is not capable of representing NaN or infinite. If the + * CBOR data contains a number outside the valid range, the conversion will + * lose precision. If the input was NaN or infinite, the result of the + * conversion will be the JSON null value. In addition, the distinction between + * half-, single- and double-precision is lost. + * + * \par + * If enabled, the original value and original type are stored in the metadata. + * + * \par Non-native types: + * CBOR's type system is richer than JSON's, which means some data values + * cannot be represented when converted to JSON. The conversion silently turns + * them into strings: CBOR simple types become "simple(nn)" where \c nn is the + * simple type's value, with the exception of CBOR undefined, which becomes + * "undefined", while CBOR byte strings are converted to an Base16, Base64, or + * Base64url encoding + * + * \par + * If enabled, the original type is stored in the metadata. + * + * \par Presence of tags: + * JSON has no support for tagged values, so by default tags are dropped when + * converting to JSON. However, if the CborConvertObeyByteStringTags option is + * active (default), then certain known tags are honored and are used to format + * the conversion of the tagged byte string to JSON. + * + * \par + * If the CborConvertTagsToObjects option is active, then the tag and the + * tagged value are converted to a JSON object. Otherwise, if enabled, the + * last (innermost) tag is stored in the metadata. + * + * \par Non-string keys in maps: + * JSON requires all Object keys to be strings, while CBOR does not. By + * default, if a non-string key is found, the conversion fails with error + * CborErrorJsonObjectKeyNotString. If the CborConvertStringifyMapKeys option + * is active, then the conversion attempts to create a string representation + * using CborPretty. Note that the \c json2cbor tool is not able to parse this + * back to the original form. + * + * \par Duplicate keys in maps: + * Neither JSON nor CBOR allow duplicated keys, but current TinyCBOR does not + * validate that this is the case. If there are duplicated keys in the input, + * they will be repeated in the output, which many JSON tools may flag as + * invalid. In addition to that, if the CborConvertStringifyMapKeys option is + * active, it is possible that a non-string key in a CBOR map will be converted + * to a string form that is identical to another key. + * + * \par + * When metadata support is active, the conversion will add extra key-value + * pairs to the JSON output so it can store the metadata. It is possible that + * the keys for the metadata clash with existing keys in the JSON map. + */ + +extern FILE *open_memstream(char **bufptr, size_t *sizeptr); + +enum ConversionStatusFlags { + TypeWasNotNative = 0x100, /* anything but strings, boolean, null, arrays and maps */ + TypeWasTagged = 0x200, + NumberPrecisionWasLost = 0x400, + NumberWasNaN = 0x800, + NumberWasInfinite = 0x1000, + NumberWasNegative = 0x2000, /* only used with NumberWasInifite or NumberWasTooBig */ + + FinalTypeMask = 0xff +}; + +typedef struct ConversionStatus { + CborTag lastTag; + uint64_t originalNumber; + int flags; +} ConversionStatus; + +static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType type, ConversionStatus *status); + +static CborError dump_bytestring_base16(char **result, CborValue *it) +{ + static const char characters[] = "0123456789abcdef"; + size_t i; + size_t n = 0; + uint8_t *buffer; + CborError err = cbor_value_calculate_string_length(it, &n); + if (err) + return err; + + /* a Base16 (hex) output is twice as big as our buffer */ + buffer = (uint8_t *)malloc(n * 2 + 1); + *result = (char *)buffer; + + /* let cbor_value_copy_byte_string know we have an extra byte for the terminating NUL */ + ++n; + err = cbor_value_copy_byte_string(it, buffer + n - 1, &n, it); + cbor_assert(err == CborNoError); + + for (i = 0; i < n; ++i) { + uint8_t byte = buffer[n + i]; + buffer[2*i] = characters[byte >> 4]; + buffer[2*i + 1] = characters[byte & 0xf]; + } + return CborNoError; +} + +static CborError generic_dump_base64(char **result, CborValue *it, const char alphabet[65]) +{ + size_t n = 0, i; + uint8_t *buffer, *out, *in; + CborError err = cbor_value_calculate_string_length(it, &n); + if (err) + return err; + + /* a Base64 output (untruncated) has 4 bytes for every 3 in the input */ + size_t len = (n + 5) / 3 * 4; + out = buffer = (uint8_t *)malloc(len + 1); + *result = (char *)buffer; + + /* we read our byte string at the tail end of the buffer + * so we can do an in-place conversion while iterating forwards */ + in = buffer + len - n; + + /* let cbor_value_copy_byte_string know we have an extra byte for the terminating NUL */ + ++n; + err = cbor_value_copy_byte_string(it, in, &n, it); + cbor_assert(err == CborNoError); + + uint_least32_t val = 0; + for (i = 0; n - i >= 3; i += 3) { + /* read 3 bytes x 8 bits = 24 bits */ + if (false) { +#ifdef __GNUC__ + } else if (i) { + __builtin_memcpy(&val, in + i - 1, sizeof(val)); + val = cbor_ntohl(val); +#endif + } else { + val = (in[i] << 16) | (in[i + 1] << 8) | in[i + 2]; + } + + /* write 4 chars x 6 bits = 24 bits */ + *out++ = alphabet[(val >> 18) & 0x3f]; + *out++ = alphabet[(val >> 12) & 0x3f]; + *out++ = alphabet[(val >> 6) & 0x3f]; + *out++ = alphabet[val & 0x3f]; + } + + /* maybe 1 or 2 bytes left */ + if (n - i) { + /* we can read in[i + 1] even if it's past the end of the string because + * we know (by construction) that it's a NUL byte */ +#ifdef __GNUC__ + uint16_t val16; + __builtin_memcpy(&val16, in + i, sizeof(val16)); + val = cbor_ntohs(val16); +#else + val = (in[i] << 8) | in[i + 1]; +#endif + val <<= 8; + + /* the 65th character in the alphabet is our filler: either '=' or '\0' */ + out[4] = '\0'; + out[3] = alphabet[64]; + if (n - i == 2) { + /* write the third char in 3 chars x 6 bits = 18 bits */ + out[2] = alphabet[(val >> 6) & 0x3f]; + } else { + out[2] = alphabet[64]; /* filler */ + } + out[1] = alphabet[(val >> 12) & 0x3f]; + out[0] = alphabet[(val >> 18) & 0x3f]; + } else { + out[0] = '\0'; + } + + return CborNoError; +} + +static CborError dump_bytestring_base64(char **result, CborValue *it) +{ + static const char alphabet[] = "ABCDEFGH" "IJKLMNOP" "QRSTUVWX" "YZabcdef" + "ghijklmn" "opqrstuv" "wxyz0123" "456789+/" "="; + return generic_dump_base64(result, it, alphabet); +} + +static CborError dump_bytestring_base64url(char **result, CborValue *it) +{ + static const char alphabet[] = "ABCDEFGH" "IJKLMNOP" "QRSTUVWX" "YZabcdef" + "ghijklmn" "opqrstuv" "wxyz0123" "456789-_"; + return generic_dump_base64(result, it, alphabet); +} + +static CborError add_value_metadata(FILE *out, CborType type, const ConversionStatus *status) +{ + int flags = status->flags; + if (flags & TypeWasTagged) { + /* extract the tagged type, which may be JSON native */ + type = flags & FinalTypeMask; + flags &= ~(FinalTypeMask | TypeWasTagged); + + if (fprintf(out, "\"tag\":\"%" PRIu64 "\"%s", status->lastTag, + flags & ~TypeWasTagged ? "," : "") < 0) + return CborErrorIO; + } + + if (!flags) + return CborNoError; + + /* print at least the type */ + if (fprintf(out, "\"t\":%d", type) < 0) + return CborErrorIO; + + if (flags & NumberWasNaN) + if (fprintf(out, ",\"v\":\"nan\"") < 0) + return CborErrorIO; + if (flags & NumberWasInfinite) + if (fprintf(out, ",\"v\":\"%sinf\"", flags & NumberWasNegative ? "-" : "") < 0) + return CborErrorIO; + if (flags & NumberPrecisionWasLost) + if (fprintf(out, ",\"v\":\"%c%" PRIx64 "\"", flags & NumberWasNegative ? '-' : '+', + status->originalNumber) < 0) + return CborErrorIO; + if (type == CborSimpleType) + if (fprintf(out, ",\"v\":%d", (int)status->originalNumber) < 0) + return CborErrorIO; + return CborNoError; +} + +static CborError find_tagged_type(CborValue *it, CborTag *tag, CborType *type) +{ + CborError err = CborNoError; + *type = cbor_value_get_type(it); + while (*type == CborTagType) { + cbor_value_get_tag(it, tag); /* can't fail */ + err = cbor_value_advance_fixed(it); + if (err) + return err; + + *type = cbor_value_get_type(it); + } + return err; +} + +static CborError tagged_value_to_json(FILE *out, CborValue *it, int flags, ConversionStatus *status) +{ + CborTag tag; + CborError err; + + if (flags & CborConvertTagsToObjects) { + cbor_value_get_tag(it, &tag); /* can't fail */ + err = cbor_value_advance_fixed(it); + if (err) + return err; + + if (fprintf(out, "{\"tag%" PRIu64 "\":", tag) < 0) + return CborErrorIO; + + CborType type = cbor_value_get_type(it); + err = value_to_json(out, it, flags, type, status); + if (err) + return err; + if (flags & CborConvertAddMetadata && status->flags) { + if (fprintf(out, ",\"tag%" PRIu64 "$cbor\":{", tag) < 0 || + add_value_metadata(out, type, status) != CborNoError || + fputc('}', out) < 0) + return CborErrorIO; + } + if (fputc('}', out) < 0) + return CborErrorIO; + status->flags = TypeWasNotNative | CborTagType; + return CborNoError; + } + + CborType type; + err = find_tagged_type(it, &status->lastTag, &type); + if (err) + return err; + tag = status->lastTag; + + /* special handling of byte strings? */ + if (type == CborByteStringType && (flags & CborConvertByteStringsToBase64Url) == 0 && + (tag == CborNegativeBignumTag || tag == CborExpectedBase16Tag || tag == CborExpectedBase64Tag)) { + char *str; + char *pre = ""; + + if (tag == CborNegativeBignumTag) { + pre = "~"; + err = dump_bytestring_base64url(&str, it); + } else if (tag == CborExpectedBase64Tag) { + err = dump_bytestring_base64(&str, it); + } else { /* tag == CborExpectedBase16Tag */ + err = dump_bytestring_base16(&str, it); + } + if (err) + return err; + err = fprintf(out, "\"%s%s\"", pre, str) < 0 ? CborErrorIO : CborNoError; + free(str); + status->flags = TypeWasNotNative | TypeWasTagged | CborByteStringType; + return err; + } + + /* no special handling */ + err = value_to_json(out, it, flags, type, status); + status->flags |= TypeWasTagged | type; + return err; +} + +static CborError stringify_map_key(char **key, CborValue *it, int flags, CborType type) +{ + (void)flags; /* unused */ + (void)type; /* unused */ +#ifdef WITHOUT_OPEN_MEMSTREAM + (void)key; /* unused */ + (void)it; /* unused */ + return CborErrorJsonNotImplemented; +#else + size_t size; + + FILE *memstream = open_memstream(key, &size); + if (memstream == NULL) + return CborErrorOutOfMemory; /* could also be EMFILE, but it's unlikely */ + CborError err = cbor_value_to_pretty_advance(memstream, it); + + if (unlikely(fclose(memstream) < 0 || *key == NULL)) + return CborErrorInternalError; + return err; +#endif +} + +static CborError array_to_json(FILE *out, CborValue *it, int flags, ConversionStatus *status) +{ + const char *comma = ""; + while (!cbor_value_at_end(it)) { + if (fprintf(out, "%s", comma) < 0) + return CborErrorIO; + comma = ","; + + CborError err = value_to_json(out, it, flags, cbor_value_get_type(it), status); + if (err) + return err; + } + return CborNoError; +} + +static CborError map_to_json(FILE *out, CborValue *it, int flags, ConversionStatus *status) +{ + const char *comma = ""; + CborError err; + while (!cbor_value_at_end(it)) { + char *key; + if (fprintf(out, "%s", comma) < 0) + return CborErrorIO; + comma = ","; + + CborType keyType = cbor_value_get_type(it); + if (likely(keyType == CborTextStringType)) { + size_t n = 0; + err = cbor_value_dup_text_string(it, &key, &n, it); + } else if (flags & CborConvertStringifyMapKeys) { + err = stringify_map_key(&key, it, flags, keyType); + } else { + return CborErrorJsonObjectKeyNotString; + } + if (err) + return err; + + /* first, print the key */ + if (fprintf(out, "\"%s\":", key) < 0) { + free(key); + return CborErrorIO; + } + + /* then, print the value */ + CborType valueType = cbor_value_get_type(it); + err = value_to_json(out, it, flags, valueType, status); + + /* finally, print any metadata we may have */ + if (flags & CborConvertAddMetadata) { + if (!err && keyType != CborTextStringType) { + if (fprintf(out, ",\"%s$keycbordump\":true", key) < 0) + err = CborErrorIO; + } + if (!err && status->flags) { + if (fprintf(out, ",\"%s$cbor\":{", key) < 0 || + add_value_metadata(out, valueType, status) != CborNoError || + fputc('}', out) < 0) + err = CborErrorIO; + } + } + + free(key); + if (err) + return err; + } + return CborNoError; +} + +static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType type, ConversionStatus *status) +{ + CborError err; + status->flags = 0; + + switch (type) { + case CborArrayType: + case CborMapType: { + /* recursive type */ + CborValue recursed; + err = cbor_value_enter_container(it, &recursed); + if (err) { + it->ptr = recursed.ptr; + return err; /* parse error */ + } + if (fputc(type == CborArrayType ? '[' : '{', out) < 0) + return CborErrorIO; + + err = (type == CborArrayType) ? + array_to_json(out, &recursed, flags, status) : + map_to_json(out, &recursed, flags, status); + if (err) { + it->ptr = recursed.ptr; + return err; /* parse error */ + } + + if (fputc(type == CborArrayType ? ']' : '}', out) < 0) + return CborErrorIO; + err = cbor_value_leave_container(it, &recursed); + if (err) + return err; /* parse error */ + + status->flags = 0; /* reset, there are never conversion errors for us */ + return CborNoError; + } + + case CborIntegerType: { + double num; /* JS numbers are IEEE double precision */ + uint64_t val; + cbor_value_get_raw_integer(it, &val); /* can't fail */ + num = (double)val; + + if (cbor_value_is_negative_integer(it)) { + num = -num - 1; /* convert to negative */ + if ((uint64_t)(-num - 1) != val) { + status->flags = NumberPrecisionWasLost | NumberWasNegative; + status->originalNumber = val; + } + } else { + if ((uint64_t)num != val) { + status->flags = NumberPrecisionWasLost; + status->originalNumber = val; + } + } + if (fprintf(out, "%.0f", num) < 0) /* this number has no fraction, so no decimal points please */ + return CborErrorIO; + break; + } + + case CborByteStringType: + case CborTextStringType: { + char *str; + if (type == CborByteStringType) { + err = dump_bytestring_base64url(&str, it); + status->flags = TypeWasNotNative; + } else { + size_t n = 0; + err = cbor_value_dup_text_string(it, &str, &n, it); + } + if (err) + return err; + err = (fprintf(out, "\"%s\"", str) < 0) ? CborErrorIO : CborNoError; + free(str); + return err; + } + + case CborTagType: + return tagged_value_to_json(out, it, flags, status); + + case CborSimpleType: { + uint8_t simple_type; + cbor_value_get_simple_type(it, &simple_type); /* can't fail */ + status->flags = TypeWasNotNative; + status->originalNumber = simple_type; + if (fprintf(out, "\"simple(%" PRIu8 ")\"", simple_type) < 0) + return CborErrorIO; + break; + } + + case CborNullType: + if (fprintf(out, "null") < 0) + return CborErrorIO; + break; + + case CborUndefinedType: + status->flags = TypeWasNotNative; + if (fprintf(out, "\"undefined\"") < 0) + return CborErrorIO; + break; + + case CborBooleanType: { + bool val; + cbor_value_get_boolean(it, &val); /* can't fail */ + if (fprintf(out, val ? "true" : "false") < 0) + return CborErrorIO; + break; + } + +#ifndef CBOR_NO_FLOATING_POINT + case CborDoubleType: { + double val; + if (false) { + float f; + case CborFloatType: + status->flags = TypeWasNotNative; + cbor_value_get_float(it, &f); + val = f; + } else if (false) { + uint16_t f16; + case CborHalfFloatType: +# ifndef CBOR_NO_HALF_FLOAT_TYPE + status->flags = TypeWasNotNative; + cbor_value_get_half_float(it, &f16); + val = decode_half(f16); +# else + (void)f16; + err = CborErrorUnsupportedType; + break; +# endif + } else { + cbor_value_get_double(it, &val); + } + + int r = fpclassify(val); + if (r == FP_NAN || r == FP_INFINITE) { + if (fprintf(out, "null") < 0) + return CborErrorIO; + status->flags |= r == FP_NAN ? NumberWasNaN : + NumberWasInfinite | (val < 0 ? NumberWasNegative : 0); + } else { + uint64_t ival = (uint64_t)fabs(val); + if ((double)ival == fabs(val)) { + /* print as integer so we get the full precision */ + r = fprintf(out, "%s%" PRIu64, val < 0 ? "-" : "", ival); + status->flags |= TypeWasNotNative; /* mark this integer number as a double */ + } else { + /* this number is definitely not a 64-bit integer */ + r = fprintf(out, "%." DBL_DECIMAL_DIG_STR "g", val); + } + if (r < 0) + return CborErrorIO; + } + break; + } +#else + case CborDoubleType: + case CborFloatType: + case CborHalfFloatType: + err = CborErrorUnsupportedType; + break; +#endif /* !CBOR_NO_FLOATING_POINT */ + + case CborInvalidType: + return CborErrorUnknownType; + } + + return cbor_value_advance_fixed(it); +} + +/** + * \enum CborToJsonFlags + * The CborToJsonFlags enum contains flags that control the conversion of CBOR to JSON. + * + * \value CborConvertAddMetadata Adds metadata to facilitate restoration of the original CBOR data. + * \value CborConvertTagsToObjects Converts CBOR tags to JSON objects + * \value CborConvertIgnoreTags (default) Ignore CBOR tags, except for byte strings + * \value CborConvertObeyByteStringTags (default) Honor formatting of CBOR byte strings if so tagged + * \value CborConvertByteStringsToBase64Url Force the conversion of all CBOR byte strings to Base64url encoding, despite any tags + * \value CborConvertRequireMapStringKeys (default) Require CBOR map keys to be strings, failing the conversion if they are not + * \value CborConvertStringifyMapKeys Convert non-string keys in CBOR maps to a string form + * \value CborConvertDefaultFlags Default conversion flags. + */ + +/** + * \fn CborError cbor_value_to_json(FILE *out, const CborValue *value, int flags) + * + * Converts the current CBOR type pointed to by \a value to JSON and writes that + * to the \a out stream. If an error occurs, this function returns an error + * code similar to CborParsing. The \a flags parameter indicates one or more of + * the flags from CborToJsonFlags that control the conversion. + * + * \sa cbor_value_to_json_advance(), cbor_value_to_pretty() + */ + +/** + * Converts the current CBOR type pointed to by \a value to JSON and writes that + * to the \a out stream. If an error occurs, this function returns an error + * code similar to CborParsing. The \a flags parameter indicates one or more of + * the flags from CborToJsonFlags that control the conversion. + * + * If no error ocurred, this function advances \a value to the next element. + * + * \sa cbor_value_to_json(), cbor_value_to_pretty_advance() + */ +CborError cbor_value_to_json_advance(FILE *out, CborValue *value, int flags) +{ + ConversionStatus status; + return value_to_json(out, value, flags, cbor_value_get_type(value), &status); +} + +#pragma GCC diagnostic pop + +/** @} */ diff --git a/src/cbor/lib/tinycbor/src/cborvalidation.c b/src/cbor/lib/tinycbor/src/cborvalidation.c new file mode 100644 index 000000000..d9445ea27 --- /dev/null +++ b/src/cbor/lib/tinycbor/src/cborvalidation.c @@ -0,0 +1,671 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#define _BSD_SOURCE 1 +#define _DEFAULT_SOURCE 1 +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" +#include "cborinternal_p.h" +#include "compilersupport_p.h" +#include "utf8_p.h" + +#include + +#ifndef CBOR_NO_FLOATING_POINT +# include +# include +#endif + + +#ifndef CBOR_PARSER_MAX_RECURSIONS +# define CBOR_PARSER_MAX_RECURSIONS 1024 +#endif + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + +/** + * \addtogroup CborParsing + * @{ + */ + +/** + * \enum CborValidationFlags + * The CborValidationFlags enum contains flags that control the validation of a + * CBOR stream. + * + * \value CborValidateBasic Validates only the syntactic correctedness of the stream. + * \value CborValidateCanonical Validates that the stream is in canonical format, according to + * RFC 7049 section 3.9. + * \value CborValidateStrictMode Performs strict validation, according to RFC 7049 section 3.10. + * \value CborValidateStrictest Attempt to perform the strictest validation we know of. + * + * \value CborValidateShortestIntegrals (Canonical) Validate that integral numbers and lengths are + * enconded in their shortest form possible. + * \value CborValidateShortestFloatingPoint (Canonical) Validate that floating-point numbers are encoded + * in their shortest form possible. + * \value CborValidateShortestNumbers (Canonical) Validate both integral and floating-point numbers + * are in their shortest form possible. + * \value CborValidateNoIndeterminateLength (Canonical) Validate that no string, array or map uses + * indeterminate length encoding. + * \value CborValidateMapIsSorted (Canonical & Strict mode) Validate that map keys appear in + * sorted order. + * \value CborValidateMapKeysAreUnique (Strict mode) Validate that map keys are unique. + * \value CborValidateTagUse (Strict mode) Validate that known tags are used with the + * correct types. This does not validate that the content of + * those types is syntactically correct. For example, this + * option validates that tag 1 (DateTimeString) is used with + * a Text String, but it does not validate that the string is + * a valid date/time representation. + * \value CborValidateUtf8 (Strict mode) Validate that text strings are appropriately + * encoded in UTF-8. + * \value CborValidateMapKeysAreString Validate that all map keys are text strings. + * \value CborValidateNoUndefined Validate that no elements of type "undefined" are present. + * \value CborValidateNoTags Validate that no tags are used. + * \value CborValidateFiniteFloatingPoint Validate that all floating point numbers are finite (no NaN or + * infinities are allowed). + * \value CborValidateCompleteData Validate that the stream is complete and there is no more data + * in the buffer. + * \value CborValidateNoUnknownSimpleTypesSA Validate that all Standards Action simple types are registered + * with IANA. + * \value CborValidateNoUnknownSimpleTypes Validate that all simple types used are registered with IANA. + * \value CborValidateNoUnknownTagsSA Validate that all Standard Actions tags are registered with IANA. + * \value CborValidateNoUnknownTagsSR Validate that all Standard Actions and Specification Required tags + * are registered with IANA (see below for limitations). + * \value CborValidateNoUnkonwnTags Validate that all tags are registered with IANA + * (see below for limitations). + * + * \par Simple type registry + * The CBOR specification requires that registration for use of the first 19 + * simple types must be done by way of Standards Action. The rest of the simple + * types only require a specification. The official list can be obtained from + * https://www.iana.org/assignments/cbor-simple-values/cbor-simple-values.xhtml. + * + * \par + * There are no registered simple types recognized by this release of TinyCBOR + * (beyond those defined by RFC 7049). + * + * \par Tag registry + * The CBOR specification requires that registration for use of the first 23 + * tags must be done by way of Standards Action. The next up to tag 255 only + * require a specification. Finally, all other tags can be registered on a + * first-come-first-serve basis. The official list can be ontained from + * https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml. + * + * \par + * Given the variability of this list, TinyCBOR cannot recognize all tags + * registered with IANA. Instead, the implementation only recognizes tags + * that are backed by an RFC. + * + * \par + * These are the tags known to the current TinyCBOR release: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TagData ItemSemantics
0UTF-8 text stringStandard date/time string
1integerEpoch-based date/time
2byte stringPositive bignum
3byte stringNegative bignum
4arrayDecimal fraction
5arrayBigfloat
16arrayCOSE Single Recipient Encrypted Data Object (RFC 8152)
17arrayCOSE Mac w/o Recipients Object (RFC 8152)
18arrayCOSE Single Signer Data Object (RFC 8162)
21byte string, array, mapExpected conversion to base64url encoding
22byte string, array, mapExpected conversion to base64 encoding
23byte string, array, mapExpected conversion to base16 encoding
24byte stringEncoded CBOR data item
32UTF-8 text stringURI
33UTF-8 text stringbase64url
34UTF-8 text stringbase64
35UTF-8 text stringRegular expression
36UTF-8 text stringMIME message
96arrayCOSE Encrypted Data Object (RFC 8152)
97arrayCOSE MACed Data Object (RFC 8152)
98arrayCOSE Signed Data Object (RFC 8152)
55799anySelf-describe CBOR
+ */ + +struct KnownTagData { uint32_t tag; uint32_t types; }; +static const struct KnownTagData knownTagData[] = { + { 0, (uint32_t)CborTextStringType }, + { 1, (uint32_t)(CborIntegerType+1) }, + { 2, (uint32_t)CborByteStringType }, + { 3, (uint32_t)CborByteStringType }, + { 4, (uint32_t)CborArrayType }, + { 5, (uint32_t)CborArrayType }, + { 16, (uint32_t)CborArrayType }, + { 17, (uint32_t)CborArrayType }, + { 18, (uint32_t)CborArrayType }, + { 21, (uint32_t)CborByteStringType | ((uint32_t)CborArrayType << 8) | ((uint32_t)CborMapType << 16) }, + { 22, (uint32_t)CborByteStringType | ((uint32_t)CborArrayType << 8) | ((uint32_t)CborMapType << 16) }, + { 23, (uint32_t)CborByteStringType | ((uint32_t)CborArrayType << 8) | ((uint32_t)CborMapType << 16) }, + { 24, (uint32_t)CborByteStringType }, + { 32, (uint32_t)CborTextStringType }, + { 33, (uint32_t)CborTextStringType }, + { 34, (uint32_t)CborTextStringType }, + { 35, (uint32_t)CborTextStringType }, + { 36, (uint32_t)CborTextStringType }, + { 96, (uint32_t)CborArrayType }, + { 97, (uint32_t)CborArrayType }, + { 98, (uint32_t)CborArrayType }, + { 55799, 0U } +}; + +static CborError validate_value(CborValue *it, uint32_t flags, int recursionLeft); + +static inline CborError validate_utf8_string(const void *ptr, size_t n) +{ + const uint8_t *buffer = (const uint8_t *)ptr; + const uint8_t * const end = buffer + n; + while (buffer < end) { + uint32_t uc = get_utf8(&buffer, end); + if (uc == ~0U) + return CborErrorInvalidUtf8TextString; + } + return CborNoError; +} + +static inline CborError validate_simple_type(uint8_t simple_type, uint32_t flags) +{ + /* At current time, all known simple types are those from RFC 7049, + * which are parsed by the parser into different CBOR types. + * That means that if we've got here, the type is unknown */ + if (simple_type < 32) + return (flags & CborValidateNoUnknownSimpleTypesSA) ? CborErrorUnknownSimpleType : CborNoError; + return (flags & CborValidateNoUnknownSimpleTypes) == CborValidateNoUnknownSimpleTypes ? + CborErrorUnknownSimpleType : CborNoError; +} + +static inline CborError validate_number(const CborValue *it, CborType type, uint32_t flags) +{ + CborError err = CborNoError; + const uint8_t *ptr = it->ptr; + size_t bytesUsed, bytesNeeded; + uint64_t value; + + if ((flags & CborValidateShortestIntegrals) == 0) + return err; + if (type >= CborHalfFloatType && type <= CborDoubleType) + return err; /* checked elsewhere */ + + err = _cbor_value_extract_number(&ptr, it->parser->end, &value); + if (err) + return err; + + bytesUsed = (size_t)(ptr - it->ptr - 1); + bytesNeeded = 0; + if (value >= Value8Bit) + ++bytesNeeded; + if (value > 0xffU) + ++bytesNeeded; + if (value > 0xffffU) + bytesNeeded += 2; + if (value > 0xffffffffU) + bytesNeeded += 4; + if (bytesNeeded < bytesUsed) + return CborErrorOverlongEncoding; + return CborNoError; +} + +static inline CborError validate_tag(CborValue *it, CborTag tag, uint32_t flags, int recursionLeft) +{ + CborType type = cbor_value_get_type(it); + const size_t knownTagCount = sizeof(knownTagData) / sizeof(knownTagData[0]); + const struct KnownTagData *tagData = knownTagData; + const struct KnownTagData * const knownTagDataEnd = knownTagData + knownTagCount; + + if (!recursionLeft) + return CborErrorNestingTooDeep; + if (flags & CborValidateNoTags) + return CborErrorExcludedType; + + /* find the tag data, if any */ + for ( ; tagData != knownTagDataEnd; ++tagData) { + if (tagData->tag < tag) + continue; + if (tagData->tag > tag) + tagData = NULL; + break; + } + if (tagData == knownTagDataEnd) + tagData = NULL; + + if (flags & CborValidateNoUnknownTags && !tagData) { + /* tag not found */ + if (flags & CborValidateNoUnknownTagsSA && tag < 24) + return CborErrorUnknownTag; + if ((flags & CborValidateNoUnknownTagsSR) == CborValidateNoUnknownTagsSR && tag < 256) + return CborErrorUnknownTag; + if ((flags & CborValidateNoUnknownTags) == CborValidateNoUnknownTags) + return CborErrorUnknownTag; + } + + if (flags & CborValidateTagUse && tagData && tagData->types) { + uint32_t allowedTypes = tagData->types; + + /* correct Integer so it's not zero */ + if (type == CborIntegerType) + type = (CborType)(type + 1); + + while (allowedTypes) { + if ((uint8_t)(allowedTypes & 0xff) == type) + break; + allowedTypes >>= 8; + } + if (!allowedTypes) + return CborErrorInappropriateTagForType; + } + + return validate_value(it, flags, recursionLeft); +} + +#ifndef CBOR_NO_FLOATING_POINT +static inline CborError validate_floating_point(CborValue *it, CborType type, uint32_t flags) +{ + CborError err; + int r; + double val; + float valf; + uint16_t valf16; + + if (type != CborDoubleType) { + if (type == CborFloatType) { + err = cbor_value_get_float(it, &valf); + val = valf; + } else { +# ifdef CBOR_NO_HALF_FLOAT_TYPE + (void)valf16; + return CborErrorUnsupportedType; +# else + err = cbor_value_get_half_float(it, &valf16); + val = decode_half(valf16); +# endif + } + } else { + err = cbor_value_get_double(it, &val); + } + cbor_assert(err == CborNoError); /* can't fail */ + + r = fpclassify(val); + if (r == FP_NAN || r == FP_INFINITE) { + if (flags & CborValidateFiniteFloatingPoint) + return CborErrorExcludedValue; + if (flags & CborValidateShortestFloatingPoint) { + if (type == CborDoubleType) + return CborErrorOverlongEncoding; +# ifndef CBOR_NO_HALF_FLOAT_TYPE + if (type == CborFloatType) + return CborErrorOverlongEncoding; + if (r == FP_NAN && valf16 != 0x7e00) + return CborErrorImproperValue; + if (r == FP_INFINITE && valf16 != 0x7c00 && valf16 != 0xfc00) + return CborErrorImproperValue; +# endif + } + } + + if (flags & CborValidateShortestFloatingPoint && type > CborHalfFloatType) { + if (type == CborDoubleType) { + valf = (float)val; + if ((double)valf == val) + return CborErrorOverlongEncoding; + } +# ifndef CBOR_NO_HALF_FLOAT_TYPE + if (type == CborFloatType) { + valf16 = encode_half(valf); + if (valf == decode_half(valf16)) + return CborErrorOverlongEncoding; + } +# endif + } + + return CborNoError; +} +#endif + +static CborError validate_container(CborValue *it, int containerType, uint32_t flags, int recursionLeft) +{ + CborError err; + const uint8_t *previous = NULL; + const uint8_t *previous_end = NULL; + + if (!recursionLeft) + return CborErrorNestingTooDeep; + + while (!cbor_value_at_end(it)) { + const uint8_t *current = cbor_value_get_next_byte(it); + + if (containerType == CborMapType) { + if (flags & CborValidateMapKeysAreString) { + CborType type = cbor_value_get_type(it); + if (type == CborTagType) { + /* skip the tags */ + CborValue copy = *it; + err = cbor_value_skip_tag(©); + if (err) + return err; + type = cbor_value_get_type(©); + } + if (type != CborTextStringType) + return CborErrorMapKeyNotString; + } + } + + err = validate_value(it, flags, recursionLeft); + if (err) + return err; + + if (containerType != CborMapType) + continue; + + if (flags & CborValidateMapIsSorted) { + if (previous) { + uint64_t len1, len2; + const uint8_t *ptr; + + /* extract the two lengths */ + ptr = previous; + _cbor_value_extract_number(&ptr, it->parser->end, &len1); + ptr = current; + _cbor_value_extract_number(&ptr, it->parser->end, &len2); + + if (len1 > len2) + return CborErrorMapNotSorted; + if (len1 == len2) { + size_t bytelen1 = (size_t)(previous_end - previous); + size_t bytelen2 = (size_t)(it->ptr - current); + int r = memcmp(previous, current, bytelen1 <= bytelen2 ? bytelen1 : bytelen2); + + if (r == 0 && bytelen1 != bytelen2) + r = bytelen1 < bytelen2 ? -1 : +1; + if (r > 0) + return CborErrorMapNotSorted; + if (r == 0 && (flags & CborValidateMapKeysAreUnique) == CborValidateMapKeysAreUnique) + return CborErrorMapKeysNotUnique; + } + } + + previous = current; + previous_end = it->ptr; + } + + /* map: that was the key, so get the value */ + err = validate_value(it, flags, recursionLeft); + if (err) + return err; + } + return CborNoError; +} + +static CborError validate_value(CborValue *it, uint32_t flags, int recursionLeft) +{ + CborError err; + CborType type = cbor_value_get_type(it); + + if (cbor_value_is_length_known(it)) { + err = validate_number(it, type, flags); + if (err) + return err; + } else { + if (flags & CborValidateNoIndeterminateLength) + return CborErrorUnknownLength; + } + + switch (type) { + case CborArrayType: + case CborMapType: { + /* recursive type */ + CborValue recursed; + err = cbor_value_enter_container(it, &recursed); + if (!err) + err = validate_container(&recursed, type, flags, recursionLeft - 1); + if (err) { + it->ptr = recursed.ptr; + return err; + } + err = cbor_value_leave_container(it, &recursed); + if (err) + return err; + return CborNoError; + } + + case CborIntegerType: { + uint64_t val; + err = cbor_value_get_raw_integer(it, &val); + cbor_assert(err == CborNoError); /* can't fail */ + + break; + } + + case CborByteStringType: + case CborTextStringType: { + size_t n = 0; + const void *ptr; + + err = _cbor_value_prepare_string_iteration(it); + if (err) + return err; + + while (1) { + err = validate_number(it, type, flags); + if (err) + return err; + + err = _cbor_value_get_string_chunk(it, &ptr, &n, it); + if (err) + return err; + if (!ptr) + break; + + if (type == CborTextStringType && flags & CborValidateUtf8) { + err = validate_utf8_string(ptr, n); + if (err) + return err; + } + } + + return CborNoError; + } + + case CborTagType: { + CborTag tag; + err = cbor_value_get_tag(it, &tag); + cbor_assert(err == CborNoError); /* can't fail */ + + err = cbor_value_advance_fixed(it); + if (err) + return err; + err = validate_tag(it, tag, flags, recursionLeft - 1); + if (err) + return err; + + return CborNoError; + } + + case CborSimpleType: { + uint8_t simple_type; + err = cbor_value_get_simple_type(it, &simple_type); + cbor_assert(err == CborNoError); /* can't fail */ + err = validate_simple_type(simple_type, flags); + if (err) + return err; + break; + } + + case CborNullType: + case CborBooleanType: + break; + + case CborUndefinedType: + if (flags & CborValidateNoUndefined) + return CborErrorExcludedType; + break; + + case CborHalfFloatType: + case CborFloatType: + case CborDoubleType: { +#ifdef CBOR_NO_FLOATING_POINT + return CborErrorUnsupportedType; +#else + err = validate_floating_point(it, type, flags); + if (err) + return err; + break; +#endif /* !CBOR_NO_FLOATING_POINT */ + } + + case CborInvalidType: + return CborErrorUnknownType; + } + + err = cbor_value_advance_fixed(it); + return err; +} + +/** + * Performs a full validation, controlled by the \a flags options, of the CBOR + * stream pointed by \a it and returns the error it found. If no error was + * found, it returns CborNoError and the application can iterate over the items + * with certainty that no errors will appear during parsing. + * + * If \a flags is CborValidateBasic, the result should be the same as + * cbor_value_validate_basic(). + * + * This function has the same timing and memory requirements as + * cbor_value_advance() and cbor_value_validate_basic(). + * + * \sa CborValidationFlags, cbor_value_validate_basic(), cbor_value_advance() + */ +CborError cbor_value_validate(const CborValue *it, uint32_t flags) +{ + CborValue value = *it; + CborError err = validate_value(&value, flags, CBOR_PARSER_MAX_RECURSIONS); + if (err) + return err; + if (flags & CborValidateCompleteData && it->ptr != it->parser->end) + return CborErrorGarbageAtEnd; + return CborNoError; +} + +#pragma GCC diagnostic pop + +/** + * @} + */ diff --git a/src/cbor/lib/tinycbor/src/compilersupport_p.h b/src/cbor/lib/tinycbor/src/compilersupport_p.h new file mode 100644 index 000000000..2b9491d34 --- /dev/null +++ b/src/cbor/lib/tinycbor/src/compilersupport_p.h @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#ifndef COMPILERSUPPORT_H +#define COMPILERSUPPORT_H + +#include "cbor.h" + +#ifndef _BSD_SOURCE +# define _BSD_SOURCE +#endif +#ifndef _DEFAULT_SOURCE +# define _DEFAULT_SOURCE +#endif +#ifndef assert +# include +#endif +#include +#include +#include + +#ifndef __cplusplus +# include +#endif + +#if __STDC_VERSION__ >= 201112L || __cplusplus >= 201103L || __cpp_static_assert >= 200410 +# define cbor_static_assert(x) static_assert(x, #x) +#elif !defined(__cplusplus) && defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 406) && (__STDC_VERSION__ > 199901L) +# define cbor_static_assert(x) _Static_assert(x, #x) +#else +# define cbor_static_assert(x) ((void)sizeof(char[2*!!(x) - 1])) +#endif +#if __STDC_VERSION__ >= 199901L || defined(__cplusplus) +/* inline is a keyword */ +#else +/* use the definition from cbor.h */ +# define inline CBOR_INLINE +#endif + +#ifdef NDEBUG +# define cbor_assert(cond) do { if (!(cond)) unreachable(); } while (0) +#else +# define cbor_assert(cond) assert(cond) +#endif + +#ifndef STRINGIFY +#define STRINGIFY(x) STRINGIFY2(x) +#endif +#define STRINGIFY2(x) #x + +#if !defined(UINT32_MAX) || !defined(INT64_MAX) +/* C89? We can define UINT32_MAX portably, but not INT64_MAX */ +# error "Your system has stdint.h but that doesn't define UINT32_MAX or INT64_MAX" +#endif + +#ifndef DBL_DECIMAL_DIG +/* DBL_DECIMAL_DIG is C11 */ +# define DBL_DECIMAL_DIG 17 +#endif +#define DBL_DECIMAL_DIG_STR STRINGIFY(DBL_DECIMAL_DIG) + +#if defined(__GNUC__) && defined(__i386__) && !defined(__iamcu__) +# define CBOR_INTERNAL_API_CC __attribute__((regparm(3))) +#elif defined(_MSC_VER) && defined(_M_IX86) +# define CBOR_INTERNAL_API_CC __fastcall +#else +# define CBOR_INTERNAL_API_CC +#endif + +#ifndef __has_builtin +# define __has_builtin(x) 0 +#endif + +#if (defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 403)) || \ + (__has_builtin(__builtin_bswap64) && __has_builtin(__builtin_bswap32)) +# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +# define cbor_ntohll __builtin_bswap64 +# define cbor_htonll __builtin_bswap64 +# define cbor_ntohl __builtin_bswap32 +# define cbor_htonl __builtin_bswap32 +# ifdef __INTEL_COMPILER +# define cbor_ntohs _bswap16 +# define cbor_htons _bswap16 +# elif (__GNUC__ * 100 + __GNUC_MINOR__ >= 608) || __has_builtin(__builtin_bswap16) +# define cbor_ntohs __builtin_bswap16 +# define cbor_htons __builtin_bswap16 +# else +# define cbor_ntohs(x) (((uint16_t)x >> 8) | ((uint16_t)x << 8)) +# define cbor_htons cbor_ntohs +# endif +# else +# define cbor_ntohll +# define cbor_htonll +# define cbor_ntohl +# define cbor_htonl +# define cbor_ntohs +# define cbor_htons +# endif +#elif defined(__sun) +# include +#elif defined(_MSC_VER) +/* MSVC, which implies Windows, which implies little-endian and sizeof(long) == 4 */ +# include +# define cbor_ntohll _byteswap_uint64 +# define cbor_htonll _byteswap_uint64 +# define cbor_ntohl _byteswap_ulong +# define cbor_htonl _byteswap_ulong +# define cbor_ntohs _byteswap_ushort +# define cbor_htons _byteswap_ushort +#endif +#ifndef cbor_ntohs +# include +# define cbor_ntohs ntohs +# define cbor_htons htons +#endif +#ifndef cbor_ntohl +# include +# define cbor_ntohl ntohl +# define cbor_htonl htonl +#endif +#ifndef cbor_ntohll +# define cbor_ntohll ntohll +# define cbor_htonll htonll +/* ntohll isn't usually defined */ +# ifndef ntohll +# if (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) || \ + (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && __BYTE_ORDER == __BIG_ENDIAN) || \ + (defined(BYTE_ORDER) && defined(BIG_ENDIAN) && BYTE_ORDER == BIG_ENDIAN) || \ + (defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN)) || (defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__)) || \ + defined(__ARMEB__) || defined(__MIPSEB__) || defined(__s390__) || defined(__sparc__) +# define ntohll +# define htonll +# elif (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || \ + (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN) || \ + (defined(BYTE_ORDER) && defined(LITTLE_ENDIAN) && BYTE_ORDER == LITTLE_ENDIAN) || \ + defined(_LITTLE_ENDIAN) || defined(__LITTLE_ENDIAN__) || defined(__ARMEL__) || defined(__MIPSEL__) || \ + defined(__i386) || defined(__i386__) || defined(__x86_64) || defined(__x86_64__) || defined(__amd64) +# define ntohll(x) ((ntohl((uint32_t)(x)) * UINT64_C(0x100000000)) + (ntohl((x) >> 32))) +# define htonll ntohll +# else +# error "Unable to determine byte order!" +# endif +# endif +#endif + + +#ifdef __cplusplus +# define CONST_CAST(t, v) const_cast(v) +#else +/* C-style const_cast without triggering a warning with -Wcast-qual */ +# define CONST_CAST(t, v) (t)(uintptr_t)(v) +#endif + +#ifdef __GNUC__ +#ifndef likely +# define likely(x) __builtin_expect(!!(x), 1) +#endif +#ifndef unlikely +# define unlikely(x) __builtin_expect(!!(x), 0) +#endif +# define unreachable() __builtin_unreachable() +#elif defined(_MSC_VER) +# define likely(x) (x) +# define unlikely(x) (x) +# define unreachable() __assume(0) +#else +# define likely(x) (x) +# define unlikely(x) (x) +# define unreachable() do {} while (0) +#endif + +static inline bool add_check_overflow(size_t v1, size_t v2, size_t *r) +{ +#if ((defined(__GNUC__) && (__GNUC__ >= 5)) && !defined(__INTEL_COMPILER)) || __has_builtin(__builtin_add_overflow) + return __builtin_add_overflow(v1, v2, r); +#else + /* unsigned additions are well-defined */ + *r = v1 + v2; + return v1 > v1 + v2; +#endif +} + +#endif /* COMPILERSUPPORT_H */ + diff --git a/src/cbor/lib/tinycbor/src/open_memstream.c b/src/cbor/lib/tinycbor/src/open_memstream.c new file mode 100644 index 000000000..830a67973 --- /dev/null +++ b/src/cbor/lib/tinycbor/src/open_memstream.c @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#define _BSD_SOURCE 1 +#define _DEFAULT_SOURCE 1 +#define _GNU_SOURCE 1 + +#include +#include +#include +#include +#include + +#include + +typedef ssize_t RetType; +typedef size_t LenType; + +#include "compilersupport_p.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + +struct Buffer +{ + char **ptr; + size_t *len; + size_t alloc; +}; + +static RetType write_to_buffer(void *cookie, const char *data, LenType len) +{ + struct Buffer *b = (struct Buffer *)cookie; + char *ptr = *b->ptr; + size_t newsize; + + errno = EFBIG; + if (unlikely(add_check_overflow(*b->len, len, &newsize))) + return -1; + + if (newsize > b->alloc) { + /* make room */ + size_t newalloc = newsize + newsize / 2 + 1; /* give 50% more room */ + ptr = realloc(ptr, newalloc); + if (ptr == NULL) + return -1; + b->alloc = newalloc; + *b->ptr = ptr; + } + + memcpy(ptr + *b->len, data, len); + *b->len = newsize; + return len; +} + +static int close_buffer(void *cookie) +{ + struct Buffer *b = (struct Buffer *)cookie; + if (*b->ptr) + (*b->ptr)[*b->len] = '\0'; + free(b); + return 0; +} + +FILE *open_memstream(char **bufptr, size_t *lenptr) +{ + struct Buffer *b = (struct Buffer *)malloc(sizeof(struct Buffer)); + if (b == NULL) + return NULL; + b->alloc = 0; + b->len = lenptr; + b->ptr = bufptr; + *bufptr = NULL; + *lenptr = 0; + +#ifdef __APPLE__ + return funopen(b, NULL, write_to_buffer, NULL, close_buffer); +#elif __GLIBC__ + static const cookie_io_functions_t vtable = { + NULL, + write_to_buffer, + NULL, + close_buffer + }; + return fopencookie(b, "w", vtable); +#endif +} + +#pragma GCC diagnostic pop diff --git a/src/cbor/lib/tinycbor/src/tags.txt b/src/cbor/lib/tinycbor/src/tags.txt new file mode 100644 index 000000000..ef78cfb5a --- /dev/null +++ b/src/cbor/lib/tinycbor/src/tags.txt @@ -0,0 +1,23 @@ +# Tag number; Tag ID; Applicable types (comma-separated); Semantics +0;DateTimeString;TextString;Standard date/time string +1;UnixTime_t;Integer;Epoch-based date/time +2;PositiveBignum;ByteString;Positive bignum +3;NegativeBignum;ByteString;Negative bignum +4;Decimal;Array;Decimal fraction +5;Bigfloat;Array;Bigfloat +16;COSE_Encrypt0;Array;COSE Single Recipient Encrypted Data Object (RFC 8152) +17;COSE_Mac0;Array;COSE Mac w/o Recipients Object (RFC 8152) +18;COSE_Sign1;Array;COSE Single Signer Data Object (RFC 8162) +21;ExpectedBase64url;ByteString,Array,Map;Expected conversion to base64url encoding +22;ExpectedBase64;ByteString,Array,Map;Expected conversion to base64 encoding +23;ExpectedBase16;ByteString,Array,Map;Expected conversion to base16 encoding +24;EncodedCbor;ByteString;Encoded CBOR data item +32;Url;TextString;URI +33;Base64url;TextString;base64url +34;Base64;TextString;base64 +35;RegularExpression;TextString;Regular expression +36;MimeMessage;TextString;MIME message +96;COSE_Encrypt;Array;COSE Encrypted Data Object (RFC 8152) +97;COSE_Mac;Array;COSE MACed Data Object (RFC 8152) +98;COSE_Sign;Array;COSE Signed Data Object (RFC 8152) +55799;Signature;;Self-describe CBOR diff --git a/src/cbor/lib/tinycbor/src/tinycbor-version.h b/src/cbor/lib/tinycbor/src/tinycbor-version.h new file mode 100644 index 000000000..c57d7fa24 --- /dev/null +++ b/src/cbor/lib/tinycbor/src/tinycbor-version.h @@ -0,0 +1,3 @@ +#define TINYCBOR_VERSION_MAJOR 0 +#define TINYCBOR_VERSION_MINOR 5 +#define TINYCBOR_VERSION_PATCH 2 diff --git a/src/cbor/lib/tinycbor/src/utf8_p.h b/src/cbor/lib/tinycbor/src/utf8_p.h new file mode 100644 index 000000000..577e54043 --- /dev/null +++ b/src/cbor/lib/tinycbor/src/utf8_p.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#ifndef CBOR_UTF8_H +#define CBOR_UTF8_H + +#include "compilersupport_p.h" + +#include + +static inline uint32_t get_utf8(const uint8_t **buffer, const uint8_t *end) +{ + int charsNeeded; + uint32_t uc, min_uc; + uint8_t b; + ptrdiff_t n = end - *buffer; + if (n == 0) + return ~0U; + + uc = *(*buffer)++; + if (uc < 0x80) { + /* single-byte UTF-8 */ + return uc; + } + + /* multi-byte UTF-8, decode it */ + if (unlikely(uc <= 0xC1)) + return ~0U; + if (uc < 0xE0) { + /* two-byte UTF-8 */ + charsNeeded = 2; + min_uc = 0x80; + uc &= 0x1f; + } else if (uc < 0xF0) { + /* three-byte UTF-8 */ + charsNeeded = 3; + min_uc = 0x800; + uc &= 0x0f; + } else if (uc < 0xF5) { + /* four-byte UTF-8 */ + charsNeeded = 4; + min_uc = 0x10000; + uc &= 0x07; + } else { + return ~0U; + } + + if (n < charsNeeded - 1) + return ~0U; + + /* first continuation character */ + b = *(*buffer)++; + if ((b & 0xc0) != 0x80) + return ~0U; + uc <<= 6; + uc |= b & 0x3f; + + if (charsNeeded > 2) { + /* second continuation character */ + b = *(*buffer)++; + if ((b & 0xc0) != 0x80) + return ~0U; + uc <<= 6; + uc |= b & 0x3f; + + if (charsNeeded > 3) { + /* third continuation character */ + b = *(*buffer)++; + if ((b & 0xc0) != 0x80) + return ~0U; + uc <<= 6; + uc |= b & 0x3f; + } + } + + /* overlong sequence? surrogate pair? out or range? */ + if (uc < min_uc || uc - 0xd800U < 2048U || uc > 0x10ffff) + return ~0U; + + return uc; +} + +#endif /* CBOR_UTF8_H */ diff --git a/src/cbor/types/CloudBool.h b/src/cbor/types/CloudBool.h new file mode 100644 index 000000000..9a0295f25 --- /dev/null +++ b/src/cbor/types/CloudBool.h @@ -0,0 +1,78 @@ +// +// This file is part of ArduinoCloudThing +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of ArduinoCloudThing. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +#ifndef CLOUDBOOL_H_ +#define CLOUDBOOL_H_ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include +#include "../ArduinoCloudProperty.h" + +/****************************************************************************** + CLASS DECLARATION + ******************************************************************************/ + + + +class CloudBool : public ArduinoCloudProperty { + protected: + bool _value, + _cloud_value; + public: + CloudBool() { + CloudBool(false); + } + CloudBool(bool v) : _value(v), _cloud_value(v) {} + operator bool() const { + return _value; + } + virtual bool isDifferentFromCloud() { + return _value != _cloud_value; + } + virtual void fromCloudToLocal() { + _value = _cloud_value; + } + virtual void fromLocalToCloud() { + _cloud_value = _value; + } + virtual void appendAttributesToCloud() { + appendAttribute(_value); + } + virtual void setAttributesFromCloud() { + setAttribute(_cloud_value); + } + //modifiers + CloudBool& operator=(bool v) { + _value = v; + updateLocalTimestamp(); + return *this; + } + CloudBool& operator=(CloudBool v) { + return operator=((bool)v); + } + //accessors + CloudBool operator!() const { + return CloudBool(!_value); + } + //friends +}; + + +#endif /* CLOUDBOOL_H_ */ diff --git a/src/cbor/types/CloudColor.h b/src/cbor/types/CloudColor.h new file mode 100644 index 000000000..cddc69751 --- /dev/null +++ b/src/cbor/types/CloudColor.h @@ -0,0 +1,203 @@ +// +// This file is part of ArduinoCloudThing +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of ArduinoCloudThing. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +#ifndef CLOUDCOLOR_H_ +#define CLOUDCOLOR_H_ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include +#include +#include "../ArduinoCloudProperty.h" + +/****************************************************************************** + CLASS DECLARATION + ******************************************************************************/ + +class Color { + public: + float hue, sat, bri; + Color(float h, float s, float b): hue(h), sat(s), bri(b) { + setColorHSB(h, s, b); + } + + bool setColorHSB(float h, float s, float b) { + if (h < 0 || h > 360 || s < 0 || s > 100 || b < 0 || b > 100) { + hue = 0; + sat = 0; + bri = 0; + return false; + } + + hue = h; + sat = s; + bri = b; + return true; + } + + bool setColorRGB(uint8_t R, uint8_t G, uint8_t B) { + float temp[3]; + float max, min, delta; + uint8_t imax; + temp[0] = (float)R / 255; + temp[1] = (float)G / 255; + temp[2] = (float)B / 255; + max = temp[0]; + imax = 0; + min = temp[0]; + + for (uint8_t j = 0; j < 3; j++) { + + if (temp[j] >= max) { + max = temp[j]; + imax = j; + } + if (temp[j] <= min) { + min = temp[j]; + } + } + + delta = max - min; + if (delta == 0) { + hue = 0; + } else if (imax == 0) { + + hue = 60 * fmod((temp[1] - temp[2]) / delta, 6); + } else if (imax == 1) { + hue = 60 * (((temp[2] - temp[0]) / delta) + 2); + } else if (imax == 2) { + hue = 60 * (((temp[0] - temp[1]) / delta) + 4); + } + + if (max == 0) { + sat = 0; + } else { + sat = (delta / max) * 100; + } + + bri = max * 100; + return true; + } + + void getRGB(uint8_t& R, uint8_t& G, uint8_t& B) { + float fC = (bri / 100) * (sat / 100); + float fHPrime = fmod(hue / 60.0, 6); + float fX = fC * (1 - fabs(fmod(fHPrime, 2) - 1)); + float fM = (bri / 100) - fC; + float fR, fG, fB; + + if (0 <= fHPrime && fHPrime < 1) { + fR = fC; + fG = fX; + fB = 0; + } else if (1 <= fHPrime && fHPrime < 2) { + fR = fX; + fG = fC; + fB = 0; + } else if (2 <= fHPrime && fHPrime < 3) { + fR = 0; + fG = fC; + fB = fX; + } else if (3 <= fHPrime && fHPrime < 4) { + fR = 0; + fG = fX; + fB = fC; + } else if (4 <= fHPrime && fHPrime < 5) { + fR = fX; + fG = 0; + fB = fC; + } else if (5 <= fHPrime && fHPrime < 6) { + fR = fC; + fG = 0; + fB = fX; + } else { + fR = 0; + fG = 0; + fB = 0; + } + R = lrint((fR + fM) * 255); + G = lrint((fG + fM) * 255); + B = lrint((fB + fM) * 255); + } + + Color& operator=(Color & aColor) { + hue = aColor.hue; + sat = aColor.sat; + bri = aColor.bri; + return *this; + } + + bool operator==(Color & aColor) { + return hue == aColor.hue && sat == aColor.sat && bri == aColor.bri; + } + + bool operator!=(Color & aColor) { + return !(operator==(aColor)); + } + +}; + +class CloudColor : public ArduinoCloudProperty { + private: + Color _value, + _cloud_value; + public: + CloudColor() : _value(0, 0, 0), _cloud_value(0, 0, 0) {} + CloudColor(float hue, float saturation, float brightness) : _value(hue, saturation, brightness), _cloud_value(hue, saturation, brightness) {} + + virtual bool isDifferentFromCloud() { + + return _value != _cloud_value; + } + + CloudColor& operator=(Color aColor) { + _value.hue = aColor.hue; + _value.sat = aColor.sat; + _value.bri = aColor.bri; + updateLocalTimestamp(); + return *this; + } + + Color getCloudValue() { + return _cloud_value; + } + + Color getValue() { + return _value; + } + + virtual void fromCloudToLocal() { + _value = _cloud_value; + } + virtual void fromLocalToCloud() { + _cloud_value = _value; + } + virtual void appendAttributesToCloud() { + appendAttribute(_value.hue); + appendAttribute(_value.sat); + appendAttribute(_value.bri); + } + virtual void setAttributesFromCloud() { + setAttribute(_cloud_value.hue); + setAttribute(_cloud_value.sat); + setAttribute(_cloud_value.bri); + } +}; + +#endif /* CLOUDCOLOR_H_ */ \ No newline at end of file diff --git a/src/cbor/types/CloudFloat.h b/src/cbor/types/CloudFloat.h new file mode 100644 index 000000000..6c9a2014d --- /dev/null +++ b/src/cbor/types/CloudFloat.h @@ -0,0 +1,187 @@ +// +// This file is part of ArduinoCloudThing +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of ArduinoCloudThing. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +#ifndef CLOUDFLOAT_H_ +#define CLOUDFLOAT_H_ + +#include + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include +#include "../ArduinoCloudProperty.h" + +/****************************************************************************** + CLASS DECLARATION + ******************************************************************************/ + + + +class CloudFloat : public ArduinoCloudProperty { + protected: + float _value, + _cloud_value; + public: + CloudFloat() { + CloudFloat(0.0f); + } + CloudFloat(float v) : _value(v), _cloud_value(v) {} + operator float() const { + return _value; + } + virtual bool isDifferentFromCloud() { + return _value != _cloud_value && (abs(_value - _cloud_value) >= ArduinoCloudProperty::_min_delta_property); + } + virtual void fromCloudToLocal() { + _value = _cloud_value; + } + virtual void fromLocalToCloud() { + _cloud_value = _value; + } + virtual void appendAttributesToCloud() { + appendAttribute(_value); + } + virtual void setAttributesFromCloud() { + setAttribute(_cloud_value); + } + //modifiers + CloudFloat& operator=(float v) { + _value = v; + updateLocalTimestamp(); + return *this; + } + CloudFloat& operator=(CloudFloat v) { + return operator=((float)v); + } + CloudFloat& operator+=(float v) { + return operator=(_value += v); + } + CloudFloat& operator-=(float v) { + return operator=(_value -= v); + } + CloudFloat& operator*=(float v) { + return operator=(_value *= v); + } + CloudFloat& operator/=(float v) { + return operator=(_value /= v); + } + CloudFloat& operator++() { + return operator=(_value + 1.0f); + } + CloudFloat& operator--() { + return operator=(_value - 1.0f); + } + CloudFloat operator++(int) { + operator=(_value + 1.0f); + return CloudFloat(_value); + } + CloudFloat operator--(int) { + operator=(_value - 1.0f); + return CloudFloat(_value); + } + + //friends + friend CloudFloat operator+(CloudFloat iw, CloudFloat v) { + return iw += v; + } + friend CloudFloat operator+(CloudFloat iw, float v) { + return iw += v; + } + friend CloudFloat operator+(CloudFloat iw, int v) { + return iw += (float)v; + } + friend CloudFloat operator+(CloudFloat iw, double v) { + return iw += (float)v; + } + friend CloudFloat operator+(float v, CloudFloat iw) { + return CloudFloat(v) += iw; + } + friend CloudFloat operator+(int v, CloudFloat iw) { + return CloudFloat(v) += iw; + } + friend CloudFloat operator+(double v, CloudFloat iw) { + return CloudFloat(v) += iw; + } + friend CloudFloat operator-(CloudFloat iw, CloudFloat v) { + return iw -= v; + } + friend CloudFloat operator-(CloudFloat iw, float v) { + return iw -= v; + } + friend CloudFloat operator-(CloudFloat iw, int v) { + return iw -= (float)v; + } + friend CloudFloat operator-(CloudFloat iw, double v) { + return iw -= (float)v; + } + friend CloudFloat operator-(float v, CloudFloat iw) { + return CloudFloat(v) -= iw; + } + friend CloudFloat operator-(int v, CloudFloat iw) { + return CloudFloat(v) -= iw; + } + friend CloudFloat operator-(double v, CloudFloat iw) { + return CloudFloat(v) -= iw; + } + friend CloudFloat operator*(CloudFloat iw, CloudFloat v) { + return iw *= v; + } + friend CloudFloat operator*(CloudFloat iw, float v) { + return iw *= v; + } + friend CloudFloat operator*(CloudFloat iw, int v) { + return iw *= (float)v; + } + friend CloudFloat operator*(CloudFloat iw, double v) { + return iw *= (float)v; + } + friend CloudFloat operator*(float v, CloudFloat iw) { + return CloudFloat(v) *= iw; + } + friend CloudFloat operator*(int v, CloudFloat iw) { + return CloudFloat(v) *= iw; + } + friend CloudFloat operator*(double v, CloudFloat iw) { + return CloudFloat(v) *= iw; + } + friend CloudFloat operator/(CloudFloat iw, CloudFloat v) { + return iw /= v; + } + friend CloudFloat operator/(CloudFloat iw, float v) { + return iw /= v; + } + friend CloudFloat operator/(CloudFloat iw, int v) { + return iw /= (float)v; + } + friend CloudFloat operator/(CloudFloat iw, double v) { + return iw /= (float)v; + } + friend CloudFloat operator/(float v, CloudFloat iw) { + return CloudFloat(v) /= iw; + } + friend CloudFloat operator/(int v, CloudFloat iw) { + return CloudFloat(v) /= iw; + } + friend CloudFloat operator/(double v, CloudFloat iw) { + return CloudFloat(v) /= iw; + } +}; + + +#endif /* CLOUDFLOAT_H_ */ diff --git a/src/cbor/types/CloudInt.h b/src/cbor/types/CloudInt.h new file mode 100644 index 000000000..7093fb05a --- /dev/null +++ b/src/cbor/types/CloudInt.h @@ -0,0 +1,222 @@ +// +// This file is part of ArduinoCloudThing +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of ArduinoCloudThing. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +#ifndef CLOUDINT_H_ +#define CLOUDINT_H_ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include +#include "../ArduinoCloudProperty.h" + +/****************************************************************************** + CLASS DECLARATION + ******************************************************************************/ + + + +class CloudInt : public ArduinoCloudProperty { + private: + int _value, + _cloud_value; + public: + CloudInt() { + CloudInt(0); + } + CloudInt(int v) : _value(v), _cloud_value(v) {} + operator int() const { + return _value; + } + virtual bool isDifferentFromCloud() { + return _value != _cloud_value && (abs(_value - _cloud_value) >= ArduinoCloudProperty::_min_delta_property); + } + virtual void fromCloudToLocal() { + _value = _cloud_value; + } + virtual void fromLocalToCloud() { + _cloud_value = _value; + } + virtual void appendAttributesToCloud() { + appendAttribute(_value); + } + virtual void setAttributesFromCloud() { + setAttribute(_cloud_value); + } + //modifiers + CloudInt& operator=(int v) { + _value = v; + updateLocalTimestamp(); + return *this; + } + CloudInt& operator=(CloudInt v) { + return operator=((int)v); + } + CloudInt& operator+=(int v) { + return operator=(_value += v); + } + CloudInt& operator-=(int v) { + return operator=(_value -= v); + } + CloudInt& operator*=(int v) { + return operator=(_value *= v); + } + CloudInt& operator/=(int v) { + return operator=(_value /= v); + } + CloudInt& operator%=(int v) { + return operator=(_value %= v); + } + CloudInt& operator++() { + return operator=(++_value); + } + CloudInt& operator--() { + return operator=(--_value); + } + CloudInt operator++(int) { + operator=(_value + 1); + return CloudInt(_value); + } + CloudInt operator--(int) { + operator=(_value - 1); + return CloudInt(_value); + } + CloudInt& operator&=(int v) { + return operator=(_value &= v); + } + CloudInt& operator|=(int v) { + return operator=(_value |= v); + } + CloudInt& operator^=(int v) { + return operator=(_value ^= v); + } + CloudInt& operator<<=(int v) { + return operator=(_value <<= v); + } + CloudInt& operator>>=(int v) { + return operator=(_value >>= v); + } + //accessors + CloudInt operator+() const { + return CloudInt(+_value); + } + CloudInt operator-() const { + return CloudInt(-_value); + } + CloudInt operator!() const { + return CloudInt(!_value); + } + CloudInt operator~() const { + return CloudInt(~_value); + } + //friends + friend CloudInt operator+(CloudInt iw, CloudInt v) { + return iw += v; + } + friend CloudInt operator+(CloudInt iw, int v) { + return iw += v; + } + friend CloudInt operator+(int v, CloudInt iw) { + return CloudInt(v) += iw; + } + friend CloudInt operator-(CloudInt iw, CloudInt v) { + return iw -= v; + } + friend CloudInt operator-(CloudInt iw, int v) { + return iw -= v; + } + friend CloudInt operator-(int v, CloudInt iw) { + return CloudInt(v) -= iw; + } + friend CloudInt operator*(CloudInt iw, CloudInt v) { + return iw *= v; + } + friend CloudInt operator*(CloudInt iw, int v) { + return iw *= v; + } + friend CloudInt operator*(int v, CloudInt iw) { + return CloudInt(v) *= iw; + } + friend CloudInt operator/(CloudInt iw, CloudInt v) { + return iw /= v; + } + friend CloudInt operator/(CloudInt iw, int v) { + return iw /= v; + } + friend CloudInt operator/(int v, CloudInt iw) { + return CloudInt(v) /= iw; + } + friend CloudInt operator%(CloudInt iw, CloudInt v) { + return iw %= v; + } + friend CloudInt operator%(CloudInt iw, int v) { + return iw %= v; + } + friend CloudInt operator%(int v, CloudInt iw) { + return CloudInt(v) %= iw; + } + friend CloudInt operator&(CloudInt iw, CloudInt v) { + return iw &= v; + } + friend CloudInt operator&(CloudInt iw, int v) { + return iw &= v; + } + friend CloudInt operator&(int v, CloudInt iw) { + return CloudInt(v) &= iw; + } + friend CloudInt operator|(CloudInt iw, CloudInt v) { + return iw |= v; + } + friend CloudInt operator|(CloudInt iw, int v) { + return iw |= v; + } + friend CloudInt operator|(int v, CloudInt iw) { + return CloudInt(v) |= iw; + } + friend CloudInt operator^(CloudInt iw, CloudInt v) { + return iw ^= v; + } + friend CloudInt operator^(CloudInt iw, int v) { + return iw ^= v; + } + friend CloudInt operator^(int v, CloudInt iw) { + return CloudInt(v) ^= iw; + } + friend CloudInt operator<<(CloudInt iw, CloudInt v) { + return iw <<= v; + } + friend CloudInt operator<<(CloudInt iw, int v) { + return iw <<= v; + } + friend CloudInt operator<<(int v, CloudInt iw) { + return CloudInt(v) <<= iw; + } + friend CloudInt operator>>(CloudInt iw, CloudInt v) { + return iw >>= v; + } + friend CloudInt operator>>(CloudInt iw, int v) { + return iw >>= v; + } + friend CloudInt operator>>(int v, CloudInt iw) { + return CloudInt(v) >>= iw; + } + +}; + + +#endif /* CLOUDINT_H_ */ diff --git a/src/cbor/types/CloudLocation.h b/src/cbor/types/CloudLocation.h new file mode 100644 index 000000000..79d65543d --- /dev/null +++ b/src/cbor/types/CloudLocation.h @@ -0,0 +1,102 @@ +// +// This file is part of ArduinoCloudThing +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of ArduinoCloudThing. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +#ifndef CLOUDLOCATION_H_ +#define CLOUDLOCATION_H_ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include +#include +#include "../ArduinoCloudProperty.h" + +/****************************************************************************** + CLASS DECLARATION + ******************************************************************************/ + + + +class Location { + public: + float lat, + lon; + Location(float lat, float lon) : lat(lat), lon(lon) {} + Location& operator=(Location& aLocation) { + lat = aLocation.lat; + lon = aLocation.lon; + return *this; + } + Location operator-(Location& aLocation) { + return Location(lat - aLocation.lat, lon - aLocation.lon); + } + bool operator==(Location& aLocation) { + return lat == aLocation.lat && lon == aLocation.lon; + } + bool operator!=(Location& aLocation) { + return !(operator==(aLocation)); + } + static float distance(Location& loc1, Location& loc2) { + return sqrt(pow(loc1.lat - loc2.lat, 2) + pow(loc1.lon - loc2.lon, 2)); + } +}; + +class CloudLocation : public ArduinoCloudProperty { + private: + Location _value, + _cloud_value; + public: + CloudLocation() : _value(0, 0), _cloud_value(0, 0) {} + CloudLocation(float lat, float lon) : _value(lat, lon), _cloud_value(lat, lon) {} + virtual bool isDifferentFromCloud() { + float const distance = Location::distance(_value, _cloud_value); + return _value != _cloud_value && (abs(distance) >= ArduinoCloudProperty::_min_delta_property); + } + + CloudLocation& operator=(Location aLocation) { + _value.lat = aLocation.lat; + _value.lon = aLocation.lon; + updateLocalTimestamp(); + return *this; + } + + Location getCloudValue() { + return _cloud_value; + } + + Location getValue() { + return _value; + } + + virtual void fromCloudToLocal() { + _value = _cloud_value; + } + virtual void fromLocalToCloud() { + _cloud_value = _value; + } + virtual void appendAttributesToCloud() { + appendAttribute(_value.lat); + appendAttribute(_value.lon); + } + virtual void setAttributesFromCloud() { + setAttribute(_cloud_value.lat); + setAttribute(_cloud_value.lon); + } +}; + +#endif /* CLOUDLOCATION_H_ */ diff --git a/src/cbor/types/CloudString.h b/src/cbor/types/CloudString.h new file mode 100644 index 000000000..800a6ddab --- /dev/null +++ b/src/cbor/types/CloudString.h @@ -0,0 +1,89 @@ +// +// This file is part of ArduinoCloudThing +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of ArduinoCloudThing. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +#ifndef CLOUDSTRING_H_ +#define CLOUDSTRING_H_ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include +#include "../ArduinoCloudProperty.h" + +/****************************************************************************** + CLASS DECLARATION + ******************************************************************************/ + + + +class CloudString : public ArduinoCloudProperty { + private: + String _value, + _cloud_value; + public: + CloudString() { + CloudString(""); + } + CloudString(const char *v) { + CloudString(String(v)); + } + CloudString(String v) : _value(v), _cloud_value(v) {} + operator String() const { + return _value; + } + virtual bool isDifferentFromCloud() { + return _value != _cloud_value; + } + virtual void fromCloudToLocal() { + _value = _cloud_value; + } + virtual void fromLocalToCloud() { + _cloud_value = _value; + } + virtual void appendAttributesToCloud() { + appendAttribute(_value); + } + virtual void setAttributesFromCloud() { + setAttribute(_cloud_value); + } + //modifiers + CloudString& operator=(String v) { + _value = v; + updateLocalTimestamp(); + return *this; + } + CloudString& operator=(const char *v) { + return operator=(String(v)); + } + CloudString& operator+=(String v) { + return operator=(_value += v); + } + bool operator==(const char *c) const { + return operator==(String(c)); + } + bool operator==(String c) const { + return _value == c; + } + //friends + friend CloudString operator+(CloudString cs, String v) { + return cs += v; + } +}; + + +#endif /* CLOUDSTRING_H_ */ diff --git a/src/cbor/types/CloudWrapperBase.h b/src/cbor/types/CloudWrapperBase.h new file mode 100644 index 000000000..3d5910c55 --- /dev/null +++ b/src/cbor/types/CloudWrapperBase.h @@ -0,0 +1,38 @@ +// +// This file is part of ArduinoCloudThing +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of ArduinoCloudThing. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +#ifndef CLOUDWRAPPERBASE_H_ +#define CLOUDWRAPPERBASE_H_ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include +#include "../ArduinoCloudProperty.h" + +/****************************************************************************** + CLASS DECLARATION + ******************************************************************************/ + +class CloudWrapperBase : public ArduinoCloudProperty { + public: + virtual bool isChangedLocally() = 0; +}; + + +#endif /* CLOUDWRAPPERBASE_H_ */ diff --git a/src/cbor/types/CloudWrapperBool.h b/src/cbor/types/CloudWrapperBool.h new file mode 100644 index 000000000..483cefc99 --- /dev/null +++ b/src/cbor/types/CloudWrapperBool.h @@ -0,0 +1,63 @@ +// +// This file is part of ArduinoCloudThing +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of ArduinoCloudThing. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +#ifndef CLOUDWRAPPERBOOL_H_ +#define CLOUDWRAPPERBOOL_H_ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include +#include "CloudWrapperBase.h" + +/****************************************************************************** + CLASS DECLARATION + ******************************************************************************/ + +class CloudWrapperBool : public CloudWrapperBase { + private: + bool &_primitive_value, + _cloud_value, + _local_value; + public: + CloudWrapperBool(bool& v) : _primitive_value(v), _cloud_value(v), _local_value(v) {} + virtual bool isDifferentFromCloud() { + return _primitive_value != _cloud_value; + } + virtual void fromCloudToLocal() { + _primitive_value = _cloud_value; + } + virtual void fromLocalToCloud() { + _cloud_value = _primitive_value; + } + virtual void appendAttributesToCloud() { + appendAttribute(_primitive_value); + } + virtual void setAttributesFromCloud() { + setAttribute(_cloud_value); + } + virtual bool isPrimitive() { + return true; + } + virtual bool isChangedLocally() { + return _primitive_value != _local_value; + } +}; + + +#endif /* CLOUDWRAPPERBOOL_H_ */ diff --git a/src/cbor/types/CloudWrapperFloat.h b/src/cbor/types/CloudWrapperFloat.h new file mode 100644 index 000000000..0c69f6df6 --- /dev/null +++ b/src/cbor/types/CloudWrapperFloat.h @@ -0,0 +1,65 @@ +// +// This file is part of ArduinoCloudThing +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of ArduinoCloudThing. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +#ifndef CLOUDWRAPPERFLOAT_H_ +#define CLOUDWRAPPERFLOAT_H_ + +#include + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include +#include "CloudWrapperBase.h" + +/****************************************************************************** + CLASS DECLARATION + ******************************************************************************/ + +class CloudWrapperFloat : public CloudWrapperBase { + private: + float &_primitive_value, + _cloud_value, + _local_value; + public: + CloudWrapperFloat(float& v) : _primitive_value(v), _cloud_value(v), _local_value(v) {} + virtual bool isDifferentFromCloud() { + return _primitive_value != _cloud_value && (abs(_primitive_value - _cloud_value) >= ArduinoCloudProperty::_min_delta_property); + } + virtual void fromCloudToLocal() { + _primitive_value = _cloud_value; + } + virtual void fromLocalToCloud() { + _cloud_value = _primitive_value; + } + virtual void appendAttributesToCloud() { + appendAttribute(_primitive_value); + } + virtual void setAttributesFromCloud() { + setAttribute(_cloud_value); + } + virtual bool isPrimitive() { + return true; + } + virtual bool isChangedLocally() { + return _primitive_value != _local_value; + } +}; + + +#endif /* CLOUWRAPPERFLOAT_H_ */ diff --git a/src/cbor/types/CloudWrapperInt.h b/src/cbor/types/CloudWrapperInt.h new file mode 100644 index 000000000..88c4a2f9a --- /dev/null +++ b/src/cbor/types/CloudWrapperInt.h @@ -0,0 +1,63 @@ +// +// This file is part of ArduinoCloudThing +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of ArduinoCloudThing. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +#ifndef CLOUDWRAPPERINT_H_ +#define CLOUDWRAPPERINT_H_ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include +#include "CloudWrapperBase.h" + +/****************************************************************************** + CLASS DECLARATION + ******************************************************************************/ + +class CloudWrapperInt : public CloudWrapperBase { + private: + int &_primitive_value, + _cloud_value, + _local_value; + public: + CloudWrapperInt(int& v) : _primitive_value(v), _cloud_value(v), _local_value(v) {} + virtual bool isDifferentFromCloud() { + return _primitive_value != _cloud_value && (abs(_primitive_value - _cloud_value) >= ArduinoCloudProperty::_min_delta_property); + } + virtual void fromCloudToLocal() { + _primitive_value = _cloud_value; + } + virtual void fromLocalToCloud() { + _cloud_value = _primitive_value; + } + virtual void appendAttributesToCloud() { + appendAttribute(_primitive_value); + } + virtual void setAttributesFromCloud() { + setAttribute(_cloud_value); + } + virtual bool isPrimitive() { + return true; + } + virtual bool isChangedLocally() { + return _primitive_value != _local_value; + } +}; + + +#endif /* CLOUDWRAPPERINT_H_ */ diff --git a/src/cbor/types/CloudWrapperString.h b/src/cbor/types/CloudWrapperString.h new file mode 100644 index 000000000..e384365ad --- /dev/null +++ b/src/cbor/types/CloudWrapperString.h @@ -0,0 +1,67 @@ +// +// This file is part of ArduinoCloudThing +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of ArduinoCloudThing. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +#ifndef CLOUDWRAPPERSTRING_H_ +#define CLOUDWRAPPERSTRING_H_ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include +#include "CloudWrapperBase.h" + +/****************************************************************************** + CLASS DECLARATION + ******************************************************************************/ + +class CloudWrapperString : public CloudWrapperBase { + private: + String &_primitive_value, + _cloud_value, + _local_value; + public: + CloudWrapperString(String& v) : + _primitive_value(v), + _cloud_value(v), + _local_value(v) { + } + virtual bool isDifferentFromCloud() { + return _primitive_value != _cloud_value; + } + virtual void fromCloudToLocal() { + _primitive_value = _cloud_value; + } + virtual void fromLocalToCloud() { + _cloud_value = _primitive_value; + } + virtual void appendAttributesToCloud() { + appendAttribute(_primitive_value); + } + virtual void setAttributesFromCloud() { + setAttribute(_cloud_value); + } + virtual bool isPrimitive() { + return true; + } + virtual bool isChangedLocally() { + return _primitive_value != _local_value; + } +}; + + +#endif /* CLOUDWRAPPERSTRING_H_ */ diff --git a/src/cbor/types/automation/CloudColoredLight.h b/src/cbor/types/automation/CloudColoredLight.h new file mode 100644 index 000000000..d5e380118 --- /dev/null +++ b/src/cbor/types/automation/CloudColoredLight.h @@ -0,0 +1,135 @@ +// +// This file is part of ArduinoCloudThing +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of ArduinoCloudThing. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +#ifndef CLOUDCOLOREDLIGHT_H_ +#define CLOUDCOLOREDLIGHT_H_ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include +#include +#include "../CloudColor.h" + +/****************************************************************************** + CLASS DECLARATION + ******************************************************************************/ + +class ColoredLight : public Color { + public: + bool swi; + ColoredLight(bool swi, float h, float s, float b): Color(h, s, b), swi(swi) { + } + + bool operator==(ColoredLight & aLight) { + return Color::operator==(aLight) && aLight.swi == swi; + } + + bool operator!=(ColoredLight & aLight) { + return !(operator==(aLight)); + } + +}; + +class CloudColoredLight : public CloudColor { + private: + ColoredLight _value, + _cloud_value; + public: + CloudColoredLight() : _value(false, 0, 0, 0), _cloud_value(false, 0, 0, 0) {} + CloudColoredLight(bool swi, float hue, float saturation, float brightness) : _value(swi, hue, saturation, brightness), _cloud_value(swi, hue, saturation, brightness) {} + + virtual bool isDifferentFromCloud() { + + return _value != _cloud_value; + } + + CloudColoredLight& operator=(ColoredLight aLight) { + _value.swi = aLight.swi; + _value.hue = aLight.hue; + _value.sat = aLight.sat; + _value.bri = aLight.bri; + updateLocalTimestamp(); + return *this; + } + + ColoredLight getCloudValue() { + return _cloud_value; + } + + ColoredLight getValue() { + return _value; + } + + bool getSwitch() { + return _value.swi; + } + + void setSwitch(bool const swi) { + _value.swi = swi; + updateLocalTimestamp(); + } + + float getHue() { + return _value.hue; + } + + void setHue(float const hue) { + _value.hue = hue; + updateLocalTimestamp(); + } + + float getSaturation() { + return _value.sat; + } + + void setSaturation(float const sat) { + _value.sat = sat; + updateLocalTimestamp(); + } + + float getBrightness() { + return _value.bri; + } + + void setBrightness(float const bri) { + _value.bri = bri; + updateLocalTimestamp(); + } + + virtual void fromCloudToLocal() { + _value = _cloud_value; + } + virtual void fromLocalToCloud() { + _cloud_value = _value; + } + virtual void appendAttributesToCloud() { + appendAttribute(_value.swi); + appendAttribute(_value.hue); + appendAttribute(_value.sat); + appendAttribute(_value.bri); + } + virtual void setAttributesFromCloud() { + setAttribute(_cloud_value.swi); + setAttribute(_cloud_value.hue); + setAttribute(_cloud_value.sat); + setAttribute(_cloud_value.bri); + } +}; + +#endif /* CLOUDCOLOREDLIGHT_H_ */ \ No newline at end of file diff --git a/src/cbor/types/automation/CloudContactSensor.h b/src/cbor/types/automation/CloudContactSensor.h new file mode 100644 index 000000000..351eaed73 --- /dev/null +++ b/src/cbor/types/automation/CloudContactSensor.h @@ -0,0 +1,47 @@ +// +// This file is part of ArduinoCloudThing +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of ArduinoCloudThing. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +#ifndef CLOUDCONTACTSENSOR_H_ +#define CLOUDCONTACTSENSOR_H_ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include +#include "../CloudBool.h" + +/****************************************************************************** + CLASS DECLARATION + ******************************************************************************/ + + + +class CloudContactSensor : public CloudBool { + private: + public: + operator bool() const { + return _value; + } + CloudContactSensor& operator=(bool v) { + CloudBool::operator=(v); + return *this; + } +}; + + +#endif /* CLOUDCONTACTSENSOR_H_ */ diff --git a/src/cbor/types/automation/CloudDimmedLight.h b/src/cbor/types/automation/CloudDimmedLight.h new file mode 100644 index 000000000..9dbfe1204 --- /dev/null +++ b/src/cbor/types/automation/CloudDimmedLight.h @@ -0,0 +1,121 @@ +// +// This file is part of ArduinoCloudThing +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of ArduinoCloudThing. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +#ifndef CLOUDDIMMEDLIGHT_H_ +#define CLOUDDIMMEDLIGHT_H_ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include +#include +#include "../../ArduinoCloudProperty.h" + +/****************************************************************************** + CLASS DECLARATION + ******************************************************************************/ +class DimmedLight { + public: + bool swi; + float bri; + DimmedLight(bool swi, float bri): swi(swi), bri(bri) { + } + + bool operator==(DimmedLight & aLight) { + return aLight.swi == swi && aLight.bri == bri; + } + + bool operator!=(DimmedLight & aLight) { + return !(operator==(aLight)); + } + +}; + +class CloudDimmedLight : public ArduinoCloudProperty { + private: + DimmedLight _value, + _cloud_value; + + public: + CloudDimmedLight() : _value(false, 0), _cloud_value(false, 0) {} + CloudDimmedLight(bool swi, float brightness) : _value(swi, brightness), _cloud_value(swi, brightness) {} + + virtual bool isDifferentFromCloud() { + + return _value != _cloud_value; + } + + CloudDimmedLight& operator=(DimmedLight aLight) { + _value.swi = aLight.swi; + _value.bri = aLight.bri; + updateLocalTimestamp(); + return *this; + } + + DimmedLight getCloudValue() { + return _cloud_value; + } + + DimmedLight getValue() { + return _value; + } + + float getBrightness() { + return _value.bri; + } + + void setBrightness(float const bri) { + _value.bri = bri; + } + + bool getSwitch() { + return _value.swi; + } + + void setSwitch(bool const swi) { + _value.swi = swi; + } + + virtual void fromCloudToLocal() { + _value = _cloud_value; + } + virtual void fromLocalToCloud() { + _cloud_value = _value; + } + + virtual void appendAttributesToCloud() { + appendAttribute(_value.swi); + // To allow visualization through color widget + // Start + float hue = 0; + float sat = 0; + appendAttributeReal(hue, getAttributeName(".hue", '.'), encoder); + appendAttributeReal(sat, getAttributeName(".sat", '.'), encoder); + appendAttribute(_value.bri); + // should be only: + // appendAttribute(_value.bri); + // end + } + + virtual void setAttributesFromCloud() { + setAttribute(_cloud_value.swi); + setAttribute(_cloud_value.bri); + } +}; + +#endif /* CLOUDDIMMEDLIGHT_H_ */ \ No newline at end of file diff --git a/src/cbor/types/automation/CloudLight.h b/src/cbor/types/automation/CloudLight.h new file mode 100644 index 000000000..358527b37 --- /dev/null +++ b/src/cbor/types/automation/CloudLight.h @@ -0,0 +1,47 @@ +// +// This file is part of ArduinoCloudThing +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of ArduinoCloudThing. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +#ifndef CLOUDLIGHT_H_ +#define CLOUDLIGHT_H_ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include +#include "../CloudBool.h" + +/****************************************************************************** + CLASS DECLARATION + ******************************************************************************/ + + + +class CloudLight : public CloudBool { + private: + public: + operator bool() const { + return _value; + } + CloudLight& operator=(bool v) { + CloudBool::operator=(v); + return *this; + } +}; + + +#endif /* CLOUDLIGHT_H_ */ diff --git a/src/cbor/types/automation/CloudMotionSensor.h b/src/cbor/types/automation/CloudMotionSensor.h new file mode 100644 index 000000000..3831d3d27 --- /dev/null +++ b/src/cbor/types/automation/CloudMotionSensor.h @@ -0,0 +1,47 @@ +// +// This file is part of ArduinoCloudThing +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of ArduinoCloudThing. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +#ifndef CLOUDMOTIONSENSOR_H_ +#define CLOUDMOTIONSENSOR_H_ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include +#include "../CloudBool.h" + +/****************************************************************************** + CLASS DECLARATION + ******************************************************************************/ + + + +class CloudMotionSensor : public CloudBool { + private: + public: + operator bool() const { + return _value; + } + CloudMotionSensor& operator=(bool v) { + CloudBool::operator=(v); + return *this; + } +}; + + +#endif /* CLOUDMOTIONSENSOR_H_ */ diff --git a/src/cbor/types/automation/CloudSmartPlug.h b/src/cbor/types/automation/CloudSmartPlug.h new file mode 100644 index 000000000..99c7886fb --- /dev/null +++ b/src/cbor/types/automation/CloudSmartPlug.h @@ -0,0 +1,47 @@ +// +// This file is part of ArduinoCloudThing +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of ArduinoCloudThing. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +#ifndef CLOUDSMARTPLUG_H_ +#define CLOUDSMARTPLUG_H_ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include +#include "../CloudBool.h" + +/****************************************************************************** + CLASS DECLARATION + ******************************************************************************/ + + + +class CloudSmartPlug : public CloudBool { + private: + public: + operator bool() const { + return _value; + } + CloudSmartPlug& operator=(bool v) { + CloudBool::operator=(v); + return *this; + } +}; + + +#endif /* CLOUDSMARTPLUG_H_ */ diff --git a/src/cbor/types/automation/CloudSwitch.h b/src/cbor/types/automation/CloudSwitch.h new file mode 100644 index 000000000..d22345d88 --- /dev/null +++ b/src/cbor/types/automation/CloudSwitch.h @@ -0,0 +1,47 @@ +// +// This file is part of ArduinoCloudThing +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of ArduinoCloudThing. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +#ifndef CLOUDSWITCH_H_ +#define CLOUDSWITCH_H_ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include +#include "../CloudBool.h" + +/****************************************************************************** + CLASS DECLARATION + ******************************************************************************/ + + + +class CloudSwitch : public CloudBool { + private: + public: + operator bool() const { + return _value; + } + CloudSwitch& operator=(bool v) { + CloudBool::operator=(v); + return *this; + } +}; + + +#endif /* CLOUDSWITCH_H_ */ diff --git a/src/cbor/types/automation/CloudTelevision.h b/src/cbor/types/automation/CloudTelevision.h new file mode 100644 index 000000000..a2ee63b48 --- /dev/null +++ b/src/cbor/types/automation/CloudTelevision.h @@ -0,0 +1,234 @@ +// +// This file is part of ArduinoCloudThing +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of ArduinoCloudThing. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +#ifndef CLOUDTELEVISION_H_ +#define CLOUDTELEVISION_H_ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include +#include "../../ArduinoCloudProperty.h" + +/****************************************************************************** + ENUM + ******************************************************************************/ +enum class PlaybackCommands : int { + FastForward = 0, + Next = 1, + Pause = 2, + Play = 3, + Previous = 4, + Rewind = 5, + StartOver = 6, + Stop = 7, + None = 255 +}; +enum class InputValue : int { + AUX1 = 0, + AUX2 = 1, + AUX3 = 2, + AUX4 = 3, + AUX5 = 4, + AUX6 = 5, + AUX7 = 6, + BLURAY = 7, + CABLE = 8, + CD = 9, + COAX1 = 10, + COAX2 = 11, + COMPOSITE1 = 12, + DVD = 13, + GAME = 14, + HDRADIO = 15, + HDMI1 = 16, + HDMI2 = 17, + HDMI3 = 18, + HDMI4 = 19, + HDMI5 = 20, + HDMI6 = 21, + HDMI7 = 22, + HDMI8 = 23, + HDMI9 = 24, + HDMI10 = 25, + HDMIARC = 26, + INPUT1 = 27, + INPUT2 = 28, + INPUT3 = 29, + INPUT4 = 30, + INPUT5 = 31, + INPUT6 = 32, + INPUT7 = 33, + INPUT8 = 34, + INPUT9 = 35, + INPUT10 = 36, + IPOD = 37, + LINE1 = 38, + LINE2 = 39, + LINE3 = 40, + LINE4 = 41, + LINE5 = 42, + LINE6 = 43, + LINE7 = 44, + MEDIAPLAYER = 45, + OPTICAL1 = 46, + OPTICAL2 = 47, + PHONO = 48, + PLAYSTATION = 49, + PLAYSTATION3 = 50, + PLAYSTATION4 = 51, + SATELLITE = 52, + SMARTCAST = 53, + TUNER = 54, + TV = 55, + USBDAC = 56, + VIDEO1 = 57, + VIDEO2 = 58, + VIDEO3 = 59, + XBOX = 60 +}; + +/****************************************************************************** + CLASS DECLARATION + ******************************************************************************/ + +class Television { + public: + bool swi; + int vol; + bool mut; + PlaybackCommands pbc; + InputValue inp; + int cha; + + + Television(bool const swi, int const vol, bool const mut, PlaybackCommands const pbc, InputValue const inp, int const cha): swi(swi), vol(vol), mut(mut), pbc(pbc), inp(inp), cha(cha) { + } + + bool operator==(Television const & aTV) { + return + aTV.swi == swi && + aTV.vol == vol && + aTV.mut == mut && + aTV.pbc == pbc && + aTV.inp == inp && + aTV.cha == cha; + } + + bool operator!=(Television const & aTV) { + return !(operator==(aTV)); + } + +}; + +class CloudTelevision : public ArduinoCloudProperty { + private: + Television _value, + _cloud_value; + public: + CloudTelevision() : _value(false, 0, false, PlaybackCommands::None, InputValue::TV, 0), _cloud_value(false, 0, false, PlaybackCommands::None, InputValue::TV, 0) {} + CloudTelevision(bool const swi, int const vol, bool const mut, PlaybackCommands const pbc, InputValue const inp, int const cha) : _value(swi, vol, mut, pbc, inp, cha), _cloud_value(swi, vol, mut, pbc, inp, cha) {} + + virtual bool isDifferentFromCloud() { + + return _value != _cloud_value; + } + + CloudTelevision& operator=(Television const aTV) { + _value.swi = aTV.swi; + _value.vol = aTV.vol; + _value.mut = aTV.mut; + _value.pbc = aTV.pbc; + _value.inp = aTV.inp; + _value.cha = aTV.cha; + updateLocalTimestamp(); + return *this; + } + + Television getCloudValue() { + return _cloud_value; + } + + Television getValue() { + return _value; + } + + void setSwitch(bool const swi) { + _value.swi = swi; + updateLocalTimestamp(); + } + + bool getSwitch() { + return _value.swi; + } + + void setVolume(uint8_t const vol) { + _value.vol = vol; + updateLocalTimestamp(); + } + + uint8_t getVolume() { + return _value.vol; + } + + void setMute(bool const mut) { + _value.mut = mut; + updateLocalTimestamp(); + } + + bool getMute() { + return _value.mut; + } + + PlaybackCommands getPlaybackCommand() { + return _value.pbc; + } + + InputValue getInputValue() { + return _value.inp; + } + + uint16_t getChannel() { + return _value.cha; + } + + virtual void fromCloudToLocal() { + _value = _cloud_value; + } + virtual void fromLocalToCloud() { + _cloud_value = _value; + } + virtual void appendAttributesToCloud() { + appendAttribute(_value.swi); + appendAttribute(_value.vol); + appendAttribute(_value.mut); + appendAttribute((int)_value.pbc); + appendAttribute((int)_value.inp); + appendAttribute(_value.cha); + } + virtual void setAttributesFromCloud() { + setAttribute(_cloud_value.swi); + setAttribute(_cloud_value.vol); + setAttribute(_cloud_value.mut); + setAttribute((int&)_cloud_value.pbc); + setAttribute((int&)_cloud_value.inp); + setAttribute(_cloud_value.cha); + } +}; + +#endif /* CLOUDTELEVISION_H_ */ diff --git a/src/cbor/types/automation/CloudTemperature.h b/src/cbor/types/automation/CloudTemperature.h new file mode 100644 index 000000000..a82559813 --- /dev/null +++ b/src/cbor/types/automation/CloudTemperature.h @@ -0,0 +1,44 @@ +// +// This file is part of ArduinoCloudThing +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of ArduinoCloudThing. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to modify or +// otherwise use the software for commercial activities involving the Arduino +// software without disclosing the source code of your own applications. To purchase +// a commercial license, send an email to license@arduino.cc. +// + +#ifndef CLOUDTEMPERATURE_H_ +#define CLOUDTEMPERATURE_H_ + +/****************************************************************************** + INCLUDE + ******************************************************************************/ + +#include +#include "../CloudFloat.h" + +/****************************************************************************** + CLASS DECLARATION + ******************************************************************************/ + + + +class CloudTemperature : public CloudFloat { + private: + public: + CloudTemperature& operator=(float v) { + CloudFloat::operator=(v); + return *this; + } +}; + + +#endif /* CLOUDTEMPERATURE_H_ */