driver/tinycbor.c (304 lines of code) (raw):
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
#include "tinycbor.h"
#include "defs.h"
/* Vars for track keeping of thread-local UTF8-UTF16 conversion (buffers
* allocated by cbor_value_get_utf16_wstr()).
* Note: these can't be freed per thread, since
* DllMain(DLL_THREAD_ATTACH/DLL_THREAD_DETACH) is optional (and apps are
* inconsistent even calling attach-detach for same thread). */
static void **utf_buffs = NULL;
static size_t utf_buff_cnt = 0;
static esodbc_mutex_lt utf_buff_mux = ESODBC_MUX_SINIT;
/* advance an iterator of an "entered" JSON-sytle map to the value for the
* given key, if that exists */
CborError cbor_map_advance_to_key(CborValue *it, const char *key,
size_t key_len, CborValue *val)
{
CborError res;
const char *buffptr;
size_t bufflen;
while (! cbor_value_at_end(it)) {
/* skip all tags */
if ((res = cbor_value_skip_tag(it)) != CborNoError) {
return res;
}
/* if current key is a string, get its name */
if (cbor_value_is_text_string(it)) {
res = cbor_value_get_string_chunk(it, &buffptr, &bufflen);
if (res != CborNoError) {
return res;
}
/* this assumes an ASCII key (which is the case with ES' info, but
* not generally valid for CBOR/JSON-style maps) */
if (bufflen != key_len || strncasecmp(key, buffptr, key_len)) {
/* skip all tags */
if ((res = cbor_value_skip_tag(it)) != CborNoError) {
return res;
}
/* advance past param's value */
if ((res = cbor_value_advance(it)) != CborNoError) {
return res;
}
continue;
}
}
/* found it! is there anything following it? */
/* TODO: does this check the entire obj or just the container?? */
if (cbor_value_at_end(it)) {
return CborErrorTooFewItems;
}
*val = *it;
return CborNoError;
}
/* key not found */
val->type = CborInvalidType;
return CborNoError;
}
/* similar to cbor_value_leave_container(), but the iterator may find itself
* anywhere within the container (and not necessarily at the end of it). */
CborError cbor_value_exit_container(CborValue *cont, CborValue *it)
{
CborError res;
assert(cbor_value_is_container(cont));
while (! cbor_value_at_end(it)) {
if ((res = cbor_value_advance(it)) != CborNoError) {
return res;
}
}
return cbor_value_leave_container(cont, it);
}
/* Looks up a number of 'cnt' objects mapped to the 'keys' of given
* 'len[gth]s'. If a key is not found, the corresponding objects are marked
* with an invalid type. */
CborError cbor_map_lookup_keys(CborValue *map, size_t cnt,
const char **keys, const size_t *lens, CborValue **objs)
{
CborError res;
CborValue it;
const char *buffptr;
size_t bufflen;
size_t i, found;
assert(cbor_value_is_map(map));
if ((res = cbor_value_enter_container(map, &it)) != CborNoError) {
return res;
}
/* mark all out values invalid since only the found keys are going to be
* returned as valid */
for (i = 0; i < cnt; i ++) {
objs[i]->type = CborInvalidType;
}
found = 0;
while ((! cbor_value_at_end(&it)) && (found < cnt)) {
/* skip all tags */
if ((res = cbor_value_skip_tag(&it)) != CborNoError) {
return res;
}
/* is current key is a string, get its name */
if (cbor_value_is_text_string(&it)) {
res = cbor_value_get_string_chunk(&it, &buffptr, &bufflen);
if (res != CborNoError) {
return res;
}
// TODO: binary search on ordered keys?
for (i = 0; i < cnt; i ++) {
/* this assumes an ASCII key (which is the case with ES' info,
* but not generally valid for CBOR/JSON-like maps) */
if (bufflen == lens[i] &&
strncasecmp(keys[i], buffptr, lens[i]) == 0) {
*objs[i] = it;
found ++;
break;
}
}
}
/* skip all tags */
if ((res = cbor_value_skip_tag(&it)) != CborNoError) {
return res;
}
/* advance past param's value */
if ((res = cbor_value_advance(&it)) != CborNoError) {
return res;
}
}
return cbor_value_exit_container(map, &it);
}
CborError cbor_container_count(CborValue cont, size_t *count)
{
CborError res;
CborValue it;
size_t cnt = 0;
assert(cbor_value_is_container(&cont));
if ((res = cbor_value_enter_container(&cont, &it)) != CborNoError) {
return res;
}
while (! cbor_value_at_end(&it)) {
if (! cbor_value_is_tag(&it)) {
cnt ++;
}
if ((res = cbor_value_advance(&it)) != CborNoError) {
return res;
}
}
*count = cnt;
return CborNoError;
}
// XXX cbor_get_map_count() should also be useful
CborError cbor_get_array_count(CborValue arr, size_t *count)
{
assert(cbor_value_is_array(&arr));
return cbor_value_is_length_known(&arr) ?
cbor_value_get_array_length(&arr, count) :
cbor_container_count(arr, count);
}
CborError cbor_container_is_empty(CborValue cont, BOOL *empty)
{
CborError res;
CborValue it;
assert(cbor_value_is_container(&cont));
if ((res = cbor_value_enter_container(&cont, &it)) != CborNoError) {
return res;
}
/* skip all tags */
if ((res = cbor_value_skip_tag(&it)) != CborNoError) {
return res;
}
*empty = cbor_value_at_end(&it) || (! cbor_value_is_valid(&it));
return CborNoError;
}
CborError cbor_value_get_tagged_uint64(CborValue *it, uint64_t *val)
{
CborError res;
CborTag type;
cstr_st bstr; /* byte string */
size_t val_offt, i;
assert(cbor_value_is_tag(it));
if ((res = cbor_value_get_tag(it, &type)) != CborNoError) {
ERR("failed to extract tag.");
return res;
}
if (type != CborPositiveBignumTag) {
ERR("tag type `%llu` not a positive bignum.", type);
return CborErrorInappropriateTagForType;
}
if ((res = cbor_value_advance_fixed(it)) != CborNoError) {
ERR("failed to advance past tag header.");
return res;
}
if (! cbor_value_is_byte_string(it)) {
ERR("tag content type `%d` not byte string.", cbor_value_get_type(it));
return CborErrorImproperValue;
}
/* only expecting/supporting single-chunk bignums */
if (! cbor_value_is_length_known(it)) {
ERR("byte string length is unknown.");
return CborErrorUnknownLength;
}
res = cbor_value_get_string_chunk(it, &bstr.str, &bstr.cnt);
if (res != CborNoError) {
ERR("failed to extract byte string chunk reference.");
return res;
}
/* Jackson will encode values in range [1<<63, 1<<64-1] on 9 bytes ->
* allow arbitrary number of leading 0s. */
i = 0;
if (sizeof(*val) < bstr.cnt) {
for (; sizeof(*val) < bstr.cnt - i; i ++) {
if (bstr.str[i] != 0) {
ERR("non-zero byte at offset %zu: 0x%x. bignum value exceeds "
"uint64_t range.", i, bstr.str[i]);
return CborErrorImproperValue;
}
}
val_offt = 0;
} else {
val_offt = sizeof(*val) - bstr.cnt;
}
*val = 0LLU;
memcpy((uint8_t *)val + val_offt, bstr.str + i, bstr.cnt - i);
#if REG_DWORD == REG_DWORD_LITTLE_ENDIAN
*val = _byteswap_uint64(*val);
#endif /* LE */
return CborNoError;
}
static BOOL enlist_utf_buffer(void *old, void *new)
{
void **r;
size_t i;
if (! old) { /* new entry must be inserted into list */
ESODBC_MUX_LOCK(&utf_buff_mux);
r = realloc(utf_buffs, (utf_buff_cnt + 1) * sizeof(void *));
if (r) {
utf_buffs = r;
utf_buffs[utf_buff_cnt ++] = new;
}
ESODBC_MUX_UNLOCK(&utf_buff_mux);
} else { /* old entry has be reallocated, store its updated ref */
ESODBC_MUX_LOCK(&utf_buff_mux);
r = NULL;
for (i = 0; i < utf_buff_cnt; i ++) {
if (utf_buffs[i] == old) {
r = &utf_buffs[i];
utf_buffs[i] = new;
break;
}
}
ESODBC_MUX_UNLOCK(&utf_buff_mux);
}
return !!r;
}
void tinycbor_cleanup()
{
size_t i;
for (i = 0; i < utf_buff_cnt; i ++) {
free(utf_buffs[i]);
}
if (i) {
free(utf_buffs);
}
}
static BOOL enlarge_buffer(void *str_ptr, size_t new_cnt, size_t usize)
{
wstr_st r; /* reallocated */
wstr_st *wbuff = (wstr_st *)str_ptr;
/* the two string struct types should remain identical (ptr, size_t),
* for the above cast to work also for cstr_st inputs */
assert(sizeof(cstr_st) == sizeof(wstr_st));
assert((void *)wbuff->str == (void *)((cstr_st *)str_ptr)->str);
assert(wbuff->cnt == ((cstr_st *)str_ptr)->cnt);
/* double scratchpad size until exceeding min needed space.
* condition on equality, to allow for a 0-term */
for (r.cnt = (0 < wbuff->cnt && wbuff->cnt < (size_t)-1) ? wbuff->cnt :
ESODBC_BODY_BUF_START_SIZE; r.cnt <= new_cnt; r.cnt *= 2) {
;
}
if (! (r.str = realloc(wbuff->str, r.cnt * usize))) {
return false;
}
if (! enlist_utf_buffer(wbuff->str, r.str)) {
/* it should only possibly fail on 1st allocation per-thread (since
* the rest of invocations are to swap the pointers) */
assert(! wbuff->str);
free(r.str);
return false;
} else {
*wbuff = r;
}
DBG("new UTF conv. buffer @0x%p, size %zu, usize: %zu.", wbuff->str,
wbuff->cnt, usize);
return true;
}
/* Fetches and converts a(n always UTF8) text string to UTF16 wide char.
* Uses a dynamically allocated thread-local buffer.
* 0-terminates the string */
CborError cbor_value_get_utf16_wstr(CborValue *it, wstr_st *utf16)
{
/* .cnt needs to be non-zero, for U8MB_TO_U16WC() to fail on 1st invoc. */
static thread_local wstr_st wbuff = {.str = NULL, .cnt = (size_t)-1};
static thread_local cstr_st cbuff = {0};
cstr_st mb_str; /* multibyte string */
CborError res;
int n;
size_t len;
assert(cbor_value_is_text_string(it));
/* get the multibyte string to convert */
if (cbor_value_is_length_known(it)) { /* str contained in single chunk? */
res = cbor_value_get_string_chunk(it, &mb_str.str, &mb_str.cnt);
if (res != CborNoError) {
return res;
}
} else { /* string is spread across multiple chunks */
res = cbor_value_calculate_string_length(it, &len);
if (res != CborNoError) {
return res;
}
if (cbuff.cnt <= len && !enlarge_buffer(&cbuff, len, sizeof(char))) {
return CborErrorOutOfMemory;
}
mb_str = cbuff;
res = cbor_value_copy_text_string(it, mb_str.str, &mb_str.cnt, it);
if (res != CborNoError) {
return res;
}
}
/* attempt string conversion */
while ((n = U8MB_TO_U16WC(mb_str.str, mb_str.cnt, wbuff.str,
wbuff.cnt)) <= 0) {
/* U8MB_TO_U16WC will return error (though not set it with
* SetLastError()) for empty source strings */
if (mb_str.cnt) {
/* is this a non-buffer related error? (like decoding) */
if ((! WAPI_ERR_EBUFF()) && wbuff.str) {
ERR("mb_str.str @ 0x%p, mb_str.cnt = %zu, wbuff.str @ 0x%p, "
"wbuff.cnt = %zu", mb_str.str, mb_str.cnt, wbuff.str,
wbuff.cnt);
ERR("WAPI_ERRNO=0x%x.", WAPI_ERRNO());
ERR("MB: [%zu] `" LCPDL "`.", mb_str.cnt, LCSTR(&mb_str));
return CborErrorInvalidUtf8TextString;
} /* else: buffer hasn't yet been allocated or is too small */
/* what's the minimum space needed? */
if ((n = U8MB_TO_U16WC(mb_str.str, mb_str.cnt, NULL, 0)) < 0) {
return CborErrorInvalidUtf8TextString;
}
} else {
n = 0; /* \0 */
if (wbuff.str) { /* has it been already allocated? */
break;
}
}
if (! enlarge_buffer(&wbuff, (size_t)n, sizeof(wchar_t))) {
return CborErrorOutOfMemory;
}
}
/* U8MB_TO_U16WC() will only convert the 0-term if counted in input*/
wbuff.str[n] = L'\0'; /* set, but not counted */
utf16->str = wbuff.str;
utf16->cnt = n;
return CborNoError;
}
/* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */