Skip to content

Commit

Permalink
Initial RESP3 and HELLO support
Browse files Browse the repository at this point in the history
  • Loading branch information
attipaci committed Dec 6, 2024
1 parent 3789bce commit 94ae01a
Show file tree
Hide file tree
Showing 10 changed files with 775 additions and 127 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,15 @@ clean:

# Remove all generated files
.PHONY: distclean
distclean: clean
distclean:
rm -f Doxyfile.local $(LIB)/libredisx.so* $(LIB)/libredisx.a


# ----------------------------------------------------------------------------
# The nitty-gritty stuff below
# ----------------------------------------------------------------------------

SOURCES = $(SRC)/redisx.c $(SRC)/redisx-net.c $(SRC)/redisx-hooks.c $(SRC)/redisx-client.c \
SOURCES = $(SRC)/redisx.c $(SRC)/resp.c $(SRC)/redisx-net.c $(SRC)/redisx-hooks.c $(SRC)/redisx-client.c \
$(SRC)/redisx-tab.c $(SRC)/redisx-sub.c $(SRC)/redisx-script.c

# Generate a list of object (obj/*.o) files from the input sources
Expand Down
45 changes: 36 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,17 @@ the default 6379), and the database authentication (if any):
redisxSetPassword(redis, mySecretPasswordString);
```
You can also set the RESP protocol to use (provided your server is compatible with Redis 6 or later):
```c
// (optional) Use RESP3 (provided the server supports it)
redisxSetProtocol(redis, REDISX_RESP3);
```

The above call will use the `HELLO` command (since Redis 6) upon connecting. If you do not set the protocol, `HELLO`
will not be used, and RESP2 will be assumed -- which is best for older servers. (Note, that you can always check the
actual protocol used after connecting, using `redisxGetProtocol()`).

You might also tweak the send/receive buffer sizes to use for clients, if you find the socket defaults sub-optimal for
your application (note, that this setting is common to all `Redis` instances managed by the library):

Expand Down Expand Up @@ -307,9 +318,9 @@ The same goes for disconnect hooks, using `redisxAddDisconnectHook()` instead.
- [RESP data type](#resp-data-type)
Redis queries are sent as strings, according the the specification of the Redis protocol. All responses sent back by
the server using the RESP protocol. Specifically, Redis uses version 2.0 of the RESP protocol (a.k.a. RESP2) by
default, with optional support for the newer RESP3 introduced in Redis version 6.0. The __RedisX__ library currently
processes the standard RESP2 replies only. RESP3 support to the library may be added in the future (stay tuned...)
the server using the RESP protocol. Specifically, Redis uses version 2 of the RESP protocol (a.k.a. RESP2) by
default, with optional support for the newer RESP3 introduced in Redis version 6.0. The __RedisX__ library provides
support for both RESP2 and RESP3.
<a name="interactive-transactions"></a>
Expand Down Expand Up @@ -378,19 +389,36 @@ whose contents are:
| `RESP_ARRAY` | `*` | number of `RESP *` pointers | `(RESP **)` |
| `RESP_INT` | `:` | integer return value | `(void)` |
| `RESP_SIMPLE_STRING` | `+` | string length | `(char *)` |
| `RESP_ERROR` | `-` | string length | `(char *)` |
| `RESP_ERROR` | `-` | total string length | `(char *)` |
| `RESP_BULK_STRING` | `$` | string length or -1 if `NULL` | `(char *)` |

| `RESP3_NULL` | `_` | 0 | `(void)` |
| `RESP3_BOOLEAN` | `#` | 1 if _true_, 0 if _false_ | `(void)` |
| `RESP3_DOUBLE` | `,` | _unused_ | `(double *)` |
| `RESP3_BIG_NUMBER` | `(` | string representation length | `(char *)` |
| `RESP3_BLOB_ERROR` | `!` | total string length | `(char *)` |
| `RESP3_VERBATIM_TEXT` | `=` | text length (incl. type) | `(char *)` |
| `RESP3_SET` | `~` | number of `RESP *` pointers | `(RESP *)` |
| `RESP3_MAP` | `%` | number of key / value pairs | `(RedisMapEntry *)` |
| `RESP3_ATTRIBUTE` | `|` | number of key / value pairs | `(RedisMapEntry *)` |
| `RESP3_PUSH` | `>` | number of `RESP *` pointers | `(RESP **)` |


