| /* |
| * 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; |
| } |