1/* 2 * Copyright 2020, Data61 3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO) 4 * ABN 41 687 119 230. 5 * 6 * This software may be distributed and modified according to the terms of 7 * the BSD 2-Clause license. Note that NO WARRANTY is provided. 8 * See "LICENSE_BSD2.txt" for details. 9 * 10 * @TAG(DATA61_BSD) 11 */ 12 13#include <utils/cbor64.h> 14 15/* Generate the initial byte indicating the type of the following data */ 16int cbor64_initial_byte(base64_t *streamer, cbor64_mt_t type, uint8_t data) 17{ 18 return base64_putbyte(streamer, (type << 5) | (data & MASK(5))); 19} 20 21/* Send a break byte to terminate indefinite-length item */ 22int cbor64_send_break(base64_t *streamer) 23{ 24 return cbor64_initial_byte(streamer, CBOR64_MT_BREAK, CBOR64_AI_INDEFINITE_LENGTH); 25} 26 27int cbor64_send_item(base64_t *streamer, cbor64_mt_t type, uint64_t number) 28{ 29 uint8_t additional_info; 30 size_t size; 31 32 if (number < CBOR64_AI_INT_LITERAL_MAX) { 33 /* Encode number in item byte */ 34 additional_info = number; 35 size = 0; 36 } else if (number < LLBIT(8)) { 37 /* Encode number as uint8_t */ 38 additional_info = CBOR64_AI_UINT8_T; 39 size = 8; 40 } else if (number < LLBIT(16)) { 41 /* Encode number as uint16_t */ 42 additional_info = CBOR64_AI_UINT16_T; 43 size = 16; 44 } else if (number < LLBIT(32)) { 45 /* Encode number as uint32_t */ 46 additional_info = CBOR64_AI_UINT32_T; 47 size = 32; 48 } else { 49 /* Encode number as uint64_t */ 50 additional_info = CBOR64_AI_UINT64_T; 51 size = 64; 52 } 53 54 int err = cbor64_initial_byte(streamer, type, additional_info); 55 if (err != 0) { 56 return err; 57 } 58 59 while (size > 0) { 60 size -= 8; 61 err = base64_putbyte(streamer, (number >> size) & MASK(8)); 62 if (err != 0) { 63 return err; 64 } 65 } 66 67 return err; 68} 69 70 71/* Send a bytearray */ 72int cbor64_send_typed_bytes(base64_t *streamer, cbor64_mt_t type, unsigned char *buffer, size_t length) 73{ 74 int err = cbor64_send_item(streamer, type, length); 75 if (err != 0) { 76 return err; 77 } 78 79 while (length > 0) { 80 err = base64_putbyte(streamer, *buffer); 81 if (err != 0) { 82 return err; 83 } 84 85 length -= 1; 86 buffer += 1; 87 } 88} 89 90/* Send a special value in one or two bytes */ 91int cbor64_send_simple(base64_t *streamer, cbor64_simple_t value) 92{ 93 if (value < CBOR64_AI_SIMPLE_BYTE) { 94 return cbor64_initial_byte(streamer, CBOR64_MT_SIMPLE, value); 95 } else { 96 int err = cbor64_initial_byte(streamer, CBOR64_MT_SIMPLE, CBOR64_AI_SIMPLE_BYTE); 97 if (err == 0) { 98 err = base64_putbyte(streamer, value); 99 } 100 return err; 101 } 102} 103 104static inline int send_endian_bytes(base64_t *streamer, unsigned char *bytes, size_t length) 105{ 106 for (int b = 0; b < length; b += 1) { 107#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 108 int err = base64_putbyte(streamer, bytes[length - (b + 1)]); 109#else 110 int err = base64_putbyte(streamer, bytes[b]); 111#endif 112 if (err != 0) { 113 return err; 114 } 115 } 116 117 return 0; 118} 119 120/* Send a single-precision float value */ 121int cbor64_float(base64_t *streamer, float number) 122{ 123 int err = cbor64_initial_byte(streamer, CBOR64_MT_FLOAT, CBOR64_AI_FLOAT32_T); 124 if (err == 0) { 125 err = send_endian_bytes(streamer, (void *)&number, 4); 126 } 127 return err; 128} 129 130/* Send a double-precision float value */ 131int cbor64_double(base64_t *streamer, double number) 132{ 133 int err = cbor64_initial_byte(streamer, CBOR64_MT_FLOAT, CBOR64_AI_FLOAT64_T); 134 if (err == 0) { 135 err = send_endian_bytes(streamer, (void *)&number, 8); 136 } 137 return err; 138} 139 140/* Find the index for a given string */ 141static size_t find_reference(cbor64_domain_t *domain, char *string) 142{ 143 size_t index = 0; 144 while (domain->strings[index] != NULL) { 145 if (string == domain->strings[index] || strcmp(string, domain->strings[index]) == 0) { 146 /* Found matching string */ 147 break; 148 } 149 index += 1; 150 } 151 152 return index; 153} 154 155/* 156 * Emit a new string and update the array 157 */ 158static int new_reference(base64_t *streamer, cbor64_domain_t *domain, size_t index) 159{ 160 /* 161 * If the string reference would be no less than the raw string 162 * encoding, don't actually track the string in the references. 163 */ 164 size_t length = strlen(domain->strings[index]); 165 size_t next_ref = domain->emitted; 166 167 bool is_referenced = true; 168 if (next_ref < 24) { 169 is_referenced = length >= 3; 170 } else if (next_ref < BIT(8)) { 171 is_referenced = length >= 4; 172 } else if (next_ref < BIT(16)) { 173 is_referenced = length >= 5; 174 } else if (next_ref < LLBIT(32)) { 175 is_referenced = length >= 7; 176 } else { 177 is_referenced = length >= 11; 178 } 179 180 if (is_referenced) { 181 char *temp = domain->strings[next_ref]; 182 domain->strings[next_ref] = domain->strings[index]; 183 domain->strings[index] = temp; 184 domain->emitted += 1; 185 186 if (domain->shared_values) { 187 return cbor64_tag(streamer, CBOR64_TAG_SHAREABLE); 188 } else { 189 return 0; 190 } 191 } else { 192 return 0; 193 } 194} 195 196typedef struct { 197 bool emit_literal; 198 int error; 199} emit_reference_ret_t; 200 201/* 202 * Emit a reference to a string 203 */ 204static emit_reference_ret_t emit_reference(base64_t *streamer, cbor64_domain_t *domain, char *string) 205{ 206 size_t index = find_reference(domain, string); 207 emit_reference_ret_t ret = { 208 .emit_literal = false, 209 .error = 0, 210 }; 211 212 if (domain->strings[index] == NULL) { 213 /* String not in index, just emit it its own namespace */ 214 if (!domain->shared_values) { 215 ret.error = cbor64_tag(streamer, CBOR64_TAG_STRING_REF_DOMAIN); 216 } 217 ret.emit_literal = true; 218 } else if (index < domain->emitted) { 219 /* String already emitted, emit reference */ 220 if (domain->shared_values) { 221 ret.error = cbor64_tag(streamer, CBOR64_TAG_SHARED_VALUE); 222 } else { 223 ret.error = cbor64_tag(streamer, CBOR64_TAG_STRING_REF); 224 } 225 if (ret.error == 0) { 226 ret.error = cbor64_int(streamer, index); 227 } 228 } else { 229 /* Create a new reference */ 230 ret.error = new_reference(streamer, domain, index); 231 ret.emit_literal = true; 232 } 233 234 return ret; 235} 236 237/* 238 * Emit a string reference 239 */ 240int cbor64_string_ref(base64_t *streamer, cbor64_domain_t *domain, char *string) 241{ 242 emit_reference_ret_t ret = emit_reference(streamer, domain, string); 243 if (ret.error == 0 && ret.emit_literal) { 244 ret.error = cbor64_string(streamer, string); 245 } 246 return ret.error; 247} 248 249/* 250 * Emit a utf8 reference 251 */ 252int cbor64_utf8_ref(base64_t *streamer, cbor64_domain_t *domain, char *string) 253{ 254 emit_reference_ret_t ret = emit_reference(streamer, domain, string); 255 if (ret.error == 0 && ret.emit_literal) { 256 ret.error = cbor64_utf8(streamer, string); 257 } 258 return ret.error; 259} 260