ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
redis_resp.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file redis_resp.h
6 * @brief RESP, the Redis serialization protocol: the command encoder and the reply parser
7 * (PROTOCORE_ENABLE_REDIS).
8 *
9 * The governing specification is not an IETF document and carries no RFC number. It is the Redis
10 * project's "Redis serialization protocol specification"
11 * (https://redis.io/docs/latest/develop/reference/protocol-spec), whose RESP3 half is also
12 * specified in redis/redis-specifications, protocol/RESP3.md. Section names below are that
13 * document's headings.
14 *
15 * "Sending commands to a Redis server": a client sends the server "an array consisting of only bulk
16 * strings", the first bulk string being the command's name and the rest its arguments, so
17 * `LLEN mylist` goes out as `*2\r\n$4\r\nLLEN\r\n$6\r\nmylist\r\n`. An encode builds that array.
18 *
19 * "RESP protocol description" gives the first byte of every type. RESP2 (Redis 2.0): Simple strings
20 * `+`, Simple errors `-`, Integers `:`, Bulk strings `$` with Null bulk strings `$-1\r\n`, and
21 * Arrays `*` with Null arrays `*-1\r\n`. RESP3 (Redis 6.0, negotiated by the HELLO command of
22 * "Client handshake") adds Nulls `_`, Booleans `#`, Doubles `,`, Big numbers `(`, Bulk errors `!`,
23 * Verbatim strings `=`, Maps `%`, Sets `~` and Pushes `>`. Any other first byte, the Attributes
24 * type `|` included, fails the parse.
25 *
26 * The parser is a cursor. One call decodes the value at the head of the buffered octets and reports
27 * how many octets that value occupied. An aggregate header (Arrays, Sets, Pushes, Maps) reports the
28 * header alone and the count of children that follow, and the caller parses each child from the
29 * remaining octets: a map of N entries reports 2*N children, one per key and one per value. Nothing
30 * recurses and nothing is allocated.
31 *
32 * A decoded string points into the buffer that was parsed and is never copied, so a caller walking
33 * an aggregate takes what it needs from @ref RespNs::reply before the next parse overwrites it.
34 *
35 * The module exports one symbol, @ref Resp. Everything in redis_resp.c has internal linkage.
36 *
37 * @author Douglas Quigg (dstroy0)
38 * @date 2026
39 */
40
41#ifndef PROTOCORE_REDIS_RESP_H
42#define PROTOCORE_REDIS_RESP_H
43
44#include "protocore_config.h" // the entry point: protocore_types.h for the widths
45
46#if PROTOCORE_ENABLE_REDIS
47
49
50/** @brief The decoded type, named after the "RESP protocol description" table of first bytes. */
51typedef enum PROTO_ENUM_PACKED
52{
53 RESP_SIMPLE_STRING, ///< Simple strings (+); the text in str/str_len
54 RESP_SIMPLE_ERROR, ///< Simple errors (-); the message in str/str_len
55 RESP_INTEGER, ///< Integers (:); the signed 64-bit value in ival
56 RESP_BULK_STRING, ///< Bulk strings ($); the octets in str/str_len
57 RESP_ARRAY, ///< Arrays (*); the element count in count
58 RESP_NULL, ///< Null bulk strings ($-1), Null arrays (*-1), and RESP3 Nulls (_)
59 RESP_BOOLEAN, ///< Booleans (#); 0 or 1 in ival
60 RESP_DOUBLE, ///< Doubles (,); the text in str/str_len and its value in dval
61 RESP_BIG_NUMBER, ///< Big numbers ((); the digits in str/str_len
62 RESP_BULK_ERROR, ///< Bulk errors (!); the message in str/str_len
63 RESP_VERBATIM_STRING, ///< Verbatim strings (=); str holds the 3-octet encoding, ':' and the data
64 RESP_MAP, ///< Maps (%); count = 2 * entries
65 RESP_SET, ///< Sets (~); the element count in count
66 RESP_PUSH, ///< Pushes (>); the element count in count
67} RespType;
68
69/** @brief One decoded value. Every string member points into the parsed octets; nothing is copied. */
70typedef struct
71{
72 RespType type; ///< which type the first byte named
73 int64_t ival; ///< the Integers value, 0 or 1 for Booleans, the child count for an aggregate
74 double dval; ///< the Doubles value, decoded from str, which stays authoritative
75 const char *str; ///< the octets of a string, an error, a big number, a verbatim string or a double
76 size_t str_len; ///< how many octets @ref RespReply::str holds
77 int64_t count; ///< children following an Arrays, Sets, Pushes or Maps header (Maps = 2 * entries)
78} RespReply;
79
80/** @brief "Sending commands to a Redis server": the array of bulk strings a client sends. */
81typedef struct
82{
83 const char *const *argv; ///< the bulk strings, the command's name first, then its arguments
84 const size_t *argv_len; ///< per-string octet counts, or NULL to measure each NUL-terminated string
85 size_t argc; ///< how many bulk strings the array carries
86} RespCommandArgs;
87
88/** @brief Where an encoded command lands. */
89typedef struct
90{
91 char *buf; ///< the buffer an encode writes the command into
92 size_t cap; ///< how much room it has, the terminating NUL included
93} RespOutArgs;
94
95/** @brief The buffered reply octets a parse reads. */
96typedef struct
97{
98 const uint8_t *buf; ///< the head of the octets still to decode
99 size_t len; ///< how many of them are buffered
100} RespWireArgs;
101
102/**
103 * @brief The RESP codec: one command out, one value in.
104 *
105 * A caller sets the members a call takes, invokes it through ::Resp, and reads the outcome off the
106 * same handle.
107 *
108 * No slot member: the codec holds no rows, so no call names one.
109 *
110 * @var RespNs::command the array of bulk strings an encode builds ("Sending commands to a Redis server")
111 * @var RespNs::out the buffer that array is written into
112 * @var RespNs::wire the buffered reply octets a parse decodes from
113 * @var RespNs::ok a call's true/false outcome
114 * @var RespNs::n the octets an encode wrote excluding the NUL, or the octets a parse consumed, 0 on failure
115 * @var RespNs::reply the value a parse decoded
116 * @var RespNs::encode_command build `*<argc>\r\n$<len>\r\n<arg>\r\n...` from @c command into @c out
117 * @var RespNs::parse_reply decode the one value at the head of @c wire into @c reply
118 */
119typedef struct
120{
121 RespCommandArgs command; ///< what a client sends
122 RespOutArgs out; ///< where the encoded command lands
123 RespWireArgs wire; ///< what a parse reads
124 proto_bool ok;
125 size_t n;
126 RespReply reply;
127} RespVars;
128
129/** @brief The operands and the outcome. */
130extern RespVars RespV;
131
132/** @brief The entries. */
133typedef struct
134{
135 void (*const encode_command)(uint8_t *work);
136 void (*const parse_reply)(uint8_t *work);
137} RespNs;
138
139// What the table binds, defined once in the .c and taking one parameter each: everything
140// else an entry needs is an operand in RespV or a region of the borrow at a fixed offset.
141void protocore_resp_encode_command(uint8_t *work);
142void protocore_resp_parse_reply(uint8_t *work);
143
144// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
145// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
146// `Resp.encode_command(work)` resolves to a named function and becomes a DIRECT call. An extern table
147// leaves the call indirect and the symbol live at every level, -O2 -flto included.
148static const RespNs Resp __attribute__((unused)) = {
149 .encode_command = protocore_resp_encode_command,
150 .parse_reply = protocore_resp_parse_reply,
151};
152
154
155#endif // PROTOCORE_ENABLE_REDIS
156
157#endif // PROTOCORE_REDIS_RESP_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97