| /* | |
| * Copyright 2020, Data61, CSIRO (ABN 41 687 119 230) | |
| * | |
| * SPDX-License-Identifier: BSD-2-Clause | |
| */ | |
| #include <utils/cbor64.h> | |
| /* Generate the initial byte indicating the type of the following data */ | |
| int cbor64_initial_byte(base64_t *streamer, cbor64_mt_t type, uint8_t data) | |
| { | |
| return base64_putbyte(streamer, (type << 5) | (data & MASK(5))); | |
| } | |
| /* Send a break byte to terminate indefinite-length item */ | |
| int cbor64_send_break(base64_t *streamer) | |
| { | |
| return cbor64_initial_byte(streamer, CBOR64_MT_BREAK, CBOR64_AI_INDEFINITE_LENGTH); | |
| } | |
| int cbor64_send_item(base64_t *streamer, cbor64_mt_t type, uint64_t number) | |
| { | |
| uint8_t additional_info; | |
| size_t size; | |
| if (number < CBOR64_AI_INT_LITERAL_MAX) { | |
| /* Encode number in item byte */ | |
| additional_info = number; | |
| size = 0; | |
| } else if (number < LLBIT(8)) { | |
| /* Encode number as uint8_t */ | |
| additional_info = CBOR64_AI_UINT8_T; | |
| size = 8; | |
| } else if (number < LLBIT(16)) { | |
| /* Encode number as uint16_t */ | |
| additional_info = CBOR64_AI_UINT16_T; | |
| size = 16; | |
| } else if (number < LLBIT(32)) { | |
| /* Encode number as uint32_t */ | |
| additional_info = CBOR64_AI_UINT32_T; | |
| size = 32; | |
| } else { | |
| /* Encode number as uint64_t */ | |
| additional_info = CBOR64_AI_UINT64_T; | |
| size = 64; | |
| } | |
| int err = cbor64_initial_byte(streamer, type, additional_info); | |
| if (err != 0) { | |
| return err; | |
| } | |
| while (size > 0) { | |
| size -= 8; | |
| err = base64_putbyte(streamer, (number >> size) & MASK(8)); | |
| if (err != 0) { | |
| return err; | |
| } | |
| } | |
| return err; | |
| } | |
| /* Send a bytearray */ | |
| int cbor64_send_typed_bytes(base64_t *streamer, cbor64_mt_t type, unsigned char *buffer, size_t length) | |
| { | |
| int err = cbor64_send_item(streamer, type, length); | |
| if (err != 0) { | |
| return err; | |
| } | |
| while (length > 0) { | |
| err = base64_putbyte(streamer, *buffer); | |
| if (err != 0) { | |
| return err; | |
| } | |
| length -= 1; | |
| buffer += 1; | |
| } | |
| return 0; | |
| } | |
| /* Send a special value in one or two bytes */ | |
| int cbor64_send_simple(base64_t *streamer, cbor64_simple_t value) | |
| { | |
| if ((uint8_t)value < CBOR64_AI_SIMPLE_BYTE) { | |
| return cbor64_initial_byte(streamer, CBOR64_MT_SIMPLE, value); | |
| } else { | |
| int err = cbor64_initial_byte(streamer, CBOR64_MT_SIMPLE, CBOR64_AI_SIMPLE_BYTE); | |
| if (err == 0) { | |
| err = base64_putbyte(streamer, value); | |
| } | |
| return err; | |
| } | |
| } | |
| static inline int send_endian_bytes(base64_t *streamer, unsigned char *bytes, size_t length) | |
| { | |
| for (unsigned int b = 0; b < length; b += 1) { | |
| #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ | |
| int err = base64_putbyte(streamer, bytes[length - (b + 1)]); | |
| #else | |
| int err = base64_putbyte(streamer, bytes[b]); | |
| #endif | |
| if (err != 0) { | |
| return err; | |
| } | |
| } | |
| return 0; | |
| } | |
| /* Send a single-precision float value */ | |
| int cbor64_float(base64_t *streamer, float number) | |
| { | |
| int err = cbor64_initial_byte(streamer, CBOR64_MT_FLOAT, CBOR64_AI_FLOAT32_T); | |
| if (err == 0) { | |
| err = send_endian_bytes(streamer, (void *)&number, 4); | |
| } | |
| return err; | |
| } | |
| /* Send a double-precision float value */ | |
| int cbor64_double(base64_t *streamer, double number) | |
| { | |
| int err = cbor64_initial_byte(streamer, CBOR64_MT_FLOAT, CBOR64_AI_FLOAT64_T); | |
| if (err == 0) { | |
| err = send_endian_bytes(streamer, (void *)&number, 8); | |
| } | |
| return err; | |
| } | |
| /* Find the index for a given string */ | |
| static size_t find_reference(cbor64_domain_t *domain, char *string) | |
| { | |
| size_t index = 0; | |
| while (domain->strings[index] != NULL) { | |
| if (string == domain->strings[index] || strcmp(string, domain->strings[index]) == 0) { | |
| /* Found matching string */ | |
| break; | |
| } | |
| index += 1; | |
| } | |
| return index; | |
| } | |
| /* | |
| * Emit a new string and update the array | |
| */ | |
| static int new_reference(base64_t *streamer, cbor64_domain_t *domain, size_t index) | |
| { | |
| /* | |
| * If the string reference would be no less than the raw string | |
| * encoding, don't actually track the string in the references. | |
| */ | |
| size_t length = strlen(domain->strings[index]); | |
| uint64_t next_ref = domain->emitted; | |
| bool is_referenced = true; | |
| if (next_ref < 24) { | |
| is_referenced = length >= 3; | |
| } else if (next_ref < BIT(8)) { | |
| is_referenced = length >= 4; | |
| } else if (next_ref < BIT(16)) { | |
| is_referenced = length >= 5; | |
| } else if (next_ref < LLBIT(32)) { | |
| is_referenced = length >= 7; | |
| } else { | |
| is_referenced = length >= 11; | |
| } | |
| if (is_referenced) { | |
| char *temp = domain->strings[next_ref]; | |
| domain->strings[next_ref] = domain->strings[index]; | |
| domain->strings[index] = temp; | |
| domain->emitted += 1; | |
| if (domain->shared_values) { | |
| return cbor64_tag(streamer, CBOR64_TAG_SHAREABLE); | |
| } else { | |
| return 0; | |
| } | |
| } else { | |
| return 0; | |
| } | |
| } | |
| typedef struct { | |
| bool emit_literal; | |
| int error; | |
| } emit_reference_ret_t; | |
| /* | |
| * Emit a reference to a string | |
| */ | |
| static emit_reference_ret_t emit_reference(base64_t *streamer, cbor64_domain_t *domain, char *string) | |
| { | |
| size_t index = find_reference(domain, string); | |
| emit_reference_ret_t ret = { | |
| .emit_literal = false, | |
| .error = 0, | |
| }; | |
| if (domain->strings[index] == NULL) { | |
| /* String not in index, just emit it its own namespace */ | |
| if (!domain->shared_values) { | |
| ret.error = cbor64_tag(streamer, CBOR64_TAG_STRING_REF_DOMAIN); | |
| } | |
| ret.emit_literal = true; | |
| } else if (index < domain->emitted) { | |
| /* String already emitted, emit reference */ | |
| if (domain->shared_values) { | |
| ret.error = cbor64_tag(streamer, CBOR64_TAG_SHARED_VALUE); | |
| } else { | |
| ret.error = cbor64_tag(streamer, CBOR64_TAG_STRING_REF); | |
| } | |
| if (ret.error == 0) { | |
| ret.error = cbor64_int(streamer, index); | |
| } | |
| } else { | |
| /* Create a new reference */ | |
| ret.error = new_reference(streamer, domain, index); | |
| ret.emit_literal = true; | |
| } | |
| return ret; | |
| } | |
| /* | |
| * Emit a string reference | |
| */ | |
| int cbor64_string_ref(base64_t *streamer, cbor64_domain_t *domain, char *string) | |
| { | |
| emit_reference_ret_t ret = emit_reference(streamer, domain, string); | |
| if (ret.error == 0 && ret.emit_literal) { | |
| ret.error = cbor64_string(streamer, string); | |
| } | |
| return ret.error; | |
| } | |
| /* | |
| * Emit a utf8 reference | |
| */ | |
| int cbor64_utf8_ref(base64_t *streamer, cbor64_domain_t *domain, char *string) | |
| { | |
| emit_reference_ret_t ret = emit_reference(streamer, domain, string); | |
| if (ret.error == 0 && ret.emit_literal) { | |
| ret.error = cbor64_utf8(streamer, string); | |
| } | |
| return ret.error; | |
| } |