aboutsummaryrefslogtreecommitdiff
path: root/examples/redis-unstable/src/call_reply.c
diff options
context:
space:
mode:
Diffstat (limited to 'examples/redis-unstable/src/call_reply.c')
-rw-r--r--examples/redis-unstable/src/call_reply.c540
1 files changed, 540 insertions, 0 deletions
diff --git a/examples/redis-unstable/src/call_reply.c b/examples/redis-unstable/src/call_reply.c
new file mode 100644
index 0000000..2a4f710
--- /dev/null
+++ b/examples/redis-unstable/src/call_reply.c
@@ -0,0 +1,540 @@
1/*
2 * Copyright (c) 2009-Present, Redis Ltd.
3 * All rights reserved.
4 *
5 * Licensed under your choice of (a) the Redis Source Available License 2.0
6 * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the
7 * GNU Affero General Public License v3 (AGPLv3).
8 */
9
10#include "server.h"
11#include "call_reply.h"
12
13#define REPLY_FLAG_ROOT (1<<0)
14#define REPLY_FLAG_PARSED (1<<1)
15#define REPLY_FLAG_RESP3 (1<<2)
16
17/* --------------------------------------------------------
18 * An opaque struct used to parse a RESP protocol reply and
19 * represent it. Used when parsing replies such as in RM_Call
20 * or Lua scripts.
21 * -------------------------------------------------------- */
22struct CallReply {
23 void *private_data;
24 sds original_proto; /* Available only for root reply. */
25 const char *proto;
26 size_t proto_len;
27 int type; /* REPLY_... */
28 int flags; /* REPLY_FLAG... */
29 size_t len; /* Length of a string, or the number elements in an array. */
30 union {
31 const char *str; /* String pointer for string and error replies. This
32 * does not need to be freed, always points inside
33 * a reply->proto buffer of the reply object or, in
34 * case of array elements, of parent reply objects. */
35 struct {
36 const char *str;
37 const char *format;
38 } verbatim_str; /* Reply value for verbatim string */
39 long long ll; /* Reply value for integer reply. */
40 double d; /* Reply value for double reply. */
41 struct CallReply *array; /* Array of sub-reply elements. used for set, array, map, and attribute */
42 } val;
43 list *deferred_error_list; /* list of errors in sds form or NULL */
44 struct CallReply *attribute; /* attribute reply, NULL if not exists */
45};
46
47static void callReplySetSharedData(CallReply *rep, int type, const char *proto, size_t proto_len, int extra_flags) {
48 rep->type = type;
49 rep->proto = proto;
50 rep->proto_len = proto_len;
51 rep->flags |= extra_flags;
52}
53
54static void callReplyNull(void *ctx, const char *proto, size_t proto_len) {
55 CallReply *rep = ctx;
56 callReplySetSharedData(rep, REDISMODULE_REPLY_NULL, proto, proto_len, REPLY_FLAG_RESP3);
57}
58
59static void callReplyNullBulkString(void *ctx, const char *proto, size_t proto_len) {
60 CallReply *rep = ctx;
61 callReplySetSharedData(rep, REDISMODULE_REPLY_NULL, proto, proto_len, 0);
62}
63
64static void callReplyNullArray(void *ctx, const char *proto, size_t proto_len) {
65 CallReply *rep = ctx;
66 callReplySetSharedData(rep, REDISMODULE_REPLY_NULL, proto, proto_len, 0);
67}
68
69static void callReplyBulkString(void *ctx, const char *str, size_t len, const char *proto, size_t proto_len) {
70 CallReply *rep = ctx;
71 callReplySetSharedData(rep, REDISMODULE_REPLY_STRING, proto, proto_len, 0);
72 rep->len = len;
73 rep->val.str = str;
74}
75
76static void callReplyError(void *ctx, const char *str, size_t len, const char *proto, size_t proto_len) {
77 CallReply *rep = ctx;
78 callReplySetSharedData(rep, REDISMODULE_REPLY_ERROR, proto, proto_len, 0);
79 rep->len = len;
80 rep->val.str = str;
81}
82
83static void callReplySimpleStr(void *ctx, const char *str, size_t len, const char *proto, size_t proto_len) {
84 CallReply *rep = ctx;
85 callReplySetSharedData(rep, REDISMODULE_REPLY_STRING, proto, proto_len, 0);
86 rep->len = len;
87 rep->val.str = str;
88}
89
90static void callReplyLong(void *ctx, long long val, const char *proto, size_t proto_len) {
91 CallReply *rep = ctx;
92 callReplySetSharedData(rep, REDISMODULE_REPLY_INTEGER, proto, proto_len, 0);
93 rep->val.ll = val;
94}
95
96static void callReplyDouble(void *ctx, double val, const char *proto, size_t proto_len) {
97 CallReply *rep = ctx;
98 callReplySetSharedData(rep, REDISMODULE_REPLY_DOUBLE, proto, proto_len, REPLY_FLAG_RESP3);
99 rep->val.d = val;
100}
101
102static void callReplyVerbatimString(void *ctx, const char *format, const char *str, size_t len, const char *proto, size_t proto_len) {
103 CallReply *rep = ctx;
104 callReplySetSharedData(rep, REDISMODULE_REPLY_VERBATIM_STRING, proto, proto_len, REPLY_FLAG_RESP3);
105 rep->len = len;
106 rep->val.verbatim_str.str = str;
107 rep->val.verbatim_str.format = format;
108}
109
110static void callReplyBigNumber(void *ctx, const char *str, size_t len, const char *proto, size_t proto_len) {
111 CallReply *rep = ctx;
112 callReplySetSharedData(rep, REDISMODULE_REPLY_BIG_NUMBER, proto, proto_len, REPLY_FLAG_RESP3);
113 rep->len = len;
114 rep->val.str = str;
115}
116
117static void callReplyBool(void *ctx, int val, const char *proto, size_t proto_len) {
118 CallReply *rep = ctx;
119 callReplySetSharedData(rep, REDISMODULE_REPLY_BOOL, proto, proto_len, REPLY_FLAG_RESP3);
120 rep->val.ll = val;
121}
122
123static void callReplyParseCollection(ReplyParser *parser, CallReply *rep, size_t len, const char *proto, size_t elements_per_entry) {
124 rep->len = len;
125 rep->val.array = zcalloc(elements_per_entry * len * sizeof(CallReply));
126 for (size_t i = 0; i < len * elements_per_entry; i += elements_per_entry) {
127 for (size_t j = 0 ; j < elements_per_entry ; ++j) {
128 rep->val.array[i + j].private_data = rep->private_data;
129 parseReply(parser, rep->val.array + i + j);
130 rep->val.array[i + j].flags |= REPLY_FLAG_PARSED;
131 if (rep->val.array[i + j].flags & REPLY_FLAG_RESP3) {
132 /* If one of the sub-replies is RESP3, then the current reply is also RESP3. */
133 rep->flags |= REPLY_FLAG_RESP3;
134 }
135 }
136 }
137 rep->proto = proto;
138 rep->proto_len = parser->curr_location - proto;
139}
140
141static void callReplyAttribute(ReplyParser *parser, void *ctx, size_t len, const char *proto) {
142 CallReply *rep = ctx;
143 rep->attribute = zcalloc(sizeof(CallReply));
144
145 /* Continue parsing the attribute reply */
146 rep->attribute->len = len;
147 rep->attribute->type = REDISMODULE_REPLY_ATTRIBUTE;
148 callReplyParseCollection(parser, rep->attribute, len, proto, 2);
149 rep->attribute->flags |= REPLY_FLAG_PARSED | REPLY_FLAG_RESP3;
150 rep->attribute->private_data = rep->private_data;
151
152 /* Continue parsing the reply */
153 parseReply(parser, rep);
154
155 /* In this case we need to fix the proto address and len, it should start from the attribute */
156 rep->proto = proto;
157 rep->proto_len = parser->curr_location - proto;
158 rep->flags |= REPLY_FLAG_RESP3;
159}
160
161static void callReplyArray(ReplyParser *parser, void *ctx, size_t len, const char *proto) {
162 CallReply *rep = ctx;
163 rep->type = REDISMODULE_REPLY_ARRAY;
164 callReplyParseCollection(parser, rep, len, proto, 1);
165}
166
167static void callReplySet(ReplyParser *parser, void *ctx, size_t len, const char *proto) {
168 CallReply *rep = ctx;
169 rep->type = REDISMODULE_REPLY_SET;
170 callReplyParseCollection(parser, rep, len, proto, 1);
171 rep->flags |= REPLY_FLAG_RESP3;
172}
173
174static void callReplyMap(ReplyParser *parser, void *ctx, size_t len, const char *proto) {
175 CallReply *rep = ctx;
176 rep->type = REDISMODULE_REPLY_MAP;
177 callReplyParseCollection(parser, rep, len, proto, 2);
178 rep->flags |= REPLY_FLAG_RESP3;
179}
180
181static void callReplyParseError(void *ctx) {
182 CallReply *rep = ctx;
183 rep->type = REDISMODULE_REPLY_UNKNOWN;
184}
185
186/* Recursively free the current call reply and its sub-replies. */
187static void freeCallReplyInternal(CallReply *rep) {
188 if (rep->type == REDISMODULE_REPLY_ARRAY || rep->type == REDISMODULE_REPLY_SET) {
189 for (size_t i = 0 ; i < rep->len ; ++i) {
190 freeCallReplyInternal(rep->val.array + i);
191 }
192 zfree(rep->val.array);
193 }
194
195 if (rep->type == REDISMODULE_REPLY_MAP || rep->type == REDISMODULE_REPLY_ATTRIBUTE) {
196 for (size_t i = 0 ; i < rep->len ; ++i) {
197 freeCallReplyInternal(rep->val.array + i * 2);
198 freeCallReplyInternal(rep->val.array + i * 2 + 1);
199 }
200 zfree(rep->val.array);
201 }
202
203 if (rep->attribute) {
204 freeCallReplyInternal(rep->attribute);
205 zfree(rep->attribute);
206 }
207}
208
209/* Free the given call reply and its children (in case of nested reply) recursively.
210 * If private data was set when the CallReply was created it will not be freed, as it's
211 * the caller's responsibility to free it before calling freeCallReply(). */
212void freeCallReply(CallReply *rep) {
213 if (!(rep->flags & REPLY_FLAG_ROOT)) {
214 return;
215 }
216 if (rep->flags & REPLY_FLAG_PARSED) {
217 if (rep->type == REDISMODULE_REPLY_PROMISE) {
218 zfree(rep);
219 return;
220 }
221 freeCallReplyInternal(rep);
222 }
223 sdsfree(rep->original_proto);
224 if (rep->deferred_error_list)
225 listRelease(rep->deferred_error_list);
226 zfree(rep);
227}
228
229CallReply *callReplyCreatePromise(void *private_data) {
230 CallReply *res = zmalloc(sizeof(*res));
231 res->type = REDISMODULE_REPLY_PROMISE;
232 /* Mark the reply as parsed so there will be not attempt to parse
233 * it when calling reply API such as freeCallReply.
234 * Also mark the reply as root so freeCallReply will not ignore it. */
235 res->flags |= REPLY_FLAG_PARSED | REPLY_FLAG_ROOT;
236 res->private_data = private_data;
237 return res;
238}
239
240static const ReplyParserCallbacks DefaultParserCallbacks = {
241 .null_callback = callReplyNull,
242 .bulk_string_callback = callReplyBulkString,
243 .null_bulk_string_callback = callReplyNullBulkString,
244 .null_array_callback = callReplyNullArray,
245 .error_callback = callReplyError,
246 .simple_str_callback = callReplySimpleStr,
247 .long_callback = callReplyLong,
248 .array_callback = callReplyArray,
249 .set_callback = callReplySet,
250 .map_callback = callReplyMap,
251 .double_callback = callReplyDouble,
252 .bool_callback = callReplyBool,
253 .big_number_callback = callReplyBigNumber,
254 .verbatim_string_callback = callReplyVerbatimString,
255 .attribute_callback = callReplyAttribute,
256 .error = callReplyParseError,
257};
258
259/* Parse the buffer located in rep->original_proto and update the CallReply
260 * structure to represent its contents. */
261static void callReplyParse(CallReply *rep) {
262 if (rep->flags & REPLY_FLAG_PARSED) {
263 return;
264 }
265
266 ReplyParser parser = {.curr_location = rep->proto, .callbacks = DefaultParserCallbacks};
267
268 parseReply(&parser, rep);
269 rep->flags |= REPLY_FLAG_PARSED;
270}
271
272/* Return the call reply type (REDISMODULE_REPLY_...). */
273int callReplyType(CallReply *rep) {
274 if (!rep) return REDISMODULE_REPLY_UNKNOWN;
275 callReplyParse(rep);
276 return rep->type;
277}
278
279/* Return reply string as buffer and len. Applicable to:
280 * - REDISMODULE_REPLY_STRING
281 * - REDISMODULE_REPLY_ERROR
282 *
283 * The return value is borrowed from CallReply, so it must not be freed
284 * explicitly or used after CallReply itself is freed.
285 *
286 * The returned value is not NULL terminated and its length is returned by
287 * reference through len, which must not be NULL.
288 */
289const char *callReplyGetString(CallReply *rep, size_t *len) {
290 callReplyParse(rep);
291 if (rep->type != REDISMODULE_REPLY_STRING &&
292 rep->type != REDISMODULE_REPLY_ERROR) return NULL;
293 if (len) *len = rep->len;
294 return rep->val.str;
295}
296
297/* Return a long long reply value. Applicable to:
298 * - REDISMODULE_REPLY_INTEGER
299 */
300long long callReplyGetLongLong(CallReply *rep) {
301 callReplyParse(rep);
302 if (rep->type != REDISMODULE_REPLY_INTEGER) return LLONG_MIN;
303 return rep->val.ll;
304}
305
306/* Return a double reply value. Applicable to:
307 * - REDISMODULE_REPLY_DOUBLE
308 */
309double callReplyGetDouble(CallReply *rep) {
310 callReplyParse(rep);
311 if (rep->type != REDISMODULE_REPLY_DOUBLE) return LLONG_MIN;
312 return rep->val.d;
313}
314
315/* Return a reply Boolean value. Applicable to:
316 * - REDISMODULE_REPLY_BOOL
317 */
318int callReplyGetBool(CallReply *rep) {
319 callReplyParse(rep);
320 if (rep->type != REDISMODULE_REPLY_BOOL) return INT_MIN;
321 return rep->val.ll;
322}
323
324/* Return reply length. Applicable to:
325 * - REDISMODULE_REPLY_STRING
326 * - REDISMODULE_REPLY_ERROR
327 * - REDISMODULE_REPLY_ARRAY
328 * - REDISMODULE_REPLY_SET
329 * - REDISMODULE_REPLY_MAP
330 * - REDISMODULE_REPLY_ATTRIBUTE
331 */
332size_t callReplyGetLen(CallReply *rep) {
333 callReplyParse(rep);
334 switch(rep->type) {
335 case REDISMODULE_REPLY_STRING:
336 case REDISMODULE_REPLY_ERROR:
337 case REDISMODULE_REPLY_ARRAY:
338 case REDISMODULE_REPLY_SET:
339 case REDISMODULE_REPLY_MAP:
340 case REDISMODULE_REPLY_ATTRIBUTE:
341 return rep->len;
342 default:
343 return 0;
344 }
345}
346
347static CallReply *callReplyGetCollectionElement(CallReply *rep, size_t idx, int elements_per_entry) {
348 if (idx >= rep->len * elements_per_entry) return NULL; // real len is rep->len * elements_per_entry
349 return rep->val.array+idx;
350}
351
352/* Return a reply array element at a given index. Applicable to:
353 * - REDISMODULE_REPLY_ARRAY
354 *
355 * The return value is borrowed from CallReply, so it must not be freed
356 * explicitly or used after CallReply itself is freed.
357 */
358CallReply *callReplyGetArrayElement(CallReply *rep, size_t idx) {
359 callReplyParse(rep);
360 if (rep->type != REDISMODULE_REPLY_ARRAY) return NULL;
361 return callReplyGetCollectionElement(rep, idx, 1);
362}
363
364/* Return a reply set element at a given index. Applicable to:
365 * - REDISMODULE_REPLY_SET
366 *
367 * The return value is borrowed from CallReply, so it must not be freed
368 * explicitly or used after CallReply itself is freed.
369 */
370CallReply *callReplyGetSetElement(CallReply *rep, size_t idx) {
371 callReplyParse(rep);
372 if (rep->type != REDISMODULE_REPLY_SET) return NULL;
373 return callReplyGetCollectionElement(rep, idx, 1);
374}
375
376static int callReplyGetMapElementInternal(CallReply *rep, size_t idx, CallReply **key, CallReply **val, int type) {
377 callReplyParse(rep);
378 if (rep->type != type) return C_ERR;
379 if (idx >= rep->len) return C_ERR;
380 if (key) *key = callReplyGetCollectionElement(rep, idx * 2, 2);
381 if (val) *val = callReplyGetCollectionElement(rep, idx * 2 + 1, 2);
382 return C_OK;
383}
384
385/* Retrieve a map reply key and value at a given index. Applicable to:
386 * - REDISMODULE_REPLY_MAP
387 *
388 * The key and value are returned by reference through key and val,
389 * which may also be NULL if not needed.
390 *
391 * Returns C_OK on success or C_ERR if reply type mismatches, or if idx is out
392 * of range.
393 *
394 * The returned values are borrowed from CallReply, so they must not be freed
395 * explicitly or used after CallReply itself is freed.
396 */
397int callReplyGetMapElement(CallReply *rep, size_t idx, CallReply **key, CallReply **val) {
398 return callReplyGetMapElementInternal(rep, idx, key, val, REDISMODULE_REPLY_MAP);
399}
400
401/* Return reply attribute, or NULL if it does not exist. Applicable to all replies.
402 *
403 * The returned values are borrowed from CallReply, so they must not be freed
404 * explicitly or used after CallReply itself is freed.
405 */
406CallReply *callReplyGetAttribute(CallReply *rep) {
407 return rep->attribute;
408}
409
410/* Retrieve attribute reply key and value at a given index. Applicable to:
411 * - REDISMODULE_REPLY_ATTRIBUTE
412 *
413 * The key and value are returned by reference through key and val,
414 * which may also be NULL if not needed.
415 *
416 * Returns C_OK on success or C_ERR if reply type mismatches, or if idx is out
417 * of range.
418 *
419 * The returned values are borrowed from CallReply, so they must not be freed
420 * explicitly or used after CallReply itself is freed.
421 */
422int callReplyGetAttributeElement(CallReply *rep, size_t idx, CallReply **key, CallReply **val) {
423 return callReplyGetMapElementInternal(rep, idx, key, val, REDISMODULE_REPLY_MAP);
424}
425
426/* Return a big number reply value. Applicable to:
427 * - REDISMODULE_REPLY_BIG_NUMBER
428 *
429 * The returned values are borrowed from CallReply, so they must not be freed
430 * explicitly or used after CallReply itself is freed.
431 *
432 * The return value is guaranteed to be a big number, as described in the RESP3
433 * protocol specifications.
434 *
435 * The returned value is not NULL terminated and its length is returned by
436 * reference through len, which must not be NULL.
437 */
438const char *callReplyGetBigNumber(CallReply *rep, size_t *len) {
439 callReplyParse(rep);
440 if (rep->type != REDISMODULE_REPLY_BIG_NUMBER) return NULL;
441 *len = rep->len;
442 return rep->val.str;
443}
444
445/* Return a verbatim string reply value. Applicable to:
446 * - REDISMODULE_REPLY_VERBATIM_STRING
447 *
448 * If format is non-NULL, the verbatim reply format is also returned by value.
449 *
450 * The optional output argument can be given to get a verbatim reply
451 * format, or can be set NULL if not needed.
452 *
453 * The return value is borrowed from CallReply, so it must not be freed
454 * explicitly or used after CallReply itself is freed.
455 *
456 * The returned value is not NULL terminated and its length is returned by
457 * reference through len, which must not be NULL.
458 */
459const char *callReplyGetVerbatim(CallReply *rep, size_t *len, const char **format){
460 callReplyParse(rep);
461 if (rep->type != REDISMODULE_REPLY_VERBATIM_STRING) return NULL;
462 *len = rep->len;
463 if (format) *format = rep->val.verbatim_str.format;
464 return rep->val.verbatim_str.str;
465}
466
467/* Return the current reply blob.
468 *
469 * The return value is borrowed from CallReply, so it must not be freed
470 * explicitly or used after CallReply itself is freed.
471 */
472const char *callReplyGetProto(CallReply *rep, size_t *proto_len) {
473 *proto_len = rep->proto_len;
474 return rep->proto;
475}
476
477/* Return CallReply private data, as set by the caller on callReplyCreate().
478 */
479void *callReplyGetPrivateData(CallReply *rep) {
480 return rep->private_data;
481}
482
483/* Return true if the reply or one of it sub-replies is RESP3 formatted. */
484int callReplyIsResp3(CallReply *rep) {
485 return rep->flags & REPLY_FLAG_RESP3;
486}
487
488/* Returns a list of errors in sds form, or NULL. */
489list *callReplyDeferredErrorList(CallReply *rep) {
490 return rep->deferred_error_list;
491}
492
493/* Create a new CallReply struct from the reply blob.
494 *
495 * The function will own the reply blob, so it must not be used or freed by
496 * the caller after passing it to this function.
497 *
498 * The reply blob will be freed when the returned CallReply struct is later
499 * freed using freeCallReply().
500 *
501 * The deferred_error_list is an optional list of errors that are present
502 * in the reply blob, if given, this function will take ownership on it.
503 *
504 * The private_data is optional and can later be accessed using
505 * callReplyGetPrivateData().
506 *
507 * NOTE: The parser used for parsing the reply and producing CallReply is
508 * designed to handle valid replies created by Redis itself. IT IS NOT
509 * DESIGNED TO HANDLE USER INPUT and using it to parse invalid replies is
510 * unsafe.
511 */
512CallReply *callReplyCreate(sds reply, list *deferred_error_list, void *private_data) {
513 CallReply *res = zmalloc(sizeof(*res));
514 res->flags = REPLY_FLAG_ROOT;
515 res->original_proto = reply;
516 res->proto = reply;
517 res->proto_len = sdslen(reply);
518 res->private_data = private_data;
519 res->attribute = NULL;
520 res->deferred_error_list = deferred_error_list;
521 return res;
522}
523
524/* Create a new CallReply struct from the reply blob representing an error message.
525 * Automatically creating deferred_error_list and set a copy of the reply in it.
526 * Refer to callReplyCreate for detailed explanation.
527 * Reply string can come in one of two forms:
528 * 1. A protocol reply starting with "-CODE" and ending with "\r\n"
529 * 2. A plain string, in which case this function adds the protocol header and footer. */
530CallReply *callReplyCreateError(sds reply, void *private_data) {
531 sds err_buff = reply;
532 if (err_buff[0] != '-') {
533 err_buff = sdscatfmt(sdsempty(), "-ERR %S\r\n", reply);
534 sdsfree(reply);
535 }
536 list *deferred_error_list = listCreate();
537 listSetFreeMethod(deferred_error_list, sdsfreegeneric);
538 listAddNodeTail(deferred_error_list, sdsnew(err_buff));
539 return callReplyCreate(err_buff, deferred_error_list, private_data);
540}