+#define DEF_COUNT 50
+#define ZERO 0
+#define MATCH_STR "MATCH"
+#define COUNT_STR "COUNT"
+#define SCANARGC 5
+
+RedisModuleString *def_count_str = NULL, *match_str = NULL, *count_str = NULL, *zero_str = NULL;
+
+typedef struct _NgetArgs {
+ RedisModuleString *key;
+ RedisModuleString *count;
+} NgetArgs;
+
+typedef struct RedisModuleBlockedClientArgs {
+ RedisModuleBlockedClient *bc;
+ NgetArgs nget_args;
+} RedisModuleBlockedClientArgs;
+
+void InitStaticVariable()
+{
+ if (def_count_str == NULL)
+ def_count_str = RedisModule_CreateStringFromLongLong(NULL, DEF_COUNT);
+ if (match_str == NULL)
+ match_str = RedisModule_CreateString(NULL, MATCH_STR, sizeof(MATCH_STR));
+ if (count_str == NULL)
+ count_str = RedisModule_CreateString(NULL, COUNT_STR, sizeof(COUNT_STR));
+ if (zero_str == NULL)
+ zero_str = RedisModule_CreateStringFromLongLong(NULL, ZERO);
+
+ return;
+}
+
+int getKeyType(RedisModuleCtx *ctx, RedisModuleString *key_str)
+{
+ RedisModuleKey *key = RedisModule_OpenKey(ctx, key_str, REDISMODULE_READ);
+ int type = RedisModule_KeyType(key);
+ RedisModule_CloseKey(key);
+ return type;
+}
+
+bool replyContentsEqualString(RedisModuleCallReply *reply, RedisModuleString *expected_value)
+{
+ size_t replylen = 0, expectedlen = 0;
+ const char *expectedval = RedisModule_StringPtrLen(expected_value, &expectedlen);
+ const char *replyval = RedisModule_CallReplyStringPtr(reply, &replylen);
+ return replyval &&
+ expectedlen == replylen &&
+ !strncmp(expectedval, replyval, replylen);
+}
+
+typedef struct _SetParams {
+ RedisModuleString **key_val_pairs;
+ size_t length;
+} SetParams;
+
+typedef struct _PubParams {
+ RedisModuleString **channel_msg_pairs;
+ size_t length;
+} PubParams;
+
+typedef struct _DelParams {
+ RedisModuleString **keys;
+ size_t length;
+} DelParams;
+
+typedef enum _ExstringsStatus {
+ EXSTRINGS_STATUS_NO_ERRORS = 0,
+ EXSTRINGS_STATUS_ERROR_AND_REPLY_SENT,
+ EXSTRINGS_STATUS_NOT_SET
+} ExstringsStatus;
+
+void readNgetArgs(RedisModuleCtx *ctx, RedisModuleString **argv, int argc,
+ NgetArgs* nget_args, ExstringsStatus* status)
+{
+ size_t str_len;
+ long long number;
+
+ if(argc == 2) {
+ nget_args->key = argv[1];
+ nget_args->count = def_count_str;
+ } else if (argc == 4) {
+ if (strcasecmp(RedisModule_StringPtrLen(argv[2], &str_len), "count")) {
+ RedisModule_ReplyWithError(ctx,"-ERR syntax error");
+ *status = EXSTRINGS_STATUS_ERROR_AND_REPLY_SENT;
+ return;
+ }
+
+ int ret = RedisModule_StringToLongLong(argv[3], &number) != REDISMODULE_OK;
+ if (ret != REDISMODULE_OK || number < 1) {
+ RedisModule_ReplyWithError(ctx,"-ERR value is not an integer or out of range");
+ *status = EXSTRINGS_STATUS_ERROR_AND_REPLY_SENT;
+ return;
+ }
+
+ nget_args->key = argv[1];
+ nget_args->count = argv[3];
+ } else {
+ /* In redis there is a bug (or undocumented feature see link)
+ * where calling 'RedisModule_WrongArity'
+ * within a blocked client will crash redis.
+ *
+ * Therefore we need to call this function to validate args
+ * before putting the client into blocking mode.
+ *
+ * Link to issue:
+ * https://github.com/antirez/redis/issues/6382
+ * 'If any thread tries to access the command arguments from
+ * within the ThreadSafeContext they will crash redis' */
+ RedisModule_WrongArity(ctx);
+ *status = EXSTRINGS_STATUS_ERROR_AND_REPLY_SENT;
+ return;
+ }
+
+ *status = EXSTRINGS_STATUS_NO_ERRORS;
+ return;
+}
+
+long long callReplyLongLong(RedisModuleCallReply* reply)
+{
+ const char* cursor_str_ptr = RedisModule_CallReplyStringPtr(reply, NULL);
+ return strtoll(cursor_str_ptr, NULL, 10);
+}
+
+void forwardIfError(RedisModuleCtx *ctx, RedisModuleCallReply *reply, ExstringsStatus* status)
+{
+ if (RedisModule_CallReplyType(reply) == REDISMODULE_REPLY_ERROR) {
+ RedisModule_ReplyWithCallReply(ctx, reply);
+ RedisModule_FreeCallReply(reply);
+ *status = EXSTRINGS_STATUS_ERROR_AND_REPLY_SENT;
+ }
+ *status = EXSTRINGS_STATUS_NO_ERRORS;
+}
+
+typedef struct _ScannedKeys {
+ RedisModuleString **keys;
+ size_t len;
+} ScannedKeys;
+
+ScannedKeys* allocScannedKeys(size_t len)
+{
+ ScannedKeys *sk = RedisModule_Alloc(sizeof(ScannedKeys));
+ if (sk) {
+ sk->len = len;
+ sk->keys = RedisModule_Alloc(sizeof(RedisModuleString *)*len);
+ }
+ return sk;
+}
+
+void freeScannedKeys(RedisModuleCtx *ctx, ScannedKeys* sk)
+{
+ if (sk) {
+ size_t j;
+ for (j = 0; j < sk->len; j++)
+ RedisModule_FreeString(ctx, sk->keys[j]);
+ RedisModule_Free(sk->keys);
+ }
+ RedisModule_Free(sk);
+}
+
+typedef struct _ScanSomeState {
+ RedisModuleString *key;
+ RedisModuleString *count;
+ long long cursor;
+} ScanSomeState;
+
+ScannedKeys *scanSome(RedisModuleCtx* ctx, ScanSomeState* state, ExstringsStatus* status)
+{
+ RedisModuleString *scanargv[SCANARGC] = {NULL};
+
+ scanargv[0] = RedisModule_CreateStringFromLongLong(ctx, state->cursor);
+ scanargv[1] = match_str;
+ scanargv[2] = state->key;
+ scanargv[3] = count_str;
+ scanargv[4] = state->count;
+
+ RedisModuleCallReply *reply;
+ reply = RedisModule_Call(ctx, "SCAN", "v", scanargv, SCANARGC);
+ RedisModule_FreeString(ctx, scanargv[0]);
+ forwardIfError(ctx, reply, status);
+ if (*status == EXSTRINGS_STATUS_ERROR_AND_REPLY_SENT)
+ return NULL;
+
+ state->cursor = callReplyLongLong(RedisModule_CallReplyArrayElement(reply, 0));
+ RedisModuleCallReply *cr_keys =
+ RedisModule_CallReplyArrayElement(reply, 1);
+
+ size_t scanned_keys_len = RedisModule_CallReplyLength(cr_keys);
+ if (scanned_keys_len == 0) {
+ RedisModule_FreeCallReply(reply);
+ *status = EXSTRINGS_STATUS_NO_ERRORS;
+ return NULL;
+ }
+
+ ScannedKeys *scanned_keys = allocScannedKeys(scanned_keys_len);
+ if (scanned_keys == NULL) {
+ RedisModule_FreeCallReply(reply);
+ RedisModule_ReplyWithError(ctx,"-ERR Out of memory");
+ *status = EXSTRINGS_STATUS_ERROR_AND_REPLY_SENT;
+ return NULL;
+ }
+
+ scanned_keys->len = scanned_keys_len;
+ size_t j;
+ for (j = 0; j < scanned_keys_len; j++) {
+ RedisModuleString *rms = RedisModule_CreateStringFromCallReply(RedisModule_CallReplyArrayElement(cr_keys,j));
+ scanned_keys->keys[j] = rms;
+ }
+ RedisModule_FreeCallReply(reply);
+ *status = EXSTRINGS_STATUS_NO_ERRORS;
+ return scanned_keys;
+}
+
+inline void unlockThreadsafeContext(RedisModuleCtx *ctx, bool using_threadsafe_context)
+{
+ if (using_threadsafe_context)
+ RedisModule_ThreadSafeContextUnlock(ctx);
+}
+
+inline void lockThreadsafeContext(RedisModuleCtx *ctx, bool using_threadsafe_context)
+{
+ if (using_threadsafe_context)
+ RedisModule_ThreadSafeContextLock(ctx);
+}
+
+void multiPubCommand(RedisModuleCtx *ctx, PubParams* pubParams)
+{
+ RedisModuleCallReply *reply = NULL;
+ for (unsigned int i = 0 ; i < pubParams->length ; i += 2) {
+ reply = RedisModule_Call(ctx, "PUBLISH", "v", pubParams->channel_msg_pairs + i, 2);
+ RedisModule_FreeCallReply(reply);
+ }
+}