Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64da57e0c7 | ||
|
|
91717bd15c | ||
|
|
f12d52c1ed |
@ -11,6 +11,11 @@ MACRO(getVersionBit name)
|
||||
STRING(REGEX REPLACE ${VERSION_REGEX} "\\1" ${name} "${VERSION_BIT}")
|
||||
ENDMACRO(getVersionBit)
|
||||
|
||||
IF (${CMAKE_C_COMPILER_ID} MATCHES "GNU" OR ${CMAKE_C_COMPILER_ID} MATCHES "Clang")
|
||||
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
|
||||
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
|
||||
ENDIF()
|
||||
|
||||
getVersionBit(HIREDIS_MAJOR)
|
||||
getVersionBit(HIREDIS_MINOR)
|
||||
getVersionBit(HIREDIS_PATCH)
|
||||
@ -75,3 +80,8 @@ ENDIF()
|
||||
IF(ENABLE_EXAMPLES)
|
||||
ADD_SUBDIRECTORY(examples)
|
||||
ENDIF(ENABLE_EXAMPLES)
|
||||
|
||||
IF(ENABLE_TESTS)
|
||||
ADD_SUBDIRECTORY(contrib/gtest)
|
||||
ADD_SUBDIRECTORY(cpptests)
|
||||
ENDIF()
|
||||
17
cpptests/CMakeLists.txt
Normal file
17
cpptests/CMakeLists.txt
Normal file
@ -0,0 +1,17 @@
|
||||
INCLUDE(CTest)
|
||||
INCLUDE_DIRECTORIES(${gtest_SOURCE_DIR}/include)
|
||||
INCLUDE_DIRECTORIES(PROJECT_SOURCE_DIR)
|
||||
INCLUDE_DIRECTORIES(${LIBEVENT_INCLUDES})
|
||||
|
||||
ADD_EXECUTABLE(hiredis-gtest t_basic.cpp t_client.cpp t_async.cpp
|
||||
common.cpp main.cpp)
|
||||
TARGET_LINK_LIBRARIES(hiredis-gtest gtest hiredis event)
|
||||
ADD_TEST(NAME hiredis-test COMMAND hiredis-test)
|
||||
SET_PROPERTY(TARGET hiredis-gtest PROPERTY CXX_STANDARD 11)
|
||||
|
||||
SET(EXAMPLE_CERT "${CMAKE_CURRENT_SOURCE_DIR}/example.crt")
|
||||
SET(EXAMPLE_KEY "${CMAKE_CURRENT_SOURCE_DIR}/example.key")
|
||||
|
||||
ADD_DEFINITIONS(-DHIREDIS_TEST_SSL_CA="${EXAMPLE_CERT}")
|
||||
ADD_DEFINITIONS(-DHIREDIS_TEST_SSL_KEY="${EXAMPLE_KEY}")
|
||||
ADD_DEFINITIONS(-DHIREDIS_TEST_SSL_CERT="${EXAMPLE_CERT}")
|
||||
106
cpptests/common.cpp
Normal file
106
cpptests/common.cpp
Normal file
@ -0,0 +1,106 @@
|
||||
#include "common.h"
|
||||
#include <sstream>
|
||||
|
||||
hiredis::ClientSettings hiredis::settings_g;
|
||||
|
||||
using namespace hiredis;
|
||||
|
||||
void ClientSettings::setHost(const char *s) {
|
||||
m_mode = REDIS_CONN_TCP;
|
||||
std::string hostval(s);
|
||||
size_t idx = hostval.find(':');
|
||||
if (idx == std::string::npos) {
|
||||
// First part is hostname only
|
||||
m_hostname = hostval;
|
||||
} else {
|
||||
m_port = atoi(hostval.c_str() + idx + 1);
|
||||
hostval.resize(idx);
|
||||
}
|
||||
if (!m_port) {
|
||||
m_port = 6379;
|
||||
}
|
||||
m_hostname = hostval;
|
||||
}
|
||||
|
||||
void ClientSettings::setUnix(const char *s) {
|
||||
m_mode = REDIS_CONN_UNIX;
|
||||
m_hostname = s;
|
||||
}
|
||||
|
||||
void ClientSettings::applyEnv() {
|
||||
std::string hostval;
|
||||
if (getenv("REDIS_SOCKET")) {
|
||||
m_hostname.assign(getenv("REDIS_SOCKET"));
|
||||
m_mode = REDIS_CONN_UNIX;
|
||||
return;
|
||||
}
|
||||
|
||||
if (getenv("REDIS_HOST")) {
|
||||
setHost(getenv("REDIS_HOST"));
|
||||
}
|
||||
|
||||
// Handle SSL settings as well
|
||||
if (getenv("REDIS_SSL_CLIENT_CERT")) {
|
||||
m_ssl_cert_path = getenv("REDIS_SSL_CLIENT_CERT");
|
||||
}
|
||||
if (getenv("REDIS_SSL_CLIENT_KEY")) {
|
||||
m_ssl_key_path = getenv("REDIS_SSL_CLIENT_KEY");
|
||||
}
|
||||
if (getenv("REDIS_SSL_CA")) {
|
||||
m_ssl_ca_path = getenv("REDIS_SSL_CA");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ClientSettings::initOptions(redisOptions& options) const {
|
||||
if (m_mode == REDIS_CONN_TCP) {
|
||||
REDIS_OPTIONS_SET_TCP(&options, hostname(), port());
|
||||
} else if (options.type == REDIS_CONN_UNIX) {
|
||||
REDIS_OPTIONS_SET_UNIX(&options, hostname());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ConnectError::ConnectError(const redisOptions& options) {
|
||||
if (options.type == REDIS_CONN_TCP) {
|
||||
endpoint = options.endpoint.tcp.ip;
|
||||
endpoint += ":";
|
||||
endpoint += options.endpoint.tcp.port;
|
||||
} else if (options.type == REDIS_CONN_UNIX) {
|
||||
endpoint = "unix://";
|
||||
endpoint += options.endpoint.unix_socket;
|
||||
}
|
||||
}
|
||||
|
||||
void ClientError::throwCode(int code) {
|
||||
switch (code) {
|
||||
case REDIS_ERR_IO:
|
||||
throw IOError();
|
||||
case REDIS_ERR_EOF:
|
||||
throw IOError("EOF");
|
||||
case REDIS_ERR_PROTOCOL:
|
||||
throw IOError("Protocol Error");
|
||||
case REDIS_ERR_TIMEOUT:
|
||||
throw TimeoutError();
|
||||
default: {
|
||||
std::stringstream ss;
|
||||
ss << "unknown error code: ";
|
||||
ss << code;
|
||||
throw ClientError(ss.str().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClientError::throwContext(const redisContext *c) {
|
||||
const char *s;
|
||||
switch (c->err) {
|
||||
case REDIS_ERR_IO:
|
||||
case REDIS_ERR_EOF:
|
||||
case REDIS_ERR_PROTOCOL:
|
||||
throw IOError(c->errstr);
|
||||
case REDIS_ERR_TIMEOUT:
|
||||
throw TimeoutError();
|
||||
default:
|
||||
throw ClientError(c->errstr);
|
||||
}
|
||||
}
|
||||
105
cpptests/common.h
Normal file
105
cpptests/common.h
Normal file
@ -0,0 +1,105 @@
|
||||
#ifndef HIREDIS_CPP_COMMON_H
|
||||
#define HIREDIS_CPP_COMMON_H
|
||||
|
||||
#include <string>
|
||||
#include <cstdlib>
|
||||
#include <stdexcept>
|
||||
#include "hiredis.h"
|
||||
|
||||
namespace hiredis {
|
||||
class ClientSettings {
|
||||
public:
|
||||
void applyEnv();
|
||||
void setHost(const char *s);
|
||||
void setUnix(const char *s);
|
||||
void setSsl(bool v) { m_ssl_enabled = v; }
|
||||
|
||||
const char *ssl_cert() const { return m_ssl_cert_path; }
|
||||
const char *ssl_key() const { return m_ssl_key_path; }
|
||||
const char *ssl_ca() const { return m_ssl_ca_path; }
|
||||
|
||||
bool is_ssl() const {
|
||||
return m_ssl_enabled;
|
||||
}
|
||||
|
||||
bool is_unix() const {
|
||||
return m_mode == REDIS_CONN_UNIX;
|
||||
}
|
||||
|
||||
const char *hostname() const {
|
||||
return m_hostname.c_str();
|
||||
}
|
||||
uint16_t port() const {
|
||||
return m_port;
|
||||
}
|
||||
int mode() const {
|
||||
return m_mode;
|
||||
}
|
||||
|
||||
void initOptions(redisOptions& options) const;
|
||||
|
||||
std::string m_hostname = "localhost";
|
||||
uint16_t m_port = 6379;
|
||||
|
||||
int m_mode = REDIS_CONN_TCP;
|
||||
const char *m_ssl_cert_path = NULL;
|
||||
const char *m_ssl_ca_path = NULL;
|
||||
const char *m_ssl_key_path = NULL;
|
||||
bool m_ssl_enabled = false;
|
||||
};
|
||||
|
||||
extern ClientSettings settings_g;
|
||||
|
||||
class ClientError : public std::runtime_error {
|
||||
public:
|
||||
ClientError() : std::runtime_error("hiredis error") {
|
||||
}
|
||||
ClientError(const char *s) : std::runtime_error(s) {
|
||||
}
|
||||
static void throwCode(int code);
|
||||
static void throwContext(const redisContext *ac);
|
||||
};
|
||||
|
||||
class ConnectError : public ClientError {
|
||||
public:
|
||||
ConnectError() : ClientError(){}
|
||||
ConnectError(const redisOptions& options);
|
||||
virtual const char *what() const noexcept override{
|
||||
return endpoint.c_str();
|
||||
}
|
||||
private:
|
||||
std::string endpoint;
|
||||
};
|
||||
|
||||
class IOError : public ClientError {
|
||||
public:
|
||||
IOError() : ClientError(){}
|
||||
IOError(const char *s) : ClientError(s) {}
|
||||
};
|
||||
|
||||
class TimeoutError : public ClientError {
|
||||
public:
|
||||
TimeoutError() : ClientError("timed out") {}
|
||||
TimeoutError(const char *s) : ClientError(s) {}
|
||||
};
|
||||
|
||||
class SSLError : public ClientError {
|
||||
public:
|
||||
SSLError() : ClientError() {}
|
||||
SSLError(const char *what) : ClientError(what) {}
|
||||
};
|
||||
|
||||
class CommandError : public ClientError {
|
||||
public:
|
||||
CommandError(const redisReply *r) {
|
||||
errstr = r->str;
|
||||
}
|
||||
virtual const char *what() const noexcept override {
|
||||
return errstr.c_str();
|
||||
}
|
||||
private:
|
||||
std::string errstr;
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
||||
30
cpptests/example.crt
Normal file
30
cpptests/example.crt
Normal file
@ -0,0 +1,30 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFJjCCAw6gAwIBAgIJALM3AMcxMe0VMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNV
|
||||
BAMTC2V4YW1wbGUuY29tMB4XDTE4MDUwNzExNTM1MloXDTI4MDUwNDExNTM1Mlow
|
||||
FjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
|
||||
ggIKAoICAQCyNzRDE8b+COhBYZNiFnMeuF6mXRnd/jaK0axpKhRNx8JG2xGkmzq9
|
||||
Fse74dBbH/mmFwgpnOmRL2QK+qStUws6PQ8fk9fw3ntTQvMccCJqO/Ns1Rd3oFCt
|
||||
kL5q3VMW3lYn/ZTZ9jaFfRRdOkfACkDUQ3ff2/8QVU+BFNL47GyVPiAQeN5efjMj
|
||||
XSpE3aX3pn468wElO3JMxrUtE/pOg/TvlX4oDOJ9n56RhdpFU5rnvcdzxIMUgBih
|
||||
9XRymPj/0aD9UCq5++6CQ0U0MP4tcCe6iMQsHOfReUPH81pyMVpox/MBUAHPXKqd
|
||||
EePWz+OeEJfT8CeDt41DF6kEBledM41IYirlB/mCoV8TDnQYVmLIGyTiDQUsoGx/
|
||||
iZuYm+6bszdtgVK0ofBYDNgrY4VnC85YXg9sr+2fpZrNqzPY3W1esHdmdfQHP/Ff
|
||||
Q1pDVz8jWhWKwEE8QSpw9tRzL2U6hDdAxdmxt6h35dZvJF30ZfLylX16KDNLcN2E
|
||||
qRm/UTjjs06rzPGEK6rRuOgJ2ixlHzK2mZFaAD22THn1+H4QXOkylqAbGXemryrF
|
||||
ESLtCSskOuacfz1bUoWXa0a9pI67vilvagO8/Yc73rqtYLrTnHEwmFDd2X33UryB
|
||||
NADSV/a4VCIoOvThAbhZIaRAPCi7nn+hzu62myoVg6KDYLGTk7v4gQIDAQABo3cw
|
||||
dTAdBgNVHQ4EFgQUOYtIaf/4JJK9IBtulHRfJSEa2G8wRgYDVR0jBD8wPYAUOYtI
|
||||
af/4JJK9IBtulHRfJSEa2G+hGqQYMBYxFDASBgNVBAMTC2V4YW1wbGUuY29tggkA
|
||||
szcAxzEx7RUwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAgXe3OTSB
|
||||
Xma+IU9MtZChAf4p6n+5magLRVeraImKeFFrk2euPcQ0FxzpwA/GlhzDGLLUIKcB
|
||||
NnPOhZ3TVZTbzPhVSeBkFgpM/D+RnYnd2TLuVbubRynqKPPIa01hnI5UPodeQnuR
|
||||
1p+kLXEcQvUj7eSipYOEIO78cy/1QKZq8Jb6qosgM1w140qthFEqrgg3ewgNHbB+
|
||||
SLOxUGIbVfi5pdWiekGwcR3nyaJdyq3Dvq65Rt6wfzfX21FZI7VGs5NPzA/TdRIX
|
||||
uwTNe0Rif+4mbAah5SZlY9J/rKY+s41EdGPY24+ZywlyLgGgeRF2LaNu5iqG2FiQ
|
||||
h/EvpFzZQttSFTNhfMwZZg0pbQ2kpG53P7Y61rtuTHI0ykTb+Ot/972DefP0jLUl
|
||||
+SCuUHSb6OTlSddZt4zvxVgSOftrSLMl6PQpQZLLtxWvmFpWHTTpHEoRAXLv41bo
|
||||
JEVNZNO+L0pVWCOWyqn/4A4umtXWRzU8qXxed7mVNbpLFj9EYFChdcJ7OO9gEW05
|
||||
RJZrzEqd0Moh0na9ol9qQmc52Z/27ex+hh6ZrF7vVjUzWUMwIgFKx7/ELL9BNBN3
|
||||
ir6hkQpH6lNA71MgkW6aUuo2exZwffpjo2/cydwcdieNyP1kHwakN6ClwCYcFZpd
|
||||
LPKxSkVpeDRSDNFyYYUOTY4BJVbwnxy5Kto=
|
||||
-----END CERTIFICATE-----
|
||||
51
cpptests/example.key
Normal file
51
cpptests/example.key
Normal file
@ -0,0 +1,51 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIJJwIBAAKCAgEAsjc0QxPG/gjoQWGTYhZzHrhepl0Z3f42itGsaSoUTcfCRtsR
|
||||
pJs6vRbHu+HQWx/5phcIKZzpkS9kCvqkrVMLOj0PH5PX8N57U0LzHHAiajvzbNUX
|
||||
d6BQrZC+at1TFt5WJ/2U2fY2hX0UXTpHwApA1EN339v/EFVPgRTS+OxslT4gEHje
|
||||
Xn4zI10qRN2l96Z+OvMBJTtyTMa1LRP6ToP075V+KAzifZ+ekYXaRVOa573Hc8SD
|
||||
FIAYofV0cpj4/9Gg/VAqufvugkNFNDD+LXAnuojELBzn0XlDx/NacjFaaMfzAVAB
|
||||
z1yqnRHj1s/jnhCX0/Ang7eNQxepBAZXnTONSGIq5Qf5gqFfEw50GFZiyBsk4g0F
|
||||
LKBsf4mbmJvum7M3bYFStKHwWAzYK2OFZwvOWF4PbK/tn6Wazasz2N1tXrB3ZnX0
|
||||
Bz/xX0NaQ1c/I1oVisBBPEEqcPbUcy9lOoQ3QMXZsbeod+XWbyRd9GXy8pV9eigz
|
||||
S3DdhKkZv1E447NOq8zxhCuq0bjoCdosZR8ytpmRWgA9tkx59fh+EFzpMpagGxl3
|
||||
pq8qxREi7QkrJDrmnH89W1KFl2tGvaSOu74pb2oDvP2HO966rWC605xxMJhQ3dl9
|
||||
91K8gTQA0lf2uFQiKDr04QG4WSGkQDwou55/oc7utpsqFYOig2Cxk5O7+IECAwEA
|
||||
AQKCAgAFca5FBkuj4v3FUYfBDVKC87rgdiOeJm/gGbucks5/+cQziemmD5/hutpr
|
||||
IODOh9GGg1mae9KevsXdl/6D1O+Y3diibE/Cael2h6sJiVtjx2UORAwteVY9lxha
|
||||
B1zMbApRumtbpSvRNBr1Jhye/zEvysfUrNHD2/dLyCkRtZczj+xG1IpmdJB3Whc6
|
||||
d1Lkl89vWZEFZCV/tuo98EhLMbi/wN9TteENWVzssRwT1hP7VE7NeIlQjQEzoV3n
|
||||
SkkA95RlaJeFiu6kSA3LJFv/Y1ezWQ41EsT6Hyw05Xgz2NIcNU91EM6dWQVVOwCs
|
||||
xTj80SDyNnneijLkg8qD9vWiNbrxGA69l+3ZzG252KariKWL+tQhK5ugDGJlZ7xR
|
||||
BanyIq3GEVMreWehViHX8XwDcNQtn9qtYqmMz7+E7X0/urwkBfiLfoid6U0GYlMS
|
||||
yQ4PQPv7kV97FKVsPin9vucg9Sl7xcZEOmJO25TDq2/oPSzLU+tGaYreYKjEdAJ7
|
||||
XRe7aOv1C/+16mo+aMuwVRbwGcsVeGqXuAEDm7TFY4eRSbuPvAa3WnMHG/vqKSig
|
||||
5MpTwfrQmh7S1DMJ6DiNBtf0OebTMeIedcagGymmhANlV5Kg42+817ct1AWPlae+
|
||||
vs3bDTtUpS/iX8PYhidkAPqfJrESYgk6W12C5pz9d3zlXJFDSQKCAQEA3vzACnIE
|
||||
vziTr6pZMQNNHaIQJaN7o4snqUXqxz+K6ZAhGxxIxhU956GRDrhU/kgOLAoc+mLV
|
||||
PmGfLO72/g4Pxs7NM07zT3JBOmc9vvQfr9jkGUu00GHToba2IlBIBOcQXOl/nFtN
|
||||
NtfV5N7YgBJRceP/TbsKuooq0ad2vwqFTVbEKprwXhoHu3OlISY6Z2wV+eP7fqbs
|
||||
TfKvqhAPADkARmwBXYljuC5ORgDGffes+QXtwANB9vHHyN2oh7I2aY1E/ylyYhvJ
|
||||
ISrLLVIw7Jdzcv4WZvNQILgP6xwGF+ljPCSWxV8mTsw9xS1T2BU2Ij8gfy5k96TO
|
||||
nhxGUIWTZQfULwKCAQEAzJmaCRvbtLBO/IQpHIocD6elsZLWlLxJiaxHiR2SoDwL
|
||||
z609fuElLDkmkKaXMBlI6ZtGVMBO9+6scjn8N11rzCzjCFIo68X/r0CgTcNuLUaN
|
||||
7WkRRT4krIz/r5+UFys+DyrCxlNmzDKcSowxf3KYWkBwQ2q1ZFdM9HtQjbGAVCWv
|
||||
4zNLqsOTo60GQOrn4AgxLuLNbES+pj+Gtymd94+Hh2L7Jsh3xkOK3CrW02uo1Rz2
|
||||
v0znGd44Ep+m9uLHE0LFTs7+qh00nyjcGx/UsF2k138+LsRA//g3ljbXLU5PVAqg
|
||||
e2Y3jAVjeyouEecBWncY04Jg6FUpuc5ZIkiJEwDiTwKCAQAHf8CFmWgHdkOhOL1f
|
||||
JJlHUdfxLBpQWbGvw6YtpTlquWojm0PnRXebfpd+Qzy4gHvZh02KLiC1xFqyDCdK
|
||||
S/bD3NiRzSnplhITgL1W2qbmJwkkwKMIDwIrAhYF+WUypQKr3T5pZ7ilC4Up+USW
|
||||
qgcLKXvAhXXK6DKgcl1P926cNzrJpARJZd60syLuhnaYW84xZTVkAQEZbfvyYC/g
|
||||
9gnIVIGHP8OWwhfnysbiHZ43kbd5KaLiRydM3gd46Mljq1iSrDYojn6pGuNSVt1G
|
||||
V6GOUHU4aR5cu1PtuDeMPlEUCLb5VEXZiIzbQLb9IVl8tVrGbC0BFw3Ly2+h7ZwT
|
||||
XbwJAoIBACokfVjg9xk5s5tJsZoiTHNhCb0QzMgoHFGSPc3dXIVKuPgW6/LFdz2r
|
||||
q+jhl4SdwKn1hMASOHHTJIwGq4/P21Nb74uYOLuPtgGoIxzBY2FKBhPfr2H+0dkE
|
||||
1embygoXqxm+qg3lwefPiOfGBrAEr6LvYPBR+3jmjoBRIh99bzxl4tu+hhhvXmq5
|
||||
Se93MzmvFkpdBwkFA+wEa8Awf0wtsTHOzoKHijw5T1HYNRWpOEZlR+HRekyWvCAB
|
||||
6Icz4ONzvmZkNopdp6gc53Fi1hFZyIlmuS0y2VygCPsU4q9/UNGzuqiQPmLF/V6y
|
||||
KnkfhuTWYTO3yDQyznxqJ2vrWuiiJvsCggEAHYPDIWvhZPC0ZO13r3HO/lc7YTU1
|
||||
XGSCzAh/oyVLgUCqYw5t2F6maduwSia3z+g3BecutYk+tkmcBsrtSVE5GZ93VDGQ
|
||||
1rj3Yulr8tnXC8HaaNsU227Js+J9ZP8CLQPMF0HbMESPp0cxIPVTsOb8K/Oa6e5h
|
||||
rVxqpuLbw/3SMtqWN9fGbXAkPoaLhsueWK9nB03+fVg7P7ftJ+C3g16eXGEnPrg5
|
||||
DmdzpbnBs2WfZNSLkrIY8bZ7Ou+k3astMM2tFSEpFblMP44WmqR5GFtvMzDl1w4S
|
||||
2KdS1mqDExX2lu9xatAApHykVjMJ+haBHkew7et90JsckKeDmQJ6p+Vrpg==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
50
cpptests/main.cpp
Normal file
50
cpptests/main.cpp
Normal file
@ -0,0 +1,50 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <tuple>
|
||||
#include <gtest/gtest.h>
|
||||
#include "common.h"
|
||||
|
||||
using namespace hiredis;
|
||||
|
||||
static std::string getKvValue(const std::string& s) {
|
||||
size_t n = s.find('=');
|
||||
if (n == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
return s.substr(n+1);
|
||||
}
|
||||
|
||||
static bool isArg(const char *arg, const char *s) {
|
||||
return strncasecmp(arg, s, strlen(s)) == 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
ClientSettings* settings = &settings_g;
|
||||
|
||||
#ifdef HIREDIS_TEST_SSL_CA
|
||||
printf("Setting SSL compile time defaults\n");
|
||||
settings->m_ssl_ca_path = HIREDIS_TEST_SSL_CA;
|
||||
settings->m_ssl_cert_path = HIREDIS_TEST_SSL_CERT;
|
||||
settings->m_ssl_key_path = HIREDIS_TEST_SSL_KEY;
|
||||
#endif
|
||||
|
||||
for (size_t ii = 1; ii < argc; ++ii) {
|
||||
const char *ss = argv[ii];
|
||||
if (isArg(ss, "--unix")) {
|
||||
settings->setUnix(getKvValue(ss).c_str());
|
||||
} else if (isArg(ss, "--host")) {
|
||||
settings->setHost(getKvValue(ss).c_str());
|
||||
printf("Set host to %s:%u\n", settings->m_hostname.c_str(), settings->m_port);
|
||||
} else if (isArg(ss, "--ssl")) {
|
||||
auto v = getKvValue(ss);
|
||||
if (v == "0" || v == "false") {
|
||||
settings->setSsl(false);
|
||||
} else {
|
||||
printf("enabling ssl for tests\n");
|
||||
settings->setSsl(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
3
cpptests/run_test.py
Normal file
3
cpptests/run_test.py
Normal file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
import argparse
|
||||
import subprocess
|
||||
128
cpptests/stunnel.conf
Normal file
128
cpptests/stunnel.conf
Normal file
@ -0,0 +1,128 @@
|
||||
; Sample stunnel configuration file for Unix by Michal Trojnara 1998-2018
|
||||
; Some options used here may be inadequate for your particular configuration
|
||||
; This sample file does *not* represent stunnel.conf defaults
|
||||
; Please consult the manual for detailed description of available options
|
||||
|
||||
; **************************************************************************
|
||||
; * Global options *
|
||||
; **************************************************************************
|
||||
|
||||
; It is recommended to drop root privileges if stunnel is started by root
|
||||
;setuid = nobody
|
||||
;setgid = nogroup
|
||||
|
||||
; PID file is created inside the chroot jail (if enabled)
|
||||
;pid = /usr/local/var/run/stunnel.pid
|
||||
|
||||
; Debugging stuff (may be useful for troubleshooting)
|
||||
foreground = yes
|
||||
debug = debug
|
||||
;output = /usr/local/var/log/stunnel.log
|
||||
|
||||
; Enable FIPS 140-2 mode if needed for compliance
|
||||
;fips = yes
|
||||
|
||||
; The pkcs11 engine allows for authentication with cryptographic
|
||||
; keys isolated in a hardware or software token
|
||||
; MODULE_PATH specifies the path to the pkcs11 module shared library,
|
||||
; e.g. softhsm2.dll or opensc-pkcs11.so
|
||||
; Each section using this feature also needs the "engineId = pkcs11" option
|
||||
;engine = pkcs11
|
||||
;engineCtrl = MODULE_PATH:/usr/lib/softhsm/libsofthsm2.so
|
||||
;engineCtrl = PIN:1234
|
||||
|
||||
; **************************************************************************
|
||||
; * Service defaults may also be specified in individual service sections *
|
||||
; **************************************************************************
|
||||
|
||||
; Enable support for the insecure SSLv3 protocol
|
||||
delay = yes
|
||||
options = NO_SSLv3
|
||||
options = NO_TLSv1
|
||||
options = CIPHER_SERVER_PREFERENCE
|
||||
options = DONT_INSERT_EMPTY_FRAGMENTS
|
||||
|
||||
|
||||
; These options provide additional security at some performance degradation
|
||||
;options = SINGLE_ECDH_USE
|
||||
;options = SINGLE_DH_USE
|
||||
|
||||
; **************************************************************************
|
||||
; * Include all configuration file fragments from the specified folder *
|
||||
; **************************************************************************
|
||||
|
||||
;include = /usr/local/etc/stunnel/conf.d
|
||||
|
||||
; **************************************************************************
|
||||
; * Service definitions (remove all services for inetd mode) *
|
||||
; **************************************************************************
|
||||
|
||||
; ***************************************** Example TLS client mode services
|
||||
|
||||
; The following examples use /etc/ssl/certs, which is the common location
|
||||
; of a hashed directory containing trusted CA certificates. This is not
|
||||
; a hardcoded path of the stunnel package, as it is not related to the
|
||||
; stunnel configuration in /usr/local/etc/stunnel/.
|
||||
|
||||
[redis]
|
||||
;client = yes
|
||||
accept = 16379
|
||||
connect = 6379
|
||||
cert = example.crt
|
||||
key = example.key
|
||||
|
||||
; Encrypted HTTP proxy authenticated with a client certificate
|
||||
; located in a cryptographic token
|
||||
;[example-pkcs11]
|
||||
;client = yes
|
||||
;accept = 127.0.0.1:8080
|
||||
;connect = example.com:8443
|
||||
;engineId = pkcs11
|
||||
;cert = pkcs11:token=MyToken;object=MyCert
|
||||
;key = pkcs11:token=MyToken;object=MyKey
|
||||
|
||||
; ***************************************** Example TLS server mode services
|
||||
|
||||
;[pop3s]
|
||||
;accept = 995
|
||||
;connect = 110
|
||||
;cert = /usr/local/etc/stunnel/stunnel.pem
|
||||
|
||||
;[imaps]
|
||||
;accept = 993
|
||||
;connect = 143
|
||||
;cert = /usr/local/etc/stunnel/stunnel.pem
|
||||
|
||||
; Either only expose this service to trusted networks, or require
|
||||
; authentication when relaying emails originated from loopback.
|
||||
; Otherwise the following configuration creates an open relay.
|
||||
;[ssmtp]
|
||||
;accept = 465
|
||||
;connect = 25
|
||||
;cert = /usr/local/etc/stunnel/stunnel.pem
|
||||
|
||||
; TLS front-end to a web server
|
||||
;[https]
|
||||
;accept = 443
|
||||
;connect = 80
|
||||
;cert = /usr/local/etc/stunnel/stunnel.pem
|
||||
; "TIMEOUTclose = 0" is a workaround for a design flaw in Microsoft SChannel
|
||||
; Microsoft implementations do not use TLS close-notify alert and thus they
|
||||
; are vulnerable to truncation attacks
|
||||
;TIMEOUTclose = 0
|
||||
|
||||
; Remote shell protected with PSK-authenticated TLS
|
||||
; Create "/usr/local/etc/stunnel/secrets.txt" containing IDENTITY:KEY pairs
|
||||
;[shell]
|
||||
;accept = 1337
|
||||
;exec = /bin/sh
|
||||
;execArgs = sh -i
|
||||
;PSKsecrets = /usr/local/etc/stunnel/secrets.txt
|
||||
|
||||
; Non-standard MySQL-over-TLS encapsulation connecting the Unix socket
|
||||
;[mysql]
|
||||
;cert = /usr/local/etc/stunnel/stunnel.pem
|
||||
;accept = 3307
|
||||
;connect = /run/mysqld/mysqld.sock
|
||||
|
||||
; vim:ft=dosini
|
||||
164
cpptests/t_async.cpp
Normal file
164
cpptests/t_async.cpp
Normal file
@ -0,0 +1,164 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <hiredis.h>
|
||||
#include "adapters/libevent.h"
|
||||
#include <functional>
|
||||
#include <cstdarg>
|
||||
#include "common.h"
|
||||
|
||||
using namespace hiredis;
|
||||
|
||||
struct AsyncClient;
|
||||
typedef std::function<void(AsyncClient*, bool)> ConnectionCallback;
|
||||
typedef std::function<void(AsyncClient*, bool)> DisconnectCallback;
|
||||
typedef std::function<void(AsyncClient*, redisReply *)> CommandCallback;
|
||||
|
||||
static void realConnectCb(const redisAsyncContext*, int);
|
||||
static void realDisconnectCb(const redisAsyncContext*, int);
|
||||
static void realCommandCb(redisAsyncContext*, void*, void*);
|
||||
|
||||
struct CmdData {
|
||||
AsyncClient *client;
|
||||
CommandCallback cb;
|
||||
};
|
||||
|
||||
struct AsyncClient {
|
||||
void connectCommon(const redisOptions& orig, event_base *b, unsigned timeoutMs) {
|
||||
redisOptions options = orig;
|
||||
struct timeval tv = { 0 };
|
||||
if (timeoutMs) {
|
||||
tv.tv_usec = timeoutMs * 1000;
|
||||
options.timeout = &tv;
|
||||
}
|
||||
|
||||
ac = redisAsyncConnectWithOptions(&options);
|
||||
ac->data = this;
|
||||
redisLibeventAttach(ac, b);
|
||||
redisAsyncSetConnectCallback(ac, realConnectCb);
|
||||
|
||||
}
|
||||
|
||||
AsyncClient(const redisOptions& options, event_base* b, unsigned timeoutMs = 0) {
|
||||
connectCommon(options, b, timeoutMs);
|
||||
}
|
||||
|
||||
AsyncClient(const ClientSettings& settings, event_base *b, unsigned timeoutMs = 0) {
|
||||
redisOptions options = { 0 };
|
||||
settings.initOptions(options);
|
||||
connectCommon(options, b, timeoutMs);
|
||||
|
||||
if (ac->c.err != REDIS_OK) {
|
||||
ClientError::throwContext(&ac->c);
|
||||
}
|
||||
|
||||
if (settings.is_ssl()) {
|
||||
printf("Securing async connection...\n");
|
||||
int rc = redisSecureConnection(&ac->c,
|
||||
settings.ssl_ca(), settings.ssl_cert(), settings.ssl_key(),
|
||||
NULL);
|
||||
if (rc != REDIS_OK) {
|
||||
throw SSLError(ac->c.errstr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AsyncClient(redisAsyncContext *ac) : ac(ac) {
|
||||
redisAsyncSetDisconnectCallback(ac, realDisconnectCb);
|
||||
}
|
||||
|
||||
void onConnect(ConnectionCallback cb) {
|
||||
conncb = cb;
|
||||
}
|
||||
|
||||
~AsyncClient() {
|
||||
if (ac != NULL) {
|
||||
auto tmpac = ac;
|
||||
ac = NULL;
|
||||
redisAsyncDisconnect(tmpac);
|
||||
}
|
||||
}
|
||||
|
||||
void cmd(CommandCallback cb, const char *fmt, ...) {
|
||||
auto data = new CmdData {this, cb };
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
redisvAsyncCommand(ac, realCommandCb, data, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void disconnect(DisconnectCallback cb) {
|
||||
disconncb = cb;
|
||||
auto tmpac = ac;
|
||||
ac = NULL;
|
||||
redisAsyncDisconnect(tmpac);
|
||||
}
|
||||
|
||||
void disconnect() {
|
||||
auto tmpac = ac;
|
||||
ac = NULL;
|
||||
redisAsyncDisconnect(tmpac);
|
||||
}
|
||||
|
||||
ConnectionCallback conncb;
|
||||
DisconnectCallback disconncb;
|
||||
redisAsyncContext *ac;
|
||||
};
|
||||
|
||||
static void realConnectCb(const redisAsyncContext *ac, int status) {
|
||||
auto self = reinterpret_cast<AsyncClient*>(ac->data);
|
||||
if (self->conncb) {
|
||||
self->conncb(self, status == 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void realDisconnectCb(const redisAsyncContext *ac, int status) {
|
||||
auto self = reinterpret_cast<AsyncClient*>(ac->data);
|
||||
if (self->disconncb) {
|
||||
self->disconncb(self, status == 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void realCommandCb(redisAsyncContext *ac, void *r, void *ctx) {
|
||||
auto *d = reinterpret_cast<CmdData*>(ctx);
|
||||
auto *rep = reinterpret_cast<redisReply*>(r);
|
||||
auto *self = reinterpret_cast<AsyncClient*>(ac->data);
|
||||
d->cb(self, rep);
|
||||
delete d;
|
||||
}
|
||||
|
||||
class AsyncTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
libevent = event_base_new();
|
||||
}
|
||||
void TearDown() override {
|
||||
event_base_free(libevent);
|
||||
libevent = NULL;
|
||||
}
|
||||
void wait() {
|
||||
event_base_dispatch(libevent);
|
||||
}
|
||||
event_base *libevent;
|
||||
};
|
||||
|
||||
TEST_F(AsyncTest, testAsync) {
|
||||
AsyncClient client(settings_g, libevent, 1000);
|
||||
|
||||
bool gotConnect = false;
|
||||
bool gotCommand = false;
|
||||
client.onConnect([&](AsyncClient*, bool status) {
|
||||
ASSERT_TRUE(status);
|
||||
gotConnect = true;
|
||||
});
|
||||
|
||||
client.cmd([&](AsyncClient *c, redisReply *r) {
|
||||
ASSERT_TRUE(r != NULL);
|
||||
ASSERT_EQ(REDIS_REPLY_STATUS, r->type);
|
||||
ASSERT_STREQ("PONG", r->str);
|
||||
c->disconnect();
|
||||
gotCommand = true;
|
||||
}, "PING");
|
||||
wait();
|
||||
|
||||
ASSERT_TRUE(gotConnect);
|
||||
ASSERT_TRUE(gotCommand);
|
||||
}
|
||||
34
cpptests/t_basic.cpp
Normal file
34
cpptests/t_basic.cpp
Normal file
@ -0,0 +1,34 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdarg>
|
||||
#include "hiredis.h"
|
||||
|
||||
class FormatterTest : public ::testing::Test {
|
||||
};
|
||||
|
||||
|
||||
static std::string formatCommand(const char *fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
char *s = NULL;
|
||||
size_t n = redisvFormatCommand(&s, fmt, ap);
|
||||
va_end(ap);
|
||||
std::string xs(s, n);
|
||||
free(s);
|
||||
return xs;
|
||||
}
|
||||
|
||||
TEST_F(FormatterTest, testFormatCommands) {
|
||||
auto expected = "*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n";
|
||||
ASSERT_EQ(expected, formatCommand("SET foo bar"))
|
||||
<< "No interpolation";
|
||||
|
||||
expected = "*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n";
|
||||
ASSERT_EQ(expected, formatCommand("SET %s %s", "foo", "bar"))
|
||||
<< "interpolation";
|
||||
|
||||
expected = "*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n";
|
||||
ASSERT_EQ(expected, formatCommand("SET %s %s", "foo", ""))
|
||||
<< "empty string";
|
||||
}
|
||||
112
cpptests/t_client.cpp
Normal file
112
cpptests/t_client.cpp
Normal file
@ -0,0 +1,112 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include "hiredis.h"
|
||||
#include "common.h"
|
||||
|
||||
|
||||
using namespace hiredis;
|
||||
|
||||
class Client {
|
||||
public:
|
||||
operator redisContext*() {
|
||||
return ctx;
|
||||
}
|
||||
|
||||
Client(redisContext *ctx) :ctx(ctx) {
|
||||
}
|
||||
|
||||
Client(const redisOptions& options) {
|
||||
connectOrThrow(options);
|
||||
}
|
||||
|
||||
Client(const ClientSettings& settings) {
|
||||
redisOptions options = {0};
|
||||
settings.initOptions(options);
|
||||
connectOrThrow(options);
|
||||
if (settings.is_ssl()) {
|
||||
secureConnection(settings);
|
||||
}
|
||||
}
|
||||
|
||||
void secureConnection(const ClientSettings& settings) {
|
||||
if (redisSecureConnection(
|
||||
ctx, settings.ssl_ca(), settings.ssl_cert(),
|
||||
settings.ssl_key(), NULL) != REDIS_OK) {
|
||||
redisFree(ctx);
|
||||
ctx = NULL;
|
||||
throw SSLError();
|
||||
}
|
||||
}
|
||||
|
||||
redisReply *cmd(const char *fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
void *p = redisvCommand(ctx, fmt, ap);
|
||||
va_end(ap);
|
||||
return reinterpret_cast<redisReply*>(p);
|
||||
}
|
||||
|
||||
void flushdb() {
|
||||
redisReply *p = cmd("FLUSHDB");
|
||||
if (p == NULL) {
|
||||
ClientError::throwCode(ctx->err);
|
||||
}
|
||||
if (p->type == REDIS_REPLY_ERROR) {
|
||||
auto pp = CommandError(p);
|
||||
freeReplyObject(p);
|
||||
throw pp;
|
||||
}
|
||||
freeReplyObject(p);
|
||||
}
|
||||
|
||||
~Client() {
|
||||
if (ctx != NULL) {
|
||||
redisFree(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
Client(Client&& other) {
|
||||
this->ctx = other.ctx;
|
||||
other.ctx = NULL;
|
||||
}
|
||||
|
||||
void nothing() const {}
|
||||
|
||||
private:
|
||||
void destroyAndThrow() {
|
||||
assert(ctx->err);
|
||||
int err = ctx->err;
|
||||
redisFree(ctx);
|
||||
ctx = NULL;
|
||||
ClientError::throwCode(err);
|
||||
}
|
||||
void connectOrThrow(const redisOptions& options) {
|
||||
ctx = redisConnectWithOptions(&options);
|
||||
if (!ctx) {
|
||||
throw ConnectError();
|
||||
}
|
||||
if (ctx->err) {
|
||||
destroyAndThrow();
|
||||
}
|
||||
}
|
||||
redisContext *ctx;
|
||||
};
|
||||
|
||||
class ClientTest : public ::testing::Test {
|
||||
};
|
||||
|
||||
TEST_F(ClientTest, testTimeout) {
|
||||
redisOptions options = {0};
|
||||
timeval tv = {0};
|
||||
tv.tv_usec = 10000; // 10k micros, small enough
|
||||
options.timeout = &tv;
|
||||
// see https://tools.ietf.org/html/rfc5737
|
||||
// this block of addresses is reserved for "documentation", and it
|
||||
// would likely not connect, ever.
|
||||
ASSERT_THROW(Client(options).nothing(), ClientError);
|
||||
|
||||
// Test the normal timeout
|
||||
Client c(settings_g);
|
||||
c.flushdb();
|
||||
}
|
||||
23
get_gtest.sh
Executable file
23
get_gtest.sh
Executable file
@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
# Run this from the toplevel directory of the source code tree
|
||||
GTEST_URL_BASE=https://s3-eu-central-1.amazonaws.com/redislabs-dev-public-deps
|
||||
GTEST_URL_BASE=https://github.com/google/googletest/archive/
|
||||
GTEST_FILENAME=release-1.8.0.tar.gz
|
||||
GTEST_TOPDIR=googletest-release-1.8.0
|
||||
DESTDIR=contrib
|
||||
|
||||
if [ -d $DESTDIR/gtest ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
curdir=$PWD
|
||||
tarball=/tmp/${GTEST_FILENAME}
|
||||
url=${GTEST_URL_BASE}/${GTEST_FILENAME}
|
||||
if [ ! -e $tarball ]; then
|
||||
wget -O $tarball $url
|
||||
fi
|
||||
|
||||
tar -C $DESTDIR -xf $tarball
|
||||
rm $DESTDIR/gtest
|
||||
cd $DESTDIR
|
||||
ln -s $GTEST_TOPDIR gtest
|
||||
@ -685,8 +685,10 @@ redisContext *redisConnectWithOptions(const redisOptions *options) {
|
||||
return NULL;
|
||||
}
|
||||
if (options->timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) {
|
||||
if (!(options->options & REDIS_OPT_NORDWRTIMEOUT)) {
|
||||
redisContextSetTimeout(c, *options->timeout);
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
@ -703,6 +705,7 @@ redisContext *redisConnectWithTimeout(const char *ip, int port, const struct tim
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||
options.timeout = &tv;
|
||||
options.options |= REDIS_OPT_NORDWRTIMEOUT;
|
||||
return redisConnectWithOptions(&options);
|
||||
}
|
||||
|
||||
|
||||
@ -133,6 +133,13 @@ struct redisSsl;
|
||||
*/
|
||||
#define REDIS_OPT_NOAUTOFREE 0x04
|
||||
|
||||
/**
|
||||
* When using connectWithOptions, have the timeout only apply to the
|
||||
* initial connect, not subsequent read/write attempts. This option
|
||||
* is here to support the legacy connectWithTimeout()
|
||||
*/
|
||||
#define REDIS_OPT_NORDWRTIMEOUT 0x08
|
||||
|
||||
/* In Unix systems a file descriptor is a regular signed int, with -1
|
||||
* representing an invalid descriptor. In Windows it is a SOCKET
|
||||
* (32- or 64-bit unsigned integer depending on the architecture), where
|
||||
|
||||
Loading…
Reference in New Issue
Block a user