blob: 8727f6708820e637f03681d32c91a97ed29a0330 [file] [log] [blame] [raw]
/*
Copyright: Boaz Segev, 2017
License: MIT
Feel free to copy, use and enjoy according to the license provided.
Copyright refers to the parser, not the protocol.
*/
#include "spnlock.inc" /* for atomic operations when reference counting. */
#include "resp.h"
#include <math.h>
#include <stdio.h>
#include <string.h>
/* *****************************************************************************
Error Reporting
***************************************************************************** */
#ifdef DEBUG
#define REPORT(...) fprintf(stderr, __VA_ARGS__);
#else
#define REPORT(...)
#endif
/* *****************************************************************************
The parser object
***************************************************************************** */
typedef struct resp_parser_s {
resp_object_s *obj;
size_t missing;
uint8_t *leftovers;
size_t llen;
unsigned has_first_object : 1;
unsigned exclude_pubsub : 1;
unsigned multiplex_pubsub : 1;
} resp_parser_s;
/* *****************************************************************************
Pub/Sub state and Extension Flags
***************************************************************************** */
/** Set the parsing mode to enable the experimental pub/sub duplexing. */
void resp_enable_duplex_pubsub(resp_parser_pt p) { p->multiplex_pubsub = 1; }
/* *****************************************************************************
Object management
+
Sometimes you just need a dirty stack.
***************************************************************************** */
typedef struct {
volatile uintptr_t ref;
resp_object_s *link;
} resp_objhead_s;
#define OBJHEAD(obj) (((resp_objhead_s *)(obj)) - 1)
static resp_object_s *resp_alloc_obj(enum resp_type_enum type, size_t length) {
resp_objhead_s *head = NULL;
switch (type) {
/* Fallthrough */
case RESP_ERR:
case RESP_STRING:
/* + 1 for NULL (safety) */
head = malloc(sizeof(*head) + sizeof(resp_string_s) + length + 1);
*head = (resp_objhead_s){.ref = 1};
resp_string_s *s = (void *)(head + 1);
*s = (resp_string_s){.type = type, .len = length};
return (void *)s;
break;
case RESP_NULL:
case RESP_OK:
head = malloc(sizeof(*head) + sizeof(resp_object_s));
*head = (resp_objhead_s){.ref = 1};
resp_object_s *ok = (void *)(head + 1);
*ok = (resp_object_s){.type = type};
return (void *)ok;
break;
case RESP_ARRAY:
case RESP_PUBSUB:
head = malloc(sizeof(*head) + sizeof(resp_array_s) +
(sizeof(resp_object_s *) * length));
*head = (resp_objhead_s){.ref = 1};
resp_array_s *ar = (void *)(head + 1);
*ar = (resp_array_s){.type = type, .len = length};
return (void *)ar;
break;
case RESP_NUMBER:
head = malloc(sizeof(*head) + sizeof(resp_number_s));
*head = (resp_objhead_s){.ref = 1};
resp_number_s *i = (void *)(head + 1);
*i = (resp_number_s){.type = RESP_NUMBER, .number = length};
return (void *)i;
break;
}
return NULL;
}
static int resp_dealloc_obj(resp_parser_pt p, resp_object_s *obj, void *arg) {
if (obj && !spn_sub(&OBJHEAD(obj)->ref, 1))
free(OBJHEAD(obj));
(void)arg;
(void)p;
return 0;
}
inline static resp_object_s *pop_obj(resp_object_s *obj) {
resp_object_s *ret = OBJHEAD(obj)->link;
if (ret) {
OBJHEAD(obj)->link = OBJHEAD(ret)->link;
OBJHEAD(ret)->link = NULL;
} else
OBJHEAD(obj)->link = NULL;
return ret;
}
inline static void push_obj(resp_object_s *dest, resp_object_s *obj) {
if (!dest)
return;
if (obj) {
OBJHEAD(obj)->link = OBJHEAD(dest)->link;
}
OBJHEAD(dest)->link = obj;
}
static int mock_obj_tks(resp_parser_pt p, resp_object_s *obj, void *arg) {
return 0;
(void)arg;
(void)p;
(void)obj;
}
/** Performs a task on each object. Protects from loop-backs. */
size_t resp_obj_each(resp_parser_pt p, resp_object_s *obj,
int (*task)(resp_parser_pt p, resp_object_s *obj,
void *arg),
void *arg) {
if (!obj)
return 0;
if (!task)
task = mock_obj_tks;
/* make sure the object isn't "dirty"*/
OBJHEAD(obj)->link = NULL;
/* a searchable store. */
resp_objhead_s performed[3] = {{.link = NULL}}; /* a memory lump. */
resp_object_s *past = (resp_object_s *)(performed + 1);
resp_object_s *next = (resp_object_s *)(performed + 2);
OBJHEAD(past)->link = NULL;
OBJHEAD(next)->link = NULL; /* a straight line */
push_obj(next, obj);
resp_object_s *err = NULL; /* might host an error object... if we need one. */
resp_object_s *tmp;
size_t count = 0;
obj = pop_obj(next);
while (obj) {
count++;
if (obj->type == RESP_ARRAY || obj->type == RESP_PUBSUB)
goto is_array;
perform:
/* in case the task is to free `obj`, "pop" the next object first */
tmp = obj;
obj = pop_obj(next);
if (task(p, tmp, arg) == -1)
break;
continue;
is_array:
/* test for loop-back. */
tmp = OBJHEAD(past)->link;
while (tmp) {
if (obj == tmp)
goto skip;
tmp = OBJHEAD(tmp)->link;
}
/* add current object to the loop back stack */
push_obj(past, obj);
/* Add all array items to the stack. */
resp_obj2arr(obj)->pos = resp_obj2arr(obj)->len;
while (resp_obj2arr(obj)->pos != 0) {
resp_obj2arr(obj)->pos--;
if (!resp_obj2arr(obj)
->array[resp_obj2arr(obj)->pos]) /* fill any holes...? */
resp_obj2arr(obj)->array[resp_obj2arr(obj)->pos] = resp_nil2obj();
push_obj(next, resp_obj2arr(obj)->array[resp_obj2arr(obj)->pos]);
}
goto perform;
skip:
if (task != resp_dealloc_obj) {
if (!err)
err =
resp_err2obj("ERR: infinit loopback detected. Can't process.", 46);
obj = err;
} else
obj = resp_err2obj("ERR: infinit loopback detected. Can't process.", 46);
goto perform;
}
/* cleaup */
if (task != resp_dealloc_obj) {
if (err)
resp_dealloc_obj(NULL, err, NULL);
while ((tmp = pop_obj(past)))
;
}
return count;
}
/* *****************************************************************************
Object API
***************************************************************************** */
/** Allocates an RESP NULL objcet. Remeber to free when done. */
resp_object_s *resp_nil2obj(void) { return resp_alloc_obj(RESP_NULL, 0); }
/** Allocates an RESP OK objcet. Remeber to free when done. */
resp_object_s *resp_OK2obj(void) { return resp_alloc_obj(RESP_OK, 0); }
/** Allocates an RESP Error objcet. Remeber to free when done. */
resp_object_s *resp_err2obj(const void *msg, size_t len) {
resp_string_s *o = (resp_string_s *)resp_alloc_obj(RESP_STRING, len);
memcpy(o->string, msg, len);
o->string[len] = 0;
return (void *)o;
}
/** Allocates an RESP Number objcet. Remeber to free when done. */
resp_object_s *resp_num2obj(uint64_t num) {
return resp_alloc_obj(RESP_NUMBER, num);
}
/** Allocates an RESP String objcet. Remeber to free when done. */
resp_object_s *resp_str2obj(const void *str, size_t len) {
resp_string_s *o = (resp_string_s *)resp_alloc_obj(RESP_STRING, len);
memcpy(o->string, str, len);
o->string[len] = 0;
return (void *)o;
}
/** Allocates an RESP Number objcet. Remeber to free when done. */
resp_object_s *resp_arr2obj(int argc, resp_object_s *argv[]) {
if (argc < 0)
return NULL;
resp_array_s *o = (resp_array_s *)resp_alloc_obj(RESP_ARRAY, argc);
if (argv) {
for (int i = 0; i < argc; i++) {
o->array[i] = (resp_object_s *)argv[i];
}
}
return (void *)o;
}
/** frees an object returned from the parser. */
void resp_free_object(resp_object_s *obj) {
resp_obj_each(NULL, obj, resp_dealloc_obj, NULL);
}
static int resp_dup_obj_task(resp_parser_pt p, resp_object_s *obj, void *arg) {
spn_add(&OBJHEAD(obj)->ref, 1);
return 0;
(void)p;
(void)arg;
}
/** Duplicates an object by increasing it's reference count. */
resp_object_s *resp_dup_object(resp_object_s *obj) {
resp_obj_each(NULL, obj, resp_dup_obj_task, NULL);
return obj;
}
/* *****************************************************************************
Formatter (RESP => Memory Buffer)
***************************************************************************** */
struct resp_format_s {
uint8_t *dest;
size_t *size;
size_t limit;
uintptr_t multiplex_pubsub;
int err;
};
static int resp_format_task(resp_parser_pt p, resp_object_s *obj, void *s_) {
struct resp_format_s *s = s_;
#define safe_write_eol() \
if ((*s->size += 2) <= s->limit) { \
*s->dest++ = '\r'; \
*s->dest++ = '\n'; \
}
#define safe_write1(data) \
if (++(*s->size) <= s->limit) { \
*s->dest++ = (data); \
}
#define safe_write2(data, len) \
do { \
*s->size += (len); \
if (*s->size <= s->limit) { \
memcpy(s->dest, (data), (len)); \
s->dest += (len); \
} \
} while (0)
#define safe_write_i(i) \
do { \
int64_t t2 = (i); \
if ((t2) < 0) { \
safe_write1('-'); \
t2 = (t2 * -1); \
} \
size_t len = t2 ? (((size_t)log10((double)(t2))) + 1) : 1; \
*s->size += (len); \
if (*s->size <= s->limit) { \
int64_t t1 = len; \
int64_t t3 = t2 / 10; \
while (t1--) { \
s->dest[t1] = '0' + (t2 - (t3 * 10)); \
t2 = t3; \
t3 = t3 / 10; \
} \
s->dest += len; \
} \
} while (0)
switch (obj->type) {
case RESP_ERR:
safe_write1('-');
safe_write2((resp_obj2str(obj)->string), (resp_obj2str(obj)->len));
safe_write_eol();
break;
case RESP_NULL:
safe_write2("$-1\r\n", (resp_obj2str(obj)->len));
break;
case RESP_OK:
safe_write2("+OK\r\n", 5);
break;
case RESP_ARRAY:
case RESP_PUBSUB:
safe_write1('*');
safe_write_i(resp_obj2arr(obj)->len);
safe_write_eol();
break;
case RESP_STRING:
safe_write1('$');
safe_write_i(s->multiplex_pubsub ? (resp_obj2str(obj)->len + 1)
: resp_obj2str(obj)->len);
safe_write_eol();
if (s->multiplex_pubsub) {
s->multiplex_pubsub = 0;
safe_write1('+');
}
safe_write2((resp_obj2str(obj)->string), (resp_obj2str(obj)->len));
safe_write_eol();
break;
case RESP_NUMBER:
safe_write1(':');
safe_write_i((resp_obj2num(obj)->number));
safe_write_eol();
break;
}
return 0;
(void)p;
}
#undef safe_write_eol
#undef safe_write1
#undef safe_write2
#undef safe_write_i
int resp_format(resp_parser_pt p, uint8_t *dest, size_t *size,
resp_object_s *obj) {
struct resp_format_s arg = {
.dest = dest,
.size = size,
.limit = *size,
.multiplex_pubsub =
(p && p->multiplex_pubsub && obj->type == RESP_ARRAY &&
((resp_array_s *)obj)->len &&
((resp_array_s *)obj)->array[0]->type == RESP_STRING &&
((((resp_string_s *)((resp_array_s *)obj)->array[0])->string[0] ==
'm') ||
(((resp_string_s *)((resp_array_s *)obj)->array[0])->string[0] ==
'M') ||
(((resp_string_s *)((resp_array_s *)obj)->array[0])->string[0] ==
'+')))};
*size = 0;
resp_obj_each(p, obj, resp_format_task, &arg);
if (*arg.size < arg.limit) {
*arg.dest = 0;
return 0;
}
if (*arg.size == arg.limit)
return 0;
return -1;
}
/* *****************************************************************************
Parser (Memory Buffer => RESP)
***************************************************************************** */
/** create the parser */
resp_parser_pt resp_parser_new(void) {
resp_parser_s *p = malloc(sizeof(*p));
*p = (resp_parser_s){.obj = NULL};
return p;
}
static inline void reset_parser(resp_parser_pt p) {
resp_object_s *tmp;
while ((tmp = p->obj)) {
p->obj = OBJHEAD(p->obj)->link;
resp_free_object(tmp);
}
if (p->leftovers)
free(p->leftovers);
if (p->obj)
resp_free_object(p->obj);
*p = (resp_parser_s){.obj = NULL, .multiplex_pubsub = p->multiplex_pubsub};
}
/** free the parser and it's resources. */
void resp_parser_destroy(resp_parser_pt p) {
reset_parser(p);
free(p);
}
void resp_parser_clear(resp_parser_pt p) {
reset_parser(p);
*p = (resp_parser_s){.obj = NULL};
}
/**
* Feed the parser with data.
*
* Returns any fully parsed object / reply (often an array, but not always) or
* NULL (needs more data / error).
*
* If an error occurs, the `pos` pointer is set to -1, otherwise it's updated
* with the amount of data consumed.
*
* Partial consumption is possible when multiple replys were available in the
* buffer. Otherwise the parser will consume the whole of the buffer.
*
*/
resp_object_s *resp_parser_feed(resp_parser_pt p, uint8_t *buf, size_t *len) {
resp_object_s *tmp;
uint8_t *pos = buf;
uint8_t *eol = NULL;
uint8_t *end = pos + (*len);
int64_t num;
uint8_t flag;
restart:
// while (pos < end && (*pos == '\r' || *pos == '\n'))
// pos++; /* consume empty EOL markers. */
if (p->missing && p->obj->type == RESP_STRING) {
/* test for pub/sub duplexing extension */
if (!p->has_first_object && p->multiplex_pubsub && pos[0] == '+' &&
(pos[1] == '+' || pos[1] == 'm' || pos[1] == 'M')) {
pos++;
p->exclude_pubsub = 1;
p->missing--;
}
p->has_first_object = 1;
/* just fill the string with missing bytes */
if (p->missing > (size_t)(end - pos)) {
memcpy(resp_obj2str(p->obj)->string + resp_obj2str(p->obj)->len -
p->missing,
pos, (size_t)(end - pos));
p->missing -= (size_t)(end - pos);
pos = end;
goto finish;
} else {
memcpy(resp_obj2str(p->obj)->string + resp_obj2str(p->obj)->len -
p->missing,
pos, p->missing);
resp_obj2str(p->obj)->string[resp_obj2str(p->obj)->len] = 0; /* set NUL */
pos += p->missing + 2; /* eat the EOL */
/* set state to equal a freash, complete object */
p->missing = 0;
tmp = p->obj;
p->obj = OBJHEAD(p->obj)->link;
goto review;
}
}
/* seek EOL */
eol = pos;
while (eol < end - 1) {
if (eol[0] == '\r' && eol[1] == '\n')
goto found_eol;
eol++;
}
/* no EOL, we can't parse. */
if (p->llen + (end - pos) >=
131072) { /* IMHO: simple objects are smaller than 128Kib. */
REPORT("ERROR: (RESP parser) single line object too long. "
"128Kib limit for simple strings, numbers exc'.\n");
eol = NULL;
goto error;
}
{
void *tmp = realloc(p->leftovers, p->llen + (end - pos));
if (!tmp) {
fprintf(stderr, "ERROR: (RESP parser) Couldn't allocate memory.\n");
goto error;
}
p->leftovers = (void *)tmp;
memcpy(p->leftovers + p->llen, pos, end - pos);
p->llen += (end - pos);
}
goto finish;
found_eol:
*eol = 0; /* mark with NUL */
num = eol - pos - 1;
/* route `pos` to p->leftovers, if data was fragmented. */
if (p->leftovers) {
void *tmp = realloc(p->leftovers, p->llen + num + 1);
if (!tmp) {
REPORT("ERROR: (RESP parser) Couldn't allocate memory.\n");
goto error;
}
p->leftovers = (void *)tmp;
memcpy(p->leftovers + p->llen, pos, num + 1); /* copy the NUL byte. */
p->llen += (end - pos);
pos = p->leftovers;
}
/* ** let's actually parse something ** put new objects in `tmp` ** */
switch (*pos++) {
case '*': /* Array */
num = 0;
while (*pos) {
if (*pos < '0' || *pos > '9')
goto error;
num = (num * 10) + (*pos - '0');
pos++;
}
tmp = resp_alloc_obj(RESP_ARRAY, num);
p->missing = num;
break;
case '$': /* String */
if (*pos == '-') {
if (pos[1] == '1') {
/* NULL Object */
tmp = resp_alloc_obj(RESP_NULL, -1);
p->missing = 0;
} else {
REPORT("ERROR: (RESP parser) Bulk String input error.\n");
goto error;
}
} else {
num = 0;
while (*pos) {
if (*pos < '0' || *pos > '9') {
REPORT("ERROR: (RESP parser) Bulk String length error.\n");
goto error;
}
num = (num * 10) + (*pos - '0');
pos++;
}
tmp = resp_alloc_obj(RESP_STRING, num);
p->missing = num;
if (!num) {
*eol = '\r';
eol += 2;
}
}
break;
case '+': /* Simple String */
case '-': /* Error String */
p->has_first_object = 1;
if (num == 2 && pos[0] == 'O' && pos[1] == 'K') {
tmp = resp_alloc_obj(RESP_OK, 0);
} else {
tmp = resp_alloc_obj((pos[-1] == '+' ? RESP_STRING : RESP_ERR), num);
memcpy(((resp_string_s *)tmp)->string, pos, num + 1); /* copy NUL */
}
p->missing = 0;
break;
case ':': /* number */
p->has_first_object = 1;
num = 0;
flag = 0;
if (*pos == '-') {
flag = 1;
pos++;
}
while (*pos) {
if (*pos < '0' || *pos > '9') {
REPORT("ERROR: (RESP parser) input error.\n");
goto error;
}
num = (num * 10) + (*pos - '0');
pos++;
}
if (flag)
num = num * -1;
tmp = resp_alloc_obj(RESP_NUMBER, num);
p->missing = 0;
break;
default:
REPORT("ERROR: (RESP Parser) input prefix unknown\n");
goto error;
}
/* replace parsing marker */
pos = eol + 2;
/* un-effect the EOL */
*eol = '\r';
eol = NULL;
/* clear the buffer used to handle fragmented transmissions. */
if (p->leftovers) {
free(p->leftovers);
p->leftovers = NULL;
p->llen = 0;
}
review:
/* handle object rotation and nesting */
if (p->missing) {
/* tmp missing data: link and step into new object (nesting objects) */
OBJHEAD(tmp)->link = p->obj;
p->obj = tmp;
tmp = NULL;
goto restart;
} else if (p->obj) {
if (p->obj->type != RESP_ARRAY) {
/* Nesting of objects can only be performed by RESP_ARRAY objects. */
fprintf(stderr, "ERROR: (RESP Parser) internal error - "
"objects can only be nested within arrays.\n");
goto error;
}
resp_obj2arr(p->obj)->array[resp_obj2arr(p->obj)->pos++] = tmp;
p->missing = resp_obj2arr(p->obj)->len - resp_obj2arr(p->obj)->pos;
if (p->missing)
goto restart; /* collect more objects. */
/* un-nest */
tmp = p->obj;
p->obj = OBJHEAD(p->obj)->link;
goto review;
}
/* tmp now holds the top-most object, it's missing no data, we're done */
/* test for pub/sub semantics */
if (!p->exclude_pubsub) {
if (tmp->type == RESP_ARRAY && resp_obj2arr(tmp)->len == 3 &&
resp_obj2arr(tmp)->array[0]->type == RESP_STRING &&
resp_obj2arr(tmp)->array[1]->type == RESP_STRING &&
resp_obj2arr(tmp)->array[2]->type == RESP_STRING &&
resp_obj2str(resp_obj2arr(tmp)->array[0])->len == 7 &&
!memcmp("message", resp_obj2str(resp_obj2arr(tmp)->array[0])->string,
7)) {
/* PUB / SUB */
tmp->type = RESP_PUBSUB;
}
}
/* reset the parser, keeping it's state. */
reset_parser(p);
/* report how much the parser actually "ate" */
*len = pos - buf;
/* return the result. */
return tmp;
error:
reset_parser(p);
*len = 0;
if (eol)
*eol = '\r';
finish:
return NULL;
}
/* *****************************************************************************
Tests
***************************************************************************** */
#ifdef DEBUG
#include <inttypes.h>
#include <stdint.h>
void resp_test(void) {
uint8_t b_null[] = "$-1\r\n";
uint8_t b_ok[] = "+OK\r\n";
uint8_t b_num[] = ":13\r\n";
uint8_t b_neg_num[] = ":-13\r\n";
uint8_t b_err[] = "-ERR: or not :-)\r\n";
uint8_t b_str[] = "$19\r\nthis is a string :)\r\n";
uint8_t b_longer[] = "*8\r\n"
":1\r\n"
":2\r\n"
":3\r\n"
":4\r\n"
":-5794\r\n"
"$6\r\n"
"foobar\r\n"
"$6\r\n"
"barfoo\r\n"
":4\r\n";
resp_parser_pt parser = resp_parser_new();
size_t len;
resp_object_s *obj;
fprintf(stderr, "* OBJHEAD test %s\n",
(OBJHEAD(((resp_objhead_s *)NULL) + 1) == NULL) ? "passed"
: "FAILED");
{
obj = resp_OK2obj();
resp_object_s *tmp = resp_nil2obj();
push_obj(obj, tmp);
fprintf(stderr, "* Push/Pop test: %s\n",
(pop_obj(obj) == tmp && pop_obj(obj) == NULL) ? "ok." : "FAILED!");
resp_free_object(obj);
resp_free_object(tmp);
}
len = sizeof(b_null) - 1;
obj = resp_parser_feed(parser, b_null, &len);
if (obj && obj->type == RESP_NULL) {
fprintf(stderr, "* NULL recognized, pos == %lu / %lu\n", len,
sizeof(b_null) - 1);
resp_free_object(obj);
} else
fprintf(stderr, "* NULL FAILED\n");
len = sizeof(b_ok) - 1;
obj = resp_parser_feed(parser, b_ok, &len);
if (obj && obj->type == RESP_OK) {
fprintf(stderr, "* OK recognized\n");
resp_free_object(obj);
} else
fprintf(stderr, "* OK FAILED\n");
len = sizeof(b_err) - 1;
obj = resp_parser_feed(parser, b_err, &len);
if (obj && obj->type == RESP_ERR) {
fprintf(stderr, "* ERR / Simple String recognized: %s\n",
resp_obj2str(obj)->string);
resp_free_object(obj);
} else
fprintf(stderr, "* ERR / Simple String FAILED (type %d)\n",
obj ? obj->type : -1);
len = sizeof(b_num) - 1;
obj = resp_parser_feed(parser, b_num, &len);
if (obj && obj->type == RESP_NUMBER) {
fprintf(stderr, "* Number recognized: %lld\n", resp_obj2num(obj)->number);
resp_free_object(obj);
} else
fprintf(stderr, "* Number FAILED\n");
len = sizeof(b_neg_num) - 1;
obj = resp_parser_feed(parser, b_neg_num, &len);
if (obj && obj->type == RESP_NUMBER) {
fprintf(stderr, "* Negative Number recognized: %lld\n",
resp_obj2num(obj)->number);
resp_free_object(obj);
} else
fprintf(stderr, "* Negative Number FAILED\n");
len = sizeof(b_str) - 1;
obj = resp_parser_feed(parser, b_str, &len);
if (obj && obj->type == RESP_STRING) {
fprintf(stderr, "* String recognized: %s\n", resp_obj2str(obj)->string);
resp_free_object(obj);
} else
fprintf(stderr, "* String FAILED\n");
{
uint8_t empty_str[] = "$0\r\n\r\n";
len = sizeof(empty_str) - 1;
obj = resp_parser_feed(parser, empty_str, &len);
if (obj && obj->type == RESP_STRING) {
fprintf(stderr, "* Empty String recognized: %s\n",
resp_obj2str(obj)->string);
resp_free_object(obj);
} else
fprintf(stderr, "* Empty String FAILED\n");
}
len = sizeof(b_longer) - 1;
obj = resp_parser_feed(parser, b_longer, &len);
if (obj && obj->type == RESP_ARRAY) {
fprintf(stderr, "* Array head recognized: (%lu)\n", resp_obj2arr(obj)->len);
resp_object_s *tmp;
for (size_t i = 0; i < resp_obj2arr(obj)->len; i++) {
tmp = resp_obj2arr(obj)->array[i];
if (resp_obj2arr(tmp)) {
fprintf(stderr, "Item %lu is an .... array?!\n", i);
}
if (resp_obj2str(tmp)) {
fprintf(stderr, "Item %lu is a String %s\n", i,
resp_obj2str(tmp)->string);
}
if (resp_obj2num(tmp)) {
fprintf(stderr, "Item %lu is a Number %" PRIi64 "\n", i,
resp_obj2num(tmp)->number);
}
}
{
fprintf(stderr, "found %lu objects\n",
resp_obj_each(NULL, obj, NULL, NULL));
uint8_t buff[128] = {0};
size_t len = 127;
resp_format(NULL, buff, &len, obj);
fprintf(stderr,
"* In RESP format, it should take %lu bytes like so:\n%s\n", len,
buff);
}
resp_free_object(obj);
} else {
fprintf(stderr, "* Array FAILED (type == %d)\n", obj ? obj->type : -1);
}
{
// uint8_t buff[128] = {0};
// size_t len = 128;
// resp_object_s *arr1 = resp_arr2obj(1, NULL);
// resp_object_s *arr2 = resp_arr2obj(1, NULL);
// resp_obj2arr(arr1)->array[0] = resp_dup_object(arr2);
// resp_obj2arr(arr2)->array[0] = resp_dup_object(arr1);
// resp_format(NULL, buff, &len, arr1);
// fprintf(stderr, "* Loopback test:\n%s\n", buff);
// resp_free_object(arr1);
// resp_free_object(arr2);
}
resp_parser_destroy(parser);
return;
}
#endif