Skip to content

Commit 508c9e7

Browse files
authored
Fix: support for CBOR chunked strings (#236)
* add support for chunked strings ES will split the large string values in chunks (of ~4KiB), even though the entire set of chunks is contained within the answer (and thus needless). This commit: - introduces a new CBOR utility function that will fail to get string references, if the value is chunked; this is mostly useful for fetching object key names, which are part of the protocol and short; - will now allow the cursor string to be chunked, in which case it'll be allocated by the tinycbor library. * cbor: support row string values received chunked This commit adds support for reading row string values when these are spread over multiple chunks.
1 parent c6556af commit 508c9e7

File tree

8 files changed

+1508
-51
lines changed

8 files changed

+1508
-51
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ list(APPEND DRV_SRC ${CMAKE_BINARY_DIR}/cborparser.c)
342342
list(APPEND DRV_SRC ${TINYCBOR_PATH_SRC}/src/cborvalidation.c)
343343
list(APPEND DRV_SRC ${TINYCBOR_PATH_SRC}/src/cborerrorstrings.c)
344344
list(APPEND DRV_SRC ${TINYCBOR_PATH_SRC}/src/cborencoder.c)
345+
list(APPEND DRV_SRC ${TINYCBOR_PATH_SRC}/src/cborparser_dup_string.c)
345346
list(APPEND DRV_SRC
346347
${TINYCBOR_PATH_SRC}/src/cborencoder_close_container_checked.c)
347348
set(TINYCBOR_INC ${TINYCBOR_PATH_SRC}/src)

driver/connect.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1626,7 +1626,8 @@ static BOOL parse_es_version_cbor(esodbc_dbc_st *dbc, cstr_st *rsp_body,
16261626
}
16271627

16281628
/* fetch `version` value */
1629-
res = cbor_value_get_string_chunk(&iter_ver, &version->str, &version->cnt);
1629+
res = cbor_value_get_unchunked_string(&iter_ver, &version->str,
1630+
&version->cnt);
16301631
CHK_RES("failed to fetch " ESINFO_KEY_NUMBER " value");
16311632

16321633
/* Note: containers must be "left" (cbor_value_leave_container()) if ever

driver/handles.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,8 @@ typedef struct struct_desc {
314314
assert(DESC_TYPE_IS_IMPLEMENTATION(_rec->desc->type) && _rec->es_type)
315315

316316
struct resultset_cbor {
317-
cstr_st curs; /* ES'es cursor; refs req's body */
317+
cstr_st curs; /* ES'es cursor */
318+
BOOL curs_allocd; /* curs.str is allocated (and reassembled) */
318319
CborValue rows_obj; /* top object rows container (EsSQLRowCount()) */
319320
CborValue rows_iter; /* iterator over received rows; refs req's body */
320321
wstr_st cols_buff /* columns descriptions; refs allocated chunk */;

driver/queries.c

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,17 @@ void clear_resultset(esodbc_stmt_st *stmt, BOOL on_close)
179179
} else {
180180
assert(! stmt->rset.pack.cbor.cols_buff.str);
181181
}
182+
if (stmt->rset.pack.cbor.curs_allocd) {
183+
free(stmt->rset.pack.cbor.curs.str);
184+
stmt->rset.pack.cbor.curs.str = NULL;
185+
stmt->rset.pack.cbor.curs_allocd = false;
186+
} else if (stmt->rset.pack.cbor.curs.str) {
187+
/* the cursor is contained entirely in the received body */
188+
assert(stmt->rset.body.str < stmt->rset.pack.cbor.curs.str); // &&
189+
assert(stmt->rset.pack.cbor.curs.str +
190+
stmt->rset.pack.cbor.curs.cnt <
191+
stmt->rset.body.str + stmt->rset.body.cnt);
192+
}
182193
}
183194
memset(&stmt->rset, 0, sizeof(stmt->rset));
184195

