#RESP3 Parser
#define FIO_RESP3
#include "fio-stl.h"A streaming, callback-based RESP3 parser for Redis 6+ and compatible servers. Implemented in ./004 resp3.h. Depends on FIO_ATOL, included automatically.
The parser pushes values through a small internal stack, calling your callbacks for primitives, containers, and streaming strings. It does not allocate on its own.
#Configuration Macros
#FIO_RESP3_MAX_NESTING
#ifndef FIO_RESP3_MAX_NESTING
#define FIO_RESP3_MAX_NESTING 32
#endifMaximum nesting depth. Valid range is 2 to 32,768. Determines the size of fio_resp3_parser_s.stack.
#FIO_RESP3_STREAM_THRESHOLD
#ifndef FIO_RESP3_STREAM_THRESHOLD
#define FIO_RESP3_STREAM_THRESHOLD 4096
#endifFixed-length blob strings ($<len>, !<len>, =<len>) larger than this threshold are streamed incrementally when the callback table provides the streaming string callbacks (on_start_string, on_string_write, on_string_done): the parser starts the string as soon as its header is parsed and feeds data in chunks as it arrives (result.consumed advances per chunk), instead of waiting for the whole blob to be contiguous in the read window. This allows blobs larger than any consumer read buffer.
Blobs at or below the threshold keep the wait-for-contiguity behavior (a single zero-copy on_string_write, even across split reads). Callback tables without streaming callbacks are unaffected (they always wait for contiguity).
#RESP3 Type Constants
#define FIO_RESP3_SIMPLE_STR '+' /* +<string>\r\n */
#define FIO_RESP3_SIMPLE_ERR '-' /* -<string>\r\n */
#define FIO_RESP3_NUMBER ':' /* :<number>\r\n */
#define FIO_RESP3_NULL '_' /* _\r\n */
#define FIO_RESP3_DOUBLE ',' /* ,<double>\r\n */
#define FIO_RESP3_BOOL '#' /* #t\r\n or #f\r\n */
#define FIO_RESP3_BIGNUM '(' /* (<big number>\r\n */
#define FIO_RESP3_BLOB_STR '$' /* $<len>\r\n<bytes>\r\n */
#define FIO_RESP3_BLOB_ERR '!' /* !<len>\r\n<bytes>\r\n */
#define FIO_RESP3_VERBATIM '=' /* =<len>\r\n<type:><bytes>\r\n */
#define FIO_RESP3_ARRAY '*' /* *<count>\r\n... */
#define FIO_RESP3_MAP '%' /* %<count>\r\n... */
#define FIO_RESP3_SET '~' /* ~<count>\r\n... */
#define FIO_RESP3_PUSH '>' /* ><count>\r\n... */
#define FIO_RESP3_ATTR '|' /* |<count>\r\n... */
#define FIO_RESP3_STREAM_CHUNK ';' /* ;<len>\r\n<bytes>\r\n */
#define FIO_RESP3_STREAM_END '.' /* .\r\n */#Types
#fio_resp3_frame_s
typedef struct {
void *ctx;
void *key;
int64_t remaining;
uint8_t type;
uint8_t streaming;
uint8_t expecting_value;
uint8_t set_as_map;
} fio_resp3_frame_s;Internal stack frame for a nested container. You do not need to touch this directly, but it is exposed in fio_resp3_parser_s.
#fio_resp3_parser_s
typedef struct {
void *udata;
uint32_t depth;
uint8_t error;
uint8_t streaming_string;
uint8_t streaming_string_type;
uint8_t streaming_blob_crlf;
void *streaming_string_ctx;
int64_t streaming_remaining;
fio_resp3_frame_s stack[FIO_RESP3_MAX_NESTING];
} fio_resp3_parser_s;Parser state. Initialize with {.udata = my_context} before the first call and reuse it for continuation after partial parses.
Members:
udata- user data passed to all callbacksdepth- current nesting deptherror- set to non-zero after a protocol error; the parser will refuse further inputstreaming_string- non-zero while a streamed string ($?chunked, or a fixed-length blob aboveFIO_RESP3_STREAM_THRESHOLD) is in progressstreaming_string_type- type of the streaming string in progressstreaming_blob_crlf- trailing CRLF bytes pending for a streamed fixed-length blob (0...2)streaming_string_ctx- context returned byon_start_stringstreaming_remaining- data bytes remaining for a streamed fixed-length blob (0 when inactive or in$?chunked mode)stack- nested container frames
#fio_resp3_callbacks_s
typedef struct {
/* primitives */
void *(*on_null)(void *udata);
void *(*on_bool)(void *udata, int is_true);
void *(*on_number)(void *udata, int64_t num);
void *(*on_double)(void *udata, double num);
void *(*on_bignum)(void *udata, const void *data, size_t len);
void *(*on_string)(void *udata, const void *data, size_t len, uint8_t type);
void *(*on_error)(void *udata, const void *data, size_t len, uint8_t type);
/* containers */
void *(*on_array)(void *udata, void *parent_ctx, int64_t len);
void *(*on_map)(void *udata, void *parent_ctx, int64_t len);
void *(*on_set)(void *udata, void *parent_ctx, int64_t len);
void *(*on_push)(void *udata, void *parent_ctx, int64_t len);
void *(*on_attr)(void *udata, void *parent_ctx, int64_t len);
/* push into containers */
int (*array_push)(void *udata, void *ctx, void *value);
int (*map_push)(void *udata, void *ctx, void *key, void *value);
int (*set_push)(void *udata, void *ctx, void *value);
int (*push_push)(void *udata, void *ctx, void *value);
int (*attr_push)(void *udata, void *ctx, void *key, void *value);
/* finalize containers */
void *(*array_done)(void *udata, void *ctx);
void *(*map_done)(void *udata, void *ctx);
void *(*set_done)(void *udata, void *ctx);
void *(*push_done)(void *udata, void *ctx);
void *(*attr_done)(void *udata, void *ctx);
/* errors */
void (*free_unused)(void *udata, void *obj);
void *(*on_error_protocol)(void *udata);
/* streaming strings */
void *(*on_start_string)(void *udata, size_t len, uint8_t type);
int (*on_string_write)(void *udata, void *ctx, const void *data, size_t len);
void *(*on_string_done)(void *udata, void *ctx, uint8_t type);
} fio_resp3_callbacks_s;Callback table. Designed to be static const. Non-streaming callbacks left NULL are replaced with safe no-ops. Primitive no-ops return a non-NULL sentinel; container no-ops do the same; push no-ops return 0; done no-ops return the context unchanged. The streaming-string callbacks (on_start_string, on_string_write, and on_string_done) are not filled with no-op fallbacks.
If on_set, set_push, and set_done are all NULL but map callbacks are provided, the parser treats RESP3 Sets as Maps, calling map_push(ctx, value, value) and map_done.
#fio_resp3_result_s
typedef struct {
void *obj;
size_t consumed;
int err;
} fio_resp3_result_s;Parse result.
Members:
obj- top-level object, orNULLwhile incomplete / on errorconsumed- bytes consumed from the buffererr- non-zero if a protocol error occurred
#API Functions
#fio_resp3_parse
SFUNC fio_resp3_result_s fio_resp3_parse(fio_resp3_parser_s *parser,
const fio_resp3_callbacks_s *callbacks,
const void *buf,
size_t len);Parses as much RESP3 data as possible. State is preserved in parser, so you can call again with the remaining bytes after a partial read.
Parameters:
parser- parser statecallbacks- callback table (may beNULL; non-streaming callbacks become no-ops, while streaming-string callbacks remain unset)buf- input bufferlen- input length
Returns: parse result.
Note: streamed strings ($?) require on_start_string, on_string_write, and on_string_done to be usable together. If on_start_string is missing or returns NULL, the parser sets err because it cannot buffer an unknown-length value. Once on_start_string returns a context, the parser enters streaming-string mode and later calls on_string_write and on_string_done directly, so those callbacks must also be supplied. For fixed-length blob, blob-error, and verbatim strings, on_start_string is optional; if it is missing or returns NULL, the parser falls back to on_string or on_error. If it returns a context for a fixed-length string, on_string_write and on_string_done are also called directly. Additionally, fixed-length blobs larger than FIO_RESP3_STREAM_THRESHOLD are always streamed incrementally when on_start_string is available (multiple on_string_write calls across parse calls possible), while smaller blobs complete in a single write once contiguous.
Note: top-level attributes (|) are delivered via callbacks but do not become the returned obj; parsing continues for the following reply.
#Example
#define FIO_RESP3
#include "fio-stl.h"
static void *on_null(void *u) { (void)u; return (void *)1; }
static void *on_number(void *u, int64_t n) { (void)u; printf("num %lld\n", (long long)n); return (void *)1; }
static void *on_string(void *u, const void *d, size_t l, uint8_t t) {
(void)u; (void)t;
printf("str %.*s\n", (int)l, (const char *)d);
return (void *)1;
}
static void *on_array(void *u, void *c, int64_t l) { (void)u; (void)c; (void)l; return (void *)2; }
static int array_push(void *u, void *c, void *v) { (void)u; (void)c; (void)v; return 0; }
static void *array_done(void *u, void *c) { (void)u; return c; }
int main(void) {
static const fio_resp3_callbacks_s cb = {
.on_null = on_null, .on_number = on_number, .on_string = on_string,
.on_array = on_array, .array_push = array_push, .array_done = array_done,
};
const char *data = "*2\r\n:42\r\n$5\r\nhello\r\n";
fio_resp3_parser_s p = {0};
fio_resp3_result_s r = fio_resp3_parse(&p, &cb, data, strlen(data));
printf("consumed=%zu err=%d obj=%p\n", r.consumed, r.err, r.obj);
return 0;
}