#ifndef AWS_COMMON_CBOR_H #define AWS_COMMON_CBOR_H /** * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ #include #include AWS_PUSH_SANE_WARNING_LEVEL AWS_EXTERN_C_BEGIN /** * The types use by APIs, not 1:1 with major type. * It's an extension for cbor major type in RFC8949 section 3.1 * Major type 0 - AWS_CBOR_TYPE_UINT * Major type 1 - AWS_CBOR_TYPE_NEGINT * Major type 2 - AWS_CBOR_TYPE_BYTES/AWS_CBOR_TYPE_INDEF_BYTES_START * Major type 3 - AWS_CBOR_TYPE_TEXT/AWS_CBOR_TYPE_INDEF_TEXT_START * Major type 4 - AWS_CBOR_TYPE_ARRAY_START/AWS_CBOR_TYPE_INDEF_ARRAY_START * Major type 5 - AWS_CBOR_TYPE_MAP_START/AWS_CBOR_TYPE_INDEF_MAP_START * Major type 6 - AWS_CBOR_TYPE_TAG * Major type 7 * - 20/21 - AWS_CBOR_TYPE_BOOL * - 22 - AWS_CBOR_TYPE_NULL * - 23 - AWS_CBOR_TYPE_UNDEFINED * - 25/26/27 - AWS_CBOR_TYPE_FLOAT * - 31 - AWS_CBOR_TYPE_BREAK * - rest of value are not supported. */ enum aws_cbor_type { AWS_CBOR_TYPE_UNKNOWN = 0, AWS_CBOR_TYPE_UINT, AWS_CBOR_TYPE_NEGINT, AWS_CBOR_TYPE_FLOAT, AWS_CBOR_TYPE_BYTES, AWS_CBOR_TYPE_TEXT, AWS_CBOR_TYPE_ARRAY_START, AWS_CBOR_TYPE_MAP_START, AWS_CBOR_TYPE_TAG, AWS_CBOR_TYPE_BOOL, AWS_CBOR_TYPE_NULL, AWS_CBOR_TYPE_UNDEFINED, AWS_CBOR_TYPE_BREAK, AWS_CBOR_TYPE_INDEF_BYTES_START, AWS_CBOR_TYPE_INDEF_TEXT_START, AWS_CBOR_TYPE_INDEF_ARRAY_START, AWS_CBOR_TYPE_INDEF_MAP_START, }; /** * The common tags, refer to RFC8949 section 3.4 * Expected value type followed by the tag: * AWS_CBOR_TAG_STANDARD_TIME - AWS_CBOR_TYPE_TEXT * AWS_CBOR_TAG_EPOCH_TIME - AWS_CBOR_TYPE_UINT/AWS_CBOR_TYPE_NEGINT/AWS_CBOR_TYPE_FLOAT * AWS_CBOR_TAG_UNSIGNED_BIGNUM - AWS_CBOR_TYPE_BYTES * AWS_CBOR_TAG_NEGATIVE_BIGNUM - AWS_CBOR_TYPE_BYTES * AWS_CBOR_TAG_DECIMAL_FRACTION - AWS_CBOR_TYPE_ARRAY_START/AWS_CBOR_TYPE_INDEF_ARRAY_START **/ #define AWS_CBOR_TAG_STANDARD_TIME 0 #define AWS_CBOR_TAG_EPOCH_TIME 1 #define AWS_CBOR_TAG_UNSIGNED_BIGNUM 2 #define AWS_CBOR_TAG_NEGATIVE_BIGNUM 3 #define AWS_CBOR_TAG_DECIMAL_FRACTION 4 struct aws_cbor_encoder; struct aws_cbor_decoder; /******************************************************************************* * ENCODE ******************************************************************************/ /* Return c-string for aws_cbor_type */ AWS_COMMON_API const char *aws_cbor_type_cstr(enum aws_cbor_type type); /** * @brief Create a new cbor encoder. Creating a encoder with a temporay buffer. * Every aws_cbor_encoder_write_* will encode directly into the buffer to follow the encoded data. * * @param allocator * @return aws_cbor_encoder */ AWS_COMMON_API struct aws_cbor_encoder *aws_cbor_encoder_new(struct aws_allocator *allocator); AWS_COMMON_API struct aws_cbor_encoder *aws_cbor_encoder_destroy(struct aws_cbor_encoder *encoder); /** * @brief 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. * * @param encoder * @return struct aws_byte_cursor from the encoder buffer. */ AWS_COMMON_API struct aws_byte_cursor aws_cbor_encoder_get_encoded_data(const struct aws_cbor_encoder *encoder); /** * @brief Clear the current encoded buffer from encoder. * * @param encoder */ AWS_COMMON_API void aws_cbor_encoder_reset(struct aws_cbor_encoder *encoder); /** * @brief Encode a AWS_CBOR_TYPE_UINT value to "smallest possible" in encoder's buffer. * Referring to RFC8949 section 4.2.1 * * TODO: maybe add a width of the encoded value. * * @param encoder * @param value value to encode. */ AWS_COMMON_API void aws_cbor_encoder_write_uint(struct aws_cbor_encoder *encoder, uint64_t value); /** * @brief 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 encoder * @param value The argument to encode to negative integer, which is (-1 - expected_val) */ AWS_COMMON_API void aws_cbor_encoder_write_negint(struct aws_cbor_encoder *encoder, uint64_t value); /** * @brief 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 conversation will not cause precision loss. * * @param encoder * @param value value to encode. */ AWS_COMMON_API void aws_cbor_encoder_write_float(struct aws_cbor_encoder *encoder, double value); /** * @brief Encode a AWS_CBOR_TYPE_BYTES value to "smallest possible" in encoder's buffer. * Referring to RFC8949 section 4.2.1, the length of "from" will be encoded first and then the value of "from" will * be followed. * * @param encoder * @param from value to encode. */ AWS_COMMON_API void aws_cbor_encoder_write_bytes(struct aws_cbor_encoder *encoder, struct aws_byte_cursor from); /** * @brief Encode a AWS_CBOR_TYPE_TEXT value to "smallest possible" in encoder's buffer. * Referring to RFC8949 section 4.2.1, the length of "from" will be encoded first and then the value of "from" will * be followed. * * @param encoder * @param from value to encode. */ AWS_COMMON_API void aws_cbor_encoder_write_text(struct aws_cbor_encoder *encoder, struct aws_byte_cursor from); /** * @brief Encode a AWS_CBOR_TYPE_ARRAY_START value to "smallest possible" in encoder's buffer. * Referring to RFC8949 section 4.2.1 * The "number_entries" is the cbor data items should be followed as the content of the array. * Notes: it's user's responsibility to keep the integrity of the array to be encoded. * * @param encoder * @param number_entries The number of data item in array. */ AWS_COMMON_API void aws_cbor_encoder_write_array_start(struct aws_cbor_encoder *encoder, size_t number_entries); /** * @brief Encode a AWS_CBOR_TYPE_MAP_START value to "smallest possible" in encoder's buffer. * Referring to RFC8949 section 4.2.1 * The "number_entries" is the number of pair of cbor data items as key and value should be followed as the content of * the map. * * Notes: it's user's responsibility to keep the integrity of the map to be encoded. * * @param encoder * @param number_entries The number of data item in map. */ AWS_COMMON_API void aws_cbor_encoder_write_map_start(struct aws_cbor_encoder *encoder, size_t number_entries); /** * @brief Encode a AWS_CBOR_TYPE_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 encoder * @param tag_number The tag value to encode. */ AWS_COMMON_API void aws_cbor_encoder_write_tag(struct aws_cbor_encoder *encoder, uint64_t tag_number); /** * @brief Encode a simple value AWS_CBOR_TYPE_NULL * * @param encoder */ AWS_COMMON_API void aws_cbor_encoder_write_null(struct aws_cbor_encoder *encoder); /** * @brief Encode a simple value AWS_CBOR_TYPE_UNDEFINED * * @param encoder */ AWS_COMMON_API void aws_cbor_encoder_write_undefined(struct aws_cbor_encoder *encoder); /** * @brief Encode a simple value AWS_CBOR_TYPE_BOOL * * @param encoder */ AWS_COMMON_API void aws_cbor_encoder_write_bool(struct aws_cbor_encoder *encoder, bool value); /** * @brief Encode a simple value AWS_CBOR_TYPE_BREAK * * Notes: no error checking, it's user's responsibility to track the break * to close the corresponding indef_start */ AWS_COMMON_API void aws_cbor_encoder_write_break(struct aws_cbor_encoder *encoder); /** * @brief Encode a AWS_CBOR_TYPE_INDEF_BYTES_START * * Notes: no error checking, it's user's responsibility to add corresponding data and the break * to close the indef_start */ AWS_COMMON_API void aws_cbor_encoder_write_indef_bytes_start(struct aws_cbor_encoder *encoder); /** * @brief Encode a AWS_CBOR_TYPE_INDEF_TEXT_START * * Notes: no error checking, it's user's responsibility to add corresponding data * and the break to close the indef_start */ AWS_COMMON_API void aws_cbor_encoder_write_indef_text_start(struct aws_cbor_encoder *encoder); /** * @brief Encode a AWS_CBOR_TYPE_INDEF_ARRAY_START * * Notes: no error checking, it's user's responsibility to add corresponding data * and the break to close the indef_start */ AWS_COMMON_API void aws_cbor_encoder_write_indef_array_start(struct aws_cbor_encoder *encoder); /** * @brief Encode a AWS_CBOR_TYPE_INDEF_MAP_START * * Notes: no error checking, it's user's responsibility to add corresponding data * and the break to close the indef_start */ AWS_COMMON_API void aws_cbor_encoder_write_indef_map_start(struct aws_cbor_encoder *encoder); /******************************************************************************* * DECODE ******************************************************************************/ /** * @brief Create a cbor decoder to take src to decode. * The typical usage of decoder will be: * - If the next element type only accept what expected, `aws_cbor_decoder_pop_next_*` * - If the next element type accept different type, invoke `aws_cbor_decoder_peek_type` first, then based on the type * to invoke corresponding `aws_cbor_decoder_pop_next_*` * - If the next element type doesn't have corrsponding value, specifically: AWS_CBOR_TYPE_NULL, * AWS_CBOR_TYPE_UNDEFINED, AWS_CBOR_TYPE_INF_*_START, AWS_CBOR_TYPE_BREAK, call * `aws_cbor_decoder_consume_next_single_element` to consume it and continues for further decoding. * - To ignore the next data item (the element and the content of it), `aws_cbor_decoder_consume_next_whole_data_item` * * Note: it's caller's responsibilty to keep the src outlive the decoder. * * @param allocator * @param src The src data to decode from. * @return decoder */ AWS_COMMON_API struct aws_cbor_decoder *aws_cbor_decoder_new(struct aws_allocator *allocator, struct aws_byte_cursor src); AWS_COMMON_API struct aws_cbor_decoder *aws_cbor_decoder_destroy(struct aws_cbor_decoder *decoder); /** * @brief 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. * * @param decoder * @return The length of bytes remaining of the decoder source. */ AWS_COMMON_API size_t aws_cbor_decoder_get_remaining_length(const struct aws_cbor_decoder *decoder); /** * @brief Decode the next element and store it in the decoder cache if there was no element cached. * If there was element cached, just return the type of the cached element. * * @param decoder * @param out_type * @return AWS_OP_SUCCESS if succeed, AWS_OP_ERR for any decoding error and corresponding error code will be raised. */ AWS_COMMON_API int aws_cbor_decoder_peek_type(struct aws_cbor_decoder *decoder, enum aws_cbor_type *out_type); /** * @brief 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. * * @param src The src to parse data from * @return AWS_OP_SUCCESS successfully consumed the next data item, otherwise AWS_OP_ERR. */ AWS_COMMON_API int aws_cbor_decoder_consume_next_whole_data_item(struct aws_cbor_decoder *decoder); /** * @brief 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" * * @param decoder The decoder to parse data from * @return AWS_OP_SUCCESS successfully consumed the next element, otherwise AWS_OP_ERR. */ AWS_COMMON_API int aws_cbor_decoder_consume_next_single_element(struct aws_cbor_decoder *decoder); /** * @brief Get the next element based on the type. If the next element doesn't match the expected type. Error will be * raised. If the next element already been cached, it will consume the cached item when no error was returned. * Specifically: * AWS_CBOR_TYPE_UINT - aws_cbor_decoder_pop_next_unsigned_int_val * AWS_CBOR_TYPE_NEGINT - aws_cbor_decoder_pop_next_negative_int_val, it represents (-1 - *out) * AWS_CBOR_TYPE_FLOAT - aws_cbor_decoder_pop_next_double_val * AWS_CBOR_TYPE_BYTES - aws_cbor_decoder_pop_next_bytes_val * AWS_CBOR_TYPE_TEXT - aws_cbor_decoder_pop_next_text_val * * @param decoder * @param out * @return AWS_OP_SUCCESS successfully consumed the next element and get the result, otherwise AWS_OP_ERR. */ AWS_COMMON_API int aws_cbor_decoder_pop_next_unsigned_int_val(struct aws_cbor_decoder *decoder, uint64_t *out); AWS_COMMON_API int aws_cbor_decoder_pop_next_negative_int_val(struct aws_cbor_decoder *decoder, uint64_t *out); AWS_COMMON_API int aws_cbor_decoder_pop_next_float_val(struct aws_cbor_decoder *decoder, double *out); AWS_COMMON_API int aws_cbor_decoder_pop_next_boolean_val(struct aws_cbor_decoder *decoder, bool *out); AWS_COMMON_API int aws_cbor_decoder_pop_next_bytes_val(struct aws_cbor_decoder *decoder, struct aws_byte_cursor *out); AWS_COMMON_API int aws_cbor_decoder_pop_next_text_val(struct aws_cbor_decoder *decoder, struct aws_byte_cursor *out); /** * @brief Get the next AWS_CBOR_TYPE_ARRAY_START element. Only consume the AWS_CBOR_TYPE_ARRAY_START 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 AWS_CBOR_TYPE_INDEF_ARRAY_START from _peek_type * - call `aws_cbor_decoder_consume_next_single_element` to pop the indefinite-length start. * - Decode the next data item until AWS_CBOR_TYPE_BREAK read. * * @param decoder * @param out_size store the size of array if succeed. * @return AWS_OP_SUCCESS successfully consumed the next element and get the result, otherwise AWS_OP_ERR. */ AWS_COMMON_API int aws_cbor_decoder_pop_next_array_start(struct aws_cbor_decoder *decoder, uint64_t *out_size); /** * @brief Get the next AWS_CBOR_TYPE_MAP_START element. Only consume the AWS_CBOR_TYPE_MAP_START 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 AWS_CBOR_TYPE_INDEF_MAP_START from _peek_type * - call `aws_cbor_decoder_consume_next_single_element` to pop the indefinite-length start. * - Decode the next data item until AWS_CBOR_TYPE_BREAK read. * * @param decoder * @param out_size store the size of map if succeed. * @return AWS_OP_SUCCESS successfully consumed the next element and get the result, otherwise AWS_OP_ERR. */ AWS_COMMON_API int aws_cbor_decoder_pop_next_map_start(struct aws_cbor_decoder *decoder, uint64_t *out_size); /** * @brief Get the next AWS_CBOR_TYPE_TAG element. Only consume the AWS_CBOR_TYPE_TAG element and set the * tag value to *out_tag_val, not the content of the tagged. The next cbor data item will be the content of the tagged * value for a valid cbor data. * * @param decoder * @param out_size store the size of map if succeed. * @return AWS_OP_SUCCESS successfully consumed the next element and get the result, otherwise AWS_OP_ERR. */ AWS_COMMON_API int aws_cbor_decoder_pop_next_tag_val(struct aws_cbor_decoder *decoder, uint64_t *out_tag_val); AWS_EXTERN_C_END AWS_POP_SANE_WARNING_LEVEL #endif // AWS_COMMON_CBOR_H