Each `RESP` has a type (e.g. `RESP_SIMPLE_STRING`), an integer value `n`, and a `value` pointer
to further data. If the type is `RESP_INT`, then `n` represents the actual return value (and the `value` pointer is
not used). For string type values `n` is the number of characters in the string `value` (not including termination),
while for `RESP_ARRAY` types the `value` is a pointer to an embedded `RESP` array and `n` is the number of elements
in that.

You may check the integrity of a `RESP` using `redisxCheckRESP()`. Since `RESP` data is dynamically allocated, the
user is responsible for discarding them once they are no longer needed, e.g. by calling `redisxDestroyRESP()`. The
two steps may be combined to automatically discard invalid or unexpected `RESP` data in a single step by calling
To help with deciding what cast to use for a given `value` field of the RESP data structure, we provide the
convenience methods `redisxIsScalarType()` when cast is `(void)` or else `(double *)`), `redisxIsArrayType()` if the
cast is `(RESP **)`, `redisxIsStringTupe()` if the cast should be `(char *)`, and `redisxIsMapType()` if the cast
should be to `(RedisMapEntry *)`.

You can check that two `RESP` data structures are equivalent with `redisxIsEqualRESP(RESP *a, RESP *b)`.

You may also check the integrity of a `RESP` using `redisxCheckRESP()`. Since `RESP` data is dynamically allocated,
the user is responsible for discarding them once they are no longer needed, e.g. by calling `redisxDestroyRESP()`.
The two steps may be combined to automatically discard invalid or unexpected `RESP` data in a single step by calling
`redisxCheckDestroyRESP()`.

