From 55f2a48cfcb837d55c7130f4b9fae4e337425bd9 Mon Sep 17 00:00:00 2001 From: Dengke Tang Date: Mon, 24 Jun 2024 11:54:50 -0700 Subject: [PATCH] cbor binding (#627) --- CMakeLists.txt | 15 +- crt/aws-c-common | 2 +- include/aws/crt/cbor/Cbor.h | 367 ++++++++++++++++++++++++++++++++++++ source/cbor/Cbor.cpp | 268 ++++++++++++++++++++++++++ tests/CMakeLists.txt | 3 + tests/CborTest.cpp | 242 ++++++++++++++++++++++++ 6 files changed, 890 insertions(+), 7 deletions(-) create mode 100644 include/aws/crt/cbor/Cbor.h create mode 100644 source/cbor/Cbor.cpp create mode 100644 tests/CborTest.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ec6d17202..fb54a14e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -171,8 +171,8 @@ file(GLOB AWS_CRT_ENDPOINT_HEADERS "include/aws/crt/endpoints/*.h" ) -file(GLOB AWS_CRT_EXTERNAL_HEADERS - "include/aws/crt/external/*.h" +file(GLOB AWS_CRT_CBOR_HEADERS + "include/aws/crt/cbor/*.h" ) file(GLOB AWS_CRT_PUBLIC_HEADERS @@ -184,6 +184,7 @@ file(GLOB AWS_CRT_PUBLIC_HEADERS ${AWS_CRT_MQTT_HEADERS} ${AWS_CRT_HTTP_HEADERS} ${AWS_CRT_ENDPOINT_HEADERS} + ${AWS_CRT_CBOR_HEADERS} ) if(BUILD_DEPS) @@ -193,7 +194,6 @@ endif() file(GLOB AWS_CRT_CPP_HEADERS ${AWS_CRT_PUBLIC_HEADERS} - ${AWS_CRT_EXTERNAL_HEADERS} ) file(GLOB AWS_CRT_SRC @@ -228,8 +228,8 @@ file(GLOB AWS_CRT_ENDPOINTS_SRC "source/endpoints/*.cpp" ) -file(GLOB AWS_CRT_EXTERNAL_SRC - "source/external/*.cpp" +file(GLOB AWS_CRT_CBOR_SRC + "source/cbor/*.cpp" ) file(GLOB AWS_CRT_CPP_SRC @@ -241,7 +241,7 @@ file(GLOB AWS_CRT_CPP_SRC ${AWS_CRT_MQTT_SRC} ${AWS_CRT_HTTP_SRC} ${AWS_CRT_ENDPOINTS_SRC} - ${AWS_CRT_EXTERNAL_SRC} + ${AWS_CRT_CBOR_SRC} ) if(WIN32) @@ -254,6 +254,7 @@ if(WIN32) source_group("Header Files\\aws\\crt\\mqtt" FILES ${AWS_CRT_MQTT_HEADERS}) source_group("Header Files\\aws\\crt\\http" FILES ${AWS_CRT_HTTP_HEADERS}) source_group("Header Files\\aws\\crt\\endpoints" FILES ${AWS_CRT_ENDPOINT_HEADERS}) + source_group("Header Files\\aws\\crt\\cbor" FILES ${AWS_CRT_CBOR_HEADERS}) source_group("Source Files" FILES ${AWS_CRT_SRC}) source_group("Source Files\\auth" FILES ${AWS_CRT_AUTH_SRC}) @@ -263,6 +264,7 @@ if(WIN32) source_group("Source Files\\mqtt" FILES ${AWS_CRT_MQTT_SRC}) source_group("Source Files\\http" FILES ${AWS_CRT_HTTP_SRC}) source_group("Source Files\\endpoints" FILES ${AWS_CRT_ENDPOINTS_SRC}) + source_group("Source Files\\cbor" FILES ${AWS_CRT_CBOR_SRC}) endif() endif() @@ -332,6 +334,7 @@ install(FILES ${AWS_CRT_IOT_HEADERS} DESTINATION "include/aws/iot" COMPONENT Dev install(FILES ${AWS_CRT_MQTT_HEADERS} DESTINATION "include/aws/crt/mqtt" COMPONENT Development) install(FILES ${AWS_CRT_HTTP_HEADERS} DESTINATION "include/aws/crt/http" COMPONENT Development) install(FILES ${AWS_CRT_ENDPOINT_HEADERS} DESTINATION "include/aws/crt/endpoints" COMPONENT Development) +install(FILES ${AWS_CRT_CBOR_HEADERS} DESTINATION "include/aws/crt/cbor" COMPONENT Development) install( TARGETS ${PROJECT_NAME} diff --git a/crt/aws-c-common b/crt/aws-c-common index 4f874cea5..6d974f92c 160000 --- a/crt/aws-c-common +++ b/crt/aws-c-common @@ -1 +1 @@ -Subproject commit 4f874cea50a70bc6ebcd85c6ce1c6c0016b5aff4 +Subproject commit 6d974f92c1d86391c1dcb1173239adf757c52b2d diff --git a/include/aws/crt/cbor/Cbor.h b/include/aws/crt/cbor/Cbor.h new file mode 100644 index 000000000..af3f82716 --- /dev/null +++ b/include/aws/crt/cbor/Cbor.h @@ -0,0 +1,367 @@ +#pragma once +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include + +#include + +namespace Aws +{ + namespace Crt + { + namespace Cbor + { + /** + * The types used by APIs, not 1:1 with major types. + * It's an extension for CBOR major type in RFC8949 section 3.1. + * Major type 0 - UInt + * Major type 1 - NegInt + * Major type 2 - Bytes / IndefBytesStart + * Major type 3 - Text / IndefTextStart + * Major type 4 - ArrayStart / IndefArrayStart + * Major type 5 - MapStart / IndefMapStart + * Major type 6 - Tag + * Major type 7: + * - 20/21 - Bool + * - 22 - Null + * - 23 - Undefined + * - 25/26/27 - Float + * - 31 - Break + * - Rest of the values are not supported. + */ + enum class CborType + { + Unknown = AWS_CBOR_TYPE_UNKNOWN, + UInt = AWS_CBOR_TYPE_UINT, + NegInt = AWS_CBOR_TYPE_NEGINT, + Float = AWS_CBOR_TYPE_FLOAT, + Bytes = AWS_CBOR_TYPE_BYTES, + Text = AWS_CBOR_TYPE_TEXT, + ArrayStart = AWS_CBOR_TYPE_ARRAY_START, + MapStart = AWS_CBOR_TYPE_MAP_START, + Tag = AWS_CBOR_TYPE_TAG, + Bool = AWS_CBOR_TYPE_BOOL, + Null = AWS_CBOR_TYPE_NULL, + Undefined = AWS_CBOR_TYPE_UNDEFINED, + Break = AWS_CBOR_TYPE_BREAK, + IndefBytesStart = AWS_CBOR_TYPE_INDEF_BYTES_START, + IndefTextStart = AWS_CBOR_TYPE_INDEF_TEXT_START, + IndefArrayStart = AWS_CBOR_TYPE_INDEF_ARRAY_START, + IndefMapStart = AWS_CBOR_TYPE_INDEF_MAP_START, + }; + + class AWS_CRT_CPP_API CborEncoder final + { + public: + CborEncoder(const CborEncoder &) = delete; + CborEncoder(CborEncoder &&) = delete; + CborEncoder &operator=(const CborEncoder &) = delete; + CborEncoder &operator=(CborEncoder &&) = delete; + + CborEncoder(Allocator *allocator = ApiAllocator()) noexcept; + ~CborEncoder() noexcept; + + /** + * Get the current encoded data from encoder. The encoded data has the same lifetime as the encoder, + * and once any other function call invoked for the encoder, the encoded data is no longer valid. + * + * @return the current encoded data + */ + ByteCursor GetEncodedData() noexcept; + + /** + * Clear the current encoded buffer from encoder. + */ + void Reset() noexcept; + + /** + * Encode a AWS_CBOR_TYPE_UINT value to "smallest possible" in encoder's buffer. + * Referring to RFC8949 section 4.2.1 + * + * @param value value to encode. + */ + void WriteUInt(uint64_t value) noexcept; + + /** + * Encode a AWS_CBOR_TYPE_NEGINT value to "smallest possible" in encoder's buffer. + * It represents (-1 - value). + * Referring to RFC8949 section 4.2.1 + * + * @param value value to encode, which is (-1 - represented value) + */ + void WriteNegInt(uint64_t value) noexcept; + + /** + * Encode a AWS_CBOR_TYPE_FLOAT value to "smallest possible", but will not be encoded into + * half-precision float, as it's not well supported cross languages. + * + * To be more specific, it will be encoded into integer/negative/float + * (Order with priority) when the conversion will not cause precision loss. + * + * @param value value to encode. + */ + void WriteFloat(double value) noexcept; + + /** + * Encode a Bytes value to "smallest possible" in encoder's buffer. + * Referring to RFC8949 section 4.2.1, the length of "value" will be encoded first and then the value of + * "value" will be followed. + * + * @param value value to encode. + */ + void WriteBytes(ByteCursor value) noexcept; + + /** + * Encode a Text value to "smallest possible" in encoder's buffer. + * Referring to RFC8949 section 4.2.1, the length of "value" will be encoded first and then the value of + * "value" will be followed. + * + * @param value value to encode. + */ + void WriteText(ByteCursor value) noexcept; + + /** + * Encode an ArrayStart value to "smallest possible" in encoder's buffer. + * Referring to RFC8949 section 4.2.1 + * Notes: it's user's responsibility to keep the integrity of the array to be encoded. + * + * @param number_entries the number of CBOR data items to be followed as the content of the array. + */ + void WriteArrayStart(size_t number_entries) noexcept; + + /** + * Encode a MapStart value to "smallest possible" in encoder's buffer. + * Referring to RFC8949 section 4.2.1 + * + * Notes: it's user's responsibility to keep the integrity of the map to be encoded. + * + * @param number_entries the number of pair of CBOR data items as key and value to be followed as + * the content of the map. + */ + void WriteMapStart(size_t number_entries) noexcept; + + /** + * Encode a Tag value to "smallest possible" in encoder's buffer. + * Referring to RFC8949 section 4.2.1 + * The following CBOR data item will be the content of the tagged value. + * Notes: it's user's responsibility to keep the integrity of the tagged value to follow the RFC8949 + * section 3.4 + * + * @param tag_number The tag value to encode. + */ + void WriteTag(uint64_t tag_number) noexcept; + + /** + * Encode a simple value Null + */ + void WriteNull() noexcept; + + /** + * Encode a simple value Undefined + */ + void WriteUndefined() noexcept; + + /** + * Encode a simple value Bool + */ + void WriteBool(bool value) noexcept; + + /** + * Encode a simple value Break + * Notes: no error checking, it's user's responsibility to track the break to close the corresponding + * indef_start + */ + void WriteBreak() noexcept; + + /** + * Encode an IndefBytesStart + * Notes: no error checking, it's user's responsibility to add corresponding data and the break to close + * the indef_start + */ + void WriteIndefBytesStart() noexcept; + + /** + * Encode an IndefTextStart + * Notes: no error checking, it's user's responsibility to add corresponding data and the break to close + * the indef_start + */ + void WriteIndefTextStart() noexcept; + + /** + * Encode an IndefArrayStart + * Notes: no error checking, it's user's responsibility to add corresponding data and the break to close + * the indef_start + */ + void WriteIndefArrayStart() noexcept; + + /** + * Encode an IndefMapStart + * Notes: no error checking, it's user's responsibility to add corresponding data and the break to close + * the indef_start + */ + void WriteIndefMapStart() noexcept; + + private: + struct aws_cbor_encoder *m_encoder; + }; + + class AWS_CRT_CPP_API CborDecoder final + { + + public: + CborDecoder(const CborDecoder &) = delete; + CborDecoder(CborDecoder &&) = delete; + CborDecoder &operator=(const CborDecoder &) = delete; + CborDecoder &operator=(CborDecoder &&) = delete; + + /** + * Construct a new Cbor Decoder object + * + * @param allocator + * @param src The src data to decode from. + */ + CborDecoder(ByteCursor src, Allocator *allocator = ApiAllocator()) noexcept; + ~CborDecoder() noexcept; + + /** + * Get the length of the remaining bytes of the source. Once the source was decoded, it will be + * consumed, and result in decrease of the remaining length of bytes. + * + * @return The length of bytes remaining of the decoder source. + */ + size_t GetRemainingLength() noexcept; + + /** + * Decode the next element and store it in the decoder cache if there was no element cached. + * If there was an element cached, just return the type of the cached element. + * + * @return If successful, return the type of next element + * If not, return will be none and LastError() can be + * used to retrieve CRT error code. + */ + Optional PeekType() noexcept; + + /** + * Consume the next data item, includes all the content within the data item. + * + * As an example for the following CBOR, this function will consume all the data + * as it's only one CBOR data item, an indefinite map with 2 key, value pair: + * 0xbf6346756ef563416d7421ff + * BF -- Start indefinite-length map + * 63 -- First key, UTF-8 string length 3 + * 46756e -- "Fun" + * F5 -- First value, true + * 63 -- Second key, UTF-8 string length 3 + * 416d74 -- "Amt" + * 21 -- Second value, -2 + * FF -- "break" + * + * Notes: this function will not ensure the data item is well-formed. + * + * @return true if the operation succeed, false otherwise and LastError() will contain the errorCode. + */ + bool ConsumeNextWholeDataItem() noexcept; + + /** + * Consume the next single element, without the content followed by the element. + * + * As an example for the following CBOR, this function will only consume the + * 0xBF, "Start indefinite-length map", not any content of the map represented. + * The next element to decode will start from 0x63. + * 0xbf6346756ef563416d7421ff + * BF -- Start indefinite-length map + * 63 -- First key, UTF-8 string length 3 + * 46756e -- "Fun" + * F5 -- First value, true + * 63 -- Second key, UTF-8 string length 3 + * 416d74 -- "Amt" + * 21 -- Second value, -2 + * FF -- "break" + * + * @return true if the operation succeed, false otherwise and LastError() will contain the errorCode. + */ + bool ConsumeNextSingleElement() noexcept; + + /** + * Get the next element based on the type. If the next element doesn't match the expected type, an error + * will be raised. If the next element has already been cached, it will consume the cached item when no + * error was returned. Specifically: + * - UInt - PopNextUnsignedIntVal + * - NegInt - PopNextNegativeIntVal, it represents (-1 - &out) + * - Float - PopNextFloatVal + * - Bytes - PopNextBytesVal + * - Text - PopNextTextVal + * + * @return If successful, return the next element + * If not, return will be none and LastError() can be + * used to retrieve CRT error code. + */ + Optional PopNextUnsignedIntVal() noexcept; + Optional PopNextNegativeIntVal() noexcept; + Optional PopNextFloatVal() noexcept; + Optional PopNextBooleanVal() noexcept; + Optional PopNextBytesVal() noexcept; + Optional PopNextTextVal() noexcept; + + /** + * Get the next ArrayStart element. Only consume the ArrayStart element and set the size of array to + * &out_size, not the content of the array. The next &out_size CBOR data items will be the content of + * the array for a valid CBOR data. + * + * Notes: For indefinite-length, this function will fail with "AWS_ERROR_CBOR_UNEXPECTED_TYPE". The + * designed way to handle indefinite-length is: + * - Get IndefArrayStart from PeekType + * - Call ConsumeNextSingleElement to pop the indefinite-length start. + * - Decode the next data item until Break is read. + * + * @return If successful, return the size of array + * If not, return will be none and LastError() can be + * used to retrieve CRT error code. + */ + Optional PopNextArrayStart() noexcept; + + /** + * Get the next MapStart element. Only consume the MapStart element and set the size of array to + * &out_size, not the content of the map. The next &out_size pair of CBOR data items as key and value + * will be the content of the array for a valid CBOR data. + * + * Notes: For indefinite-length, this function will fail with "AWS_ERROR_CBOR_UNEXPECTED_TYPE". The + * designed way to handle indefinite-length is: + * - Get IndefMapStart from PeekType + * - Call ConsumeNextSingleElement to pop the indefinite-length start. + * - Decode the next data item until Break is read. + * + * @return If successful, return the size of map + * If not, return will be none and LastError() can be + * used to retrieve CRT error code. + */ + Optional PopNextMapStart() noexcept; + + /** + * Get the next Tag element. Only consume the Tag element and set the tag value to out_tag_val, + * not the content of the tagged value. The next CBOR data item will be the content of the tagged value + * for a valid CBOR data. + * + * @return If successful, return the tag value + * If not, return will be none and LastError() can be + * used to retrieve CRT error code. + */ + Optional PopNextTagVal() noexcept; + + /** + * @return the value of the last aws error encountered by operations on this instance. + */ + int LastError() const noexcept { return m_lastError ? m_lastError : AWS_ERROR_UNKNOWN; } + + private: + struct aws_cbor_decoder *m_decoder; + /* Error */ + int m_lastError; + }; + } // namespace Cbor + + } // namespace Crt +} // namespace Aws diff --git a/source/cbor/Cbor.cpp b/source/cbor/Cbor.cpp new file mode 100644 index 000000000..7061d4f4b --- /dev/null +++ b/source/cbor/Cbor.cpp @@ -0,0 +1,268 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include + +namespace Aws +{ + namespace Crt + { + namespace Cbor + { + /***************************************************** + * + * CborEncoder + * + *****************************************************/ + CborEncoder::CborEncoder(Crt::Allocator *allocator) noexcept + { + m_encoder = aws_cbor_encoder_new(allocator); + } + + CborEncoder::~CborEncoder() noexcept + { + aws_cbor_encoder_destroy(m_encoder); + } + + ByteCursor CborEncoder::GetEncodedData() noexcept + { + return aws_cbor_encoder_get_encoded_data(m_encoder); + } + void CborEncoder::Reset() noexcept + { + aws_cbor_encoder_reset(m_encoder); + } + + void CborEncoder::WriteUInt(uint64_t value) noexcept + { + aws_cbor_encoder_write_uint(m_encoder, value); + } + void CborEncoder::WriteNegInt(uint64_t value) noexcept + { + aws_cbor_encoder_write_negint(m_encoder, value); + } + + void CborEncoder::WriteFloat(double value) noexcept + { + aws_cbor_encoder_write_float(m_encoder, value); + } + + void CborEncoder::WriteBytes(ByteCursor value) noexcept + { + aws_cbor_encoder_write_bytes(m_encoder, value); + } + + void CborEncoder::WriteText(ByteCursor value) noexcept + { + aws_cbor_encoder_write_text(m_encoder, value); + } + + void CborEncoder::WriteArrayStart(size_t number_entries) noexcept + { + aws_cbor_encoder_write_array_start(m_encoder, number_entries); + } + + void CborEncoder::WriteMapStart(size_t number_entries) noexcept + { + aws_cbor_encoder_write_map_start(m_encoder, number_entries); + } + + void CborEncoder::WriteTag(uint64_t tag_number) noexcept + { + aws_cbor_encoder_write_tag(m_encoder, tag_number); + } + + void CborEncoder::WriteNull() noexcept + { + aws_cbor_encoder_write_null(m_encoder); + } + + void CborEncoder::WriteUndefined() noexcept + { + aws_cbor_encoder_write_undefined(m_encoder); + } + + void CborEncoder::WriteBool(bool value) noexcept + { + aws_cbor_encoder_write_bool(m_encoder, value); + } + + void CborEncoder::WriteBreak() noexcept + { + aws_cbor_encoder_write_break(m_encoder); + } + + void CborEncoder::WriteIndefBytesStart() noexcept + { + aws_cbor_encoder_write_indef_bytes_start(m_encoder); + } + + void CborEncoder::WriteIndefTextStart() noexcept + { + aws_cbor_encoder_write_indef_text_start(m_encoder); + } + + void CborEncoder::WriteIndefArrayStart() noexcept + { + aws_cbor_encoder_write_indef_array_start(m_encoder); + } + + void CborEncoder::WriteIndefMapStart() noexcept + { + aws_cbor_encoder_write_indef_map_start(m_encoder); + } + + /***************************************************** + * + * CborDecoder + * + *****************************************************/ + CborDecoder::CborDecoder(ByteCursor src, Crt::Allocator *allocator) noexcept + { + m_decoder = aws_cbor_decoder_new(allocator, src); + } + + CborDecoder::~CborDecoder() noexcept + { + aws_cbor_decoder_destroy(m_decoder); + } + + size_t CborDecoder::GetRemainingLength() noexcept + { + return aws_cbor_decoder_get_remaining_length(m_decoder); + } + + Optional CborDecoder::PeekType() noexcept + { + enum aws_cbor_type out_type_c = AWS_CBOR_TYPE_UNKNOWN; + if (aws_cbor_decoder_peek_type(m_decoder, &out_type_c) != AWS_OP_SUCCESS) + { + m_lastError = aws_last_error(); + return Optional(); + } + return Optional((CborType)out_type_c); + } + bool CborDecoder::ConsumeNextWholeDataItem() noexcept + { + if (aws_cbor_decoder_consume_next_whole_data_item(m_decoder) != AWS_OP_SUCCESS) + { + m_lastError = aws_last_error(); + return false; + } + return true; + } + + bool CborDecoder::ConsumeNextSingleElement() noexcept + { + if (aws_cbor_decoder_consume_next_single_element(m_decoder) != AWS_OP_SUCCESS) + { + m_lastError = aws_last_error(); + return false; + } + return true; + } + + Optional CborDecoder::PopNextUnsignedIntVal() noexcept + { + uint64_t out = 0; + if (aws_cbor_decoder_pop_next_unsigned_int_val(m_decoder, &out) != AWS_OP_SUCCESS) + { + m_lastError = aws_last_error(); + return Optional(); + } + return Optional(out); + } + + Optional CborDecoder::PopNextNegativeIntVal() noexcept + { + uint64_t out = 0; + if (aws_cbor_decoder_pop_next_negative_int_val(m_decoder, &out) != AWS_OP_SUCCESS) + { + m_lastError = aws_last_error(); + return Optional(); + } + return Optional(out); + } + + Optional CborDecoder::PopNextFloatVal() noexcept + { + double out = 0; + if (aws_cbor_decoder_pop_next_float_val(m_decoder, &out) != AWS_OP_SUCCESS) + { + m_lastError = aws_last_error(); + return Optional(); + } + return Optional(out); + } + + Optional CborDecoder::PopNextBooleanVal() noexcept + { + bool out = false; + if (aws_cbor_decoder_pop_next_boolean_val(m_decoder, &out) != AWS_OP_SUCCESS) + { + m_lastError = aws_last_error(); + return Optional(); + } + return Optional(out); + } + + Optional CborDecoder::PopNextBytesVal() noexcept + { + ByteCursor out = {0}; + if (aws_cbor_decoder_pop_next_bytes_val(m_decoder, &out) != AWS_OP_SUCCESS) + { + m_lastError = aws_last_error(); + return Optional(); + } + return Optional(out); + } + + Optional CborDecoder::PopNextTextVal() noexcept + { + ByteCursor out = {0}; + if (aws_cbor_decoder_pop_next_text_val(m_decoder, &out) != AWS_OP_SUCCESS) + { + m_lastError = aws_last_error(); + return Optional(); + } + return Optional(out); + } + + Optional CborDecoder::PopNextArrayStart() noexcept + { + uint64_t out_size = 0; + if (aws_cbor_decoder_pop_next_array_start(m_decoder, &out_size) != AWS_OP_SUCCESS) + { + m_lastError = aws_last_error(); + return Optional(); + } + return Optional(out_size); + } + + Optional CborDecoder::PopNextMapStart() noexcept + { + uint64_t out_size = 0; + if (aws_cbor_decoder_pop_next_map_start(m_decoder, &out_size) != AWS_OP_SUCCESS) + { + m_lastError = aws_last_error(); + return Optional(); + } + return Optional(out_size); + } + + Optional CborDecoder::PopNextTagVal() noexcept + { + uint64_t out_tag_val = 0; + if (aws_cbor_decoder_pop_next_tag_val(m_decoder, &out_tag_val) != AWS_OP_SUCCESS) + { + m_lastError = aws_last_error(); + return Optional(); + } + return Optional(out_tag_val); + } + + } // namespace Cbor + } // namespace Crt +} // namespace Aws diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d0576813b..dfdc36083 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -274,6 +274,9 @@ if(AWS_HAS_CI_ENVIRONMENT AND NOT BYO_CRYPTO) endif() endif() +add_test_case(CborSanityTest) +add_test_case(CborTimeStampTest) + generate_cpp_test_driver(${TEST_BINARY_NAME}) aws_add_sanitizers(${TEST_BINARY_NAME}) diff --git a/tests/CborTest.cpp b/tests/CborTest.cpp new file mode 100644 index 000000000..f6fa95370 --- /dev/null +++ b/tests/CborTest.cpp @@ -0,0 +1,242 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include +#include + +using namespace Aws::Crt; + +static int s_CborSanityTest(struct aws_allocator *allocator, void *ctx) +{ + /** + * Simply test every method works. + */ + (void)ctx; + { + ApiHandle apiHandle(allocator); + Cbor::CborEncoder encoder(allocator); + + // Define expected values + auto expected_uint_val = 42ULL; + auto expected_negint_val = 123ULL; + auto expected_float_val = 3.14; + ByteCursor expected_bytes_val = aws_byte_cursor_from_c_str("write more"); + ByteCursor expected_text_val = aws_byte_cursor_from_c_str("test"); + size_t expected_array_size = 5; + size_t expected_map_size = 3; + auto expected_tag_val = 999ULL; + bool expected_bool_val = true; + + encoder.WriteUInt(expected_uint_val); + encoder.WriteNegInt(expected_negint_val); + encoder.WriteFloat(expected_float_val); + encoder.WriteBytes(expected_bytes_val); + encoder.WriteText(expected_text_val); + encoder.WriteArrayStart(expected_array_size); + encoder.WriteMapStart(expected_map_size); + encoder.WriteTag(expected_tag_val); + encoder.WriteBool(expected_bool_val); + encoder.WriteNull(); + encoder.WriteUndefined(); + encoder.WriteBreak(); + encoder.WriteIndefBytesStart(); + encoder.WriteIndefTextStart(); + encoder.WriteIndefArrayStart(); + encoder.WriteIndefMapStart(); + + ByteCursor cursor = encoder.GetEncodedData(); + + Cbor::CborDecoder decoder(cursor, allocator); + + // Check for UInt + ASSERT_TRUE(decoder.PeekType().value() == Cbor::CborType::UInt); + ASSERT_TRUE(decoder.PopNextUnsignedIntVal().value() == expected_uint_val); + + // Check for NegInt + ASSERT_TRUE(decoder.PeekType().value() == Cbor::CborType::NegInt); + ASSERT_TRUE(decoder.PopNextNegativeIntVal().value() == expected_negint_val); + + // Check for Float + ASSERT_TRUE(decoder.PeekType().value() == Cbor::CborType::Float); + ASSERT_TRUE(decoder.PopNextFloatVal().value() == expected_float_val); + + // Check for Bytes + ASSERT_TRUE(decoder.PeekType().value() == Cbor::CborType::Bytes); + ByteCursor decoded_val = decoder.PopNextBytesVal().value(); + ASSERT_TRUE(aws_byte_cursor_eq(&decoded_val, &expected_bytes_val)); + + // Check for Text + ASSERT_TRUE(decoder.PeekType().value() == Cbor::CborType::Text); + decoded_val = decoder.PopNextTextVal().value(); + ASSERT_TRUE(aws_byte_cursor_eq(&decoded_val, &expected_text_val)); + + // Check for ArrayStart + ASSERT_TRUE(decoder.PeekType().value() == Cbor::CborType::ArrayStart); + ASSERT_TRUE(decoder.PopNextArrayStart().value() == expected_array_size); + + // Check for MapStart + ASSERT_TRUE(decoder.PeekType().value() == Cbor::CborType::MapStart); + ASSERT_TRUE(decoder.PopNextMapStart().value() == expected_map_size); + // Check for Tag + ASSERT_TRUE(decoder.PeekType().value() == Cbor::CborType::Tag); + ASSERT_TRUE(decoder.PopNextTagVal().value() == expected_tag_val); + + // Check for Bool + ASSERT_TRUE(decoder.PeekType().value() == Cbor::CborType::Bool); + ASSERT_TRUE(decoder.PopNextBooleanVal().value() == expected_bool_val); + + // Check for Null + ASSERT_TRUE(decoder.PeekType().value() == Cbor::CborType::Null); + ASSERT_TRUE(decoder.ConsumeNextWholeDataItem()); + + // Check for Undefined + ASSERT_TRUE(decoder.PeekType().value() == Cbor::CborType::Undefined); + ASSERT_TRUE(decoder.ConsumeNextWholeDataItem()); + + // Check for Break + ASSERT_TRUE(decoder.PeekType().value() == Cbor::CborType::Break); + ASSERT_TRUE(decoder.ConsumeNextSingleElement()); + + // Check for IndefBytesStart + ASSERT_TRUE(decoder.PeekType().value() == Cbor::CborType::IndefBytesStart); + ASSERT_TRUE(decoder.ConsumeNextSingleElement()); + + // Check for IndefTextStart + ASSERT_TRUE(decoder.PeekType().value() == Cbor::CborType::IndefTextStart); + ASSERT_TRUE(decoder.ConsumeNextSingleElement()); + + // Check for IndefArrayStart + ASSERT_TRUE(decoder.PeekType().value() == Cbor::CborType::IndefArrayStart); + ASSERT_TRUE(decoder.ConsumeNextSingleElement()); + + // Check for IndefMapStart + ASSERT_TRUE(decoder.PeekType().value() == Cbor::CborType::IndefMapStart); + ASSERT_TRUE(decoder.ConsumeNextSingleElement()); + + auto length = decoder.GetRemainingLength(); + ASSERT_UINT_EQUALS(0, length); + } + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(CborSanityTest, s_CborSanityTest) + +static void s_encode_timestamp_helper( + Cbor::CborEncoder &encoder, + const std::chrono::system_clock::time_point &timePoint) noexcept +{ + /* Get seconds with MS precision. */ + std::chrono::duration timestamp(timePoint.time_since_epoch()); + double seconds = timestamp.count(); + + encoder.WriteTag(AWS_CBOR_TAG_EPOCH_TIME); + // Use the encoder to write the duration in seconds + encoder.WriteFloat(seconds); +} + +static int s_decode_timestamp_helper(Cbor::CborDecoder &decoder, std::chrono::system_clock::time_point &outTimePoint) +{ + if (decoder.PeekType().value() != Cbor::CborType::Tag) + { + return AWS_OP_ERR; + } + if (decoder.PopNextTagVal().value() != AWS_CBOR_TAG_EPOCH_TIME) + { + return AWS_OP_ERR; + } + Cbor::CborType out_type = decoder.PeekType().value(); + switch (out_type) + { + case Cbor::CborType::UInt: + case Cbor::CborType::NegInt: + { + int64_t secs_timestamp = 0; + uint64_t unsigned_val = 0; + if (out_type == Cbor::CborType::UInt) + { + unsigned_val = decoder.PopNextUnsignedIntVal().value(); + if (unsigned_val > INT64_MAX) + { + /* Overflow */ + return AWS_OP_ERR; + } + secs_timestamp = (int64_t)unsigned_val; + } + else + { + unsigned_val = decoder.PopNextNegativeIntVal().value(); + if (unsigned_val > INT64_MAX) + { + /* Overflow */ + return AWS_OP_ERR; + } + /* convert the encoded number to real negative number */ + secs_timestamp = (int64_t)(-1 - unsigned_val); + } + std::chrono::duration timestamp(secs_timestamp); + outTimePoint = std::chrono::system_clock::time_point(timestamp); + return AWS_OP_SUCCESS; + } + case Cbor::CborType::Float: + { + double double_val = decoder.PopNextFloatVal().value(); + std::chrono::duration timestamp(double_val); + outTimePoint = + std::chrono::system_clock::time_point(std::chrono::duration_cast(timestamp)); + return AWS_OP_SUCCESS; + } + default: + break; + } + + return AWS_OP_ERR; +} + +static bool s_check_time_point_equals_ms_precision( + const std::chrono::time_point &a, + const std::chrono::time_point &b) +{ + using Ms = std::chrono::milliseconds; + auto a_ms = std::chrono::duration_cast(a.time_since_epoch()); + auto b_ms = std::chrono::duration_cast(b.time_since_epoch()); + if (a_ms == b_ms) + { + return true; + } + else + { + return false; + } +} + +static int s_CborTimeStampTest(struct aws_allocator *allocator, void *ctx) +{ + /** + * Example of how timestamp will be encoded and decoded with `std::chrono::system_clock::time_point` + */ + (void)ctx; + { + ApiHandle apiHandle(allocator); + Cbor::CborEncoder encoder(allocator); + const std::chrono::time_point now = std::chrono::system_clock::now(); + + s_encode_timestamp_helper(encoder, now); + ByteCursor cursor = encoder.GetEncodedData(); + + Cbor::CborDecoder decoder(cursor); + std::chrono::time_point decoded; + ASSERT_SUCCESS(s_decode_timestamp_helper(decoder, decoded)); + + /* We only check the ms precision */ + ASSERT_TRUE(s_check_time_point_equals_ms_precision(decoded, now)); + auto length = decoder.GetRemainingLength(); + ASSERT_UINT_EQUALS(0, length); + } + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(CborTimeStampTest, s_CborTimeStampTest)