@@ -521,7 +532,7 @@ static BOOL iterate_on_columns(esodbc_stmt_st *stmt, CborValue columns)
521532
ERRH(stmt, "invalid non-text element '" PACK_PARAM_COL_NAME "'.");
522533
return FALSE;
523534
}
524-
res = cbor_value_get_string_chunk(&name_obj, &name_cstr.str,
535+
res = cbor_value_get_unchunked_string(&name_obj, &name_cstr.str,
525536
&name_cstr.cnt);
526537
CHK_RES(stmt, "can't fetch value of '" PACK_PARAM_COL_NAME "' elem");
527538

@@ -553,7 +564,7 @@ static BOOL iterate_on_columns(esodbc_stmt_st *stmt, CborValue columns)
553564
ERRH(stmt, "invalid non-text element '" PACK_PARAM_COL_TYPE "'.");
554565
return FALSE;
555566
}
556-
res = cbor_value_get_string_chunk(&type_obj, &type_cstr.str,
567+
res = cbor_value_get_unchunked_string(&type_obj, &type_cstr.str,
557568
&type_cstr.cnt);
558569
CHK_RES(stmt, "can't fetch value of '" PACK_PARAM_COL_TYPE "' elem");
559570
/* U8MB_TO_U16WC fails with 0-len source */
@@ -727,9 +738,20 @@ static SQLRETURN attach_answer_cbor(esodbc_stmt_st *stmt)
727738
}
728739
/* should have been cleared by now */
729740
assert(! stmt->rset.pack.cbor.curs.cnt);
730-
res = cbor_value_get_string_chunk(&curs_obj,
741+
res = cbor_value_get_unchunked_string(&curs_obj,
731742
&stmt->rset.pack.cbor.curs.str,
732743
&stmt->rset.pack.cbor.curs.cnt);
744+
if (res == CborErrorUnknownLength) {
745+
assert(stmt->rset.pack.cbor.curs_allocd == false);
746+
/* cursor is in chunked string; get it assembled in one chunk */
747+
res = cbor_value_dup_text_string(&curs_obj,
748+
&stmt->rset.pack.cbor.curs.str,
749+
&stmt->rset.pack.cbor.curs.cnt,
750+
&curs_obj);
751+
if (res == CborNoError) {
752+
stmt->rset.pack.cbor.curs_allocd = true;
753+
}
754+
}
733755
CHK_RES(stmt, "failed to read '" PACK_PARAM_CURSOR "' value");
734756
DBGH(stmt, "new paginating cursor: [%zd] `" LCPDL "`.",
735757
stmt->rset.pack.cbor.curs.cnt, LWSTR(&stmt->rset.pack.cbor.curs));

driver/tinycbor.c

Lines changed: 78 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
* Note: these can't be freed per thread, since
1313
* DllMain(DLL_THREAD_ATTACH/DLL_THREAD_DETACH) is optional (and apps are
1414
* inconsistent even calling attach-detach for same thread). */
15-
static wchar_t **u16buffs = NULL;
16-
static size_t u16buff_cnt = 0;
17-
static esodbc_mutex_lt u16buff_mux = ESODBC_MUX_SINIT;
15+
static void **utf_buffs = NULL;
16+
static size_t utf_buff_cnt = 0;
17+
static esodbc_mutex_lt utf_buff_mux = ESODBC_MUX_SINIT;
1818

1919
/* advance an iterator of an "entered" JSON-sytle map to the value for the
2020
* given key, if that exists */
@@ -189,31 +189,30 @@ CborError cbor_container_is_empty(CborValue cont, BOOL *empty)
189189
return CborNoError;
190190
}
191191