```c
Expand Down Expand Up @@ -1045,7 +1073,6 @@ Some obvious ways the library could evolve and grow in the not too distant futur
- Automated regression testing and coverage tracking.
- Keep track of subscription patterns, and automatically resubscribe to them on reconnecting.
- Support for the [RESP3](https://github.com/antirez/RESP3/blob/master/spec.md) standard and Redis `HELLO`.
- Support for [Redis Sentinel](https://redis.io/docs/latest/develop/reference/sentinel-clients/) clients, for
high-availability server configurations.
- TLS support (perhaps...)
Expand Down
2 changes: 1 addition & 1 deletion build.mk
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ clean: clean-local

# Remove intermediate files (general)
.PHONY: distclean
distclean: distclean-local
distclean: clean distclean-local

# Static code analysis using 'cppcheck'
.PHONY: analyze
Expand Down
7 changes: 6 additions & 1 deletion include/redisx-priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ typedef struct {
uint32_t addr; ///< The 32-bit inet address
int port; ///< port number (usually 6379)
int dbIndex; ///< the zero-based database index
char *username; ///< REdis user name (if any)
char *username; ///< Redis user name (if any)
char *password; ///< Redis password (if any)
int protocol; ///< RESP version to use
boolean hello; ///< whether to use HELLO (introduced in Redis 6.0.0 only)

RedisClient *clients;

Expand Down Expand Up @@ -93,6 +95,9 @@ int rConnectClient(Redis *redis, enum redisx_channel channel);
void rCloseClient(RedisClient *cl);
boolean rIsLowLatency(const ClientPrivate *cp);

// in resp.c ------------------------------>
int redisxAppendRESP(RESP *resp, RESP *part);

/// \endcond

#endif /* REDISX_PRIV_H_ */
55 changes: 55 additions & 0 deletions include/redisx.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,19 @@
#define RESP_ERROR '-' ///< \hideinitializer RESP error message type
#define RESP_BULK_STRING '$' ///< \hideinitializer RESP bulk string type

// RESP3 types
#define RESP3_NULL '_' ///< \hideinitializer RESP3 null value
#define RESP3_DOUBLE ',' ///< \hideinitializer RESP3 floating-point value
#define RESP3_BOOLEAN '#' ///< \hideinitializer RESP3 boolean value
#define RESP3_BLOB_ERROR '!' ///< \hideinitializer RESP3 blob error
#define RESP3_VERBATIM_STRING '=' ///< \hideinitializer RESP3 verbatim string (with type)
#define RESP3_BIG_NUMBER '(' ///< \hideinitializer RESP3 big integer / decimal
#define RESP3_MAP '%' ///< \hideinitializer RESP3 dictionary of key / value
#define RESP3_SET '~' ///< \hideinitializer RESP3 unordered set of elements
#define RESP3_ATTRIBUTE '|' ///< \hideinitializer RESP3 dictionary of attributes (metadata)
#define RESP3_PUSH '>' ///< \hideinitializer RESP3 dictionary of attributes (metadata)
#define RESP3_SNIPPET ';' ///< \hideinitializer RESP3 dictionary of attributes (metadata)

#define REDIS_INVALID_CHANNEL (-101) ///< \hideinitializer There is no such channel in the Redis instance.
#define REDIS_NULL (-102) ///< \hideinitializer Redis returned NULL
#define REDIS_ERROR (-103) ///< \hideinitializer Redis returned an error
Expand All @@ -112,11 +125,29 @@ enum redisx_channel {

#define REDISX_CHANNELS (REDISX_SUBSCRIPTION_CHANNEL + 1) ///< \hideinitializer The number of channels a Redis instance has.

/**
* The RESP protocol to use for a Redis instance. Redis originally used RESP2, but later releases added
* support for RESP3.
*
*/
enum redisx_protocol {
REDISX_RESP2 = 2, ///< \hideinitializer RESP2 protocol
REDISX_RESP3 ///< \hideinitializer RESP3 protocol (since Redis version 6.0.0)
};

/**
* \brief Structure that represents a Redis response (RESP format).
*
* REFERENCES:
* <ol>
* <li>https://github.com/redis/redis-specifications/tree/master/protocol</li>
* </ol>
*
* \sa redisxDestroyRESP()
* \sa redisxIsScalarType()
* \sa redisxIsStringType()
* \sa redisxIsArrayType()
* \sa redisxIsMapType()
*/
typedef struct RESP {
char type; ///< RESP type RESP_ARRAY, RESP_INT ...
Expand All @@ -126,6 +157,19 @@ typedef struct RESP {
///< (RESP**)...
} RESP;

/**
* Structure that represents a key/value mapping in RESP3.
*
* @sa redisxIsMapType()
* @sa RESP3_MAP
* @sa RESP3_ATTRIBUTE
*
*/
typedef struct {
RESP *key; ///< The keyword component
RESP *value; ///< The associated value component
} RedisMapEntry;


/**
* \brief A single key / value entry, or field, in the Redis database.
Expand Down Expand Up @@ -228,6 +272,8 @@ int redisxSetPort(Redis *redis, int port);
int redisxSetUser(Redis *redis, const char *username);
int redisxSetPassword(Redis *redis, const char *passwd);
int redisxSelectDB(Redis *redis, int idx);
int redisxSetProtocol(Redis *redis, enum redisx_protocol protocol);
enum redisx_protocol redisxGetProtocol(const Redis *redis);

Redis *redisxInit(const char *server);
void redisxDestroy(Redis *redis);
Expand Down Expand Up @@ -286,6 +332,15 @@ int redisxGetTime(Redis *redis, struct timespec *t);
int redisxCheckRESP(const RESP *resp, char expectedType, int expectedSize);
int redisxCheckDestroyRESP(RESP *resp, char expectedType, int expectedSize);
void redisxDestroyRESP(RESP *resp);
boolean redisxIsScalarType(const RESP *r);
boolean redisxIsStringType(const RESP *r);
boolean redisxIsArrayType(const RESP *r);
boolean redisxIsMapType(const RESP *r);
boolean redisxIsEqualRESP(const RESP *a, const RESP *b);
int redisxSplitText(RESP *resp, char **text);

RedisMapEntry *redisxGetMapEntry(const RESP *map, const RESP *key);
RedisMapEntry *redisxGetKeywordEntry(const RESP *map, const char *key);

// Locks for async calls
int redisxLockClient(RedisClient *cl);
Expand Down
Loading

0 comments on commit 94ae01a

Please sign in to comment.