diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 13:40:54 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 13:40:54 +0000 |
commit | 317c0644ccf108aa23ef3fd8358bd66c2840bfc0 (patch) | |
tree | c417b3d25c86b775989cb5ac042f37611b626c8a /src/call_reply.c | |
parent | Initial commit. (diff) | |
download | redis-317c0644ccf108aa23ef3fd8358bd66c2840bfc0.tar.xz redis-317c0644ccf108aa23ef3fd8358bd66c2840bfc0.zip |
Adding upstream version 5:7.2.4.upstream/5%7.2.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/call_reply.c')
-rw-r--r-- | src/call_reply.c | 560 |
1 files changed, 560 insertions, 0 deletions
diff --git a/src/call_reply.c b/src/call_reply.c new file mode 100644 index 0000000..ccd1b36 --- /dev/null +++ b/src/call_reply.c @@ -0,0 +1,560 @@ +/* + * Copyright (c) 2009-2021, Redis Labs Ltd. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "server.h" +#include "call_reply.h" + +#define REPLY_FLAG_ROOT (1<<0) +#define REPLY_FLAG_PARSED (1<<1) +#define REPLY_FLAG_RESP3 (1<<2) + +/* -------------------------------------------------------- + * An opaque struct used to parse a RESP protocol reply and + * represent it. Used when parsing replies such as in RM_Call + * or Lua scripts. + * -------------------------------------------------------- */ +struct CallReply { + void *private_data; + sds original_proto; /* Available only for root reply. */ + const char *proto; + size_t proto_len; + int type; /* REPLY_... */ + int flags; /* REPLY_FLAG... */ + size_t len; /* Length of a string, or the number elements in an array. */ + union { + const char *str; /* String pointer for string and error replies. This + * does not need to be freed, always points inside + * a reply->proto buffer of the reply object or, in + * case of array elements, of parent reply objects. */ + struct { + const char *str; + const char *format; + } verbatim_str; /* Reply value for verbatim string */ + long long ll; /* Reply value for integer reply. */ + double d; /* Reply value for double reply. */ + struct CallReply *array; /* Array of sub-reply elements. used for set, array, map, and attribute */ + } val; + list *deferred_error_list; /* list of errors in sds form or NULL */ + struct CallReply *attribute; /* attribute reply, NULL if not exists */ +}; + +static void callReplySetSharedData(CallReply *rep, int type, const char *proto, size_t proto_len, int extra_flags) { + rep->type = type; + rep->proto = proto; + rep->proto_len = proto_len; + rep->flags |= extra_flags; +} + +static void callReplyNull(void *ctx, const char *proto, size_t proto_len) { + CallReply *rep = ctx; + callReplySetSharedData(rep, REDISMODULE_REPLY_NULL, proto, proto_len, REPLY_FLAG_RESP3); +} + +static void callReplyNullBulkString(void *ctx, const char *proto, size_t proto_len) { + CallReply *rep = ctx; + callReplySetSharedData(rep, REDISMODULE_REPLY_NULL, proto, proto_len, 0); +} + +static void callReplyNullArray(void *ctx, const char *proto, size_t proto_len) { + CallReply *rep = ctx; + callReplySetSharedData(rep, REDISMODULE_REPLY_NULL, proto, proto_len, 0); +} + +static void callReplyBulkString(void *ctx, const char *str, size_t len, const char *proto, size_t proto_len) { + CallReply *rep = ctx; + callReplySetSharedData(rep, REDISMODULE_REPLY_STRING, proto, proto_len, 0); + rep->len = len; + rep->val.str = str; +} + +static void callReplyError(void *ctx, const char *str, size_t len, const char *proto, size_t proto_len) { + CallReply *rep = ctx; + callReplySetSharedData(rep, REDISMODULE_REPLY_ERROR, proto, proto_len, 0); + rep->len = len; + rep->val.str = str; +} + +static void callReplySimpleStr(void *ctx, const char *str, size_t len, const char *proto, size_t proto_len) { + CallReply *rep = ctx; + callReplySetSharedData(rep, REDISMODULE_REPLY_STRING, proto, proto_len, 0); + rep->len = len; + rep->val.str = str; +} + +static void callReplyLong(void *ctx, long long val, const char *proto, size_t proto_len) { + CallReply *rep = ctx; + callReplySetSharedData(rep, REDISMODULE_REPLY_INTEGER, proto, proto_len, 0); + rep->val.ll = val; +} + +static void callReplyDouble(void *ctx, double val, const char *proto, size_t proto_len) { + CallReply *rep = ctx; + callReplySetSharedData(rep, REDISMODULE_REPLY_DOUBLE, proto, proto_len, REPLY_FLAG_RESP3); + rep->val.d = val; +} + +static void callReplyVerbatimString(void *ctx, const char *format, const char *str, size_t len, const char *proto, size_t proto_len) { + CallReply *rep = ctx; + callReplySetSharedData(rep, REDISMODULE_REPLY_VERBATIM_STRING, proto, proto_len, REPLY_FLAG_RESP3); + rep->len = len; + rep->val.verbatim_str.str = str; + rep->val.verbatim_str.format = format; +} + +static void callReplyBigNumber(void *ctx, const char *str, size_t len, const char *proto, size_t proto_len) { + CallReply *rep = ctx; + callReplySetSharedData(rep, REDISMODULE_REPLY_BIG_NUMBER, proto, proto_len, REPLY_FLAG_RESP3); + rep->len = len; + rep->val.str = str; +} + +static void callReplyBool(void *ctx, int val, const char *proto, size_t proto_len) { + CallReply *rep = ctx; + callReplySetSharedData(rep, REDISMODULE_REPLY_BOOL, proto, proto_len, REPLY_FLAG_RESP3); + rep->val.ll = val; +} + +static void callReplyParseCollection(ReplyParser *parser, CallReply *rep, size_t len, const char *proto, size_t elements_per_entry) { + rep->len = len; + rep->val.array = zcalloc(elements_per_entry * len * sizeof(CallReply)); + for (size_t i = 0; i < len * elements_per_entry; i += elements_per_entry) { + for (size_t j = 0 ; j < elements_per_entry ; ++j) { + rep->val.array[i + j].private_data = rep->private_data; + parseReply(parser, rep->val.array + i + j); + rep->val.array[i + j].flags |= REPLY_FLAG_PARSED; + if (rep->val.array[i + j].flags & REPLY_FLAG_RESP3) { + /* If one of the sub-replies is RESP3, then the current reply is also RESP3. */ + rep->flags |= REPLY_FLAG_RESP3; + } + } + } + rep->proto = proto; + rep->proto_len = parser->curr_location - proto; +} + +static void callReplyAttribute(ReplyParser *parser, void *ctx, size_t len, const char *proto) { + CallReply *rep = ctx; + rep->attribute = zcalloc(sizeof(CallReply)); + + /* Continue parsing the attribute reply */ + rep->attribute->len = len; + rep->attribute->type = REDISMODULE_REPLY_ATTRIBUTE; + callReplyParseCollection(parser, rep->attribute, len, proto, 2); + rep->attribute->flags |= REPLY_FLAG_PARSED | REPLY_FLAG_RESP3; + rep->attribute->private_data = rep->private_data; + + /* Continue parsing the reply */ + parseReply(parser, rep); + + /* In this case we need to fix the proto address and len, it should start from the attribute */ + rep->proto = proto; + rep->proto_len = parser->curr_location - proto; + rep->flags |= REPLY_FLAG_RESP3; +} + +static void callReplyArray(ReplyParser *parser, void *ctx, size_t len, const char *proto) { + CallReply *rep = ctx; + rep->type = REDISMODULE_REPLY_ARRAY; + callReplyParseCollection(parser, rep, len, proto, 1); +} + +static void callReplySet(ReplyParser *parser, void *ctx, size_t len, const char *proto) { + CallReply *rep = ctx; + rep->type = REDISMODULE_REPLY_SET; + callReplyParseCollection(parser, rep, len, proto, 1); + rep->flags |= REPLY_FLAG_RESP3; +} + +static void callReplyMap(ReplyParser *parser, void *ctx, size_t len, const char *proto) { + CallReply *rep = ctx; + rep->type = REDISMODULE_REPLY_MAP; + callReplyParseCollection(parser, rep, len, proto, 2); + rep->flags |= REPLY_FLAG_RESP3; +} + +static void callReplyParseError(void *ctx) { + CallReply *rep = ctx; + rep->type = REDISMODULE_REPLY_UNKNOWN; +} + +/* Recursively free the current call reply and its sub-replies. */ +static void freeCallReplyInternal(CallReply *rep) { + if (rep->type == REDISMODULE_REPLY_ARRAY || rep->type == REDISMODULE_REPLY_SET) { + for (size_t i = 0 ; i < rep->len ; ++i) { + freeCallReplyInternal(rep->val.array + i); + } + zfree(rep->val.array); + } + + if (rep->type == REDISMODULE_REPLY_MAP || rep->type == REDISMODULE_REPLY_ATTRIBUTE) { + for (size_t i = 0 ; i < rep->len ; ++i) { + freeCallReplyInternal(rep->val.array + i * 2); + freeCallReplyInternal(rep->val.array + i * 2 + 1); + } + zfree(rep->val.array); + } + + if (rep->attribute) { + freeCallReplyInternal(rep->attribute); + zfree(rep->attribute); + } +} + +/* Free the given call reply and its children (in case of nested reply) recursively. + * If private data was set when the CallReply was created it will not be freed, as it's + * the caller's responsibility to free it before calling freeCallReply(). */ +void freeCallReply(CallReply *rep) { + if (!(rep->flags & REPLY_FLAG_ROOT)) { + return; + } + if (rep->flags & REPLY_FLAG_PARSED) { + if (rep->type == REDISMODULE_REPLY_PROMISE) { + zfree(rep); + return; + } + freeCallReplyInternal(rep); + } + sdsfree(rep->original_proto); + if (rep->deferred_error_list) + listRelease(rep->deferred_error_list); + zfree(rep); +} + +CallReply *callReplyCreatePromise(void *private_data) { + CallReply *res = zmalloc(sizeof(*res)); + res->type = REDISMODULE_REPLY_PROMISE; + /* Mark the reply as parsed so there will be not attempt to parse + * it when calling reply API such as freeCallReply. + * Also mark the reply as root so freeCallReply will not ignore it. */ + res->flags |= REPLY_FLAG_PARSED | REPLY_FLAG_ROOT; + res->private_data = private_data; + return res; +} + +static const ReplyParserCallbacks DefaultParserCallbacks = { + .null_callback = callReplyNull, + .bulk_string_callback = callReplyBulkString, + .null_bulk_string_callback = callReplyNullBulkString, + .null_array_callback = callReplyNullArray, + .error_callback = callReplyError, + .simple_str_callback = callReplySimpleStr, + .long_callback = callReplyLong, + .array_callback = callReplyArray, + .set_callback = callReplySet, + .map_callback = callReplyMap, + .double_callback = callReplyDouble, + .bool_callback = callReplyBool, + .big_number_callback = callReplyBigNumber, + .verbatim_string_callback = callReplyVerbatimString, + .attribute_callback = callReplyAttribute, + .error = callReplyParseError, +}; + +/* Parse the buffer located in rep->original_proto and update the CallReply + * structure to represent its contents. */ +static void callReplyParse(CallReply *rep) { + if (rep->flags & REPLY_FLAG_PARSED) { + return; + } + + ReplyParser parser = {.curr_location = rep->proto, .callbacks = DefaultParserCallbacks}; + + parseReply(&parser, rep); + rep->flags |= REPLY_FLAG_PARSED; +} + +/* Return the call reply type (REDISMODULE_REPLY_...). */ +int callReplyType(CallReply *rep) { + if (!rep) return REDISMODULE_REPLY_UNKNOWN; + callReplyParse(rep); + return rep->type; +} + +/* Return reply string as buffer and len. Applicable to: + * - REDISMODULE_REPLY_STRING + * - REDISMODULE_REPLY_ERROR + * + * The return value is borrowed from CallReply, so it must not be freed + * explicitly or used after CallReply itself is freed. + * + * The returned value is not NULL terminated and its length is returned by + * reference through len, which must not be NULL. + */ +const char *callReplyGetString(CallReply *rep, size_t *len) { + callReplyParse(rep); + if (rep->type != REDISMODULE_REPLY_STRING && + rep->type != REDISMODULE_REPLY_ERROR) return NULL; + if (len) *len = rep->len; + return rep->val.str; +} + +/* Return a long long reply value. Applicable to: + * - REDISMODULE_REPLY_INTEGER + */ +long long callReplyGetLongLong(CallReply *rep) { + callReplyParse(rep); + if (rep->type != REDISMODULE_REPLY_INTEGER) return LLONG_MIN; + return rep->val.ll; +} + +/* Return a double reply value. Applicable to: + * - REDISMODULE_REPLY_DOUBLE + */ +double callReplyGetDouble(CallReply *rep) { + callReplyParse(rep); + if (rep->type != REDISMODULE_REPLY_DOUBLE) return LLONG_MIN; + return rep->val.d; +} + +/* Return a reply Boolean value. Applicable to: + * - REDISMODULE_REPLY_BOOL + */ +int callReplyGetBool(CallReply *rep) { + callReplyParse(rep); + if (rep->type != REDISMODULE_REPLY_BOOL) return INT_MIN; + return rep->val.ll; +} + +/* Return reply length. Applicable to: + * - REDISMODULE_REPLY_STRING + * - REDISMODULE_REPLY_ERROR + * - REDISMODULE_REPLY_ARRAY + * - REDISMODULE_REPLY_SET + * - REDISMODULE_REPLY_MAP + * - REDISMODULE_REPLY_ATTRIBUTE + */ +size_t callReplyGetLen(CallReply *rep) { + callReplyParse(rep); + switch(rep->type) { + case REDISMODULE_REPLY_STRING: + case REDISMODULE_REPLY_ERROR: + case REDISMODULE_REPLY_ARRAY: + case REDISMODULE_REPLY_SET: + case REDISMODULE_REPLY_MAP: + case REDISMODULE_REPLY_ATTRIBUTE: + return rep->len; + default: + return 0; + } +} + +static CallReply *callReplyGetCollectionElement(CallReply *rep, size_t idx, int elements_per_entry) { + if (idx >= rep->len * elements_per_entry) return NULL; // real len is rep->len * elements_per_entry + return rep->val.array+idx; +} + +/* Return a reply array element at a given index. Applicable to: + * - REDISMODULE_REPLY_ARRAY + * + * The return value is borrowed from CallReply, so it must not be freed + * explicitly or used after CallReply itself is freed. + */ +CallReply *callReplyGetArrayElement(CallReply *rep, size_t idx) { + callReplyParse(rep); + if (rep->type != REDISMODULE_REPLY_ARRAY) return NULL; + return callReplyGetCollectionElement(rep, idx, 1); +} + +/* Return a reply set element at a given index. Applicable to: + * - REDISMODULE_REPLY_SET + * + * The return value is borrowed from CallReply, so it must not be freed + * explicitly or used after CallReply itself is freed. + */ +CallReply *callReplyGetSetElement(CallReply *rep, size_t idx) { + callReplyParse(rep); + if (rep->type != REDISMODULE_REPLY_SET) return NULL; + return callReplyGetCollectionElement(rep, idx, 1); +} + +static int callReplyGetMapElementInternal(CallReply *rep, size_t idx, CallReply **key, CallReply **val, int type) { + callReplyParse(rep); + if (rep->type != type) return C_ERR; + if (idx >= rep->len) return C_ERR; + if (key) *key = callReplyGetCollectionElement(rep, idx * 2, 2); + if (val) *val = callReplyGetCollectionElement(rep, idx * 2 + 1, 2); + return C_OK; +} + +/* Retrieve a map reply key and value at a given index. Applicable to: + * - REDISMODULE_REPLY_MAP + * + * The key and value are returned by reference through key and val, + * which may also be NULL if not needed. + * + * Returns C_OK on success or C_ERR if reply type mismatches, or if idx is out + * of range. + * + * The returned values are borrowed from CallReply, so they must not be freed + * explicitly or used after CallReply itself is freed. + */ +int callReplyGetMapElement(CallReply *rep, size_t idx, CallReply **key, CallReply **val) { + return callReplyGetMapElementInternal(rep, idx, key, val, REDISMODULE_REPLY_MAP); +} + +/* Return reply attribute, or NULL if it does not exist. Applicable to all replies. + * + * The returned values are borrowed from CallReply, so they must not be freed + * explicitly or used after CallReply itself is freed. + */ +CallReply *callReplyGetAttribute(CallReply *rep) { + return rep->attribute; +} + +/* Retrieve attribute reply key and value at a given index. Applicable to: + * - REDISMODULE_REPLY_ATTRIBUTE + * + * The key and value are returned by reference through key and val, + * which may also be NULL if not needed. + * + * Returns C_OK on success or C_ERR if reply type mismatches, or if idx is out + * of range. + * + * The returned values are borrowed from CallReply, so they must not be freed + * explicitly or used after CallReply itself is freed. + */ +int callReplyGetAttributeElement(CallReply *rep, size_t idx, CallReply **key, CallReply **val) { + return callReplyGetMapElementInternal(rep, idx, key, val, REDISMODULE_REPLY_MAP); +} + +/* Return a big number reply value. Applicable to: + * - REDISMODULE_REPLY_BIG_NUMBER + * + * The returned values are borrowed from CallReply, so they must not be freed + * explicitly or used after CallReply itself is freed. + * + * The return value is guaranteed to be a big number, as described in the RESP3 + * protocol specifications. + * + * The returned value is not NULL terminated and its length is returned by + * reference through len, which must not be NULL. + */ +const char *callReplyGetBigNumber(CallReply *rep, size_t *len) { + callReplyParse(rep); + if (rep->type != REDISMODULE_REPLY_BIG_NUMBER) return NULL; + *len = rep->len; + return rep->val.str; +} + +/* Return a verbatim string reply value. Applicable to: + * - REDISMODULE_REPLY_VERBATIM_STRING + * + * If format is non-NULL, the verbatim reply format is also returned by value. + * + * The optional output argument can be given to get a verbatim reply + * format, or can be set NULL if not needed. + * + * The return value is borrowed from CallReply, so it must not be freed + * explicitly or used after CallReply itself is freed. + * + * The returned value is not NULL terminated and its length is returned by + * reference through len, which must not be NULL. + */ +const char *callReplyGetVerbatim(CallReply *rep, size_t *len, const char **format){ + callReplyParse(rep); + if (rep->type != REDISMODULE_REPLY_VERBATIM_STRING) return NULL; + *len = rep->len; + if (format) *format = rep->val.verbatim_str.format; + return rep->val.verbatim_str.str; +} + +/* Return the current reply blob. + * + * The return value is borrowed from CallReply, so it must not be freed + * explicitly or used after CallReply itself is freed. + */ +const char *callReplyGetProto(CallReply *rep, size_t *proto_len) { + *proto_len = rep->proto_len; + return rep->proto; +} + +/* Return CallReply private data, as set by the caller on callReplyCreate(). + */ +void *callReplyGetPrivateData(CallReply *rep) { + return rep->private_data; +} + +/* Return true if the reply or one of it sub-replies is RESP3 formatted. */ +int callReplyIsResp3(CallReply *rep) { + return rep->flags & REPLY_FLAG_RESP3; +} + +/* Returns a list of errors in sds form, or NULL. */ +list *callReplyDeferredErrorList(CallReply *rep) { + return rep->deferred_error_list; +} + +/* Create a new CallReply struct from the reply blob. + * + * The function will own the reply blob, so it must not be used or freed by + * the caller after passing it to this function. + * + * The reply blob will be freed when the returned CallReply struct is later + * freed using freeCallReply(). + * + * The deferred_error_list is an optional list of errors that are present + * in the reply blob, if given, this function will take ownership on it. + * + * The private_data is optional and can later be accessed using + * callReplyGetPrivateData(). + * + * NOTE: The parser used for parsing the reply and producing CallReply is + * designed to handle valid replies created by Redis itself. IT IS NOT + * DESIGNED TO HANDLE USER INPUT and using it to parse invalid replies is + * unsafe. + */ +CallReply *callReplyCreate(sds reply, list *deferred_error_list, void *private_data) { + CallReply *res = zmalloc(sizeof(*res)); + res->flags = REPLY_FLAG_ROOT; + res->original_proto = reply; + res->proto = reply; + res->proto_len = sdslen(reply); + res->private_data = private_data; + res->attribute = NULL; + res->deferred_error_list = deferred_error_list; + return res; +} + +/* Create a new CallReply struct from the reply blob representing an error message. + * Automatically creating deferred_error_list and set a copy of the reply in it. + * Refer to callReplyCreate for detailed explanation. + * Reply string can come in one of two forms: + * 1. A protocol reply starting with "-CODE" and ending with "\r\n" + * 2. A plain string, in which case this function adds the protocol header and footer. */ +CallReply *callReplyCreateError(sds reply, void *private_data) { + sds err_buff = reply; + if (err_buff[0] != '-') { + err_buff = sdscatfmt(sdsempty(), "-ERR %S\r\n", reply); + sdsfree(reply); + } + list *deferred_error_list = listCreate(); + listSetFreeMethod(deferred_error_list, (void (*)(void*))sdsfree); + listAddNodeTail(deferred_error_list, sdsnew(err_buff)); + return callReplyCreate(err_buff, deferred_error_list, private_data); +} |