192-
static BOOL enlist_utf16_buffer(wchar_t *old, wchar_t *new)
192+
static BOOL enlist_utf_buffer(void *old, void *new)
193193
{
194-
wchar_t **r;
194+
void **r;
195195
size_t i;
196196

197-
if (! old) {
198-
/* new entry must be inserted into list */
199-
ESODBC_MUX_LOCK(&u16buff_mux);
200-
r = realloc(u16buffs, (u16buff_cnt + 1) * sizeof(wchar_t *));
197+
if (! old) { /* new entry must be inserted into list */
198+
ESODBC_MUX_LOCK(&utf_buff_mux);
199+
r = realloc(utf_buffs, (utf_buff_cnt + 1) * sizeof(void *));
201200
if (r) {
202-
u16buffs = r;
203-
u16buffs[u16buff_cnt ++] = new;
201+
utf_buffs = r;
202+
utf_buffs[utf_buff_cnt ++] = new;
204203
}
205-
ESODBC_MUX_UNLOCK(&u16buff_mux);
206-
} else {
207-
ESODBC_MUX_LOCK(&u16buff_mux);
204+
ESODBC_MUX_UNLOCK(&utf_buff_mux);
205+
} else { /* old entry has be reallocated, store its updated ref */
206+
ESODBC_MUX_LOCK(&utf_buff_mux);
208207
r = NULL;
209-
for (i = 0; i < u16buff_cnt; i ++) {
210-
if (u16buffs[i] == old) {
211-
r = &u16buffs[i];
212-
u16buffs[i] = new;
208+
for (i = 0; i < utf_buff_cnt; i ++) {
209+
if (utf_buffs[i] == old) {
210+
r = &utf_buffs[i];
211+
utf_buffs[i] = new;
213212
break;
214213
}
215214
}
216-
ESODBC_MUX_UNLOCK(&u16buff_mux);
215+
ESODBC_MUX_UNLOCK(&utf_buff_mux);
217216
}
218217

219218
return !!r;
@@ -222,31 +221,82 @@ static BOOL enlist_utf16_buffer(wchar_t *old, wchar_t *new)
222221
void tinycbor_cleanup()
223222
{
224223
size_t i;
225-
for (i = 0; i < u16buff_cnt; i ++) {
226-
free(u16buffs[i]);
224+
for (i = 0; i < utf_buff_cnt; i ++) {
225+
free(utf_buffs[i]);
227226
}
228227
if (i) {
229-
free(u16buffs);
228+
free(utf_buffs);
230229
}
231230
}
232231

232+
static BOOL enlarge_buffer(void *str_ptr, size_t new_cnt, size_t usize)
233+
{
234+
wstr_st r; /* reallocated */
235+
wstr_st *wbuff = (wstr_st *)str_ptr;
236+
/* the two string struct types should remain identical (ptr, size_t),
237+
* for the above cast to work also for cstr_st inputs */
238+
assert(sizeof(cstr_st) == sizeof(wstr_st));
239+
assert((void *)wbuff->str == (void *)((cstr_st *)str_ptr)->str);
240+
assert(wbuff->cnt == ((cstr_st *)str_ptr)->cnt);
241+
242+
/* double scratchpad size until exceeding min needed space.
243+
* condition on equality, to allow for a 0-term */
244+
for (r.cnt = (0 < wbuff->cnt && wbuff->cnt < (size_t)-1) ? wbuff->cnt :
245+
ESODBC_BODY_BUF_START_SIZE; r.cnt <= new_cnt; r.cnt *= 2) {
246+
;
247+
}
248+
if (! (r.str = realloc(wbuff->str, r.cnt * usize))) {
249+
return false;
250+
}
251+
if (! enlist_utf_buffer(wbuff->str, r.str)) {
252+
/* it should only possibly fail on 1st allocation per-thread (since
253+
* the rest of invocations are to swap the pointers) */
254+
assert(! wbuff->str);
255+
free(r.str);
256+
return false;
257+
} else {
258+
*wbuff = r;
259+
}
260+
DBG("new UTF conv. buffer @0x%p, size %zu, usize: %zu.", wbuff->str,
261+
wbuff->cnt, usize);
262+
return true;
263+
}
264+
233265
/* Fetches and converts a(n always UTF8) text string to UTF16 wide char.
234266
* Uses a dynamically allocated thread-local buffer.
235267
* 0-terminates the string */
236268
CborError cbor_value_get_utf16_wstr(CborValue *it, wstr_st *utf16)
237269
{
270+
/* .cnt needs to be non-zero, for U8MB_TO_U16WC() to fail on 1st invoc. */
238271
static thread_local wstr_st wbuff = {.str = NULL, .cnt = (size_t)-1};
239-
wstr_st r; /* reallocated */
272+
static thread_local cstr_st cbuff = {0};
240273
cstr_st mb_str; /* multibyte string */
241274
CborError res;
242275
int n;
276+
size_t len;
243277

244278
assert(cbor_value_is_text_string(it));
245279
/* get the multibyte string to convert */
246-
res = cbor_value_get_string_chunk(it, &mb_str.str, &mb_str.cnt);
247-
if (res != CborNoError) {
248-
return res;
280+
if (cbor_value_is_length_known(it)) { /* str contained in single chunk? */
281+
res = cbor_value_get_string_chunk(it, &mb_str.str, &mb_str.cnt);
282+
if (res != CborNoError) {
283+
return res;
284+
}
285+
} else { /* string is spread across multiple chunks */
286+
res = cbor_value_calculate_string_length(it, &len);
287+
if (res != CborNoError) {
288+
return res;
289+
}
290+
if (cbuff.cnt <= len && !enlarge_buffer(&cbuff, len, sizeof(char))) {
291+
return CborErrorOutOfMemory;
292+
}
293+
mb_str = cbuff;
294+
res = cbor_value_copy_text_string(it, mb_str.str, &mb_str.cnt, it);
295+
if (res != CborNoError) {
296+
return res;
297+
}
249298
}
299+
250300
/* attempt string conversion */
251301
while ((n = U8MB_TO_U16WC(mb_str.str, mb_str.cnt, wbuff.str,
252302
wbuff.cnt)) <= 0) {
@@ -264,7 +314,6 @@ CborError cbor_value_get_utf16_wstr(CborValue *it, wstr_st *utf16)
264314
} /* else: buffer hasn't yet been allocated or is too small */
265315
/* what's the minimum space needed? */
266316
if ((n = U8MB_TO_U16WC(mb_str.str, mb_str.cnt, NULL, 0)) < 0) {
267-
TRACE;
268317
return CborErrorInvalidUtf8TextString;
269318
}
270319
} else {
@@ -273,28 +322,13 @@ CborError cbor_value_get_utf16_wstr(CborValue *it, wstr_st *utf16)
273322
break;
274323
}
275324
}
276-
/* double scratchpad size until exceeding min needed space.
277-
* condition on equality, to allow for a 0-term */
278-
for (r.cnt = wbuff.cnt < (size_t)-1 ? wbuff.cnt :
279-
ESODBC_BODY_BUF_START_SIZE; r.cnt <= (size_t)n; r.cnt *= 2) {
280-
;
281-
}
282-
if (! (r.str = realloc(wbuff.str, r.cnt))) {
325+
if (! enlarge_buffer(&wbuff, (size_t)n, sizeof(wchar_t))) {
283326
return CborErrorOutOfMemory;
284327
}
285-
if (! enlist_utf16_buffer(wbuff.str, r.str)) {
286-
/* it should only fail on 1st allocation per-thread */
287-
assert(! wbuff.str);
288-
free(r.str);
289-
return CborErrorOutOfMemory;
290-
} else {
291-
wbuff = r;
292-
}
293-
DBG("new UTF8/16 conv. buffer @0x%p, size %zu.", wbuff.str, wbuff.cnt);
294328
}
295329

296330
/* U8MB_TO_U16WC() will only convert the 0-term if counted in input*/
297-
wbuff.str[n] = '\0'; /* set, but not counted */
331+
wbuff.str[n] = L'\0'; /* set, but not counted */
298332
utf16->str = wbuff.str;
299333
utf16->cnt = n;
300334
return CborNoError;

driver/tinycbor.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,15 @@ void tinycbor_cleanup();
9595
CborError cbor_value_get_string_chunk(CborValue *it,
9696
const char **bufferptr, size_t *len);
9797

98+
static inline CborError cbor_value_get_unchunked_string(CborValue *it,
99+
const char **bufferptr, size_t *len)
100+
{
101+
return cbor_value_is_length_known(it)
102+
/* string is all in one chunk */
103+
? cbor_value_get_string_chunk(it, bufferptr, len)
104+
/* if the string is chunked, fail the call */
105+
: CborErrorUnknownLength;
106+
}
107+
108+
98109
#endif /* __TINYCBOR_H__ */

0 commit comments

Comments
 (0)