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